diff --git a/DEPS b/DEPS
index c8aac75..1697d07 100644
--- a/DEPS
+++ b/DEPS
@@ -144,11 +144,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': '5207f35f33b1ccc173cff02bd0a38223554ba6f2',
+  'skia_revision': '84bcd0c3ae08f2add3a3999cff784fe907b03c45',
   # 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': '12c0646785a3207f5cdd747038a9cf9910e2bab5',
+  'v8_revision': '109b30b38f8365b80ff4b7c563d19264789d6865',
   # 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.
@@ -156,7 +156,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ANGLE
   # and whatever else without interference from each other.
-  'angle_revision': 'cd369bb8a2518eaca7fbdb0936095d3b5d44b4ee',
+  'angle_revision': 'dd3de6f095c1a0e8b7c4fff579541287ab2f4fe6',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -207,7 +207,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '249e608964291a3db12869d6158b6df5679c7a22',
+  'catapult_revision': '282f4dfee653d0ecb74a111a57b501617f6eb99f',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -279,11 +279,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '4e3b218e9695b1f7011ff80319e75e14a35cf466',
+  'dawn_revision': '68d97adf88de11cd9c5b9e2f9a77d50ab985ede4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': 'ad0248681e65620726a9cd2fc22df10da8f85346',
+  'quiche_revision': '0b5bfaebe8ba794946fe335423c4013f2743ddee',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -298,7 +298,7 @@
   # revisions.
 
   # GN CIPD package version.
-  'gn_version': 'git_revision:972ed755f8e6d31cae9ba15fcd08136ae1a7886f',
+  'gn_version': 'git_revision:152c5144ceed9592c20f0c8fd55769646077569b',
 
   # Also, if you change these, update buildtools/DEPS too. Also update the
   # libc++ svn_revision in //buildtools/deps_revisions.gni.
@@ -1374,7 +1374,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'abaae129d9a0c6e1e092067e0b105475df43352e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '61689ab0630a4489549a67024d6bd4fb2c153295',
+    Var('webrtc_git') + '/src.git' + '@' + 'cfefa0aef329aac0206c5e56efd90b3b0bdb88b6',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1415,7 +1415,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0ecadc16190aacfd177863dc7c64c9d39e2a7502',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@59b2d77a1e398b1154bbcb0e022683d57ca6f5a2',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc
index cd47d1b..466cf5f 100644
--- a/android_webview/browser/aw_content_browser_client.cc
+++ b/android_webview/browser/aw_content_browser_client.cc
@@ -994,7 +994,7 @@
     if (content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)) {
       // Manages its own lifetime.
       new android_webview::AwProxyingURLLoaderFactory(
-          0 /* process_id */, std::move(request), nullptr, nullptr,
+          0 /* process_id */, std::move(request), nullptr,
           true /* intercept_only */);
     } else {
       base::PostTaskWithTraits(
@@ -1003,7 +1003,7 @@
               [](network::mojom::URLLoaderFactoryRequest request) {
                 // Manages its own lifetime.
                 new android_webview::AwProxyingURLLoaderFactory(
-                    0 /* process_id */, std::move(request), nullptr, nullptr,
+                    0 /* process_id */, std::move(request), nullptr,
                     true /* intercept_only */);
               },
               std::move(request)));
@@ -1069,8 +1069,7 @@
       FROM_HERE, {content::BrowserThread::IO},
       base::BindOnce(&AwProxyingURLLoaderFactory::CreateProxy, process_id,
                      std::move(proxied_receiver),
-                     std::move(target_factory_info),
-                     nullptr /* AwInterceptedRequestHandler */));
+                     std::move(target_factory_info)));
   return true;
 }
 
@@ -1088,8 +1087,7 @@
       FROM_HERE, {content::BrowserThread::IO},
       base::BindOnce(&AwProxyingURLLoaderFactory::CreateProxy,
                      render_process_id, std::move(factory_receiver),
-                     std::move(pending_proxy),
-                     nullptr /* AwInterceptedRequestHandler */));
+                     std::move(pending_proxy)));
 }
 
 uint32_t AwContentBrowserClient::GetWebSocketOptions(
diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
index e78f414..0efa517 100644
--- a/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
+++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory.cc
@@ -711,13 +711,10 @@
     int process_id,
     network::mojom::URLLoaderFactoryRequest loader_request,
     network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
-    std::unique_ptr<AwInterceptedRequestHandler> request_handler,
     bool intercept_only)
     : process_id_(process_id),
-      request_handler_(std::move(request_handler)),
       intercept_only_(intercept_only),
       weak_factory_(this) {
-  // actual creation of the factory
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(!(intercept_only_ && target_factory_info));
   if (target_factory_info) {
@@ -738,14 +735,12 @@
 void AwProxyingURLLoaderFactory::CreateProxy(
     int process_id,
     network::mojom::URLLoaderFactoryRequest loader_request,
-    network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
-    std::unique_ptr<AwInterceptedRequestHandler> request_handler) {
+    network::mojom::URLLoaderFactoryPtrInfo target_factory_info) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
   // will manage its own lifetime
   new AwProxyingURLLoaderFactory(process_id, std::move(loader_request),
-                                 std::move(target_factory_info),
-                                 std::move(request_handler), false);
+                                 std::move(target_factory_info), false);
 }
 
 void AwProxyingURLLoaderFactory::CreateLoaderAndStart(
diff --git a/android_webview/browser/network_service/aw_proxying_url_loader_factory.h b/android_webview/browser/network_service/aw_proxying_url_loader_factory.h
index 0a4cb7887..4a367eb 100644
--- a/android_webview/browser/network_service/aw_proxying_url_loader_factory.h
+++ b/android_webview/browser/network_service/aw_proxying_url_loader_factory.h
@@ -22,11 +22,32 @@
 
 namespace android_webview {
 
-class AwInterceptedRequestHandler {};
-
-// URL Loader Factory for android webview, for supporting request/response
-// interception, processing and callback invocation. Currently contains basic
-// pass-through implementation.
+// URL Loader Factory for Android WebView. This is the entry point for handling
+// Android WebView callbacks (i.e. error, interception and other callbacks) and
+// loading of android specific schemes and overridden responses.
+//
+// This class contains centralized logic for:
+//  - request interception and blocking,
+//  - setting load flags and headers,
+//  - loading requests depending on the scheme (e.g. different delegates are
+//    used for loading android assets/resources as compared to overridden
+//    responses).
+//  - handling errors (e.g. no input stream, redirect or safebrowsing related
+//    errors).
+//
+// In particular handles the following Android WebView callbacks:
+//  - shouldInterceptRequest
+//  - onReceivedError
+//  - onReceivedHttpError
+//  - onReceivedLoginRequest
+//
+// Threading:
+//  Currently the factory and the associated loader assume they live on the IO
+//  thread. This is also required by the shouldInterceptRequest callback (which
+//  should be called on a non-UI thread). The other callbacks (i.e.
+//  onReceivedError, onReceivedHttpError and onReceivedLoginRequest) are posted
+//  on the UI thread.
+//
 class AwProxyingURLLoaderFactory : public network::mojom::URLLoaderFactory {
  public:
   // Create a factory that will create specialized URLLoaders for Android
@@ -38,7 +59,6 @@
       int process_id,
       network::mojom::URLLoaderFactoryRequest loader_request,
       network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
-      std::unique_ptr<AwInterceptedRequestHandler> request_handler,
       bool intercept_only);
 
   ~AwProxyingURLLoaderFactory() override;
@@ -47,8 +67,7 @@
   static void CreateProxy(
       int process_id,
       network::mojom::URLLoaderFactoryRequest loader,
-      network::mojom::URLLoaderFactoryPtrInfo target_factory_info,
-      std::unique_ptr<AwInterceptedRequestHandler> request_handler);
+      network::mojom::URLLoaderFactoryPtrInfo target_factory_info);
 
   void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader,
                             int32_t routing_id,
@@ -69,10 +88,6 @@
   mojo::BindingSet<network::mojom::URLLoaderFactory> proxy_bindings_;
   network::mojom::URLLoaderFactoryPtr target_factory_;
 
-  // TODO(timvolodine): consider functionality to have multiple interception
-  // handlers operating in sequence.
-  std::unique_ptr<AwInterceptedRequestHandler> request_handler_;
-
   // When true the loader resulting from this factory will only execute
   // intercept callback (shouldInterceptRequest). If that returns without
   // a response, the loader will abort loading.
diff --git a/android_webview/lib/aw_main_delegate.cc b/android_webview/lib/aw_main_delegate.cc
index e0929a1..1dbb1ab 100644
--- a/android_webview/lib/aw_main_delegate.cc
+++ b/android_webview/lib/aw_main_delegate.cc
@@ -53,7 +53,6 @@
 #include "gpu/ipc/gl_in_process_context.h"
 #include "media/base/media_switches.h"
 #include "media/media_buildflags.h"
-#include "services/network/public/cpp/features.h"
 #include "ui/base/ui_base_paths.h"
 #include "ui/base/ui_base_switches.h"
 #include "ui/events/gesture_detection/gesture_configuration.h"
@@ -201,10 +200,6 @@
 
     features.DisableIfNotSet(features::kAndroidSurfaceControl);
 
-    // Network service for WebView is still being evaluated. Once enabled
-    // by default the following line can be deleted.
-    features.DisableIfNotSet(network::features::kNetworkService);
-
     // TODO(https://crbug.com/963653): SmsReceiver is not yet supported on
     // WebView.
     features.DisableIfNotSet(features::kSmsReceiver);
diff --git a/ash/app_list/views/app_list_folder_view.cc b/ash/app_list/views/app_list_folder_view.cc
index d9f433c..4fdea27 100644
--- a/ash/app_list/views/app_list_folder_view.cc
+++ b/ash/app_list/views/app_list_folder_view.cc
@@ -64,20 +64,14 @@
 // folder's background when opening the folder. Transit the other way when
 // closing the folder.
 class BackgroundAnimation : public AppListFolderView::Animation,
-                            public views::AnimationDelegateViews {
+                            public ui::ImplicitAnimationObserver {
  public:
   BackgroundAnimation(bool show,
                       AppListFolderView* folder_view,
                       views::View* background_view)
-      : AnimationDelegateViews(background_view),
-        show_(show),
-        animation_(this),
+      : show_(show),
         folder_view_(folder_view),
-        background_view_(background_view) {
-    animation_.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
-    animation_.SetSlideDuration(
-        AppListConfig::instance().folder_transition_in_duration_ms());
-  }
+        background_view_(background_view) {}
 
   ~BackgroundAnimation() override = default;
 
@@ -88,14 +82,14 @@
     const int icon_radius = AppListConfig::instance().folder_icon_radius();
     const int folder_radius =
         AppListConfig::instance().folder_background_radius();
-    from_radius_ = show_ ? icon_radius : folder_radius;
-    to_radius_ = show_ ? folder_radius : icon_radius;
-    from_rect_ = show_ ? folder_view_->folder_item_icon_bounds()
-                       : background_view_->bounds();
-    from_rect_ -= background_view_->bounds().OffsetFromOrigin();
-    to_rect_ = show_ ? background_view_->bounds()
-                     : folder_view_->folder_item_icon_bounds();
-    to_rect_ -= background_view_->bounds().OffsetFromOrigin();
+    const int from_radius = show_ ? icon_radius : folder_radius;
+    const int to_radius = show_ ? folder_radius : icon_radius;
+    gfx::Rect from_rect = show_ ? folder_view_->folder_item_icon_bounds()
+                                : background_view_->bounds();
+    from_rect -= background_view_->bounds().OffsetFromOrigin();
+    gfx::Rect to_rect = show_ ? background_view_->bounds()
+                              : folder_view_->folder_item_icon_bounds();
+    to_rect -= background_view_->bounds().OffsetFromOrigin();
     const SkColor background_color =
         AppListConfig::instance().folder_background_color();
     const SkColor from_color =
@@ -106,55 +100,36 @@
               : AppListConfig::instance().folder_bubble_color();
 
     background_view_->layer()->SetColor(from_color);
-    background_view_->layer()->SetClipRect(from_rect_);
+    background_view_->layer()->SetClipRect(from_rect);
     background_view_->layer()->SetRoundedCornerRadius(
-        gfx::RoundedCornersF(from_radius_));
+        gfx::RoundedCornersF(from_radius));
 
-    // We use the layer animation for the color, while gfx::Animation to update
-    // the corner radius and the clip rect. They can be slightly inconsistent
-    // since these animations run independently.
     ui::ScopedLayerAnimationSettings settings(
         background_view_->layer()->GetAnimator());
     settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
         AppListConfig::instance().folder_transition_in_duration_ms()));
     settings.SetTweenType(gfx::Tween::FAST_OUT_SLOW_IN);
+    settings.AddObserver(this);
     background_view_->layer()->SetColor(to_color);
-
-    animation_.Show();
-  }
-
-  bool IsAnimationRunning() override { return animation_.is_animating(); }
-
-  // gfx::AnimationDelegate
-  void AnimationProgressed(const gfx::Animation* animation) override {
-    const double progress = animation->GetCurrentValue();
-    const int current_radius =
-        gfx::Tween::IntValueBetween(progress, from_radius_, to_radius_);
+    background_view_->layer()->SetClipRect(to_rect);
     background_view_->layer()->SetRoundedCornerRadius(
-        gfx::RoundedCornersF(current_radius));
-    const gfx::Rect current_bounds =
-        gfx::Tween::RectValueBetween(progress, from_rect_, to_rect_);
-    background_view_->layer()->SetClipRect(current_bounds);
+        gfx::RoundedCornersF(to_radius));
+    is_animating_ = true;
   }
-  void AnimationEnded(const gfx::Animation* animation) override {
+
+  bool IsAnimationRunning() override { return is_animating_; }
+
+  // ui::ImplicitAnimationObserver:
+  void OnImplicitAnimationsCompleted() override {
+    is_animating_ = false;
     folder_view_->RecordAnimationSmoothness();
   }
-  void AnimationCanceled(const gfx::Animation* animation) override {
-    AnimationEnded(animation);
-  }
 
   // True if opening the folder.
   const bool show_;
 
-  // The source and target state of the background's corner radius.
-  int from_radius_ = 0;
-  int to_radius_ = 0;
+  bool is_animating_ = false;
 
-  // The source and target state of the bounds of the background.
-  gfx::Rect from_rect_;
-  gfx::Rect to_rect_;
-
-  gfx::SlideAnimation animation_;
   AppListFolderView* const folder_view_;  // Not owned.
   views::View* const background_view_;    // Not owned.
 
diff --git a/ash/app_list/views/app_list_item_view.cc b/ash/app_list/views/app_list_item_view.cc
index 07399b9..c4ffef24 100644
--- a/ash/app_list/views/app_list_item_view.cc
+++ b/ash/app_list/views/app_list_item_view.cc
@@ -159,6 +159,22 @@
     return old_layer;
   }
 
+  // Update the rounded corner and insets with animation. |show| is true when
+  // the target rounded corner radius and insets are for showing the indicator
+  // circle.
+  void AnimateRoundedCornerAndInsets(bool show) {
+    ui::ScopedLayerAnimationSettings settings(layer()->GetAnimator());
+    settings.SetTweenType(gfx::Tween::EASE_IN);
+    settings.SetTransitionDuration(base::TimeDelta::FromMilliseconds(
+        kDraggedViewHoverAnimationDurationForFolder));
+
+    SetRoundedCornerAndInsets(
+        show ? AppListConfig::instance().folder_unclipped_icon_dimension() / 2
+             : AppListConfig::instance().folder_icon_dimension() / 2,
+        show ? gfx::Insets()
+             : gfx::Insets(AppListConfig::instance().folder_icon_insets()));
+  }
+
   // Sets the rounded corner and the clip insets.
   void SetRoundedCornerAndInsets(int corner_radius, const gfx::Insets& insets) {
     EnsureLayer();
@@ -761,11 +777,19 @@
 }
 
 void AppListItemView::OnDraggedViewEnter() {
+  if (is_folder_) {
+    icon_->AnimateRoundedCornerAndInsets(true);
+    return;
+  }
   CreateDraggedViewHoverAnimation();
   dragged_view_hover_animation_->Show();
 }
 
 void AppListItemView::OnDraggedViewExit() {
+  if (is_folder_) {
+    icon_->AnimateRoundedCornerAndInsets(false);
+    return;
+  }
   CreateDraggedViewHoverAnimation();
   dragged_view_hover_animation_->Hide();
 }
@@ -786,18 +810,7 @@
 }
 
 void AppListItemView::AnimationProgressed(const gfx::Animation* animation) {
-  if (is_folder_) {
-    // Animate the folder icon via changing mask layer's corner radius and
-    // insets.
-    const double progress = animation->GetCurrentValue();
-    const int corner_radius = gfx::Tween::IntValueBetween(
-        progress, AppListConfig::instance().folder_icon_dimension() / 2,
-        AppListConfig::instance().folder_unclipped_icon_dimension() / 2);
-    const int insets = gfx::Tween::IntValueBetween(
-        progress, AppListConfig::instance().folder_icon_insets(), 0);
-    icon_->SetRoundedCornerAndInsets(corner_radius, gfx::Insets(insets));
-    return;
-  }
+  DCHECK(!is_folder_);
 
   preview_circle_radius_ = gfx::Tween::IntValueBetween(
       animation->GetCurrentValue(), 0,
@@ -926,14 +939,15 @@
 }
 
 void AppListItemView::CreateDraggedViewHoverAnimation() {
+  DCHECK(!is_folder_);
+
   if (dragged_view_hover_animation_)
     return;
 
   dragged_view_hover_animation_ = std::make_unique<gfx::SlideAnimation>(this);
   dragged_view_hover_animation_->SetTweenType(gfx::Tween::EASE_IN);
   dragged_view_hover_animation_->SetSlideDuration(
-      is_folder_ ? kDraggedViewHoverAnimationDurationForFolder
-                 : kDraggedViewHoverAnimationDuration);
+      kDraggedViewHoverAnimationDuration);
 }
 
 void AppListItemView::AdaptBoundsForSelectionHighlight(gfx::Rect* bounds) {
diff --git a/ash/public/cpp/wallpaper_controller_client.h b/ash/public/cpp/wallpaper_controller_client.h
index 5f41ae9..2889165 100644
--- a/ash/public/cpp/wallpaper_controller_client.h
+++ b/ash/public/cpp/wallpaper_controller_client.h
@@ -14,12 +14,6 @@
  public:
   // Opens the wallpaper picker window.
   virtual void OpenWallpaperPicker() = 0;
-
-  // Notifies the client that the animation of the first wallpaper since the
-  // controller initialization has completed.
-  // TODO(crbug.com/875128): Remove this after web-ui login code is completely
-  // removed.
-  virtual void OnFirstWallpaperAnimationFinished() = 0;
 };
 
 }  // namespace ash
diff --git a/ash/shelf/shelf_context_menu_model_unittest.cc b/ash/shelf/shelf_context_menu_model_unittest.cc
index 69504da..4b1671d 100644
--- a/ash/shelf/shelf_context_menu_model_unittest.cc
+++ b/ash/shelf/shelf_context_menu_model_unittest.cc
@@ -50,7 +50,6 @@
 
   // WallpaperControllerClient:
   void OpenWallpaperPicker() override { open_count_++; }
-  void OnFirstWallpaperAnimationFinished() override {}
 
  private:
   size_t open_count_ = 0;
diff --git a/ash/wallpaper/wallpaper_controller_impl.cc b/ash/wallpaper/wallpaper_controller_impl.cc
index df68ae5f..3edaa22e 100644
--- a/ash/wallpaper/wallpaper_controller_impl.cc
+++ b/ash/wallpaper/wallpaper_controller_impl.cc
@@ -615,12 +615,6 @@
   return true;
 }
 
-void WallpaperControllerImpl::OnWallpaperAnimationFinished() {
-  // TODO(crbug.com/875128): Remove this after web-ui login code is removed.
-  if (wallpaper_controller_client_ && is_first_wallpaper_)
-    wallpaper_controller_client_->OnFirstWallpaperAnimationFinished();
-}
-
 bool WallpaperControllerImpl::CanOpenWallpaperPicker() {
   return ShouldShowWallpaperSetting() &&
          !IsActiveUserWallpaperControlledByPolicy();
diff --git a/ash/wallpaper/wallpaper_controller_impl.h b/ash/wallpaper/wallpaper_controller_impl.h
index b5e0cdcf..6c869fa 100644
--- a/ash/wallpaper/wallpaper_controller_impl.h
+++ b/ash/wallpaper/wallpaper_controller_impl.h
@@ -133,9 +133,6 @@
   // wallpapers at login screen).
   bool ShouldShowInitialAnimation();
 
-  // Notifies the controller that the wallpaper animation has finished.
-  void OnWallpaperAnimationFinished();
-
   // Returns true if the active user is allowed to open the wallpaper picker.
   bool CanOpenWallpaperPicker();
 
diff --git a/ash/wallpaper/wallpaper_controller_unittest.cc b/ash/wallpaper/wallpaper_controller_unittest.cc
index 2890304..15894459 100644
--- a/ash/wallpaper/wallpaper_controller_unittest.cc
+++ b/ash/wallpaper/wallpaper_controller_unittest.cc
@@ -231,15 +231,12 @@
   virtual ~TestWallpaperControllerClient() = default;
 
   size_t open_count() const { return open_count_; }
-  size_t animation_count() const { return animation_count_; }
 
   // WallpaperControllerClient:
   void OpenWallpaperPicker() override { open_count_++; }
-  void OnFirstWallpaperAnimationFinished() override { animation_count_++; }
 
  private:
   size_t open_count_ = 0;
-  size_t animation_count_ = 0;
 
   DISALLOW_COPY_AND_ASSIGN(TestWallpaperControllerClient);
 };
@@ -552,13 +549,6 @@
   EXPECT_TRUE(controller_->CanOpenWallpaperPicker());
   controller_->OpenWallpaperPickerIfAllowed();
   EXPECT_EQ(1u, client.open_count());
-
-  EXPECT_EQ(0u, client.animation_count());
-  controller_->ShowWallpaperImage(CreateImage(640, 480, SK_ColorBLUE),
-                                  CreateWallpaperInfo(WALLPAPER_LAYOUT_STRETCH),
-                                  /*preview_mode=*/false,
-                                  /*always_on_top=*/false);
-  EXPECT_EQ(1u, client.animation_count());
 }
 
 TEST_F(WallpaperControllerTest, BasicReparenting) {
diff --git a/ash/wallpaper/wallpaper_view.cc b/ash/wallpaper/wallpaper_view.cc
index f930f96..8d117d8 100644
--- a/ash/wallpaper/wallpaper_view.cc
+++ b/ash/wallpaper/wallpaper_view.cc
@@ -232,7 +232,7 @@
   ::wm::SetWindowVisibilityAnimationType(wallpaper_window, animation_type);
 
   // Enable wallpaper transition for the following cases:
-  // 1. Initial(OOBE) wallpaper animation.
+  // 1. Initial wallpaper animation after device boot.
   // 2. Wallpaper fades in from a non empty background.
   // 3. From an empty background, chrome transit to a logged in user session.
   // 4. From an empty background, guest user logged in.
diff --git a/ash/wallpaper/wallpaper_widget_controller.cc b/ash/wallpaper/wallpaper_widget_controller.cc
index f6b2395..f9b13ad 100644
--- a/ash/wallpaper/wallpaper_widget_controller.cc
+++ b/ash/wallpaper/wallpaper_widget_controller.cc
@@ -8,8 +8,6 @@
 
 #include "ash/ash_export.h"
 #include "ash/root_window_controller.h"
-#include "ash/shell.h"
-#include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "base/scoped_observer.h"
 #include "ui/aura/window.h"
 #include "ui/aura/window_observer.h"
@@ -262,7 +260,6 @@
 
   // Notify observers that animation finished.
   RunAnimationEndCallbacks();
-  Shell::Get()->wallpaper_controller()->OnWallpaperAnimationFinished();
 }
 
 void WallpaperWidgetController::RunAnimationEndCallbacks() {
diff --git a/base/scoped_observer.h b/base/scoped_observer.h
index ecad6c0..f5ddc73 100644
--- a/base/scoped_observer.h
+++ b/base/scoped_observer.h
@@ -52,6 +52,8 @@
 
   bool IsObservingSources() const { return !sources_.empty(); }
 
+  size_t GetSourcesCount() const { return sources_.size(); }
+
  private:
   Observer* observer_;
 
diff --git a/build/config/ios/ios_sdk_overrides.gni b/build/config/ios/ios_sdk_overrides.gni
index 5699ebe..d139a97 100644
--- a/build/config/ios/ios_sdk_overrides.gni
+++ b/build/config/ios/ios_sdk_overrides.gni
@@ -7,11 +7,11 @@
 
 declare_args() {
   # Version of iOS that we're targeting.
-  ios_deployment_target = "11.0"
+  ios_deployment_target = "12.0"
 }
 
 # Always assert that ios_deployment_target is used on non-iOS platforms to
 # prevent unused args warnings.
 if (!is_ios) {
-  assert(ios_deployment_target == "11.0" || true)
+  assert(ios_deployment_target == "12.0" || true)
 }
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index a6da973..bdc6462 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8907154544763250576
\ No newline at end of file
+8907128039086305440
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 21f99f0..77d0db30 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8907153950120029888
\ No newline at end of file
+8907132289784850704
\ No newline at end of file
diff --git a/buildtools/DEPS b/buildtools/DEPS
index 46c3966..602bc00 100644
--- a/buildtools/DEPS
+++ b/buildtools/DEPS
@@ -14,7 +14,7 @@
   #
 
   # GN CIPD package version.
-  'gn_version': 'git_revision:972ed755f8e6d31cae9ba15fcd08136ae1a7886f',
+  'gn_version': 'git_revision:152c5144ceed9592c20f0c8fd55769646077569b',
 
   # When changing these, also update the svn revisions in deps_revisions.gni
   'clang_format_revision': '96636aa0e9f047f17447f2d45a094d0b59ed7917',
diff --git a/cc/animation/keyframed_animation_curve.h b/cc/animation/keyframed_animation_curve.h
index 27e87ba3..c843fc7f 100644
--- a/cc/animation/keyframed_animation_curve.h
+++ b/cc/animation/keyframed_animation_curve.h
@@ -164,12 +164,15 @@
   // BackgrounColorAnimationCurve implementation
   SkColor GetValue(base::TimeDelta t) const override;
 
+  using Keyframes = std::vector<std::unique_ptr<ColorKeyframe>>;
+  const Keyframes& keyframes_for_testing() const { return keyframes_; }
+
  private:
   KeyframedColorAnimationCurve();
 
   // Always sorted in order of increasing time. No two keyframes have the
   // same time.
-  std::vector<std::unique_ptr<ColorKeyframe>> keyframes_;
+  Keyframes keyframes_;
   std::unique_ptr<TimingFunction> timing_function_;
   double scaled_duration_;
 };
diff --git a/chrome/VERSION b/chrome/VERSION
index d9d8172..a8f6692 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=77
 MINOR=0
-BUILD=3862
+BUILD=3863
 PATCH=0
diff --git a/chrome/android/features/vr/java/AndroidManifest.xml b/chrome/android/features/vr/java/AndroidManifest.xml
index 4a2c233..388353f7 100644
--- a/chrome/android/features/vr/java/AndroidManifest.xml
+++ b/chrome/android/features/vr/java/AndroidManifest.xml
@@ -8,9 +8,17 @@
     featureSplit="vr">
 
     <dist:module
-        dist:onDemand="true"
-        dist:title="@string/vr_module_title">
-        <dist:fusing dist:include="false" />
+      dist:instant="false"
+      dist:title="@string/vr_module_title">
+      <dist:fusing dist:include="false" />
+      <dist:delivery>
+        <dist:install-time>
+          <dist:conditions>
+            <dist:device-feature dist:name="android.hardware.vr.high_performance" />
+          </dist:conditions>
+        </dist:install-time>
+        <dist:on-demand />
+      </dist:delivery>
     </dist:module>
 
     <application>
diff --git a/chrome/android/features/vr/java/AndroidManifest_chrome_modern.xml b/chrome/android/features/vr/java/AndroidManifest_chrome_modern.xml
new file mode 100644
index 0000000..4a2c233
--- /dev/null
+++ b/chrome/android/features/vr/java/AndroidManifest_chrome_modern.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2018 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:dist="http://schemas.android.com/apk/distribution"
+    featureSplit="vr">
+
+    <dist:module
+        dist:onDemand="true"
+        dist:title="@string/vr_module_title">
+        <dist:fusing dist:include="false" />
+    </dist:module>
+
+    <application>
+    </application>
+</manifest>
diff --git a/chrome/android/features/vr/vr_module.gni b/chrome/android/features/vr/vr_module.gni
index 429f1d7..7dd4443 100644
--- a/chrome/android/features/vr/vr_module.gni
+++ b/chrome/android/features/vr/vr_module.gni
@@ -21,3 +21,7 @@
     loadable_module = "libvr_ui_dummy_lib.so"
   }
 }
+
+vr_chrome_modern_module_desc = vr_module_desc
+vr_chrome_modern_module_desc.android_manifest =
+    "//chrome/android/features/vr/java/AndroidManifest_chrome_modern.xml"
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadFilter.java b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadFilter.java
index a7cb1f6..d3dcbc4f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadFilter.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/ui/DownloadFilter.java
@@ -109,6 +109,9 @@
     public static @Type int fromMimeType(String mimeType) {
         if (TextUtils.isEmpty(mimeType)) return Type.OTHER;
 
+        Integer type = filterForSpecialMimeTypes(mimeType);
+        if (type != null) return type;
+
         String[] pieces = mimeType.toLowerCase(Locale.getDefault()).split("/");
         if (pieces.length != 2) return Type.OTHER;
 
@@ -124,4 +127,9 @@
             return Type.OTHER;
         }
     }
+
+    private static Integer filterForSpecialMimeTypes(String mimeType) {
+        if (mimeType.equals("application/ogg")) return Type.AUDIO;
+        return null;
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java
index cfd66cc..0fb7e6a5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/ChromeNotificationBuilder.java
@@ -31,6 +31,8 @@
 
     ChromeNotificationBuilder setSmallIcon(Icon icon);
 
+    ChromeNotificationBuilder setColor(int argb);
+
     ChromeNotificationBuilder setTicker(CharSequence text);
 
     ChromeNotificationBuilder setLocalOnly(boolean localOnly);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilder.java
index 39e6295..4c9ec2f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationBuilder.java
@@ -89,6 +89,14 @@
     }
 
     @Override
+    public ChromeNotificationBuilder setColor(int argb) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            mBuilder.setColor(argb);
+        }
+        return this;
+    }
+
+    @Override
     public ChromeNotificationBuilder setTicker(CharSequence text) {
         mBuilder.setTicker(text);
         return this;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationCompatBuilder.java b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationCompatBuilder.java
index b3e3659..501c519 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationCompatBuilder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/notifications/NotificationCompatBuilder.java
@@ -86,6 +86,12 @@
     }
 
     @Override
+    public ChromeNotificationBuilder setColor(int argb) {
+        mBuilder.setColor(argb);
+        return this;
+    }
+
+    @Override
     public ChromeNotificationBuilder setTicker(CharSequence text) {
         mBuilder.setTicker(text);
         return this;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
index c66bcd6..0a2ad62 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/suggestions/entity/EntitySuggestionProcessor.java
@@ -114,8 +114,8 @@
         models.add(model);
         mPendingImageRequests.put(url, models);
 
-        mImageFetcher.fetchImage(url, "EntitySuggestionProcessor", mEntityImageSizePx,
-                mEntityImageSizePx, (Bitmap bitmap) -> {
+        mImageFetcher.fetchImage(url, ImageFetcher.ENTITY_SUGGESTIONS_UMA_CLIENT_NAME,
+                mEntityImageSizePx, mEntityImageSizePx, (Bitmap bitmap) -> {
                     ThreadUtils.assertOnUiThread();
 
                     final List<PropertyModel> pendingModels = mPendingImageRequests.remove(url);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java
index 51decac..b11df5f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sharing/click_to_call/ClickToCallMessageHandler.java
@@ -14,6 +14,7 @@
 import android.support.v4.app.NotificationCompat;
 import android.text.TextUtils;
 
+import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.metrics.CachedMetrics;
@@ -81,6 +82,8 @@
                         .setContentIntent(contentIntent)
                         .setContentTitle(phoneNumber)
                         .setContentText(text)
+                        .setColor(ApiCompatibilityUtils.getColor(
+                                context.getResources(), R.color.default_icon_color_blue))
                         .setGroup(NotificationConstants.GROUP_CLICK_TO_CALL)
                         .setPriorityBeforeO(NotificationCompat.PRIORITY_HIGH)
                         .setVibrate(new long[0])
diff --git a/chrome/android/modules/chrome_feature_modules.gni b/chrome/android/modules/chrome_feature_modules.gni
index 765f7a6..4f72ca8 100644
--- a/chrome/android/modules/chrome_feature_modules.gni
+++ b/chrome/android/modules/chrome_feature_modules.gni
@@ -49,7 +49,7 @@
 # Modules shipped in Chrome Modern (Android L+).
 chrome_modern_module_descs = []
 if (enable_vr) {
-  chrome_modern_module_descs += [ vr_module_desc ]
+  chrome_modern_module_descs += [ vr_chrome_modern_module_desc ]
 }
 if (dfmify_dev_ui) {
   chrome_modern_module_descs += [ dev_ui_module_desc ]
@@ -57,6 +57,10 @@
 
 # Modules shipped in Monochrome (Android N+).
 monochrome_module_descs = chrome_modern_module_descs
+if (enable_vr) {
+  monochrome_module_descs -= [ vr_chrome_modern_module_desc ]
+  monochrome_module_descs += [ vr_module_desc ]
+}
 if (enable_arcore) {
   monochrome_module_descs += [ ar_module_desc ]
 }
diff --git a/chrome/app/chromium_strings.grd b/chrome/app/chromium_strings.grd
index 6d25c29..0c2cb55 100644
--- a/chrome/app/chromium_strings.grd
+++ b/chrome/app/chromium_strings.grd
@@ -1169,9 +1169,6 @@
         <message name="IDS_WELCOME_HEADER" desc="A message which will appear as the header on the Welcome UI if the user has never run Chromium before.">
           Welcome to Chromium
         </message>
-        <message name="IDS_WELCOME_HEADER_AFTER_FIRST_RUN" desc="A message which will appear as the header on the Welcome UI if the user has run Chromium before.">
-          Take Chromium everywhere
-        </message>
       </if>
 
       <!-- Native notifications for Windows 10 -->
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index e09cf16..1dfdd3c 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -9330,22 +9330,6 @@
       </message>
     </if>
 
-    <!-- Welcome page (chrome://welcome) strings -->
-    <if expr="not chromeos">
-      <message name="IDS_WELCOME_SUBHEADER" desc="A message which will appear underneath the header on the Welcome UI.">
-        The web browser by Google, for you
-      </message>
-      <message name="IDS_WELCOME_DESCRIPTION" desc="A message on the welcome page explaining the purpose of signing in.">
-        Sign in to Chrome with your Google Account to get your bookmarks, history, passwords, and other settings on all your devices.
-      </message>
-      <message name="IDS_WELCOME_ACCEPT_BUTTON" desc="A button on the welcome page which accepts the offer to sign in to Chrome.">
-        Sign in
-      </message>
-      <message name="IDS_WELCOME_DECLINE_BUTTON" desc="A button on the welcome page which declines the offer to sign in to Chrome.">
-        No thanks
-      </message>
-    </if>
-
     <!-- Windows 10 toast strings -->
     <if expr="is_win">
       <message name="IDS_WIN10_TOAST_BROWSE_FAST" desc="Toast message variant: browse fast.">
diff --git a/chrome/app/google_chrome_strings.grd b/chrome/app/google_chrome_strings.grd
index 5c58d56..fb159f1 100644
--- a/chrome/app/google_chrome_strings.grd
+++ b/chrome/app/google_chrome_strings.grd
@@ -1188,9 +1188,6 @@
         <message name="IDS_WELCOME_HEADER" desc="A message which will appear as the header on the Welcome UI if the user has never run Chrome before.">
           Welcome to Chrome
         </message>
-        <message name="IDS_WELCOME_HEADER_AFTER_FIRST_RUN" desc="A message which will appear as the header on the Welcome UI if the user has run Chrome before.">
-          Take Chrome everywhere
-        </message>
       </if>
 
       <!-- Native notifications for Windows 10 -->
diff --git a/chrome/app/onboarding_welcome_strings.grdp b/chrome/app/onboarding_welcome_strings.grdp
index cbabb3c..ca34a69 100644
--- a/chrome/app/onboarding_welcome_strings.grdp
+++ b/chrome/app/onboarding_welcome_strings.grdp
@@ -1,9 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
   <!-- Shared strings -->
-  <message name="IDS_ONBOARDING_WELCOME_GET_STARTED" desc="Label for a confirmation button to finish adding a bookmark and get the user started with using the browser.">
-    Get started
-  </message>
   <message name="IDS_ONBOARDING_WELCOME_NEXT" desc="Label for a button to confirm and continue from the current onboarding step.">
     Next
   </message>
@@ -90,9 +87,6 @@
   <message name="IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_SUB_HEADER" desc="Sub-header for the page that prompts user to set Chrome as their default browser.">
     Get Google Search and Google smarts everytime you browse
   </message>
-  <message name="IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_SKIP" desc="The label for a button to skip setting Chrome as their default browser.">
-    Skip
-  </message>
   <message name="IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_SET_AS_DEFAULT" desc="The label for a button to confirm setting Chrome as their default browser.">
     Set as default
   </message>
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0389149f..11ee113a 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3530,11 +3530,6 @@
      flag_descriptions::kEnableAssistantAppSupportDescription, kOsCrOS,
      FEATURE_VALUE_TYPE(chromeos::assistant::features::kAssistantAppSupport)},
 
-    {"enable-assistant-key-remapping",
-     flag_descriptions::kEnableAssistantKeyRemappingName,
-     flag_descriptions::kEnableAssistantKeyRemappingDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(chromeos::assistant::features::kAssistantKeyRemapping)},
-
     {"enable-assistant-media-session-integration",
      flag_descriptions::kEnableAssistantMediaSessionIntegrationName,
      flag_descriptions::kEnableAssistantMediaSessionIntegrationDescription,
diff --git a/chrome/browser/android/feed/feed_offline_bridge.cc b/chrome/browser/android/feed/feed_offline_bridge.cc
index 664fd053..dae5d0fe 100644
--- a/chrome/browser/android/feed/feed_offline_bridge.cc
+++ b/chrome/browser/android/feed/feed_offline_bridge.cc
@@ -134,7 +134,7 @@
   if (!j_snippet.is_null()) {
     metadata.snippet = base::android::ConvertJavaStringToUTF8(env, j_snippet);
   }
-  known_content_metadata_buffer_.push_back(std::move(metadata));
+  known_content_metadata_buffer_.emplace_back(std::move(metadata));
 }
 
 void FeedOfflineBridge::OnGetKnownContentDone(
diff --git a/chrome/browser/android/vr/BUILD.gn b/chrome/browser/android/vr/BUILD.gn
index 177b1ea..3deba2912 100644
--- a/chrome/browser/android/vr/BUILD.gn
+++ b/chrome/browser/android/vr/BUILD.gn
@@ -85,6 +85,7 @@
       "arcore_device/arcore_impl.h",
       "arcore_device/arcore_java_utils.cc",
       "arcore_device/arcore_java_utils.h",
+      "arcore_device/arcore_sdk.h",
       "arcore_device/arcore_session_utils.h",
       "arcore_device/arcore_shim.cc",
       "arcore_device/arcore_shim.h",
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index 1054860..aba8193 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -73,6 +73,11 @@
     return false;
   }
 
+  // Set incognito mode for ARCore session - this is done unconditionally as we
+  // always want to limit the amount of logging done by ARCore.
+  ArSession_enableIncognitoMode_private(session.get());
+  DVLOG(1) << __func__ << ": ARCore incognito mode enabled";
+
   internal::ScopedArCoreObject<ArConfig*> arcore_config;
   ArConfig_create(
       session.get(),
@@ -247,7 +252,7 @@
 
     fn(ar_plane);
   }
-}
+}  // namespace device
 
 std::vector<mojom::XRPlaneDataPtr> ArCoreImpl::GetUpdatedPlanesData() {
   EnsureArCorePlanesList();
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.h b/chrome/browser/android/vr/arcore_device/arcore_impl.h
index 013418d..4863016 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.h
@@ -9,8 +9,8 @@
 #include "base/optional.h"
 #include "base/scoped_generic.h"
 #include "chrome/browser/android/vr/arcore_device/arcore.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_sdk.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
-#include "third_party/arcore-android-sdk/src/libraries/include/arcore_c_api.h"
 
 namespace device {
 
diff --git a/chrome/browser/android/vr/arcore_device/arcore_sdk.h b/chrome/browser/android/vr/arcore_device/arcore_sdk.h
new file mode 100644
index 0000000..428c4c76
--- /dev/null
+++ b/chrome/browser/android/vr/arcore_device/arcore_sdk.h
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SDK_H_
+#define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SDK_H_
+
+#include "third_party/arcore-android-sdk/src/libraries/include/arcore_c_api.h"
+
+/// Sets ARCore to comply with incognito mode in Google Chrome and Chromium.
+/// When called before calling Resume(), it will minimize the amount of logging
+/// done by ARCore for this ArSession.
+///
+/// @param[in] session - the ARCore session.
+void ArSession_enableIncognitoMode_private(ArSession* session);
+
+#endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_ARCORE_SDK_H_
diff --git a/chrome/browser/android/vr/arcore_device/arcore_shim.cc b/chrome/browser/android/vr/arcore_device/arcore_shim.cc
index f6b01197..4ee8a62f2 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_shim.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_shim.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 "third_party/arcore-android-sdk/src/libraries/include/arcore_c_api.h"
+#include "chrome/browser/android/vr/arcore_device/arcore_sdk.h"
 
 #include <dlfcn.h>
 
@@ -12,52 +12,53 @@
 namespace {
 
 // Run CALL macro for every function defined in the API.
-#define FOR_EACH_API_FN                 \
-  CALL(ArCamera_getDisplayOrientedPose) \
-  CALL(ArCamera_getProjectionMatrix)    \
-  CALL(ArCamera_getTrackingState)       \
-  CALL(ArCamera_getViewMatrix)          \
-  CALL(ArConfig_create)                 \
-  CALL(ArConfig_destroy)                \
-  CALL(ArFrame_acquireCamera)           \
-  CALL(ArFrame_create)                  \
-  CALL(ArFrame_destroy)                 \
-  CALL(ArFrame_getUpdatedTrackables)    \
-  CALL(ArFrame_hitTestRay)              \
-  CALL(ArFrame_transformCoordinates2d)  \
-  CALL(ArHitResult_acquireTrackable)    \
-  CALL(ArHitResult_create)              \
-  CALL(ArHitResult_destroy)             \
-  CALL(ArHitResult_getHitPose)          \
-  CALL(ArHitResultList_create)          \
-  CALL(ArHitResultList_destroy)         \
-  CALL(ArHitResultList_getItem)         \
-  CALL(ArHitResultList_getSize)         \
-  CALL(ArPlane_acquireSubsumedBy)       \
-  CALL(ArPlane_getCenterPose)           \
-  CALL(ArPlane_getPolygon)              \
-  CALL(ArPlane_getPolygonSize)          \
-  CALL(ArPlane_getType)                 \
-  CALL(ArPlane_isPoseInPolygon)         \
-  CALL(ArPose_create)                   \
-  CALL(ArPose_destroy)                  \
-  CALL(ArPose_getMatrix)                \
-  CALL(ArPose_getPoseRaw)               \
-  CALL(ArSession_configure)             \
-  CALL(ArSession_create)                \
-  CALL(ArSession_destroy)               \
-  CALL(ArSession_getAllTrackables)      \
-  CALL(ArSession_pause)                 \
-  CALL(ArSession_resume)                \
-  CALL(ArSession_setCameraTextureName)  \
-  CALL(ArSession_setDisplayGeometry)    \
-  CALL(ArSession_update)                \
-  CALL(ArTrackable_getTrackingState)    \
-  CALL(ArTrackable_getType)             \
-  CALL(ArTrackable_release)             \
-  CALL(ArTrackableList_acquireItem)     \
-  CALL(ArTrackableList_create)          \
-  CALL(ArTrackableList_destroy)         \
+#define FOR_EACH_API_FN                       \
+  CALL(ArCamera_getDisplayOrientedPose)       \
+  CALL(ArCamera_getProjectionMatrix)          \
+  CALL(ArCamera_getTrackingState)             \
+  CALL(ArCamera_getViewMatrix)                \
+  CALL(ArConfig_create)                       \
+  CALL(ArConfig_destroy)                      \
+  CALL(ArFrame_acquireCamera)                 \
+  CALL(ArFrame_create)                        \
+  CALL(ArFrame_destroy)                       \
+  CALL(ArFrame_getUpdatedTrackables)          \
+  CALL(ArFrame_hitTestRay)                    \
+  CALL(ArFrame_transformCoordinates2d)        \
+  CALL(ArHitResult_acquireTrackable)          \
+  CALL(ArHitResult_create)                    \
+  CALL(ArHitResult_destroy)                   \
+  CALL(ArHitResult_getHitPose)                \
+  CALL(ArHitResultList_create)                \
+  CALL(ArHitResultList_destroy)               \
+  CALL(ArHitResultList_getItem)               \
+  CALL(ArHitResultList_getSize)               \
+  CALL(ArPlane_acquireSubsumedBy)             \
+  CALL(ArPlane_getCenterPose)                 \
+  CALL(ArPlane_getPolygon)                    \
+  CALL(ArPlane_getPolygonSize)                \
+  CALL(ArPlane_getType)                       \
+  CALL(ArPlane_isPoseInPolygon)               \
+  CALL(ArPose_create)                         \
+  CALL(ArPose_destroy)                        \
+  CALL(ArPose_getMatrix)                      \
+  CALL(ArPose_getPoseRaw)                     \
+  CALL(ArSession_configure)                   \
+  CALL(ArSession_create)                      \
+  CALL(ArSession_destroy)                     \
+  CALL(ArSession_enableIncognitoMode_private) \
+  CALL(ArSession_getAllTrackables)            \
+  CALL(ArSession_pause)                       \
+  CALL(ArSession_resume)                      \
+  CALL(ArSession_setCameraTextureName)        \
+  CALL(ArSession_setDisplayGeometry)          \
+  CALL(ArSession_update)                      \
+  CALL(ArTrackable_getTrackingState)          \
+  CALL(ArTrackable_getType)                   \
+  CALL(ArTrackable_release)                   \
+  CALL(ArTrackableList_acquireItem)           \
+  CALL(ArTrackableList_create)                \
+  CALL(ArTrackableList_destroy)               \
   CALL(ArTrackableList_getSize)
 
 #define CALL(fn) decltype(&fn) impl_##fn = nullptr;
@@ -352,6 +353,10 @@
   arcore_api->impl_ArSession_destroy(session);
 }
 
+void ArSession_enableIncognitoMode_private(ArSession* session) {
+  arcore_api->impl_ArSession_enableIncognitoMode_private(session);
+}
+
 void ArSession_getAllTrackables(const ArSession* session,
                                 ArTrackableType filter_type,
                                 ArTrackableList* out_trackable_list) {
diff --git a/chrome/browser/android/vr/arcore_device/type_converters.h b/chrome/browser/android/vr/arcore_device/type_converters.h
index 111f104..c8cf665 100644
--- a/chrome/browser/android/vr/arcore_device/type_converters.h
+++ b/chrome/browser/android/vr/arcore_device/type_converters.h
@@ -5,8 +5,8 @@
 #ifndef CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_TYPE_CONVERTERS_H_
 #define CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_TYPE_CONVERTERS_H_
 
+#include "chrome/browser/android/vr/arcore_device/arcore_sdk.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
-#include "third_party/arcore-android-sdk/src/libraries/include/arcore_c_api.h"
 
 namespace mojo {
 
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
index a2ec7d61..33b016e 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client.cc
@@ -58,6 +58,10 @@
 #include "chrome/browser/upgrade_detector/upgrade_detector.h"
 #endif
 
+#if defined(OS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif
+
 namespace {
 
 #if !defined(OS_ANDROID)
@@ -69,18 +73,22 @@
     chrome::kLanguageOptionsSubPage,  chrome::kPasswordManagerSubPage,
     chrome::kPaymentsSubPage,         chrome::kResetProfileSettingsSubPage,
     chrome::kSearchEnginesSubPage,    chrome::kSyncSetupSubPage,
-#if defined(OS_CHROMEOS)
-    chrome::kAccessibilitySubPage,    chrome::kBluetoothSubPage,
-    chrome::kDateTimeSubPage,         chrome::kDisplaySubPage,
-    chrome::kInternetSubPage,         chrome::kPowerSubPage,
-    chrome::kStylusSubPage,
-#else
+#if !defined(OS_CHROMEOS)
     chrome::kCreateProfileSubPage,    chrome::kImportDataSubPage,
     chrome::kManageProfileSubPage,    chrome::kPeopleSubPage,
 #endif
 };
 #endif  // !defined(OS_ANDROID)
 
+#if defined(OS_CHROMEOS)
+const char* const kChromeOSSettingsSubPages[] = {
+    chrome::kAccessibilitySubPage, chrome::kBluetoothSubPage,
+    chrome::kDateTimeSubPage,      chrome::kDisplaySubPage,
+    chrome::kInternetSubPage,      chrome::kPowerSubPage,
+    chrome::kStylusSubPage,
+};
+#endif  // defined(OS_CHROMEOS)
+
 }  // namespace
 
 ChromeAutocompleteProviderClient::ChromeAutocompleteProviderClient(
@@ -227,6 +235,18 @@
   }
 #endif
 
+#if defined(OS_CHROMEOS)
+  // TODO(crbug/950007): Delete this after the settings split is complete since
+  // the OS setting routes should not show up as an autocomplete suggestion in
+  // browser.
+  if (!base::FeatureList::IsEnabled(chromeos::features::kSplitSettings)) {
+    for (size_t i = 0; i < base::size(kChromeOSSettingsSubPages); i++) {
+      builtins.push_back(settings +
+                         base::ASCIIToUTF16(kChromeOSSettingsSubPages[i]));
+    }
+  }
+#endif
+
   return builtins;
 }
 
diff --git a/chrome/browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc b/chrome/browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc
index 00eb2a5..fd037b2 100644
--- a/chrome/browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc
+++ b/chrome/browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc
@@ -7,6 +7,7 @@
 #include <memory>
 
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/test/base/testing_profile.h"
 #include "components/prefs/pref_service.h"
@@ -16,6 +17,10 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
+#if defined(OS_CHROMEOS)
+#include "chromeos/constants/chromeos_features.h"
+#endif
+
 namespace {
 
 class TestSchemeClassifier : public AutocompleteSchemeClassifier {
@@ -146,3 +151,24 @@
                                             GURL(test_case.url2), &input));
   }
 }
+
+// TODO(crbug/950007): Remove this test when the split settings work is complete
+#if defined(OS_CHROMEOS)
+TEST_F(ChromeAutocompleteProviderClientTest, OSSettingsDoNotShowUp) {
+  size_t builtin_urls_with_os_settings;
+  size_t builtin_urls_without_os_settings;
+
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndDisableFeature(chromeos::features::kSplitSettings);
+    builtin_urls_with_os_settings = client_->GetBuiltinURLs().size();
+  }
+  {
+    base::test::ScopedFeatureList feature_list;
+    feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
+    builtin_urls_without_os_settings = client_->GetBuiltinURLs().size();
+  }
+
+  EXPECT_GT(builtin_urls_with_os_settings, builtin_urls_without_os_settings);
+}
+#endif
diff --git a/chrome/browser/background_sync/background_sync_controller_impl.cc b/chrome/browser/background_sync/background_sync_controller_impl.cc
index 290296c..1bf97cd 100644
--- a/chrome/browser/background_sync/background_sync_controller_impl.cc
+++ b/chrome/browser/background_sync/background_sync_controller_impl.cc
@@ -191,10 +191,13 @@
 #endif
 }
 
-int BackgroundSyncControllerImpl::GetSiteEngagementPenalty(
-    const GURL& url) const {
+int BackgroundSyncControllerImpl::GetSiteEngagementPenalty(const GURL& url) {
   blink::mojom::EngagementLevel engagement_level =
       site_engagement_service_->GetEngagementLevel(url);
+  if (engagement_level == blink::mojom::EngagementLevel::NONE) {
+    suspended_periodic_sync_origins_.insert(
+        url::Origin::Create(url.GetOrigin()));
+  }
 
   switch (engagement_level) {
     case blink::mojom::EngagementLevel::NONE:
@@ -283,3 +286,11 @@
 BackgroundSyncControllerImpl::BackgroundSyncEventKeepAliveImpl::
     ~BackgroundSyncEventKeepAliveImpl() = default;
 #endif
+
+void BackgroundSyncControllerImpl::NoteSuspendedPeriodicSyncOrigins(
+    std::set<url::Origin> suspended_origins) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  for (auto& origin : suspended_origins)
+    suspended_periodic_sync_origins_.insert(std::move(origin));
+}
diff --git a/chrome/browser/background_sync/background_sync_controller_impl.h b/chrome/browser/background_sync/background_sync_controller_impl.h
index 52a3a2a..7871ffd 100644
--- a/chrome/browser/background_sync/background_sync_controller_impl.h
+++ b/chrome/browser/background_sync/background_sync_controller_impl.h
@@ -9,6 +9,8 @@
 
 #include <stdint.h>
 
+#include <set>
+
 #include "base/macros.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
@@ -19,6 +21,7 @@
 #include "content/public/browser/background_sync_registration.h"
 #include "content/public/browser/browser_thread.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
+#include "url/gurl.h"
 
 namespace content {
 struct BackgroundSyncParameters;
@@ -92,6 +95,8 @@
       content::BackgroundSyncParameters* parameters) override;
   std::unique_ptr<BackgroundSyncEventKeepAlive>
   CreateBackgroundSyncEventKeepAlive() override;
+  void NoteSuspendedPeriodicSyncOrigins(
+      std::set<url::Origin> suspended_origins) override;
 
  private:
   // Gets the site engagement penalty for |url|, which is inversely proportional
@@ -99,7 +104,7 @@
   // the less often periodic sync events will be fired.
   // Returns kEngagementLevelNonePenalty if the engagement level is
   // blink::mojom::EngagementLevel::NONE.
-  int GetSiteEngagementPenalty(const GURL& url) const;
+  int GetSiteEngagementPenalty(const GURL& url);
 
   // Once we've identified the minimum number of hours between each periodicsync
   // event for an origin, every delay calculated for the origin should be a
@@ -114,6 +119,8 @@
 
   BackgroundSyncMetrics background_sync_metrics_;
 
+  std::set<url::Origin> suspended_periodic_sync_origins_;
+
   DISALLOW_COPY_AND_ASSIGN(BackgroundSyncControllerImpl);
 };
 
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 510dc12..c0cef30 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -67,11 +67,6 @@
           </else>
         </if>
       </if>
-      <if expr="not is_android and not chromeos">
-        <structure name="IDR_WELCOME_CSS" file="resources\welcome\welcome.css" type="chrome_html" preprocess="true"/>
-        <structure name="IDR_WELCOME_HTML" file="resources\welcome\welcome.html" type="chrome_html" preprocess="true"/>
-        <structure name="IDR_WELCOME_JS" file="resources\welcome\welcome.js" type="chrome_html" preprocess="true"/>
-      </if>
       <if expr="chromeos">
         <structure name="IDR_FIRST_RUN_HTML" file="resources\chromeos\first_run\first_run.html" flattenhtml="true" type="chrome_html"/>
         <structure name="IDR_FIRST_RUN_JS" file="resources\chromeos\first_run\first_run.js" flattenhtml="true" type="chrome_html" />
diff --git a/chrome/browser/chrome_notification_types.h b/chrome/browser/chrome_notification_types.h
index 82d48c6..1a0c83d 100644
--- a/chrome/browser/chrome_notification_types.h
+++ b/chrome/browser/chrome_notification_types.h
@@ -278,11 +278,6 @@
   // which was installed.
   NOTIFICATION_APP_INSTALLED_TO_NTP,
 
-#if defined(OS_CHROMEOS)
-  // Sent when wallpaper show animation has finished.
-  NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
-#endif
-
   // Note:-
   // Currently only Content and Chrome define and use notifications.
   // Custom notifications not belonging to Content and Chrome should start
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
index d6781fc..9e97973e 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.cc
@@ -353,20 +353,6 @@
          device.type != chromeos::AudioDeviceType::AUDIO_TYPE_OTHER;
 }
 
-bool ShouldInitializeWebUIHidden() {
-  // Always postpone WebUI initialization on first boot, otherwise we miss
-  // initial animation.
-  if (!StartupUtils::IsOobeCompleted())
-    return false;
-
-  // Tests and kiosk app autolaunch don't support hidden.
-  if (WizardController::IsZeroDelayEnabled())
-    return false;
-
-  // Default.
-  return true;
-}
-
 }  // namespace
 
 // static
@@ -400,8 +386,7 @@
 // LoginDisplayHostWebUI, public
 
 LoginDisplayHostWebUI::LoginDisplayHostWebUI()
-    : initialize_webui_hidden_(ShouldInitializeWebUIHidden()),
-      oobe_startup_sound_played_(StartupUtils::IsOobeCompleted()),
+    : oobe_startup_sound_played_(StartupUtils::IsOobeCompleted()),
       weak_factory_(this) {
   SessionManagerClient::Get()->AddObserver(this);
   CrasAudioHandler::Get()->AddAudioObserver(this);
@@ -410,26 +395,12 @@
 
   ui::DeviceDataManager::GetInstance()->AddObserver(this);
 
-  bool zero_delay_enabled = WizardController::IsZeroDelayEnabled();
-  waiting_for_wallpaper_load_ = !zero_delay_enabled;
-
-  if (waiting_for_wallpaper_load_) {
-    registrar_.Add(this, chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
-                   content::NotificationService::AllSources());
-  }
-
   // When we wait for WebUI to be initialized we wait for one of
   // these notifications.
-  if (waiting_for_wallpaper_load_ && initialize_webui_hidden_) {
-    registrar_.Add(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
-                   content::NotificationService::AllSources());
-    registrar_.Add(this, chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
-                   content::NotificationService::AllSources());
-  }
-  VLOG(1) << "Login WebUI >> "
-          << "zero_delay: " << zero_delay_enabled
-          << " wait_for_wp_load_: " << waiting_for_wallpaper_load_
-          << " init_webui_hidden_: " << initialize_webui_hidden_;
+  registrar_.Add(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
+                 content::NotificationService::AllSources());
+  registrar_.Add(this, chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
+                 content::NotificationService::AllSources());
 
   audio::SoundsManager* manager = audio::SoundsManager::Get();
   ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
@@ -520,9 +491,9 @@
 }
 
 void LoginDisplayHostWebUI::SetStatusAreaVisible(bool visible) {
-  if (initialize_webui_hidden_)
+  if (!login_view_)
     status_area_saved_visibility_ = visible;
-  else if (login_view_)
+  else
     login_view_->SetStatusAreaVisible(visible);
 }
 
@@ -555,10 +526,6 @@
   first_screen_ = first_screen;
   is_showing_login_ = false;
 
-  if (waiting_for_wallpaper_load_ && !initialize_webui_hidden_) {
-    VLOG(1) << "Login WebUI >> wizard postponed";
-    return;
-  }
   VLOG(1) << "Login WebUI >> wizard";
 
   if (!login_window_)
@@ -635,10 +602,6 @@
   is_showing_login_ = true;
   finalize_animation_type_ = ANIMATION_WORKSPACE;
 
-  if (waiting_for_wallpaper_load_ && !initialize_webui_hidden_) {
-    VLOG(1) << "Login WebUI >> sign in postponed";
-    return;
-  }
   VLOG(1) << "Login WebUI >> sign in";
 
   // TODO(crbug.com/784495): Make sure this is ported to views.
@@ -731,35 +694,11 @@
   if (chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE == type ||
       chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN == type) {
     VLOG(1) << "Login WebUI >> WEBUI_VISIBLE";
-    if (waiting_for_wallpaper_load_ && initialize_webui_hidden_) {
-      // Reduce time till login UI is shown - show it as soon as possible.
-      waiting_for_wallpaper_load_ = false;
-      ShowWebUI();
-    }
+    ShowWebUI();
     registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
                       content::NotificationService::AllSources());
     registrar_.Remove(this, chrome::NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN,
                       content::NotificationService::AllSources());
-  } else if (chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED == type) {
-    VLOG(1) << "Login WebUI >> wp animation done";
-    is_wallpaper_loaded_ = true;
-    if (waiting_for_wallpaper_load_) {
-      // StartWizard / StartSignInScreen could be called multiple times through
-      // the lifetime of host.
-      // Make sure that subsequent calls are not postponed.
-      waiting_for_wallpaper_load_ = false;
-      if (initialize_webui_hidden_) {
-        // If we're in the process of switching locale, the wallpaper might
-        // have finished loading before the locale switch was completed.
-        // Only show the UI if it already exists.
-        if (login_window_ && login_view_)
-          ShowWebUI();
-      } else {
-        StartPostponedWebUI();
-      }
-    }
-    registrar_.Remove(this, chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
-                      content::NotificationService::AllSources());
   }
 }
 
@@ -901,49 +840,14 @@
 }
 
 void LoginDisplayHostWebUI::ShowWebUI() {
-  if (!login_window_ || !login_view_) {
-    NOTREACHED();
-    return;
-  }
+  DCHECK(login_window_);
+  DCHECK(login_view_);
+
   VLOG(1) << "Login WebUI >> Show already initialized UI";
   login_window_->Show();
   login_view_->GetWebContents()->Focus();
   login_view_->SetStatusAreaVisible(status_area_saved_visibility_);
   login_view_->OnPostponedShow();
-
-  // We should reset this flag to allow changing of status area visibility.
-  initialize_webui_hidden_ = false;
-}
-
-void LoginDisplayHostWebUI::StartPostponedWebUI() {
-  if (!is_wallpaper_loaded_) {
-    NOTREACHED();
-    return;
-  }
-  VLOG(1) << "Login WebUI >> Init postponed WebUI";
-
-  // Wallpaper has finished loading before StartWizard/StartSignInScreen has
-  // been called. In general this should not happen.
-  // Let go through normal code path when one of those will be called.
-  if (restore_path_ == RESTORE_UNKNOWN) {
-    NOTREACHED();
-    return;
-  }
-
-  switch (restore_path_) {
-    case RESTORE_WIZARD:
-      StartWizard(first_screen_);
-      break;
-    case RESTORE_SIGN_IN:
-      StartSignInScreen(LoginScreenContext());
-      break;
-    case RESTORE_ADD_USER_INTO_SESSION:
-      StartUserAdding(base::OnceClosure());
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
 }
 
 void LoginDisplayHostWebUI::InitLoginWindowAndView() {
@@ -984,18 +888,11 @@
   login_window_->AddObserver(this);
   login_window_->AddRemovalsObserver(this);
   login_window_->SetContentsView(login_view_);
-
-  // If WebUI is initialized in hidden state, show it only if we're no
-  // longer waiting for wallpaper animation/user images loading. Otherwise,
-  // always show it.
-  if (!initialize_webui_hidden_ || !waiting_for_wallpaper_load_) {
-    VLOG(1) << "Login WebUI >> show login wnd on create";
-    login_window_->Show();
-  } else {
-    VLOG(1) << "Login WebUI >> login wnd is hidden on create";
-    login_view_->set_is_hidden(true);
-  }
   login_window_->GetNativeView()->SetName("WebUILoginView");
+
+  // Delay showing the window until the login webui is ready.
+  VLOG(1) << "Login WebUI >> login window is hidden on create";
+  login_view_->set_is_hidden(true);
 }
 
 void LoginDisplayHostWebUI::ResetLoginWindowAndView() {
diff --git a/chrome/browser/chromeos/login/ui/login_display_host_webui.h b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
index a1ac3310..c8a7cbd6 100644
--- a/chrome/browser/chromeos/login/ui/login_display_host_webui.h
+++ b/chrome/browser/chromeos/login/ui/login_display_host_webui.h
@@ -42,8 +42,13 @@
 class LoginDisplayWebUI;
 class WebUILoginView;
 
-// An implementation class for OOBE/login WebUI screen host.
-// It encapsulates controllers, wallpaper integration and flow.
+// An implementation class for OOBE and user adding screen host via WebUI.
+// For OOBE, it provides wizard screens such as welcome, network, EULA, update,
+// GAIA etc. For user adding, it is legacy support and provides the user
+// selection screen (aka account picker).
+// The WebUI (chrome://oobe) is loaded hidden on start and made visible when
+// WebUI signals ready (via NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE) or there
+// is a network error (via NOTIFICATION_LOGIN_NETWORK_ERROR_SHOWN).
 class LoginDisplayHostWebUI : public LoginDisplayHostCommon,
                               public content::WebContentsObserver,
                               public chromeos::SessionManagerClient::Observer,
@@ -166,10 +171,6 @@
   // Shows OOBE/sign in WebUI that was previously initialized in hidden state.
   void ShowWebUI();
 
-  // Starts postponed WebUI (OOBE/sign in) if it was waiting for
-  // wallpaper animation end.
-  void StartPostponedWebUI();
-
   // Initializes |login_window_| and |login_view_| fields if needed.
   void InitLoginWindowAndView();
 
@@ -216,24 +217,10 @@
   // True if the login display is the current screen.
   bool is_showing_login_ = false;
 
-  // True if NOTIFICATION_WALLPAPER_ANIMATION_FINISHED notification has been
-  // received.
-  bool is_wallpaper_loaded_ = false;
-
   // Stores status area current visibility to be applied once login WebUI
   // is shown.
   bool status_area_saved_visibility_ = false;
 
-  // If true, WebUI is initialized in a hidden state and shown after the
-  // wallpaper animation is finished (when it is enabled) or the user pods have
-  // been loaded (otherwise).
-  // By default is true. Could be used to tune performance if needed.
-  bool initialize_webui_hidden_;
-
-  // True if WebUI is initialized in hidden state and we're waiting for
-  // wallpaper load animation to finish.
-  bool waiting_for_wallpaper_load_;
-
   // True if WebUI is initialized in hidden state, the OOBE is not completed
   // and we're waiting for OOBE configuration check to finish.
   bool waiting_for_configuration_ = false;
diff --git a/chrome/browser/download/offline_item_utils.cc b/chrome/browser/download/offline_item_utils.cc
index 6200d1f..061d88d 100644
--- a/chrome/browser/download/offline_item_utils.cc
+++ b/chrome/browser/download/offline_item_utils.cc
@@ -41,8 +41,18 @@
 // The remaining time for a download item if it cannot be calculated.
 constexpr int64_t kUnknownRemainingTime = -1;
 
+base::Optional<OfflineItemFilter> FilterForSpecialMimeTypes(
+    const std::string& mime_type) {
+  if (base::EqualsCaseInsensitiveASCII(mime_type, "application/ogg"))
+    return OfflineItemFilter::FILTER_AUDIO;
+
+  return base::nullopt;
+}
+
 OfflineItemFilter MimeTypeToOfflineItemFilter(const std::string& mime_type) {
-  OfflineItemFilter filter = OfflineItemFilter::FILTER_OTHER;
+  auto filter = FilterForSpecialMimeTypes(mime_type);
+  if (filter.has_value())
+    return filter.value();
 
   if (base::StartsWith(mime_type, "audio/", base::CompareCase::SENSITIVE)) {
     filter = OfflineItemFilter::FILTER_AUDIO;
@@ -59,7 +69,7 @@
     filter = OfflineItemFilter::FILTER_OTHER;
   }
 
-  return filter;
+  return filter.value();
 }
 
 bool IsInterruptedDownloadAutoResumable(download::DownloadItem* item) {
diff --git a/chrome/browser/extensions/chrome_extensions_interface_registration.cc b/chrome/browser/extensions/chrome_extensions_interface_registration.cc
index 666e402a..1483684 100644
--- a/chrome/browser/extensions/chrome_extensions_interface_registration.cc
+++ b/chrome/browser/extensions/chrome_extensions_interface_registration.cc
@@ -22,6 +22,7 @@
 #include "services/service_manager/public/cpp/binder_registry.h"
 
 #if defined(OS_CHROMEOS)
+#include "base/task/post_task.h"
 #include "chromeos/services/ime/public/mojom/constants.mojom.h"
 #include "chromeos/services/ime/public/mojom/input_engine.mojom.h"
 #include "chromeos/services/media_perception/public/mojom/media_perception.mojom.h"
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 454968f..1655b8ed 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3126,11 +3126,6 @@
 const char kEnableAssistantAppSupportDescription[] =
     "Enable the Assistant App Support feature";
 
-const char kEnableAssistantKeyRemappingName[] =
-    "Enable Assistant Key Remapping";
-const char kEnableAssistantKeyRemappingDescription[] =
-    "Allow Assistant key remapping in the keyboard settings.";
-
 const char kEnableAssistantLauncherIntegrationName[] =
     "Assistant & Launcher integration";
 const char kEnableAssistantLauncherIntegrationDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 2fcd89d4..a57e9791 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1870,9 +1870,6 @@
 extern const char kEnableAssistantAppSupportName[];
 extern const char kEnableAssistantAppSupportDescription[];
 
-extern const char kEnableAssistantKeyRemappingName[];
-extern const char kEnableAssistantKeyRemappingDescription[];
-
 extern const char kEnableAssistantLauncherIntegrationName[];
 extern const char kEnableAssistantLauncherIntegrationDescription[];
 
diff --git a/chrome/browser/google/google_brand_code_map_chromeos.cc b/chrome/browser/google/google_brand_code_map_chromeos.cc
index a2187fa..3630cf6 100644
--- a/chrome/browser/google/google_brand_code_map_chromeos.cc
+++ b/chrome/browser/google/google_brand_code_map_chromeos.cc
@@ -45,6 +45,7 @@
                      {"BAQN", {"YJJJ", "LDCA", "QSJF"}},
                      {"BCOL", {"YJDV", "GSIC", "BAUL"}},
                      {"BDIW", {"UDUG", "TRYQ", "PWFV"}},
+                     {"CQPQ", {"GATZ", "QAVU", "WRXC"}},
                      {"CYQR", {"XGJJ", "DRMC", "RUQD"}},
                      {"CYSQ", {"NHHD", "TAVM", "FHSA"}},
                      {"DEAA", {"HXUG", "BJUN", "IYTV"}},
@@ -107,6 +108,7 @@
                      {"VEUT", {"JDFA", "ALIR", "DDJM"}},
                      {"VHUH", {"JYDF", "SFJY", "JMBU"}},
                      {"WBZQ", {"LAYK", "LQDM", "QBFV"}},
+                     {"WJOZ", {"BASQ", "BRTL", "CQAV"}},
                      {"XVTK", {"TMUU", "BTWW", "THQH"}},
                      {"XVYQ", {"UAVB", "OEMI", "VQVK"}},
                      {"XWJE", {"KDZI", "IYPJ", "ERIM"}},
diff --git a/chrome/browser/media/router/discovery/BUILD.gn b/chrome/browser/media/router/discovery/BUILD.gn
index ef7a335..2e3ece36 100644
--- a/chrome/browser/media/router/discovery/BUILD.gn
+++ b/chrome/browser/media/router/discovery/BUILD.gn
@@ -1,7 +1,8 @@
 # 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.
-#
+
+assert(!is_android)
 
 static_library("discovery") {
   inputs = [
@@ -46,6 +47,16 @@
     "dial/safe_dial_app_info_parser.h",
     "dial/safe_dial_device_description_parser.cc",
     "dial/safe_dial_device_description_parser.h",
+    "discovery_network_info.cc",
+    "discovery_network_info.h",
+    "discovery_network_list.h",
+    "discovery_network_list_wifi.h",
+    "discovery_network_monitor.cc",
+    "discovery_network_monitor.h",
+    "discovery_network_monitor_metric_observer.cc",
+    "discovery_network_monitor_metric_observer.h",
+    "discovery_network_monitor_metrics.cc",
+    "discovery_network_monitor_metrics.h",
     "mdns/cast_media_sink_service.cc",
     "mdns/cast_media_sink_service.h",
     "mdns/cast_media_sink_service_impl.cc",
@@ -62,28 +73,20 @@
     "media_sink_discovery_metrics.h",
   ]
 
-  if (!is_android) {
-    sources += [
-      "discovery_network_info.cc",
-      "discovery_network_info.h",
-      "discovery_network_list.h",
-      "discovery_network_list_wifi.h",
-      "discovery_network_list_wifi_linux.cc",
-      "discovery_network_list_wifi_mac.mm",
-      "discovery_network_list_win.cc",
-      "discovery_network_monitor.cc",
-      "discovery_network_monitor.h",
-      "discovery_network_monitor_metric_observer.cc",
-      "discovery_network_monitor_metric_observer.h",
-      "discovery_network_monitor_metrics.cc",
-      "discovery_network_monitor_metrics.h",
-    ]
-    if (is_posix) {
-      sources += [ "discovery_network_list_posix.cc" ]
-    }
+  if (is_linux) {
+    sources += [ "discovery_network_list_wifi_linux.cc" ]
+  }
+
+  if (is_posix) {
+    sources += [ "discovery_network_list_posix.cc" ]
   }
 
   if (is_mac) {
+    sources += [ "discovery_network_list_wifi_mac.mm" ]
     libs = [ "CoreWLAN.framework" ]
   }
+
+  if (is_win) {
+    sources += [ "discovery_network_list_win.cc" ]
+  }
 }
diff --git a/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.cc b/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.cc
index 717d5bd6..08ab3d3f 100644
--- a/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.cc
+++ b/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.h"
 
+#include "chrome/browser/media/router/discovery/mdns/dns_sd_delegate.h"
+
 using local_discovery::ServiceDescription;
 
 namespace media_router {
diff --git a/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.h b/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.h
index 43d814a3..a4cbefd 100644
--- a/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.h
+++ b/chrome/browser/media/router/discovery/mdns/dns_sd_device_lister.h
@@ -10,7 +10,6 @@
 
 #include "base/macros.h"
 #include "chrome/browser/local_discovery/service_discovery_device_lister.h"
-#include "chrome/browser/media/router/discovery/mdns/dns_sd_delegate.h"
 
 namespace local_discovery {
 class ServiceDiscoveryClient;
@@ -18,6 +17,8 @@
 
 namespace media_router {
 
+class DnsSdDelegate;
+
 // Manages a watcher for a specific MDNS/DNS-SD service type and notifies
 // a delegate of changes to watched services.
 class DnsSdDeviceLister
@@ -35,6 +36,7 @@
   void Reset();
 
  protected:
+  // local_discovery::ServiceDiscoveryDeviceLister::Delegate:
   void OnDeviceChanged(
       const std::string& service_type,
       bool added,
@@ -52,7 +54,7 @@
 
   // The client and service type used to create |device_lister_|.
   local_discovery::ServiceDiscoveryClient* const service_discovery_client_;
-  std::string service_type_;
+  const std::string service_type_;
 
   DISALLOW_COPY_AND_ASSIGN(DnsSdDeviceLister);
 };
diff --git a/chrome/browser/media/router/providers/cast/cast_media_route_provider.h b/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
index 502c5b5..14d594c 100644
--- a/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
+++ b/chrome/browser/media/router/providers/cast/cast_media_route_provider.h
@@ -5,6 +5,10 @@
 #ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_ROUTE_PROVIDER_H_
 #define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_CAST_CAST_MEDIA_ROUTE_PROVIDER_H_
 
+#include <memory>
+#include <string>
+#include <vector>
+
 #include "base/containers/flat_map.h"
 #include "base/macros.h"
 #include "chrome/browser/media/router/providers/cast/cast_app_discovery_service.h"
@@ -16,6 +20,10 @@
 class CastMessageHandler;
 }
 
+namespace service_manager {
+class Connector;
+}
+
 namespace url {
 class Origin;
 }
@@ -24,6 +32,7 @@
 
 class CastActivityManager;
 class CastSessionTracker;
+class DataDecoder;
 
 // MediaRouteProvider for Cast sinks. This class may be created on any sequence.
 // All other methods, however, must be called on the task runner provided
diff --git a/chrome/browser/media/router/providers/cast/dual_media_sink_service.h b/chrome/browser/media/router/providers/cast/dual_media_sink_service.h
index b5ea6ca..8ac672c 100644
--- a/chrome/browser/media/router/providers/cast/dual_media_sink_service.h
+++ b/chrome/browser/media/router/providers/cast/dual_media_sink_service.h
@@ -15,8 +15,6 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/sequence_checker.h"
-#include "chrome/browser/media/router/discovery/mdns/cast_media_sink_service.h"
-#include "chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.h"
 #include "chrome/browser/media/router/media_sinks_observer.h"
 #include "chrome/common/media_router/discovery/media_sink_internal.h"
 #include "chrome/common/media_router/media_source.h"
@@ -28,6 +26,7 @@
 class CastMediaSinkService;
 class DialMediaSinkService;
 class DialMediaSinkServiceImpl;
+class MediaSinkServiceBase;
 
 // This class uses DialMediaSinkService and CastMediaSinkService to discover
 // sinks used by the Cast MediaRouteProvider. It also encapsulates the setup
diff --git a/chrome/browser/metrics/power_metrics_provider_mac.mm b/chrome/browser/metrics/power_metrics_provider_mac.mm
index 63a77c4..40c7d2b 100644
--- a/chrome/browser/metrics/power_metrics_provider_mac.mm
+++ b/chrome/browser/metrics/power_metrics_provider_mac.mm
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/power_monitor/power_monitor.h"
 #include "base/process/process.h"
 #include "base/sequenced_task_runner.h"
 #include "base/task/post_task.h"
@@ -239,6 +240,7 @@
       RecordSMC("DuringStartup");
     } else {
       RecordSMC("All");
+      RecordIsOnBattery();
       if (@available(macOS 10.10.3, *)) {
         RecordThermal();
       }
@@ -264,6 +266,13 @@
     }
   }
 
+  void RecordIsOnBattery() {
+    bool is_on_battery = false;
+    if (base::PowerMonitor::IsInitialized())
+      is_on_battery = base::PowerMonitor::IsOnBatteryPower();
+    UMA_HISTOGRAM_BOOLEAN("Power.Mac.IsOnBattery", is_on_battery);
+  }
+
   void RecordThermal() API_AVAILABLE(macos(10.10.3)) {
     UMA_HISTOGRAM_ENUMERATION(
         "Power.Mac.ThermalState",
diff --git a/chrome/browser/offline_pages/android/downloads/offline_page_download_bridge.cc b/chrome/browser/offline_pages/android/downloads/offline_page_download_bridge.cc
index bf5e83c0..d1d5f9d 100644
--- a/chrome/browser/offline_pages/android/downloads/offline_page_download_bridge.cc
+++ b/chrome/browser/offline_pages/android/downloads/offline_page_download_bridge.cc
@@ -38,8 +38,8 @@
 #include "components/offline_items_collection/core/offline_content_provider.h"
 #include "components/offline_pages/core/background/request_coordinator.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/downloads/download_ui_adapter.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_item_utils.h"
 #include "components/offline_pages/core/offline_page_model.h"
@@ -111,7 +111,7 @@
 
 bool DownloadUIAdapterDelegate::IsVisibleInUI(const ClientId& client_id) {
   const std::string& name_space = client_id.name_space;
-  return model_->GetPolicyController()->IsSupportedByDownload(name_space) &&
+  return GetPolicy(name_space).is_supported_by_download &&
          base::IsValidGUID(client_id.id);
 }
 
diff --git a/chrome/browser/offline_pages/android/downloads/offline_page_share_helper.cc b/chrome/browser/offline_pages/android/downloads/offline_page_share_helper.cc
index 60a5882..561d6815 100644
--- a/chrome/browser/offline_pages/android/downloads/offline_page_share_helper.cc
+++ b/chrome/browser/offline_pages/android/downloads/offline_page_share_helper.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/android/download/download_controller_base.h"
 #include "chrome/browser/android/download/download_utils.h"
 #include "chrome/browser/offline_pages/offline_page_mhtml_archiver.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_model.h"
 #include "components/offline_pages/core/page_criteria.h"
@@ -66,9 +67,8 @@
     return;
   }
   const OfflinePageItem& page = pages[0];
-  bool is_suggested =
-      model_->GetPolicyController()->IsSuggested(page.client_id.name_space);
-  bool in_private_dir = model_->IsArchiveInInternalDir(page.file_path);
+  const bool is_suggested = GetPolicy(page.client_id.name_space).is_suggested;
+  const bool in_private_dir = model_->IsArchiveInInternalDir(page.file_path);
 
   // Need to publish internal page to public directory to share the file with
   // content URI instead of the web page URL.
diff --git a/chrome/browser/offline_pages/android/offline_page_bridge.cc b/chrome/browser/offline_pages/android/offline_page_bridge.cc
index 2ee0400..f5782564 100644
--- a/chrome/browser/offline_pages/android/offline_page_bridge.cc
+++ b/chrome/browser/offline_pages/android/offline_page_bridge.cc
@@ -36,7 +36,6 @@
 #include "components/offline_pages/core/archive_validator.h"
 #include "components/offline_pages/core/background/request_queue_results.h"
 #include "components/offline_pages/core/background/save_page_request.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_item.h"
@@ -712,7 +711,7 @@
     const base::android::JavaParamRef<jobject>& obj,
     const base::android::JavaParamRef<jstring>& j_name_space) {
   std::string name_space(ConvertJavaStringToUTF8(env, j_name_space));
-  return (offline_page_model_->GetPolicyController()->IsTemporary(name_space));
+  return GetPolicy(name_space).lifetime_type == LifetimeType::TEMPORARY;
 }
 
 ScopedJavaLocalRef<jobject> OfflinePageBridge::GetOfflinePage(
diff --git a/chrome/browser/offline_pages/background_loader_offliner.cc b/chrome/browser/offline_pages/background_loader_offliner.cc
index b8844d08..d63fc8e 100644
--- a/chrome/browser/offline_pages/background_loader_offliner.cc
+++ b/chrome/browser/offline_pages/background_loader_offliner.cc
@@ -28,6 +28,7 @@
 #include "components/offline_pages/core/background/offliner_policy.h"
 #include "components/offline_pages/core/background/save_page_request.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_model.h"
 #include "components/offline_pages/core/renovations/page_renovation_loader.h"
@@ -147,10 +148,8 @@
     return false;
   }
 
-  ClientPolicyController* policy_controller =
-      offline_page_model_->GetPolicyController();
-  if (policy_controller->RequiresSpecificUserSettings(
-          request.client_id().name_space) &&
+  if (GetPolicy(request.client_id().name_space)
+          .requires_specific_user_settings &&
       (AreThirdPartyCookiesBlocked(browser_context_) ||
        IsNetworkPredictionDisabled(browser_context_))) {
     DVLOG(1) << "WARNING: Unable to load when 3rd party cookies blocked or "
@@ -261,9 +260,8 @@
   // If we want to proceed with the file download, fail with
   // DOWNLOAD_THROTTLED. If we don't want to proceed with the file download,
   // fail with LOADING_FAILED_DOWNLOAD.
-  if (offline_page_model_->GetPolicyController()
-          ->AllowsConversionToBackgroundFileDownload(
-              pending_request_.get()->client_id().name_space)) {
+  if (GetPolicy(pending_request_.get()->client_id().name_space)
+          .allows_conversion_to_background_file_download) {
     should_allow_downloads = true;
     final_status = Offliner::RequestStatus::DOWNLOAD_THROTTLED;
   }
diff --git a/chrome/browser/offline_pages/fresh_offline_content_observer.cc b/chrome/browser/offline_pages/fresh_offline_content_observer.cc
index 39feb93..41e2b2c 100644
--- a/chrome/browser/offline_pages/fresh_offline_content_observer.cc
+++ b/chrome/browser/offline_pages/fresh_offline_content_observer.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/offline_pages/fresh_offline_content_observer.h"
 
 #include "chrome/browser/offline_pages/prefetch/prefetched_pages_notifier.h"
-#include "components/offline_pages/core/client_policy_controller.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_item.h"
 
@@ -36,8 +36,7 @@
 void FreshOfflineContentObserver::OfflinePageAdded(
     OfflinePageModel* model,
     const OfflinePageItem& added_page) {
-  if (model->GetPolicyController()->IsSupportedByDownload(
-          added_page.client_id.name_space)) {
+  if (GetPolicy(added_page.client_id.name_space).is_supported_by_download) {
     OnFreshOfflineContentAvailableForNotification();
   }
 }
diff --git a/chrome/browser/offline_pages/offline_page_tab_helper.cc b/chrome/browser/offline_pages/offline_page_tab_helper.cc
index 9cd8e2a7..4bd40d8 100644
--- a/chrome/browser/offline_pages/offline_page_tab_helper.cc
+++ b/chrome/browser/offline_pages/offline_page_tab_helper.cc
@@ -21,6 +21,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "components/offline_pages/core/background/request_coordinator.h"
 #include "components/offline_pages/core/model/offline_page_model_utils.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_item_utils.h"
 #include "components/offline_pages/core/offline_page_model.h"
@@ -266,7 +267,7 @@
 
   if (offline_page()) {
     // Report prefetch usage.
-    if (policy_controller_.IsSuggested(offline_page()->client_id.name_space))
+    if (GetPolicy(offline_page()->client_id.name_space).is_suggested)
       metrics_collector->OnPrefetchedPageOpened();
     // Note that navigation to offline page may happen even if network is
     // connected. For the purposes of collecting offline usage statistics,
diff --git a/chrome/browser/offline_pages/offline_page_tab_helper.h b/chrome/browser/offline_pages/offline_page_tab_helper.h
index 2d40cd3..7f703d6 100644
--- a/chrome/browser/offline_pages/offline_page_tab_helper.h
+++ b/chrome/browser/offline_pages/offline_page_tab_helper.h
@@ -211,11 +211,6 @@
   // Service, outlives this object.
   PrefetchService* prefetch_service_ = nullptr;
 
-  // Table of OfflinePages policies.
-  // TODO(dimich): When we only have one shared version of PolicyController,
-  // replace this instance with access to a shared one.
-  ClientPolicyController policy_controller_;
-
   // TODO(crbug.com/827215): We only really want interface messages for the main
   // frame but this is not easily done with the current helper classes.
   content::WebContentsFrameBindingSet<mojom::MhtmlPageNotifier>
diff --git a/chrome/browser/offline_pages/offline_page_utils.cc b/chrome/browser/offline_pages/offline_page_utils.cc
index b8d02b5..c12fe77 100644
--- a/chrome/browser/offline_pages/offline_page_utils.cc
+++ b/chrome/browser/offline_pages/offline_page_utils.cc
@@ -28,8 +28,8 @@
 #include "components/offline_pages/core/background/request_coordinator.h"
 #include "components/offline_pages/core/background/save_page_request.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_clock.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_item_utils.h"
@@ -60,13 +60,7 @@
 
 bool IsSupportedByDownload(content::BrowserContext* browser_context,
                            const std::string& name_space) {
-  OfflinePageModel* offline_page_model =
-      OfflinePageModelFactory::GetForBrowserContext(browser_context);
-  DCHECK(offline_page_model);
-  ClientPolicyController* policy_controller =
-      offline_page_model->GetPolicyController();
-  DCHECK(policy_controller);
-  return policy_controller->IsSupportedByDownload(name_space);
+  return GetPolicy(name_space).is_supported_by_download;
 }
 
 void CheckDuplicateOngoingDownloads(
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
index 750a80c..20ad898 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_unittest.cc
@@ -1128,6 +1128,10 @@
   EXPECT_EQ(1u, entries.size());
   test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_TotalName, 1500);
+  test_ukm_recorder().ExpectEntryMetric(
+      entries.front(),
+      ukm::builders::AdFrameLoad::kCpuTime_PeakWindowedPercentName,
+      100 * 1500 / 30000);
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_PreActivationName));
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
@@ -1182,6 +1186,10 @@
   EXPECT_EQ(1u, entries.size());
   test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_TotalName, 1500);
+  test_ukm_recorder().ExpectEntryMetric(
+      entries.front(),
+      ukm::builders::AdFrameLoad::kCpuTime_PeakWindowedPercentName,
+      100 * 1500 / 30000);
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_PreActivationName));
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
@@ -1240,6 +1248,10 @@
   test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_TotalName, 1500);
   test_ukm_recorder().ExpectEntryMetric(
+      entries.front(),
+      ukm::builders::AdFrameLoad::kCpuTime_PeakWindowedPercentName,
+      100 * 1000 / 30000);
+  test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_PreActivationName,
       1000);
   test_ukm_recorder().ExpectEntryMetric(
@@ -1293,6 +1305,9 @@
   EXPECT_EQ(1u, entries.size());
   test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_TotalName, 0);
+  test_ukm_recorder().ExpectEntryMetric(
+      entries.front(),
+      ukm::builders::AdFrameLoad::kCpuTime_PeakWindowedPercentName, 0);
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_PreActivationName));
   EXPECT_FALSE(test_ukm_recorder().EntryHasMetric(
@@ -1330,6 +1345,10 @@
   EXPECT_EQ(1u, entries.size());
   test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_TotalName, 500);
+  test_ukm_recorder().ExpectEntryMetric(
+      entries.front(),
+      ukm::builders::AdFrameLoad::kCpuTime_PeakWindowedPercentName,
+      100 * 500 / 30000);
 }
 
 TEST_F(AdsPageLoadMetricsObserverTest, TestCpuTimingMetricsShortTimeframes) {
@@ -1373,6 +1392,10 @@
   EXPECT_EQ(1u, entries.size());
   test_ukm_recorder().ExpectEntryMetric(
       entries.front(), ukm::builders::AdFrameLoad::kCpuTime_TotalName, 1500);
+  test_ukm_recorder().ExpectEntryMetric(
+      entries.front(),
+      ukm::builders::AdFrameLoad::kCpuTime_PeakWindowedPercentName,
+      100 * 1500 / 30000);
 }
 
 TEST_F(AdsPageLoadMetricsObserverTest, AdFrameLoadTiming) {
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc
index 8ef3b3d..8a62600 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/frame_data.cc
@@ -248,6 +248,8 @@
         pre_activation_foreground_duration().InMilliseconds());
   }
 
+  builder.SetCpuTime_PeakWindowedPercent(peak_windowed_cpu_percent_);
+
   builder
       .SetVisibility_FrameWidth(
           ukm::GetExponentialBucketMinForCounts1000(frame_size().width()))
diff --git a/chrome/browser/predictors/loading_predictor_unittest.cc b/chrome/browser/predictors/loading_predictor_unittest.cc
index c168cd5..75cfada 100644
--- a/chrome/browser/predictors/loading_predictor_unittest.cc
+++ b/chrome/browser/predictors/loading_predictor_unittest.cc
@@ -49,8 +49,10 @@
   MOCK_METHOD1(StartPreresolveHost, void(const GURL& url));
   MOCK_METHOD1(StartPreresolveHosts,
                void(const std::vector<std::string>& hostnames));
-  MOCK_METHOD2(StartPreconnectUrl,
-               void(const GURL& url, bool allow_credentials));
+  MOCK_METHOD3(StartPreconnectUrl,
+               void(const GURL& url,
+                    bool allow_credentials,
+                    net::NetworkIsolationKey network_isolation_key));
   MOCK_METHOD1(Stop, void(const GURL& url));
 
   void Start(const GURL& url,
@@ -303,7 +305,8 @@
 TEST_F(LoadingPredictorPreconnectTest, TestHandleOmniboxHint) {
   const GURL preconnect_suggestion = GURL("http://search.com/kittens");
   EXPECT_CALL(*mock_preconnect_manager_,
-              StartPreconnectUrl(preconnect_suggestion, true));
+              StartPreconnectUrl(preconnect_suggestion, true,
+                                 net::NetworkIsolationKey()));
   predictor_->PrepareForPageLoad(preconnect_suggestion, HintOrigin::OMNIBOX,
                                  true);
   // The second suggestion for the same host should be filtered out.
diff --git a/chrome/browser/predictors/loading_test_util.cc b/chrome/browser/predictors/loading_test_util.cc
index 8cb9ace..ab0ec9af 100644
--- a/chrome/browser/predictors/loading_test_util.cc
+++ b/chrome/browser/predictors/loading_test_util.cc
@@ -31,11 +31,12 @@
 MockResourcePrefetchPredictor::~MockResourcePrefetchPredictor() = default;
 
 void InitializeRedirectStat(RedirectStat* redirect,
-                            const std::string& url,
+                            const GURL& url,
                             int number_of_hits,
                             int number_of_misses,
                             int consecutive_misses) {
-  redirect->set_url(url);
+  redirect->set_url(url.host());
+  redirect->set_url_scheme(url.scheme());
   redirect->set_number_of_hits(number_of_hits);
   redirect->set_number_of_misses(number_of_misses);
   redirect->set_consecutive_misses(consecutive_misses);
@@ -168,9 +169,9 @@
 }
 
 std::ostream& operator<<(std::ostream& os, const RedirectStat& redirect) {
-  return os << "[" << redirect.url() << "," << redirect.number_of_hits() << ","
-            << redirect.number_of_misses() << ","
-            << redirect.consecutive_misses() << "]";
+  return os << "[" << redirect.url() << "," << redirect.url_scheme() << ","
+            << redirect.number_of_hits() << "," << redirect.number_of_misses()
+            << "," << redirect.consecutive_misses() << "]";
 }
 
 std::ostream& operator<<(std::ostream& os, const OriginData& data) {
@@ -237,7 +238,7 @@
 }
 
 bool operator==(const RedirectStat& lhs, const RedirectStat& rhs) {
-  return lhs.url() == rhs.url() &&
+  return lhs.url() == rhs.url() && lhs.url_scheme() == rhs.url_scheme() &&
          lhs.number_of_hits() == rhs.number_of_hits() &&
          lhs.number_of_misses() == rhs.number_of_misses() &&
          lhs.consecutive_misses() == rhs.consecutive_misses();
diff --git a/chrome/browser/predictors/loading_test_util.h b/chrome/browser/predictors/loading_test_util.h
index fdfe434..8923b83 100644
--- a/chrome/browser/predictors/loading_test_util.h
+++ b/chrome/browser/predictors/loading_test_util.h
@@ -36,7 +36,7 @@
 };
 
 void InitializeRedirectStat(RedirectStat* redirect,
-                            const std::string& url,
+                            const GURL& url,
                             int number_of_hits,
                             int number_of_misses,
                             int consecutive_misses);
diff --git a/chrome/browser/predictors/preconnect_manager.cc b/chrome/browser/predictors/preconnect_manager.cc
index b095937..cdee4d11 100644
--- a/chrome/browser/predictors/preconnect_manager.cc
+++ b/chrome/browser/predictors/preconnect_manager.cc
@@ -46,10 +46,23 @@
 PreresolveJob::PreresolveJob(const GURL& url,
                              int num_sockets,
                              bool allow_credentials,
+                             net::NetworkIsolationKey network_isolation_key,
                              PreresolveInfo* info)
     : url(url),
       num_sockets(num_sockets),
       allow_credentials(allow_credentials),
+      network_isolation_key(std::move(network_isolation_key)),
+      info(info) {
+  DCHECK_GE(num_sockets, 0);
+}
+
+PreresolveJob::PreresolveJob(PreconnectRequest preconnect_request,
+                             PreresolveInfo* info)
+    : url(std::move(preconnect_request.origin)),
+      num_sockets(preconnect_request.num_sockets),
+      allow_credentials(preconnect_request.allow_credentials),
+      network_isolation_key(
+          std::move(preconnect_request.network_isolation_key)),
       info(info) {
   DCHECK_GE(num_sockets, 0);
 }
@@ -79,11 +92,11 @@
       host, std::make_unique<PreresolveInfo>(url, requests.size()));
   PreresolveInfo* info = iterator_and_whether_inserted.first->second.get();
 
-  for (const auto& request : requests) {
-    DCHECK(request.origin.GetOrigin() == request.origin);
+  for (auto request_it = requests.begin(); request_it != requests.end();
+       ++request_it) {
+    DCHECK(request_it->origin.GetOrigin() == request_it->origin);
     PreresolveJobId job_id = preresolve_jobs_.Add(
-        std::make_unique<PreresolveJob>(request.origin, request.num_sockets,
-                                        request.allow_credentials, info));
+        std::make_unique<PreresolveJob>(std::move(*request_it), info));
     queued_jobs_.push_back(job_id);
   }
 
@@ -95,7 +108,8 @@
   if (!url.SchemeIsHTTPOrHTTPS())
     return;
   PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
-      url.GetOrigin(), 0, kAllowCredentialsOnPreconnectByDefault, nullptr));
+      url.GetOrigin(), 0, kAllowCredentialsOnPreconnectByDefault,
+      net::NetworkIsolationKey(), nullptr));
   queued_jobs_.push_front(job_id);
 
   TryToLaunchPreresolveJobs();
@@ -109,20 +123,23 @@
     PreresolveJobId job_id =
         preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
             GURL("http://" + *it), 0, kAllowCredentialsOnPreconnectByDefault,
-            nullptr));
+            net::NetworkIsolationKey(), nullptr));
     queued_jobs_.push_front(job_id);
   }
 
   TryToLaunchPreresolveJobs();
 }
 
-void PreconnectManager::StartPreconnectUrl(const GURL& url,
-                                           bool allow_credentials) {
+void PreconnectManager::StartPreconnectUrl(
+    const GURL& url,
+    bool allow_credentials,
+    net::NetworkIsolationKey network_isolation_key) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   if (!url.SchemeIsHTTPOrHTTPS())
     return;
   PreresolveJobId job_id = preresolve_jobs_.Add(std::make_unique<PreresolveJob>(
-      url.GetOrigin(), 1, allow_credentials, nullptr));
+      url.GetOrigin(), 1, allow_credentials, std::move(network_isolation_key),
+      nullptr));
   queued_jobs_.push_front(job_id);
 
   TryToLaunchPreresolveJobs();
@@ -138,9 +155,11 @@
   it->second->was_canceled = true;
 }
 
-void PreconnectManager::PreconnectUrl(const GURL& url,
-                                      int num_sockets,
-                                      bool allow_credentials) const {
+void PreconnectManager::PreconnectUrl(
+    const GURL& url,
+    int num_sockets,
+    bool allow_credentials,
+    const net::NetworkIsolationKey& network_isolation_key) const {
   DCHECK(url.GetOrigin() == url);
   DCHECK(url.SchemeIsHTTPOrHTTPS());
   if (observer_)
@@ -159,9 +178,8 @@
                  net::LOAD_DO_NOT_SEND_AUTH_DATA;
   }
 
-  // TODO(mmenke): Use an appropriate NetworkIsolationKey().
   network_context->PreconnectSockets(num_sockets, url, load_flags, privacy_mode,
-                                     net::NetworkIsolationKey());
+                                     network_isolation_key);
 }
 
 std::unique_ptr<ResolveHostClientImpl> PreconnectManager::PreresolveUrl(
@@ -275,8 +293,10 @@
   DCHECK(job);
 
   bool need_preconnect = success && job->need_preconnect();
-  if (need_preconnect)
-    PreconnectUrl(job->url, job->num_sockets, job->allow_credentials);
+  if (need_preconnect) {
+    PreconnectUrl(job->url, job->num_sockets, job->allow_credentials,
+                  job->network_isolation_key);
+  }
 
   PreresolveInfo* info = job->info;
   if (info)
diff --git a/chrome/browser/predictors/preconnect_manager.h b/chrome/browser/predictors/preconnect_manager.h
index e5962a0c..51a842d 100644
--- a/chrome/browser/predictors/preconnect_manager.h
+++ b/chrome/browser/predictors/preconnect_manager.h
@@ -17,6 +17,7 @@
 #include "chrome/browser/predictors/proxy_lookup_client_impl.h"
 #include "chrome/browser/predictors/resolve_host_client_impl.h"
 #include "chrome/browser/predictors/resource_prefetch_predictor.h"
+#include "net/base/network_isolation_key.h"
 #include "url/gurl.h"
 
 class Profile;
@@ -75,7 +76,9 @@
   PreresolveJob(const GURL& url,
                 int num_sockets,
                 bool allow_credentials,
+                net::NetworkIsolationKey network_isolation_key,
                 PreresolveInfo* info);
+  PreresolveJob(PreconnectRequest preconnect_request, PreresolveInfo* info);
   PreresolveJob(PreresolveJob&& other);
   ~PreresolveJob();
   bool need_preconnect() const {
@@ -85,8 +88,9 @@
   GURL url;
   int num_sockets;
   bool allow_credentials;
+  net::NetworkIsolationKey network_isolation_key;
   // Raw pointer usage is fine here because even though PreresolveJob can
-  // outlive PreresolveInfo it's only accessed on PreconnectManager class
+  // outlive PreresolveInfo. It's only accessed on PreconnectManager class
   // context and PreresolveInfo lifetime is tied to PreconnectManager.
   // May be equal to nullptr in case of detached job.
   PreresolveInfo* info;
@@ -145,7 +149,19 @@
   // than trackable requests thus they are put in the front of the jobs queue.
   virtual void StartPreresolveHost(const GURL& url);
   virtual void StartPreresolveHosts(const std::vector<std::string>& hostnames);
-  virtual void StartPreconnectUrl(const GURL& url, bool allow_credentials);
+  // |network_isolation_key| specifies the key that network requests for the
+  // preconnected URL are expected to use. If a request is issued with a
+  // different key, it may not use the preconnected socket.
+  //
+  // TODO(https://crbug.com/966896): Update consumers and make
+  // |network_isolation_key| a mandatory argument. Note that this is a temporary
+  // style guide violation, but keeping this until all consumers correctly fill
+  // the argument reduces the chances of forgetting to update one.
+  virtual void StartPreconnectUrl(
+      const GURL& url,
+      bool allow_credentials,
+      net::NetworkIsolationKey network_isolation_key =
+          net::NetworkIsolationKey());
 
   // No additional jobs keyed by the |url| will be queued after this.
   virtual void Stop(const GURL& url);
@@ -166,9 +182,11 @@
   using PreresolveJobId = PreresolveJobMap::KeyType;
   friend class PreconnectManagerTest;
 
-  void PreconnectUrl(const GURL& url,
-                     int num_sockets,
-                     bool allow_credentials) const;
+  void PreconnectUrl(
+      const GURL& url,
+      int num_sockets,
+      bool allow_credentials,
+      const net::NetworkIsolationKey& network_isolation_key) const;
   std::unique_ptr<ResolveHostClientImpl> PreresolveUrl(
       const GURL& url,
       ResolveHostCallback callback) const;
diff --git a/chrome/browser/predictors/preconnect_manager_unittest.cc b/chrome/browser/predictors/preconnect_manager_unittest.cc
index 4bda676..c9161f8 100644
--- a/chrome/browser/predictors/preconnect_manager_unittest.cc
+++ b/chrome/browser/predictors/preconnect_manager_unittest.cc
@@ -219,6 +219,27 @@
   mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
 }
 
+TEST_F(PreconnectManagerTest,
+       TestStartOneUrlPreconnectWithNetworkIsolationKey) {
+  GURL main_frame_url("http://google.com");
+  GURL url_to_preconnect("http://cdn.google.com");
+  url::Origin requesting_origin = url::Origin::Create(GURL("http://foo.test"));
+  net::NetworkIsolationKey network_isolation_key(requesting_origin,
+                                                 requesting_origin);
+
+  EXPECT_CALL(*mock_network_context_,
+              ResolveHostProxy(url_to_preconnect.host()));
+  preconnect_manager_->Start(
+      main_frame_url,
+      {PreconnectRequest(url_to_preconnect, 1, network_isolation_key)});
+  EXPECT_CALL(*mock_network_context_,
+              PreconnectSockets(1, url_to_preconnect, kNormalLoadFlags,
+                                false /* privacy_mode_enabled */,
+                                network_isolation_key));
+  EXPECT_CALL(*mock_delegate_, PreconnectFinishedProxy(main_frame_url));
+  mock_network_context_->CompleteHostLookup(url_to_preconnect.host(), net::OK);
+}
+
 // Sends preconnect request for a webpage, and stops the request before
 // all pertaining preconnect requests finish. Next, preconnect request
 // for the same webpage is sent again. Verifies that all the preconnects
@@ -599,7 +620,8 @@
   bool allow_credentials = false;
 
   EXPECT_CALL(*mock_network_context_, ResolveHostProxy(origin.host()));
-  preconnect_manager_->StartPreconnectUrl(url, allow_credentials);
+  preconnect_manager_->StartPreconnectUrl(url, allow_credentials,
+                                          net::NetworkIsolationKey());
 
   EXPECT_CALL(
       *mock_network_context_,
@@ -609,7 +631,26 @@
 
   // Non http url shouldn't be preconnected.
   GURL non_http_url("file:///tmp/index.html");
-  preconnect_manager_->StartPreconnectUrl(non_http_url, allow_credentials);
+  preconnect_manager_->StartPreconnectUrl(non_http_url, allow_credentials,
+                                          net::NetworkIsolationKey());
+}
+
+TEST_F(PreconnectManagerTest, TestStartPreconnectUrlWithNetworkIsolationKey) {
+  GURL url("http://cdn.google.com/script.js");
+  GURL origin("http://cdn.google.com");
+  bool allow_credentials = false;
+  url::Origin requesting_origin = url::Origin::Create(GURL("http://foo.test"));
+  net::NetworkIsolationKey network_isolation_key(requesting_origin,
+                                                 requesting_origin);
+
+  EXPECT_CALL(*mock_network_context_, ResolveHostProxy(origin.host()));
+  preconnect_manager_->StartPreconnectUrl(url, allow_credentials,
+                                          network_isolation_key);
+
+  EXPECT_CALL(*mock_network_context_,
+              PreconnectSockets(1, origin, kPrivateLoadFlags,
+                                !allow_credentials, network_isolation_key));
+  mock_network_context_->CompleteHostLookup(origin.host(), net::OK);
 }
 
 TEST_F(PreconnectManagerTest, TestDetachedRequestHasHigherPriority) {
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.cc b/chrome/browser/predictors/resource_prefetch_predictor.cc
index 808be8f1..ef3b4e7 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor.cc
@@ -59,8 +59,13 @@
 
 }  // namespace
 
-PreconnectRequest::PreconnectRequest(const GURL& origin, int num_sockets)
-    : origin(origin), num_sockets(num_sockets) {
+PreconnectRequest::PreconnectRequest(
+    const GURL& origin,
+    int num_sockets,
+    net::NetworkIsolationKey network_isolation_key)
+    : origin(origin),
+      num_sockets(num_sockets),
+      network_isolation_key(std::move(network_isolation_key)) {
   DCHECK_GE(num_sockets, 0);
 }
 
@@ -188,9 +193,10 @@
     return;
   }
 
-  const std::string& host = summary->main_frame_url.host();
-  LearnRedirect(summary->initial_url.host(), host, host_redirect_data_.get());
-  LearnOrigins(host, summary->main_frame_url.GetOrigin(), summary->origins);
+  LearnRedirect(summary->initial_url.host(), summary->main_frame_url,
+                host_redirect_data_.get());
+  LearnOrigins(summary->main_frame_url.host(),
+               summary->main_frame_url.GetOrigin(), summary->origins);
 
   if (observer_)
     observer_->OnNavigationLearned(*summary);
@@ -287,7 +293,7 @@
 }
 
 void ResourcePrefetchPredictor::LearnRedirect(const std::string& key,
-                                              const std::string& final_redirect,
+                                              const GURL& final_redirect,
                                               RedirectDataMap* redirect_data) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   // If the primary key is too long reject it.
@@ -300,18 +306,36 @@
     data.set_primary_key(key);
     data.set_last_visit_time(base::Time::Now().ToInternalValue());
     RedirectStat* redirect_to_add = data.add_redirect_endpoints();
-    redirect_to_add->set_url(final_redirect);
+    redirect_to_add->set_url(final_redirect.host());
     redirect_to_add->set_number_of_hits(1);
+    redirect_to_add->set_url_scheme(final_redirect.scheme());
   } else {
     data.set_last_visit_time(base::Time::Now().ToInternalValue());
 
     bool need_to_add = true;
     for (RedirectStat& redirect : *(data.mutable_redirect_endpoints())) {
-      if (redirect.url() == final_redirect) {
+      const bool host_mismatch = redirect.url() != final_redirect.host();
+
+      // When the existing scheme in database is empty, then difference in
+      // schemes is not considered a scheme mismatch. This case is treated
+      // specially since scheme was added later to the database, and previous
+      // entries would have empty scheme. In such case, we do not consider this
+      // as a mismatch, and simply update the scheme in the database.
+      const bool url_scheme_mismatch =
+          !redirect.url_scheme().empty() &&
+          redirect.url_scheme() != final_redirect.scheme();
+
+      if (!host_mismatch && !url_scheme_mismatch) {
+        // No mismatch.
         need_to_add = false;
         redirect.set_number_of_hits(redirect.number_of_hits() + 1);
         redirect.set_consecutive_misses(0);
+
+        // If existing scheme in database is empty, then update it.
+        if (redirect.url_scheme().empty())
+          redirect.set_url_scheme(final_redirect.scheme());
       } else {
+        // A real mismatch.
         redirect.set_number_of_misses(redirect.number_of_misses() + 1);
         redirect.set_consecutive_misses(redirect.consecutive_misses() + 1);
       }
@@ -319,8 +343,9 @@
 
     if (need_to_add) {
       RedirectStat* redirect_to_add = data.add_redirect_endpoints();
-      redirect_to_add->set_url(final_redirect);
+      redirect_to_add->set_url(final_redirect.host());
       redirect_to_add->set_number_of_hits(1);
+      redirect_to_add->set_url_scheme(final_redirect.scheme());
     }
   }
 
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.h b/chrome/browser/predictors/resource_prefetch_predictor.h
index 0fb21eb..c43d5ed 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.h
+++ b/chrome/browser/predictors/resource_prefetch_predictor.h
@@ -16,6 +16,7 @@
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "base/time/time.h"
@@ -28,6 +29,7 @@
 #include "components/history/core/browser/history_types.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "content/public/common/resource_type.h"
+#include "net/base/network_isolation_key.h"
 #include "url/gurl.h"
 
 class PredictorsHandler;
@@ -54,12 +56,23 @@
 // Stores all values needed to trigger a preconnect/preresolve job to a single
 // origin.
 struct PreconnectRequest {
-  PreconnectRequest(const GURL& origin, int num_sockets);
+  // |network_isolation_key| specifies the key that network requests for the
+  // preconnected URL are expected to use. If a request is issued with a
+  // different key, it may not use the preconnected socket. It has no effect
+  // when |num_sockets| == 0.
+  //
+  // TODO(https://crbug.com/966896): Update consumers and make
+  // |network_isolation_key| a mandatory argument.
+  PreconnectRequest(const GURL& origin,
+                    int num_sockets,
+                    net::NetworkIsolationKey network_isolation_key =
+                        net::NetworkIsolationKey());
 
   GURL origin;
   // A zero-value means that we need to preresolve a host only.
   int num_sockets = 0;
   bool allow_credentials = true;
+  net::NetworkIsolationKey network_isolation_key;
 };
 
 // Stores a result of preconnect prediction. The |requests| vector is the main
@@ -224,7 +237,7 @@
   // Updates information about final redirect destination for the |key| in
   // |redirect_data| and correspondingly updates the predictor database.
   void LearnRedirect(const std::string& key,
-                     const std::string& final_redirect,
+                     const GURL& final_redirect,
                      RedirectDataMap* redirect_data);
 
   void LearnOrigins(const std::string& host,
diff --git a/chrome/browser/predictors/resource_prefetch_predictor.proto b/chrome/browser/predictors/resource_prefetch_predictor.proto
index 1ceed7d..2181648 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor.proto
+++ b/chrome/browser/predictors/resource_prefetch_predictor.proto
@@ -14,12 +14,19 @@
 option optimize_for = LITE_RUNTIME;
 
 // Represents a single redirect chain endpoint.
+// When adding a field here, please also update the equality operator and the
+// output operator in
+// chrome/browser/predictors/loading_test_util.cc.
 message RedirectStat {
   // Represents the host for RedirectData in a host-based table.
   optional string url = 1;
   optional uint32 number_of_hits = 2;
   optional uint32 number_of_misses = 3;
   optional uint32 consecutive_misses = 4;
+  // |url_scheme| is typically either "http" or "https". This field was added
+  // in M-77 without wiping the database. As such, it may be empty in some
+  // cases even when originally |url| had a non-empty scheme.
+  optional string url_scheme = 5;
 }
 
 // Represents a mapping from URL or host to a list of redirect endpoints.
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_tables_unittest.cc b/chrome/browser/predictors/resource_prefetch_predictor_tables_unittest.cc
index cafa2003..f84761fb 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_tables_unittest.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_tables_unittest.cc
@@ -69,9 +69,11 @@
   void TestOriginStatsAreEqual(const std::vector<OriginStat>& lhs,
                                const std::vector<OriginStat>& rhs) const;
 
-  void AddKey(RedirectDataMap* m, const std::string& key) const;
+  void AddKey(RedirectDataMap* m, const GURL& url) const;
   void AddKey(OriginDataMap* m, const std::string& key) const;
 
+  std::string GetKeyForRedirectStat(const RedirectStat& stat) const;
+
   RedirectDataMap test_host_redirect_data_;
   OriginDataMap test_origin_data_;
 };
@@ -140,7 +142,7 @@
 
   RedirectDataMap expected_host_redirect_data;
   OriginDataMap expected_origin_data;
-  AddKey(&expected_host_redirect_data, "bbc.com");
+  AddKey(&expected_host_redirect_data, GURL("http://bbc.com"));
   AddKey(&expected_origin_data, "abc.xyz");
 
   TestRedirectDataAreEqual(expected_host_redirect_data,
@@ -150,10 +152,10 @@
 
 void ResourcePrefetchPredictorTablesTest::TestUpdateData() {
   RedirectData microsoft = CreateRedirectData("microsoft.com", 21);
-  InitializeRedirectStat(microsoft.add_redirect_endpoints(), "m.microsoft.com",
-                         5, 7, 1);
-  InitializeRedirectStat(microsoft.add_redirect_endpoints(), "microsoft.org", 7,
-                         2, 0);
+  InitializeRedirectStat(microsoft.add_redirect_endpoints(),
+                         GURL("https://m.microsoft.com"), 5, 7, 1);
+  InitializeRedirectStat(microsoft.add_redirect_endpoints(),
+                         GURL("https://microsoft.org"), 7, 2, 0);
 
   tables_->ExecuteDBTaskOnDBSequence(
       base::BindOnce(&LoadingPredictorKeyValueTable<RedirectData>::UpdateData,
@@ -174,7 +176,7 @@
   RedirectDataMap expected_host_redirect_data;
   OriginDataMap expected_origin_data;
 
-  AddKey(&expected_host_redirect_data, "bbc.com");
+  AddKey(&expected_host_redirect_data, GURL("https://bbc.com"));
   expected_host_redirect_data.insert(
       std::make_pair("microsoft.com", microsoft));
 
@@ -225,16 +227,19 @@
 
   std::map<std::string, RedirectStat> lhs_index;
   // Repeated redirects are not allowed.
-  for (const auto& r : lhs)
-    EXPECT_TRUE(lhs_index.insert(std::make_pair(r.url(), r)).second);
+  for (const auto& r : lhs) {
+    EXPECT_TRUE(
+        lhs_index.insert(std::make_pair(GetKeyForRedirectStat(r), r)).second)
+        << " r.url()=" << r.url();
+  }
 
   for (const auto& r : rhs) {
-    auto lhs_it = lhs_index.find(r.url());
+    auto lhs_it = lhs_index.find(GetKeyForRedirectStat(r));
     if (lhs_it != lhs_index.end()) {
       EXPECT_EQ(r, lhs_it->second);
       lhs_index.erase(lhs_it);
     } else {
-      ADD_FAILURE() << r.url();
+      ADD_FAILURE() << r.url() << " " << r.url_scheme();
     }
   }
 
@@ -282,8 +287,8 @@
 }
 
 void ResourcePrefetchPredictorTablesTest::AddKey(RedirectDataMap* m,
-                                                 const std::string& key) const {
-  auto it = test_host_redirect_data_.find(key);
+                                                 const GURL& url) const {
+  auto it = test_host_redirect_data_.find(url.host());
   EXPECT_TRUE(it != test_host_redirect_data_.end());
   m->insert(*it);
 }
@@ -295,6 +300,11 @@
   m->insert(*it);
 }
 
+std::string ResourcePrefetchPredictorTablesTest::GetKeyForRedirectStat(
+    const RedirectStat& stat) const {
+  return stat.url() + "," + stat.url_scheme();
+}
+
 void ResourcePrefetchPredictorTablesTest::DeleteAllData() {
   tables_->ExecuteDBTaskOnDBSequence(base::BindOnce(
       &LoadingPredictorKeyValueTable<RedirectData>::DeleteAllData,
@@ -318,14 +328,22 @@
 void ResourcePrefetchPredictorTablesTest::InitializeSampleData() {
   {  // Host redirect data.
     RedirectData bbc = CreateRedirectData("bbc.com", 9);
-    InitializeRedirectStat(bbc.add_redirect_endpoints(), "www.bbc.com", 8, 4,
-                           1);
-    InitializeRedirectStat(bbc.add_redirect_endpoints(), "m.bbc.com", 5, 8, 0);
-    InitializeRedirectStat(bbc.add_redirect_endpoints(), "bbc.co.uk", 1, 3, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("https://www.bbc.com"), 8, 4, 1);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("https://m.bbc.com"), 5, 8, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("https://bbc.co.uk"), 1, 3, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("http://www.bbc.com"), 8, 4, 1);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("http://m.bbc.com"), 5, 8, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("http://bbc.co.uk"), 1, 3, 0);
 
     RedirectData microsoft = CreateRedirectData("microsoft.com", 10);
     InitializeRedirectStat(microsoft.add_redirect_endpoints(),
-                           "www.microsoft.com", 10, 0, 0);
+                           GURL("https://www.microsoft.com"), 10, 0, 0);
 
     test_host_redirect_data_.clear();
     test_host_redirect_data_.insert(std::make_pair(bbc.primary_key(), bbc));
diff --git a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
index 9b318b3..a6e00f2 100644
--- a/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
+++ b/chrome/browser/predictors/resource_prefetch_predictor_unittest.cc
@@ -187,14 +187,18 @@
 void ResourcePrefetchPredictorTest::InitializeSampleData() {
   {  // Host redirect data.
     RedirectData bbc = CreateRedirectData("bbc.com", 9);
-    InitializeRedirectStat(bbc.add_redirect_endpoints(), "www.bbc.com", 8, 4,
-                           1);
-    InitializeRedirectStat(bbc.add_redirect_endpoints(), "m.bbc.com", 5, 8, 0);
-    InitializeRedirectStat(bbc.add_redirect_endpoints(), "bbc.co.uk", 1, 3, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("https://www.bbc.com"), 8, 4, 1);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("https://m.bbc.com"), 5, 8, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("http://bbc.co.uk"), 1, 3, 0);
+    InitializeRedirectStat(bbc.add_redirect_endpoints(),
+                           GURL("https://bbc.co.uk"), 1, 3, 0);
 
     RedirectData microsoft = CreateRedirectData("microsoft.com", 10);
     InitializeRedirectStat(microsoft.add_redirect_endpoints(),
-                           "www.microsoft.com", 10, 0, 0);
+                           GURL("https://www.microsoft.com"), 10, 0, 0);
 
     test_host_redirect_data_.clear();
     test_host_redirect_data_.insert(std::make_pair(bbc.primary_key(), bbc));
@@ -295,7 +299,7 @@
 
   RedirectData host_redirect_data = CreateRedirectData("www.google.com");
   InitializeRedirectStat(host_redirect_data.add_redirect_endpoints(),
-                         "www.google.com", 1, 0, 0);
+                         GURL("http://www.google.com"), 1, 0, 0);
   EXPECT_EQ(mock_tables_->host_redirect_table_.data_,
             RedirectDataMap(
                 {{host_redirect_data.primary_key(), host_redirect_data}}));
@@ -339,7 +343,7 @@
 
   RedirectData host_redirect_data = CreateRedirectData("www.google.com");
   InitializeRedirectStat(host_redirect_data.add_redirect_endpoints(),
-                         "www.google.com", 1, 0, 0);
+                         GURL("http://www.google.com"), 1, 0, 0);
   EXPECT_EQ(mock_tables_->host_redirect_table_.data_,
             RedirectDataMap(
                 {{host_redirect_data.primary_key(), host_redirect_data}}));
@@ -381,7 +385,7 @@
 
   RedirectData host_redirect_data = CreateRedirectData("www.nike.com");
   InitializeRedirectStat(host_redirect_data.add_redirect_endpoints(),
-                         "www.nike.com", 1, 0, 0);
+                         GURL("http://www.nike.com"), 1, 0, 0);
   EXPECT_EQ(mock_tables_->host_redirect_table_.data_,
             RedirectDataMap(
                 {{host_redirect_data.primary_key(), host_redirect_data}}));
@@ -451,7 +455,7 @@
 
   RedirectData host_redirect_data = CreateRedirectData("fb.com");
   InitializeRedirectStat(host_redirect_data.add_redirect_endpoints(),
-                         "facebook.com", 1, 0, 0);
+                         GURL("https://facebook.com"), 1, 0, 0);
   EXPECT_EQ(mock_tables_->host_redirect_table_.data_,
             RedirectDataMap(
                 {{host_redirect_data.primary_key(), host_redirect_data}}));
@@ -480,7 +484,7 @@
 
   RedirectData host_redirect_data = CreateRedirectData("fb.com");
   InitializeRedirectStat(host_redirect_data.add_redirect_endpoints(),
-                         "facebook.com", 1, 0, 0);
+                         GURL("https://facebook.com"), 1, 0, 0);
   RedirectDataMap expected_host_redirect_data = test_host_redirect_data_;
   expected_host_redirect_data.erase("bbc.com");
   expected_host_redirect_data[host_redirect_data.primary_key()] =
@@ -489,6 +493,85 @@
             expected_host_redirect_data);
 }
 
+// Tests that redirect is recorded correctly for URL already present in
+// the database cache. Test with both https and http schemes for the same
+// host.
+TEST_F(ResourcePrefetchPredictorTest, RedirectUrlInDB_MultipleSchemes) {
+  mock_tables_->host_redirect_table_.data_ = test_host_redirect_data_;
+
+  ResetPredictor();
+  InitializePredictor();
+
+  {
+    std::vector<content::mojom::ResourceLoadInfoPtr> resources_https;
+    resources_https.push_back(CreateResourceLoadInfoWithRedirects(
+        {"https://fb.com/google", "https://facebook.com/google"}));
+    auto page_summary_https =
+        CreatePageRequestSummary("https://facebook.com/google",
+                                 "https://fb.com/google", resources_https);
+
+    StrictMock<MockResourcePrefetchPredictorObserver> mock_observer(predictor_);
+    EXPECT_CALL(mock_observer, OnNavigationLearned(page_summary_https));
+
+    predictor_->RecordPageRequestSummary(
+        std::make_unique<PageRequestSummary>(page_summary_https));
+    profile_->BlockUntilHistoryProcessesPendingRequests();
+
+    RedirectData host_redirect_data_https = CreateRedirectData("fb.com");
+    InitializeRedirectStat(host_redirect_data_https.add_redirect_endpoints(),
+                           GURL("https://facebook.com"), 1, 0, 0);
+    RedirectDataMap expected_host_redirect_data_https =
+        test_host_redirect_data_;
+    expected_host_redirect_data_https.erase("bbc.com");
+    expected_host_redirect_data_https[host_redirect_data_https.primary_key()] =
+        host_redirect_data_https;
+    EXPECT_EQ(mock_tables_->host_redirect_table_.data_,
+              expected_host_redirect_data_https);
+    EXPECT_EQ(1, mock_tables_->host_redirect_table_
+                     .data_[host_redirect_data_https.primary_key()]
+                     .redirect_endpoints()
+                     .size());
+    EXPECT_EQ("https", mock_tables_->host_redirect_table_
+                           .data_[host_redirect_data_https.primary_key()]
+                           .redirect_endpoints(0)
+                           .url_scheme());
+  }
+  {
+    std::vector<content::mojom::ResourceLoadInfoPtr> resources_http;
+    resources_http.push_back(CreateResourceLoadInfoWithRedirects(
+        {"http://fb.com/google", "http://facebook.com/google"}));
+    auto page_summary_http = CreatePageRequestSummary(
+        "http://facebook.com/google", "http://fb.com/google", resources_http);
+
+    StrictMock<MockResourcePrefetchPredictorObserver> mock_observer(predictor_);
+    EXPECT_CALL(mock_observer, OnNavigationLearned(page_summary_http));
+
+    predictor_->RecordPageRequestSummary(
+        std::make_unique<PageRequestSummary>(page_summary_http));
+    profile_->BlockUntilHistoryProcessesPendingRequests();
+
+    RedirectData host_redirect_data_http = CreateRedirectData("fb.com");
+    InitializeRedirectStat(host_redirect_data_http.add_redirect_endpoints(),
+                           GURL("http://facebook.com"), 1, 0, 0);
+    RedirectDataMap expected_host_redirect_data_http = test_host_redirect_data_;
+    expected_host_redirect_data_http.erase("bbc.com");
+    expected_host_redirect_data_http[host_redirect_data_http.primary_key()] =
+        host_redirect_data_http;
+    EXPECT_EQ(2, mock_tables_->host_redirect_table_
+                     .data_[host_redirect_data_http.primary_key()]
+                     .redirect_endpoints()
+                     .size());
+    EXPECT_EQ("facebook.com", mock_tables_->host_redirect_table_
+                                  .data_[host_redirect_data_http.primary_key()]
+                                  .redirect_endpoints(1)
+                                  .url());
+    EXPECT_EQ("http", mock_tables_->host_redirect_table_
+                          .data_[host_redirect_data_http.primary_key()]
+                          .redirect_endpoints(1)
+                          .url_scheme());
+  }
+}
+
 TEST_F(ResourcePrefetchPredictorTest, DeleteUrls) {
   ResetPredictor(false);
   InitializePredictor();
@@ -555,8 +638,8 @@
 
   // The data to be requested for the confident endpoint.
   RedirectData nyt = CreateRedirectData("nyt.com", 1);
-  InitializeRedirectStat(nyt.add_redirect_endpoints(), "mobile.nytimes.com", 10,
-                         0, 0);
+  InitializeRedirectStat(nyt.add_redirect_endpoints(),
+                         GURL("https://mobile.nytimes.com"), 10, 0, 0);
   redirect_data.UpdateData(nyt.primary_key(), nyt);
   EXPECT_TRUE(predictor_->GetRedirectEndpoint("nyt.com", redirect_data,
                                               &redirect_endpoint));
@@ -564,20 +647,20 @@
 
   // The data to check negative result due not enough confidence.
   RedirectData facebook = CreateRedirectData("fb.com", 3);
-  InitializeRedirectStat(facebook.add_redirect_endpoints(), "facebook.com", 5,
-                         5, 0);
+  InitializeRedirectStat(facebook.add_redirect_endpoints(),
+                         GURL("https://facebook.com"), 5, 5, 0);
   redirect_data.UpdateData(facebook.primary_key(), facebook);
   EXPECT_FALSE(predictor_->GetRedirectEndpoint("fb.com", redirect_data,
                                                &redirect_endpoint));
 
   // The data to check negative result due ambiguity.
   RedirectData google = CreateRedirectData("google.com", 4);
-  InitializeRedirectStat(google.add_redirect_endpoints(), "google.com", 10, 0,
-                         0);
-  InitializeRedirectStat(google.add_redirect_endpoints(), "google.fr", 10, 1,
-                         0);
-  InitializeRedirectStat(google.add_redirect_endpoints(), "google.ws", 20, 20,
-                         0);
+  InitializeRedirectStat(google.add_redirect_endpoints(),
+                         GURL("https://google.com"), 10, 0, 0);
+  InitializeRedirectStat(google.add_redirect_endpoints(),
+                         GURL("https://google.fr"), 10, 1, 0);
+  InitializeRedirectStat(google.add_redirect_endpoints(),
+                         GURL("https://google.ws"), 20, 20, 0);
   redirect_data.UpdateData(google.primary_key(), google);
   EXPECT_FALSE(predictor_->GetRedirectEndpoint("google.com", redirect_data,
                                                &redirect_endpoint));
@@ -617,8 +700,8 @@
 
   // Add a redirect.
   RedirectData redirect = CreateRedirectData("google.com", 3);
-  InitializeRedirectStat(redirect.add_redirect_endpoints(), "www.google.com",
-                         10, 0, 0);
+  InitializeRedirectStat(redirect.add_redirect_endpoints(),
+                         GURL("https://www.google.com"), 10, 0, 0);
   predictor_->host_redirect_data_->UpdateData(redirect.primary_key(), redirect);
 
   // Prediction failed: no data associated with the redirect endpoint.
diff --git a/chrome/browser/previews/previews_prober.cc b/chrome/browser/previews/previews_prober.cc
index 93af097b..8662587 100644
--- a/chrome/browser/previews/previews_prober.cc
+++ b/chrome/browser/previews/previews_prober.cc
@@ -26,7 +26,6 @@
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
-#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/cpp/simple_url_loader.h"
@@ -221,6 +220,7 @@
     const net::HttpRequestHeaders headers,
     const RetryPolicy& retry_policy,
     const TimeoutPolicy& timeout_policy,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
     const size_t max_cache_entries,
     base::TimeDelta revalidate_cache_after)
     : PreviewsProber(delegate,
@@ -232,6 +232,7 @@
                      headers,
                      retry_policy,
                      timeout_policy,
+                     traffic_annotation,
                      max_cache_entries,
                      revalidate_cache_after,
                      base::DefaultTickClock::GetInstance(),
@@ -247,6 +248,7 @@
     const net::HttpRequestHeaders headers,
     const RetryPolicy& retry_policy,
     const TimeoutPolicy& timeout_policy,
+    const net::NetworkTrafficAnnotationTag& traffic_annotation,
     const size_t max_cache_entries,
     base::TimeDelta revalidate_cache_after,
     const base::TickClock* tick_clock,
@@ -261,6 +263,7 @@
       timeout_policy_(timeout_policy),
       max_cache_entries_(max_cache_entries),
       revalidate_cache_after_(revalidate_cache_after),
+      traffic_annotation_(traffic_annotation),
       successive_retry_count_(0),
       successive_timeout_count_(0),
       cached_probe_results_(std::make_unique<base::DictionaryValue>()),
@@ -385,28 +388,6 @@
     url = url.ReplaceComponents(replacements);
   }
 
-  net::NetworkTrafficAnnotationTag traffic_annotation =
-      net::DefineNetworkTrafficAnnotation("previews_prober", R"(
-        semantics {
-          sender: "Previews Prober"
-          description:
-            "Requests a small resource to test network connectivity to a given "
-            "resource or domain which will either be a Google owned domain or"
-            "the website that the user is navigating to."
-          trigger:
-            "Requested when Lite mode and Previews are enabled on startup and "
-            "on every network change."
-          data: "None."
-          destination: WEBSITE
-        }
-        policy {
-          cookies_allowed: NO
-          setting:
-            "Users can control Lite mode on Android via the settings menu. "
-            "Lite mode is not available on iOS, and on desktop only for "
-            "developer testing."
-          policy_exception_justification: "Not implemented."
-        })");
   auto request = std::make_unique<network::ResourceRequest>();
   request->url = url;
   request->method = HttpMethodToString(http_method_);
@@ -415,7 +396,7 @@
   request->allow_credentials = false;
 
   url_loader_ =
-      network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
+      network::SimpleURLLoader::Create(std::move(request), traffic_annotation_);
   url_loader_->SetAllowHttpErrorResults(true);
 
   url_loader_->DownloadToString(
diff --git a/chrome/browser/previews/previews_prober.h b/chrome/browser/previews/previews_prober.h
index 543b995..c118278 100644
--- a/chrome/browser/previews/previews_prober.h
+++ b/chrome/browser/previews/previews_prober.h
@@ -23,6 +23,7 @@
 #include "build/build_config.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_request_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/network_connection_tracker.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "url/gurl.h"
@@ -140,6 +141,7 @@
       const net::HttpRequestHeaders headers,
       const RetryPolicy& retry_policy,
       const TimeoutPolicy& timeout_policy,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
       const size_t max_cache_entries,
       base::TimeDelta revalidate_cache_after);
   ~PreviewsProber() override;
@@ -180,6 +182,7 @@
       const net::HttpRequestHeaders headers,
       const RetryPolicy& retry_policy,
       const TimeoutPolicy& timeout_policy,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
       const size_t max_cache_entries,
       base::TimeDelta revalidate_cache_after,
       const base::TickClock* tick_clock,
@@ -234,6 +237,9 @@
   // background.
   const base::TimeDelta revalidate_cache_after_;
 
+  // The traffic annotation to use for creating |url_loader_|.
+  const net::NetworkTrafficAnnotationTag traffic_annotation_;
+
   // The number of retries that have been attempted. This count does not include
   // the original probe.
   size_t successive_retry_count_;
diff --git a/chrome/browser/previews/previews_prober_browsertest.cc b/chrome/browser/previews/previews_prober_browsertest.cc
index 0db82db..acbe139a 100644
--- a/chrome/browser/previews/previews_prober_browsertest.cc
+++ b/chrome/browser/previews/previews_prober_browsertest.cc
@@ -19,6 +19,7 @@
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/public/mojom/network_service_test.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 
@@ -133,7 +134,8 @@
                         browser()->profile()->GetPrefs(),
                         PreviewsProber::ClientName::kLitepages, url,
                         PreviewsProber::HttpMethod::kGet, headers, retry_policy,
-                        timeout_policy, 1, base::TimeDelta::FromDays(1));
+                        timeout_policy, TRAFFIC_ANNOTATION_FOR_TESTS, 1,
+                        base::TimeDelta::FromDays(1));
   prober.SendNowIfInactive(false);
   WaitForCompletedProbe(&prober);
 
@@ -155,7 +157,8 @@
                         browser()->profile()->GetPrefs(),
                         PreviewsProber::ClientName::kLitepages, url,
                         PreviewsProber::HttpMethod::kGet, headers, retry_policy,
-                        timeout_policy, 1, base::TimeDelta::FromDays(1));
+                        timeout_policy, TRAFFIC_ANNOTATION_FOR_TESTS, 1,
+                        base::TimeDelta::FromDays(1));
   prober.SendNowIfInactive(false);
   WaitForCompletedProbe(&prober);
 
@@ -180,7 +183,8 @@
                         browser()->profile()->GetPrefs(),
                         PreviewsProber::ClientName::kLitepages, url,
                         PreviewsProber::HttpMethod::kGet, headers, retry_policy,
-                        timeout_policy, 1, base::TimeDelta::FromDays(1));
+                        timeout_policy, TRAFFIC_ANNOTATION_FOR_TESTS, 1,
+                        base::TimeDelta::FromDays(1));
   SimulateNetworkChange(network::mojom::ConnectionType::CONNECTION_4G);
   WaitForCompletedProbe(&prober);
 
diff --git a/chrome/browser/previews/previews_prober_unittest.cc b/chrome/browser/previews/previews_prober_unittest.cc
index 4c6dcb4c..3b0653e 100644
--- a/chrome/browser/previews/previews_prober_unittest.cc
+++ b/chrome/browser/previews/previews_prober_unittest.cc
@@ -16,6 +16,7 @@
 #include "net/base/load_flags.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_status_code.h"
+#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
@@ -63,6 +64,7 @@
       const net::HttpRequestHeaders headers,
       const RetryPolicy& retry_policy,
       const TimeoutPolicy& timeout_policy,
+      const net::NetworkTrafficAnnotationTag& traffic_annotation,
       const size_t max_cache_entries,
       base::TimeDelta revalidate_cache_after,
       const base::TickClock* tick_clock,
@@ -76,6 +78,7 @@
                        headers,
                        retry_policy,
                        timeout_policy,
+                       traffic_annotation,
                        max_cache_entries,
                        revalidate_cache_after,
                        tick_clock,
@@ -125,8 +128,9 @@
             delegate, test_shared_loader_factory_, &test_prefs_,
             PreviewsProber::ClientName::kLitepages, kTestUrl,
             PreviewsProber::HttpMethod::kGet, headers, retry_policy,
-            timeout_policy, 1, kCacheRevalidateAfter,
-            thread_bundle_.GetMockTickClock(), thread_bundle_.GetMockClock());
+            timeout_policy, TRAFFIC_ANNOTATION_FOR_TESTS, 1,
+            kCacheRevalidateAfter, thread_bundle_.GetMockTickClock(),
+            thread_bundle_.GetMockClock());
     prober->SetOnCompleteCallback(base::BindRepeating(
         &PreviewsProberTest::OnProbeComplete, base::Unretained(this)));
     return prober;
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.cc b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
index 99539e2b..55083525c 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.cc
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.cc
@@ -1256,19 +1256,18 @@
       menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
       if (send_tab_to_self::GetValidDeviceCount(GetBrowser()->profile()) == 1) {
 #if defined(OS_MACOSX)
-        menu_model_.AddItem(
-            IDC_CONTENT_LINK_SEND_TAB_TO_SELF_SINGLE_TARGET,
-            l10n_util::GetStringFUTF16(
-                IDS_LINK_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-                base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                    GetBrowser()->profile()))));
+        menu_model_.AddItem(IDC_CONTENT_LINK_SEND_TAB_TO_SELF_SINGLE_TARGET,
+                            l10n_util::GetStringFUTF16(
+                                IDS_LINK_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
+                                send_tab_to_self::GetSingleTargetDeviceName(
+                                    GetBrowser()->profile())));
 #else
         menu_model_.AddItemWithIcon(
             IDC_CONTENT_LINK_SEND_TAB_TO_SELF_SINGLE_TARGET,
             l10n_util::GetStringFUTF16(
                 IDS_LINK_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-                base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                    GetBrowser()->profile()))),
+                send_tab_to_self::GetSingleTargetDeviceName(
+                    GetBrowser()->profile())),
             *send_tab_to_self::GetImageSkia());
 #endif
         send_tab_to_self::RecordSendTabToSelfClickResult(
@@ -1491,19 +1490,18 @@
     menu_model_.AddSeparator(ui::NORMAL_SEPARATOR);
     if (send_tab_to_self::GetValidDeviceCount(GetBrowser()->profile()) == 1) {
 #if defined(OS_MACOSX)
-      menu_model_.AddItem(
-          IDC_SEND_TAB_TO_SELF_SINGLE_TARGET,
-          l10n_util::GetStringFUTF16(
-              IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-              base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                  GetBrowser()->profile()))));
+      menu_model_.AddItem(IDC_SEND_TAB_TO_SELF_SINGLE_TARGET,
+                          l10n_util::GetStringFUTF16(
+                              IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
+                              send_tab_to_self::GetSingleTargetDeviceName(
+                                  GetBrowser()->profile())));
 #else
       menu_model_.AddItemWithIcon(
           IDC_SEND_TAB_TO_SELF_SINGLE_TARGET,
           l10n_util::GetStringFUTF16(
               IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-              base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                  GetBrowser()->profile()))),
+              send_tab_to_self::GetSingleTargetDeviceName(
+                  GetBrowser()->profile())),
           *send_tab_to_self::GetImageSkia());
 #endif
       send_tab_to_self::RecordSendTabToSelfClickResult(
diff --git a/chrome/browser/renderer_context_menu/render_view_context_menu.h b/chrome/browser/renderer_context_menu/render_view_context_menu.h
index 49728eb..7e993b49 100644
--- a/chrome/browser/renderer_context_menu/render_view_context_menu.h
+++ b/chrome/browser/renderer_context_menu/render_view_context_menu.h
@@ -71,8 +71,6 @@
                                        bool is_checked);
 
   // Range of command IDs to use for the items in the send tab to self submenu.
-  static const int kMinSendTabToSelfSubMenuCommandId =
-      send_tab_to_self::SendTabToSelfSubMenuModel::kMinCommandId;
   static const int kMaxSendTabToSelfSubMenuCommandId =
       send_tab_to_self::SendTabToSelfSubMenuModel::kMaxCommandId;
 
diff --git a/chrome/browser/renderer_host/chrome_render_message_filter.cc b/chrome/browser/renderer_host/chrome_render_message_filter.cc
index 15cdd8e..ee593372 100644
--- a/chrome/browser/renderer_host/chrome_render_message_filter.cc
+++ b/chrome/browser/renderer_host/chrome_render_message_filter.cc
@@ -32,6 +32,7 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_process_host.h"
 #include "extensions/buildflags/buildflags.h"
+#include "net/base/network_isolation_key.h"
 #include "ppapi/buildflags/buildflags.h"
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
@@ -130,12 +131,17 @@
     return;
   }
 
-  if (preconnect_manager_initialized_) {
-    base::PostTaskWithTraits(
-        FROM_HERE, {BrowserThread::UI},
-        base::BindOnce(&predictors::PreconnectManager::StartPreconnectUrl,
-                       preconnect_manager_, url, allow_credentials));
-  }
+  if (!preconnect_manager_initialized_)
+    return;
+
+  // TODO(mmenke):  Use process and frame ids to populate NetworkIsolationKey.
+  // May also need to think about enabling cross-site preconnects, though that
+  // will result in at least some cross-site information leakage.
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&predictors::PreconnectManager::StartPreconnectUrl,
+                     preconnect_manager_, url, allow_credentials,
+                     net::NetworkIsolationKey()));
 }
 
 void ChromeRenderMessageFilter::OnAllowDatabase(
diff --git a/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js b/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
index 73c7b66e..b6e14ac9 100644
--- a/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
+++ b/chrome/browser/resources/chromeos/chromevox/chromevox/background/prefs.js
@@ -168,6 +168,15 @@
       localStorage[pref] = cvox.ChromeVoxPrefs.DEFAULT_PREFS[pref];
     }
   }
+  // Since language switching is currently an experimental feature, ensure that
+  // it is off if the feature flag is absent.
+  chrome.commandLinePrivate.hasSwitch(
+      'enable-experimental-accessibility-chromevox-language-switching',
+      function(enabled) {
+        if (!enabled) {
+          localStorage['languageSwitching'] = false;
+        }
+      });
 };
 
 /**
diff --git a/chrome/browser/resources/local_ntp/local_ntp.js b/chrome/browser/resources/local_ntp/local_ntp.js
index 7ab4d242..24a75930 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.js
+++ b/chrome/browser/resources/local_ntp/local_ntp.js
@@ -148,22 +148,6 @@
  * @const
  */
 const LOG_TYPE = {
-  // A static Doodle was shown, coming from cache.
-  NTP_STATIC_LOGO_SHOWN_FROM_CACHE: 30,
-  // A static Doodle was shown, coming from the network.
-  NTP_STATIC_LOGO_SHOWN_FRESH: 31,
-  // A call-to-action Doodle image was shown, coming from cache.
-  NTP_CTA_LOGO_SHOWN_FROM_CACHE: 32,
-  // A call-to-action Doodle image was shown, coming from the network.
-  NTP_CTA_LOGO_SHOWN_FRESH: 33,
-
-  // A static Doodle was clicked.
-  NTP_STATIC_LOGO_CLICKED: 34,
-  // A call-to-action Doodle was clicked.
-  NTP_CTA_LOGO_CLICKED: 35,
-  // An animated Doodle was clicked.
-  NTP_ANIMATED_LOGO_CLICKED: 36,
-
   // The One Google Bar was shown.
   NTP_ONE_GOOGLE_BAR_SHOWN: 37,
 
diff --git a/chrome/browser/resources/management/management_browser_proxy.js b/chrome/browser/resources/management/management_browser_proxy.js
index 20c5400..0633bdc 100644
--- a/chrome/browser/resources/management/management_browser_proxy.js
+++ b/chrome/browser/resources/management/management_browser_proxy.js
@@ -2,70 +2,69 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('management');
-/**
- * @typedef {{
- *   name: string,
- *   permissions: !Array<string>
- * }}
- */
-management.Extension;
-
-/** @enum {string} */
-management.ReportingType = {
-  SECURITY: 'security',
-  DEVICE: 'device',
-  USER: 'user',
-  USER_ACTIVITY: 'user-activity',
-  EXTENSIONS: 'extensions'
-};
-
-/**
- * @typedef {{
- *   messageId: string,
- *   reportingType: !management.ReportingType,
- * }}
- */
-management.BrowserReportingResponse;
-
-/**
- * @typedef {{
- *   browserManagementNotice: string,
- *   extensionReportingTitle: string,
- *   pageSubtitle: string,
- *   managed: boolean,
- *   overview: string,
- *   customerLogo: string,
- * }}
- */
-management.ManagedDataResponse;
-
-// <if expr="chromeos">
-/**
- * @enum {string} Look at ToJSDeviceReportingType usage in
- *    management_ui_handler.cc for more details.
- */
-management.DeviceReportingType = {
-  SUPERVISED_USER: 'supervised user',
-  DEVICE_ACTIVITY: 'device activity',
-  STATISTIC: 'device statistics',
-  DEVICE: 'device',
-  LOGS: 'logs',
-  PRINT: 'print',
-  CROSTINI: 'crostini'
-};
-
-
-/**
- * @typedef {{
- *   messageId: string,
- *   reportingType: !management.DeviceReportingType,
- * }}
- */
-management.DeviceReportingResponse;
-// </if>
-
 cr.define('management', function() {
+  /**
+   * @typedef {{
+   *   name: string,
+   *   permissions: !Array<string>
+   * }}
+   */
+  let Extension;
+
+  /** @enum {string} */
+  const ReportingType = {
+    SECURITY: 'security',
+    DEVICE: 'device',
+    USER: 'user',
+    USER_ACTIVITY: 'user-activity',
+    EXTENSIONS: 'extensions'
+  };
+
+  /**
+   * @typedef {{
+   *   messageId: string,
+   *   reportingType: !management.ReportingType,
+   * }}
+   */
+  let BrowserReportingResponse;
+
+  /**
+   * @typedef {{
+   *   browserManagementNotice: string,
+   *   extensionReportingTitle: string,
+   *   pageSubtitle: string,
+   *   managed: boolean,
+   *   overview: string,
+   *   customerLogo: string,
+   * }}
+   */
+  let ManagedDataResponse;
+
+  // <if expr="chromeos">
+  /**
+   * @enum {string} Look at ToJSDeviceReportingType usage in
+   *    management_ui_handler.cc for more details.
+   */
+  const DeviceReportingType = {
+    SUPERVISED_USER: 'supervised user',
+    DEVICE_ACTIVITY: 'device activity',
+    STATISTIC: 'device statistics',
+    DEVICE: 'device',
+    LOGS: 'logs',
+    PRINT: 'print',
+    CROSTINI: 'crostini'
+  };
+
+
+  /**
+   * @typedef {{
+   *   messageId: string,
+   *   reportingType: !management.DeviceReportingType,
+   * }}
+   */
+  let DeviceReportingResponse;
+  // </if>
+
   /** @interface */
   class ManagementBrowserProxy {
     /** @return {!Promise<!Array<!management.Extension>>} */
@@ -128,7 +127,15 @@
   cr.addSingletonGetter(ManagementBrowserProxyImpl);
 
   return {
+    BrowserReportingResponse: BrowserReportingResponse,
+    // <if expr="chromeos">
+    DeviceReportingResponse: DeviceReportingResponse,
+    DeviceReportingType: DeviceReportingType,
+    // </if>
+    Extension: Extension,
+    ManagedDataResponse: ManagedDataResponse,
+    ManagementBrowserProxyImpl: ManagementBrowserProxyImpl,
     ManagementBrowserProxy: ManagementBrowserProxy,
-    ManagementBrowserProxyImpl: ManagementBrowserProxyImpl
+    ReportingType: ReportingType,
   };
 });
diff --git a/chrome/browser/resources/management/management_ui.js b/chrome/browser/resources/management/management_ui.js
index 3d5011b..a4984aa 100644
--- a/chrome/browser/resources/management/management_ui.js
+++ b/chrome/browser/resources/management/management_ui.js
@@ -2,264 +2,270 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('management');
-/**
- * @typedef {{
- *   messageIds: !Array<string>,
- *   icon: string,
- * }}
- */
-management.BrowserReportingData;
+cr.define('management', function() {
+  /**
+   * @typedef {{
+   *   messageIds: !Array<string>,
+   *   icon: string,
+   * }}
+   */
+  let BrowserReportingData;
 
-Polymer({
-  is: 'management-ui',
+  Polymer({
+    is: 'management-ui',
 
-  behaviors: [
-    I18nBehavior,
-    WebUIListenerBehavior,
-  ],
+    behaviors: [
+      I18nBehavior,
+      WebUIListenerBehavior,
+    ],
 
-  properties: {
-    /**
-     * List of messages related to browser reporting.
-     * @private {?Array<!management.BrowserReportingData>}
-     */
-    browserReportingInfo_: Array,
+    properties: {
+      /**
+       * List of messages related to browser reporting.
+       * @private {?Array<!management.BrowserReportingData>}
+       */
+      browserReportingInfo_: Array,
+
+      /**
+       * List of messages related to browser reporting.
+       * @private {?Array<!management.Extension>}
+       */
+      extensions_: Array,
+
+      // <if expr="chromeos">
+      /**
+       * List of messages related to device reporting.
+       * @private {?Array<!management.DeviceReportingResponse>}
+       */
+      deviceReportingInfo_: Array,
+
+      /**
+       * Message stating if the Trust Roots are configured.
+       * @private
+       */
+      localTrustRoots_: String,
+
+      /** @private */
+      customerLogo_: String,
+
+      /** @private */
+      managementOverview_: String,
+
+      // </if>
+
+      /** @private */
+      subtitle_: String,
+
+      // <if expr="not chromeos">
+      /** @private */
+      managementNoticeHtml_: String,
+      // </if>
+
+      /** @private */
+      managed_: Boolean,
+
+      /** @private */
+      extensionReportingSubtitle_: String,
+    },
+
+    /** @private {?management.ManagementBrowserProxy} */
+    browserProxy_: null,
+
+    /** @override */
+    attached() {
+      document.documentElement.classList.remove('loading');
+      this.browserProxy_ = management.ManagementBrowserProxyImpl.getInstance();
+      this.updateManagedFields_();
+      this.initBrowserReportingInfo_();
+
+      this.addWebUIListener(
+          'browser-reporting-info-updated',
+          reportingInfo => this.onBrowserReportingInfoReceived_(reportingInfo));
+
+      this.addWebUIListener('managed_data_changed', () => {
+        this.updateManagedFields_();
+      });
+
+      this.getExtensions_();
+      // <if expr="chromeos">
+      this.getDeviceReportingInfo_();
+      this.getLocalTrustRootsInfo_();
+      // </if>
+    },
+
+    /** @private */
+    initBrowserReportingInfo_() {
+      this.browserProxy_.initBrowserReportingInfo().then(
+          reportingInfo => this.onBrowserReportingInfoReceived_(reportingInfo));
+    },
 
     /**
-     * List of messages related to browser reporting.
-     * @private {?Array<!management.Extension>}
-     */
-    extensions_: Array,
-
-    // <if expr="chromeos">
-    /**
-     * List of messages related to device reporting.
-     * @private {?Array<!management.DeviceReportingResponse>}
-     */
-    deviceReportingInfo_: Array,
-
-    /**
-     * Message stating if the Trust Roots are configured.
+     * @param {!Array<!management.BrowserReportingResponse>} reportingInfo
      * @private
      */
-    localTrustRoots_: String,
+    onBrowserReportingInfoReceived_(reportingInfo) {
+      const reportingInfoMap = reportingInfo.reduce((info, response) => {
+        info[response.reportingType] = info[response.reportingType] || {
+          icon: this.getIconForReportingType_(response.reportingType),
+          messageIds: []
+        };
+        info[response.reportingType].messageIds.push(response.messageId);
+        return info;
+      }, {});
 
-    /** @private */
-    customerLogo_: String,
-
-    /** @private */
-    managementOverview_: String,
-
-    // </if>
-
-    /** @private */
-    subtitle_: String,
-
-    // <if expr="not chromeos">
-    /** @private */
-    managementNoticeHtml_: String,
-    // </if>
-
-    /** @private */
-    managed_: Boolean,
-
-    /** @private */
-    extensionReportingSubtitle_: String,
-  },
-
-  /** @private {?management.ManagementBrowserProxy} */
-  browserProxy_: null,
-
-  /** @override */
-  attached() {
-    document.documentElement.classList.remove('loading');
-    this.browserProxy_ = management.ManagementBrowserProxyImpl.getInstance();
-    this.updateManagedFields_();
-    this.initBrowserReportingInfo_();
-
-    this.addWebUIListener(
-        'browser-reporting-info-updated',
-        reportingInfo => this.onBrowserReportingInfoReceived_(reportingInfo));
-
-    this.addWebUIListener('managed_data_changed', () => {
-      this.updateManagedFields_();
-    });
-
-    this.getExtensions_();
-    // <if expr="chromeos">
-    this.getDeviceReportingInfo_();
-    this.getLocalTrustRootsInfo_();
-    // </if>
-  },
-
-  /** @private */
-  initBrowserReportingInfo_() {
-    this.browserProxy_.initBrowserReportingInfo().then(
-        reportingInfo => this.onBrowserReportingInfoReceived_(reportingInfo));
-  },
-
-  /**
-   * @param {!Array<!management.BrowserReportingResponse>} reportingInfo
-   * @private
-   */
-  onBrowserReportingInfoReceived_(reportingInfo) {
-    const reportingInfoMap = reportingInfo.reduce((info, response) => {
-      info[response.reportingType] = info[response.reportingType] || {
-        icon: this.getIconForReportingType_(response.reportingType),
-        messageIds: []
+      const reportingTypeOrder = {
+        [management.ReportingType.SECURITY]: 1,
+        [management.ReportingType.EXTENSIONS]: 2,
+        [management.ReportingType.USER]: 3,
+        [management.ReportingType.USER_ACTIVITY]: 4,
+        [management.ReportingType.DEVICE]: 5,
       };
-      info[response.reportingType].messageIds.push(response.messageId);
-      return info;
-    }, {});
 
-    const reportingTypeOrder = {
-      [management.ReportingType.SECURITY]: 1,
-      [management.ReportingType.EXTENSIONS]: 2,
-      [management.ReportingType.USER]: 3,
-      [management.ReportingType.USER_ACTIVITY]: 4,
-      [management.ReportingType.DEVICE]: 5,
-    };
+      this.browserReportingInfo_ =
+          Object.keys(reportingInfoMap)
+              .sort((a, b) => reportingTypeOrder[a] - reportingTypeOrder[b])
+              .map(reportingType => reportingInfoMap[reportingType]);
+    },
 
-    this.browserReportingInfo_ =
-        Object.keys(reportingInfoMap)
-            .sort((a, b) => reportingTypeOrder[a] - reportingTypeOrder[b])
-            .map(reportingType => reportingInfoMap[reportingType]);
-  },
+    /** @private */
+    getExtensions_() {
+      this.browserProxy_.getExtensions().then(extensions => {
+        this.extensions_ = extensions;
+      });
+    },
 
-  /** @private */
-  getExtensions_() {
-    this.browserProxy_.getExtensions().then(extensions => {
-      this.extensions_ = extensions;
-    });
-  },
+    // <if expr="chromeos">
+    /** @private */
+    getLocalTrustRootsInfo_() {
+      this.browserProxy_.getLocalTrustRootsInfo().then(trustRootsConfigured => {
+        this.localTrustRoots_ = trustRootsConfigured ?
+            loadTimeData.getString('managementTrustRootsConfigured') :
+            '';
+      });
+    },
 
-  // <if expr="chromeos">
-  /** @private */
-  getLocalTrustRootsInfo_() {
-    this.browserProxy_.getLocalTrustRootsInfo().then(trustRootsConfigured => {
-      this.localTrustRoots_ = trustRootsConfigured ?
-          loadTimeData.getString('managementTrustRootsConfigured') :
-          '';
-    });
-  },
+    /** @private */
+    getDeviceReportingInfo_() {
+      this.browserProxy_.getDeviceReportingInfo().then(reportingInfo => {
+        this.deviceReportingInfo_ = reportingInfo;
+      });
+    },
 
-  /** @private */
-  getDeviceReportingInfo_() {
-    this.browserProxy_.getDeviceReportingInfo().then(reportingInfo => {
-      this.deviceReportingInfo_ = reportingInfo;
-    });
-  },
+    /**
+     * @return {boolean} True of there are device reporting info to show.
+     * @private
+     */
+    showDeviceReportingInfo_() {
+      return !!this.deviceReportingInfo_ &&
+          this.deviceReportingInfo_.length > 0;
+    },
 
-  /**
-   * @return {boolean} True of there are device reporting info to show.
-   * @private
-   */
-  showDeviceReportingInfo_() {
-    return !!this.deviceReportingInfo_ && this.deviceReportingInfo_.length > 0;
-  },
+    /**
+     * @param {management.DeviceReportingType} reportingType
+     * @return {string} The associated icon.
+     * @private
+     */
+    getIconForDeviceReportingType_(reportingType) {
+      switch (reportingType) {
+        case management.DeviceReportingType.SUPERVISED_USER:
+          return 'management:supervised-user';
+        case management.DeviceReportingType.DEVICE_ACTIVITY:
+          return 'management:timelapse';
+        case management.DeviceReportingType.STATISTIC:
+          return 'management:bar-chart';
+        case management.DeviceReportingType.DEVICE:
+          return 'cr:computer';
+        case management.DeviceReportingType.LOGS:
+          return 'management:report';
+        case management.DeviceReportingType.PRINT:
+          return 'cr:print';
+        case management.DeviceReportingType.CROSTINI:
+          return 'management:linux';
+        default:
+          return 'cr:computer';
+      }
+    },
+    // </if>
 
-  /**
-   * @param {management.DeviceReportingType} reportingType
-   * @return {string} The associated icon.
-   * @private
-   */
-  getIconForDeviceReportingType_(reportingType) {
-    switch (reportingType) {
-      case management.DeviceReportingType.SUPERVISED_USER:
-        return 'management:supervised-user';
-      case management.DeviceReportingType.DEVICE_ACTIVITY:
-        return 'management:timelapse';
-      case management.DeviceReportingType.STATISTIC:
-        return 'management:bar-chart';
-      case management.DeviceReportingType.DEVICE:
-        return 'cr:computer';
-      case management.DeviceReportingType.LOGS:
-        return 'management:report';
-      case management.DeviceReportingType.PRINT:
-        return 'cr:print';
-      case management.DeviceReportingType.CROSTINI:
-        return 'management:linux';
-      default:
-        return 'cr:computer';
-    }
-  },
-  // </if>
+    /**
+     * @return {boolean} True of there are browser reporting info to show.
+     * @private
+     */
+    showBrowserReportingInfo_() {
+      return !!this.browserReportingInfo_ &&
+          this.browserReportingInfo_.length > 0;
+    },
 
-  /**
-   * @return {boolean} True of there are browser reporting info to show.
-   * @private
-   */
-  showBrowserReportingInfo_() {
-    return !!this.browserReportingInfo_ &&
-        this.browserReportingInfo_.length > 0;
-  },
+    /**
+     * @return {boolean} True of there are extension reporting info to show.
+     * @private
+     */
+    showExtensionReportingInfo_() {
+      return !!this.extensions_ && this.extensions_.length > 0;
+    },
 
-  /**
-   * @return {boolean} True of there are extension reporting info to show.
-   * @private
-   */
-  showExtensionReportingInfo_() {
-    return !!this.extensions_ && this.extensions_.length > 0;
-  },
+    /**
+     * @param {management.ReportingType} reportingType
+     * @returns {string} The associated icon.
+     * @private
+     */
+    getIconForReportingType_(reportingType) {
+      switch (reportingType) {
+        case management.ReportingType.SECURITY:
+          return 'cr:security';
+        case management.ReportingType.DEVICE:
+          return 'cr:computer';
+        case management.ReportingType.EXTENSIONS:
+          return 'cr:extension';
+        case management.ReportingType.USER:
+          return 'management:account-circle';
+        case management.ReportingType.USER_ACTIVITY:
+          return 'management:public';
+        default:
+          return 'cr:security';
+      }
+    },
 
-  /**
-   * @param {management.ReportingType} reportingType
-   * @returns {string} The associated icon.
-   * @private
-   */
-  getIconForReportingType_(reportingType) {
-    switch (reportingType) {
-      case management.ReportingType.SECURITY:
-        return 'cr:security';
-      case management.ReportingType.DEVICE:
-        return 'cr:computer';
-      case management.ReportingType.EXTENSIONS:
-        return 'cr:extension';
-      case management.ReportingType.USER:
-        return 'management:account-circle';
-      case management.ReportingType.USER_ACTIVITY:
-        return 'management:public';
-      default:
-        return 'cr:security';
-    }
-  },
+    /**
+     * Handles the 'search-changed' event fired from the toolbar.
+     * Redirects to the settings page initialized the the current
+     * search query.
+     * @param {!CustomEvent<string>} e
+     * @private
+     */
+    onSearchChanged_: function(e) {
+      const query = e.detail;
+      window.location.href =
+          `chrome://settings?search=${encodeURIComponent(query)}`;
+    },
 
-  /**
-   * Handles the 'search-changed' event fired from the toolbar.
-   * Redirects to the settings page initialized the the current
-   * search query.
-   * @param {!CustomEvent<string>} e
-   * @private
-   */
-  onSearchChanged_: function(e) {
-    const query = e.detail;
-    window.location.href =
-        `chrome://settings?search=${encodeURIComponent(query)}`;
-  },
+    /** @private */
+    onTapBack_() {
+      if (history.length > 1) {
+        history.back();
+      } else {
+        window.location.href = 'chrome://settings/help';
+      }
+    },
 
-  /** @private */
-  onTapBack_() {
-    if (history.length > 1) {
-      history.back();
-    } else {
-      window.location.href = 'chrome://settings/help';
-    }
-  },
+    /** @private */
+    updateManagedFields_() {
+      this.browserProxy_.getContextualManagedData().then(data => {
+        this.managed_ = data.managed;
+        this.extensionReportingSubtitle_ = data.extensionReportingTitle;
+        this.subtitle_ = data.pageSubtitle;
+        // <if expr="chromeos">
+        this.customerLogo_ = data.customerLogo;
+        this.managementOverview_ = data.overview;
+        // </if>
+        // <if expr="not chromeos">
+        this.managementNoticeHtml_ = data.browserManagementNotice;
+        // </if>
+      });
+    },
+  });
 
-  /** @private */
-  updateManagedFields_() {
-    this.browserProxy_.getContextualManagedData().then(data => {
-      this.managed_ = data.managed;
-      this.extensionReportingSubtitle_ = data.extensionReportingTitle;
-      this.subtitle_ = data.pageSubtitle;
-      // <if expr="chromeos">
-      this.customerLogo_ = data.customerLogo;
-      this.managementOverview_ = data.overview;
-      // </if>
-      // <if expr="not chromeos">
-      this.managementNoticeHtml_ = data.browserManagementNotice;
-      // </if>
-    });
-  },
+  return {
+    BrowserReportingData: BrowserReportingData,
+  };
 });
diff --git a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
index 837247f..53688f8 100644
--- a/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
+++ b/chrome/browser/resources/welcome/onboarding_welcome/set_as_default/nux_set_as_default.html
@@ -88,7 +88,7 @@
       <div class="illustration slide-in" aria-hidden="true"></div>
       <div class="button-bar">
         <cr-button id="decline-button" on-click="onDeclineClick_">
-          $i18n{setDefaultSkip}
+          $i18n{skip}
         </cr-button>
         <step-indicator model="[[indicatorModel]]"></step-indicator>
         <cr-button class="action-button" on-click="onSetDefaultClick_">
diff --git a/chrome/browser/resources/welcome/welcome.css b/chrome/browser/resources/welcome/welcome.css
deleted file mode 100644
index 94d8481..0000000
--- a/chrome/browser/resources/welcome/welcome.css
+++ /dev/null
@@ -1,46 +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. */
-
-body {
-  align-items: center;
-  box-sizing: border-box;
-  color: var(--paper-grey-900);
-  display: flex;
-  flex-direction: column;
-  font-size: 100%;
-  justify-content: center;
-  margin: 0;
-  min-height: 100vh;
-  padding: 8px;
-}
-
-@media (prefers-color-scheme: dark) {
-  body {
-    color: var(--cr-primary-text-color);
-  }
-}
-
-.watermark {
-  -webkit-mask-image: url(chrome://resources/images/google_logo.svg);
-  -webkit-mask-repeat: no-repeat;
-  -webkit-mask-size: 100%;
-  animation: fadeIn 1s cubic-bezier(0, 0, .2, 1) both;
-  background: var(--paper-grey-400);
-  bottom: 24px;
-  height: 24px;
-  position: absolute;
-  width: 74px;
-}
-
-@media (prefers-color-scheme: dark) {
-  .watermark {
-    background: var(--cr-secondary-text-color);
-  }
-}
-
-@media(max-height: 608px) {
-  .watermark {
-    display: none;
-  }
-}
diff --git a/chrome/browser/resources/welcome/welcome.html b/chrome/browser/resources/welcome/welcome.html
deleted file mode 100644
index 8f0474a..0000000
--- a/chrome/browser/resources/welcome/welcome.html
+++ /dev/null
@@ -1,244 +0,0 @@
-<!doctype html>
-<html dir="$i18n{textdirection}" lang="$i18n{language}">
-<head>
-  <meta charset="utf-8">
-  <title>$i18n{headerText}</title>
-
-  <link rel="import" href="chrome://resources/html/polymer.html">
-
-  <link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.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/html/util.html">
-
-  <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
-  <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
-  <link rel="stylesheet" href="chrome://welcome/welcome.css">
-
-  <style>
-    @media (prefers-color-scheme: dark) {
-      html {
-        background-color: var(--md-background-color);
-      }
-    }
-  </style>
-
-  <dom-module id="welcome-app">
-    <template>
-      <style>
-        @keyframes slideUpContent {
-          from {
-            transform: translateY(186px);
-          }
-        }
-
-        @keyframes fadeIn {
-          from {
-            opacity: 0;
-          }
-        }
-
-        @keyframes fadeOut {
-          to {
-            opacity: 0;
-          }
-        }
-
-
-        @keyframes fadeInAndSlideUp {
-          from {
-            opacity: 0;
-            transform: translateY(8px);
-          }
-        }
-
-        @keyframes spin {
-          from {
-            transform: rotate(1440deg) scale(0.8);
-          }
-        }
-
-        @keyframes fadeInAndSlideDownShadow {
-          from {
-            opacity: .6;
-            top: 0;
-          }
-        }
-
-        @keyframes scaleUp {
-          0% {
-            transform: scale(.8);
-          }
-        }
-
-        @keyframes colorize {
-          from {
-            filter: grayscale(100%) brightness(128%) contrast(20%) brightness(161%);
-            opacity: .6;
-          }
-        }
-
-        @keyframes bounce {
-          0% {
-            transform: matrix3d(0.8, 0, 0, 0, 0, 0.8, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          7.61% {
-            transform: matrix3d(0.907, 0, 0, 0, 0, 0.907, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          11.41% {
-            transform: matrix3d(0.948, 0, 0, 0, 0, 0.948, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          15.12% {
-            transform: matrix3d(0.976, 0, 0, 0, 0, 0.976, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          18.92% {
-            transform: matrix3d(0.996, 0, 0, 0, 0, 0.996, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          22.72% {
-            transform: matrix3d(1.008, 0, 0, 0, 0, 1.008, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          30.23% {
-            transform: matrix3d(1.014, 0, 0, 0, 0, 1.014, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          50.25% {
-            transform: matrix3d(1.003, 0, 0, 0, 0, 1.003, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          70.27% {
-            transform: matrix3d(0.999, 0, 0, 0, 0, 0.999, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-          100% {
-            transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
-          }
-        }
-
-        .content {
-          height: 100%;
-          overflow-y: hidden;
-        }
-
-        .slider {
-          align-items: center;
-          animation: slideUpContent 600ms 1.8s cubic-bezier(.4, .2, 0, 1) both;
-          display: flex;
-          flex: 1;
-          flex-direction: column;
-          justify-content: center;
-          max-width: 500px;
-        }
-
-        .heading {
-          animation: fadeInAndSlideUp 600ms 1.9s cubic-bezier(.4, .2, 0, 1) both;
-          font-size: 2.125em;
-          margin-bottom: .25em;
-          margin-top: 1.5em;
-          text-align: center;
-        }
-
-        .subheading {
-          animation: fadeInAndSlideUp 600ms 1.9s cubic-bezier(.4, .2, 0, 1) both;
-          color: rgb(95, 99, 104);
-          font-size: 1em;
-          font-weight: 500;
-          margin-top: .25em;
-          text-align: center;
-        }
-
-        @media (prefers-color-scheme: dark) {
-          .subheading {
-            color: var(--cr-secondary-text-color);
-          }
-        }
-
-        .logo {
-          animation: fadeIn 600ms both, bounce 1s 600ms linear both;
-          height: 96px;
-          position: relative;
-          width: 96px;
-        }
-
-        .logo-icon {
-          animation: spin 2.4s cubic-bezier(.4, .2, 0, 1) both,
-                     colorize 300ms 700ms linear both;
-          background-image: -webkit-image-set(url(chrome://welcome/logo.png) 1x,
-                                              url(chrome://welcome/logo2x.png) 2x);
-          background-size: 100%;
-          height: 96px;
-          width: 96px;
-        }
-
-        .logo-shadow {
-          animation: fadeInAndSlideDownShadow 300ms 600ms both;
-          background: rgba(0, 0, 0, .2);
-          border-radius: 50%;
-          filter: blur(16px);
-          height: 96px;
-          position: absolute;
-          top: 16px;
-          width: 96px;
-          z-index: -1;
-        }
-
-        .signin {
-          animation: fadeInAndSlideUp 600ms 2s cubic-bezier(.4, .2, 0, 1) both;
-          margin-top: 3em;
-        }
-
-        .signin-description {
-          font-size: .875em;
-          line-height: 1.725em;
-          max-width: 344px;
-        }
-
-        .signin-buttons {
-          align-items: center;
-          display: flex;
-          flex-direction: column;
-          margin: auto;
-          margin-top: 2em;
-          width: fit-content;
-        }
-
-        cr-button {
-          font-size: .8125em;
-          /* Makes sure the two cr-button's are the same width since they're
-             placed vertically. Requires parent to be "width: fit-content;". */
-          width: 100%;
-        }
-
-        #cancel {
-          margin-bottom: 2px;  /* Prevent focus ring from being chopped. */
-          margin-top: 1.5em;
-        }
-      </style>
-      <div class="content">
-        <div class="slider">
-          <div class="logo">
-            <div class="logo-icon" on-click="onLogoTap_"></div>
-            <div class="logo-shadow"></div>
-          </div>
-          <div class="heading">$i18n{headerText}</div>
-    <if expr="_google_chrome">
-          <div class="subheading">$i18n{subheaderText}</div>
-    </if>
-          <div class="signin">
-            <div class="signin-description">$i18n{descriptionText}</div>
-            <div class="signin-buttons">
-              <cr-button class="action-button" on-click="onAccept_">
-                $i18n{acceptText}
-              </cr-button>
-              <cr-button id="cancel" on-click="onDecline_">
-                $i18n{declineText}
-              </cr-button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </template>
-    <script src="welcome.js"></script>
-  </dom-module>
-</head>
-<body>
-  <welcome-app></welcome-app>
-  <div class="watermark"></div>
-</body>
-</html>
diff --git a/chrome/browser/resources/welcome/welcome.js b/chrome/browser/resources/welcome/welcome.js
deleted file mode 100644
index 7343242c..0000000
--- a/chrome/browser/resources/welcome/welcome.js
+++ /dev/null
@@ -1,30 +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: 'welcome-app',
-
-  /** @private */
-  onAccept_: function() {
-    chrome.send('handleActivateSignIn');
-  },
-
-  /** @private */
-  onDecline_: function() {
-    chrome.send('handleUserDecline');
-  },
-
-  /** @private */
-  onLogoTap_: function() {
-    this.$$('.logo-icon')
-        .animate(
-            {
-              transform: ['none', 'rotate(-10turn)'],
-            },
-            /** @type {!KeyframeEffectOptions} */ ({
-              duration: 500,
-              easing: 'cubic-bezier(1, 0, 0, 1)',
-            }));
-  },
-});
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc b/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc
index bda0b78..d1e3150 100644
--- a/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc
+++ b/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.cc
@@ -64,8 +64,8 @@
 
 void ShareToSingleTarget(content::WebContents* tab, const GURL& link_url) {
   Profile* profile = Profile::FromBrowserContext(tab->GetBrowserContext());
-  DCHECK(GetValidDeviceCount(profile) == 1);
-  std::vector<TargetDeviceInfo> devices =
+  DCHECK_EQ(GetValidDeviceCount(profile), 1u);
+  const std::vector<TargetDeviceInfo>& devices =
       SendTabToSelfSyncServiceFactory::GetForProfile(profile)
           ->GetSendTabToSelfModel()
           ->GetTargetDeviceInfoSortedList();
@@ -94,24 +94,25 @@
                               device_count);
 }
 
-int GetValidDeviceCount(Profile* profile) {
+size_t GetValidDeviceCount(Profile* profile) {
   SendTabToSelfSyncService* service =
       SendTabToSelfSyncServiceFactory::GetForProfile(profile);
   DCHECK(service);
   SendTabToSelfModel* model = service->GetSendTabToSelfModel();
   DCHECK(model);
-  std::vector<TargetDeviceInfo> devices =
+  const std::vector<TargetDeviceInfo>& devices =
       model->GetTargetDeviceInfoSortedList();
   return devices.size();
 }
 
-std::string GetSingleTargetDeviceName(Profile* profile) {
-  DCHECK(GetValidDeviceCount(profile) == 1);
-  return SendTabToSelfSyncServiceFactory::GetForProfile(profile)
-      ->GetSendTabToSelfModel()
-      ->GetTargetDeviceInfoSortedList()
-      .begin()
-      ->device_name;
+base::string16 GetSingleTargetDeviceName(Profile* profile) {
+  DCHECK_EQ(GetValidDeviceCount(profile), 1u);
+  return base::UTF8ToUTF16(
+      SendTabToSelfSyncServiceFactory::GetForProfile(profile)
+          ->GetSendTabToSelfModel()
+          ->GetTargetDeviceInfoSortedList()
+          .begin()
+          ->device_name);
 }
 
 }  // namespace send_tab_to_self
diff --git a/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h b/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h
index 15387963..5b8322a 100644
--- a/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h
+++ b/chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h
@@ -6,6 +6,8 @@
 #define CHROME_BROWSER_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_DESKTOP_UTIL_H_
 
 #include <string>
+
+#include "base/strings/string16.h"
 #include "url/gurl.h"
 
 class GURL;
@@ -63,11 +65,11 @@
                                     const int& device_count);
 
 // Gets the count of valid device number.
-int GetValidDeviceCount(Profile* profile);
+size_t GetValidDeviceCount(Profile* profile);
 
 // Gets the name of the single valid device. Will be called when
 // GetValidDeviceCount() == 1.
-std::string GetSingleTargetDeviceName(Profile* profile);
+base::string16 GetSingleTargetDeviceName(Profile* profile);
 
 }  // namespace send_tab_to_self
 
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc
index 4c85674..cc5d714 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc
+++ b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller.h"
 
 #include "base/strings/strcat.h"
+#include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/external_protocol/external_protocol_handler.h"
 #include "chrome/browser/sharing/click_to_call/click_to_call_constants.h"
 #include "chrome/browser/sharing/click_to_call/click_to_call_dialog.h"
@@ -23,6 +24,7 @@
 #include "content/public/browser/web_contents.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/strings/grit/ui_strings.h"
+#include "url/url_util.h"
 
 using SharingMessage = chrome_browser_sharing::SharingMessage;
 using App = ClickToCallSharingDialogController::App;
@@ -137,9 +139,17 @@
   send_failed_ = false;
   UpdateIcon();
 
+  std::string phone_number_string(phone_url_.GetContent());
+  url::RawCanonOutputT<base::char16> unescaped_phone_number;
+  url::DecodeURLEscapeSequences(
+      phone_number_string.data(), phone_number_string.size(),
+      url::DecodeURLMode::kUTF8OrIsomorphic, &unescaped_phone_number);
+
   SharingMessage sharing_message;
   sharing_message.mutable_click_to_call_message()->set_phone_number(
-      phone_url_.GetContent());
+      base::UTF16ToUTF8(base::string16(unescaped_phone_number.data(),
+                                       unescaped_phone_number.length())));
+
   sharing_service_->SendMessageToDevice(
       device.guid(), kSharingClickToCallMessageTTL, std::move(sharing_message),
       base::Bind(&ClickToCallSharingDialogController::OnMessageSentToDevice,
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller_unittest.cc b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller_unittest.cc
index 5d0c4c6..71994b7 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller_unittest.cc
+++ b/chrome/browser/sharing/click_to_call/click_to_call_sharing_dialog_controller_unittest.cc
@@ -36,7 +36,8 @@
 
 namespace {
 
-const char kPhoneNumber[] = "987654321";
+const char kPhoneNumber[] = "073%2087%202525%2078";
+const char kExpectedPhoneNumber[] = "073 87 2525 78";
 const char kReceiverGuid[] = "test_receiver_guid";
 const char kReceiverName[] = "test_receiver_name";
 
@@ -112,7 +113,7 @@
       sync_pb::SyncEnums::TYPE_PHONE, base::Time::Now(), 1);
   chrome_browser_sharing::SharingMessage sharing_message;
   sharing_message.mutable_click_to_call_message()->set_phone_number(
-      kPhoneNumber);
+      kExpectedPhoneNumber);
   EXPECT_CALL(*service(), SendMessageToDevice(Eq(kReceiverGuid),
                                               Eq(kSharingClickToCallMessageTTL),
                                               ProtoEquals(sharing_message), _));
diff --git a/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc b/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc
index 5754f2f..cdae9e9 100644
--- a/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_custom_passphrase_sync_test.cc
@@ -9,6 +9,7 @@
 #include "components/sync/base/passphrase_enums.h"
 #include "components/sync/driver/profile_sync_service.h"
 #include "components/sync/driver/sync_driver_switches.h"
+#include "components/sync/engine/sync_engine_switches.h"
 #include "components/sync/nigori/cryptographer.h"
 
 namespace {
@@ -399,6 +400,59 @@
       expected, {KeyDerivationParams::CreateForPbkdf2(), "hunter2"}));
 }
 
+// TODO(https://crbug.com/952074): re-enable once flakiness is addressed.
+#if defined(THREAD_SANITIZER)
+#define MAYBE_PRE_ShouldLoadUSSCustomPassphraseInDirectoryMode \
+  DISABLED_PRE_ShouldLoadUSSCustomPassphraseInDirectoryMode
+#else
+#define MAYBE_PRE_ShouldLoadUSSCustomPassphraseInDirectoryMode \
+  PRE_ShouldLoadUSSCustomPassphraseInDirectoryMode
+#endif
+
+IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
+                       PRE_ShouldLoadUSSCustomPassphraseInDirectoryMode) {
+  base::test::ScopedFeatureList override_features;
+  // TODO(crbug.com/922900): Don't disable scrypt derivation and use it as key
+  // derivation method in ShouldLoadUSSCustomPassphraseInDirectoryMode, once
+  // USS implementation support it for new passphrases.
+  override_features.InitWithFeatures(
+      /*enabled_features=*/{switches::kSyncUSSBookmarks,
+                            switches::kSyncUSSPasswords,
+                            switches::kSyncUSSAutofillWalletMetadata,
+                            switches::kSyncUSSNigori},
+      /*disabled_features=*/{switches::kSyncUseScryptForNewCustomPassphrases});
+  ASSERT_TRUE(SetupSync());
+  ASSERT_TRUE(WaitForNigori(PassphraseType::KEYSTORE_PASSPHRASE));
+  GetSyncService()->GetUserSettings()->SetEncryptionPassphrase("hunter2");
+  ASSERT_TRUE(WaitForNigori(PassphraseType::CUSTOM_PASSPHRASE));
+}
+
+// TODO(https://crbug.com/952074): re-enable once flakiness is addressed.
+#if defined(THREAD_SANITIZER)
+#define MAYBE_ShouldLoadUSSCustomPassphraseInDirectoryMode \
+  DISABLED_ShouldLoadUSSCustomPassphraseInDirectoryMode
+#else
+#define MAYBE_ShouldLoadUSSCustomPassphraseInDirectoryMode \
+  ShouldLoadUSSCustomPassphraseInDirectoryMode
+#endif
+
+IN_PROC_BROWSER_TEST_F(SingleClientCustomPassphraseSyncTest,
+                       ShouldLoadUSSCustomPassphraseInDirectoryMode) {
+  // We should be able to decrypt bookmarks with passphrase, which was set when
+  // kSyncUSSNigori was enabled, without providing it again once kSyncUSSNigori
+  // is disabled.
+  base::test::ScopedFeatureList override_features;
+  override_features.InitAndDisableFeature(switches::kSyncUSSNigori);
+  const KeyParams key_params = {KeyDerivationParams::CreateForPbkdf2(),
+                                "hunter2"};
+  InjectEncryptedServerBookmark(
+      "some bookmark", GURL("http://example.com/doesnt-matter"), key_params);
+  ASSERT_TRUE(SetupClients());
+
+  EXPECT_TRUE(WaitForPassphraseRequiredState(/*desired_state=*/false));
+  EXPECT_TRUE(WaitForClientBookmarkWithTitle("some bookmark"));
+}
+
 INSTANTIATE_TEST_SUITE_P(USS,
                          SingleClientCustomPassphraseDoNotUseScryptSyncTest,
                          testing::Values(false, true));
diff --git a/chrome/browser/ui/app_list/app_list_client_impl.cc b/chrome/browser/ui/app_list/app_list_client_impl.cc
index e234b828..09aaa7cc 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl.cc
@@ -191,10 +191,7 @@
     return;
   }
 
-  requested_model_updater->ActivateChromeItem(id, event_flags);
-
   // Send a training signal to the search controller.
-  CHECK(current_model_updater_);
   const auto* item = current_model_updater_->FindItem(id);
   if (item) {
     app_list::AppLaunchData app_launch_data;
@@ -205,6 +202,19 @@
   }
 
   app_launch_event_logger_.OnGridClicked(id);
+
+  requested_model_updater->ActivateChromeItem(id, event_flags);
+
+  // Suspect that |id| may be destructed after calling ActivateChromeItem. Add
+  // the following two lines to help investigate the crash.
+  std::string copy = id;
+  base::debug::Alias(&copy);
+
+  // Suspect that the model updater may change after calling ActivateChromeItem.
+  // Add checks to help invesigate the crash.
+  CHECK(current_model_updater_);
+  CHECK(requested_model_updater);
+  CHECK(current_model_updater_ == requested_model_updater);
 }
 
 void AppListClientImpl::GetContextMenuModel(
diff --git a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
index a408dbd..88e6e9e 100644
--- a/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
+++ b/chrome/browser/ui/app_list/chrome_app_list_model_updater.cc
@@ -310,7 +310,7 @@
 // Methods for item querying
 
 ChromeAppListItem* ChromeAppListModelUpdater::FindItem(const std::string& id) {
-  return items_.count(id) ? items_[id].get() : nullptr;
+  return items_.find(id) != items_.end() ? items_[id].get() : nullptr;
 }
 
 size_t ChromeAppListModelUpdater::ItemCount() {
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.cc b/chrome/browser/ui/ash/wallpaper_controller_client.cc
index f81641e..1e37d89 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.cc
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.cc
@@ -9,7 +9,6 @@
 #include "base/path_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/chromeos/customization/customization_wallpaper_util.h"
 #include "chrome/browser/chromeos/login/wizard_controller.h"
 #include "chrome/browser/chromeos/policy/device_local_account.h"
@@ -26,7 +25,6 @@
 #include "components/session_manager/core/session_manager.h"
 #include "components/user_manager/known_user.h"
 #include "components/user_manager/user_manager.h"
-#include "content/public/browser/notification_service.h"
 #include "content/public/common/service_manager_connection.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/constants.h"
@@ -501,13 +499,6 @@
                       extensions::AppLaunchSource::kSourceChromeInternal));
 }
 
-void WallpaperControllerClient::OnFirstWallpaperAnimationFinished() {
-  content::NotificationService::current()->Notify(
-      chrome::NOTIFICATION_WALLPAPER_ANIMATION_FINISHED,
-      content::NotificationService::AllSources(),
-      content::NotificationService::NoDetails());
-}
-
 bool WallpaperControllerClient::ShouldShowUserNamesOnLogin() const {
   bool show_user_names = true;
   chromeos::CrosSettings::Get()->GetBoolean(
diff --git a/chrome/browser/ui/ash/wallpaper_controller_client.h b/chrome/browser/ui/ash/wallpaper_controller_client.h
index 16299018..17cd1a8 100644
--- a/chrome/browser/ui/ash/wallpaper_controller_client.h
+++ b/chrome/browser/ui/ash/wallpaper_controller_client.h
@@ -104,7 +104,6 @@
 
   // ash::WallpaperControllerClient:
   void OpenWallpaperPicker() override;
-  void OnFirstWallpaperAnimationFinished() override;
 
   void DeviceWallpaperImageFilePathChanged();
 
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
index a690c98..6083b88 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.cc
@@ -62,8 +62,8 @@
   return l10n_util::GetStringUTF16(IDS_CONTEXT_MENU_SEND_TAB_TO_SELF);
 }
 
-std::vector<TargetDeviceInfo> SendTabToSelfBubbleController::GetValidDevices()
-    const {
+const std::vector<TargetDeviceInfo>&
+SendTabToSelfBubbleController::GetValidDevices() const {
   return valid_devices_;
 }
 
@@ -91,7 +91,7 @@
 SendTabToSelfBubbleController::SendTabToSelfBubbleController(
     content::WebContents* web_contents)
     : web_contents_(web_contents) {
-  this->FetchDeviceInfo();
+  FetchDeviceInfo();
 }
 
 void SendTabToSelfBubbleController::FetchDeviceInfo() {
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
index 00ec118..5ae3ddb 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_controller.h
@@ -40,7 +40,7 @@
   // Returns the title of send tab to self bubble.
   base::string16 GetWindowTitle() const;
   // Returns the valid devices info map.
-  std::vector<TargetDeviceInfo> GetValidDevices() const;
+  const std::vector<TargetDeviceInfo>& GetValidDevices() const;
   // Returns current profile.
   Profile* GetProfile() const;
 
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h
index ac4d9dd..aa31214 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_bubble_view.h
@@ -11,6 +11,8 @@
 // This object is responsible for its own lifetime.
 class SendTabToSelfBubbleView {
  public:
+  virtual ~SendTabToSelfBubbleView() = default;
+
   // Called to close the bubble and prevent future callbacks into the
   // controller.
   virtual void Hide() = 0;
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.cc b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.cc
index bca6921..e48a9da 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.cc
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.cc
@@ -41,7 +41,7 @@
 
 // Returns true if the command id identifies a non-link contextual menu item.
 bool IsShareTabCommandId(int command_id) {
-  return (command_id >= kShareTabCommandId && command_id < kShareLinkCommandId);
+  return command_id >= kShareTabCommandId && command_id < kShareLinkCommandId;
 }
 
 // Returns true if the command id identifies a link contextual menu item.
@@ -53,7 +53,8 @@
 int CommandIdToVectorIndex(int command_id) {
   if (IsShareTabCommandId(command_id)) {
     return command_id - kShareTabCommandId;
-  } else if (IsShareLinkCommandId(command_id)) {
+  }
+  if (IsShareLinkCommandId(command_id)) {
     return command_id - kShareLinkCommandId;
   }
   return -1;
@@ -87,6 +88,11 @@
 
 SendTabToSelfSubMenuModel::SendTabToSelfSubMenuModel(
     content::WebContents* tab,
+    SendTabToSelfMenuType menu_type)
+    : SendTabToSelfSubMenuModel(tab, menu_type, GURL()) {}
+
+SendTabToSelfSubMenuModel::SendTabToSelfSubMenuModel(
+    content::WebContents* tab,
     SendTabToSelfMenuType menu_type,
     const GURL& link_url)
     : ui::SimpleMenuModel(this),
@@ -155,14 +161,8 @@
                                                 const std::string& cache_guid,
                                                 int index) {
   ValidDeviceItem item(device_name, cache_guid);
-  int command_id;
-  if (menu_type_ == kLink) {
-    // Generates command ids for sharing a link.
-    command_id = index + kShareLinkCommandId;
-  } else {
-    // Generates command ids for sharing a tab.
-    command_id = index + kShareTabCommandId;
-  }
+  int command_id =
+      (menu_type_ == kTab) ? kShareTabCommandId : kShareLinkCommandId + index;
   InsertItemAt(index, command_id, base::UTF8ToUTF16(device_name));
   valid_device_items_.push_back(item);
 }
diff --git a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.h b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.h
index 464a8df..0d5ddf170 100644
--- a/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.h
+++ b/chrome/browser/ui/send_tab_to_self/send_tab_to_self_sub_menu_model.h
@@ -33,8 +33,10 @@
   struct ValidDeviceItem;
 
   SendTabToSelfSubMenuModel(content::WebContents* tab,
+                            SendTabToSelfMenuType menu_type);
+  SendTabToSelfSubMenuModel(content::WebContents* tab,
                             SendTabToSelfMenuType menu_type,
-                            const GURL& link_url = GURL());
+                            const GURL& link_url);
   ~SendTabToSelfSubMenuModel() override;
 
   // Overridden from ui::SimpleMenuModel::Delegate:
diff --git a/chrome/browser/ui/tabs/pinned_tab_service.cc b/chrome/browser/ui/tabs/pinned_tab_service.cc
index ac761ab0..29e862bb 100644
--- a/chrome/browser/ui/tabs/pinned_tab_service.cc
+++ b/chrome/browser/ui/tabs/pinned_tab_service.cc
@@ -7,47 +7,16 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/tabs/pinned_tab_codec.h"
 #include "content/public/browser/notification_service.h"
 
-namespace {
-
-// Returns true if |browser| is the only normal (tabbed) browser for |browser|'s
-// profile (across all desktops).
-bool IsOnlyNormalBrowser(Browser* browser) {
-  for (auto* b : *BrowserList::GetInstance()) {
-    if (b != browser && b->is_type_tabbed() &&
-        b->profile() == browser->profile()) {
-      return false;
-    }
-  }
-  return true;
-}
-
-// Returns true if there's at lease one tabbed browser associated with
-// |profile|.
-bool BrowserListHasNormalBrowser(Profile* profile) {
-  for (auto* b : *BrowserList::GetInstance()) {
-    if (b->is_type_tabbed() && b->profile() == profile)
-      return true;
-  }
-  return false;
-}
-
-}  // namespace
-
-PinnedTabService::PinnedTabService(Profile* profile)
-    : profile_(profile),
-      save_pinned_tabs_(true),
-      has_normal_browser_(false),
-      browser_list_observer_(this) {
-  registrar_.Add(this, chrome::NOTIFICATION_BROWSER_OPENED,
-                 content::NotificationService::AllBrowserContextsAndSources());
+PinnedTabService::PinnedTabService(Profile* profile) : profile_(profile) {
   registrar_.Add(this, chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST,
                  content::NotificationService::AllSources());
-  registrar_.Add(this, chrome::NOTIFICATION_TAB_ADDED,
-                 content::NotificationService::AllSources());
+
+  for (Browser* browser : *BrowserList::GetInstance())
+    OnBrowserAdded(browser);
+
   browser_list_observer_.Add(BrowserList::GetInstance());
 }
 
@@ -56,14 +25,12 @@
 void PinnedTabService::Observe(int type,
                                const content::NotificationSource& source,
                                const content::NotificationDetails& details) {
-  // Saving of tabs happens when saving is enabled, and when either the user
-  // exits the application or closes the last browser window.
-  // Saving is disabled when the user exits the application to prevent the
-  // pin state of all the open browsers being overwritten by the state of the
-  // last browser window to close.
-  // Saving is re-enabled when a browser window or tab is opened again.
-  // Note, cancelling a shutdown (via onbeforeunload) will not re-enable pinned
-  // tab saving immediately, to prevent the following situation:
+  // Saving of tabs happens when the user exits the application or closes the
+  // last browser window. After saving, |need_to_write_pinned_tabs_| is set to
+  // false to make sure subsequent window closures don't overwrite the pinned
+  // tab state. Saving is re-enabled when a browser window or tab is opened
+  // again. Note, cancelling a shutdown (via onbeforeunload) will not re-enable
+  // pinned tab saving immediately, to prevent the following situation:
   //   * two windows are open, one with pinned tabs
   //   * user exits
   //   * pinned tabs are saved
@@ -73,55 +40,52 @@
   //   * pinned tabs are saved, without the window with the pinned tabs,
   //     over-writing the correct state.
   // Saving is re-enabled if a new tab or window is opened.
-  switch (type) {
-    case chrome::NOTIFICATION_BROWSER_OPENED: {
-      Browser* browser = content::Source<Browser>(source).ptr();
-      if (!has_normal_browser_ && browser->is_type_tabbed() &&
-          browser->profile() == profile_) {
-        has_normal_browser_ = true;
-      }
-      save_pinned_tabs_ = true;
-      break;
-    }
+  DCHECK_EQ(type, chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST);
+  if (observed_tab_strips_.IsObservingSources())
+    WritePinnedTabsIfNecessary();
+}
 
-    case chrome::NOTIFICATION_TAB_ADDED: {
-      save_pinned_tabs_ = true;
-      break;
-    }
+void PinnedTabService::OnBrowserAdded(Browser* browser) {
+  if (browser->profile() != profile_ || !browser->is_type_tabbed())
+    return;
 
-    case chrome::NOTIFICATION_CLOSE_ALL_BROWSERS_REQUEST: {
-      if (has_normal_browser_ && save_pinned_tabs_) {
-        PinnedTabCodec::WritePinnedTabs(profile_);
-        save_pinned_tabs_ = false;
-      }
-      break;
-    }
-
-    default:
-      NOTREACHED();
-  }
+  need_to_write_pinned_tabs_ = true;
+  observed_tab_strips_.Add(browser->tab_strip_model());
 }
 
 void PinnedTabService::OnBrowserClosing(Browser* browser) {
-  if (has_normal_browser_ && save_pinned_tabs_ &&
-      browser->profile() == profile_ && IsOnlyNormalBrowser(browser)) {
-    has_normal_browser_ = false;
-    PinnedTabCodec::WritePinnedTabs(profile_);
-  }
+  if (browser->profile() != profile_ || !browser->is_type_tabbed())
+    return;
+
+  if (observed_tab_strips_.GetSourcesCount() == 1U)
+    WritePinnedTabsIfNecessary();
 }
 
 void PinnedTabService::OnBrowserRemoved(Browser* browser) {
-  if (!browser->is_type_tabbed() || browser->profile() != profile_)
+  if (browser->profile() != profile_ || !browser->is_type_tabbed())
     return;
 
-  if (save_pinned_tabs_ && has_normal_browser_ &&
-      !BrowserListHasNormalBrowser(browser->profile())) {
-    // This happens when user closes each tabs manually via the close button on
-    // them. In this case OnBrowserClosing() above is not called. This causes
-    // pinned tabs to repopen on the next startup. So we should call
-    // WritePinnedTab() to clear the data.
-    // http://crbug.com/71939
-    has_normal_browser_ = false;
+  observed_tab_strips_.Remove(browser->tab_strip_model());
+
+  // This happens when user closes each tabs manually via the close button on
+  // them. In this case OnBrowserClosing() above is not called. This causes
+  // pinned tabs to repopen on the next startup. So we should call
+  // WritePinnedTab() to clear the data.
+  // http://crbug.com/71939
+  if (!observed_tab_strips_.IsObservingSources())
+    WritePinnedTabsIfNecessary();
+}
+
+void PinnedTabService::OnTabStripModelChanged(
+    TabStripModel* tab_strip_model,
+    const TabStripModelChange& change,
+    const TabStripSelectionChange& selection) {
+  if (change.type() == TabStripModelChange::kInserted)
+    need_to_write_pinned_tabs_ = true;
+}
+
+void PinnedTabService::WritePinnedTabsIfNecessary() {
+  if (need_to_write_pinned_tabs_)
     PinnedTabCodec::WritePinnedTabs(profile_);
-  }
+  need_to_write_pinned_tabs_ = false;
 }
diff --git a/chrome/browser/ui/tabs/pinned_tab_service.h b/chrome/browser/ui/tabs/pinned_tab_service.h
index 23922c3..2d624c0 100644
--- a/chrome/browser/ui/tabs/pinned_tab_service.h
+++ b/chrome/browser/ui/tabs/pinned_tab_service.h
@@ -10,6 +10,8 @@
 #include "base/scoped_observer.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/browser_list_observer.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
@@ -21,6 +23,7 @@
 // appropriate set of notifications to know it should update preferences.
 class PinnedTabService : public content::NotificationObserver,
                          public BrowserListObserver,
+                         public TabStripModelObserver,
                          public KeyedService {
  public:
   explicit PinnedTabService(Profile* profile);
@@ -33,21 +36,34 @@
                const content::NotificationDetails& details) override;
 
   // BrowserListObserver:
+  void OnBrowserAdded(Browser* browser) override;
   void OnBrowserClosing(Browser* browser) override;
   void OnBrowserRemoved(Browser* browser) override;
 
+  // TabStripModelObserver:
+  void OnTabStripModelChanged(
+      TabStripModel* tab_strip_model,
+      const TabStripModelChange& change,
+      const TabStripSelectionChange& selection) override;
+
+  // Writes the pinned tabs for |profile_|, but only if a new tab or browser
+  // window has been added since the last time the method was called.
+  void WritePinnedTabsIfNecessary();
+
   Profile* profile_;
 
   // True if we should save the pinned tabs when a browser window closes or the
-  // user exits the application.
-  bool save_pinned_tabs_;
-
-  // True if there is at least one normal browser for our profile.
-  bool has_normal_browser_;
+  // user exits the application. This is set to false after writing pinned tabs,
+  // and set back to true when new tabs or windows are added.
+  bool need_to_write_pinned_tabs_ = true;
 
   content::NotificationRegistrar registrar_;
 
-  ScopedObserver<BrowserList, BrowserListObserver> browser_list_observer_;
+  ScopedObserver<BrowserList, BrowserListObserver> browser_list_observer_{this};
+
+  // |this| observes all tabbed browsers that match |profile_|.
+  ScopedObserver<TabStripModel, TabStripModelObserver> observed_tab_strips_{
+      this};
 
   DISALLOW_COPY_AND_ASSIGN(PinnedTabService);
 };
diff --git a/chrome/browser/ui/tabs/tab_menu_model.cc b/chrome/browser/ui/tabs/tab_menu_model.cc
index 717748c..a796b6e 100644
--- a/chrome/browser/ui/tabs/tab_menu_model.cc
+++ b/chrome/browser/ui/tabs/tab_menu_model.cc
@@ -6,7 +6,6 @@
 
 #include "base/command_line.h"
 #include "base/metrics/user_metrics.h"
-#include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/send_tab_to_self/send_tab_to_self_desktop_util.h"
@@ -94,16 +93,15 @@
       AddItem(TabStripModel::CommandSendTabToSelfSingleTarget,
               l10n_util::GetStringFUTF16(
                   IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-                  base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                      tab_strip->profile()))));
+                  send_tab_to_self::GetSingleTargetDeviceName(
+                      tab_strip->profile())));
 #else
-      AddItemWithIcon(
-          TabStripModel::CommandSendTabToSelfSingleTarget,
-          l10n_util::GetStringFUTF16(
-              IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-              base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                  tab_strip->profile()))),
-          *send_tab_to_self::GetImageSkia());
+      AddItemWithIcon(TabStripModel::CommandSendTabToSelfSingleTarget,
+                      l10n_util::GetStringFUTF16(
+                          IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
+                          (send_tab_to_self::GetSingleTargetDeviceName(
+                              tab_strip->profile()))),
+                      *send_tab_to_self::GetImageSkia());
 #endif
       send_tab_to_self::RecordSendTabToSelfClickResult(
           send_tab_to_self::kTabMenu,
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index a7c88b8..f270fc7 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -66,6 +66,7 @@
 #include "chrome/browser/ui/tabs/tab_menu_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/browser/ui/tabs/tab_utils.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/accelerator_table.h"
 #include "chrome/browser/ui/views/accessibility/invert_bubble_view.h"
@@ -1163,6 +1164,8 @@
 }
 
 ToolbarActionsBar* BrowserView::GetToolbarActionsBar() {
+  CHECK(!base::FeatureList::IsEnabled(features::kExtensionsToolbarMenu));
+
   BrowserActionsContainer* container =
       toolbar_button_provider_->GetBrowserActionsContainer();
   return container ? container->toolbar_actions_bar() : nullptr;
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view.cc b/chrome/browser/ui/views/location_bar/location_bar_view.cc
index 2abc7d0..1f30038 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view.cc
@@ -1041,10 +1041,7 @@
   PageActionIconView* icon =
       this->omnibox_page_action_icon_container_view()->GetPageActionIconView(
           PageActionIconType::kSendTabToSelf);
-  if (icon) {
-    return icon->Update();
-  }
-  return false;
+  return icon && icon->Update();
 }
 
 void LocationBarView::SaveStateToContents(WebContents* contents) {
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
index ab3ecf2..df3e693 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view.cc
@@ -6,6 +6,7 @@
 
 #include "base/i18n/message_formatter.h"
 #include "base/i18n/unicodestring.h"
+#include "base/stl_util.h"
 #include "chrome/app/vector_icons/vector_icons.h"
 #include "chrome/browser/native_file_system/chrome_native_file_system_permission_context.h"
 #include "chrome/browser/ui/browser.h"
@@ -248,6 +249,18 @@
           ->toolbar_button_provider()
           ->GetOmniboxPageActionIconContainerView();
 
+  // Writable directories are generally also readable, but we don't want to
+  // display the same directory twice. So filter out any writable directories
+  // from the readable directories list.
+  std::set<base::FilePath> writable_directories(
+      usage.writable_directories.begin(), usage.writable_directories.end());
+  std::vector<base::FilePath> readable_directories;
+  for (base::FilePath& path : usage.readable_directories) {
+    if (!base::Contains(writable_directories, path))
+      readable_directories.push_back(std::move(path));
+  }
+  usage.readable_directories = readable_directories;
+
   bubble_ = new NativeFileSystemUsageBubbleView(
       anchor_view, gfx::Point(), web_contents, origin, std::move(usage));
 
diff --git a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view_browsertest.cc b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view_browsertest.cc
index f4ef40d..0fd7a8c 100644
--- a/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/native_file_system/native_file_system_usage_bubble_view_browsertest.cc
@@ -54,6 +54,16 @@
       usage.readable_directories.emplace_back(
           FILE_PATH_LITERAL("/baz/My Project"));
       usage.readable_directories.emplace_back(FILE_PATH_LITERAL("/baz/Assets"));
+    } else if (name == "ReadableAndWritableFolders") {
+      usage.readable_directories.emplace_back(
+          FILE_PATH_LITERAL("/foo/bar/Images"));
+      usage.readable_directories.emplace_back(
+          FILE_PATH_LITERAL("/baz/My Project"));
+      usage.readable_directories.emplace_back(FILE_PATH_LITERAL("/baz/Assets"));
+      usage.writable_directories.emplace_back(FILE_PATH_LITERAL("/baz/Assets"));
+      usage.writable_directories.emplace_back(
+          FILE_PATH_LITERAL("/la/asdf/Processing"));
+      usage.writable_directories.emplace_back(FILE_PATH_LITERAL("/baz/Images"));
     } else if (name == "default") {
       usage.readable_directories.emplace_back(
           FILE_PATH_LITERAL("/home/me/Images"));
@@ -119,3 +129,8 @@
                        InvokeUi_MultipleReadableFolders) {
   ShowAndVerifyUi();
 }
+
+IN_PROC_BROWSER_TEST_F(NativeFileSystemUsageBubbleViewTest,
+                       InvokeUi_ReadableAndWritableFolders) {
+  ShowAndVerifyUi();
+}
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index d1031ab..dda1742c 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -12,7 +12,6 @@
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "build/build_config.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/command_updater.h"
@@ -1773,8 +1772,8 @@
           index, IDC_SEND_TAB_TO_SELF_SINGLE_TARGET,
           l10n_util::GetStringFUTF16(
               IDS_CONTEXT_MENU_SEND_TAB_TO_SELF_SINGLE_TARGET,
-              base::UTF8ToUTF16(send_tab_to_self::GetSingleTargetDeviceName(
-                  location_bar_view_->profile()))));
+              send_tab_to_self::GetSingleTargetDeviceName(
+                  location_bar_view_->profile())));
       send_tab_to_self::RecordSendTabToSelfClickResult(
           send_tab_to_self::kOmniboxMenu,
           SendTabToSelfClickResult::kShowDeviceList);
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc
index 5ba7e8791..b55cccd42 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.cc
@@ -6,6 +6,7 @@
 
 #include <string>
 
+#include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
 #include "chrome/app/vector_icons/vector_icons.h"
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.h
index d560bebc..01c15f79 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.h
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_device_button.h
@@ -24,8 +24,8 @@
                                   int button_tag);
   ~SendTabToSelfBubbleDeviceButton() override;
 
-  const std::string device_name() const { return device_name_; }
-  const std::string device_guid() const { return device_guid_; }
+  const std::string& device_name() const { return device_name_; }
+  const std::string& device_guid() const { return device_guid_; }
   sync_pb::SyncEnums::DeviceType device_type() const { return device_type_; }
 
  private:
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
index b494f8c1..9d5dfc3 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.cc
@@ -25,6 +25,17 @@
 
 namespace send_tab_to_self {
 
+namespace {
+
+// The valid device button height.
+constexpr int kDeviceButtonHeight = 56;
+// Maximum number of buttons that are shown without scroll. If the device
+// number is larger than kMaximumButtons, the bubble content will be
+// scrollable.
+constexpr int kMaximumButtons = 5;
+
+}  // namespace
+
 SendTabToSelfBubbleViewImpl::SendTabToSelfBubbleViewImpl(
     views::View* anchor_view,
     const gfx::Point& anchor_point,
@@ -71,10 +82,7 @@
 
 void SendTabToSelfBubbleViewImpl::ButtonPressed(views::Button* sender,
                                                 const ui::Event& event) {
-  base::PostTaskWithTraits(
-      FROM_HERE, {content::BrowserThread::UI},
-      base::BindOnce(&SendTabToSelfBubbleViewImpl::DevicePressed,
-                     weak_factory_.GetWeakPtr(), sender->tag()));
+  DevicePressed(sender->tag());
 }
 
 gfx::Size SendTabToSelfBubbleViewImpl::CalculatePreferredSize() const {
@@ -129,7 +137,7 @@
 }
 
 void SendTabToSelfBubbleViewImpl::PopulateScrollView(
-    const std::vector<TargetDeviceInfo> devices) {
+    const std::vector<TargetDeviceInfo>& devices) {
   device_buttons_.clear();
   auto device_list_view = std::make_unique<views::View>();
   device_list_view->SetLayoutManager(std::make_unique<views::BoxLayout>(
@@ -152,8 +160,7 @@
   if (!controller_) {
     return;
   }
-  SendTabToSelfBubbleDeviceButton* device_button =
-      device_buttons_.at(index).get();
+  SendTabToSelfBubbleDeviceButton* device_button = device_buttons_[index].get();
   controller_->OnDeviceSelected(device_button->device_name(),
                                 device_button->device_guid());
   Hide();
diff --git a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h
index d1222f2..910064b 100644
--- a/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h
+++ b/chrome/browser/ui/views/send_tab_to_self/send_tab_to_self_bubble_view_impl.h
@@ -5,7 +5,9 @@
 #ifndef CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_IMPL_H_
 #define CHROME_BROWSER_UI_VIEWS_SEND_TAB_TO_SELF_SEND_TAB_TO_SELF_BUBBLE_VIEW_IMPL_H_
 
+#include <map>
 #include <memory>
+#include <string>
 #include <vector>
 
 #include "base/memory/weak_ptr.h"
@@ -21,7 +23,7 @@
 
 namespace content {
 class WebContents;
-}
+}  // namespace content
 
 namespace send_tab_to_self {
 
@@ -35,13 +37,6 @@
                                     public views::ButtonListener,
                                     public LocationBarBubbleDelegateView {
  public:
-  // The valid device button height.
-  static constexpr int kDeviceButtonHeight = 56;
-  // Maximum number of buttons that are shown without scroll. If the device
-  // number is larger than kMaximumButtons, the bubble content will be
-  // scrollable.
-  static constexpr int kMaximumButtons = 5;
-
   // Bubble will be anchored to |anchor_view|.
   SendTabToSelfBubbleViewImpl(views::View* anchor_view,
                               const gfx::Point& anchor_point,
@@ -88,7 +83,7 @@
   void CreateScrollView();
 
   // Populates the scroll view containing valid devices.
-  void PopulateScrollView(const std::vector<TargetDeviceInfo> devices);
+  void PopulateScrollView(const std::vector<TargetDeviceInfo>& devices);
 
   // Handles the action when a target device has been pressed.
   void DevicePressed(size_t index);
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
index 7dc7653..de342d5a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.cc
@@ -11,7 +11,6 @@
 #include "base/values.h"
 #include "chrome/browser/ui/ash/tablet_mode_client.h"
 #include "chromeos/constants/chromeos_switches.h"
-#include "chromeos/services/assistant/public/features.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/common/service_manager_connection.h"
 #include "services/service_manager/public/cpp/connector.h"
@@ -150,9 +149,7 @@
   keyboard_params.SetKey("hasInternalKeyboard",
                          base::Value(keyboards_state.has_internal_keyboard));
 
-  const bool show_assistant_key_settings =
-      chromeos::assistant::features::IsKeyRemappingEnabled() &&
-      ui::DeviceKeyboardHasAssistantKey();
+  const bool show_assistant_key_settings = ui::DeviceKeyboardHasAssistantKey();
   keyboard_params.SetKey("hasAssistantKey",
                          base::Value(show_assistant_key_settings));
 
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.cc b/chrome/browser/ui/webui/welcome/welcome_ui.cc
index 6786e280..4ae52e2 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.cc
@@ -5,9 +5,7 @@
 #include "chrome/browser/ui/webui/welcome/welcome_ui.h"
 
 #include "base/bind.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/stl_util.h"
-#include "build/build_config.h"
+#include "base/strings/string_number_conversions.h"
 #include "chrome/browser/signin/account_consistency_mode_manager.h"
 #include "chrome/browser/ui/webui/localized_string.h"
 #include "chrome/browser/ui/webui/welcome/nux/bookmark_handler.h"
@@ -18,7 +16,6 @@
 #include "chrome/browser/ui/webui/welcome/nux_helper.h"
 #include "chrome/browser/ui/webui/welcome/welcome_handler.h"
 #include "chrome/common/pref_names.h"
-#include "chrome/grit/browser_resources.h"
 #include "chrome/grit/chrome_unscaled_resources.h"
 #include "chrome/grit/chromium_strings.h"
 #include "chrome/grit/generated_resources.h"
@@ -27,9 +24,7 @@
 #include "components/prefs/pref_service.h"
 #include "components/signin/public/base/signin_pref_names.h"
 #include "components/strings/grit/components_strings.h"
-#include "content/public/browser/web_contents.h"
 #include "net/base/url_util.h"
-#include "ui/base/l10n/l10n_util.h"
 
 #if defined(OS_WIN)
 #include "base/win/windows_version.h"
@@ -37,13 +32,6 @@
 
 namespace {
 
-const bool kIsBranded =
-#if defined(GOOGLE_CHROME_BUILD)
-    true;
-#else
-    false;
-#endif
-
 const char kPreviewBackgroundPath[] = "preview-background.jpg";
 
 bool ShouldHandleRequestCallback(base::WeakPtr<WelcomeUI> weak_ptr,
@@ -81,13 +69,11 @@
 void AddOnboardingStrings(content::WebUIDataSource* html_source) {
   static constexpr LocalizedString kLocalizedStrings[] = {
       // Shared strings.
-      {"acceptText", IDS_WELCOME_ACCEPT_BUTTON},
       {"bookmarkAdded", IDS_ONBOARDING_WELCOME_BOOKMARK_ADDED},
       {"bookmarksAdded", IDS_ONBOARDING_WELCOME_BOOKMARKS_ADDED},
       {"bookmarkRemoved", IDS_ONBOARDING_WELCOME_BOOKMARK_REMOVED},
       {"bookmarksRemoved", IDS_ONBOARDING_WELCOME_BOOKMARKS_REMOVED},
       {"defaultBrowserChanged", IDS_ONBOARDING_DEFAULT_BROWSER_CHANGED},
-      {"getStarted", IDS_ONBOARDING_WELCOME_GET_STARTED},
       {"headerText", IDS_WELCOME_HEADER},
       {"next", IDS_ONBOARDING_WELCOME_NEXT},
       {"noThanks", IDS_NO_THANKS},
@@ -115,7 +101,6 @@
       {"setDefaultHeader", IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_HEADER},
       {"setDefaultSubHeader",
        IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_SUB_HEADER},
-      {"setDefaultSkip", IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_SKIP},
       {"setDefaultConfirm",
        IDS_ONBOARDING_WELCOME_NUX_SET_AS_DEFAULT_SET_AS_DEFAULT},
 
@@ -148,111 +133,81 @@
   content::WebUIDataSource* html_source =
       content::WebUIDataSource::Create(url.host());
 
-  if (nux::IsNuxOnboardingEnabled(profile)) {
-    // Add Onboarding welcome strings.
-    AddOnboardingStrings(html_source);
+  // Add Onboarding welcome strings.
+  AddOnboardingStrings(html_source);
 
-    // Add all Onboarding resources.
-    for (size_t i = 0; i < kOnboardingWelcomeResourcesSize; ++i) {
-      html_source->AddResourcePath(kOnboardingWelcomeResources[i].name,
-                                   kOnboardingWelcomeResources[i].value);
-    }
+  // Add all Onboarding resources.
+  for (size_t i = 0; i < kOnboardingWelcomeResourcesSize; ++i) {
+    html_source->AddResourcePath(kOnboardingWelcomeResources[i].name,
+                                 kOnboardingWelcomeResources[i].value);
+  }
 
 #if defined(GOOGLE_CHROME_BUILD)
-    // Load unscaled images.
-    html_source->AddResourcePath("images/module_icons/google_dark.svg",
-                                 IDR_WELCOME_MODULE_ICONS_GOOGLE_DARK);
-    html_source->AddResourcePath("images/module_icons/google_light.svg",
-                                 IDR_WELCOME_MODULE_ICONS_GOOGLE_LIGHT);
-    html_source->AddResourcePath("images/module_icons/set_default_dark.svg",
-                                 IDR_WELCOME_MODULE_ICONS_SET_DEFAULT_DARK);
-    html_source->AddResourcePath("images/module_icons/set_default_light.svg",
-                                 IDR_WELCOME_MODULE_ICONS_SET_DEFAULT_LIGHT);
-    html_source->AddResourcePath("images/module_icons/wallpaper_dark.svg",
-                                 IDR_WELCOME_MODULE_ICONS_WALLPAPER_DARK);
-    html_source->AddResourcePath("images/module_icons/wallpaper_light.svg",
-                                 IDR_WELCOME_MODULE_ICONS_WALLPAPER_LIGHT);
-    html_source->AddResourcePath("images/ntp_thumbnails/art.jpg",
-                                 IDR_WELCOME_NTP_THUMBNAILS_ART);
-    html_source->AddResourcePath("images/ntp_thumbnails/cityscape.jpg",
-                                 IDR_WELCOME_NTP_THUMBNAILS_CITYSCAPE);
-    html_source->AddResourcePath("images/ntp_thumbnails/earth.jpg",
-                                 IDR_WELCOME_NTP_THUMBNAILS_EARTH);
-    html_source->AddResourcePath("images/ntp_thumbnails/geometric_shapes.jpg",
-                                 IDR_WELCOME_NTP_THUMBNAILS_GEOMETRIC_SHAPES);
-    html_source->AddResourcePath("images/ntp_thumbnails/landscape.jpg",
-                                 IDR_WELCOME_NTP_THUMBNAILS_LANDSCAPE);
-    html_source->AddResourcePath("images/set_default_dark.svg",
-                                 IDR_WELCOME_SET_DEFAULT_DARK);
-    html_source->AddResourcePath("images/set_default_light.svg",
-                                 IDR_WELCOME_SET_DEFAULT_LIGHT);
+  // Load unscaled images.
+  html_source->AddResourcePath("images/module_icons/google_dark.svg",
+                               IDR_WELCOME_MODULE_ICONS_GOOGLE_DARK);
+  html_source->AddResourcePath("images/module_icons/google_light.svg",
+                               IDR_WELCOME_MODULE_ICONS_GOOGLE_LIGHT);
+  html_source->AddResourcePath("images/module_icons/set_default_dark.svg",
+                               IDR_WELCOME_MODULE_ICONS_SET_DEFAULT_DARK);
+  html_source->AddResourcePath("images/module_icons/set_default_light.svg",
+                               IDR_WELCOME_MODULE_ICONS_SET_DEFAULT_LIGHT);
+  html_source->AddResourcePath("images/module_icons/wallpaper_dark.svg",
+                               IDR_WELCOME_MODULE_ICONS_WALLPAPER_DARK);
+  html_source->AddResourcePath("images/module_icons/wallpaper_light.svg",
+                               IDR_WELCOME_MODULE_ICONS_WALLPAPER_LIGHT);
+  html_source->AddResourcePath("images/ntp_thumbnails/art.jpg",
+                               IDR_WELCOME_NTP_THUMBNAILS_ART);
+  html_source->AddResourcePath("images/ntp_thumbnails/cityscape.jpg",
+                               IDR_WELCOME_NTP_THUMBNAILS_CITYSCAPE);
+  html_source->AddResourcePath("images/ntp_thumbnails/earth.jpg",
+                               IDR_WELCOME_NTP_THUMBNAILS_EARTH);
+  html_source->AddResourcePath("images/ntp_thumbnails/geometric_shapes.jpg",
+                               IDR_WELCOME_NTP_THUMBNAILS_GEOMETRIC_SHAPES);
+  html_source->AddResourcePath("images/ntp_thumbnails/landscape.jpg",
+                               IDR_WELCOME_NTP_THUMBNAILS_LANDSCAPE);
+  html_source->AddResourcePath("images/set_default_dark.svg",
+                               IDR_WELCOME_SET_DEFAULT_DARK);
+  html_source->AddResourcePath("images/set_default_light.svg",
+                               IDR_WELCOME_SET_DEFAULT_LIGHT);
 #endif  // defined(GOOGLE_CHROME_BUILD)
 
-    // chrome://welcome
-    html_source->SetDefaultResource(
-        IDR_WELCOME_ONBOARDING_WELCOME_WELCOME_HTML);
+  // chrome://welcome
+  html_source->SetDefaultResource(IDR_WELCOME_ONBOARDING_WELCOME_WELCOME_HTML);
 
 #if defined(OS_WIN)
-    html_source->AddBoolean(
-        "is_win10", base::win::GetVersion() >= base::win::Version::WIN10);
+  html_source->AddBoolean("is_win10",
+                          base::win::GetVersion() >= base::win::Version::WIN10);
 #endif
 
-    // Add the shared bookmark handler for onboarding modules.
-    web_ui->AddMessageHandler(
-        std::make_unique<nux::BookmarkHandler>(profile->GetPrefs()));
+  // Add the shared bookmark handler for onboarding modules.
+  web_ui->AddMessageHandler(
+      std::make_unique<nux::BookmarkHandler>(profile->GetPrefs()));
 
-    // Add google apps bookmarking onboarding module.
-    web_ui->AddMessageHandler(std::make_unique<nux::GoogleAppsHandler>());
+  // Add google apps bookmarking onboarding module.
+  web_ui->AddMessageHandler(std::make_unique<nux::GoogleAppsHandler>());
 
-    // Add NTP custom background onboarding module.
-    web_ui->AddMessageHandler(std::make_unique<nux::NtpBackgroundHandler>());
+  // Add NTP custom background onboarding module.
+  web_ui->AddMessageHandler(std::make_unique<nux::NtpBackgroundHandler>());
 
-    // Add set-as-default onboarding module.
-    web_ui->AddMessageHandler(std::make_unique<nux::SetAsDefaultHandler>());
+  // Add set-as-default onboarding module.
+  web_ui->AddMessageHandler(std::make_unique<nux::SetAsDefaultHandler>());
 
-    html_source->AddString(
-        "newUserModules",
-        nux::GetNuxOnboardingModules(profile).FindKey("new-user")->GetString());
-    html_source->AddString("returningUserModules",
-                           nux::GetNuxOnboardingModules(profile)
-                               .FindKey("returning-user")
-                               ->GetString());
-    html_source->AddBoolean("signinAllowed", profile->GetPrefs()->GetBoolean(
-                                                 prefs::kSigninAllowed));
-    html_source->SetRequestFilter(
-        base::BindRepeating(&ShouldHandleRequestCallback,
-                            weak_ptr_factory_.GetWeakPtr()),
-        base::BindRepeating(&HandleRequestCallback,
-                            weak_ptr_factory_.GetWeakPtr()));
-    html_source->SetJsonPath("strings.js");
-  } else {
-    // Use default layout for non-DICE or unbranded build.
-    std::string value;
-    bool is_everywhere_variant =
-        (net::GetValueForKeyInQuery(url, "variant", &value) &&
-         value == "everywhere");
-
-    if (kIsBranded) {
-      base::string16 subheader =
-          is_everywhere_variant
-              ? base::string16()
-              : l10n_util::GetStringUTF16(IDS_WELCOME_SUBHEADER);
-      html_source->AddString("subheaderText", subheader);
-    }
-
-    int header_id = is_everywhere_variant ? IDS_WELCOME_HEADER_AFTER_FIRST_RUN
-                                          : IDS_WELCOME_HEADER;
-    html_source->AddString("headerText", l10n_util::GetStringUTF16(header_id));
-    html_source->AddLocalizedString("acceptText", IDS_WELCOME_ACCEPT_BUTTON);
-    html_source->AddLocalizedString("descriptionText", IDS_WELCOME_DESCRIPTION);
-    html_source->AddLocalizedString("declineText", IDS_WELCOME_DECLINE_BUTTON);
-    html_source->AddResourcePath("welcome.js", IDR_WELCOME_JS);
-    html_source->AddResourcePath("welcome.css", IDR_WELCOME_CSS);
-    html_source->SetDefaultResource(IDR_WELCOME_HTML);
-
-    html_source->AddResourcePath("logo.png", IDR_PRODUCT_LOGO_128);
-    html_source->AddResourcePath("logo2x.png", IDR_PRODUCT_LOGO_256);
-  }
+  html_source->AddString(
+      "newUserModules",
+      nux::GetNuxOnboardingModules(profile).FindKey("new-user")->GetString());
+  html_source->AddString("returningUserModules",
+                         nux::GetNuxOnboardingModules(profile)
+                             .FindKey("returning-user")
+                             ->GetString());
+  html_source->AddBoolean(
+      "signinAllowed", profile->GetPrefs()->GetBoolean(prefs::kSigninAllowed));
+  html_source->SetRequestFilter(
+      base::BindRepeating(&ShouldHandleRequestCallback,
+                          weak_ptr_factory_.GetWeakPtr()),
+      base::BindRepeating(&HandleRequestCallback,
+                          weak_ptr_factory_.GetWeakPtr()));
+  html_source->SetJsonPath("strings.js");
 
   content::WebUIDataSource::Add(profile, html_source);
 }
diff --git a/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java b/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java
index d3925ba..fb760d8 100644
--- a/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java
+++ b/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java
@@ -22,6 +22,7 @@
     public static final String ANSWER_SUGGESTIONS_UMA_CLIENT_NAME = "AnswerSuggestions";
     public static final String ASSISTANT_DETAILS_UMA_CLIENT_NAME = "AssistantDetails";
     public static final String ASSISTANT_INFO_BOX_UMA_CLIENT_NAME = "AssistantInfoBox";
+    public static final String ENTITY_SUGGESTIONS_UMA_CLIENT_NAME = "EntitySuggestions";
     public static final String FEED_UMA_CLIENT_NAME = "Feed";
     public static final String NTP_ANIMATED_LOGO_UMA_CLIENT_NAME = "NewTabPageAnimatedLogo";
 
diff --git a/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/NetworkImageFetcher.java b/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/NetworkImageFetcher.java
index aae248a..5f8e827 100644
--- a/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/NetworkImageFetcher.java
+++ b/chrome/lib/image_fetcher/public/android/java/src/org/chromium/chrome/browser/image_fetcher/NetworkImageFetcher.java
@@ -39,7 +39,12 @@
     @Override
     public void fetchImage(
             String url, String clientName, int width, int height, Callback<Bitmap> callback) {
-        mImageFetcherBridge.fetchImage(getConfig(), url, clientName, width, height, callback);
+        long startTimeMillis = System.currentTimeMillis();
+        mImageFetcherBridge.fetchImage(
+                getConfig(), url, clientName, width, height, (Bitmap bitmapFromNative) -> {
+                    callback.onResult(bitmapFromNative);
+                    mImageFetcherBridge.reportTotalFetchTimeFromNative(clientName, startTimeMillis);
+                });
     }
 
     @Override
diff --git a/chrome/renderer/extensions/extension_hooks_delegate.cc b/chrome/renderer/extensions/extension_hooks_delegate.cc
index 6a536f2..b4e8bef 100644
--- a/chrome/renderer/extensions/extension_hooks_delegate.cc
+++ b/chrome/renderer/extensions/extension_hooks_delegate.cc
@@ -17,6 +17,7 @@
 #include "extensions/renderer/message_target.h"
 #include "extensions/renderer/messaging_util.h"
 #include "extensions/renderer/native_renderer_messaging_service.h"
+#include "extensions/renderer/runtime_hooks_delegate.h"
 #include "extensions/renderer/script_context.h"
 #include "gin/converter.h"
 #include "gin/dictionary.h"
@@ -238,17 +239,10 @@
 RequestResult ExtensionHooksDelegate::HandleGetURL(
     ScriptContext* script_context,
     const std::vector<v8::Local<v8::Value>>& arguments) {
-  DCHECK_EQ(1u, arguments.size());
-  DCHECK(arguments[0]->IsString());
-  DCHECK(script_context->extension());
-
-  std::string path = gin::V8ToString(script_context->isolate(), arguments[0]);
-
-  RequestResult result(RequestResult::HANDLED);
-  result.return_value =
-      gin::StringToV8(script_context->isolate(),
-                      script_context->extension()->GetResourceURL(path).spec());
-  return result;
+  // We call a static implementation here rather using an alias due to not being
+  // able to remove the extension.json GetURL entry, as it is used for generated
+  // documentation and api feature lists some other methods refer to.
+  return RuntimeHooksDelegate::GetURL(script_context, arguments);
 }
 
 APIBindingHooks::RequestResult ExtensionHooksDelegate::HandleGetViews(
diff --git a/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc b/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
index be0703f..1bc125b6 100644
--- a/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
+++ b/chrome/renderer/extensions/extension_hooks_delegate_unittest.cc
@@ -237,4 +237,30 @@
                       context, 0, nullptr);
 }
 
+// Ensure that HandleGetURL allows extension URLs and doesn't allow arbitrary
+// non-extension URLs. Very similar to RuntimeHooksDeligateTest that tests a
+// similar function.
+TEST_F(ExtensionHooksDelegateTest, GetURL) {
+  v8::HandleScope handle_scope(isolate());
+  v8::Local<v8::Context> context = MainContext();
+
+  auto get_url = [this, context](const char* args, const GURL& expected_url) {
+    SCOPED_TRACE(base::StringPrintf("Args: `%s`", args));
+    constexpr char kGetUrlTemplate[] =
+        "(function() { return chrome.extension.getURL(%s); })";
+    v8::Local<v8::Function> get_url =
+        FunctionFromString(context, base::StringPrintf(kGetUrlTemplate, args));
+    v8::Local<v8::Value> url = RunFunction(get_url, context, 0, nullptr);
+    ASSERT_FALSE(url.IsEmpty());
+    ASSERT_TRUE(url->IsString());
+    EXPECT_EQ(expected_url.spec(), gin::V8ToString(isolate(), url));
+  };
+
+  get_url("''", extension()->url());
+  get_url("'foo'", extension()->GetResourceURL("foo"));
+  get_url("'/foo'", extension()->GetResourceURL("foo"));
+  get_url("'https://www.google.com'",
+          GURL(extension()->url().spec() + "https://www.google.com"));
+}
+
 }  // namespace extensions
diff --git a/chrome/renderer/searchbox/searchbox_extension.cc b/chrome/renderer/searchbox/searchbox_extension.cc
index 55fdaaae2..5f608b4 100644
--- a/chrome/renderer/searchbox/searchbox_extension.cc
+++ b/chrome/renderer/searchbox/searchbox_extension.cc
@@ -114,7 +114,7 @@
                               !theme_info.custom_background_url.is_empty();
   if (theme_info.logo_alternate && !has_background_image) {
     logo_color = GetContrastingColorForBackground(theme_info.background_color,
-                                                  /*luminosity_change=*/0.4f);
+                                                  /*luminosity_change=*/0.3f);
   }
 
   return logo_color;
diff --git a/chrome/test/android/BUILD.gn b/chrome/test/android/BUILD.gn
index 22eb2bf7..2121673 100644
--- a/chrome/test/android/BUILD.gn
+++ b/chrome/test/android/BUILD.gn
@@ -13,6 +13,8 @@
     "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/SyncController.java",
     "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/first_run/TOSController.java",
     "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/notifications/DownloadNotificationController.java",
+    "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleActionsMenu.java",
+    "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleCardController.java",
     "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ChromeMenu.java",
     "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/IncognitoNewTabPageController.java",
     "javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java",
@@ -38,6 +40,7 @@
     "//base:base_java_test_support",
     "//chrome/android:chrome_java",
     "//third_party/android_support_test_runner:runner_java",
+    "//third_party/guava:guava_android_java",
     "//third_party/junit",
     "//third_party/ub-uiautomator:ub_uiautomator_java",
   ]
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleActionsMenu.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleActionsMenu.java
new file mode 100644
index 0000000..4147434
--- /dev/null
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleActionsMenu.java
@@ -0,0 +1,77 @@
+// 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.test.pagecontroller.controllers.ntp;
+
+import com.google.android.libraries.feed.sharedstream.contextmenumanager.R;
+
+import org.chromium.chrome.test.pagecontroller.controllers.PageController;
+import org.chromium.chrome.test.pagecontroller.controllers.urlpage.UrlPage;
+import org.chromium.chrome.test.pagecontroller.utils.IUi2Locator;
+import org.chromium.chrome.test.pagecontroller.utils.Ui2Locators;
+
+/**
+ * Article Actions Menu (long-press on NTP article) Page Controller.
+ */
+public class ArticleActionsMenu extends PageController {
+    private static final IUi2Locator LOCATOR_MENU = Ui2Locators.withClassRegex(".*ListView");
+
+    private static final IUi2Locator LOCATOR_OPEN_NEW_TAB = Ui2Locators.withResEntriesByIndex(
+            0, org.chromium.chrome.R.id.title, R.id.feed_simple_list_item);
+    private static final IUi2Locator LOCATOR_OPEN_INCOGNITO = Ui2Locators.withResEntriesByIndex(
+            1, org.chromium.chrome.R.id.title, R.id.feed_simple_list_item);
+    private static final IUi2Locator LOCATOR_DOWNLOAD_LINK = Ui2Locators.withResEntriesByIndex(
+            2, org.chromium.chrome.R.id.title, R.id.feed_simple_list_item);
+    private static final IUi2Locator LOCATOR_REMOVE = Ui2Locators.withResEntriesByIndex(
+            3, org.chromium.chrome.R.id.title, R.id.feed_simple_list_item);
+    private static final IUi2Locator LOCATOR_LEARN_MORE = Ui2Locators.withResEntriesByIndex(
+            4, org.chromium.chrome.R.id.title, R.id.feed_simple_list_item);
+
+    static private ArticleActionsMenu sInstance = new ArticleActionsMenu();
+    private ArticleActionsMenu() {}
+    static public ArticleActionsMenu getInstance() {
+        return sInstance;
+    }
+
+    @Override
+    public boolean isCurrentPageThis() {
+        return mLocatorHelper.isOnScreen(LOCATOR_LEARN_MORE);
+    }
+
+    public UrlPage clickOpenNewTab() {
+        mUtils.click(LOCATOR_OPEN_NEW_TAB);
+        UrlPage inst = UrlPage.getInstance();
+        inst.verify();
+        return inst;
+    }
+
+    public UrlPage clickOpenIncognitoTab() {
+        mUtils.click(LOCATOR_OPEN_INCOGNITO);
+        UrlPage inst = UrlPage.getInstance();
+        inst.verify();
+        return inst;
+    }
+
+    public void clickDownloadLink() {
+        mUtils.click(LOCATOR_DOWNLOAD_LINK);
+    }
+
+    public NewTabPageController clickRemoveArticle() {
+        mUtils.click(LOCATOR_REMOVE);
+        NewTabPageController inst = NewTabPageController.getInstance();
+        inst.verify();
+        return inst;
+    }
+
+    public void clickLearnMore() {
+        mUtils.click(LOCATOR_LEARN_MORE);
+    }
+
+    public NewTabPageController dismiss() {
+        mUtils.clickOutsideOf(LOCATOR_MENU);
+        NewTabPageController inst = NewTabPageController.getInstance();
+        inst.verify();
+        return inst;
+    }
+}
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleCardController.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleCardController.java
new file mode 100644
index 0000000..56534220
--- /dev/null
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/ArticleCardController.java
@@ -0,0 +1,202 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.test.pagecontroller.controllers.ntp;
+
+import static org.chromium.chrome.test.pagecontroller.utils.Ui2Locators.withText;
+import static org.chromium.chrome.test.pagecontroller.utils.Ui2Locators.withTextRegex;
+
+import android.support.test.uiautomator.UiObject2;
+
+import com.google.common.base.Joiner;
+
+import org.chromium.chrome.R;
+import org.chromium.chrome.test.pagecontroller.controllers.ElementController;
+import org.chromium.chrome.test.pagecontroller.utils.IUi2Locator;
+import org.chromium.chrome.test.pagecontroller.utils.Ui2Locators;
+import org.chromium.chrome.test.pagecontroller.utils.UiLocationException;
+import org.chromium.chrome.test.pagecontroller.utils.UiLocatorHelper;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * NTP Article Element Controller.
+ */
+public class ArticleCardController extends ElementController {
+    // Implementation type of the article card.
+    public enum ImplementationType { ZINE, FEED };
+
+    /**
+     * Represents a single article, can be used by the NewTabPageController
+     * to perform actions.
+     */
+    static public class Info {
+        private final String mHeadline, mPublisher, mAge;
+        private final ImplementationType mImplType;
+
+        public Info(String headline, String publisher, String age,
+                ImplementationType implementationType) {
+            mHeadline = headline;
+            mPublisher = publisher;
+            mAge = age;
+            mImplType = implementationType;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof Info)) return false;
+            Info that = (Info) o;
+            return Objects.equals(mHeadline, that.mHeadline)
+                    && Objects.equals(mPublisher, that.mPublisher)
+                    && Objects.equals(mAge, that.mAge) && mImplType == that.mImplType;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mHeadline, mPublisher, mAge, mImplType);
+        }
+
+        @Override
+        public String toString() {
+            return "ArticleCardController{"
+                    + "mHeadline='" + mHeadline + '\'' + ", mPublisher='" + mPublisher + '\''
+                    + ", mAge='" + mAge + '\'' + ", mImplementation=" + mImplType + '}';
+        }
+
+        public String getHeadline() {
+            return mHeadline;
+        }
+
+        public String getPublisher() {
+            return mPublisher;
+        }
+
+        public String getAge() {
+            return mAge;
+        }
+
+        public ImplementationType getImplementationType() {
+            return mImplType;
+        }
+    }
+
+    private static abstract class ArticleImpl {
+        abstract public List<Info> parseScreenForArticles(UiLocatorHelper locatorHelper);
+        abstract public IUi2Locator getLocator(Info cardInfo);
+        protected List<Info> parseScreenForArticles(final UiLocatorHelper locatorHelper,
+                final ImplementationType implementationType, final IUi2Locator cardsLocator,
+                final IUi2Locator headlineLocator, final IUi2Locator publisherLocator,
+                final IUi2Locator ageLocator) {
+            return locatorHelper.getCustomElements(
+                    cardsLocator, new UiLocatorHelper.CustomElementMaker<Info>() {
+                        @Override
+                        public Info makeElement(UiObject2 articleCard, boolean isLastAttempt) {
+                            String headline =
+                                    locatorHelper.getOneTextImmediate(headlineLocator, articleCard);
+                            String publisher = locatorHelper.getOneTextImmediate(
+                                    publisherLocator, articleCard);
+                            String age = locatorHelper.getOneTextImmediate(ageLocator, articleCard);
+                            if (headline != null && publisher != null && age != null) {
+                                return new Info(headline, publisher, age, implementationType);
+                            } else if (isLastAttempt) {
+                                return null;
+                            } else {
+                                if (headline == null)
+                                    throw UiLocationException.newInstance(
+                                            "Headline not found", headlineLocator, articleCard);
+                                else if (publisher == null)
+                                    throw UiLocationException.newInstance(
+                                            "Publisher not found", publisherLocator, articleCard);
+                                else
+                                    throw UiLocationException.newInstance(
+                                            "Age not found", ageLocator, articleCard);
+                            }
+                        }
+                    });
+        }
+    }
+
+    private static class FeedArticleImpl extends ArticleImpl {
+        private static final IUi2Locator LOCATOR_NON_EMPTY_STRING = withTextRegex(".+");
+        private static final IUi2Locator LOCATOR_CARDS = Ui2Locators.withPath(
+                Ui2Locators.withResEntries(R.id.content),
+                Ui2Locators.withResEntries(com.google.android.libraries.feed.basicstream.R.id
+                                                   .feed_stream_recycler_view),
+                Ui2Locators.withResEntries(com.google.android.libraries.feed.basicstream.internal
+                                                   .viewholders.R.id.feed_content_card));
+        private static final IUi2Locator LOCATOR_HEADLINE =
+                Ui2Locators.withPath(Ui2Locators.withChildIndex(0, 6), LOCATOR_NON_EMPTY_STRING);
+        private static final IUi2Locator LOCATOR_PUBLISHER =
+                Ui2Locators.withPath(Ui2Locators.withChildIndex(0, 5),
+                        Ui2Locators.withChildIndex(1, 2), LOCATOR_NON_EMPTY_STRING);
+        private static final IUi2Locator LOCATOR_AGE = Ui2Locators.withPath(
+                Ui2Locators.withChildIndex(0, 5), Ui2Locators.withChildIndex(1),
+                Ui2Locators.withChildIndex(2), LOCATOR_NON_EMPTY_STRING);
+        @Override
+        public List<Info> parseScreenForArticles(UiLocatorHelper locatorHelper) {
+            return parseScreenForArticles(locatorHelper, ImplementationType.FEED, LOCATOR_CARDS,
+                    LOCATOR_HEADLINE, LOCATOR_PUBLISHER, LOCATOR_AGE);
+        }
+        @Override
+        public IUi2Locator getLocator(Info cardInfo) {
+            return Ui2Locators.withPath(LOCATOR_HEADLINE, withText(cardInfo.getHeadline()));
+        }
+    }
+
+    private static class ZineArticleImpl extends ArticleImpl {
+        private static final IUi2Locator LOCATOR_CARDS =
+                Ui2Locators.withPath(Ui2Locators.withResEntries(R.id.content),
+                        Ui2Locators.withResEntries(R.id.card_contents));
+        private static final IUi2Locator LOCATOR_HEADLINE =
+                Ui2Locators.withResEntries(R.id.article_headline);
+        private static final IUi2Locator LOCATOR_PUBLISHER =
+                Ui2Locators.withResEntries(R.id.article_publisher);
+        private static final IUi2Locator LOCATOR_AGE = Ui2Locators.withResEntries(R.id.article_age);
+        @Override
+        public List<Info> parseScreenForArticles(UiLocatorHelper locatorHelper) {
+            return parseScreenForArticles(locatorHelper, ImplementationType.ZINE, LOCATOR_CARDS,
+                    LOCATOR_HEADLINE, LOCATOR_PUBLISHER, LOCATOR_AGE);
+        }
+        @Override
+        public IUi2Locator getLocator(Info cardInfo) {
+            return Ui2Locators.withPath(LOCATOR_HEADLINE, withText(cardInfo.getHeadline()));
+        }
+    }
+
+    private static ArticleCardController sInstance = new ArticleCardController();
+    private ArticleImpl mFeedImpl, mZineImpl;
+    private ArticleCardController() {
+        mFeedImpl = new FeedArticleImpl();
+        mZineImpl = new ZineArticleImpl();
+    }
+
+    public static ArticleCardController getInstance() {
+        return sInstance;
+    }
+
+    public IUi2Locator getLocator(Info cardInfo) {
+        return getCurrentImplementation(cardInfo.getImplementationType()).getLocator(cardInfo);
+    }
+
+    public List<Info> parseScreenForArticles(final ImplementationType cardImplementationType) {
+        return getCurrentImplementation(cardImplementationType)
+                .parseScreenForArticles(mLocatorHelper);
+    }
+
+    public String articlesToString(List<Info> cards) {
+        return Joiner.on("\n").join(cards);
+    }
+
+    private ArticleImpl getCurrentImplementation(ImplementationType implementationType) {
+        switch (implementationType) {
+            case FEED:
+                return mFeedImpl;
+            case ZINE:
+                return mZineImpl;
+        }
+        throw new IllegalArgumentException("Unknown implementation type" + implementationType);
+    }
+}
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java
index 7375b3d..27120579 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/pagecontroller/controllers/ntp/NewTabPageController.java
@@ -57,16 +57,26 @@
                     com.google.android.libraries.feed.basicstream.R.id.feed_stream_recycler_view,
                     R.id.card_contents);
 
+    private ArticleCardController mAriticleCardController;
     private SuggestionTileController mSuggestionsTileController;
 
     private static final NewTabPageController sInstance = new NewTabPageController();
     private NewTabPageController() {
+        mAriticleCardController = ArticleCardController.getInstance();
         mSuggestionsTileController = SuggestionTileController.getInstance();
     }
     public static NewTabPageController getInstance() {
         return sInstance;
     }
 
+    public ArticleCardController.ImplementationType getArticleImplementationType() {
+        if (mLocatorHelper.isOnScreen(LOCATOR_FEED_STREAM_RECYCLER_VIEW)) {
+            return ArticleCardController.ImplementationType.FEED;
+        } else {
+            return ArticleCardController.ImplementationType.ZINE;
+        }
+    }
+
     /**
      * Hide articles if shown, and vice-versa.  This will cause page to scroll to where the
      * show/hide articles button is visible.
@@ -155,6 +165,10 @@
         }
     }
 
+    public List<ArticleCardController.Info> getAllLoadedArticles() {
+        return getAllLoadedArticles(getArticleImplementationType());
+    }
+
     /**
      * Get all suggestion tiles.  This will cause the page to scroll to the top.
      * @return List of suggestion infos, possibly empty.
@@ -167,11 +181,52 @@
     }
 
     /**
+     * Get all loaded articles.  This will cause the page to scroll to top then down to the bottom.
+     * @param implementationType The article implementation type, FEED or ZINE.
+     * @return                      List of article card infos, this is a list to preserve the
+     *                              order of the articles as they appeared on the screen.
+     */
+    public List<ArticleCardController.Info> getAllLoadedArticles(
+            ArticleCardController.ImplementationType implementationType) {
+        scrollToTop();
+        List<ArticleCardController.Info> allArticles =
+                mAriticleCardController.parseScreenForArticles(implementationType);
+        do {
+            scrollTowardsBottom(SCROLL_SWIPE_FRACTION);
+            List<ArticleCardController.Info> currentArticles =
+                    mAriticleCardController.parseScreenForArticles(implementationType);
+            for (ArticleCardController.Info article : currentArticles) {
+                if (!allArticles.contains(article)) {
+                    allArticles.add(article);
+                }
+            }
+        } while (!hasScrolledToBottom());
+
+        return allArticles;
+    }
+
+    /**
+     * Perform the default card action by tapping on it.  This will cause the page to scroll to top
+     * then down to where the article is located (or hit bottom if it isn't found).
+     * @param article The article info.
+     * @return        UrlPage Controller where the article will be loaded.
+     */
+    public UrlPage clickArticle(ArticleCardController.Info article) {
+        scrollToTop();
+        IUi2Locator locator = ArticleCardController.getInstance().getLocator(article);
+        mUtils.swipeUpVerticallyUntilFound(locator, LOCATOR_BOTTOM_OF_PAGE);
+        mUtils.click(locator);
+        UrlPage inst = UrlPage.getInstance();
+        inst.verify();
+        return inst;
+    }
+
+    /**
      * The default action is the one that gets performed when the user taps on the tile icon
      * (opens site in a new page).  If user long taps, then a menu is shown providing more choices
      * (not yet implemented).  This will cause the page to scroll to the top.
      */
-    public UrlPage performDefaultSuggestionTileAction(SuggestionTileController.Info tile) {
+    public UrlPage clickSuggestionTile(SuggestionTileController.Info tile) {
         scrollToTop();
         IUi2Locator locator = mSuggestionsTileController.getLocator(tile);
         mUtils.swipeUpVerticallyUntilFound(locator, LOCATOR_BOTTOM_OF_PAGE);
@@ -202,6 +257,16 @@
         return inst;
     }
 
+    public ArticleActionsMenu openArticleContextMenu(ArticleCardController.Info card) {
+        scrollToTop();
+        IUi2Locator locator = mAriticleCardController.getLocator(card);
+        mUtils.swipeUpVerticallyUntilFound(locator, LOCATOR_BOTTOM_OF_PAGE);
+        mUtils.longClick(locator);
+        ArticleActionsMenu inst = ArticleActionsMenu.getInstance();
+        inst.verify();
+        return inst;
+    }
+
     public UrlPage omniboxSearch(String url) {
         mUtils.click(LOCATOR_SEARCH_BOX_TEXT);
         mUtils.setTextAndEnter(LOCATOR_URL_BAR, url);
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 3dcca5b..dcc4b2a 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -1429,10 +1429,10 @@
         }
       }
 
-      int init_x, init_y;
-      if (!input_state->GetInteger("x", &init_x) ||
-          !input_state->GetInteger("y", &init_y))
-        return Status(kUnknownError, "invalid input state");
+      int init_x = 0;
+      int init_y = 0;
+      input_state->GetInteger("x", &init_x);
+      input_state->GetInteger("y", &init_y);
 
       if (pointer_type == "mouse" || pointer_type == "pen") {
         longest_mouse_list_size =
diff --git a/chromeos/services/assistant/public/features.cc b/chromeos/services/assistant/public/features.cc
index c99d4bd..0ed4fdb71 100644
--- a/chromeos/services/assistant/public/features.cc
+++ b/chromeos/services/assistant/public/features.cc
@@ -53,9 +53,6 @@
 const base::Feature kEnablePowerManager{"ChromeOSAssistantEnablePowerManager",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
-const base::Feature kAssistantKeyRemapping{"AssistantKeyRemapping",
-                                           base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables sending a screen context request ("What's on my screen?" and
 // metalayer selection) as a text query. This is as opposed to sending
 // the request as a contextual cards request.
@@ -95,10 +92,6 @@
   return base::FeatureList::IsEnabled(kInAssistantNotifications);
 }
 
-bool IsKeyRemappingEnabled() {
-  return base::FeatureList::IsEnabled(kAssistantKeyRemapping);
-}
-
 bool IsMediaSessionIntegrationEnabled() {
   return base::FeatureList::IsEnabled(kEnableMediaSessionIntegration);
 }
diff --git a/chromeos/services/assistant/public/features.h b/chromeos/services/assistant/public/features.h
index 5d9960b1..fb5376f 100644
--- a/chromeos/services/assistant/public/features.h
+++ b/chromeos/services/assistant/public/features.h
@@ -76,10 +76,6 @@
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
 extern const base::Feature kTimerTicks;
 
-// Enables Assistant key remapping on keyboards.
-COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC)
-extern const base::Feature kAssistantKeyRemapping;
-
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsAlarmTimerManagerEnabled();
 
 COMPONENT_EXPORT(ASSISTANT_SERVICE_PUBLIC) bool IsAppSupportEnabled();
diff --git a/chromeos/services/device_sync/BUILD.gn b/chromeos/services/device_sync/BUILD.gn
index cf81320..fdc393d2 100644
--- a/chromeos/services/device_sync/BUILD.gn
+++ b/chromeos/services/device_sync/BUILD.gn
@@ -44,6 +44,10 @@
     "cryptauth_enrollment_manager_impl.h",
     "cryptauth_enrollment_result.cc",
     "cryptauth_enrollment_result.h",
+    "cryptauth_feature_status_getter.cc",
+    "cryptauth_feature_status_getter.h",
+    "cryptauth_feature_status_getter_impl.cc",
+    "cryptauth_feature_status_getter_impl.h",
     "cryptauth_feature_type.cc",
     "cryptauth_feature_type.h",
     "cryptauth_gcm_manager.cc",
@@ -165,10 +169,14 @@
     "fake_cryptauth_enrollment_manager.h",
     "fake_cryptauth_gcm_manager.cc",
     "fake_cryptauth_gcm_manager.h",
+    "fake_cryptauth_group_private_key_sharer.cc",
+    "fake_cryptauth_group_private_key_sharer.h",
     "fake_cryptauth_key_creator.cc",
     "fake_cryptauth_key_creator.h",
     "fake_cryptauth_key_proof_computer.cc",
     "fake_cryptauth_key_proof_computer.h",
+    "fake_cryptauth_metadata_syncer.cc",
+    "fake_cryptauth_metadata_syncer.h",
     "fake_cryptauth_scheduler.cc",
     "fake_cryptauth_scheduler.h",
     "fake_cryptauth_v2_enroller.cc",
@@ -216,6 +224,7 @@
     "cryptauth_ecies_encryptor_impl_unittest.cc",
     "cryptauth_enroller_impl_unittest.cc",
     "cryptauth_enrollment_manager_impl_unittest.cc",
+    "cryptauth_feature_status_getter_impl_unittest.cc",
     "cryptauth_gcm_manager_impl_unittest.cc",
     "cryptauth_group_private_key_sharer_impl_unittest.cc",
     "cryptauth_key_bundle_unittest.cc",
diff --git a/chromeos/services/device_sync/cryptauth_feature_status_getter.cc b/chromeos/services/device_sync/cryptauth_feature_status_getter.cc
new file mode 100644
index 0000000..93b8c53
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_feature_status_getter.cc
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/device_sync/cryptauth_feature_status_getter.h"
+
+#include <utility>
+
+namespace chromeos {
+
+namespace device_sync {
+
+CryptAuthFeatureStatusGetter::CryptAuthFeatureStatusGetter() = default;
+
+CryptAuthFeatureStatusGetter::~CryptAuthFeatureStatusGetter() = default;
+
+void CryptAuthFeatureStatusGetter::GetFeatureStatuses(
+    const cryptauthv2::RequestContext& request_context,
+    const base::flat_set<std::string>& device_ids,
+    GetFeatureStatusesAttemptFinishedCallback callback) {
+  // Enforce that GetFeatureStatuses() can only be called once.
+  DCHECK(!was_get_feature_statuses_called_);
+  was_get_feature_statuses_called_ = true;
+
+  callback_ = std::move(callback);
+
+  OnAttemptStarted(request_context, device_ids);
+}
+
+void CryptAuthFeatureStatusGetter::OnAttemptFinished(
+    const IdToFeatureStatusMap& id_to_feature_status_map,
+    const CryptAuthDeviceSyncResult::ResultCode& device_sync_result_code) {
+  DCHECK(callback_);
+  std::move(callback_).Run(id_to_feature_status_map, device_sync_result_code);
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/cryptauth_feature_status_getter.h b/chromeos/services/device_sync/cryptauth_feature_status_getter.h
new file mode 100644
index 0000000..8644a0a3
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_feature_status_getter.h
@@ -0,0 +1,75 @@
+// 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_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_GETTER_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_GETTER_H_
+
+#include <map>
+#include <string>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/cryptauth_device_sync_result.h"
+
+namespace cryptauthv2 {
+class RequestContext;
+}  // namespace cryptauthv2
+
+namespace chromeos {
+
+namespace device_sync {
+
+// Handles the BatchGetFeatureStatuses portion of the CryptAuth v2 DeviceSync
+// protocol. Returns the feature statuses for each input device ID as a map from
+// multidevice::SoftwareFeature to multidevice::SoftwareFeatureState.
+//
+// A CryptAuthFeatureStatusGetter object is designed to be used for only one
+// GetFeatureStatuses() call. For a new attempt, a new object should be created.
+class CryptAuthFeatureStatusGetter {
+ public:
+  using FeatureStatusMap =
+      std::map<multidevice::SoftwareFeature, multidevice::SoftwareFeatureState>;
+  using IdToFeatureStatusMap = base::flat_map<std::string, FeatureStatusMap>;
+  using GetFeatureStatusesAttemptFinishedCallback =
+      base::OnceCallback<void(const IdToFeatureStatusMap&,
+                              const CryptAuthDeviceSyncResult::ResultCode&)>;
+
+  virtual ~CryptAuthFeatureStatusGetter();
+
+  // Starts the BatchGetFeatureStatuses portion of the CryptAuth v2 DeviceSync
+  // flow, retrieving feature status for |device_ids|.
+  void GetFeatureStatuses(const cryptauthv2::RequestContext& request_context,
+                          const base::flat_set<std::string>& device_ids,
+                          GetFeatureStatusesAttemptFinishedCallback callback);
+
+ protected:
+  CryptAuthFeatureStatusGetter();
+
+  // Implementations should retrieve feature statuses for devices with IDs
+  // |device_ids|, using CryptAuth's BatchGetFeatureStatuses API, and call
+  // OnAttemptFinished() with the results.
+  virtual void OnAttemptStarted(
+      const cryptauthv2::RequestContext& request_context,
+      const base::flat_set<std::string>& device_ids) = 0;
+
+  void OnAttemptFinished(
+      const IdToFeatureStatusMap& id_to_feature_status_map,
+      const CryptAuthDeviceSyncResult::ResultCode& device_sync_result_code);
+
+ private:
+  GetFeatureStatusesAttemptFinishedCallback callback_;
+  bool was_get_feature_statuses_called_ = false;
+
+  DISALLOW_COPY_AND_ASSIGN(CryptAuthFeatureStatusGetter);
+};
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  //  CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_GETTER_H_
diff --git a/chromeos/services/device_sync/cryptauth_feature_status_getter_impl.cc b/chromeos/services/device_sync/cryptauth_feature_status_getter_impl.cc
new file mode 100644
index 0000000..2952944
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_feature_status_getter_impl.cc
@@ -0,0 +1,267 @@
+// 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/services/device_sync/cryptauth_feature_status_getter_impl.h"
+
+#include <array>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/no_destructor.h"
+#include "base/stl_util.h"
+#include "chromeos/components/multidevice/logging/logging.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/cryptauth_better_together_feature_types.h"
+#include "chromeos/services/device_sync/cryptauth_client.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+namespace {
+
+// Timeout value for asynchronous operation.
+// TODO(https://crbug.com/933656): Tune this value.
+constexpr base::TimeDelta kWaitingForBatchGetFeatureStatusesResponseTimeout =
+    base::TimeDelta::FromSeconds(10);
+
+constexpr std::array<multidevice::SoftwareFeature, 8> kAllSoftwareFeatures = {
+    multidevice::SoftwareFeature::kBetterTogetherHost,
+    multidevice::SoftwareFeature::kBetterTogetherClient,
+    multidevice::SoftwareFeature::kSmartLockHost,
+    multidevice::SoftwareFeature::kSmartLockClient,
+    multidevice::SoftwareFeature::kInstantTetheringHost,
+    multidevice::SoftwareFeature::kInstantTetheringClient,
+    multidevice::SoftwareFeature::kMessagesForWebHost,
+    multidevice::SoftwareFeature::kMessagesForWebClient};
+
+CryptAuthDeviceSyncResult::ResultCode
+BatchGetFeatureStatusesNetworkRequestErrorToResultCode(
+    NetworkRequestError error) {
+  switch (error) {
+    case NetworkRequestError::kOffline:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallOffline;
+    case NetworkRequestError::kEndpointNotFound:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallEndpointNotFound;
+    case NetworkRequestError::kAuthenticationError:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallAuthenticationError;
+    case NetworkRequestError::kBadRequest:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallBadRequest;
+    case NetworkRequestError::kResponseMalformed:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallResponseMalformed;
+    case NetworkRequestError::kInternalServerError:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallInternalServerError;
+    case NetworkRequestError::kUnknown:
+      return CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallUnknownError;
+  }
+}
+
+CryptAuthFeatureStatusGetter::FeatureStatusMap
+ConvertFeatureStatusesToSoftwareFeatureMap(
+    const ::google::protobuf::RepeatedPtrField<
+        cryptauthv2::DeviceFeatureStatus::FeatureStatus>& feature_statuses,
+    bool* did_non_fatal_error_occur) {
+  base::flat_set<multidevice::SoftwareFeature> marked_supported;
+  base::flat_set<multidevice::SoftwareFeature> marked_enabled;
+  for (const cryptauthv2::DeviceFeatureStatus::FeatureStatus& status :
+       feature_statuses) {
+    // TODO(https://crbug.com/936273): Add metrics to track unknown feature type
+    // occurrences.
+    if (!base::Contains(GetBetterTogetherFeatureTypes(),
+                        status.feature_type())) {
+      PA_LOG(ERROR) << "Unknown feature type: " << status.feature_type();
+      *did_non_fatal_error_occur = true;
+      continue;
+    }
+
+    multidevice::SoftwareFeature feature =
+        BetterTogetherFeatureTypeStringToSoftwareFeature(status.feature_type());
+
+    if (base::Contains(GetSupportedBetterTogetherFeatureTypes(),
+                       status.feature_type()) &&
+        status.enabled()) {
+      marked_supported.insert(feature);
+      continue;
+    }
+
+    if (base::Contains(GetEnabledBetterTogetherFeatureTypes(),
+                       status.feature_type()) &&
+        status.enabled()) {
+      marked_enabled.insert(feature);
+      continue;
+    }
+  }
+
+  CryptAuthFeatureStatusGetter::FeatureStatusMap feature_states;
+  for (const multidevice::SoftwareFeature& feature : kAllSoftwareFeatures) {
+    bool is_marked_supported = base::Contains(marked_supported, feature);
+    bool is_marked_enabled = base::Contains(marked_enabled, feature);
+    if (is_marked_supported) {
+      feature_states[feature] =
+          is_marked_enabled ? multidevice::SoftwareFeatureState::kEnabled
+                            : multidevice::SoftwareFeatureState::kSupported;
+      continue;
+    }
+
+    // TODO(https://crbug.com/936273): Add metrics to track occurrences of
+    // unsupported features marked as enabled.
+    if (!is_marked_supported && is_marked_enabled) {
+      PA_LOG(ERROR) << "SoftwareFeature " << feature << " flagged as enabled "
+                    << "but not supported. Marking unsupported.";
+      *did_non_fatal_error_occur = true;
+    }
+
+    feature_states[feature] = multidevice::SoftwareFeatureState::kNotSupported;
+  }
+
+  return feature_states;
+}
+
+}  // namespace
+
+// static
+CryptAuthFeatureStatusGetterImpl::Factory*
+    CryptAuthFeatureStatusGetterImpl::Factory::test_factory_ = nullptr;
+
+// static
+CryptAuthFeatureStatusGetterImpl::Factory*
+CryptAuthFeatureStatusGetterImpl::Factory::Get() {
+  if (test_factory_)
+    return test_factory_;
+
+  static base::NoDestructor<CryptAuthFeatureStatusGetterImpl::Factory> factory;
+  return factory.get();
+}
+
+// static
+void CryptAuthFeatureStatusGetterImpl::Factory::SetFactoryForTesting(
+    Factory* test_factory) {
+  test_factory_ = test_factory;
+}
+
+CryptAuthFeatureStatusGetterImpl::Factory::~Factory() = default;
+
+std::unique_ptr<CryptAuthFeatureStatusGetter>
+CryptAuthFeatureStatusGetterImpl::Factory::BuildInstance(
+    CryptAuthClientFactory* client_factory,
+    std::unique_ptr<base::OneShotTimer> timer) {
+  return base::WrapUnique(
+      new CryptAuthFeatureStatusGetterImpl(client_factory, std::move(timer)));
+}
+
+CryptAuthFeatureStatusGetterImpl::CryptAuthFeatureStatusGetterImpl(
+    CryptAuthClientFactory* client_factory,
+    std::unique_ptr<base::OneShotTimer> timer)
+    : client_factory_(client_factory), timer_(std::move(timer)) {
+  DCHECK(client_factory);
+}
+
+CryptAuthFeatureStatusGetterImpl::~CryptAuthFeatureStatusGetterImpl() = default;
+
+void CryptAuthFeatureStatusGetterImpl::OnAttemptStarted(
+    const cryptauthv2::RequestContext& request_context,
+    const base::flat_set<std::string>& device_ids) {
+  cryptauthv2::BatchGetFeatureStatusesRequest request;
+  request.mutable_context()->CopyFrom(request_context);
+  *request.mutable_device_ids() = {device_ids.begin(), device_ids.end()};
+  *request.mutable_feature_types() = {GetBetterTogetherFeatureTypes().begin(),
+                                      GetBetterTogetherFeatureTypes().end()};
+
+  // TODO(https://crbug.com/936273): Add metrics to track failure rates due to
+  // async timeouts.
+  timer_->Start(
+      FROM_HERE, kWaitingForBatchGetFeatureStatusesResponseTimeout,
+      base::BindOnce(
+          &CryptAuthFeatureStatusGetterImpl::OnBatchGetFeatureStatusesTimeout,
+          base::Unretained(this)));
+
+  cryptauth_client_ = client_factory_->CreateInstance();
+  cryptauth_client_->BatchGetFeatureStatuses(
+      request,
+      base::Bind(
+          &CryptAuthFeatureStatusGetterImpl::OnBatchGetFeatureStatusesSuccess,
+          base::Unretained(this), device_ids),
+      base::Bind(
+          &CryptAuthFeatureStatusGetterImpl::OnBatchGetFeatureStatusesFailure,
+          base::Unretained(this)));
+}
+
+void CryptAuthFeatureStatusGetterImpl::OnBatchGetFeatureStatusesSuccess(
+    const base::flat_set<std::string>& input_device_ids,
+    const cryptauthv2::BatchGetFeatureStatusesResponse& feature_response) {
+  DCHECK(id_to_feature_status_map_.empty());
+
+  bool did_non_fatal_error_occur = false;
+  for (const cryptauthv2::DeviceFeatureStatus& device_feature_status :
+       feature_response.device_feature_statuses()) {
+    const std::string& id = device_feature_status.device_id();
+
+    // TODO(https://crbug.com/936273): Log metrics for unrequested devices
+    // in response.
+    bool was_id_requested = base::Contains(input_device_ids, id);
+    if (!was_id_requested) {
+      PA_LOG(ERROR) << "Unrequested device (ID: " << id
+                    << ") in BatchGetFeatureStatuses response.";
+      did_non_fatal_error_occur = true;
+      continue;
+    }
+
+    // TODO(https://crbug.com/936273): Log metrics for duplicate device IDs.
+    bool is_duplicate_id = base::Contains(id_to_feature_status_map_, id);
+    if (is_duplicate_id) {
+      PA_LOG(ERROR) << "Duplicate device IDs (" << id
+                    << ") in BatchGetFeatureStatuses response.";
+      did_non_fatal_error_occur = true;
+      continue;
+    }
+
+    id_to_feature_status_map_[device_feature_status.device_id()] =
+        ConvertFeatureStatusesToSoftwareFeatureMap(
+            device_feature_status.feature_statuses(),
+            &did_non_fatal_error_occur);
+  }
+
+  // TODO(https://crbug.com/936273): Log metrics for missing devices.
+  if (input_device_ids.size() != id_to_feature_status_map_.size()) {
+    PA_LOG(ERROR) << "Devices missing in BatchGetFeatureStatuses response.";
+    did_non_fatal_error_occur = true;
+  }
+
+  CryptAuthDeviceSyncResult::ResultCode result_code =
+      did_non_fatal_error_occur
+          ? CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors
+          : CryptAuthDeviceSyncResult::ResultCode::kSuccess;
+  FinishAttempt(result_code);
+}
+
+void CryptAuthFeatureStatusGetterImpl::OnBatchGetFeatureStatusesFailure(
+    NetworkRequestError error) {
+  FinishAttempt(BatchGetFeatureStatusesNetworkRequestErrorToResultCode(error));
+}
+
+void CryptAuthFeatureStatusGetterImpl::OnBatchGetFeatureStatusesTimeout() {
+  FinishAttempt(CryptAuthDeviceSyncResult::ResultCode::
+                    kErrorTimeoutWaitingForBatchGetFeatureStatusesResponse);
+}
+
+void CryptAuthFeatureStatusGetterImpl::FinishAttempt(
+    const CryptAuthDeviceSyncResult::ResultCode& result_code) {
+  cryptauth_client_.reset();
+  timer_->Stop();
+
+  OnAttemptFinished(id_to_feature_status_map_, result_code);
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/cryptauth_feature_status_getter_impl.h b/chromeos/services/device_sync/cryptauth_feature_status_getter_impl.h
new file mode 100644
index 0000000..f0a7ad9
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_feature_status_getter_impl.h
@@ -0,0 +1,81 @@
+// 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_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_GETTER_IMPL_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_GETTER_IMPL_H_
+
+#include <memory>
+#include <string>
+
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+#include "base/timer/timer.h"
+#include "chromeos/services/device_sync/cryptauth_device_sync_result.h"
+#include "chromeos/services/device_sync/cryptauth_feature_status_getter.h"
+#include "chromeos/services/device_sync/network_request_error.h"
+#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+class CryptAuthClient;
+class CryptAuthClientFactory;
+
+// An implementation of CryptAuthFeatureStatusGetter, using instances of
+// CryptAuthClient to make the BatchGetFeatureStatuses API calls to CryptAuth.
+// Timeouts are handled internally, so GetFeatureStatuses() is always guaranteed
+// to return.
+class CryptAuthFeatureStatusGetterImpl : public CryptAuthFeatureStatusGetter {
+ public:
+  class Factory {
+   public:
+    static Factory* Get();
+    static void SetFactoryForTesting(Factory* test_factory);
+    virtual ~Factory();
+    virtual std::unique_ptr<CryptAuthFeatureStatusGetter> BuildInstance(
+        CryptAuthClientFactory* client_factory,
+        std::unique_ptr<base::OneShotTimer> timer =
+            std::make_unique<base::OneShotTimer>());
+
+   private:
+    static Factory* test_factory_;
+  };
+
+  ~CryptAuthFeatureStatusGetterImpl() override;
+
+ private:
+  CryptAuthFeatureStatusGetterImpl(CryptAuthClientFactory* client_factory,
+                                   std::unique_ptr<base::OneShotTimer> timer);
+
+  // CryptAuthFeatureStatusGetter:
+  void OnAttemptStarted(const cryptauthv2::RequestContext& request_context,
+                        const base::flat_set<std::string>& device_ids) override;
+
+  void OnBatchGetFeatureStatusesSuccess(
+      const base::flat_set<std::string>& input_device_ids,
+      const cryptauthv2::BatchGetFeatureStatusesResponse& feature_response);
+  void OnBatchGetFeatureStatusesFailure(NetworkRequestError error);
+  void OnBatchGetFeatureStatusesTimeout();
+
+  void FinishAttempt(const CryptAuthDeviceSyncResult::ResultCode& result_code);
+
+  IdToFeatureStatusMap id_to_feature_status_map_;
+
+  // The CryptAuthClient for the latest CryptAuth request. The client can only
+  // be used for one call; therefore, for each API call, a new client needs to
+  // be generated from |client_factory_|.
+  std::unique_ptr<CryptAuthClient> cryptauth_client_;
+
+  CryptAuthClientFactory* client_factory_ = nullptr;
+  std::unique_ptr<base::OneShotTimer> timer_;
+
+  DISALLOW_COPY_AND_ASSIGN(CryptAuthFeatureStatusGetterImpl);
+};
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_FEATURE_STATUS_GETTER_IMPL_H_
diff --git a/chromeos/services/device_sync/cryptauth_feature_status_getter_impl_unittest.cc b/chromeos/services/device_sync/cryptauth_feature_status_getter_impl_unittest.cc
new file mode 100644
index 0000000..e1062f9
--- /dev/null
+++ b/chromeos/services/device_sync/cryptauth_feature_status_getter_impl_unittest.cc
@@ -0,0 +1,416 @@
+// 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/services/device_sync/cryptauth_feature_status_getter_impl.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/containers/flat_set.h"
+#include "base/macros.h"
+#include "base/no_destructor.h"
+#include "base/optional.h"
+#include "base/timer/mock_timer.h"
+#include "chromeos/components/multidevice/software_feature.h"
+#include "chromeos/components/multidevice/software_feature_state.h"
+#include "chromeos/services/device_sync/cryptauth_better_together_feature_types.h"
+#include "chromeos/services/device_sync/cryptauth_client.h"
+#include "chromeos/services/device_sync/cryptauth_device.h"
+#include "chromeos/services/device_sync/cryptauth_device_sync_result.h"
+#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
+#include "chromeos/services/device_sync/cryptauth_v2_device_sync_test_devices.h"
+#include "chromeos/services/device_sync/mock_cryptauth_client.h"
+#include "chromeos/services/device_sync/network_request_error.h"
+#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
+#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
+#include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+namespace {
+
+const char kAccessTokenUsed[] = "access token used by CryptAuthClient";
+
+const cryptauthv2::ClientMetadata& GetClientMetadata() {
+  static const base::NoDestructor<cryptauthv2::ClientMetadata> client_metadata(
+      cryptauthv2::BuildClientMetadata(0 /* retry_count */,
+                                       cryptauthv2::ClientMetadata::PERIODIC));
+  return *client_metadata;
+}
+
+const cryptauthv2::RequestContext& GetRequestContext() {
+  static const base::NoDestructor<cryptauthv2::RequestContext> request_context(
+      [] {
+        return cryptauthv2::BuildRequestContext(
+            CryptAuthKeyBundle::KeyBundleNameEnumToString(
+                CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether),
+            GetClientMetadata(),
+            cryptauthv2::GetClientAppMetadataForTest().instance_id(),
+            cryptauthv2::GetClientAppMetadataForTest().instance_id_token());
+      }());
+
+  return *request_context;
+}
+
+cryptauthv2::DeviceFeatureStatus ConvertDeviceToDeviceFeatureStatus(
+    const CryptAuthDevice& device,
+    const base::flat_set<std::string>& feature_types) {
+  cryptauthv2::DeviceFeatureStatus device_feature_status;
+  device_feature_status.set_device_id(device.instance_id());
+  for (const std::string& feature_type : feature_types) {
+    bool is_supported_feature_type =
+        base::Contains(GetSupportedBetterTogetherFeatureTypes(), feature_type);
+
+    const auto it = device.feature_states.find(
+        BetterTogetherFeatureTypeStringToSoftwareFeature(feature_type));
+    bool is_supported =
+        it != device.feature_states.end() &&
+        it->second != multidevice::SoftwareFeatureState::kNotSupported;
+    bool is_enabled = it != device.feature_states.end() &&
+                      it->second == multidevice::SoftwareFeatureState::kEnabled;
+
+    cryptauthv2::DeviceFeatureStatus::FeatureStatus* feature_status =
+        device_feature_status.add_feature_statuses();
+    feature_status->set_feature_type(feature_type);
+    if (is_supported_feature_type) {
+      feature_status->set_enabled(is_supported);
+    } else {
+      EXPECT_TRUE(
+          base::Contains(GetEnabledBetterTogetherFeatureTypes(), feature_type));
+      feature_status->set_enabled(is_enabled);
+    }
+  }
+
+  return device_feature_status;
+}
+
+}  // namespace
+
+class DeviceSyncCryptAuthFeatureStatusGetterImplTest
+    : public testing::Test,
+      public MockCryptAuthClientFactory::Observer {
+ protected:
+  DeviceSyncCryptAuthFeatureStatusGetterImplTest()
+      : client_factory_(std::make_unique<MockCryptAuthClientFactory>(
+            MockCryptAuthClientFactory::MockType::MAKE_NICE_MOCKS)) {
+    client_factory_->AddObserver(this);
+  }
+
+  ~DeviceSyncCryptAuthFeatureStatusGetterImplTest() override {
+    client_factory_->RemoveObserver(this);
+  }
+
+  // testing::Test:
+  void SetUp() override {
+    auto mock_timer = std::make_unique<base::MockOneShotTimer>();
+    timer_ = mock_timer.get();
+
+    feature_status_getter_ =
+        CryptAuthFeatureStatusGetterImpl::Factory::Get()->BuildInstance(
+            client_factory_.get(), std::move(mock_timer));
+  }
+
+  // MockCryptAuthClientFactory::Observer:
+  void OnCryptAuthClientCreated(MockCryptAuthClient* client) override {
+    ON_CALL(*client,
+            BatchGetFeatureStatuses(testing::_, testing::_, testing::_))
+        .WillByDefault(
+            Invoke(this, &DeviceSyncCryptAuthFeatureStatusGetterImplTest::
+                             OnBatchGetFeatureStatuses));
+
+    ON_CALL(*client, GetAccessTokenUsed())
+        .WillByDefault(testing::Return(kAccessTokenUsed));
+  }
+
+  void GetFeatureStatuses(const base::flat_set<std::string>& device_ids) {
+    feature_status_getter_->GetFeatureStatuses(
+        GetRequestContext(), device_ids,
+        base::BindOnce(&DeviceSyncCryptAuthFeatureStatusGetterImplTest::
+                           OnGetFeatureStatusesComplete,
+                       base::Unretained(this)));
+  }
+
+  void VerifyBatchGetFeatureStatusesRequest(
+      const base::flat_set<std::string>& expected_device_ids) {
+    ASSERT_TRUE(batch_get_feature_statuses_request_);
+    EXPECT_TRUE(batch_get_feature_statuses_success_callback_);
+    EXPECT_TRUE(batch_get_feature_statuses_failure_callback_);
+
+    EXPECT_EQ(
+        GetRequestContext().SerializeAsString(),
+        batch_get_feature_statuses_request_->context().SerializeAsString());
+    EXPECT_EQ(expected_device_ids,
+              base::flat_set<std::string>(
+                  batch_get_feature_statuses_request_->device_ids().begin(),
+                  batch_get_feature_statuses_request_->device_ids().end()));
+    EXPECT_EQ(GetBetterTogetherFeatureTypes(),
+              base::flat_set<std::string>(
+                  batch_get_feature_statuses_request_->feature_types().begin(),
+                  batch_get_feature_statuses_request_->feature_types().end()));
+  }
+
+  void SendCorrectBatchGetFeatureStatusesResponse(
+      const base::flat_set<std::string>& device_ids,
+      const base::flat_set<std::string>& feature_types) {
+    cryptauthv2::BatchGetFeatureStatusesResponse response;
+    for (const std::string& device_id : device_ids) {
+      base::Optional<CryptAuthDevice> device = GetTestDeviceWithId(device_id);
+      if (!device)
+        continue;
+
+      response.add_device_feature_statuses()->CopyFrom(
+          ConvertDeviceToDeviceFeatureStatus(*device, feature_types));
+    }
+    ASSERT_TRUE(batch_get_feature_statuses_success_callback_);
+    std::move(batch_get_feature_statuses_success_callback_).Run(response);
+  }
+
+  void SendCustomBatchGetFeatureStatusesResponse(
+      const cryptauthv2::BatchGetFeatureStatusesResponse& response) {
+    ASSERT_TRUE(batch_get_feature_statuses_success_callback_);
+    std::move(batch_get_feature_statuses_success_callback_).Run(response);
+  }
+
+  void FailBatchGetFeatureStatusesRequest(
+      const NetworkRequestError& network_request_error) {
+    ASSERT_TRUE(batch_get_feature_statuses_failure_callback_);
+    std::move(batch_get_feature_statuses_failure_callback_)
+        .Run(network_request_error);
+  }
+
+  void VerifyGetFeatureStatuesResult(
+      const base::flat_set<std::string>& expected_device_ids,
+      const CryptAuthDeviceSyncResult::ResultCode& expected_result_code) {
+    ASSERT_TRUE(device_sync_result_code_);
+    EXPECT_EQ(expected_device_ids.size(), id_to_feature_status_map_.size());
+    EXPECT_EQ(expected_result_code, device_sync_result_code_);
+
+    for (const std::string& id : expected_device_ids) {
+      const auto it = id_to_feature_status_map_.find(id);
+      ASSERT_TRUE(it != id_to_feature_status_map_.end());
+      EXPECT_EQ(GetTestDeviceWithId(id).feature_states, it->second);
+    }
+  }
+
+  base::MockOneShotTimer* timer() { return timer_; }
+
+ private:
+  void OnBatchGetFeatureStatuses(
+      const cryptauthv2::BatchGetFeatureStatusesRequest& request,
+      const CryptAuthClient::BatchGetFeatureStatusesCallback& callback,
+      const CryptAuthClient::ErrorCallback& error_callback) {
+    EXPECT_FALSE(batch_get_feature_statuses_request_);
+    EXPECT_FALSE(batch_get_feature_statuses_success_callback_);
+    EXPECT_FALSE(batch_get_feature_statuses_failure_callback_);
+
+    batch_get_feature_statuses_request_ = request;
+    batch_get_feature_statuses_success_callback_ = callback;
+    batch_get_feature_statuses_failure_callback_ = error_callback;
+  }
+
+  void OnGetFeatureStatusesComplete(
+      const CryptAuthFeatureStatusGetter::IdToFeatureStatusMap&
+          id_to_feature_status_map,
+      const CryptAuthDeviceSyncResult::ResultCode& device_sync_result_code) {
+    id_to_feature_status_map_ = id_to_feature_status_map;
+    device_sync_result_code_ = device_sync_result_code;
+  }
+
+  base::Optional<cryptauthv2::BatchGetFeatureStatusesRequest>
+      batch_get_feature_statuses_request_;
+  CryptAuthClient::BatchGetFeatureStatusesCallback
+      batch_get_feature_statuses_success_callback_;
+  CryptAuthClient::ErrorCallback batch_get_feature_statuses_failure_callback_;
+
+  CryptAuthFeatureStatusGetter::IdToFeatureStatusMap id_to_feature_status_map_;
+  base::Optional<CryptAuthDeviceSyncResult::ResultCode>
+      device_sync_result_code_;
+
+  std::unique_ptr<MockCryptAuthClientFactory> client_factory_;
+  base::MockOneShotTimer* timer_;
+
+  std::unique_ptr<CryptAuthFeatureStatusGetter> feature_status_getter_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthFeatureStatusGetterImplTest);
+};
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest, Success) {
+  GetFeatureStatuses(GetAllTestDeviceIds());
+
+  VerifyBatchGetFeatureStatusesRequest(GetAllTestDeviceIds());
+
+  SendCorrectBatchGetFeatureStatusesResponse(GetAllTestDeviceIds(),
+                                             GetBetterTogetherFeatureTypes());
+
+  VerifyGetFeatureStatuesResult(
+      GetAllTestDeviceIds(), CryptAuthDeviceSyncResult::ResultCode::kSuccess);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       FinishedWithNonFatalErrors_UnknownFeatureType) {
+  base::flat_set<std::string> device_ids = {
+      GetLocalDeviceMetadataPacketForTest().device_id()};
+  GetFeatureStatuses(device_ids);
+
+  VerifyBatchGetFeatureStatusesRequest(device_ids);
+
+  // Include an unknown feature type string in the response. The unknown feature
+  // type should be ignored.
+  cryptauthv2::DeviceFeatureStatus status = ConvertDeviceToDeviceFeatureStatus(
+      GetLocalDeviceForTest(), GetBetterTogetherFeatureTypes());
+  status.add_feature_statuses()->set_feature_type("Unknown_feature_type");
+
+  cryptauthv2::BatchGetFeatureStatusesResponse response;
+  response.add_device_feature_statuses()->CopyFrom(status);
+  SendCustomBatchGetFeatureStatusesResponse(response);
+
+  VerifyGetFeatureStatuesResult(
+      device_ids,
+      CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       FinishedWithNonFatalErrors_UnsupportedFeatureMarkedEnabled) {
+  base::flat_set<std::string> device_ids = {
+      GetLocalDeviceMetadataPacketForTest().device_id()};
+  GetFeatureStatuses(device_ids);
+
+  VerifyBatchGetFeatureStatusesRequest(device_ids);
+
+  cryptauthv2::DeviceFeatureStatus status = ConvertDeviceToDeviceFeatureStatus(
+      GetLocalDeviceForTest(), GetBetterTogetherFeatureTypes());
+
+  // The BetterTogether host feature is not supported for the local device.
+  EXPECT_EQ(multidevice::SoftwareFeatureState::kNotSupported,
+            GetLocalDeviceForTest()
+                .feature_states
+                .find(multidevice::SoftwareFeature::kBetterTogetherHost)
+                ->second);
+
+  // Ensure that BetterTogether host is marked as not supported in the response.
+  auto beto_host_supported_it =
+      std::find_if(status.mutable_feature_statuses()->begin(),
+                   status.mutable_feature_statuses()->end(),
+                   [](const cryptauthv2::DeviceFeatureStatus::FeatureStatus&
+                          feature_status) {
+                     return feature_status.feature_type() ==
+                            kCryptAuthFeatureTypeBetterTogetherHostSupported;
+                   });
+  EXPECT_FALSE(beto_host_supported_it->enabled());
+
+  // Erroneously mark the BetterTogether host feature state as enabled in the
+  // response though it is not supported.
+  auto beto_host_enabled_it =
+      std::find_if(status.mutable_feature_statuses()->begin(),
+                   status.mutable_feature_statuses()->end(),
+                   [](const cryptauthv2::DeviceFeatureStatus::FeatureStatus&
+                          feature_status) {
+                     return feature_status.feature_type() ==
+                            kCryptAuthFeatureTypeBetterTogetherHostEnabled;
+                   });
+  beto_host_enabled_it->set_enabled(true);
+
+  cryptauthv2::BatchGetFeatureStatusesResponse response;
+  response.add_device_feature_statuses()->CopyFrom(status);
+  SendCustomBatchGetFeatureStatusesResponse(response);
+
+  // The final output BetterTogether host state should continue to be
+  // unsupported for the local device.
+  VerifyGetFeatureStatuesResult(
+      device_ids,
+      CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       FinishedWithNonFatalErrors_UnrequestedDevicesInResponse) {
+  base::flat_set<std::string> requested_device_ids = {
+      GetLocalDeviceMetadataPacketForTest().device_id()};
+  GetFeatureStatuses(requested_device_ids);
+
+  VerifyBatchGetFeatureStatusesRequest(requested_device_ids);
+
+  // Include features statuses for unrequested devices. These extra devices
+  // should be ignored.
+  SendCorrectBatchGetFeatureStatusesResponse(GetAllTestDeviceIds(),
+                                             GetBetterTogetherFeatureTypes());
+
+  VerifyGetFeatureStatuesResult(
+      requested_device_ids,
+      CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       FinishedWithNonFatalErrors_DuplicateDeviceIdsInResponse) {
+  base::flat_set<std::string> requested_device_ids = {
+      GetLocalDeviceMetadataPacketForTest().device_id()};
+  GetFeatureStatuses(requested_device_ids);
+
+  VerifyBatchGetFeatureStatusesRequest(requested_device_ids);
+
+  // Send duplicate local device entries in the response. These duplicate
+  // entries should be ignored.
+  cryptauthv2::DeviceFeatureStatus status = ConvertDeviceToDeviceFeatureStatus(
+      GetLocalDeviceForTest(), GetBetterTogetherFeatureTypes());
+  cryptauthv2::BatchGetFeatureStatusesResponse response;
+  response.add_device_feature_statuses()->CopyFrom(status);
+  response.add_device_feature_statuses()->CopyFrom(status);
+  SendCustomBatchGetFeatureStatusesResponse(response);
+
+  VerifyGetFeatureStatuesResult(
+      requested_device_ids,
+      CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       FinishedWithNonFatalErrors_DevicesMissingInResponse) {
+  GetFeatureStatuses(GetAllTestDeviceIds());
+
+  VerifyBatchGetFeatureStatusesRequest(GetAllTestDeviceIds());
+
+  // Send feature statuses for only one of the three requested devices.
+  base::flat_set<std::string> returned_device_ids = {
+      GetLocalDeviceMetadataPacketForTest().device_id()};
+  SendCorrectBatchGetFeatureStatusesResponse(returned_device_ids,
+                                             GetBetterTogetherFeatureTypes());
+
+  VerifyGetFeatureStatuesResult(
+      returned_device_ids,
+      CryptAuthDeviceSyncResult::ResultCode::kFinishedWithNonFatalErrors);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       Failure_Timeout_BatchGetFeatureStatusesResponse) {
+  GetFeatureStatuses(GetAllTestDeviceIds());
+
+  VerifyBatchGetFeatureStatusesRequest(GetAllTestDeviceIds());
+
+  timer()->Fire();
+
+  VerifyGetFeatureStatuesResult(
+      {} /* expected_device_ids */,
+      CryptAuthDeviceSyncResult::ResultCode::
+          kErrorTimeoutWaitingForBatchGetFeatureStatusesResponse);
+}
+
+TEST_F(DeviceSyncCryptAuthFeatureStatusGetterImplTest,
+       Failure_ApiCall_BatchGetFeatureStatuses) {
+  GetFeatureStatuses(GetAllTestDeviceIds());
+
+  VerifyBatchGetFeatureStatusesRequest(GetAllTestDeviceIds());
+
+  FailBatchGetFeatureStatusesRequest(NetworkRequestError::kBadRequest);
+
+  VerifyGetFeatureStatuesResult(
+      {} /* expected_device_ids */,
+      CryptAuthDeviceSyncResult::ResultCode::
+          kErrorBatchGetFeatureStatusesApiCallBadRequest);
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.cc b/chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.cc
new file mode 100644
index 0000000..c62a86d0
--- /dev/null
+++ b/chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.cc
@@ -0,0 +1,57 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.h"
+
+#include "chromeos/services/device_sync/cryptauth_key.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+FakeCryptAuthGroupPrivateKeySharer::FakeCryptAuthGroupPrivateKeySharer() =
+    default;
+
+FakeCryptAuthGroupPrivateKeySharer::~FakeCryptAuthGroupPrivateKeySharer() =
+    default;
+
+void FakeCryptAuthGroupPrivateKeySharer::FinishAttempt(
+    const CryptAuthDeviceSyncResult::ResultCode& device_sync_result_code) {
+  DCHECK(request_context_);
+  DCHECK(group_key_);
+  DCHECK(id_to_encrypting_key_map_);
+
+  OnAttemptFinished(device_sync_result_code);
+}
+
+void FakeCryptAuthGroupPrivateKeySharer::OnAttemptStarted(
+    const cryptauthv2::RequestContext& request_context,
+    const CryptAuthKey& group_key,
+    const IdToEncryptingKeyMap& id_to_encrypting_key_map) {
+  request_context_ = request_context;
+  group_key_ = std::make_unique<CryptAuthKey>(group_key);
+  id_to_encrypting_key_map_ = id_to_encrypting_key_map;
+}
+
+FakeCryptAuthGroupPrivateKeySharerFactory::
+    FakeCryptAuthGroupPrivateKeySharerFactory() = default;
+
+FakeCryptAuthGroupPrivateKeySharerFactory::
+    ~FakeCryptAuthGroupPrivateKeySharerFactory() = default;
+
+std::unique_ptr<CryptAuthGroupPrivateKeySharer>
+FakeCryptAuthGroupPrivateKeySharerFactory::BuildInstance(
+    CryptAuthClientFactory* client_factory,
+    std::unique_ptr<base::OneShotTimer> timer) {
+  last_client_factory_ = client_factory;
+
+  auto instance = std::make_unique<FakeCryptAuthGroupPrivateKeySharer>();
+  instances_.push_back(instance.get());
+
+  return instance;
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.h b/chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.h
new file mode 100644
index 0000000..e5ddd8c
--- /dev/null
+++ b/chromeos/services/device_sync/fake_cryptauth_group_private_key_sharer.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 CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_GROUP_PRIVATE_KEY_SHARER_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_GROUP_PRIVATE_KEY_SHARER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/timer/timer.h"
+#include "chromeos/services/device_sync/cryptauth_device_sync_result.h"
+#include "chromeos/services/device_sync/cryptauth_group_private_key_sharer.h"
+#include "chromeos/services/device_sync/cryptauth_group_private_key_sharer_impl.h"
+#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
+
+namespace chromeos {
+namespace device_sync {
+
+class CryptAuthClientFactory;
+class CryptAuthKey;
+
+class FakeCryptAuthGroupPrivateKeySharer
+    : public CryptAuthGroupPrivateKeySharer {
+ public:
+  FakeCryptAuthGroupPrivateKeySharer();
+  ~FakeCryptAuthGroupPrivateKeySharer() override;
+
+  // The RequestContext passed to ShareGroupPrivateKey(). Returns null if
+  // ShareGroupPrivateKey() has not been called yet.
+  const base::Optional<cryptauthv2::RequestContext>& request_context() const {
+    return request_context_;
+  }
+
+  // The group key passed to ShareGroupPrivateKey(). Returns null if
+  // ShareGroupPrivateKey() has not been called yet.
+  const CryptAuthKey* group_key() const { return group_key_.get(); }
+
+  // The device ID to encrypting key map passed to ShareGroupPrivateKey().
+  // Returns null if ShareGroupPrivateKey() has not been called yet.
+  const base::Optional<IdToEncryptingKeyMap>& id_to_encrypting_key_map() const {
+    return id_to_encrypting_key_map_;
+  }
+
+  void FinishAttempt(
+      const CryptAuthDeviceSyncResult::ResultCode& device_sync_result_code);
+
+ private:
+  // CryptAuthGroupPrivateKeySharer:
+  void OnAttemptStarted(
+      const cryptauthv2::RequestContext& request_context,
+      const CryptAuthKey& group_key,
+      const IdToEncryptingKeyMap& id_to_encrypting_key_map) override;
+
+  base::Optional<cryptauthv2::RequestContext> request_context_;
+  std::unique_ptr<CryptAuthKey> group_key_;
+  base::Optional<IdToEncryptingKeyMap> id_to_encrypting_key_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthGroupPrivateKeySharer);
+};
+
+class FakeCryptAuthGroupPrivateKeySharerFactory
+    : public CryptAuthGroupPrivateKeySharerImpl::Factory {
+ public:
+  FakeCryptAuthGroupPrivateKeySharerFactory();
+  ~FakeCryptAuthGroupPrivateKeySharerFactory() override;
+
+  // Returns a vector of all FakeCryptAuthGroupPrivateKeySharer instances
+  // created by BuildInstance().
+  const std::vector<FakeCryptAuthGroupPrivateKeySharer*>& instances() const {
+    return instances_;
+  }
+
+  // Returns the most recent CryptAuthClientFactory input into BuildInstance().
+  const CryptAuthClientFactory* last_client_factory() const {
+    return last_client_factory_;
+  }
+
+ private:
+  // CryptAuthGroupPrivateKeySharerImpl::Factory:
+  std::unique_ptr<CryptAuthGroupPrivateKeySharer> BuildInstance(
+      CryptAuthClientFactory* client_factory,
+      std::unique_ptr<base::OneShotTimer> timer = nullptr) override;
+
+  std::vector<FakeCryptAuthGroupPrivateKeySharer*> instances_;
+  CryptAuthClientFactory* last_client_factory_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthGroupPrivateKeySharerFactory);
+};
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  //  CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_GROUP_PRIVATE_KEY_SHARER_H_
diff --git a/chromeos/services/device_sync/fake_cryptauth_metadata_syncer.cc b/chromeos/services/device_sync/fake_cryptauth_metadata_syncer.cc
new file mode 100644
index 0000000..9d38f99
--- /dev/null
+++ b/chromeos/services/device_sync/fake_cryptauth_metadata_syncer.cc
@@ -0,0 +1,58 @@
+// 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/services/device_sync/fake_cryptauth_metadata_syncer.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+FakeCryptAuthMetadataSyncer::FakeCryptAuthMetadataSyncer() = default;
+
+FakeCryptAuthMetadataSyncer::~FakeCryptAuthMetadataSyncer() = default;
+
+void FakeCryptAuthMetadataSyncer::FinishAttempt(
+    const IdToDeviceMetadataPacketMap& id_to_device_metadata_packet_map,
+    std::unique_ptr<CryptAuthKey> new_group_key,
+    const base::Optional<cryptauthv2::EncryptedGroupPrivateKey>&
+        encrypted_group_private_key,
+    const CryptAuthDeviceSyncResult& device_sync_result) {
+  DCHECK(request_context_);
+  DCHECK(local_device_metadata_);
+  DCHECK(initial_group_key_);
+
+  OnAttemptFinished(id_to_device_metadata_packet_map, std::move(new_group_key),
+                    encrypted_group_private_key, device_sync_result);
+}
+
+void FakeCryptAuthMetadataSyncer::OnAttemptStarted(
+    const cryptauthv2::RequestContext& request_context,
+    const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
+    const CryptAuthKey* initial_group_key) {
+  request_context_ = request_context;
+  local_device_metadata_ = local_device_metadata;
+  initial_group_key_ = initial_group_key;
+}
+
+FakeCryptAuthMetadataSyncerFactory::FakeCryptAuthMetadataSyncerFactory() =
+    default;
+
+FakeCryptAuthMetadataSyncerFactory::~FakeCryptAuthMetadataSyncerFactory() =
+    default;
+
+std::unique_ptr<CryptAuthMetadataSyncer>
+FakeCryptAuthMetadataSyncerFactory::BuildInstance(
+    CryptAuthClientFactory* client_factory,
+    std::unique_ptr<base::OneShotTimer> timer) {
+  last_client_factory_ = client_factory;
+
+  auto instance = std::make_unique<FakeCryptAuthMetadataSyncer>();
+  instances_.push_back(instance.get());
+
+  return instance;
+}
+
+}  // namespace device_sync
+
+}  // namespace chromeos
diff --git a/chromeos/services/device_sync/fake_cryptauth_metadata_syncer.h b/chromeos/services/device_sync/fake_cryptauth_metadata_syncer.h
new file mode 100644
index 0000000..af9f471d
--- /dev/null
+++ b/chromeos/services/device_sync/fake_cryptauth_metadata_syncer.h
@@ -0,0 +1,106 @@
+// 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_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_METADATA_SYNCER_H_
+#define CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_METADATA_SYNCER_H_
+
+#include <memory>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/timer/timer.h"
+#include "chromeos/services/device_sync/cryptauth_metadata_syncer.h"
+#include "chromeos/services/device_sync/cryptauth_metadata_syncer_impl.h"
+#include "chromeos/services/device_sync/proto/cryptauth_better_together_device_metadata.pb.h"
+#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
+
+namespace chromeos {
+
+namespace device_sync {
+
+class CryptAuthClientFactory;
+class CryptAuthDeviceSyncResult;
+class CryptAuthKey;
+
+class FakeCryptAuthMetadataSyncer : public CryptAuthMetadataSyncer {
+ public:
+  FakeCryptAuthMetadataSyncer();
+  ~FakeCryptAuthMetadataSyncer() override;
+
+  // The RequestContext passed to SyncMetadata(). Returns null if
+  // SyncMetadata() has not been called yet.
+  const base::Optional<cryptauthv2::RequestContext>& request_context() const {
+    return request_context_;
+  }
+
+  // The local device's BetterTogetherDeviceMetadata passed to SyncMetadata().
+  // Returns null if SyncMetadata() has not been called yet.
+  const base::Optional<cryptauthv2::BetterTogetherDeviceMetadata>&
+  local_device_metadata() const {
+    return local_device_metadata_;
+  }
+
+  // The initial group key passed to SyncMetadata(). Returns null if
+  // SyncMetadata() has not been called yet.
+  const base::Optional<const CryptAuthKey*>& initial_group_key() const {
+    return initial_group_key_;
+  }
+
+  void FinishAttempt(
+      const IdToDeviceMetadataPacketMap& id_to_device_metadata_packet_map,
+      std::unique_ptr<CryptAuthKey> new_group_key,
+      const base::Optional<cryptauthv2::EncryptedGroupPrivateKey>&
+          encrypted_group_private_key,
+      const CryptAuthDeviceSyncResult& device_sync_result);
+
+ private:
+  // CryptAuthMetadataSyncer:
+  void OnAttemptStarted(
+      const cryptauthv2::RequestContext& request_context,
+      const cryptauthv2::BetterTogetherDeviceMetadata& local_device_metadata,
+      const CryptAuthKey* initial_group_key) override;
+
+  base::Optional<cryptauthv2::RequestContext> request_context_;
+  base::Optional<cryptauthv2::BetterTogetherDeviceMetadata>
+      local_device_metadata_;
+  base::Optional<const CryptAuthKey*> initial_group_key_;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthMetadataSyncer);
+};
+
+class FakeCryptAuthMetadataSyncerFactory
+    : public CryptAuthMetadataSyncerImpl::Factory {
+ public:
+  FakeCryptAuthMetadataSyncerFactory();
+  ~FakeCryptAuthMetadataSyncerFactory() override;
+
+  // Returns a vector of all FakeCryptAuthMetadataSyncer instances created
+  // by BuildInstance().
+  const std::vector<FakeCryptAuthMetadataSyncer*>& instances() const {
+    return instances_;
+  }
+
+  // Returns the most recent CryptAuthClientFactory input into BuildInstance().
+  const CryptAuthClientFactory* last_client_factory() const {
+    return last_client_factory_;
+  }
+
+ private:
+  // CryptAuthMetadataSyncerImpl::Factory:
+  std::unique_ptr<CryptAuthMetadataSyncer> BuildInstance(
+      CryptAuthClientFactory* client_factory,
+      std::unique_ptr<base::OneShotTimer> timer = nullptr) override;
+
+  std::vector<FakeCryptAuthMetadataSyncer*> instances_;
+  CryptAuthClientFactory* last_client_factory_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthMetadataSyncerFactory);
+};
+
+}  // namespace device_sync
+
+}  // namespace chromeos
+
+#endif  // CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_METADATA_SYNCER_H_
diff --git a/components/autofill/ios/browser/autofill_agent.mm b/components/autofill/ios/browser/autofill_agent.mm
index 55d7f476..fadbcb4 100644
--- a/components/autofill/ios/browser/autofill_agent.mm
+++ b/components/autofill/ios/browser/autofill_agent.mm
@@ -45,13 +45,13 @@
 #import "components/prefs/ios/pref_observer_bridge.h"
 #include "components/prefs/pref_change_registrar.h"
 #include "components/prefs/pref_service.h"
+#include "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #include "ios/web/public/deprecated/url_verification_constants.h"
 #include "ios/web/public/js_messaging/web_frame.h"
 #include "ios/web/public/js_messaging/web_frame_util.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
 #import "ios/web/public/navigation/navigation_context.h"
-#include "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_state/web_state.h"
 #import "ios/web/public/web_state/web_state_observer_bridge.h"
 #include "ui/gfx/geometry/rect.h"
diff --git a/components/cronet/stale_host_resolver_unittest.cc b/components/cronet/stale_host_resolver_unittest.cc
index 1232471..bbe32a619 100644
--- a/components/cronet/stale_host_resolver_unittest.cc
+++ b/components/cronet/stale_host_resolver_unittest.cc
@@ -185,8 +185,10 @@
     if (dns_client) {
       inner_resolver->GetManagerForTesting()->SetDnsClientForTesting(
           std::move(dns_client));
+      inner_resolver->GetManagerForTesting()->SetInsecureDnsClientEnabled(true);
     } else {
-      inner_resolver->GetManagerForTesting()->SetDnsClientEnabled(false);
+      inner_resolver->GetManagerForTesting()->SetInsecureDnsClientEnabled(
+          false);
     }
     return inner_resolver;
   }
diff --git a/components/cronet/third_party/curl_headers/BUILD.gn b/components/cronet/third_party/curl_headers/BUILD.gn
new file mode 100644
index 0000000..b97609d
--- /dev/null
+++ b/components/cronet/third_party/curl_headers/BUILD.gn
@@ -0,0 +1,24 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+config("curl_headers_include_config") {
+  include_dirs = [ "//components/cronet/third_party/curl_headers/include" ]
+}
+
+source_set("curl_headers") {
+  configs += [ ":curl_headers_include_config" ]
+  public_configs = [ ":curl_headers_include_config" ]
+
+  public = [
+    "include/curl/curl.h",
+    "include/curl/curlver.h",
+    "include/curl/easy.h",
+    "include/curl/mprintf.h",
+    "include/curl/multi.h",
+    "include/curl/stdcheaders.h",
+    "include/curl/system.h",
+    "include/curl/typecheck-gcc.h",
+    "include/curl/urlapi.h",
+  ]
+}
diff --git a/components/cronet/url_request_context_config.cc b/components/cronet/url_request_context_config.cc
index c27fa41..d3e297e 100644
--- a/components/cronet/url_request_context_config.cc
+++ b/components/cronet/url_request_context_config.cc
@@ -679,7 +679,8 @@
     CHECK(net_log) << "All DNS-related experiments require NetLog.";
     std::unique_ptr<net::HostResolver> host_resolver;
     net::HostResolver::ManagerOptions host_resolver_manager_options;
-    host_resolver_manager_options.dns_client_enabled = async_dns_enable;
+    host_resolver_manager_options.insecure_dns_client_enabled =
+        async_dns_enable;
     host_resolver_manager_options.check_ipv6_on_wifi = !disable_ipv6_on_wifi;
     // TODO(crbug.com/934402): Consider using a shared HostResolverManager for
     // Cronet HostResolvers.
diff --git a/components/download/internal/common/download_stats.cc b/components/download/internal/common/download_stats.cc
index 025a169..1a141f7 100644
--- a/components/download/internal/common/download_stats.cc
+++ b/components/download/internal/common/download_stats.cc
@@ -650,8 +650,6 @@
   bool unknown_size = total <= 0;
   int64_t received_kb = received / 1024;
   int64_t total_kb = total / 1024;
-  UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedReceivedSizeK", received_kb,
-                              1, kMaxKb, kBuckets);
   if (is_parallel_download_enabled) {
     UMA_HISTOGRAM_CUSTOM_COUNTS(
         "Download.InterruptedReceivedSizeK.ParallelDownload", received_kb, 1,
@@ -668,31 +666,9 @@
     }
     if (delta_bytes == 0) {
       RecordDownloadCountWithSource(INTERRUPTED_AT_END_COUNT, download_source);
-      UMA_HISTOGRAM_CUSTOM_ENUMERATION("Download.InterruptedAtEndReason",
-                                       reason, samples);
-
       if (is_parallelizable) {
         RecordParallelizableDownloadCount(INTERRUPTED_AT_END_COUNT,
                                           is_parallel_download_enabled);
-        UMA_HISTOGRAM_CUSTOM_ENUMERATION(
-            "Download.InterruptedAtEndReason.ParallelDownload", reason,
-            samples);
-      }
-    } else if (delta_bytes > 0) {
-      UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedOverrunBytes",
-                                  delta_bytes, 1, kMaxKb, kBuckets);
-      if (is_parallel_download_enabled) {
-        UMA_HISTOGRAM_CUSTOM_COUNTS(
-            "Download.InterruptedOverrunBytes.ParallelDownload", delta_bytes, 1,
-            kMaxKb, kBuckets);
-      }
-    } else {
-      UMA_HISTOGRAM_CUSTOM_COUNTS("Download.InterruptedUnderrunBytes",
-                                  -delta_bytes, 1, kMaxKb, kBuckets);
-      if (is_parallel_download_enabled) {
-        UMA_HISTOGRAM_CUSTOM_COUNTS(
-            "Download.InterruptedUnderrunBytes.ParallelDownload", -delta_bytes,
-            1, kMaxKb, kBuckets);
       }
     }
   }
diff --git a/components/feed/content/feed_offline_host.cc b/components/feed/content/feed_offline_host.cc
index fa84beb..54f818a9 100644
--- a/components/feed/content/feed_offline_host.cc
+++ b/components/feed/content/feed_offline_host.cc
@@ -121,9 +121,8 @@
 
 void RunSuggestionCallbackWithConversion(
     SuggestionsProvider::SuggestionCallback suggestions_callback,
-    std::vector<ContentMetadata> metadataVector) {
-  std::move(suggestions_callback)
-      .Run(ConvertMetadataToSuggestions(std::move(metadataVector)));
+    std::vector<offline_pages::PrefetchSuggestion> metadataVector) {
+  std::move(suggestions_callback).Run(metadataVector);
 }
 
 }  //  namespace
@@ -136,6 +135,7 @@
       prefetch_service_(prefetch_service),
       on_suggestion_consumed_(on_suggestion_consumed),
       on_suggestions_shown_(on_suggestions_shown) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(offline_page_model_);
   DCHECK(prefetch_service_);
   DCHECK(!on_suggestion_consumed_.is_null());
@@ -143,6 +143,7 @@
 }
 
 FeedOfflineHost::~FeedOfflineHost() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Safe to call RemoveObserver() even if AddObserver() has not been called.
   offline_page_model_->RemoveObserver(this);
 }
@@ -150,6 +151,7 @@
 void FeedOfflineHost::Initialize(
     const base::RepeatingClosure& trigger_get_known_content,
     const NotifyStatusChangeCallback& notify_status_change) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(trigger_get_known_content_.is_null());
   DCHECK(!trigger_get_known_content.is_null());
   DCHECK(notify_status_change_.is_null());
@@ -168,10 +170,12 @@
 }
 
 void FeedOfflineHost::SetSuggestionProvider() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   prefetch_service_->SetSuggestionProvider(this);
 }
 
 base::Optional<int64_t> FeedOfflineHost::GetOfflineId(const std::string& url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   auto iter = url_hash_to_id_.find(base::Hash(url));
   return iter == url_hash_to_id_.end() ? base::Optional<int64_t>()
                                        : base::Optional<int64_t>(iter->second);
@@ -180,6 +184,7 @@
 void FeedOfflineHost::GetOfflineStatus(
     std::vector<std::string> urls,
     base::OnceCallback<void(std::vector<std::string>)> callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   UMA_HISTOGRAM_EXACT_LINEAR("ContentSuggestions.Feed.Offline.GetStatusCount",
                              urls.size(), 50);
 
@@ -200,38 +205,42 @@
 }
 
 void FeedOfflineHost::OnContentRemoved(std::vector<std::string> urls) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   for (const std::string& url : urls) {
     prefetch_service_->RemoveSuggestion(GURL(url));
   }
 }
 
 void FeedOfflineHost::OnNewContentReceived() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   prefetch_service_->NewSuggestionsAvailable();
 }
 
 void FeedOfflineHost::OnNoListeners() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   url_hash_to_id_.clear();
 }
 
 void FeedOfflineHost::OnGetKnownContentDone(
     std::vector<ContentMetadata> suggestions) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // While |suggestions| are movable, there might be multiple callbacks in
-  // |pending_known_content_callbacks_|. All but one callback will need to
-  // receive a full copy of |suggestions|, and the last one will received a
-  // moved copy.
-  for (size_t i = 0; i < pending_known_content_callbacks_.size(); i++) {
-    bool can_move = (i + 1 == pending_known_content_callbacks_.size());
-    std::move(pending_known_content_callbacks_[i])
-        .Run(can_move ? std::move(suggestions) : suggestions);
+  // |pending_known_content_callbacks_|. To be safe, copy all the suggestions.
+  std::vector<offline_pages::PrefetchSuggestion> converted_suggestions =
+      ConvertMetadataToSuggestions(std::move(suggestions));
+  for (auto& callback : pending_known_content_callbacks_) {
+    RunSuggestionCallbackWithConversion(std::move(callback),
+                                        converted_suggestions);
   }
   pending_known_content_callbacks_.clear();
 }
 
 void FeedOfflineHost::GetCurrentArticleSuggestions(
     SuggestionsProvider::SuggestionCallback suggestions_callback) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!trigger_get_known_content_.is_null());
-  pending_known_content_callbacks_.push_back(base::BindOnce(
-      &RunSuggestionCallbackWithConversion, std::move(suggestions_callback)));
+  pending_known_content_callbacks_.emplace_back(
+      std::move(suggestions_callback));
   // Trigger after push_back() in case triggering results in a synchronous
   // response via OnGetKnownContentDone().
   if (pending_known_content_callbacks_.size() <= 1) {
@@ -240,19 +249,23 @@
 }
 
 void FeedOfflineHost::ReportArticleListViewed() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   on_suggestion_consumed_.Run();
 }
 
 void FeedOfflineHost::ReportArticleViewed(GURL article_url) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   on_suggestions_shown_.Run();
 }
 
 void FeedOfflineHost::OfflinePageModelLoaded(OfflinePageModel* model) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   // Ignored.
 }
 
 void FeedOfflineHost::OfflinePageAdded(OfflinePageModel* model,
                                        const OfflinePageItem& added_page) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!notify_status_change_.is_null());
   const std::string& url = added_page.GetOriginalUrl().spec();
   CacheOfflinePageUrlAndId(url, added_page.offline_id);
@@ -260,6 +273,7 @@
 }
 
 void FeedOfflineHost::OfflinePageDeleted(const OfflinePageItem& deleted_page) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(!notify_status_change_.is_null());
   const std::string& url = deleted_page.url.spec();
   EvictOfflinePageUrl(url);
diff --git a/components/feed/content/feed_offline_host.h b/components/feed/content/feed_offline_host.h
index 66a9fdf..34b8567c 100644
--- a/components/feed/content/feed_offline_host.h
+++ b/components/feed/content/feed_offline_host.h
@@ -13,6 +13,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/optional.h"
+#include "base/sequenced_task_runner.h"
 #include "components/feed/core/content_metadata.h"
 #include "components/offline_pages/core/offline_page_model.h"
 #include "components/offline_pages/core/prefetch/suggestions_provider.h"
@@ -130,11 +131,14 @@
 
   // Holds all consumers of GetKnownContent(). It is assumed that there's an
   // outstanding GetKnownContent() if and only if this vector is not empty.
-  std::vector<GetKnownContentCallback> pending_known_content_callbacks_;
+  std::vector<SuggestionsProvider::SuggestionCallback>
+      pending_known_content_callbacks_;
 
   // Calls all OfflineStatusListeners with the updated status.
   NotifyStatusChangeCallback notify_status_change_;
 
+  SEQUENCE_CHECKER(sequence_checker_);
+
   base::WeakPtrFactory<FeedOfflineHost> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(FeedOfflineHost);
diff --git a/components/gcm_driver/web_push_metrics.cc b/components/gcm_driver/web_push_metrics.cc
index 3e175d1..56d3b44e 100644
--- a/components/gcm_driver/web_push_metrics.cc
+++ b/components/gcm_driver/web_push_metrics.cc
@@ -12,4 +12,9 @@
   base::UmaHistogramEnumeration("GCM.SendWebPushMessageResult", result);
 }
 
+void LogSendWebPushMessagePayloadSize(int size) {
+  // Note: The maximum size accepted by FCM is 4096.
+  base::UmaHistogramCounts10000("GCM.SendWebPushMessagePayloadSize", size);
+}
+
 }  // namespace gcm
diff --git a/components/gcm_driver/web_push_metrics.h b/components/gcm_driver/web_push_metrics.h
index bbdb5ff0e..52d0aa1b 100644
--- a/components/gcm_driver/web_push_metrics.h
+++ b/components/gcm_driver/web_push_metrics.h
@@ -23,6 +23,10 @@
 // is sent.
 void LogSendWebPushMessageResult(SendWebPushMessageResult result);
 
+// Logs the size of message payload to UMA. This should be called right before a
+// web push message is sent.
+void LogSendWebPushMessagePayloadSize(int size);
+
 }  // namespace gcm
 
 #endif  // COMPONENTS_GCM_DRIVER_WEB_PUSH_METRICS_H_
diff --git a/components/gcm_driver/web_push_sender.cc b/components/gcm_driver/web_push_sender.cc
index 506ec5df..2976db5 100644
--- a/components/gcm_driver/web_push_sender.cc
+++ b/components/gcm_driver/web_push_sender.cc
@@ -149,6 +149,7 @@
     return;
   }
 
+  LogSendWebPushMessagePayloadSize(message.payload.size());
   std::unique_ptr<network::SimpleURLLoader> url_loader = BuildURLLoader(
       fcm_token, message.time_to_live, *auth_header, message.payload);
   url_loader->DownloadToString(
diff --git a/components/image_fetcher/core/cached_image_fetcher_unittest.cc b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
index a6f6448..2323f8bd5 100644
--- a/components/image_fetcher/core/cached_image_fetcher_unittest.cc
+++ b/components/image_fetcher/core/cached_image_fetcher_unittest.cc
@@ -53,10 +53,9 @@
 constexpr char kImageDataOther[] = "other";
 
 const char kImageFetcherEventHistogramName[] = "ImageFetcher.Events";
-const char kCacheLoadHistogramName[] =
-    "CachedImageFetcher.ImageLoadFromCacheTime";
+const char kCacheLoadHistogramName[] = "ImageFetcher.ImageLoadFromCacheTime";
 const char kNetworkLoadHistogramName[] =
-    "CachedImageFetcher.ImageLoadFromNetworkTime";
+    "ImageFetcher.ImageLoadFromNetworkTime";
 
 }  // namespace
 
diff --git a/components/image_fetcher/core/image_fetcher_metrics_reporter.cc b/components/image_fetcher/core/image_fetcher_metrics_reporter.cc
index 9779f5b1..6147b4ea 100644
--- a/components/image_fetcher/core/image_fetcher_metrics_reporter.cc
+++ b/components/image_fetcher/core/image_fetcher_metrics_reporter.cc
@@ -20,16 +20,18 @@
 
 constexpr char kEventsHistogram[] = "ImageFetcher.Events";
 constexpr char kImageLoadFromCacheHistogram[] =
-    "CachedImageFetcher.ImageLoadFromCacheTime";
+    "ImageFetcher.ImageLoadFromCacheTime";
 constexpr char kImageLoadFromCacheJavaHistogram[] =
-    "CachedImageFetcher.ImageLoadFromCacheTimeJava";
+    "ImageFetcher.ImageLoadFromCacheTimeJava";
 constexpr char kTotalFetchFromNativeTimeJavaHistogram[] =
-    "CachedImageFetcher.ImageLoadFromNativeTimeJava";
+    "ImageFetcher.ImageLoadFromNativeTimeJava";
 constexpr char kImageLoadFromNetworkHistogram[] =
-    "CachedImageFetcher.ImageLoadFromNetworkTime";
+    "ImageFetcher.ImageLoadFromNetworkTime";
 constexpr char kImageLoadFromNetworkAfterCacheHitHistogram[] =
-    "CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit";
-constexpr char kLoadImageMetadata[] = "CachedImageFetcher.LoadImageMetadata";
+    "ImageFetcher.ImageLoadFromNetworkAfterCacheHit";
+constexpr char kTimeSinceLastLRUEvictionHistogram[] =
+    "ImageFetcher.TimeSinceLastCacheLRUEviction";
+constexpr char kLoadImageMetadata[] = "ImageFetcher.LoadImageMetadata";
 
 // Returns a raw pointer to a histogram which is owned
 base::HistogramBase* GetTimeHistogram(const std::string& histogram_name,
@@ -115,8 +117,7 @@
 void ImageFetcherMetricsReporter::ReportTimeSinceLastCacheLRUEviction(
     base::Time start_time) {
   base::TimeDelta time_delta = base::Time::Now() - start_time;
-  UMA_HISTOGRAM_TIMES("CachedImageFetcher.TimeSinceLastCacheLRUEviction",
-                      time_delta);
+  UMA_HISTOGRAM_TIMES(kTimeSinceLastLRUEvictionHistogram, time_delta);
 }
 
 // static
diff --git a/components/image_fetcher/core/image_fetcher_metrics_reporter_unittest.cc b/components/image_fetcher/core/image_fetcher_metrics_reporter_unittest.cc
index 5fe1acf..39d15e35 100644
--- a/components/image_fetcher/core/image_fetcher_metrics_reporter_unittest.cc
+++ b/components/image_fetcher/core/image_fetcher_metrics_reporter_unittest.cc
@@ -17,18 +17,17 @@
 const char kUmaClientNameOther[] = "bar";
 
 const char kImageFetcherEventHistogramName[] = "ImageFetcher.Events";
-const char kCacheLoadHistogramName[] =
-    "CachedImageFetcher.ImageLoadFromCacheTime";
+const char kCacheLoadHistogramName[] = "ImageFetcher.ImageLoadFromCacheTime";
 const char kCacheLoadHistogramNameJava[] =
-    "CachedImageFetcher.ImageLoadFromCacheTimeJava";
+    "ImageFetcher.ImageLoadFromCacheTimeJava";
 constexpr char kTotalFetchFromNativeHistogramNameJava[] =
-    "CachedImageFetcher.ImageLoadFromNativeTimeJava";
+    "ImageFetcher.ImageLoadFromNativeTimeJava";
 const char kNetworkLoadHistogramName[] =
-    "CachedImageFetcher.ImageLoadFromNetworkTime";
+    "ImageFetcher.ImageLoadFromNetworkTime";
 const char kNetworkLoadAfterCacheHitHistogram[] =
-    "CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit";
+    "ImageFetcher.ImageLoadFromNetworkAfterCacheHit";
 const char kTimeSinceLastCacheLRUEviction[] =
-    "CachedImageFetcher.TimeSinceLastCacheLRUEviction";
+    "ImageFetcher.TimeSinceLastCacheLRUEviction";
 
 }  // namespace
 
diff --git a/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc b/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc
index a892490..e3f0a99b 100644
--- a/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc
+++ b/components/ntp_snippets/offline_pages/offline_pages_test_utils.cc
@@ -12,10 +12,8 @@
 #include "base/guid.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 
 using offline_pages::ClientId;
-using offline_pages::ClientPolicyController;
 using offline_pages::MultipleOfflinePageItemCallback;
 using offline_pages::OfflinePageItem;
 using offline_pages::PageCriteria;
@@ -36,10 +34,9 @@
 void FakeOfflinePageModel::GetPagesWithCriteria(
     const PageCriteria& criteria,
     MultipleOfflinePageItemCallback callback) {
-  ClientPolicyController controller;
   MultipleOfflinePageItemResult filtered_result;
   for (auto& item : items_) {
-    if (offline_pages::MeetsCriteria(controller, criteria, item))
+    if (offline_pages::MeetsCriteria(criteria, item))
       filtered_result.emplace_back(item);
   }
   std::move(callback).Run(filtered_result);
diff --git a/components/ntp_tiles/icon_cacher_impl.cc b/components/ntp_tiles/icon_cacher_impl.cc
index 52a3b5f..49c5e9f87 100644
--- a/components/ntp_tiles/icon_cacher_impl.cc
+++ b/components/ntp_tiles/icon_cacher_impl.cc
@@ -152,7 +152,6 @@
     const image_fetcher::RequestMetadata& metadata) {
   if (fetched_image.IsEmpty()) {
     FinishRequestAndNotifyIconAvailable(site.url, /*newly_available=*/false);
-    UMA_HISTOGRAM_BOOLEAN("NewTabPage.TileFaviconFetchSuccess.Popular", false);
     return;
   }
 
@@ -163,7 +162,6 @@
   }
   SaveIconForSite(site, fetched_image);
   FinishRequestAndNotifyIconAvailable(site.url, /*newly_available=*/true);
-  UMA_HISTOGRAM_BOOLEAN("NewTabPage.TileFaviconFetchSuccess.Popular", true);
 }
 
 void IconCacherImpl::SaveAndNotifyDefaultIconForSite(
@@ -276,9 +274,6 @@
 void IconCacherImpl::OnMostLikelyFaviconDownloaded(
     const GURL& request_url,
     favicon_base::GoogleFaviconServerRequestStatus status) {
-  UMA_HISTOGRAM_ENUMERATION(
-      "NewTabPage.TileFaviconFetchStatus.Server", status,
-      favicon_base::GoogleFaviconServerRequestStatus::COUNT);
   FinishRequestAndNotifyIconAvailable(
       request_url,
       status == favicon_base::GoogleFaviconServerRequestStatus::SUCCESS);
diff --git a/components/ntp_tiles/icon_cacher_impl_unittest.cc b/components/ntp_tiles/icon_cacher_impl_unittest.cc
index 26f184bf..94e1c2b 100644
--- a/components/ntp_tiles/icon_cacher_impl_unittest.cc
+++ b/components/ntp_tiles/icon_cacher_impl_unittest.cc
@@ -195,9 +195,6 @@
   WaitForMainThreadTasksToFinish();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kFavicon));
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
-              IsEmpty());
 }
 
 TEST_F(IconCacherTestPopularSites, LargeNotCachedAndFetchSucceeded) {
@@ -217,9 +214,6 @@
   loop.Run();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kFavicon));
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
-              ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, SmallNotCachedAndFetchSucceeded) {
@@ -257,9 +251,6 @@
   WaitForMainThreadTasksToFinish();
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kFavicon));
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
-              ElementsAre(Bucket(/*bucket=*/false, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, HandlesEmptyCallbacksNicely) {
@@ -274,9 +265,6 @@
   EXPECT_FALSE(IconIsCachedFor(site_.url, favicon_base::IconType::kFavicon));
   EXPECT_TRUE(IconIsCachedFor(site_.url, favicon_base::IconType::kTouchIcon));
   // The histogram gets reported despite empty callbacks.
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
-              ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, ProvidesDefaultIconAndSucceedsWithFetching) {
@@ -321,11 +309,6 @@
   EXPECT_THAT(
       GetCachedIconFor(site_.url, favicon_base::IconType::kTouchIcon).Size(),
       Eq(gfx::Size(128, 128)));  // Compares dimensions, not objects.
-  // The histogram gets reported only once (for the downloaded icon, not for the
-  // default one).
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchSuccess.Popular"),
-              ElementsAre(Bucket(/*bucket=*/true, /*count=*/1)));
 }
 
 TEST_F(IconCacherTestPopularSites, LargeNotCachedAndFetchPerformedOnlyOnce) {
@@ -379,9 +362,6 @@
 
   EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::IconType::kFavicon));
   EXPECT_TRUE(IconIsCachedFor(page_url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchStatus.Server"),
-              IsEmpty());
 }
 
 TEST_F(IconCacherTestMostLikely, NotCachedAndFetchSucceeded) {
@@ -413,12 +393,6 @@
   loop.Run();
   EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::IconType::kFavicon));
   EXPECT_TRUE(IconIsCachedFor(page_url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchStatus.Server"),
-              ElementsAre(Bucket(
-                  /*bucket=*/static_cast<int>(
-                      favicon_base::GoogleFaviconServerRequestStatus::SUCCESS),
-                  /*count=*/1)));
 }
 
 TEST_F(IconCacherTestMostLikely, NotCachedAndFetchFailed) {
@@ -448,13 +422,6 @@
 
   EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::IconType::kFavicon));
   EXPECT_FALSE(IconIsCachedFor(page_url, favicon_base::IconType::kTouchIcon));
-  EXPECT_THAT(histogram_tester.GetAllSamples(
-                  "NewTabPage.TileFaviconFetchStatus.Server"),
-              ElementsAre(Bucket(
-                  /*bucket=*/static_cast<int>(
-                      favicon_base::GoogleFaviconServerRequestStatus::
-                          FAILURE_CONNECTION_ERROR),
-                  /*count=*/1)));
 }
 
 TEST_F(IconCacherTestMostLikely, HandlesEmptyCallbacksNicely) {
diff --git a/components/offline_pages/core/BUILD.gn b/components/offline_pages/core/BUILD.gn
index 23d262f..6340993e 100644
--- a/components/offline_pages/core/BUILD.gn
+++ b/components/offline_pages/core/BUILD.gn
@@ -20,8 +20,6 @@
     "client_id.h",
     "client_namespace_constants.cc",
     "client_namespace_constants.h",
-    "client_policy_controller.cc",
-    "client_policy_controller.h",
     "model/add_page_task.cc",
     "model/add_page_task.h",
     "model/cleanup_visuals_task.cc",
@@ -157,7 +155,6 @@
     "archive_validator_unittest.cc",
     "auto_fetch_unittest.cc",
     "background_snapshot_controller_unittest.cc",
-    "client_policy_controller_unittest.cc",
     "model/add_page_task_unittest.cc",
     "model/cleanup_visuals_task_unittest.cc",
     "model/clear_storage_task_unittest.cc",
@@ -173,6 +170,7 @@
     "model/update_publish_id_task_unittest.cc",
     "model/visuals_availability_task_unittest.cc",
     "offline_event_logger_unittest.cc",
+    "offline_page_client_policy_unittest.cc",
     "offline_page_feature_unittest.cc",
     "offline_page_item_utils_unittest.cc",
     "offline_page_metadata_store_unittest.cc",
diff --git a/components/offline_pages/core/background/pick_request_task.cc b/components/offline_pages/core/background/pick_request_task.cc
index 893b545..348acae 100644
--- a/components/offline_pages/core/background/pick_request_task.cc
+++ b/components/offline_pages/core/background/pick_request_task.cc
@@ -19,8 +19,8 @@
 #include "components/offline_pages/core/background/request_notifier.h"
 #include "components/offline_pages/core/background/request_queue_store.h"
 #include "components/offline_pages/core/background/save_page_request.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_clock.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 
 namespace {
 template <typename T>
@@ -42,7 +42,6 @@
 PickRequestTask::PickRequestTask(
     RequestQueueStore* store,
     OfflinerPolicy* policy,
-    ClientPolicyController* policy_controller,
     RequestPickedCallback picked_callback,
     RequestNotPickedCallback not_picked_callback,
     RequestCountCallback request_count_callback,
@@ -51,7 +50,6 @@
     base::circular_deque<int64_t>* prioritized_requests)
     : store_(store),
       policy_(policy),
-      policy_controller_(policy_controller),
       picked_callback_(std::move(picked_callback)),
       not_picked_callback_(std::move(not_picked_callback)),
       request_count_callback_(std::move(request_count_callback)),
@@ -134,7 +132,7 @@
       available_requests->push_back(*request);
     if (!RequestConditionsSatisfied(*request))
       continue;
-    if (policy_controller_->GetPolicy(request->client_id().name_space)
+    if (GetPolicy(request->client_id().name_space)
             .defer_background_fetch_while_page_is_active) {
       if (!request->last_attempt_time().is_null() &&
           OfflineTimeNow() - request->last_attempt_time() < kDeferInterval) {
diff --git a/components/offline_pages/core/background/pick_request_task.h b/components/offline_pages/core/background/pick_request_task.h
index 88814f3..7332891 100644
--- a/components/offline_pages/core/background/pick_request_task.h
+++ b/components/offline_pages/core/background/pick_request_task.h
@@ -18,7 +18,6 @@
 
 namespace offline_pages {
 
-class ClientPolicyController;
 class OfflinerPolicy;
 class PickRequestTask;
 class RequestQueueStore;
@@ -49,7 +48,6 @@
 
   PickRequestTask(RequestQueueStore* store,
                   OfflinerPolicy* policy,
-                  ClientPolicyController* policy_controller,
                   RequestPickedCallback picked_callback,
                   RequestNotPickedCallback not_picked_callback,
                   RequestCountCallback request_count_callback,
@@ -101,7 +99,6 @@
   // Member variables, all pointers are not owned here.
   RequestQueueStore* store_;
   OfflinerPolicy* policy_;
-  ClientPolicyController* policy_controller_;
   RequestPickedCallback picked_callback_;
   RequestNotPickedCallback not_picked_callback_;
   RequestCountCallback request_count_callback_;
diff --git a/components/offline_pages/core/background/pick_request_task_unittest.cc b/components/offline_pages/core/background/pick_request_task_unittest.cc
index 41ff45b..88b34e9 100644
--- a/components/offline_pages/core/background/pick_request_task_unittest.cc
+++ b/components/offline_pages/core/background/pick_request_task_unittest.cc
@@ -20,7 +20,6 @@
 #include "components/offline_pages/core/background/request_queue_task_test_base.h"
 #include "components/offline_pages/core/background/save_page_request.h"
 #include "components/offline_pages/core/background/test_request_queue_store.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_clock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -123,7 +122,6 @@
   std::unique_ptr<RequestNotifierStub> notifier_;
   std::unique_ptr<SavePageRequest> last_picked_;
   std::unique_ptr<OfflinerPolicy> policy_;
-  ClientPolicyController policy_controller_;
   RequestCoordinatorEventLogger event_logger_;
   std::set<int64_t> disabled_requests_;
   base::circular_deque<int64_t> prioritized_requests_;
@@ -192,7 +190,7 @@
 void PickRequestTaskTest::MakePickRequestTask() {
   DeviceConditions conditions;
   task_.reset(new PickRequestTask(
-      &store_, policy_.get(), &policy_controller_,
+      &store_, policy_.get(),
       base::BindOnce(&PickRequestTaskTest::RequestPicked,
                      base::Unretained(this)),
       base::BindOnce(&PickRequestTaskTest::RequestNotPicked,
diff --git a/components/offline_pages/core/background/request_coordinator.cc b/components/offline_pages/core/background/request_coordinator.cc
index 508e4690..df0a163b 100644
--- a/components/offline_pages/core/background/request_coordinator.cc
+++ b/components/offline_pages/core/background/request_coordinator.cc
@@ -21,8 +21,8 @@
 #include "components/offline_pages/core/background/offliner_client.h"
 #include "components/offline_pages/core/background/offliner_policy.h"
 #include "components/offline_pages/core/background/save_page_request.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_clock.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_model.h"
@@ -279,7 +279,6 @@
       policy_(std::move(policy)),
       queue_(std::move(queue)),
       scheduler_(std::move(scheduler)),
-      policy_controller_(new ClientPolicyController()),
       network_quality_tracker_(network_quality_tracker),
       network_quality_at_request_start_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
       last_offlining_status_(Offliner::RequestStatus::UNKNOWN),
@@ -801,7 +800,7 @@
   // Ask request queue to make a new PickRequestTask object, then put it on
   // the task queue.
   queue_->PickNextRequest(
-      policy_.get(), policy_controller_.get(),
+      policy_.get(),
       base::BindOnce(&RequestCoordinator::RequestPicked,
                      weak_ptr_factory_.GetWeakPtr()),
       base::BindOnce(&RequestCoordinator::RequestNotPicked,
@@ -921,7 +920,7 @@
     RecordStartTimeUMA(request);
   }
   const OfflinePageClientPolicy& policy =
-      policy_controller_->GetPolicy(request.client_id().name_space);
+      GetPolicy(request.client_id().name_space);
   if (policy.defer_background_fetch_while_page_is_active &&
       active_tab_info_->DoesActiveTabMatch(request.url())) {
     queue_->MarkAttemptDeferred(
@@ -1181,10 +1180,6 @@
     observer.OnNetworkProgress(request, received_bytes);
 }
 
-ClientPolicyController* RequestCoordinator::GetPolicyController() {
-  return policy_controller_.get();
-}
-
 void RequestCoordinator::RecordOfflinerResult(const SavePageRequest& request,
                                               Offliner::RequestStatus status) {
   event_logger_.RecordOfflinerResult(request.client_id().name_space, status,
diff --git a/components/offline_pages/core/background/request_coordinator.h b/components/offline_pages/core/background/request_coordinator.h
index 92bffaf..bb919ed 100644
--- a/components/offline_pages/core/background/request_coordinator.h
+++ b/components/offline_pages/core/background/request_coordinator.h
@@ -40,7 +40,6 @@
 class OfflinerPolicy;
 class Offliner;
 class SavePageRequest;
-class ClientPolicyController;
 
 // Coordinates queueing and processing save page later requests.
 class RequestCoordinator : public KeyedService,
@@ -241,8 +240,6 @@
 
   OfflinerPolicy* policy() { return policy_.get(); }
 
-  ClientPolicyController* GetPolicyController();
-
   // Return the state of the request coordinator.
   RequestCoordinatorState state() { return state_; }
 
@@ -468,8 +465,6 @@
   std::unique_ptr<RequestQueue> queue_;
   // Scheduler. Used to request a callback when network is available.  Owned.
   std::unique_ptr<Scheduler> scheduler_;
-  // Controller of client policies. Owned.
-  std::unique_ptr<ClientPolicyController> policy_controller_;
   // Unowned pointer. Guaranteed to be non-null during the lifetime of |this|.
   // Must be accessed only on the UI thread.
   network::NetworkQualityTracker* network_quality_tracker_;
diff --git a/components/offline_pages/core/background/request_queue.cc b/components/offline_pages/core/background/request_queue.cc
index 6c10b2c..1a413bf 100644
--- a/components/offline_pages/core/background/request_queue.cc
+++ b/components/offline_pages/core/background/request_queue.cc
@@ -131,7 +131,6 @@
 
 void RequestQueue::PickNextRequest(
     OfflinerPolicy* policy,
-    ClientPolicyController* policy_controller,
     PickRequestTask::RequestPickedCallback picked_callback,
     PickRequestTask::RequestNotPickedCallback not_picked_callback,
     PickRequestTask::RequestCountCallback request_count_callback,
@@ -140,7 +139,7 @@
     base::circular_deque<int64_t>* prioritized_requests) {
   // Using the PickerContext, create a picker task.
   std::unique_ptr<Task> task(new PickRequestTask(
-      store_.get(), policy, policy_controller, std::move(picked_callback),
+      store_.get(), policy, std::move(picked_callback),
       std::move(not_picked_callback), std::move(request_count_callback),
       std::move(conditions), disabled_requests, prioritized_requests));
 
diff --git a/components/offline_pages/core/background/request_queue.h b/components/offline_pages/core/background/request_queue.h
index 31c4cf9..e55e1f6 100644
--- a/components/offline_pages/core/background/request_queue.h
+++ b/components/offline_pages/core/background/request_queue.h
@@ -30,7 +30,6 @@
 namespace offline_pages {
 
 class CleanupTaskFactory;
-class ClientPolicyController;
 class RequestQueueStore;
 
 // Class responsible for managing save page requests.
@@ -130,7 +129,6 @@
   // callbacks.
   void PickNextRequest(
       OfflinerPolicy* policy,
-      ClientPolicyController* policy_controller,
       PickRequestTask::RequestPickedCallback picked_callback,
       PickRequestTask::RequestNotPickedCallback not_picked_callback,
       PickRequestTask::RequestCountCallback request_count_callback,
diff --git a/components/offline_pages/core/client_policy_controller.cc b/components/offline_pages/core/client_policy_controller.cc
deleted file mode 100644
index e05a7f5..0000000
--- a/components/offline_pages/core/client_policy_controller.cc
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/offline_pages/core/client_policy_controller.h"
-
-#include <utility>
-
-#include "base/time/time.h"
-#include "components/offline_pages/core/client_namespace_constants.h"
-
-namespace offline_pages {
-
-OfflinePageClientPolicy* ClientPolicyController::AddTemporaryPolicy(
-    const std::string& name_space,
-    const base::TimeDelta& expiration_period) {
-  auto iter_and_was_inserted_pair = policies_.emplace(
-      name_space, OfflinePageClientPolicy(name_space, LifetimeType::TEMPORARY));
-  DCHECK(iter_and_was_inserted_pair.second) << "Policy was not inserted";
-  OfflinePageClientPolicy& policy = (*iter_and_was_inserted_pair.first).second;
-  policy.expiration_period = expiration_period;
-  return &policy;
-}
-
-OfflinePageClientPolicy* ClientPolicyController::AddPersistentPolicy(
-    const std::string& name_space) {
-  auto iter_and_was_inserted_pair = policies_.emplace(
-      name_space,
-      OfflinePageClientPolicy(name_space, LifetimeType::PERSISTENT));
-  DCHECK(iter_and_was_inserted_pair.second) << "Policy was not inserted";
-  OfflinePageClientPolicy& policy = (*iter_and_was_inserted_pair.first).second;
-  return &policy;
-}
-
-ClientPolicyController::ClientPolicyController() {
-  {
-    OfflinePageClientPolicy* policy =
-        AddTemporaryPolicy(kBookmarkNamespace, base::TimeDelta::FromDays(7));
-    policy->pages_allowed_per_url = 1;
-  }
-  {
-    OfflinePageClientPolicy* policy =
-        AddTemporaryPolicy(kLastNNamespace, base::TimeDelta::FromDays(30));
-    policy->is_restricted_to_tab_from_client_id = true;
-  }
-  {
-    OfflinePageClientPolicy* policy = AddPersistentPolicy(kAsyncNamespace);
-    policy->is_supported_by_download = true;
-  }
-  {
-    OfflinePageClientPolicy* policy =
-        AddTemporaryPolicy(kCCTNamespace, base::TimeDelta::FromDays(2));
-    policy->pages_allowed_per_url = 1;
-    policy->requires_specific_user_settings = true;
-  }
-  {
-    OfflinePageClientPolicy* policy = AddPersistentPolicy(kDownloadNamespace);
-    policy->is_supported_by_download = true;
-  }
-  {
-    OfflinePageClientPolicy* policy =
-        AddPersistentPolicy(kNTPSuggestionsNamespace);
-    policy->is_supported_by_download = true;
-  }
-  {
-    OfflinePageClientPolicy* policy = AddTemporaryPolicy(
-        kSuggestedArticlesNamespace, base::TimeDelta::FromDays(30));
-    policy->is_supported_by_download = 1;
-    policy->is_suggested = true;
-  }
-  {
-    OfflinePageClientPolicy* policy =
-        AddPersistentPolicy(kBrowserActionsNamespace);
-    policy->is_supported_by_download = true;
-    policy->allows_conversion_to_background_file_download = true;
-  }
-  {
-    OfflinePageClientPolicy* policy = AddTemporaryPolicy(
-        kLivePageSharingNamespace, base::TimeDelta::FromHours(1));
-    policy->pages_allowed_per_url = 1;
-    policy->is_restricted_to_tab_from_client_id = true;
-  }
-  {
-    OfflinePageClientPolicy* policy =
-        AddTemporaryPolicy(kAutoAsyncNamespace, base::TimeDelta::FromDays(30));
-    policy->pages_allowed_per_url = 1;
-    policy->defer_background_fetch_while_page_is_active = true;
-  }
-
-  // Fallback policy.
-  {
-    OfflinePageClientPolicy* policy =
-        AddTemporaryPolicy(kDefaultNamespace, base::TimeDelta::FromDays(1));
-    policy->page_limit = 10;
-    policy->pages_allowed_per_url = 1;
-  }
-
-  for (const auto& policy_item : policies_) {
-    const std::string& name = policy_item.first;
-    switch (policy_item.second.lifetime_type) {
-      case LifetimeType::TEMPORARY:
-        temporary_namespaces_.push_back(name);
-        break;
-      case LifetimeType::PERSISTENT:
-        persistent_namespaces_.push_back(name);
-        break;
-    }
-  }
-}
-
-ClientPolicyController::~ClientPolicyController() {}
-
-const OfflinePageClientPolicy& ClientPolicyController::GetPolicy(
-    const std::string& name_space) const {
-  const auto& iter = policies_.find(name_space);
-  if (iter != policies_.end())
-    return iter->second;
-  // Fallback when the namespace isn't defined.
-  return policies_.at(kDefaultNamespace);
-}
-
-std::vector<std::string> ClientPolicyController::GetAllNamespaces() const {
-  std::vector<std::string> result;
-  for (const auto& policy_item : policies_)
-    result.emplace_back(policy_item.first);
-
-  return result;
-}
-
-bool ClientPolicyController::IsTemporary(const std::string& name_space) const {
-  return GetPolicy(name_space).lifetime_type == LifetimeType::TEMPORARY;
-}
-
-const std::vector<std::string>& ClientPolicyController::GetTemporaryNamespaces()
-    const {
-  return temporary_namespaces_;
-}
-
-bool ClientPolicyController::IsSupportedByDownload(
-    const std::string& name_space) const {
-  return GetPolicy(name_space).is_supported_by_download;
-}
-
-bool ClientPolicyController::IsPersistent(const std::string& name_space) const {
-  return GetPolicy(name_space).lifetime_type == LifetimeType::PERSISTENT;
-}
-
-const std::vector<std::string>&
-ClientPolicyController::GetPersistentNamespaces() const {
-  return persistent_namespaces_;
-}
-
-bool ClientPolicyController::IsRestrictedToTabFromClientId(
-    const std::string& name_space) const {
-  return GetPolicy(name_space).is_restricted_to_tab_from_client_id;
-}
-
-bool ClientPolicyController::RequiresSpecificUserSettings(
-    const std::string& name_space) const {
-  return GetPolicy(name_space).requires_specific_user_settings;
-}
-
-bool ClientPolicyController::IsSuggested(const std::string& name_space) const {
-  return GetPolicy(name_space).is_suggested;
-}
-
-bool ClientPolicyController::AllowsConversionToBackgroundFileDownload(
-    const std::string& name_space) const {
-  return GetPolicy(name_space).allows_conversion_to_background_file_download;
-}
-
-}  // namespace offline_pages
diff --git a/components/offline_pages/core/client_policy_controller.h b/components/offline_pages/core/client_policy_controller.h
deleted file mode 100644
index 6d973e8e..0000000
--- a/components/offline_pages/core/client_policy_controller.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_OFFLINE_PAGES_CORE_CLIENT_POLICY_CONTROLLER_H_
-#define COMPONENTS_OFFLINE_PAGES_CORE_CLIENT_POLICY_CONTROLLER_H_
-
-#include <map>
-#include <string>
-#include <vector>
-
-#include "components/offline_pages/core/offline_page_client_policy.h"
-
-namespace offline_pages {
-
-// This is the class which is a singleton for offline page model
-// to get client policies based on namespaces.
-class ClientPolicyController {
- public:
-  ClientPolicyController();
-  ClientPolicyController(const ClientPolicyController&) = delete;
-  ~ClientPolicyController();
-
-  ClientPolicyController& operator=(const ClientPolicyController&) = delete;
-
-  // Get the client policy for |name_space|.
-  const OfflinePageClientPolicy& GetPolicy(const std::string& name_space) const;
-
-  // Returns a list of all known namespaces.
-  std::vector<std::string> GetAllNamespaces() const;
-
-  // Returns whether pages for |name_space| are temporary.
-  bool IsTemporary(const std::string& name_space) const;
-
-  // Returns a list of all temporary namespaces.
-  const std::vector<std::string>& GetTemporaryNamespaces() const;
-
-  // Returns whether pages for |name_space| persistent.
-  bool IsPersistent(const std::string& name_space) const;
-
-  // Returns a list of all persistent namespaces.
-  const std::vector<std::string>& GetPersistentNamespaces() const;
-
-  // Returns whether pages for |name_space| are shown in Download UI.
-  bool IsSupportedByDownload(const std::string& name_space) const;
-
-  // Returns whether pages for |name_space| should only be opened in a
-  // specifically assigned tab.
-  // Note: For this restriction to work offline pages saved to this namespace
-  // must have the respective tab id set to their ClientId::id field.
-  bool IsRestrictedToTabFromClientId(const std::string& name_space) const;
-
-  // Returns whether pages for |name_space| can be saved only if specific user
-  // settings are properly set. See
-  // OfflinePageClientPolicy::requires_specific_user_settings for details).
-  bool RequiresSpecificUserSettings(const std::string& name_space) const;
-
-  // Returns whether pages for |name_space| originate from suggested URLs and
-  // are downloaded on behalf of user.
-  bool IsSuggested(const std::string& name_space) const;
-
-  // Returns whether we should allow background downloads of pages for
-  // |name_space| to be converted to regular file downloads.
-  bool AllowsConversionToBackgroundFileDownload(
-      const std::string& name_space) const;
-
- private:
-  OfflinePageClientPolicy* AddTemporaryPolicy(
-      const std::string& name_space,
-      const base::TimeDelta& expiration_period);
-  OfflinePageClientPolicy* AddPersistentPolicy(const std::string& name_space);
-
-  // The map from name_space to a client policy. Will be generated
-  // as pre-defined values for now.
-  std::map<std::string, OfflinePageClientPolicy> policies_;
-
-  std::vector<std::string> temporary_namespaces_;
-  std::vector<std::string> persistent_namespaces_;
-};
-
-}  // namespace offline_pages
-
-#endif  // COMPONENTS_OFFLINE_PAGES_CORE_CLIENT_POLICY_CONTROLLER_H_
diff --git a/components/offline_pages/core/client_policy_controller_unittest.cc b/components/offline_pages/core/client_policy_controller_unittest.cc
index 9d354a2..d8b10aee 100644
--- a/components/offline_pages/core/client_policy_controller_unittest.cc
+++ b/components/offline_pages/core/client_policy_controller_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "components/offline_pages/core/client_policy_controller.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 
 #include <algorithm>
 #include <memory>
@@ -25,11 +25,10 @@
 
 class ClientPolicyControllerTest : public testing::Test {
  public:
-  ClientPolicyController* controller() { return controller_.get(); }
 
   // testing::Test
-  void SetUp() override;
-  void TearDown() override;
+  void SetUp() override {}
+  void TearDown() override {}
 
  protected:
   void ExpectTemporary(std::string name_space);
@@ -39,70 +38,47 @@
                                          bool expectation);
   void ExpectRequiresSpecificUserSettings(std::string name_space,
                                           bool expectation);
-
- private:
-  std::unique_ptr<ClientPolicyController> controller_;
 };
 
-void ClientPolicyControllerTest::SetUp() {
-  controller_.reset(new ClientPolicyController());
-}
-
-void ClientPolicyControllerTest::TearDown() {
-  controller_.reset();
-}
-
 void ClientPolicyControllerTest::ExpectTemporary(std::string name_space) {
-  EXPECT_TRUE(
-      base::Contains(controller()->GetTemporaryNamespaces(), name_space))
+  EXPECT_TRUE(base::Contains(GetTemporaryPolicyNamespaces(), name_space))
       << "Namespace " << name_space
       << " had incorrect lifetime type when getting temporary namespaces.";
-  EXPECT_TRUE(controller()->IsTemporary(name_space))
+  EXPECT_EQ(GetPolicy(name_space).lifetime_type, LifetimeType::TEMPORARY)
       << "Namespace " << name_space
       << " had incorrect lifetime type setting when directly checking"
          " if it is temporary.";
-  EXPECT_FALSE(
-      base::Contains(controller()->GetPersistentNamespaces(), name_space))
+  EXPECT_FALSE(base::Contains(GetPersistentPolicyNamespaces(), name_space))
       << "Namespace " << name_space
       << " had incorrect lifetime type when getting persistent namespaces.";
-  EXPECT_FALSE(controller()->IsPersistent(name_space))
-      << "Namespace " << name_space
-      << " had incorrect lifetime type setting when directly checking"
-         " if it is persistent.";
 }
 
 void ClientPolicyControllerTest::ExpectDownloadSupport(std::string name_space,
                                                        bool expectation) {
-  EXPECT_EQ(expectation, controller()->IsSupportedByDownload(name_space))
+  EXPECT_EQ(expectation, GetPolicy(name_space).is_supported_by_download)
       << "Namespace " << name_space
       << " had incorrect download support when directly checking if supported"
          " by download.";
 }
 
 void ClientPolicyControllerTest::ExpectPersistent(std::string name_space) {
-  EXPECT_FALSE(
-      base::Contains(controller()->GetTemporaryNamespaces(), name_space))
+  EXPECT_FALSE(base::Contains(GetTemporaryPolicyNamespaces(), name_space))
       << "Namespace " << name_space
       << " had incorrect lifetime type when getting temporary namespaces.";
-  EXPECT_FALSE(controller()->IsTemporary(name_space))
+  EXPECT_EQ(GetPolicy(name_space).lifetime_type, LifetimeType::PERSISTENT)
       << "Namespace " << name_space
       << " had incorrect lifetime type setting when directly checking"
          " if it is temporary.";
-  EXPECT_TRUE(
-      base::Contains(controller()->GetPersistentNamespaces(), name_space))
+  EXPECT_TRUE(base::Contains(GetPersistentPolicyNamespaces(), name_space))
       << "Namespace " << name_space
       << " had incorrect lifetime type when getting persistent namespaces.";
-  EXPECT_TRUE(controller()->IsPersistent(name_space))
-      << "Namespace " << name_space
-      << " had incorrect lifetime type setting when directly checking"
-         " if it is persistent.";
 }
 
 void ClientPolicyControllerTest::ExpectRestrictedToTabFromClientId(
     std::string name_space,
     bool expectation) {
   EXPECT_EQ(expectation,
-            controller()->IsRestrictedToTabFromClientId(name_space))
+            GetPolicy(name_space).is_restricted_to_tab_from_client_id)
       << "Namespace " << name_space
       << " had incorrect restriction when directly checking if the namespace"
          " is restricted to the tab from the client id field";
@@ -111,20 +87,21 @@
 void ClientPolicyControllerTest::ExpectRequiresSpecificUserSettings(
     std::string name_space,
     bool expectation) {
-  EXPECT_EQ(expectation, controller()->RequiresSpecificUserSettings(name_space))
+  EXPECT_EQ(expectation, GetPolicy(name_space).requires_specific_user_settings)
       << "Namespace " << name_space
       << " had incorrect download support when directly checking if disabled"
          " when prefetch settings are disabled.";
 }
 
 TEST_F(ClientPolicyControllerTest, FallbackTest) {
-  OfflinePageClientPolicy policy = controller()->GetPolicy(kUndefinedNamespace);
+  const OfflinePageClientPolicy& policy = GetPolicy(kUndefinedNamespace);
   EXPECT_EQ(policy.name_space, kDefaultNamespace);
   EXPECT_TRUE(isTemporary(policy));
   ExpectTemporary(kDefaultNamespace);
-  EXPECT_FALSE(base::Contains(controller()->GetTemporaryNamespaces(),
-                              kUndefinedNamespace));
-  EXPECT_TRUE(controller()->IsTemporary(kUndefinedNamespace));
+  EXPECT_FALSE(
+      base::Contains(GetTemporaryPolicyNamespaces(), kUndefinedNamespace));
+  EXPECT_EQ(GetPolicy(kUndefinedNamespace).lifetime_type,
+            LifetimeType::TEMPORARY);
   ExpectDownloadSupport(kUndefinedNamespace, false);
   ExpectDownloadSupport(kDefaultNamespace, false);
   ExpectRestrictedToTabFromClientId(kUndefinedNamespace, false);
@@ -134,7 +111,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckBookmarkDefined) {
-  OfflinePageClientPolicy policy = controller()->GetPolicy(kBookmarkNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kBookmarkNamespace);
   EXPECT_EQ(policy.name_space, kBookmarkNamespace);
   EXPECT_TRUE(isTemporary(policy));
   ExpectTemporary(kBookmarkNamespace);
@@ -144,7 +121,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckLastNDefined) {
-  OfflinePageClientPolicy policy = controller()->GetPolicy(kLastNNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kLastNNamespace);
   EXPECT_EQ(policy.name_space, kLastNNamespace);
   EXPECT_TRUE(isTemporary(policy));
   ExpectTemporary(kLastNNamespace);
@@ -154,7 +131,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckAsyncDefined) {
-  OfflinePageClientPolicy policy = controller()->GetPolicy(kAsyncNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kAsyncNamespace);
   EXPECT_EQ(policy.name_space, kAsyncNamespace);
   EXPECT_FALSE(isTemporary(policy));
   ExpectDownloadSupport(kAsyncNamespace, true);
@@ -164,7 +141,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckCCTDefined) {
-  OfflinePageClientPolicy policy = controller()->GetPolicy(kCCTNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kCCTNamespace);
   EXPECT_EQ(policy.name_space, kCCTNamespace);
   EXPECT_TRUE(isTemporary(policy));
   ExpectTemporary(kCCTNamespace);
@@ -174,7 +151,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckDownloadDefined) {
-  OfflinePageClientPolicy policy = controller()->GetPolicy(kDownloadNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kDownloadNamespace);
   EXPECT_EQ(policy.name_space, kDownloadNamespace);
   EXPECT_FALSE(isTemporary(policy));
   ExpectDownloadSupport(kDownloadNamespace, true);
@@ -184,8 +161,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckNTPSuggestionsDefined) {
-  OfflinePageClientPolicy policy =
-      controller()->GetPolicy(kNTPSuggestionsNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kNTPSuggestionsNamespace);
   EXPECT_EQ(policy.name_space, kNTPSuggestionsNamespace);
   EXPECT_FALSE(isTemporary(policy));
   ExpectDownloadSupport(kNTPSuggestionsNamespace, true);
@@ -195,8 +171,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckSuggestedArticlesDefined) {
-  OfflinePageClientPolicy policy =
-      controller()->GetPolicy(kSuggestedArticlesNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kSuggestedArticlesNamespace);
   EXPECT_EQ(policy.name_space, kSuggestedArticlesNamespace);
   EXPECT_TRUE(isTemporary(policy));
   ExpectTemporary(kSuggestedArticlesNamespace);
@@ -206,8 +181,7 @@
 }
 
 TEST_F(ClientPolicyControllerTest, CheckLivePageSharingDefined) {
-  OfflinePageClientPolicy policy =
-      controller()->GetPolicy(kLivePageSharingNamespace);
+  OfflinePageClientPolicy policy = GetPolicy(kLivePageSharingNamespace);
   EXPECT_EQ(policy.name_space, kLivePageSharingNamespace);
   EXPECT_TRUE(isTemporary(policy));
   ExpectTemporary(kLivePageSharingNamespace);
@@ -217,9 +191,9 @@
 }
 
 TEST_F(ClientPolicyControllerTest, AllTemporaryNamespaces) {
-  std::vector<std::string> all_namespaces = controller()->GetAllNamespaces();
+  std::vector<std::string> all_namespaces = GetAllPolicyNamespaces();
   const std::vector<std::string>& cache_reset_namespaces_list =
-      controller()->GetTemporaryNamespaces();
+      GetTemporaryPolicyNamespaces();
   std::set<std::string> cache_reset_namespaces(
       cache_reset_namespaces_list.begin(), cache_reset_namespaces_list.end());
   for (auto name_space : cache_reset_namespaces) {
diff --git a/components/offline_pages/core/downloads/download_ui_adapter.cc b/components/offline_pages/core/downloads/download_ui_adapter.cc
index b4e23a8..4019831c 100644
--- a/components/offline_pages/core/downloads/download_ui_adapter.cc
+++ b/components/offline_pages/core/downloads/download_ui_adapter.cc
@@ -18,8 +18,8 @@
 #include "components/offline_pages/core/background/request_notifier.h"
 #include "components/offline_pages/core/background/save_page_request.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/downloads/offline_item_conversions.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_model.h"
 #include "components/offline_pages/core/page_criteria.h"
 #include "components/offline_pages/core/visuals_decoder.h"
@@ -35,20 +35,17 @@
 namespace {
 
 bool RequestsMatchesGuid(const std::string& guid,
-                         ClientPolicyController* policy_controller,
                          const SavePageRequest& request) {
   return request.client_id().id == guid &&
-         policy_controller->IsSupportedByDownload(
-             request.client_id().name_space);
+         GetPolicy(request.client_id().name_space).is_supported_by_download;
 }
 
 std::vector<int64_t> FilterRequestsByGuid(
     std::vector<std::unique_ptr<SavePageRequest>> requests,
-    const std::string& guid,
-    ClientPolicyController* policy_controller) {
+    const std::string& guid) {
   std::vector<int64_t> request_ids;
   for (const auto& request : requests) {
-    if (RequestsMatchesGuid(guid, policy_controller, *request))
+    if (RequestsMatchesGuid(guid, *request))
       request_ids.push_back(request->request_id());
   }
   return request_ids;
@@ -125,8 +122,8 @@
   if (!delegate_->IsVisibleInUI(added_page.client_id))
     return;
 
-  bool is_suggested = model->GetPolicyController()->IsSuggested(
-      added_page.client_id.name_space);
+  const bool is_suggested =
+      GetPolicy(added_page.client_id.name_space).is_suggested;
 
   OfflineItem offline_item(
       OfflineItemConversions::CreateOfflineItem(added_page, is_suggested));
@@ -357,10 +354,8 @@
   if (!page)
     return;
 
-  bool is_suggested =
-      model_->GetPolicyController()->IsSuggested(page->client_id.name_space);
-  auto offline_item =
-      OfflineItemConversions::CreateOfflineItem(*page, is_suggested);
+  auto offline_item = OfflineItemConversions::CreateOfflineItem(
+      *page, GetPolicy(page->client_id.name_space).is_suggested);
 
   offline_items_collection::UpdateDelta update_delta;
   update_delta.visuals_changed = true;
@@ -389,8 +384,8 @@
     const std::vector<OfflinePageItem>& pages) {
   if (!pages.empty()) {
     const OfflinePageItem* page = &pages[0];
-    bool is_suggested =
-        model_->GetPolicyController()->IsSuggested(page->client_id.name_space);
+    const bool is_suggested =
+        GetPolicy(page->client_id.name_space).is_suggested;
     base::ThreadTaskRunnerHandle::Get()->PostTask(
         FROM_HERE, base::BindOnce(std::move(callback),
                                   OfflineItemConversions::CreateOfflineItem(
@@ -430,8 +425,7 @@
   if (pages.empty())
     return;
   const OfflinePageItem* page = &pages[0];
-  bool is_suggested =
-      model_->GetPolicyController()->IsSuggested(page->client_id.name_space);
+  const bool is_suggested = GetPolicy(page->client_id.name_space).is_suggested;
   OfflineItem item =
       OfflineItemConversions::CreateOfflineItem(*page, is_suggested);
   delegate_->OpenItem(item, page->offline_id, location);
@@ -447,11 +441,7 @@
 }
 
 void DownloadUIAdapter::CancelDownload(const ContentId& id) {
-  auto predicate =
-      base::BindRepeating(&RequestsMatchesGuid, id.id,
-                          // Since RequestCoordinator is calling us back,
-                          // binding its policy controller is safe.
-                          request_coordinator_->GetPolicyController());
+  auto predicate = base::BindRepeating(&RequestsMatchesGuid, id.id);
   request_coordinator_->RemoveRequestsIf(predicate, base::DoNothing());
 }
 
@@ -466,8 +456,8 @@
 void DownloadUIAdapter::PauseDownloadContinuation(
     const std::string& guid,
     std::vector<std::unique_ptr<SavePageRequest>> requests) {
-  request_coordinator_->PauseRequests(FilterRequestsByGuid(
-      std::move(requests), guid, request_coordinator_->GetPolicyController()));
+  request_coordinator_->PauseRequests(
+      FilterRequestsByGuid(std::move(requests), guid));
 }
 
 void DownloadUIAdapter::ResumeDownload(const ContentId& id,
@@ -486,8 +476,8 @@
 void DownloadUIAdapter::ResumeDownloadContinuation(
     const std::string& guid,
     std::vector<std::unique_ptr<SavePageRequest>> requests) {
-  request_coordinator_->ResumeRequests(FilterRequestsByGuid(
-      std::move(requests), guid, request_coordinator_->GetPolicyController()));
+  request_coordinator_->ResumeRequests(
+      FilterRequestsByGuid(std::move(requests), guid));
 }
 
 void DownloadUIAdapter::OnOfflinePagesLoaded(
@@ -497,10 +487,8 @@
   for (const auto& page : pages) {
     if (delegate_->IsVisibleInUI(page.client_id)) {
       std::string guid = page.client_id.id;
-      bool is_suggested =
-          model_->GetPolicyController()->IsSuggested(page.client_id.name_space);
-      offline_items->push_back(
-          OfflineItemConversions::CreateOfflineItem(page, is_suggested));
+      offline_items->push_back(OfflineItemConversions::CreateOfflineItem(
+          page, GetPolicy(page.client_id.name_space).is_suggested));
     }
   }
   request_coordinator_->GetAllRequests(base::BindOnce(
diff --git a/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc b/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc
index 2c7ee95..80d7cbf6 100644
--- a/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc
+++ b/components/offline_pages/core/downloads/download_ui_adapter_unittest.cc
@@ -30,8 +30,8 @@
 #include "components/offline_pages/core/background/offliner_stub.h"
 #include "components/offline_pages/core/background/request_coordinator_stub_taco.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/downloads/offline_item_conversions.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/stub_offline_page_model.h"
 #include "components/offline_pages/core/visuals_decoder.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -116,9 +116,7 @@
 class MockOfflinePageModel : public StubOfflinePageModel {
  public:
   explicit MockOfflinePageModel(base::TestMockTimeTaskRunner* task_runner)
-      : observer_(nullptr),
-        task_runner_(task_runner),
-        policy_controller_(new ClientPolicyController()) {}
+      : observer_(nullptr), task_runner_(task_runner) {}
 
   ~MockOfflinePageModel() override {}
 
@@ -193,10 +191,9 @@
 
   void GetPagesWithCriteria(const PageCriteria& criteria,
                             MultipleOfflinePageItemCallback callback) override {
-    ClientPolicyController policy_controller;
     std::vector<OfflinePageItem> matches;
     for (const auto& page : pages) {
-      if (MeetsCriteria(policy_controller, criteria, page.second)) {
+      if (MeetsCriteria(criteria, page.second)) {
         matches.push_back(page.second);
       }
     }
@@ -209,18 +206,12 @@
     observer_->OfflinePageAdded(this, page);
   }
 
-  ClientPolicyController* GetPolicyController() override {
-    return policy_controller_.get();
-  }
-
   std::map<int64_t, OfflinePageItem> pages;
   std::unique_ptr<OfflinePageVisuals> visuals_by_offline_id_result;
 
  private:
   OfflinePageModel::Observer* observer_;
   base::TestMockTimeTaskRunner* task_runner_;
-  // Normally owned by OfflinePageModel.
-  std::unique_ptr<ClientPolicyController> policy_controller_;
 
   DISALLOW_COPY_AND_ASSIGN(MockOfflinePageModel);
 };
diff --git a/components/offline_pages/core/model/clear_storage_task.cc b/components/offline_pages/core/model/clear_storage_task.cc
index 2316fb8..a4820d6 100644
--- a/components/offline_pages/core/model/clear_storage_task.cc
+++ b/components/offline_pages/core/model/clear_storage_task.cc
@@ -17,7 +17,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/model/delete_page_task.h"
 #include "components/offline_pages/core/model/get_pages_task.h"
 #include "components/offline_pages/core/offline_page_client_policy.h"
@@ -42,18 +41,14 @@
 
 class PageClearCriteria {
  public:
-  PageClearCriteria(const ClientPolicyController* policy_controller,
-                    base::Time start_time,
+  PageClearCriteria(base::Time start_time,
                     const ArchiveManager::StorageStats& stats)
-      : policy_controller_(policy_controller),
-        start_time_(start_time),
-        stats_(stats) {}
+      : start_time_(start_time), stats_(stats) {}
 
   // Returns whether a page should be deleted.
   bool should_delete_item(const OfflinePageItem& page) {
     const std::string& name_space = page.client_id.name_space;
-    const OfflinePageClientPolicy& policy =
-        policy_controller_->GetPolicy(name_space);
+    const OfflinePageClientPolicy& policy = GetPolicy(name_space);
     const size_t page_limit = policy.page_limit;
     const base::TimeDelta expiration_period = policy.expiration_period;
 
@@ -96,7 +91,6 @@
   }
 
  private:
-  const ClientPolicyController* policy_controller_;
   base::Time start_time_;
   const ArchiveManager::StorageStats& stats_;
 
@@ -105,12 +99,11 @@
 };
 
 std::vector<OfflinePageItem> GetPagesToClear(
-    const ClientPolicyController* policy_controller,
     const base::Time& start_time,
     const ArchiveManager::StorageStats& stats,
     sql::Database* db) {
   std::map<std::string, size_t> namespace_page_count;
-  PageClearCriteria additional_criteria(policy_controller, start_time, stats);
+  PageClearCriteria additional_criteria(start_time, stats);
 
   PageCriteria criteria;
   criteria.lifetime_type = LifetimeType::TEMPORARY;
@@ -120,7 +113,7 @@
       base::BindRepeating(&PageClearCriteria::should_delete_item,
                           base::Unretained(&additional_criteria));
   GetPagesTask::ReadResult result =
-      GetPagesTask::ReadPagesWithCriteriaSync(policy_controller, criteria, db);
+      GetPagesTask::ReadPagesWithCriteriaSync(criteria, db);
 
   return std::move(result.pages);
 }
@@ -131,12 +124,11 @@
 }
 
 std::pair<size_t, DeletePageResult> ClearPagesSync(
-    ClientPolicyController* policy_controller,
     const base::Time& start_time,
     const ArchiveManager::StorageStats& stats,
     sql::Database* db) {
   std::vector<OfflinePageItem> pages_to_delete =
-      GetPagesToClear(policy_controller, start_time, stats, db);
+      GetPagesToClear(start_time, stats, db);
 
   size_t pages_cleared = 0;
   for (const OfflinePageItem& page : pages_to_delete) {
@@ -163,17 +155,14 @@
 
 ClearStorageTask::ClearStorageTask(OfflinePageMetadataStore* store,
                                    ArchiveManager* archive_manager,
-                                   ClientPolicyController* policy_controller,
                                    const base::Time& clearup_time,
                                    ClearStorageCallback callback)
     : store_(store),
       archive_manager_(archive_manager),
-      policy_controller_(policy_controller),
       callback_(std::move(callback)),
       clearup_time_(clearup_time) {
   DCHECK(store_);
   DCHECK(archive_manager_);
-  DCHECK(policy_controller_);
   DCHECK(!callback_.is_null());
 }
 
@@ -188,11 +177,10 @@
 
 void ClearStorageTask::OnGetStorageStatsDone(
     const ArchiveManager::StorageStats& stats) {
-  store_->Execute(
-      base::BindOnce(&ClearPagesSync, policy_controller_, clearup_time_, stats),
-      base::BindOnce(&ClearStorageTask::OnClearPagesDone,
-                     weak_ptr_factory_.GetWeakPtr()),
-      {0, DeletePageResult::STORE_FAILURE});
+  store_->Execute(base::BindOnce(&ClearPagesSync, clearup_time_, stats),
+                  base::BindOnce(&ClearStorageTask::OnClearPagesDone,
+                                 weak_ptr_factory_.GetWeakPtr()),
+                  {0, DeletePageResult::STORE_FAILURE});
 }
 
 void ClearStorageTask::OnClearPagesDone(
diff --git a/components/offline_pages/core/model/clear_storage_task.h b/components/offline_pages/core/model/clear_storage_task.h
index 8e6aa2ba..f67f6f9 100644
--- a/components/offline_pages/core/model/clear_storage_task.h
+++ b/components/offline_pages/core/model/clear_storage_task.h
@@ -19,7 +19,6 @@
 
 namespace offline_pages {
 
-class ClientPolicyController;
 class OfflinePageMetadataStore;
 
 // This task is responsible for clearing expired temporary pages from metadata
@@ -47,7 +46,6 @@
 
   ClearStorageTask(OfflinePageMetadataStore* store,
                    ArchiveManager* archive_manager,
-                   ClientPolicyController* policy_controller,
                    const base::Time& clearup_time,
                    ClearStorageCallback callback);
   ~ClearStorageTask() override;
@@ -65,9 +63,6 @@
   // The archive manager owning the archive directories to delete pages from.
   // Not owned.
   ArchiveManager* archive_manager_;
-  // The policy controller which is used to determine if a page needs to be
-  // cleared. Not owned.
-  ClientPolicyController* policy_controller_;
   ClearStorageCallback callback_;
   base::Time clearup_time_;
 
diff --git a/components/offline_pages/core/model/clear_storage_task_unittest.cc b/components/offline_pages/core/model/clear_storage_task_unittest.cc
index 3392c91..7f48904 100644
--- a/components/offline_pages/core/model/clear_storage_task_unittest.cc
+++ b/components/offline_pages/core/model/clear_storage_task_unittest.cc
@@ -18,6 +18,7 @@
 #include "components/offline_pages/core/client_namespace_constants.h"
 #include "components/offline_pages/core/model/model_task_test_base.h"
 #include "components/offline_pages/core/model/offline_page_test_utils.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/test_scoped_offline_clock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -148,12 +149,13 @@
   // during each test.
 
   // Make sure no persistent pages are marked as expired.
-  if (!policy_controller()->IsTemporary(setting.name_space))
+  const OfflinePageClientPolicy& policy = GetPolicy(setting.name_space);
+  if (policy.lifetime_type == LifetimeType::PERSISTENT)
     ASSERT_FALSE(setting.expired_page_count);
 
   generator()->SetCreationTime(clock()->Now());
   generator()->SetNamespace(setting.name_space);
-  if (policy_controller()->IsTemporary(setting.name_space)) {
+  if (policy.lifetime_type == LifetimeType::TEMPORARY) {
     generator()->SetArchiveDirectory(TemporaryDir());
   } else {
     generator()->SetArchiveDirectory(PrivateDir());
@@ -164,9 +166,7 @@
     AddPage();
   }
 
-  generator()->SetLastAccessTime(
-      clock_.Now() -
-      policy_controller()->GetPolicy(setting.name_space).expiration_period);
+  generator()->SetLastAccessTime(clock_.Now() - policy.expiration_period);
   for (int i = 0; i < setting.expired_page_count; ++i) {
     AddPage();
   }
@@ -174,7 +174,7 @@
 
 void ClearStorageTaskTest::RunClearStorageTask(const base::Time& start_time) {
   auto task = std::make_unique<ClearStorageTask>(
-      store(), archive_manager(), policy_controller(), start_time,
+      store(), archive_manager(), start_time,
       base::BindOnce(&ClearStorageTaskTest::OnClearStorageDone,
                      base::AsWeakPtr(this)));
 
@@ -263,12 +263,11 @@
 
   // Check preconditions, especially that last_n expiration is longer than
   // bookmark's.
-  OfflinePageClientPolicy bookmark_policy =
-      policy_controller()->GetPolicy(kBookmarkNamespace);
-  OfflinePageClientPolicy last_n_policy =
-      policy_controller()->GetPolicy(kLastNNamespace);
-  OfflinePageClientPolicy download_policy =
-      policy_controller()->GetPolicy(kDownloadNamespace);
+  const OfflinePageClientPolicy& bookmark_policy =
+      GetPolicy(kBookmarkNamespace);
+  const OfflinePageClientPolicy& last_n_policy = GetPolicy(kLastNNamespace);
+  const OfflinePageClientPolicy& download_policy =
+      GetPolicy(kDownloadNamespace);
   ASSERT_EQ(LifetimeType::TEMPORARY, bookmark_policy.lifetime_type);
   ASSERT_EQ(LifetimeType::TEMPORARY, last_n_policy.lifetime_type);
   ASSERT_EQ(LifetimeType::PERSISTENT, download_policy.lifetime_type);
diff --git a/components/offline_pages/core/model/delete_page_task.cc b/components/offline_pages/core/model/delete_page_task.cc
index d5d0120..26cfa9ee 100644
--- a/components/offline_pages/core/model/delete_page_task.cc
+++ b/components/offline_pages/core/model/delete_page_task.cc
@@ -14,7 +14,6 @@
 #include "base/metrics/histogram_functions.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/model/get_pages_task.h"
 #include "components/offline_pages/core/model/offline_page_model_utils.h"
 #include "components/offline_pages/core/offline_clock.h"
@@ -100,7 +99,6 @@
 }
 
 DeletePageTaskResult DeletePagesWithCriteria(
-    const ClientPolicyController* policy_controller,
     const PageCriteria& criteria,
     sql::Database* db) {
   // If you create a transaction but dont Commit() it is automatically
@@ -110,7 +108,7 @@
     return {DeletePageResult::STORE_FAILURE, {}};
 
   GetPagesTask::ReadResult read_result =
-      GetPagesTask::ReadPagesWithCriteriaSync(policy_controller, criteria, db);
+      GetPagesTask::ReadPagesWithCriteriaSync(criteria, db);
   if (!read_result.success)
     return {DeletePageResult::STORE_FAILURE, {}};
 
@@ -125,7 +123,6 @@
 // Deletes all but |limit| pages that match |criteria|, in the order specified
 // by |criteria|.
 DeletePageTaskResult DeletePagesForPageLimit(
-    const ClientPolicyController* policy_controller,
     const PageCriteria& criteria,
     size_t limit,
     sql::Database* db) {
@@ -143,7 +140,7 @@
     return {DeletePageResult::STORE_FAILURE, {}};
 
   GetPagesTask::ReadResult read_result =
-      GetPagesTask::ReadPagesWithCriteriaSync(policy_controller, criteria, db);
+      GetPagesTask::ReadPagesWithCriteriaSync(criteria, db);
   if (!read_result.success)
     return {DeletePageResult::STORE_FAILURE, {}};
 
@@ -165,12 +162,10 @@
 // static
 std::unique_ptr<DeletePageTask> DeletePageTask::CreateTaskWithCriteria(
     OfflinePageMetadataStore* store,
-    const ClientPolicyController& policy_controller,
     const PageCriteria& criteria,
     DeletePageTask::DeletePageTaskCallback callback) {
   return std::unique_ptr<DeletePageTask>(new DeletePageTask(
-      store,
-      base::BindOnce(&DeletePagesWithCriteria, &policy_controller, criteria),
+      store, base::BindOnce(&DeletePagesWithCriteria, criteria),
       std::move(callback)));
 }
 
@@ -178,7 +173,6 @@
 std::unique_ptr<DeletePageTask>
 DeletePageTask::CreateTaskMatchingUrlPredicateForCachedPages(
     OfflinePageMetadataStore* store,
-    const ClientPolicyController& policy_controller,
     DeletePageTask::DeletePageTaskCallback callback,
     const UrlPredicate& predicate) {
   PageCriteria criteria;
@@ -188,29 +182,25 @@
         return predicate.Run(item.url);
       },
       predicate);
-  return CreateTaskWithCriteria(store, policy_controller, criteria,
-                                std::move(callback));
+  return CreateTaskWithCriteria(store, criteria, std::move(callback));
 }
 
 // static
 std::unique_ptr<DeletePageTask> DeletePageTask::CreateTaskDeletingForPageLimit(
     OfflinePageMetadataStore* store,
-    const ClientPolicyController& policy_controller,
     DeletePageTask::DeletePageTaskCallback callback,
     const OfflinePageItem& page) {
   std::string name_space = page.client_id.name_space;
-  size_t limit = policy_controller.GetPolicy(name_space).pages_allowed_per_url;
+  size_t limit = GetPolicy(name_space).pages_allowed_per_url;
   PageCriteria criteria;
   criteria.url = page.url;
   criteria.client_namespaces = std::vector<std::string>{name_space};
   // Sorting is important here. DeletePagesForPageLimit will delete the results
   // in order, leaving only the last |limit| pages.
   criteria.result_order = PageCriteria::kAscendingAccessTime;
-  return base::WrapUnique(
-      new DeletePageTask(store,
-                         base::BindOnce(&DeletePagesForPageLimit,
-                                        &policy_controller, criteria, limit),
-                         std::move(callback)));
+  return base::WrapUnique(new DeletePageTask(
+      store, base::BindOnce(&DeletePagesForPageLimit, criteria, limit),
+      std::move(callback)));
 }
 
 DeletePageTask::DeletePageTask(OfflinePageMetadataStore* store,
diff --git a/components/offline_pages/core/model/delete_page_task.h b/components/offline_pages/core/model/delete_page_task.h
index b642f97..0ff39b4 100644
--- a/components/offline_pages/core/model/delete_page_task.h
+++ b/components/offline_pages/core/model/delete_page_task.h
@@ -39,7 +39,6 @@
 
   static std::unique_ptr<DeletePageTask> CreateTaskWithCriteria(
       OfflinePageMetadataStore* store,
-      const ClientPolicyController& policy_controller,
       const PageCriteria& criteria,
       DeletePageTask::DeletePageTaskCallback callback);
 
@@ -47,7 +46,6 @@
   static std::unique_ptr<DeletePageTask>
   CreateTaskMatchingUrlPredicateForCachedPages(
       OfflinePageMetadataStore* store,
-      const ClientPolicyController& policy_controller,
       DeletePageTask::DeletePageTaskCallback callback,
       const UrlPredicate& predicate);
 
@@ -57,7 +55,6 @@
   // Returns nullptr if there's no page limit per url of the page's namespace.
   static std::unique_ptr<DeletePageTask> CreateTaskDeletingForPageLimit(
       OfflinePageMetadataStore* store,
-      const ClientPolicyController& policy_controller,
       DeletePageTask::DeletePageTaskCallback callback,
       const OfflinePageItem& page);
 
diff --git a/components/offline_pages/core/model/delete_page_task_unittest.cc b/components/offline_pages/core/model/delete_page_task_unittest.cc
index 98a94d8..0c812ca 100644
--- a/components/offline_pages/core/model/delete_page_task_unittest.cc
+++ b/components/offline_pages/core/model/delete_page_task_unittest.cc
@@ -109,8 +109,8 @@
   // Run DeletePageTask for to delete the page.
   PageCriteria criteria;
   criteria.offline_ids = std::vector<int64_t>{page1.offline_id};
-  auto task = DeletePageTask::CreateTaskWithCriteria(
-      store(), *policy_controller(), criteria, delete_page_callback());
+  auto task = DeletePageTask::CreateTaskWithCriteria(store(), criteria,
+                                                     delete_page_callback());
   RunTask(std::move(task));
 
   EXPECT_EQ(DeletePageResult::SUCCESS, last_delete_page_result());
@@ -152,7 +152,7 @@
   });
 
   auto task = DeletePageTask::CreateTaskMatchingUrlPredicateForCachedPages(
-      store(), *policy_controller(), delete_page_callback(), predicate);
+      store(), delete_page_callback(), predicate);
   RunTask(std::move(task));
 
   EXPECT_EQ(DeletePageResult::SUCCESS, last_delete_page_result());
@@ -201,7 +201,7 @@
       base::BindRepeating([](const GURL& url) -> bool { return false; });
 
   auto task = DeletePageTask::CreateTaskMatchingUrlPredicateForCachedPages(
-      store(), *policy_controller(), delete_page_callback(), predicate);
+      store(), delete_page_callback(), predicate);
   RunTask(std::move(task));
 
   EXPECT_EQ(DeletePageResult::SUCCESS, last_delete_page_result());
@@ -243,7 +243,7 @@
   EXPECT_TRUE(base::PathExists(page3.file_path));
 
   auto task = DeletePageTask::CreateTaskDeletingForPageLimit(
-      store(), *policy_controller(), delete_page_callback(), page);
+      store(), delete_page_callback(), page);
   RunTask(std::move(task));
 
   EXPECT_EQ(DeletePageResult::SUCCESS, last_delete_page_result());
@@ -281,7 +281,7 @@
   EXPECT_TRUE(base::PathExists(page3.file_path));
 
   auto task = DeletePageTask::CreateTaskDeletingForPageLimit(
-      store(), *policy_controller(), delete_page_callback(), page);
+      store(), delete_page_callback(), page);
   RunTask(std::move(task));
 
   // Since there's no limit for page per url of Download Namespace, the result
diff --git a/components/offline_pages/core/model/get_pages_task.cc b/components/offline_pages/core/model/get_pages_task.cc
index b076df73..6a1aa03 100644
--- a/components/offline_pages/core/model/get_pages_task.cc
+++ b/components/offline_pages/core/model/get_pages_task.cc
@@ -12,7 +12,6 @@
 #include "base/files/file_path.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/string_number_conversions.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_item_utils.h"
 #include "components/offline_pages/core/offline_store_utils.h"
@@ -86,11 +85,9 @@
 GetPagesTask::ReadResult::~ReadResult() = default;
 
 GetPagesTask::GetPagesTask(OfflinePageMetadataStore* store,
-                           const ClientPolicyController* policy_controller,
                            const PageCriteria& criteria,
                            MultipleOfflinePageItemCallback callback)
     : store_(store),
-      policy_controller_(policy_controller),
       criteria_(criteria),
       callback_(std::move(callback)) {
   DCHECK(store_);
@@ -101,7 +98,7 @@
 
 void GetPagesTask::Run() {
   store_->Execute(base::BindOnce(&GetPagesTask::ReadPagesWithCriteriaSync,
-                                 policy_controller_, std::move(criteria_)),
+                                 std::move(criteria_)),
                   base::BindOnce(&GetPagesTask::CompleteWithResult,
                                  weak_ptr_factory_.GetWeakPtr()),
                   ReadResult());
@@ -129,7 +126,6 @@
 //   the db is loaded to memory, and disk access will likely dwarf any
 //   other query optimizations.
 ReadResult GetPagesTask::ReadPagesWithCriteriaSync(
-    const ClientPolicyController* policy_controller,
     const PageCriteria& criteria,
     sql::Database* db) {
   ReadResult result;
@@ -186,7 +182,7 @@
   // want to find, and then search that string for the row's namespace and
   // client_id respectively.
   std::vector<std::string> potential_namespaces =
-      PotentiallyMatchingNamespaces(*policy_controller, criteria);
+      PotentiallyMatchingNamespaces(criteria);
   if (!potential_namespaces.empty()) {
     statement.BindBool(param++, false);
     statement.BindString(param++, base::JoinString(potential_namespaces, ""));
@@ -239,12 +235,11 @@
   while (statement.Step()) {
     // Initially, read just the client ID to avoid creating the offline item
     // if it's filtered out.
-    if (!MeetsCriteria(*policy_controller, criteria,
-                       OfflinePageClientId(statement))) {
+    if (!MeetsCriteria(criteria, OfflinePageClientId(statement))) {
       continue;
     }
     OfflinePageItem item = MakeOfflinePageItem(statement);
-    if (!MeetsCriteria(*policy_controller, criteria, item))
+    if (!MeetsCriteria(criteria, item))
       continue;
 
     result.pages.push_back(std::move(item));
diff --git a/components/offline_pages/core/model/get_pages_task.h b/components/offline_pages/core/model/get_pages_task.h
index 47940151..4a49bb00 100644
--- a/components/offline_pages/core/model/get_pages_task.h
+++ b/components/offline_pages/core/model/get_pages_task.h
@@ -17,7 +17,6 @@
 #include "components/offline_pages/task/task.h"
 
 namespace offline_pages {
-class ClientPolicyController;
 
 // Gets offline pages that match the criteria.
 class GetPagesTask : public Task {
@@ -33,7 +32,6 @@
   };
 
   GetPagesTask(OfflinePageMetadataStore* store,
-               const ClientPolicyController* policy_controller,
                const PageCriteria& criteria,
                MultipleOfflinePageItemCallback callback);
 
@@ -46,7 +44,6 @@
   // from the database and should be called from within an
   // |SqlStoreBase::Execute()| call.
   static ReadResult ReadPagesWithCriteriaSync(
-      const ClientPolicyController* policy_controller,
       const PageCriteria& criteria,
       sql::Database* db);
 
@@ -54,7 +51,6 @@
   void CompleteWithResult(ReadResult result);
 
   OfflinePageMetadataStore* store_;
-  const ClientPolicyController* policy_controller_;
   PageCriteria criteria_;
   MultipleOfflinePageItemCallback callback_;
 
diff --git a/components/offline_pages/core/model/get_pages_task_unittest.cc b/components/offline_pages/core/model/get_pages_task_unittest.cc
index 1c3d3a6..5ead6fe5 100644
--- a/components/offline_pages/core/model/get_pages_task_unittest.cc
+++ b/components/offline_pages/core/model/get_pages_task_unittest.cc
@@ -41,7 +41,7 @@
 
   std::unique_ptr<GetPagesTask> CreateTask(const PageCriteria& criteria) {
     return std::make_unique<GetPagesTask>(
-        store(), &policy_controller_, criteria,
+        store(), criteria,
         base::BindOnce(&GetPagesTaskTest::OnGetPagesDone,
                        base::Unretained(this)));
   }
@@ -57,7 +57,6 @@
   }
 
  protected:
-  ClientPolicyController policy_controller_;
   std::set<OfflinePageItem> task_result_;
   std::vector<OfflinePageItem> ordered_task_result_;
 };
diff --git a/components/offline_pages/core/model/model_task_test_base.h b/components/offline_pages/core/model/model_task_test_base.h
index 69600f5f..09d8851 100644
--- a/components/offline_pages/core/model/model_task_test_base.h
+++ b/components/offline_pages/core/model/model_task_test_base.h
@@ -12,7 +12,6 @@
 #include "base/test/test_mock_time_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/offline_pages/core/archive_manager.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/model/offline_page_item_generator.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_metadata_store_test_util.h"
@@ -51,7 +50,6 @@
   OfflinePageMetadataStore* store() { return store_test_util_.store(); }
   OfflinePageItemGenerator* generator() { return &generator_; }
   ArchiveManager* archive_manager() { return archive_manager_.get(); }
-  ClientPolicyController* policy_controller() { return &policy_controller_; }
 
  private:
   OfflinePageMetadataStoreTestUtil store_test_util_;
@@ -60,7 +58,6 @@
   base::ScopedTempDir private_dir_;
   base::ScopedTempDir public_dir_;
   std::unique_ptr<ArchiveManager> archive_manager_;
-  ClientPolicyController policy_controller_;
 };
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.cc b/components/offline_pages/core/model/offline_page_model_taskified.cc
index 539deee..e1ecaf0 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.cc
+++ b/components/offline_pages/core/model/offline_page_model_taskified.cc
@@ -31,6 +31,7 @@
 #include "components/offline_pages/core/model/update_publish_id_task.h"
 #include "components/offline_pages/core/model/visuals_availability_task.h"
 #include "components/offline_pages/core/offline_clock.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_feature.h"
 #include "components/offline_pages/core/offline_page_metadata_store.h"
 #include "components/offline_pages/core/offline_page_model.h"
@@ -176,7 +177,6 @@
     : store_(std::move(store)),
       archive_manager_(std::move(archive_manager)),
       archive_publisher_(std::move(archive_publisher)),
-      policy_controller_(new ClientPolicyController()),
       task_queue_(this),
       skip_clearing_original_url_for_testing_(false),
       skip_maintenance_tasks_for_testing_(false),
@@ -262,7 +262,7 @@
     const PageCriteria& criteria,
     DeletePageCallback callback) {
   task_queue_.AddTask(DeletePageTask::CreateTaskWithCriteria(
-      store_.get(), *policy_controller_.get(), criteria,
+      store_.get(), criteria,
       base::BindOnce(&OfflinePageModelTaskified::OnDeleteDone,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))));
 }
@@ -271,7 +271,7 @@
     const UrlPredicate& predicate,
     DeletePageCallback callback) {
   auto task = DeletePageTask::CreateTaskMatchingUrlPredicateForCachedPages(
-      store_.get(), *policy_controller_.get(),
+      store_.get(),
       base::BindOnce(&OfflinePageModelTaskified::OnDeleteDone,
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)),
       predicate);
@@ -303,8 +303,8 @@
 void OfflinePageModelTaskified::GetPagesWithCriteria(
     const PageCriteria& criteria,
     MultipleOfflinePageItemCallback callback) {
-  task_queue_.AddTask(std::make_unique<GetPagesTask>(
-      store_.get(), policy_controller_.get(), criteria, std::move(callback)));
+  task_queue_.AddTask(std::make_unique<GetPagesTask>(store_.get(), criteria,
+                                                     std::move(callback)));
 }
 
 void OfflinePageModelTaskified::GetOfflineIdsForClientId(
@@ -351,7 +351,7 @@
 
 const base::FilePath& OfflinePageModelTaskified::GetInternalArchiveDirectory(
     const std::string& name_space) const {
-  if (policy_controller_->IsTemporary(name_space))
+  if (GetPolicy(name_space).lifetime_type == LifetimeType::TEMPORARY)
     return archive_manager_->GetTemporaryArchivesDir();
   return archive_manager_->GetPrivateArchivesDir();
 }
@@ -366,10 +366,6 @@
          archive_manager_->GetPrivateArchivesDir().IsParent(file_path);
 }
 
-ClientPolicyController* OfflinePageModelTaskified::GetPolicyController() {
-  return policy_controller_.get();
-}
-
 OfflineEventLogger* OfflinePageModelTaskified::GetLogger() {
   return &offline_event_logger_;
 }
@@ -435,7 +431,8 @@
     offline_page.original_url_if_different = save_page_params.original_url;
   }
 
-  if (policy_controller_->IsPersistent(offline_page.client_id.name_space)) {
+  if (GetPolicy(offline_page.client_id.name_space).lifetime_type ==
+      LifetimeType::PERSISTENT) {
     // If the user intentionally downloaded the page (aka it belongs to a
     // persistent namespace), move it to a public place.
     archive_publisher_->PublishArchive(
@@ -541,8 +538,8 @@
                                              successful_finish_time);
     // TODO(romax): Just keep the same with logic in OPMImpl (which was wrong).
     // This should be fixed once we have the new strategy for clearing pages.
-    if (policy_controller_->GetPolicy(page_attempted.client_id.name_space)
-            .pages_allowed_per_url != kUnlimitedPages) {
+    if (GetPolicy(page_attempted.client_id.name_space).pages_allowed_per_url !=
+        kUnlimitedPages) {
       RemovePagesMatchingUrlAndNamespace(page_attempted);
     }
     offline_event_logger_.RecordPageSaved(page_attempted.client_id.name_space,
@@ -642,20 +639,20 @@
   // reporting storage usage UMA.
   if (first_run) {
     task_queue_.AddTask(std::make_unique<StartupMaintenanceTask>(
-        store_.get(), archive_manager_.get(), policy_controller_.get()));
+        store_.get(), archive_manager_.get()));
 
     task_queue_.AddTask(std::make_unique<CleanupVisualsTask>(
         store_.get(), OfflineTimeNow(), base::DoNothing()));
   }
 
   task_queue_.AddTask(std::make_unique<ClearStorageTask>(
-      store_.get(), archive_manager_.get(), policy_controller_.get(), now,
+      store_.get(), archive_manager_.get(), now,
       base::BindOnce(&OfflinePageModelTaskified::OnClearCachedPagesDone,
                      weak_ptr_factory_.GetWeakPtr())));
 
   // TODO(https://crbug.com/834902) This might need a better execution plan.
   task_queue_.AddTask(std::make_unique<PersistentPageConsistencyCheckTask>(
-      store_.get(), archive_manager_.get(), policy_controller_.get(), now,
+      store_.get(), archive_manager_.get(), now,
       base::BindOnce(
           &OfflinePageModelTaskified::OnPersistentPageConsistencyCheckDone,
           weak_ptr_factory_.GetWeakPtr())));
@@ -680,7 +677,7 @@
 void OfflinePageModelTaskified::RemovePagesMatchingUrlAndNamespace(
     const OfflinePageItem& page) {
   auto task = DeletePageTask::CreateTaskDeletingForPageLimit(
-      store_.get(), *policy_controller_.get(),
+      store_.get(),
       base::BindOnce(&OfflinePageModelTaskified::OnDeleteDone,
                      weak_ptr_factory_.GetWeakPtr(),
                      base::DoNothing::Once<DeletePageResult>()),
diff --git a/components/offline_pages/core/model/offline_page_model_taskified.h b/components/offline_pages/core/model/offline_page_model_taskified.h
index 74ee8b4..ba8b37561 100644
--- a/components/offline_pages/core/model/offline_page_model_taskified.h
+++ b/components/offline_pages/core/model/offline_page_model_taskified.h
@@ -39,7 +39,7 @@
 struct OfflinePageItem;
 
 class ArchiveManager;
-class ClientPolicyController;
+class OfflinePageArchivePublisher;
 class OfflinePageArchiver;
 class OfflinePageMetadataStore;
 
@@ -105,7 +105,6 @@
   const base::FilePath& GetInternalArchiveDirectory(
       const std::string& name_space) const override;
   bool IsArchiveInInternalDir(const base::FilePath& file_path) const override;
-  ClientPolicyController* GetPolicyController() override;
   OfflineEventLogger* GetLogger() override;
   void PublishInternalArchive(
       const OfflinePageItem& offline_page,
@@ -200,9 +199,6 @@
   // Used for moving archives into public storage.
   std::unique_ptr<OfflinePageArchivePublisher> archive_publisher_;
 
-  // Controller of the client policies.
-  std::unique_ptr<ClientPolicyController> policy_controller_;
-
   // The observers.
   base::ObserverList<Observer>::Unchecked observers_;
 
diff --git a/components/offline_pages/core/model/persistent_page_consistency_check_task.cc b/components/offline_pages/core/model/persistent_page_consistency_check_task.cc
index c2b9d44..ed294f1 100644
--- a/components/offline_pages/core/model/persistent_page_consistency_check_task.cc
+++ b/components/offline_pages/core/model/persistent_page_consistency_check_task.cc
@@ -14,7 +14,6 @@
 #include "base/metrics/histogram_macros.h"
 #include "base/numerics/safe_conversions.h"
 #include "components/offline_pages/core/archive_manager.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/model/delete_page_task.h"
 #include "components/offline_pages/core/model/get_pages_task.h"
 #include "components/offline_pages/core/offline_page_client_policy.h"
@@ -36,13 +35,10 @@
 const base::TimeDelta kExpireThreshold = base::TimeDelta::FromDays(365);
 
 std::vector<OfflinePageItem> GetPersistentPages(
-    const ClientPolicyController* policy_controller,
     sql::Database* db) {
   PageCriteria criteria;
   criteria.lifetime_type = LifetimeType::PERSISTENT;
-  return std::move(
-      GetPagesTask::ReadPagesWithCriteriaSync(policy_controller, criteria, db)
-          .pages);
+  return std::move(GetPagesTask::ReadPagesWithCriteriaSync(criteria, db).pages);
 }
 
 bool SetItemsFileMissingTimeSync(const std::vector<int64_t>& item_ids,
@@ -78,7 +74,6 @@
     OfflinePageMetadataStore* store,
     const base::FilePath& private_dir,
     const base::FilePath& public_dir,
-    const ClientPolicyController* policy_controller,
     base::Time check_time,
     sql::Database* db) {
   std::vector<PublishedArchiveId> publish_ids_of_deleted_pages;
@@ -88,8 +83,7 @@
     return {SyncOperationResult::TRANSACTION_BEGIN_ERROR,
             publish_ids_of_deleted_pages};
 
-  std::vector<OfflinePageItem> persistent_page_infos =
-      GetPersistentPages(policy_controller, db);
+  std::vector<OfflinePageItem> persistent_page_infos = GetPersistentPages(db);
 
   std::vector<int64_t> pages_found_missing;
   std::vector<int64_t> pages_reappeared;
@@ -163,31 +157,28 @@
 PersistentPageConsistencyCheckTask::PersistentPageConsistencyCheckTask(
     OfflinePageMetadataStore* store,
     ArchiveManager* archive_manager,
-    ClientPolicyController* policy_controller,
     base::Time check_time,
     PersistentPageConsistencyCheckCallback callback)
     : store_(store),
       archive_manager_(archive_manager),
-      policy_controller_(policy_controller),
       check_time_(check_time),
       callback_(std::move(callback)) {
   DCHECK(store_);
   DCHECK(archive_manager_);
-  DCHECK(policy_controller_);
 }
 
 PersistentPageConsistencyCheckTask::~PersistentPageConsistencyCheckTask() =
     default;
 
 void PersistentPageConsistencyCheckTask::Run() {
-  store_->Execute(base::BindOnce(&PersistentPageConsistencyCheckSync, store_,
-                                 archive_manager_->GetPrivateArchivesDir(),
-                                 archive_manager_->GetPublicArchivesDir(),
-                                 policy_controller_, check_time_),
-                  base::BindOnce(&PersistentPageConsistencyCheckTask::
-                                     OnPersistentPageConsistencyCheckDone,
-                                 weak_ptr_factory_.GetWeakPtr()),
-                  CheckResult{SyncOperationResult::INVALID_DB_CONNECTION, {}});
+  store_->Execute(
+      base::BindOnce(&PersistentPageConsistencyCheckSync, store_,
+                     archive_manager_->GetPrivateArchivesDir(),
+                     archive_manager_->GetPublicArchivesDir(), check_time_),
+      base::BindOnce(&PersistentPageConsistencyCheckTask::
+                         OnPersistentPageConsistencyCheckDone,
+                     weak_ptr_factory_.GetWeakPtr()),
+      CheckResult{SyncOperationResult::INVALID_DB_CONNECTION, {}});
 }
 
 void PersistentPageConsistencyCheckTask::OnPersistentPageConsistencyCheckDone(
diff --git a/components/offline_pages/core/model/persistent_page_consistency_check_task.h b/components/offline_pages/core/model/persistent_page_consistency_check_task.h
index 9e3a006..051fca15 100644
--- a/components/offline_pages/core/model/persistent_page_consistency_check_task.h
+++ b/components/offline_pages/core/model/persistent_page_consistency_check_task.h
@@ -16,7 +16,6 @@
 namespace offline_pages {
 
 class ArchiveManager;
-class ClientPolicyController;
 class OfflinePageMetadataStore;
 
 // This task is responsible for checking consistency of persistent pages, mark
@@ -43,7 +42,6 @@
   PersistentPageConsistencyCheckTask(
       OfflinePageMetadataStore* store,
       ArchiveManager* archive_manager,
-      ClientPolicyController* policy_controller,
       base::Time check_time,
       PersistentPageConsistencyCheckCallback callback);
   ~PersistentPageConsistencyCheckTask() override;
@@ -58,9 +56,6 @@
   OfflinePageMetadataStore* store_;
   // The archive manager storing archive directories. Not owned.
   ArchiveManager* archive_manager_;
-  // The policy controller which is used to acquire names of namespaces. Not
-  // owned.
-  ClientPolicyController* policy_controller_;
   base::Time check_time_;
   // The callback for the task.
   PersistentPageConsistencyCheckCallback callback_;
diff --git a/components/offline_pages/core/model/persistent_page_consistency_check_task_unittest.cc b/components/offline_pages/core/model/persistent_page_consistency_check_task_unittest.cc
index d52c5540..a590207 100644
--- a/components/offline_pages/core/model/persistent_page_consistency_check_task_unittest.cc
+++ b/components/offline_pages/core/model/persistent_page_consistency_check_task_unittest.cc
@@ -92,8 +92,7 @@
               PublishedArchiveId{page5.system_download_id, page5.file_path})));
 
   RunTask(std::make_unique<PersistentPageConsistencyCheckTask>(
-      store(), archive_manager(), policy_controller(), base::Time::Now(),
-      callback.Get()));
+      store(), archive_manager(), base::Time::Now(), callback.Get()));
 
   EXPECT_EQ(4LL, store_test_util()->GetPageCount());
   EXPECT_EQ(1UL, test_utils::GetFileCountInDirectory(PrivateDir()));
@@ -146,8 +145,7 @@
                                 page.system_download_id, page.file_path})));
 
   RunTask(std::make_unique<PersistentPageConsistencyCheckTask>(
-      store(), archive_manager(), policy_controller(), base::Time::Now(),
-      callback.Get()));
+      store(), archive_manager(), base::Time::Now(), callback.Get()));
 
   EXPECT_FALSE(store_test_util()->GetPageByOfflineId(page.offline_id));
   histogram_tester()->ExpectUniqueSample(
diff --git a/components/offline_pages/core/model/startup_maintenance_task.cc b/components/offline_pages/core/model/startup_maintenance_task.cc
index 4d421ab..d95873b 100644
--- a/components/offline_pages/core/model/startup_maintenance_task.cc
+++ b/components/offline_pages/core/model/startup_maintenance_task.cc
@@ -17,7 +17,6 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/trace_event/trace_event.h"
 #include "components/offline_pages/core/archive_manager.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/model/delete_page_task.h"
 #include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_metadata_store.h"
@@ -88,8 +87,6 @@
 //   Delete the files, since they're 'headless' and has no way to be accessed.
 SyncOperationResult ClearLegacyPagesInPrivateDirSync(
     sql::Database* db,
-    const std::vector<std::string>& temporary_namespaces,
-    const std::vector<std::string>& persistent_namespaces,
     const base::FilePath& private_dir) {
   // One large database transaction that will:
   // 1. Get temporary page infos from the database.
@@ -107,9 +104,9 @@
     return SyncOperationResult::TRANSACTION_BEGIN_ERROR;
 
   std::vector<PageInfo> temporary_page_infos =
-      GetPageInfosByNamespaces(temporary_namespaces, db);
+      GetPageInfosByNamespaces(GetTemporaryPolicyNamespaces(), db);
   std::vector<PageInfo> persistent_page_infos =
-      GetPageInfosByNamespaces(persistent_namespaces, db);
+      GetPageInfosByNamespaces(GetPersistentPolicyNamespaces(), db);
   std::map<base::FilePath, PageInfo> path_to_page_info;
 
   std::set<base::FilePath> archive_paths = GetAllArchives(private_dir);
@@ -153,7 +150,6 @@
 
 SyncOperationResult CheckTemporaryPageConsistencySync(
     sql::Database* db,
-    const std::vector<std::string>& namespaces,
     const base::FilePath& archives_dir) {
   // One large database transaction that will:
   // 1. Get page infos by |namespaces| from the database.
@@ -163,7 +159,8 @@
   if (!transaction.Begin())
     return SyncOperationResult::TRANSACTION_BEGIN_ERROR;
 
-  std::vector<PageInfo> page_infos = GetPageInfosByNamespaces(namespaces, db);
+  std::vector<PageInfo> page_infos =
+      GetPageInfosByNamespaces(GetTemporaryPolicyNamespaces(), db);
 
   std::set<base::FilePath> page_info_paths;
   std::vector<int64_t> offline_ids_to_delete;
@@ -213,12 +210,11 @@
   return SyncOperationResult::SUCCESS;
 }
 
-void ReportStorageUsageSync(sql::Database* db,
-                            const std::vector<std::string>& namespaces) {
+void ReportStorageUsageSync(sql::Database* db) {
   static const char kSql[] =
       "SELECT sum(file_size) FROM " OFFLINE_PAGES_TABLE_NAME
       " WHERE client_namespace = ?";
-  for (const auto& name_space : namespaces) {
+  for (const auto& name_space : GetAllPolicyNamespaces()) {
     sql::Statement statement(db->GetCachedStatement(SQL_FROM_HERE, kSql));
     statement.BindString(0, name_space);
     int size_in_kib = 0;
@@ -232,43 +228,34 @@
 }
 
 bool StartupMaintenanceSync(
-    const std::vector<std::string>& persistent_namespaces,
-    const std::vector<std::string>& temporary_namespaces,
     const base::FilePath& temporary_archives_dir,
     const base::FilePath& private_archives_dir,
     sql::Database* db) {
   // Clear temporary pages that are in legacy directory, which is also the
   // directory that serves as the 'private' directory.
-  SyncOperationResult result = ClearLegacyPagesInPrivateDirSync(
-      db, temporary_namespaces, persistent_namespaces, private_archives_dir);
+  SyncOperationResult result =
+      ClearLegacyPagesInPrivateDirSync(db, private_archives_dir);
 
   // Clear temporary pages in cache directory.
-  result = CheckTemporaryPageConsistencySync(db, temporary_namespaces,
-                                             temporary_archives_dir);
+  result = CheckTemporaryPageConsistencySync(db, temporary_archives_dir);
   UMA_HISTOGRAM_ENUMERATION("OfflinePages.ConsistencyCheck.Temporary.Result",
                             result);
 
   // Report storage usage UMA, |temporary_namespaces| + |persistent_namespaces|
   // should be all namespaces. This is implicitly checked by the
   // TestReportStorageUsage unit test.
-  ReportStorageUsageSync(db, temporary_namespaces);
-  ReportStorageUsageSync(db, persistent_namespaces);
+  ReportStorageUsageSync(db);
 
   return true;
 }
 
 }  // namespace
 
-StartupMaintenanceTask::StartupMaintenanceTask(
-    OfflinePageMetadataStore* store,
-    ArchiveManager* archive_manager,
-    ClientPolicyController* policy_controller)
-    : store_(store),
-      archive_manager_(archive_manager),
-      policy_controller_(policy_controller) {
+StartupMaintenanceTask::StartupMaintenanceTask(OfflinePageMetadataStore* store,
+                                               ArchiveManager* archive_manager)
+    : store_(store), archive_manager_(archive_manager) {
   DCHECK(store_);
   DCHECK(archive_manager_);
-  DCHECK(policy_controller_);
 }
 
 StartupMaintenanceTask::~StartupMaintenanceTask() = default;
@@ -276,16 +263,8 @@
 void StartupMaintenanceTask::Run() {
   TRACE_EVENT_ASYNC_BEGIN0("offline_pages", "StartupMaintenanceTask running",
                            this);
-  std::vector<std::string> all_namespaces =
-      policy_controller_->GetAllNamespaces();
-  std::vector<std::string> temporary_namespaces =
-      policy_controller_->GetTemporaryNamespaces();
-  std::vector<std::string> persistent_namespaces =
-      policy_controller_->GetPersistentNamespaces();
-
   store_->Execute(
-      base::BindOnce(&StartupMaintenanceSync, persistent_namespaces,
-                     temporary_namespaces,
+      base::BindOnce(&StartupMaintenanceSync,
                      archive_manager_->GetTemporaryArchivesDir(),
                      archive_manager_->GetPrivateArchivesDir()),
       base::BindOnce(&StartupMaintenanceTask::OnStartupMaintenanceDone,
diff --git a/components/offline_pages/core/model/startup_maintenance_task.h b/components/offline_pages/core/model/startup_maintenance_task.h
index fe85315..bab8ca6 100644
--- a/components/offline_pages/core/model/startup_maintenance_task.h
+++ b/components/offline_pages/core/model/startup_maintenance_task.h
@@ -12,7 +12,6 @@
 namespace offline_pages {
 
 class ArchiveManager;
-class ClientPolicyController;
 class OfflinePageMetadataStore;
 
 // This task is responsible for executing maintenance sub-tasks during Chrome
@@ -21,8 +20,7 @@
 class StartupMaintenanceTask : public Task {
  public:
   StartupMaintenanceTask(OfflinePageMetadataStore* store,
-                         ArchiveManager* archive_manager,
-                         ClientPolicyController* policy_controller);
+                         ArchiveManager* archive_manager);
   ~StartupMaintenanceTask() override;
 
   // Task implementation:
@@ -35,9 +33,6 @@
   OfflinePageMetadataStore* store_;
   // The archive manager storing archive directories. Not owned.
   ArchiveManager* archive_manager_;
-  // The policy controller which is used to acquire names of namespaces. Not
-  // owned.
-  ClientPolicyController* policy_controller_;
 
   base::WeakPtrFactory<StartupMaintenanceTask> weak_ptr_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(StartupMaintenanceTask);
diff --git a/components/offline_pages/core/model/startup_maintenance_task_unittest.cc b/components/offline_pages/core/model/startup_maintenance_task_unittest.cc
index 8595cda..0b5f645 100644
--- a/components/offline_pages/core/model/startup_maintenance_task_unittest.cc
+++ b/components/offline_pages/core/model/startup_maintenance_task_unittest.cc
@@ -16,6 +16,7 @@
 #include "components/offline_pages/core/client_namespace_constants.h"
 #include "components/offline_pages/core/model/model_task_test_base.h"
 #include "components/offline_pages/core/model/offline_page_test_utils.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace offline_pages {
@@ -96,8 +97,8 @@
   EXPECT_EQ(2LL, store_test_util()->GetPageCount());
   EXPECT_EQ(4UL, test_utils::GetFileCountInDirectory(PrivateDir()));
 
-  auto task = std::make_unique<StartupMaintenanceTask>(
-      store(), archive_manager(), policy_controller());
+  auto task =
+      std::make_unique<StartupMaintenanceTask>(store(), archive_manager());
   RunTask(std::move(task));
 
   EXPECT_EQ(1LL, store_test_util()->GetPageCount());
@@ -141,8 +142,8 @@
   EXPECT_EQ(PagePresence::FILESYSTEM_ONLY, CheckPagePresence(temporary_page2));
   EXPECT_EQ(PagePresence::FILESYSTEM_ONLY, CheckPagePresence(persistent_page2));
 
-  auto task = std::make_unique<StartupMaintenanceTask>(
-      store(), archive_manager(), policy_controller());
+  auto task =
+      std::make_unique<StartupMaintenanceTask>(store(), archive_manager());
   RunTask(std::move(task));
 
   EXPECT_EQ(2LL, store_test_util()->GetPageCount());
@@ -195,8 +196,8 @@
   EXPECT_EQ(PagePresence::DB_ONLY, CheckPagePresence(temporary_page2));
   EXPECT_EQ(PagePresence::DB_ONLY, CheckPagePresence(persistent_page2));
 
-  auto task = std::make_unique<StartupMaintenanceTask>(
-      store(), archive_manager(), policy_controller());
+  auto task =
+      std::make_unique<StartupMaintenanceTask>(store(), archive_manager());
   RunTask(std::move(task));
 
   EXPECT_EQ(3LL, store_test_util()->GetPageCount());
@@ -251,8 +252,8 @@
   EXPECT_EQ(2UL, test_utils::GetFileCountInDirectory(TemporaryDir()));
   EXPECT_EQ(4UL, test_utils::GetFileCountInDirectory(PrivateDir()));
 
-  auto task = std::make_unique<StartupMaintenanceTask>(
-      store(), archive_manager(), policy_controller());
+  auto task =
+      std::make_unique<StartupMaintenanceTask>(store(), archive_manager());
   RunTask(std::move(task));
 
   EXPECT_EQ(2LL, store_test_util()->GetPageCount());
@@ -298,8 +299,8 @@
   EXPECT_EQ(0LL, store_test_util()->GetPageCount());
   EXPECT_EQ(2UL, test_utils::GetFileCountInDirectory(PublicDir()));
 
-  auto task = std::make_unique<StartupMaintenanceTask>(
-      store(), archive_manager(), policy_controller());
+  auto task =
+      std::make_unique<StartupMaintenanceTask>(store(), archive_manager());
   RunTask(std::move(task));
 
   EXPECT_EQ(0LL, store_test_util()->GetPageCount());
@@ -309,7 +310,7 @@
 
 TEST_F(StartupMaintenanceTaskTest, TestReportStorageUsage) {
   generator()->SetFileSize(kTestFileSize);
-  std::vector<std::string> namespaces = policy_controller()->GetAllNamespaces();
+  const std::vector<std::string>& namespaces = GetAllPolicyNamespaces();
 
   // Adding pages into each namespace.
   for (const auto& name_space : namespaces) {
@@ -317,7 +318,7 @@
     // correct directories, otherwise they might be cleaned based on consistency
     // check.
     generator()->SetNamespace(name_space);
-    if (policy_controller()->IsTemporary(name_space))
+    if (GetPolicy(name_space).lifetime_type == LifetimeType::TEMPORARY)
       generator()->SetArchiveDirectory(TemporaryDir());
     else
       generator()->SetArchiveDirectory(PrivateDir());
@@ -328,8 +329,8 @@
       AddPage();
   }
 
-  auto task = std::make_unique<StartupMaintenanceTask>(
-      store(), archive_manager(), policy_controller());
+  auto task =
+      std::make_unique<StartupMaintenanceTask>(store(), archive_manager());
   RunTask(std::move(task));
 
   // For each namespace, check if the storage usage was correctly reported,
diff --git a/components/offline_pages/core/offline_page_client_policy.cc b/components/offline_pages/core/offline_page_client_policy.cc
index 7038fbb..8849aac9 100644
--- a/components/offline_pages/core/offline_page_client_policy.cc
+++ b/components/offline_pages/core/offline_page_client_policy.cc
@@ -4,15 +4,168 @@
 
 #include "components/offline_pages/core/offline_page_client_policy.h"
 
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/time/time.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "components/offline_pages/core/offline_page_client_policy.h"
+
 namespace offline_pages {
 
+namespace {
+struct PolicyData {
+  std::map<std::string, OfflinePageClientPolicy> policies;
+  std::vector<std::string> temporary_namespaces;
+  std::vector<std::string> persistent_namespaces;
+  std::vector<std::string> all_namespaces;
+};
+
+PolicyData BuildPolicies() {
+  std::vector<OfflinePageClientPolicy> all_policies;
+  {
+    auto policy = OfflinePageClientPolicy::CreateTemporary(
+        kBookmarkNamespace, base::TimeDelta::FromDays(7));
+    policy.pages_allowed_per_url = 1;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreateTemporary(
+        kLastNNamespace, base::TimeDelta::FromDays(30));
+    policy.is_restricted_to_tab_from_client_id = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreatePersistent(kAsyncNamespace);
+    policy.is_supported_by_download = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreateTemporary(
+        kCCTNamespace, base::TimeDelta::FromDays(2));
+    policy.pages_allowed_per_url = 1;
+    policy.requires_specific_user_settings = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreatePersistent(kDownloadNamespace);
+    policy.is_supported_by_download = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy =
+        OfflinePageClientPolicy::CreatePersistent(kNTPSuggestionsNamespace);
+    policy.is_supported_by_download = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreateTemporary(
+        kSuggestedArticlesNamespace, base::TimeDelta::FromDays(30));
+    policy.is_supported_by_download = 1;
+    policy.is_suggested = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy =
+        OfflinePageClientPolicy::CreatePersistent(kBrowserActionsNamespace);
+    policy.is_supported_by_download = true;
+    policy.allows_conversion_to_background_file_download = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreateTemporary(
+        kLivePageSharingNamespace, base::TimeDelta::FromHours(1));
+    policy.pages_allowed_per_url = 1;
+    policy.is_restricted_to_tab_from_client_id = true;
+    all_policies.push_back(policy);
+  }
+  {
+    auto policy = OfflinePageClientPolicy::CreateTemporary(
+        kAutoAsyncNamespace, base::TimeDelta::FromDays(30));
+    policy.pages_allowed_per_url = 1;
+    policy.defer_background_fetch_while_page_is_active = true;
+    all_policies.push_back(policy);
+  }
+
+  // Fallback policy.
+  {
+    OfflinePageClientPolicy policy = OfflinePageClientPolicy::CreateTemporary(
+        kDefaultNamespace, base::TimeDelta::FromDays(1));
+    policy.page_limit = 10;
+    policy.pages_allowed_per_url = 1;
+    all_policies.push_back(policy);
+  }
+
+  PolicyData policy_data;
+  for (const auto& policy : all_policies) {
+    policy_data.all_namespaces.push_back(policy.name_space);
+    switch (policy.lifetime_type) {
+      case LifetimeType::TEMPORARY:
+        policy_data.temporary_namespaces.push_back(policy.name_space);
+        break;
+      case LifetimeType::PERSISTENT:
+        policy_data.persistent_namespaces.push_back(policy.name_space);
+        break;
+    }
+    policy_data.policies.emplace(policy.name_space, policy);
+  }
+
+  return policy_data;
+}
+
+const PolicyData& GetPolicyData() {
+  static base::NoDestructor<PolicyData> instance(BuildPolicies());
+  return *instance;
+}
+
+}  // namespace
+
 OfflinePageClientPolicy::OfflinePageClientPolicy(std::string namespace_val,
                                                  LifetimeType lifetime_type_val)
     : name_space(namespace_val), lifetime_type(lifetime_type_val) {}
 
+// static
+OfflinePageClientPolicy OfflinePageClientPolicy::CreateTemporary(
+    const std::string& name_space,
+    const base::TimeDelta& expiration_period) {
+  OfflinePageClientPolicy policy(name_space, LifetimeType::TEMPORARY);
+  policy.expiration_period = expiration_period;
+  return policy;
+}
+
+// static
+OfflinePageClientPolicy OfflinePageClientPolicy::CreatePersistent(
+    const std::string& name_space) {
+  return {name_space, LifetimeType::PERSISTENT};
+}
+
 OfflinePageClientPolicy::OfflinePageClientPolicy(
     const OfflinePageClientPolicy& other) = default;
 
 OfflinePageClientPolicy::~OfflinePageClientPolicy() = default;
 
+const OfflinePageClientPolicy& GetPolicy(const std::string& name) {
+  const std::map<std::string, OfflinePageClientPolicy>& policies =
+      GetPolicyData().policies;
+  const auto& iter = policies.find(name);
+  if (iter != policies.end())
+    return iter->second;
+  // Fallback when the namespace isn't defined.
+  return policies.at(kDefaultNamespace);
+}
+
+const std::vector<std::string>& GetAllPolicyNamespaces() {
+  return GetPolicyData().all_namespaces;
+}
+const std::vector<std::string>& GetTemporaryPolicyNamespaces() {
+  return GetPolicyData().temporary_namespaces;
+}
+const std::vector<std::string>& GetPersistentPolicyNamespaces() {
+  return GetPolicyData().persistent_namespaces;
+}
+
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/offline_page_client_policy.h b/components/offline_pages/core/offline_page_client_policy.h
index 3a06d7d..bf29dd10 100644
--- a/components/offline_pages/core/offline_page_client_policy.h
+++ b/components/offline_pages/core/offline_page_client_policy.h
@@ -8,6 +8,7 @@
 #include <stdint.h>
 
 #include <string>
+#include <vector>
 
 #include "base/time/time.h"
 
@@ -33,6 +34,12 @@
 struct OfflinePageClientPolicy {
   OfflinePageClientPolicy(std::string namespace_val,
                           LifetimeType lifetime_type_val);
+  static OfflinePageClientPolicy CreateTemporary(
+      const std::string& name_space,
+      const base::TimeDelta& expiration_period);
+  static OfflinePageClientPolicy CreatePersistent(
+      const std::string& name_space);
+
   OfflinePageClientPolicy(const OfflinePageClientPolicy& other);
   ~OfflinePageClientPolicy();
 
@@ -81,6 +88,15 @@
   bool defer_background_fetch_while_page_is_active = false;
 };
 
+// Get the client policy for |name_space|.
+const OfflinePageClientPolicy& GetPolicy(const std::string& name_space);
+// Returns a list of all known namespaces.
+const std::vector<std::string>& GetAllPolicyNamespaces();
+// Returns a list of all temporary namespaces.
+const std::vector<std::string>& GetTemporaryPolicyNamespaces();
+// Returns a list of all persistent namespaces.
+const std::vector<std::string>& GetPersistentPolicyNamespaces();
+
 }  // namespace offline_pages
 
 #endif  // COMPONENTS_OFFLINE_PAGES_CORE_OFFLINE_PAGE_CLIENT_POLICY_H_
diff --git a/components/offline_pages/core/offline_page_client_policy_unittest.cc b/components/offline_pages/core/offline_page_client_policy_unittest.cc
new file mode 100644
index 0000000..e7ab113f
--- /dev/null
+++ b/components/offline_pages/core/offline_page_client_policy_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/offline_pages/core/offline_page_client_policy.h"
+
+#include <algorithm>
+#include <memory>
+#include <set>
+
+#include "base/stl_util.h"
+#include "components/offline_pages/core/client_namespace_constants.h"
+#include "components/offline_pages/core/offline_page_feature.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace offline_pages {
+
+namespace {
+const char kUndefinedNamespace[] = "undefined";
+
+bool isTemporary(const OfflinePageClientPolicy& policy) {
+  return policy.lifetime_type == LifetimeType::TEMPORARY;
+}
+}  // namespace
+
+class ClientPolicyTest : public testing::Test {
+ public:
+  // testing::Test
+  void SetUp() override {}
+  void TearDown() override {}
+
+ protected:
+  void ExpectTemporary(std::string name_space);
+  void ExpectDownloadSupport(std::string name_space, bool expectation);
+  void ExpectPersistent(std::string name_space);
+  void ExpectRestrictedToTabFromClientId(std::string name_space,
+                                         bool expectation);
+  void ExpectRequiresSpecificUserSettings(std::string name_space,
+                                          bool expectation);
+};
+
+void ClientPolicyTest::ExpectTemporary(std::string name_space) {
+  EXPECT_TRUE(base::Contains(GetTemporaryPolicyNamespaces(), name_space))
+      << "Namespace " << name_space
+      << " had incorrect lifetime type when getting temporary namespaces.";
+  EXPECT_EQ(GetPolicy(name_space).lifetime_type, LifetimeType::TEMPORARY)
+      << "Namespace " << name_space
+      << " had incorrect lifetime type setting when directly checking"
+         " if it is temporary.";
+  EXPECT_FALSE(base::Contains(GetPersistentPolicyNamespaces(), name_space))
+      << "Namespace " << name_space
+      << " had incorrect lifetime type when getting persistent namespaces.";
+}
+
+void ClientPolicyTest::ExpectDownloadSupport(std::string name_space,
+                                             bool expectation) {
+  EXPECT_EQ(expectation, GetPolicy(name_space).is_supported_by_download)
+      << "Namespace " << name_space
+      << " had incorrect download support when directly checking if supported"
+         " by download.";
+}
+
+void ClientPolicyTest::ExpectPersistent(std::string name_space) {
+  EXPECT_FALSE(base::Contains(GetTemporaryPolicyNamespaces(), name_space))
+      << "Namespace " << name_space
+      << " had incorrect lifetime type when getting temporary namespaces.";
+  EXPECT_EQ(GetPolicy(name_space).lifetime_type, LifetimeType::PERSISTENT)
+      << "Namespace " << name_space
+      << " had incorrect lifetime type setting when directly checking"
+         " if it is temporary.";
+  EXPECT_TRUE(base::Contains(GetPersistentPolicyNamespaces(), name_space))
+      << "Namespace " << name_space
+      << " had incorrect lifetime type when getting persistent namespaces.";
+}
+
+void ClientPolicyTest::ExpectRestrictedToTabFromClientId(std::string name_space,
+                                                         bool expectation) {
+  EXPECT_EQ(expectation,
+            GetPolicy(name_space).is_restricted_to_tab_from_client_id)
+      << "Namespace " << name_space
+      << " had incorrect restriction when directly checking if the namespace"
+         " is restricted to the tab from the client id field";
+}
+
+void ClientPolicyTest::ExpectRequiresSpecificUserSettings(
+    std::string name_space,
+    bool expectation) {
+  EXPECT_EQ(expectation, GetPolicy(name_space).requires_specific_user_settings)
+      << "Namespace " << name_space
+      << " had incorrect download support when directly checking if disabled"
+         " when prefetch settings are disabled.";
+}
+
+TEST_F(ClientPolicyTest, FallbackTest) {
+  const OfflinePageClientPolicy& policy = GetPolicy(kUndefinedNamespace);
+  EXPECT_EQ(policy.name_space, kDefaultNamespace);
+  EXPECT_TRUE(isTemporary(policy));
+  ExpectTemporary(kDefaultNamespace);
+  EXPECT_FALSE(
+      base::Contains(GetTemporaryPolicyNamespaces(), kUndefinedNamespace));
+  EXPECT_EQ(GetPolicy(kUndefinedNamespace).lifetime_type,
+            LifetimeType::TEMPORARY);
+  ExpectDownloadSupport(kUndefinedNamespace, false);
+  ExpectDownloadSupport(kDefaultNamespace, false);
+  ExpectRestrictedToTabFromClientId(kUndefinedNamespace, false);
+  ExpectRestrictedToTabFromClientId(kDefaultNamespace, false);
+  ExpectRequiresSpecificUserSettings(kUndefinedNamespace, false);
+  ExpectRequiresSpecificUserSettings(kDefaultNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckBookmarkDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kBookmarkNamespace);
+  EXPECT_EQ(policy.name_space, kBookmarkNamespace);
+  EXPECT_TRUE(isTemporary(policy));
+  ExpectTemporary(kBookmarkNamespace);
+  ExpectDownloadSupport(kBookmarkNamespace, false);
+  ExpectRestrictedToTabFromClientId(kBookmarkNamespace, false);
+  ExpectRequiresSpecificUserSettings(kBookmarkNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckLastNDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kLastNNamespace);
+  EXPECT_EQ(policy.name_space, kLastNNamespace);
+  EXPECT_TRUE(isTemporary(policy));
+  ExpectTemporary(kLastNNamespace);
+  ExpectDownloadSupport(kLastNNamespace, false);
+  ExpectRestrictedToTabFromClientId(kLastNNamespace, true);
+  ExpectRequiresSpecificUserSettings(kLastNNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckAsyncDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kAsyncNamespace);
+  EXPECT_EQ(policy.name_space, kAsyncNamespace);
+  EXPECT_FALSE(isTemporary(policy));
+  ExpectDownloadSupport(kAsyncNamespace, true);
+  ExpectPersistent(kAsyncNamespace);
+  ExpectRestrictedToTabFromClientId(kAsyncNamespace, false);
+  ExpectRequiresSpecificUserSettings(kAsyncNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckCCTDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kCCTNamespace);
+  EXPECT_EQ(policy.name_space, kCCTNamespace);
+  EXPECT_TRUE(isTemporary(policy));
+  ExpectTemporary(kCCTNamespace);
+  ExpectDownloadSupport(kCCTNamespace, false);
+  ExpectRestrictedToTabFromClientId(kCCTNamespace, false);
+  ExpectRequiresSpecificUserSettings(kCCTNamespace, true);
+}
+
+TEST_F(ClientPolicyTest, CheckDownloadDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kDownloadNamespace);
+  EXPECT_EQ(policy.name_space, kDownloadNamespace);
+  EXPECT_FALSE(isTemporary(policy));
+  ExpectDownloadSupport(kDownloadNamespace, true);
+  ExpectPersistent(kDownloadNamespace);
+  ExpectRestrictedToTabFromClientId(kDownloadNamespace, false);
+  ExpectRequiresSpecificUserSettings(kDownloadNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckNTPSuggestionsDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kNTPSuggestionsNamespace);
+  EXPECT_EQ(policy.name_space, kNTPSuggestionsNamespace);
+  EXPECT_FALSE(isTemporary(policy));
+  ExpectDownloadSupport(kNTPSuggestionsNamespace, true);
+  ExpectPersistent(kNTPSuggestionsNamespace);
+  ExpectRestrictedToTabFromClientId(kNTPSuggestionsNamespace, false);
+  ExpectRequiresSpecificUserSettings(kNTPSuggestionsNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckSuggestedArticlesDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kSuggestedArticlesNamespace);
+  EXPECT_EQ(policy.name_space, kSuggestedArticlesNamespace);
+  EXPECT_TRUE(isTemporary(policy));
+  ExpectTemporary(kSuggestedArticlesNamespace);
+  ExpectDownloadSupport(kSuggestedArticlesNamespace, IsOfflinePagesEnabled());
+  ExpectRestrictedToTabFromClientId(kSuggestedArticlesNamespace, false);
+  ExpectRequiresSpecificUserSettings(kSuggestedArticlesNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, CheckLivePageSharingDefined) {
+  OfflinePageClientPolicy policy = GetPolicy(kLivePageSharingNamespace);
+  EXPECT_EQ(policy.name_space, kLivePageSharingNamespace);
+  EXPECT_TRUE(isTemporary(policy));
+  ExpectTemporary(kLivePageSharingNamespace);
+  ExpectDownloadSupport(kLivePageSharingNamespace, false);
+  ExpectRestrictedToTabFromClientId(kLivePageSharingNamespace, true);
+  ExpectRequiresSpecificUserSettings(kLivePageSharingNamespace, false);
+}
+
+TEST_F(ClientPolicyTest, AllTemporaryNamespaces) {
+  std::vector<std::string> all_namespaces = GetAllPolicyNamespaces();
+  const std::vector<std::string>& cache_reset_namespaces_list =
+      GetTemporaryPolicyNamespaces();
+  std::set<std::string> cache_reset_namespaces(
+      cache_reset_namespaces_list.begin(), cache_reset_namespaces_list.end());
+  for (auto name_space : cache_reset_namespaces) {
+    if (cache_reset_namespaces.count(name_space) > 0)
+      ExpectTemporary(name_space);
+    else
+      ExpectPersistent(name_space);
+  }
+}
+
+}  // namespace offline_pages
diff --git a/components/offline_pages/core/offline_page_metadata_store_test_util.cc b/components/offline_pages/core/offline_page_metadata_store_test_util.cc
index 2b67495..b121417 100644
--- a/components/offline_pages/core/offline_page_metadata_store_test_util.cc
+++ b/components/offline_pages/core/offline_page_metadata_store_test_util.cc
@@ -94,7 +94,7 @@
   criteria.offline_ids = std::vector<int64_t>{offline_id};
   OfflinePageItem* page = nullptr;
   auto task = std::make_unique<GetPagesTask>(
-      store(), nullptr, criteria,
+      store(), criteria,
       base::BindOnce(
           [](OfflinePageItem** out_page,
              const std::vector<OfflinePageItem>& cb_pages) {
diff --git a/components/offline_pages/core/offline_page_model.h b/components/offline_pages/core/offline_page_model.h
index 8a1f1d87..86c8e5a 100644
--- a/components/offline_pages/core/offline_page_model.h
+++ b/components/offline_pages/core/offline_page_model.h
@@ -14,7 +14,6 @@
 
 #include "base/supports_user_data.h"
 #include "components/keyed_service/core/keyed_service.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_event_logger.h"
 #include "components/offline_pages/core/offline_page_archive_publisher.h"
 #include "components/offline_pages/core/offline_page_archiver.h"
@@ -191,9 +190,6 @@
       const OfflinePageItem& offline_page,
       PublishPageCallback publish_done_callback) = 0;
 
-  // Returns the policy controller.
-  virtual ClientPolicyController* GetPolicyController() = 0;
-
   // Get the archive directory based on client policy of the namespace.
   virtual const base::FilePath& GetInternalArchiveDirectory(
       const std::string& name_space) const = 0;
diff --git a/components/offline_pages/core/page_criteria.cc b/components/offline_pages/core/page_criteria.cc
index 11f64128..b1dd8ca2 100644
--- a/components/offline_pages/core/page_criteria.cc
+++ b/components/offline_pages/core/page_criteria.cc
@@ -6,7 +6,6 @@
 
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_page_client_policy.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "components/offline_pages/core/offline_page_item_utils.h"
@@ -18,9 +17,7 @@
 PageCriteria::PageCriteria(const PageCriteria&) = default;
 PageCriteria::PageCriteria(PageCriteria&&) = default;
 
-bool MeetsCriteria(const ClientPolicyController& policy_controller,
-                   const PageCriteria& criteria,
-                   const ClientId& client_id) {
+bool MeetsCriteria(const PageCriteria& criteria, const ClientId& client_id) {
   if (criteria.client_ids &&
       !base::Contains(criteria.client_ids.value(), client_id)) {
     return false;
@@ -32,12 +29,10 @@
   }
   if (!criteria.guid.empty() && client_id.id != criteria.guid)
     return false;
-  // Only fetches the policy if it will be needed allowing simpler criteria
-  // queries to not require a ClientPolicyController instance.
+  // Only fetches the policy if it will be needed.
   if (criteria.exclude_tab_bound_pages || criteria.pages_for_tab_id ||
       criteria.supported_by_downloads || criteria.lifetime_type) {
-    const OfflinePageClientPolicy& policy =
-        policy_controller.GetPolicy(client_id.name_space);
+    const OfflinePageClientPolicy& policy = GetPolicy(client_id.name_space);
     if (criteria.exclude_tab_bound_pages &&
         policy.is_restricted_to_tab_from_client_id) {
       return false;
@@ -61,10 +56,8 @@
   return true;
 }
 
-bool MeetsCriteria(const ClientPolicyController& policy_controller,
-                   const PageCriteria& criteria,
-                   const OfflinePageItem& item) {
-  if (!MeetsCriteria(policy_controller, criteria, item.client_id))
+bool MeetsCriteria(const PageCriteria& criteria, const OfflinePageItem& item) {
+  if (!MeetsCriteria(criteria, item.client_id))
     return false;
 
   if (criteria.file_size && item.file_size != criteria.file_size.value())
@@ -95,22 +88,20 @@
 }
 
 std::vector<std::string> PotentiallyMatchingNamespaces(
-    const ClientPolicyController& policy_controller,
     const PageCriteria& criteria) {
   std::vector<std::string> matching_namespaces;
   if (criteria.supported_by_downloads || criteria.lifetime_type) {
     std::vector<std::string> allowed_namespaces =
         criteria.client_namespaces ? criteria.client_namespaces.value()
-                                   : policy_controller.GetAllNamespaces();
+                                   : GetAllPolicyNamespaces();
     std::vector<std::string> filtered;
     for (const std::string& name_space : allowed_namespaces) {
-      if (criteria.supported_by_downloads &&
-          !policy_controller.IsSupportedByDownload(name_space)) {
+      const OfflinePageClientPolicy& policy = GetPolicy(name_space);
+      if (criteria.supported_by_downloads && !policy.is_supported_by_download) {
         continue;
       }
       if (criteria.lifetime_type &&
-          criteria.lifetime_type.value() !=
-              policy_controller.GetPolicy(name_space).lifetime_type) {
+          criteria.lifetime_type.value() != policy.lifetime_type) {
         continue;
       }
       matching_namespaces.push_back(name_space);
diff --git a/components/offline_pages/core/page_criteria.h b/components/offline_pages/core/page_criteria.h
index 4725917..df4b34d0 100644
--- a/components/offline_pages/core/page_criteria.h
+++ b/components/offline_pages/core/page_criteria.h
@@ -17,7 +17,6 @@
 #include "url/gurl.h"
 
 namespace offline_pages {
-class ClientPolicyController;
 struct OfflinePageItem;
 
 // Criteria for matching an offline page. The default |PageCriteria| matches
@@ -76,19 +75,14 @@
 
 // Returns true if an offline page with |client_id| could potentially match
 // |criteria|.
-bool MeetsCriteria(const ClientPolicyController& policy_controller,
-                   const PageCriteria& criteria,
-                   const ClientId& client_id);
+bool MeetsCriteria(const PageCriteria& criteria, const ClientId& client_id);
 
 // Returns whether |item| matches |criteria|.
-bool MeetsCriteria(const ClientPolicyController& policy_controller,
-                   const PageCriteria& criteria,
-                   const OfflinePageItem& item);
+bool MeetsCriteria(const PageCriteria& criteria, const OfflinePageItem& item);
 
 // Returns the list of offline page namespaces that could potentially match
 // Criteria. Returns an empty list if any namespace could match.
 std::vector<std::string> PotentiallyMatchingNamespaces(
-    const ClientPolicyController& policy_controller,
     const PageCriteria& criteria);
 
 }  // namespace offline_pages
diff --git a/components/offline_pages/core/page_criteria_unittest.cc b/components/offline_pages/core/page_criteria_unittest.cc
index d179e85..8db3774e2 100644
--- a/components/offline_pages/core/page_criteria_unittest.cc
+++ b/components/offline_pages/core/page_criteria_unittest.cc
@@ -6,7 +6,6 @@
 
 #include "base/bind.h"
 #include "components/offline_pages/core/client_namespace_constants.h"
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_page_item.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -25,8 +24,6 @@
 }
 
 class PageCriteriaTest : public testing::Test {
- protected:
-  ClientPolicyController policy_controller_;
 };
 
 TEST_F(PageCriteriaTest, MeetsCriteria_Url) {
@@ -36,13 +33,13 @@
   OfflinePageItem item;
 
   item.url = TestURLWithFragment();
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.url = TestURL();
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.url = OtherURL();
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_UrlWithFragment) {
@@ -52,13 +49,13 @@
   OfflinePageItem item;
 
   item.url = TestURLWithFragment();
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.url = TestURL();
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.url = OtherURL();
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_ExcludeTabBoundPages) {
@@ -67,13 +64,13 @@
 
   OfflinePageItem item;
   item.client_id.name_space = kLastNNamespace;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id.name_space = "";
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id.name_space = kDownloadNamespace;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_PagesForTabId) {
@@ -83,17 +80,17 @@
   OfflinePageItem item;
   item.client_id.id = "0";
   item.client_id.name_space = kLastNNamespace;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   // Namespace not restricted to tab.
   item.client_id.id = "1";
   item.client_id.name_space = kDownloadNamespace;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   // Different tab id.
   item.client_id.id = "1";
   item.client_id.name_space = kLastNNamespace;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_SupportedByDownloads) {
@@ -102,12 +99,12 @@
 
   OfflinePageItem item;
   item.client_id.name_space = kDownloadNamespace;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item.client_id));
 
   item.client_id.name_space = kLastNNamespace;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item.client_id));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_PersistentLifetime) {
@@ -116,12 +113,12 @@
 
   OfflinePageItem item;
   item.client_id.name_space = kDownloadNamespace;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item.client_id));
 
   item.client_id.name_space = kLastNNamespace;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item.client_id));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_TemporaryLifetime) {
@@ -130,12 +127,12 @@
 
   OfflinePageItem item;
   item.client_id.name_space = kLastNNamespace;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item.client_id));
 
   item.client_id.name_space = kDownloadNamespace;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item.client_id));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_FileSize) {
@@ -144,13 +141,13 @@
 
   OfflinePageItem item;
   item.file_size = 123;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.file_size = 124;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.file_size = 0;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_Digest) {
@@ -159,13 +156,13 @@
 
   OfflinePageItem item;
   item.digest = "abc";
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.digest = "";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.digest = "def";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_Namespaces) {
@@ -174,16 +171,16 @@
 
   OfflinePageItem item;
   item.client_id.name_space = "namespace1";
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item.client_id));
 
   item.client_id.name_space = "namespace2";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item.client_id));
 
   item.client_id.name_space = "";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item.client_id));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item.client_id));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_MultipleNamespaces) {
@@ -193,19 +190,19 @@
 
   OfflinePageItem item;
   item.client_id.name_space = "namespace1";
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id.name_space = "foobar1";
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id.name_space = "namespace";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id.name_space = "foobar";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id.name_space = "";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_ClientId) {
@@ -214,16 +211,16 @@
 
   OfflinePageItem item;
   item.client_id = ClientId("namespace1", "id");
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace2", "id");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace1", "id2");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId();
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_MultipleClientId) {
@@ -234,25 +231,25 @@
 
   OfflinePageItem item;
   item.client_id = ClientId("namespace1", "id");
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace2", "id");
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace3", "id3");
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace", "i");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace", "");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("name", "id");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace", "foo");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_Guid) {
@@ -261,16 +258,16 @@
 
   OfflinePageItem item;
   item.client_id = ClientId("namespace", "abc");
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace2", "abc");
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId("namespace", "abcd");
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.client_id = ClientId();
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_RequestOrigin) {
@@ -279,13 +276,13 @@
 
   OfflinePageItem item;
   item.request_origin = "abc";
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.request_origin = "abcd";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 
   item.request_origin = "";
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_OfflineId) {
@@ -294,10 +291,10 @@
 
   OfflinePageItem item;
   item.offline_id = 5;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.offline_id = 4;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 TEST_F(PageCriteriaTest, MeetsCriteria_AdditionalCriteria) {
@@ -307,10 +304,10 @@
 
   OfflinePageItem item;
   item.offline_id = 5;
-  EXPECT_TRUE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_TRUE(MeetsCriteria(criteria, item));
 
   item.offline_id = 4;
-  EXPECT_FALSE(MeetsCriteria(policy_controller_, criteria, item));
+  EXPECT_FALSE(MeetsCriteria(criteria, item));
 }
 
 }  // namespace
diff --git a/components/offline_pages/core/stub_offline_page_model.cc b/components/offline_pages/core/stub_offline_page_model.cc
index b90e2ca01e..7163dc2 100644
--- a/components/offline_pages/core/stub_offline_page_model.cc
+++ b/components/offline_pages/core/stub_offline_page_model.cc
@@ -64,9 +64,6 @@
   return archive_directory_.IsParent(file_path);
 }
 
-ClientPolicyController* StubOfflinePageModel::GetPolicyController() {
-  return &policy_controller_;
-}
 OfflineEventLogger* StubOfflinePageModel::GetLogger() {
   return nullptr;
 }
diff --git a/components/offline_pages/core/stub_offline_page_model.h b/components/offline_pages/core/stub_offline_page_model.h
index e076671f..05e207e8 100644
--- a/components/offline_pages/core/stub_offline_page_model.h
+++ b/components/offline_pages/core/stub_offline_page_model.h
@@ -10,7 +10,6 @@
 #include <string>
 #include <vector>
 
-#include "components/offline_pages/core/client_policy_controller.h"
 #include "components/offline_pages/core/offline_page_model.h"
 
 namespace offline_pages {
@@ -57,11 +56,9 @@
   const base::FilePath& GetInternalArchiveDirectory(
       const std::string& name_space) const override;
   bool IsArchiveInInternalDir(const base::FilePath& file_path) const override;
-  ClientPolicyController* GetPolicyController() override;
   OfflineEventLogger* GetLogger() override;
 
  private:
-  ClientPolicyController policy_controller_;
   std::vector<int64_t> offline_ids_;
   base::FilePath archive_directory_;
 };
diff --git a/components/safe_browsing/db/v4_database.cc b/components/safe_browsing/db/v4_database.cc
index 303da575..cad85de 100644
--- a/components/safe_browsing/db/v4_database.cc
+++ b/components/safe_browsing/db/v4_database.cc
@@ -69,9 +69,9 @@
   const scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner =
       base::ThreadTaskRunnerHandle::Get();
   db_task_runner->PostTask(
-      FROM_HERE, base::BindOnce(&V4Database::CreateOnTaskRunner, db_task_runner,
-                                base_path, list_infos, callback_task_runner,
-                                new_db_callback, TimeTicks::Now()));
+      FROM_HERE,
+      base::BindOnce(&V4Database::CreateOnTaskRunner, db_task_runner, base_path,
+                     list_infos, callback_task_runner, new_db_callback));
 }
 
 // static
@@ -80,8 +80,7 @@
     const base::FilePath& base_path,
     const ListInfos& list_infos,
     const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
-    NewDatabaseReadyCallback new_db_callback,
-    const TimeTicks create_start_time) {
+    NewDatabaseReadyCallback new_db_callback) {
   DCHECK(db_task_runner->RunsTasksInCurrentSequence());
 
   if (!g_store_factory.Get())
@@ -112,9 +111,6 @@
   // thread. This would unblock resource loads.
   callback_task_runner->PostTask(
       FROM_HERE, base::BindOnce(new_db_callback, std::move(v4_database)));
-
-  UMA_HISTOGRAM_TIMES("SafeBrowsing.V4DatabaseOpen.Time",
-                      TimeTicks::Now() - create_start_time);
 }
 
 // static
diff --git a/components/safe_browsing/db/v4_database.h b/components/safe_browsing/db/v4_database.h
index 459e744..2fe43952 100644
--- a/components/safe_browsing/db/v4_database.h
+++ b/components/safe_browsing/db/v4_database.h
@@ -198,8 +198,7 @@
       const base::FilePath& base_path,
       const ListInfos& list_infos,
       const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
-      NewDatabaseReadyCallback callback,
-      const base::TimeTicks create_start_time);
+      NewDatabaseReadyCallback callback);
 
   // Makes the passed |factory| the factory used to instantiate a V4Database.
   // Only for tests.
diff --git a/components/sync/driver/glue/sync_engine_backend.cc b/components/sync/driver/glue/sync_engine_backend.cc
index 6a043063..2539c36 100644
--- a/components/sync/driver/glue/sync_engine_backend.cc
+++ b/components/sync/driver/glue/sync_engine_backend.cc
@@ -348,8 +348,8 @@
     nigori_controller_ = std::make_unique<ModelTypeController>(
         NIGORI, std::make_unique<ForwardingModelTypeControllerDelegate>(
                     nigori_processor->GetControllerDelegate().get()));
-    sync_encryption_handler_ =
-        std::make_unique<NigoriSyncBridgeImpl>(std::move(nigori_processor));
+    sync_encryption_handler_ = std::make_unique<NigoriSyncBridgeImpl>(
+        std::move(nigori_processor), &encryptor_);
   } else {
     sync_encryption_handler_ = std::make_unique<SyncEncryptionHandlerImpl>(
         &user_share_, &encryptor_, params.restored_key_for_bootstrapping,
diff --git a/components/sync/nigori/cryptographer.h b/components/sync/nigori/cryptographer.h
index 2eea3102..49a38389 100644
--- a/components/sync/nigori/cryptographer.h
+++ b/components/sync/nigori/cryptographer.h
@@ -180,7 +180,8 @@
   std::string GetDefaultNigoriKeyName() const;
 
   // Returns a serialized sync_pb::NigoriKey version of current default
-  // encryption key.
+  // encryption key. Returns empty string if Cryptographer is not initialized
+  // or protobuf serialization error occurs.
   std::string GetDefaultNigoriKeyData() const;
 
   // Generates a new Nigori from |serialized_nigori_key|, and if successful
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.cc b/components/sync/nigori/nigori_sync_bridge_impl.cc
index 60b3f69..ca1fb10e 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl.cc
@@ -8,6 +8,7 @@
 
 #include "base/base64.h"
 #include "base/location.h"
+#include "components/sync/base/encryptor.h"
 #include "components/sync/base/passphrase_enums.h"
 #include "components/sync/base/sync_base_switches.h"
 #include "components/sync/base/time.h"
@@ -337,13 +338,39 @@
   specifics->set_encrypt_web_apps(encrypted_types.Has(WEB_APPS));
 }
 
+// Packs explicit passphrase key in order to persist it. Should be aligned with
+// Directory implementation (Cryptographer::GetBootstrapToken()) unless it is
+// removed. Returns empty string in case of errors.
+std::string PackExplicitPassphraseKey(const Encryptor& encryptor,
+                                      const Cryptographer& cryptographer) {
+  // Explicit passphrase key should always be default one.
+  std::string serialized_key = cryptographer.GetDefaultNigoriKeyData();
+  if (serialized_key.empty()) {
+    DLOG(ERROR) << "Failed to serialize explicit passphrase key.";
+    return std::string();
+  }
+
+  std::string encrypted_key;
+  if (!encryptor.EncryptString(serialized_key, &encrypted_key)) {
+    DLOG(ERROR) << "Failed to encrypt explicit passphrase key.";
+    return std::string();
+  }
+
+  std::string encoded_key;
+  base::Base64Encode(encrypted_key, &encoded_key);
+  return encoded_key;
+}
+
 }  // namespace
 
 NigoriSyncBridgeImpl::NigoriSyncBridgeImpl(
-    std::unique_ptr<NigoriLocalChangeProcessor> processor)
-    : processor_(std::move(processor)),
+    std::unique_ptr<NigoriLocalChangeProcessor> processor,
+    const Encryptor* encryptor)
+    : encryptor_(encryptor),
+      processor_(std::move(processor)),
       passphrase_type_(NigoriSpecifics::UNKNOWN),
       encrypt_everything_(false) {
+  DCHECK(encryptor);
   processor_->ModelReadyToSync(this, NigoriMetadataBatch());
 }
 
@@ -427,9 +454,9 @@
     observer.OnEncryptedTypesChanged(EncryptableUserTypes(),
                                      encrypt_everything_);
   }
+  MaybeNotifyBootstrapTokenUpdated();
   // OnLocalSetPassphraseEncryption() is intentionally not called here, because
   // it's needed only for the Directory implementation unit tests.
-  // TODO(crbug.com/922900): persist |passphrase| in corresponding storage.
   // TODO(crbug.com/922900): support SCRYPT key derivation method.
   NOTIMPLEMENTED();
 }
@@ -437,8 +464,8 @@
 void NigoriSyncBridgeImpl::SetDecryptionPassphrase(
     const std::string& passphrase) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  // |passphrase| should be a valid one already (verified by the UI part, using
-  // pending keys exposed by OnPassphraseRequired()).
+  // |passphrase| should be a valid one already (verified by SyncServiceCrypto,
+  // using pending keys exposed by OnPassphraseRequired()).
   DCHECK(!passphrase.empty());
   DCHECK(cryptographer_.has_pending_keys());
   KeyParams key_params = {GetKeyDerivationParamsForPendingKeys(), passphrase};
@@ -466,7 +493,7 @@
   for (auto& observer : observers_) {
     observer.OnPassphraseAccepted();
   }
-  // TODO(crbug.com/922900): persist |passphrase| in corresponding storage.
+  MaybeNotifyBootstrapTokenUpdated();
   // TODO(crbug.com/922900): we may need to rewrite encryption_keybag in Nigori
   // node in case we have some keys in |cryptographer_| which is not stored in
   // encryption_keybag yet.
@@ -831,4 +858,29 @@
   }
 }
 
+void NigoriSyncBridgeImpl::MaybeNotifyBootstrapTokenUpdated() const {
+  switch (passphrase_type_) {
+    case NigoriSpecifics::UNKNOWN:
+    case NigoriSpecifics::IMPLICIT_PASSPHRASE:
+      NOTREACHED();
+      return;
+    case NigoriSpecifics::KEYSTORE_PASSPHRASE:
+      // TODO(crbug.com/922900): notify about keystore bootstrap token updates.
+      NOTIMPLEMENTED();
+      return;
+    case NigoriSpecifics::FROZEN_IMPLICIT_PASSPHRASE:
+    case NigoriSpecifics::CUSTOM_PASSPHRASE:
+      // |packed_custom_passphrase_key| will be empty in case serialization or
+      // encryption error occurs.
+      std::string packed_custom_passphrase_key =
+          PackExplicitPassphraseKey(*encryptor_, cryptographer_);
+      if (!packed_custom_passphrase_key.empty()) {
+        for (auto& observer : observers_) {
+          observer.OnBootstrapTokenUpdated(packed_custom_passphrase_key,
+                                           PASSPHRASE_BOOTSTRAP_TOKEN);
+        }
+      }
+  }
+}
+
 }  // namespace syncer
diff --git a/components/sync/nigori/nigori_sync_bridge_impl.h b/components/sync/nigori/nigori_sync_bridge_impl.h
index 5765b08..f655a832 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl.h
+++ b/components/sync/nigori/nigori_sync_bridge_impl.h
@@ -24,6 +24,8 @@
 
 namespace syncer {
 
+class Encryptor;
+
 // USS implementation of SyncEncryptionHandler.
 // This class holds the current Nigori state and processes incoming changes and
 // queries:
@@ -36,8 +38,9 @@
                              public NigoriSyncBridge,
                              public SyncEncryptionHandler {
  public:
-  explicit NigoriSyncBridgeImpl(
-      std::unique_ptr<NigoriLocalChangeProcessor> processor);
+  // |encryptor| must be not null and must outlive this object.
+  NigoriSyncBridgeImpl(std::unique_ptr<NigoriLocalChangeProcessor> processor,
+                       const Encryptor* encryptor);
   ~NigoriSyncBridgeImpl() override;
 
   // SyncEncryptionHandler implementation.
@@ -90,6 +93,13 @@
   // |passphrase_type_| is an explicit passphrase.
   KeyDerivationParams GetKeyDerivationParamsForPendingKeys() const;
 
+  // Persists Nigori derived from explicit passphrase into preferences, in case
+  // error occurs during serialization/encryption, corresponding preference
+  // just won't be updated.
+  void MaybeNotifyBootstrapTokenUpdated() const;
+
+  const Encryptor* const encryptor_;
+
   const std::unique_ptr<NigoriLocalChangeProcessor> processor_;
 
   // Base64 encoded keystore keys. The last element is the current keystore
diff --git a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
index 5cc0301..d38e4a0 100644
--- a/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
+++ b/components/sync/nigori/nigori_sync_bridge_impl_unittest.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/base64.h"
+#include "components/sync/base/fake_encryptor.h"
 #include "components/sync/base/time.h"
 #include "components/sync/model/entity_data.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -152,7 +153,8 @@
     auto processor =
         std::make_unique<testing::NiceMock<MockNigoriLocalChangeProcessor>>();
     processor_ = processor.get();
-    bridge_ = std::make_unique<NigoriSyncBridgeImpl>(std::move(processor));
+    bridge_ = std::make_unique<NigoriSyncBridgeImpl>(std::move(processor),
+                                                     &encryptor_);
     bridge_->AddObserver(&observer_);
   }
 
@@ -238,6 +240,7 @@
   }
 
  private:
+  const FakeEncryptor encryptor_;
   std::unique_ptr<NigoriSyncBridgeImpl> bridge_;
   // Ownership transferred to |bridge_|.
   testing::NiceMock<MockNigoriLocalChangeProcessor>* processor_;
@@ -500,6 +503,8 @@
 
   EXPECT_CALL(*observer(), OnPassphraseAccepted());
   EXPECT_CALL(*observer(), OnCryptographerStateChanged(NotNull()));
+  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string()),
+                                                   PASSPHRASE_BOOTSTRAP_TOKEN));
   bridge()->SetDecryptionPassphrase(passphrase_key_params.password);
 
   const Cryptographer& cryptographer = bridge()->GetCryptographerForTesting();
@@ -537,6 +542,8 @@
   EXPECT_CALL(*observer(),
               OnPassphraseTypeChanged(PassphraseType::CUSTOM_PASSPHRASE,
                                       /*passphrase_time=*/NotNullTime()));
+  EXPECT_CALL(*observer(), OnBootstrapTokenUpdated(Ne(std::string()),
+                                                   PASSPHRASE_BOOTSTRAP_TOKEN));
   EXPECT_CALL(*processor(), Put(HasCustomPassphraseNigori()));
   bridge()->SetEncryptionPassphrase(kPassphraseKeyParams.password);
   EXPECT_THAT(bridge()->GetData(), HasCustomPassphraseNigori());
diff --git a/components/translate/ios/DEPS b/components/translate/ios/DEPS
index 4dd6307..a63307e7 100644
--- a/components/translate/ios/DEPS
+++ b/components/translate/ios/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
   "+ios/web/public",
+  "+ios/web/common",
   "+third_party/ocmock",
 ]
diff --git a/components/translate/ios/browser/BUILD.gn b/components/translate/ios/browser/BUILD.gn
index 04276a0..f5e3887c 100644
--- a/components/translate/ios/browser/BUILD.gn
+++ b/components/translate/ios/browser/BUILD.gn
@@ -30,6 +30,7 @@
     "//components/translate/core/common",
     "//components/translate/core/language_detection",
     "//ios/chrome/browser/metrics:ukm_url_recorder",
+    "//ios/web/common",
     "//ios/web/public",
     "//ios/web/public/deprecated",
     "//ios/web/public/js_messaging",
diff --git a/components/translate/ios/browser/language_detection_controller.mm b/components/translate/ios/browser/language_detection_controller.mm
index ac7b521..5ff1aa79 100644
--- a/components/translate/ios/browser/language_detection_controller.mm
+++ b/components/translate/ios/browser/language_detection_controller.mm
@@ -17,9 +17,9 @@
 #include "components/translate/core/language_detection/language_detection_util.h"
 #import "components/translate/ios/browser/js_language_detection_manager.h"
 #include "components/translate/ios/browser/string_clipping_util.h"
+#import "ios/web/common/url_scheme_util.h"
 #include "ios/web/public/js_messaging/web_frame.h"
 #import "ios/web/public/navigation/navigation_context.h"
-#import "ios/web/public/url_scheme_util.h"
 #include "ios/web/public/web_state/web_state.h"
 #include "net/http/http_response_headers.h"
 
diff --git a/components/viz/common/resources/resource_format_utils.cc b/components/viz/common/resources/resource_format_utils.cc
index 0da5887..1c0767d 100644
--- a/components/viz/common/resources/resource_format_utils.cc
+++ b/components/viz/common/resources/resource_format_utils.cc
@@ -435,7 +435,7 @@
     case RG_88:
       return VK_FORMAT_R8G8_UNORM;
     case RGBA_F16:
-      return VK_FORMAT_R16_SFLOAT;
+      return VK_FORMAT_R16G16B16A16_SFLOAT;
     case R16_EXT:
       return VK_FORMAT_R16_UNORM;
     case RGBX_8888:
diff --git a/components/viz/common/skia_helper.cc b/components/viz/common/skia_helper.cc
index f6413733..54ab3fa 100644
--- a/components/viz/common/skia_helper.cc
+++ b/components/viz/common/skia_helper.cc
@@ -4,6 +4,8 @@
 #include "components/viz/common/skia_helper.h"
 #include "base/trace_event/trace_event.h"
 #include "cc/base/math_util.h"
+#include "third_party/skia/include/effects/SkColorFilterImageFilter.h"
+#include "third_party/skia/include/effects/SkColorMatrix.h"
 #include "third_party/skia/include/effects/SkOverdrawColorFilter.h"
 #include "third_party/skia/include/gpu/GrBackendSurface.h"
 #include "third_party/skia/include/gpu/GrContext.h"
@@ -66,4 +68,11 @@
   return SkOverdrawColorFilter::Make(colors);
 }
 
+sk_sp<SkImageFilter> SkiaHelper::BuildOpacityFilter(float opacity) {
+  SkColorMatrix matrix;
+  matrix.setScale(1.f, 1.f, 1.f, opacity);
+  return SkColorFilterImageFilter::Make(SkColorFilters::Matrix(matrix),
+                                        nullptr);
+}
+
 }  // namespace viz
diff --git a/components/viz/common/skia_helper.h b/components/viz/common/skia_helper.h
index 8aefe8f..dc351b7 100644
--- a/components/viz/common/skia_helper.h
+++ b/components/viz/common/skia_helper.h
@@ -29,6 +29,8 @@
                                          bool flush);
 
   static sk_sp<SkColorFilter> MakeOverdrawColorFilter();
+
+  static sk_sp<SkImageFilter> BuildOpacityFilter(float opacity);
 };
 
 }  // namespace viz
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index 5c173202..e2c5be9 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -867,6 +867,8 @@
     const gfx::Transform& backdrop_filter_bounds_transform) {
   DCHECK(ShouldApplyBackdropFilters(params->backdrop_filters));
   DCHECK(params->backdrop_filter_quality);
+  DCHECK(!params->filters)
+      << "Filters should always be in a separate Effect node";
   const RenderPassDrawQuad* quad = params->quad;
   auto use_gr_context = ScopedUseGrContext::Create(this);
 
@@ -874,19 +876,8 @@
       (params->background_rect.top_right() - unclipped_rect.top_right()) +
       (params->background_rect.bottom_left() - unclipped_rect.bottom_left());
 
-  // Update the backdrop filter to include opacity.
-  cc::FilterOperations backdrop_filters_plus_opacity =
-      *params->backdrop_filters;
-  DCHECK(!params->filters)
-      << "Filters should always be in a separate Effect node";
-  if (quad->shared_quad_state->opacity < 1.0) {
-    backdrop_filters_plus_opacity.Append(
-        cc::FilterOperation::CreateOpacityFilter(
-            quad->shared_quad_state->opacity));
-  }
-
   auto paint_filter = cc::RenderSurfaceFilters::BuildImageFilter(
-      backdrop_filters_plus_opacity, gfx::SizeF(params->background_rect.size()),
+      *params->backdrop_filters, gfx::SizeF(params->background_rect.size()),
       gfx::Vector2dF(clipping_offset));
 
   // TODO(senorblanco): background filters should be moved to the
@@ -971,6 +962,12 @@
     surface->getCanvas()->resetMatrix();
   }
 
+  SkPaint paint;
+  // Paint the filtered backdrop image with opacity.
+  if (quad->shared_quad_state->opacity < 1.0) {
+    paint.setImageFilter(
+        SkiaHelper::BuildOpacityFilter(quad->shared_quad_state->opacity));
+  }
   // Apply the mask image, if present, to filtered backdrop content. Note that
   // this needs to be performed here, in addition to elsewhere, because of the
   // order of operations:
@@ -979,7 +976,6 @@
   //   2. Render the parent render pass (containing the "backdrop image" to be
   //      filtered).
   //   3. Run this code, to filter, and possibly mask, the backdrop image.
-  SkPaint paint;
   const SkImage* mask_image = nullptr;
   base::Optional<DisplayResourceProvider::ScopedReadLockSkImage>
       backdrop_image_lock;
diff --git a/components/viz/service/display/software_renderer.cc b/components/viz/service/display/software_renderer.cc
index 3709a55..914075ed 100644
--- a/components/viz/service/display/software_renderer.cc
+++ b/components/viz/service/display/software_renderer.cc
@@ -17,6 +17,7 @@
 #include "components/viz/common/quads/solid_color_draw_quad.h"
 #include "components/viz/common/quads/texture_draw_quad.h"
 #include "components/viz/common/quads/tile_draw_quad.h"
+#include "components/viz/common/skia_helper.h"
 #include "components/viz/common/viz_utils.h"
 #include "components/viz/service/display/output_surface.h"
 #include "components/viz/service/display/output_surface_frame.h"
@@ -803,21 +804,13 @@
       (unclipped_rect.top_right() - backdrop_rect.top_right()) +
       (backdrop_rect.bottom_left() - unclipped_rect.bottom_left());
 
-  // Update the backdrop filter to include opacity.
-  cc::FilterOperations backdrop_filters_plus_opacity = *backdrop_filters;
   DCHECK(!regular_filters)
       << "Filters should always be in a separate Effect node";
-  if (quad->shared_quad_state->opacity < 1.0) {
-    backdrop_filters_plus_opacity.Append(
-        cc::FilterOperation::CreateOpacityFilter(
-            quad->shared_quad_state->opacity));
-  }
-
   gfx::Rect bitmap_rect =
       gfx::Rect(0, 0, backdrop_bitmap.width(), backdrop_bitmap.height());
   sk_sp<SkImageFilter> filter =
       cc::RenderSurfaceFilters::BuildImageFilter(
-          backdrop_filters_plus_opacity,
+          *backdrop_filters,
           gfx::SizeF(bitmap_rect.width(), bitmap_rect.height()),
           clipping_offset)
           ->cached_sk_filter_;
@@ -844,9 +837,16 @@
     canvas.resetMatrix();
   }
 
+  // Paint the filtered backdrop image with opacity.
+  SkPaint paint;
+  if (quad->shared_quad_state->opacity < 1.0) {
+    paint.setImageFilter(
+        SkiaHelper::BuildOpacityFilter(quad->shared_quad_state->opacity));
+  }
+
   // Now paint the pre-filtered image onto the canvas.
   SkRect bitmap_skrect = RectToSkRect(bitmap_rect);
-  canvas.drawImageRect(filtered_image, bitmap_skrect, bitmap_skrect, nullptr);
+  canvas.drawImageRect(filtered_image, bitmap_skrect, bitmap_skrect, &paint);
 
   return SkImage::MakeFromBitmap(bitmap)->makeShader(
       content_tile_mode, content_tile_mode, &filter_backdrop_transform);
diff --git a/content/browser/background_sync/background_sync_manager.cc b/content/browser/background_sync/background_sync_manager.cc
index 0a7cb483..3850348e 100644
--- a/content/browser/background_sync/background_sync_manager.cc
+++ b/content/browser/background_sync/background_sync_manager.cc
@@ -277,6 +277,27 @@
                                     : BackgroundSyncType::PERIODIC;
 }
 
+std::string GetSyncEventName(const BackgroundSyncType sync_type) {
+  if (sync_type == BackgroundSyncType::ONE_SHOT)
+    return "sync";
+  else
+    return "periodicsync";
+}
+
+DevToolsBackgroundService GetDevToolsBackgroundService(
+    BackgroundSyncType sync_type) {
+  if (sync_type == BackgroundSyncType::ONE_SHOT)
+    return DevToolsBackgroundService::kBackgroundSync;
+  else
+    return DevToolsBackgroundService::kPeriodicBackgroundSync;
+}
+
+std::string GetDelayAsString(base::TimeDelta delay) {
+  if (delay.is_max())
+    return "infinite";
+  return base::NumberToString(delay.InMilliseconds());
+}
+
 std::string GetEventStatusString(blink::ServiceWorkerStatusCode status_code) {
   // The |status_code| is derived from blink::mojom::ServiceWorkerEventStatus.
   switch (status_code) {
@@ -603,6 +624,7 @@
     return;
   }
 
+  std::set<url::Origin> suspended_periodic_sync_origins;
   for (const std::pair<int64_t, std::string>& data : user_data) {
     BackgroundSyncRegistrationsProto registrations_proto;
     if (registrations_proto.ParseFromString(data.second)) {
@@ -638,6 +660,9 @@
         registration->set_num_attempts(registration_proto.num_attempts());
         registration->set_delay_until(
             base::Time::FromInternalValue(registration_proto.delay_until()));
+        if (registration->is_suspended()) {
+          suspended_periodic_sync_origins.insert(registration->origin());
+        }
         registration->set_resolved();
         if (registration_proto.has_max_attempts())
           registration->set_max_attempts(registration_proto.max_attempts());
@@ -649,6 +674,8 @@
 
   FireReadyEvents(BackgroundSyncType::ONE_SHOT, base::DoNothing::Once());
   FireReadyEvents(BackgroundSyncType::PERIODIC, base::DoNothing::Once());
+  proxy_.SendSuspendedPeriodicSyncOrigins(
+      std::move(suspended_periodic_sync_origins));
   base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(callback));
 }
 
@@ -862,6 +889,16 @@
     return;
   }
 
+  if (registration.sync_type() == BackgroundSyncType::PERIODIC &&
+      ShouldLogToDevTools(registration.sync_type())) {
+    devtools_context_->LogBackgroundServiceEventOnIO(
+        sw_registration_id, registration.origin(),
+        DevToolsBackgroundService::kPeriodicBackgroundSync,
+        /* event_name= */ "Got next event delay",
+        /* instance_id= */ registration.options()->tag,
+        {{"Next Attempt Delay (ms)", GetDelayAsString(delay)}});
+  }
+
   AddOrUpdateActiveRegistration(
       sw_registration_id,
       url::Origin::Create(sw_registration->scope().GetOrigin()), registration);
@@ -1109,9 +1146,20 @@
 
   BackgroundSyncRegistrations* registrations =
       &active_registrations_[registration_info.service_worker_registration_id];
+  const url::Origin& origin = registrations->origin;
 
   registrations->registration_map.erase(
       {registration_info.tag, registration_info.sync_type});
+
+  if (registration_info.sync_type == BackgroundSyncType::PERIODIC &&
+      ShouldLogToDevTools(registration_info.sync_type)) {
+    devtools_context_->LogBackgroundServiceEventOnIO(
+        registration_info.service_worker_registration_id, origin,
+        DevToolsBackgroundService::kPeriodicBackgroundSync,
+        /* event_name= */ "Unregistered periodicsync",
+        /* instance_id= */ registration_info.tag,
+        /* event_metadata= */ {});
+  }
 }
 
 void BackgroundSyncManager::AddOrUpdateActiveRegistration(
@@ -1129,12 +1177,16 @@
       ->registration_map[{sync_registration.options()->tag, sync_type}] =
       sync_registration;
 
-  if (ShouldLogToDevTools(sync_type)) {
+  if (ShouldLogToDevTools(sync_registration.sync_type())) {
+    std::map<std::string, std::string> event_metadata;
+    if (sync_registration.sync_type() == BackgroundSyncType::PERIODIC) {
+      event_metadata["minInterval"] =
+          base::NumberToString(sync_registration.options()->min_interval);
+    }
     devtools_context_->LogBackgroundServiceEventOnIO(
-        sw_registration_id, origin, DevToolsBackgroundService::kBackgroundSync,
-        /* event_name= */ "Registered sync",
-        /* instance_id= */ sync_registration.options()->tag,
-        /* event_metadata= */ {});
+        sw_registration_id, origin, GetDevToolsBackgroundService(sync_type),
+        /* event_name= */ "Registered " + GetSyncEventName(sync_type),
+        /* instance_id= */ sync_registration.options()->tag, event_metadata);
   }
 }
 
@@ -1235,7 +1287,15 @@
       base::BindOnce(&OnSyncEventFinished, active_version, request_id,
                      std::move(repeating_callback)));
 
-  // TODO(crbug.com/961238): Record Periodic Sync events for DevTools.
+  if (devtools_context_->IsRecording(
+          DevToolsBackgroundService::kPeriodicBackgroundSync)) {
+    devtools_context_->LogBackgroundServiceEventOnIO(
+        active_version->registration_id(), active_version->script_origin(),
+        DevToolsBackgroundService::kPeriodicBackgroundSync,
+        /* event_name= */ "Dispatched periodicsync event",
+        /* instance_id= */ tag,
+        /* event_metadata= */ {});
+  }
 }
 
 base::CancelableOnceClosure& BackgroundSyncManager::get_delayed_task(
@@ -1753,7 +1813,7 @@
     if (ShouldLogToDevTools(registration->sync_type())) {
       devtools_context_->LogBackgroundServiceEventOnIO(
           registration_info->service_worker_registration_id, origin,
-          DevToolsBackgroundService::kBackgroundSync,
+          GetDevToolsBackgroundService(registration->sync_type()),
           /* event_name= */ "Sync event reregistered",
           /* instance_id= */ registration_info->tag,
           /* event_metadata= */ {});
@@ -1765,17 +1825,20 @@
     registration->set_delay_until(GetDelayUntilAfterApplyingMinGapForOrigin(
         registration->origin(), delay));
 
+    std::string event_name = GetSyncEventName(registration->sync_type()) +
+                             (succeeded ? " event completed" : " event failed");
+    std::map<std::string, std::string> event_metadata = {
+        {"Next Attempt Delay (ms)", GetDelayAsString(delay)}};
+    if (!succeeded) {
+      event_metadata.emplace("Failure Reason",
+                             GetEventStatusString(status_code));
+    }
+
     if (ShouldLogToDevTools(registration->sync_type())) {
-      std::string delay_ms = delay.is_max()
-                                 ? "infinite"
-                                 : base::NumberToString(delay.InMilliseconds());
       devtools_context_->LogBackgroundServiceEventOnIO(
           registration_info->service_worker_registration_id, origin,
-          DevToolsBackgroundService::kBackgroundSync,
-          /* event_name= */ "Sync event failed",
-          /* instance_id= */ registration_info->tag,
-          {{"Next Attempt Delay (ms)", delay_ms},
-           {"Failure Reason", GetEventStatusString(status_code)}});
+          GetDevToolsBackgroundService(registration->sync_type()), event_name,
+          /* instance_id= */ registration_info->tag, event_metadata);
     }
   }
 
@@ -1786,7 +1849,7 @@
     if (ShouldLogToDevTools(registration->sync_type())) {
       devtools_context_->LogBackgroundServiceEventOnIO(
           registration_info->service_worker_registration_id, origin,
-          DevToolsBackgroundService::kBackgroundSync,
+          GetDevToolsBackgroundService(registration->sync_type()),
           /* event_name= */ "Sync completed",
           /* instance_id= */ registration_info->tag,
           {{"Status", GetEventStatusString(status_code)}});
@@ -1909,9 +1972,8 @@
 }
 
 bool BackgroundSyncManager::ShouldLogToDevTools(BackgroundSyncType sync_type) {
-  return sync_type == BackgroundSyncType::ONE_SHOT &&
-         devtools_context_->IsRecording(
-             DevToolsBackgroundService::kBackgroundSync);
+  return devtools_context_->IsRecording(
+      GetDevToolsBackgroundService(sync_type));
 }
 
 }  // namespace content
diff --git a/content/browser/background_sync/background_sync_manager.h b/content/browser/background_sync/background_sync_manager.h
index cd0ae24..398cf71 100644
--- a/content/browser/background_sync/background_sync_manager.h
+++ b/content/browser/background_sync/background_sync_manager.h
@@ -395,7 +395,7 @@
   void SetMaxSyncAttemptsImpl(int max_sync_attempts,
                               base::OnceClosure callback);
 
-  // Whether an event should be logged for debuggability.
+  // Whether an event should be logged for debuggability, for |sync_type|.
   bool ShouldLogToDevTools(blink::mojom::BackgroundSyncType sync_type);
 
   base::OnceClosure MakeEmptyCompletion();
diff --git a/content/browser/background_sync/background_sync_manager_unittest.cc b/content/browser/background_sync/background_sync_manager_unittest.cc
index abb2a6b..5674277 100644
--- a/content/browser/background_sync/background_sync_manager_unittest.cc
+++ b/content/browser/background_sync/background_sync_manager_unittest.cc
@@ -563,6 +563,13 @@
         blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected));
   }
 
+  void InitFailedPeriodicSyncEventTest() {
+    SetupForPeriodicSyncEvent(base::BindRepeating(
+        &BackgroundSyncManagerTest::DispatchPeriodicSyncStatusCallback,
+        base::Unretained(this),
+        blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected));
+  }
+
   void DispatchSyncDelayedCallback(
       scoped_refptr<ServiceWorkerVersion> active_version,
       ServiceWorkerVersion::StatusCallback callback) {
@@ -2133,6 +2140,56 @@
   }
 }
 
+TEST_F(BackgroundSyncManagerTest, EventsLoggedForPeriodicSyncRegistration) {
+  storage_partition_impl_->GetDevToolsBackgroundServicesContext()
+      ->StartRecording(devtools::proto::PERIODIC_BACKGROUND_SYNC);
+
+  SetMaxSyncAttemptsAndRestartManager(3);
+  InitFailedPeriodicSyncEventTest();
+
+  {
+    // We expect a "Registered" event, and a "GotDelay" event.
+    EXPECT_CALL(*this, OnEventReceived(_)).Times(2);
+    int thirteen_hours_ms = 13 * 60 * 60 * 1000;
+    sync_options_1_.min_interval = thirteen_hours_ms;
+
+    EXPECT_TRUE(Register(sync_options_1_));
+    EXPECT_TRUE(GetRegistration(sync_options_1_));
+    EXPECT_TRUE(
+        test_background_sync_manager()->IsDelayedTaskScheduledPeriodicSync());
+  }
+
+  test_clock_.Advance(
+      test_background_sync_manager()->delayed_periodic_sync_task_delta());
+  {
+    // Expect a "Fired" event. Dispatch is mocked out, so that event is not
+    // registered by this test.
+    EXPECT_CALL(*this, OnEventReceived(_)).Times(1);
+    test_background_sync_manager()->RunPeriodicSyncDelayedTask();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_TRUE(GetRegistration(sync_options_1_));
+  }
+
+  // The event should succeed now.
+  InitSyncEventTest();
+
+  test_clock_.Advance(
+      test_background_sync_manager()->delayed_periodic_sync_task_delta());
+  {
+    // Expect a "GotDelay" event.
+    EXPECT_CALL(*this, OnEventReceived(_)).Times(1);
+    test_background_sync_manager()->RunPeriodicSyncDelayedTask();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_TRUE(GetRegistration(sync_options_1_));
+  }
+
+  {
+    // Expect a call for "Unregister" event.
+    EXPECT_CALL(*this, OnEventReceived(_)).Times(1);
+    Unregister(sync_options_1_);
+  }
+}
+
 TEST_F(BackgroundSyncManagerTest, UkmRecordedAtCompletion) {
   InitSyncEventTest();
   {
diff --git a/content/browser/background_sync/background_sync_proxy.cc b/content/browser/background_sync/background_sync_proxy.cc
index 1ddaf7e..2fcd3e3 100644
--- a/content/browser/background_sync/background_sync_proxy.cc
+++ b/content/browser/background_sync/background_sync_proxy.cc
@@ -65,6 +65,18 @@
     controller->ScheduleBrowserWakeUp(sync_type);
   }
 
+  void SendSuspendedPeriodicSyncOrigins(
+      std::set<url::Origin> suspended_origins) {
+    DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    if (!browser_context())
+      return;
+
+    auto* controller = browser_context()->GetBackgroundSyncController();
+    DCHECK(controller);
+
+    controller->NoteSuspendedPeriodicSyncOrigins(std::move(suspended_origins));
+  }
+
  private:
   base::WeakPtr<BackgroundSyncProxy> io_parent_;
   scoped_refptr<ServiceWorkerContextWrapper> service_worker_context_;
@@ -95,4 +107,14 @@
                                           ui_core_weak_ptr_, sync_type));
 }
 
+void BackgroundSyncProxy::SendSuspendedPeriodicSyncOrigins(
+    std::set<url::Origin> suspended_origins) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::UI},
+      base::BindOnce(&Core::SendSuspendedPeriodicSyncOrigins, ui_core_weak_ptr_,
+                     std::move(suspended_origins)));
+}
+
 }  // namespace content
diff --git a/content/browser/background_sync/background_sync_proxy.h b/content/browser/background_sync/background_sync_proxy.h
index 01c72cd6..90fe84e 100644
--- a/content/browser/background_sync/background_sync_proxy.h
+++ b/content/browser/background_sync/background_sync_proxy.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_BACKGROUND_SYNC_BACKGROUND_SYNC_PROXY_H_
 
 #include <memory>
+#include <set>
 
 #include "base/callback.h"
 #include "base/memory/weak_ptr.h"
@@ -13,6 +14,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "third_party/blink/public/mojom/background_sync/background_sync.mojom.h"
+#include "url/origin.h"
 
 namespace content {
 
@@ -29,6 +31,8 @@
   ~BackgroundSyncProxy();
 
   void ScheduleBrowserWakeUp(blink::mojom::BackgroundSyncType sync_type);
+  void SendSuspendedPeriodicSyncOrigins(
+      std::set<url::Origin> suspended_origins);
 
  private:
   // Constructed on the IO thread, lives and dies on the UI thread.
diff --git a/content/browser/devtools/devtools_background_services.proto b/content/browser/devtools/devtools_background_services.proto
index 459f290..070e9c5 100644
--- a/content/browser/devtools/devtools_background_services.proto
+++ b/content/browser/devtools/devtools_background_services.proto
@@ -19,9 +19,10 @@
   PUSH_MESSAGING = 4;
   NOTIFICATIONS = 5;
   PAYMENT_HANDLER = 6;
+  PERIODIC_BACKGROUND_SYNC = 7;
 
   // Keep as last, must have the largest tag value.
-  COUNT = 7;
+  COUNT = 8;
 }
 
 // A proto for storing the background service event with common metadata for
diff --git a/content/browser/devtools/devtools_background_services_context_impl.cc b/content/browser/devtools/devtools_background_services_context_impl.cc
index d8c31d5..b0891f03 100644
--- a/content/browser/devtools/devtools_background_services_context_impl.cc
+++ b/content/browser/devtools/devtools_background_services_context_impl.cc
@@ -53,6 +53,8 @@
       return devtools::proto::NOTIFICATIONS;
     case DevToolsBackgroundService::kPaymentHandler:
       return devtools::proto::PAYMENT_HANDLER;
+    case DevToolsBackgroundService::kPeriodicBackgroundSync:
+      return devtools::proto::PERIODIC_BACKGROUND_SYNC;
   }
 }
 
diff --git a/content/browser/devtools/devtools_instrumentation.cc b/content/browser/devtools/devtools_instrumentation.cc
index 0e036011..7fce34ad 100644
--- a/content/browser/devtools/devtools_instrumentation.cc
+++ b/content/browser/devtools/devtools_instrumentation.cc
@@ -433,7 +433,7 @@
     int routing_id,
     const std::string& devtools_request_id,
     const net::CookieStatusList& request_cookie_list,
-    const std::vector<std::pair<std::string, std::string>>& request_headers) {
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& request_headers) {
   FrameTreeNode* ftn = GetFtnForNetworkRequest(process_id, routing_id);
   if (!ftn)
     return;
@@ -447,7 +447,7 @@
     int routing_id,
     const std::string& devtools_request_id,
     const net::CookieAndLineStatusList& response_cookie_list,
-    const std::vector<std::pair<std::string, std::string>>& response_headers,
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
     const base::Optional<std::string>& response_headers_text) {
   FrameTreeNode* ftn = GetFtnForNetworkRequest(process_id, routing_id);
   if (!ftn)
diff --git a/content/browser/devtools/devtools_instrumentation.h b/content/browser/devtools/devtools_instrumentation.h
index 51bfa6b..2207045 100644
--- a/content/browser/devtools/devtools_instrumentation.h
+++ b/content/browser/devtools/devtools_instrumentation.h
@@ -14,6 +14,7 @@
 #include "base/optional.h"
 #include "content/common/navigation_params.mojom.h"
 #include "content/public/browser/certificate_request_result_type.h"
+#include "services/network/public/mojom/network_service.mojom.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "third_party/blink/public/mojom/choosers/file_chooser.mojom.h"
 
@@ -118,13 +119,13 @@
     int routing_id,
     const std::string& devtools_request_id,
     const net::CookieStatusList& request_cookie_list,
-    const std::vector<std::pair<std::string, std::string>>& request_headers);
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& request_headers);
 void OnResponseReceivedExtraInfo(
     int process_id,
     int routing_id,
     const std::string& devtools_request_id,
     const net::CookieAndLineStatusList& response_cookie_list,
-    const std::vector<std::pair<std::string, std::string>>& response_headers,
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
     const base::Optional<std::string>& response_headers_text);
 
 std::vector<std::unique_ptr<NavigationThrottle>> CreateNavigationThrottles(
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 7ca7f3b..6e71433 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -695,6 +695,19 @@
   return Object::fromValue(headers_dict.get(), nullptr);
 }
 
+std::unique_ptr<Object> GetRawHeaders(
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& headers) {
+  std::unique_ptr<DictionaryValue> headers_dict(DictionaryValue::create());
+  for (const auto& header : headers) {
+    std::string value;
+    bool merge_with_another = headers_dict->getString(header->key, &value);
+    headers_dict->setString(header->key, merge_with_another
+                                             ? value + '\n' + header->value
+                                             : header->value);
+  }
+  return Object::fromValue(headers_dict.get(), nullptr);
+}
+
 String GetProtocol(const GURL& url, const network::ResourceResponseInfo& info) {
   std::string protocol = info.alpn_negotiated_protocol;
   if (protocol.empty() || protocol == "unknown") {
@@ -2454,26 +2467,26 @@
 void NetworkHandler::OnRequestWillBeSentExtraInfo(
     const std::string& devtools_request_id,
     const net::CookieStatusList& request_cookie_list,
-    const std::vector<std::pair<std::string, std::string>>& request_headers) {
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& request_headers) {
   if (!enabled_)
     return;
 
   frontend_->RequestWillBeSentExtraInfo(
       devtools_request_id, BuildProtocolBlockedCookies(request_cookie_list),
-      GetHeaders(request_headers));
+      GetRawHeaders(request_headers));
 }
 
 void NetworkHandler::OnResponseReceivedExtraInfo(
     const std::string& devtools_request_id,
     const net::CookieAndLineStatusList& response_cookie_list,
-    const std::vector<std::pair<std::string, std::string>>& response_headers,
+    const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
     const base::Optional<std::string>& response_headers_text) {
   if (!enabled_)
     return;
 
   frontend_->ResponseReceivedExtraInfo(
       devtools_request_id, BuildProtocolBlockedSetCookies(response_cookie_list),
-      GetHeaders(response_headers),
+      GetRawHeaders(response_headers),
       response_headers_text.has_value() ? response_headers_text.value()
                                         : Maybe<String>());
 }
diff --git a/content/browser/devtools/protocol/network_handler.h b/content/browser/devtools/protocol/network_handler.h
index a5b6a8b..d6c42bd2 100644
--- a/content/browser/devtools/protocol/network_handler.h
+++ b/content/browser/devtools/protocol/network_handler.h
@@ -191,11 +191,11 @@
   void OnRequestWillBeSentExtraInfo(
       const std::string& devtools_request_id,
       const net::CookieStatusList& request_cookie_list,
-      const std::vector<std::pair<std::string, std::string>>& request_headers);
+      const std::vector<network::mojom::HttpRawHeaderPairPtr>& request_headers);
   void OnResponseReceivedExtraInfo(
       const std::string& devtools_request_id,
       const net::CookieAndLineStatusList& response_cookie_list,
-      const std::vector<std::pair<std::string, std::string>>& response_headers,
+      const std::vector<network::mojom::HttpRawHeaderPairPtr>& response_headers,
       const base::Optional<std::string>& response_headers_text);
 
   bool enabled() const { return enabled_; }
diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc
index 0380136..33bcde9 100644
--- a/content/browser/gpu/gpu_data_manager_impl_private.cc
+++ b/content/browser/gpu/gpu_data_manager_impl_private.cc
@@ -359,6 +359,8 @@
           if (base::CommandLine::ForCurrentProcess()->HasSwitch(
                   switches::kDisableGpu))
             *reason += "through commandline switch --disable-gpu.";
+          else if (hardware_disabled_by_fallback_)
+            *reason += "due to frequent crashes.";
           else
             *reason += "in chrome://settings.";
         }
@@ -904,6 +906,7 @@
 #else
   switch (gpu_mode_) {
     case gpu::GpuMode::HARDWARE_ACCELERATED:
+      hardware_disabled_by_fallback_ = true;
       DisableHardwareAcceleration();
       break;
     case gpu::GpuMode::SWIFTSHADER:
diff --git a/content/browser/gpu/gpu_data_manager_impl_private.h b/content/browser/gpu/gpu_data_manager_impl_private.h
index eaa2d3c..f7bca5e 100644
--- a/content/browser/gpu/gpu_data_manager_impl_private.h
+++ b/content/browser/gpu/gpu_data_manager_impl_private.h
@@ -201,6 +201,9 @@
   // What the gpu process is being run for.
   gpu::GpuMode gpu_mode_ = gpu::GpuMode::HARDWARE_ACCELERATED;
 
+  // Used to tell if the gpu was disabled due to process crashes.
+  bool hardware_disabled_by_fallback_ = false;
+
   // We disable histogram stuff in testing, especially in unit tests because
   // they cause random failures.
   bool update_histograms_ = true;
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index fea66c7..2f3c7e91 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -191,9 +191,11 @@
       request_info->network_isolation_key;
 
   if (request_info->is_main_frame) {
+    new_request->update_network_isolation_key_on_redirect = network::mojom::
+        UpdateNetworkIsolationKeyOnRedirect::kUpdateTopFrameAndFrameOrigin;
+  } else {
     new_request->update_network_isolation_key_on_redirect =
-        network::mojom::UpdateNetworkIsolationKeyOnRedirect::
-            kUpdateTopFrameAndInitiatingFrameOrigin;
+        network::mojom::UpdateNetworkIsolationKeyOnRedirect::kUpdateFrameOrigin;
   }
 
   net::RequestPriority net_priority = net::HIGHEST;
@@ -1152,12 +1154,20 @@
     resource_request_->site_for_cookies = redirect_info_.new_site_for_cookies;
 
     // See if navigation network isolation key needs to be updated.
-    // TODO(crbug.com/950069): Also add the case for ResourceType::kSubFrame.
     if (resource_request_->resource_type ==
         static_cast<int>(ResourceType::kMainFrame)) {
       url::Origin origin = url::Origin::Create(resource_request_->url);
       resource_request_->trusted_network_isolation_key =
           net::NetworkIsolationKey(origin, origin);
+    } else {
+      DCHECK_EQ(static_cast<int>(ResourceType::kSubFrame),
+                resource_request_->resource_type);
+      url::Origin subframe_origin = url::Origin::Create(resource_request_->url);
+      base::Optional<url::Origin> top_frame_origin =
+          resource_request_->trusted_network_isolation_key.GetTopFrameOrigin();
+      DCHECK(top_frame_origin);
+      resource_request_->trusted_network_isolation_key =
+          net::NetworkIsolationKey(top_frame_origin.value(), subframe_origin);
     }
 
     resource_request_->referrer = GURL(redirect_info_.new_referrer);
diff --git a/content/browser/navigation_browsertest.cc b/content/browser/navigation_browsertest.cc
index 4e217a1..a3d0bad 100644
--- a/content/browser/navigation_browsertest.cc
+++ b/content/browser/navigation_browsertest.cc
@@ -55,6 +55,7 @@
 #include "content/test/content_browser_test_utils_internal.h"
 #include "content/test/did_commit_navigation_interceptor.h"
 #include "ipc/ipc_security_test_util.h"
+#include "net/base/features.h"
 #include "net/base/filename_util.h"
 #include "net/base/load_flags.h"
 #include "net/dns/mock_host_resolver.h"
@@ -252,6 +253,23 @@
     NavigationBaseBrowserTest::SetUpOnMainThread();
     ASSERT_TRUE(embedded_test_server()->Start());
   }
+};
+
+class NetworkIsolationNavigationBrowserTest
+    : public ContentBrowserTest,
+      public ::testing::WithParamInterface<bool> {
+ protected:
+  void SetUpOnMainThread() override {
+    if (GetParam()) {
+      feature_list_.InitAndEnableFeature(
+          net::features::kAppendFrameOriginToNetworkIsolationKey);
+    } else {
+      feature_list_.InitAndDisableFeature(
+          net::features::kAppendFrameOriginToNetworkIsolationKey);
+    }
+    ASSERT_TRUE(embedded_test_server()->Start());
+    ContentBrowserTest::SetUpOnMainThread();
+  }
 
   // Navigate to |url| and for each ResourceRequest record its
   // trusted_network_isolation_key. Stop listening after |final_resource| has
@@ -292,10 +310,12 @@
     // Wait until the last resource we care about has been requested.
     run_loop.Run();
   }
+
+  base::test::ScopedFeatureList feature_list_;
 };
 
 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
-                         NavigationBrowserTest,
+                         NetworkIsolationNavigationBrowserTest,
                          ::testing::Bool());
 
 // Ensure that browser initiated basic navigations work.
@@ -831,7 +851,7 @@
   EXPECT_EQ("\"done\"", done);
 }
 
-IN_PROC_BROWSER_TEST_P(NavigationBrowserTest,
+IN_PROC_BROWSER_TEST_P(NetworkIsolationNavigationBrowserTest,
                        BrowserNavigationNetworkIsolationKey) {
   std::map<GURL, net::NetworkIsolationKey> network_isolation_keys;
   std::map<GURL, network::mojom::UpdateNetworkIsolationKeyOnRedirect>
@@ -845,11 +865,11 @@
   EXPECT_EQ(net::NetworkIsolationKey(origin, origin),
             network_isolation_keys[url]);
   EXPECT_EQ(network::mojom::UpdateNetworkIsolationKeyOnRedirect::
-                kUpdateTopFrameAndInitiatingFrameOrigin,
+                kUpdateTopFrameAndFrameOrigin,
             update_network_isolation_key_on_redirects[url]);
 }
 
-IN_PROC_BROWSER_TEST_P(NavigationBrowserTest,
+IN_PROC_BROWSER_TEST_P(NetworkIsolationNavigationBrowserTest,
                        RenderNavigationNetworkIsolationKey) {
   std::map<GURL, net::NetworkIsolationKey> network_isolation_keys;
   std::map<GURL, network::mojom::UpdateNetworkIsolationKeyOnRedirect>
@@ -863,11 +883,12 @@
   EXPECT_EQ(net::NetworkIsolationKey(origin, origin),
             network_isolation_keys[url]);
   EXPECT_EQ(network::mojom::UpdateNetworkIsolationKeyOnRedirect::
-                kUpdateTopFrameAndInitiatingFrameOrigin,
+                kUpdateTopFrameAndFrameOrigin,
             update_network_isolation_key_on_redirects[url]);
 }
 
-IN_PROC_BROWSER_TEST_P(NavigationBrowserTest, SubframeNetworkIsolationKey) {
+IN_PROC_BROWSER_TEST_P(NetworkIsolationNavigationBrowserTest,
+                       SubframeNetworkIsolationKey) {
   std::map<GURL, net::NetworkIsolationKey> network_isolation_keys;
   std::map<GURL, network::mojom::UpdateNetworkIsolationKeyOnRedirect>
       update_network_isolation_key_on_redirects;
@@ -882,12 +903,13 @@
   EXPECT_EQ(net::NetworkIsolationKey(origin, origin),
             network_isolation_keys[url]);
   EXPECT_EQ(network::mojom::UpdateNetworkIsolationKeyOnRedirect::
-                kUpdateTopFrameAndInitiatingFrameOrigin,
+                kUpdateTopFrameAndFrameOrigin,
             update_network_isolation_key_on_redirects[url]);
   EXPECT_EQ(net::NetworkIsolationKey(origin, iframe_origin),
             network_isolation_keys[iframe_document]);
-  EXPECT_EQ(network::mojom::UpdateNetworkIsolationKeyOnRedirect::kDoNotUpdate,
-            update_network_isolation_key_on_redirects[iframe_document]);
+  EXPECT_EQ(
+      network::mojom::UpdateNetworkIsolationKeyOnRedirect::kUpdateFrameOrigin,
+      update_network_isolation_key_on_redirects[iframe_document]);
 }
 
 // Helper class to extract the initiator values from URLLoaderFactory calls
diff --git a/content/browser/network_service_client.cc b/content/browser/network_service_client.cc
index cb60866..b89e764f 100644
--- a/content/browser/network_service_client.cc
+++ b/content/browser/network_service_client.cc
@@ -13,6 +13,7 @@
 #include "base/threading/sequence_bound.h"
 #include "base/unguessable_token.h"
 #include "content/browser/browsing_data/clear_site_data_handler.h"
+#include "content/browser/devtools/devtools_instrumentation.h"
 #include "content/browser/devtools/devtools_url_loader_interceptor.h"
 #include "content/browser/frame_host/frame_tree_node.h"
 #include "content/browser/loader/resource_dispatcher_host_impl.h"
@@ -694,7 +695,7 @@
   auth_negotiate->set_can_delegate(can_delegate);
 
   auto auth_token = std::make_unique<std::string>();
-  auth_negotiate_raw->GenerateAuthToken(
+  auth_negotiate_raw->GenerateAuthTokenAndroid(
       nullptr, spn, std::string(), auth_token.get(),
       base::BindOnce(&FinishGenerateNegotiateAuthToken,
                      std::move(auth_negotiate), std::move(auth_token),
@@ -702,4 +703,26 @@
 }
 #endif
 
+void NetworkServiceClient::OnRawRequest(
+    int32_t process_id,
+    int32_t routing_id,
+    const std::string& devtools_request_id,
+    const net::CookieStatusList& cookies_with_status,
+    std::vector<network::mojom::HttpRawHeaderPairPtr> headers) {
+  devtools_instrumentation::OnRequestWillBeSentExtraInfo(
+      process_id, routing_id, devtools_request_id, cookies_with_status,
+      headers);
+}
+
+void NetworkServiceClient::OnRawResponse(
+    int32_t process_id,
+    int32_t routing_id,
+    const std::string& devtools_request_id,
+    const net::CookieAndLineStatusList& cookies_with_status,
+    std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
+    const base::Optional<std::string>& raw_response_headers) {
+  devtools_instrumentation::OnResponseReceivedExtraInfo(
+      process_id, routing_id, devtools_request_id, cookies_with_status, headers,
+      raw_response_headers);
+}
 }  // namespace content
diff --git a/content/browser/network_service_client.h b/content/browser/network_service_client.h
index 30f8ac1..0517ef64 100644
--- a/content/browser/network_service_client.h
+++ b/content/browser/network_service_client.h
@@ -86,6 +86,19 @@
       const std::string& spn,
       OnGenerateHttpNegotiateAuthTokenCallback callback) override;
 #endif
+  void OnRawRequest(
+      int32_t process_id,
+      int32_t routing_id,
+      const std::string& devtools_request_id,
+      const net::CookieStatusList& cookies_with_status,
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers) override;
+  void OnRawResponse(
+      int32_t process_id,
+      int32_t routing_id,
+      const std::string& devtools_request_id,
+      const net::CookieAndLineStatusList& cookies_with_status,
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
+      const base::Optional<std::string>& raw_response_headers) override;
   // net::CertDatabase::Observer implementation:
   void OnCertDBChanged() override;
 
diff --git a/content/browser/notifications/blink_notification_service_impl.cc b/content/browser/notifications/blink_notification_service_impl.cc
index 63a1fe8..ceb4bc62 100644
--- a/content/browser/notifications/blink_notification_service_impl.cc
+++ b/content/browser/notifications/blink_notification_service_impl.cc
@@ -89,17 +89,17 @@
     BrowserContext* browser_context,
     scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
     const url::Origin& origin,
-    mojo::InterfaceRequest<blink::mojom::NotificationService> request)
+    mojo::PendingReceiver<blink::mojom::NotificationService> receiver)
     : notification_context_(notification_context),
       browser_context_(browser_context),
       service_worker_context_(std::move(service_worker_context)),
       origin_(origin),
-      binding_(this, std::move(request)) {
+      receiver_(this, std::move(receiver)) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(notification_context_);
   DCHECK(browser_context_);
 
-  binding_.set_connection_error_handler(base::BindOnce(
+  receiver_.set_disconnect_handler(base::BindOnce(
       &BlinkNotificationServiceImpl::OnConnectionError,
       base::Unretained(this) /* the channel is owned by |this| */));
 }
@@ -129,7 +129,8 @@
     const std::string& token,
     const blink::PlatformNotificationData& platform_notification_data,
     const blink::NotificationResources& notification_resources,
-    blink::mojom::NonPersistentNotificationListenerPtr event_listener_ptr) {
+    mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+        event_listener_remote) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   if (!ValidateNotificationResources(notification_resources))
     return;
@@ -147,7 +148,7 @@
   NotificationEventDispatcherImpl* event_dispatcher =
       NotificationEventDispatcherImpl::GetInstance();
   event_dispatcher->RegisterNonPersistentNotificationListener(
-      notification_id, std::move(event_listener_ptr));
+      notification_id, std::move(event_listener_remote));
 
   GetNotificationService(browser_context_)
       ->DisplayNotification(notification_id, origin_.GetURL(),
@@ -188,7 +189,7 @@
   if (notification_resources.image.drawsNothing() ||
       base::FeatureList::IsEnabled(features::kNotificationContentImage))
     return true;
-  binding_.ReportBadMessage(kBadMessageImproperNotificationImage);
+  receiver_.ReportBadMessage(kBadMessageImproperNotificationImage);
   // The above ReportBadMessage() closes |binding_| but does not trigger its
   // connection error handler, so we need to call the error handler explicitly
   // here to do some necessary work.
@@ -200,7 +201,7 @@
 bool BlinkNotificationServiceImpl::ValidateNotificationData(
     const blink::PlatformNotificationData& notification_data) {
   if (!CheckNotificationTriggerRange(notification_data)) {
-    binding_.ReportBadMessage(kBadMessageInvalidNotificationTriggerTimestamp);
+    receiver_.ReportBadMessage(kBadMessageInvalidNotificationTriggerTimestamp);
     OnConnectionError();
     return false;
   }
diff --git a/content/browser/notifications/blink_notification_service_impl.h b/content/browser/notifications/blink_notification_service_impl.h
index 3b7875f0..5253f6b 100644
--- a/content/browser/notifications/blink_notification_service_impl.h
+++ b/content/browser/notifications/blink_notification_service_impl.h
@@ -5,13 +5,17 @@
 #ifndef CONTENT_BROWSER_NOTIFICATIONS_BLINK_NOTIFICATION_SERVICE_IMPL_H_
 #define CONTENT_BROWSER_NOTIFICATIONS_BLINK_NOTIFICATION_SERVICE_IMPL_H_
 
+#include <string>
+#include <vector>
+
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_context.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
 #include "url/origin.h"
@@ -37,7 +41,7 @@
       BrowserContext* browser_context,
       scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
       const url::Origin& origin,
-      mojo::InterfaceRequest<blink::mojom::NotificationService> request);
+      mojo::PendingReceiver<blink::mojom::NotificationService> receiver);
   ~BlinkNotificationServiceImpl() override;
 
   // blink::mojom::NotificationService implementation.
@@ -46,7 +50,8 @@
       const std::string& token,
       const blink::PlatformNotificationData& platform_notification_data,
       const blink::NotificationResources& notification_resources,
-      blink::mojom::NonPersistentNotificationListenerPtr listener_ptr) override;
+      mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+          listener_remote) override;
   void CloseNonPersistentNotification(const std::string& token) override;
   void DisplayPersistentNotification(
       int64_t service_worker_registration_id,
@@ -101,7 +106,7 @@
   // The origin that this notification service is communicating with.
   url::Origin origin_;
 
-  mojo::Binding<blink::mojom::NotificationService> binding_;
+  mojo::Receiver<blink::mojom::NotificationService> receiver_;
 
   base::WeakPtrFactory<BlinkNotificationServiceImpl> weak_factory_for_io_{this};
   base::WeakPtrFactory<BlinkNotificationServiceImpl> weak_factory_for_ui_{this};
diff --git a/content/browser/notifications/blink_notification_service_impl_unittest.cc b/content/browser/notifications/blink_notification_service_impl_unittest.cc
index a1f8312..f64eb73 100644
--- a/content/browser/notifications/blink_notification_service_impl_unittest.cc
+++ b/content/browser/notifications/blink_notification_service_impl_unittest.cc
@@ -4,6 +4,8 @@
 
 #include <stdint.h>
 #include <memory>
+#include <set>
+#include <utility>
 #include <vector>
 
 #include "base/bind.h"
@@ -30,8 +32,8 @@
 #include "content/test/mock_platform_notification_service.h"
 #include "content/test/test_content_browser_client.h"
 #include "mojo/core/embedder/embedder.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/notifications/notification_constants.h"
@@ -65,13 +67,14 @@
 class MockNonPersistentNotificationListener
     : public blink::mojom::NonPersistentNotificationListener {
  public:
-  MockNonPersistentNotificationListener() : binding_(this) {}
+  MockNonPersistentNotificationListener() = default;
   ~MockNonPersistentNotificationListener() override = default;
 
-  blink::mojom::NonPersistentNotificationListenerPtr GetPtr() {
-    blink::mojom::NonPersistentNotificationListenerPtr ptr;
-    binding_.Bind(mojo::MakeRequest(&ptr));
-    return ptr;
+  mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+  GetRemote() {
+    mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener> remote;
+    receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
+    return remote;
   }
 
   // NonPersistentNotificationListener interface.
@@ -84,7 +87,8 @@
   }
 
  private:
-  mojo::Binding<blink::mojom::NonPersistentNotificationListener> binding_;
+  mojo::Receiver<blink::mojom::NonPersistentNotificationListener> receiver_{
+      this};
 };
 
 // This is for overriding the Platform Notification Service with a mock one.
@@ -136,7 +140,7 @@
         notification_context_.get(), &browser_context_,
         embedded_worker_helper_->context_wrapper(),
         url::Origin::Create(GURL(kTestOrigin)),
-        mojo::MakeRequest(&notification_service_ptr_));
+        notification_service_remote_.BindNewPipeAndPassReceiver());
 
     // Provide a mock permission manager to the |browser_context_|.
     browser_context_.SetPermissionControllerDelegate(
@@ -293,10 +297,11 @@
       const std::string& token,
       const blink::PlatformNotificationData& platform_notification_data,
       const blink::NotificationResources& notification_resources,
-      blink::mojom::NonPersistentNotificationListenerPtr event_listener_ptr) {
-    notification_service_ptr_->DisplayNonPersistentNotification(
+      mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+          event_listener_remote) {
+    notification_service_remote_->DisplayNonPersistentNotification(
         token, platform_notification_data, notification_resources,
-        std::move(event_listener_ptr));
+        std::move(event_listener_remote));
     // TODO(https://crbug.com/787459): Pass a callback to
     // DisplayNonPersistentNotification instead of waiting for all tasks to run
     // here; a callback parameter will be needed anyway to enable
@@ -309,9 +314,8 @@
       const blink::PlatformNotificationData& platform_notification_data,
       const blink::NotificationResources& notification_resources) {
     base::RunLoop run_loop;
-    notification_service_ptr_.set_connection_error_handler(
-        run_loop.QuitClosure());
-    notification_service_ptr_->DisplayPersistentNotification(
+    notification_service_remote_.set_disconnect_handler(run_loop.QuitClosure());
+    notification_service_remote_->DisplayPersistentNotification(
         service_worker_registration_id, platform_notification_data,
         notification_resources,
         base::BindOnce(
@@ -422,7 +426,7 @@
 
   std::unique_ptr<BlinkNotificationServiceImpl> notification_service_;
 
-  blink::mojom::NotificationServicePtr notification_service_ptr_;
+  mojo::Remote<blink::mojom::NotificationService> notification_service_remote_;
 
   TestBrowserContext browser_context_;
 
@@ -502,7 +506,7 @@
   DisplayNonPersistentNotification(
       "token", blink::PlatformNotificationData(),
       blink::NotificationResources(),
-      non_persistent_notification_listener_.GetPtr());
+      non_persistent_notification_listener_.GetRemote());
 
   EXPECT_EQ(1u, GetDisplayedNotifications().size());
 }
@@ -514,7 +518,7 @@
   DisplayNonPersistentNotification(
       "token", blink::PlatformNotificationData(),
       blink::NotificationResources(),
-      non_persistent_notification_listener_.GetPtr());
+      non_persistent_notification_listener_.GetRemote());
 
   EXPECT_EQ(0u, GetDisplayedNotifications().size());
 }
@@ -527,7 +531,7 @@
   resources.image = CreateBitmap(200, 100, SK_ColorMAGENTA);
   DisplayNonPersistentNotification(
       "token", blink::PlatformNotificationData(), resources,
-      non_persistent_notification_listener_.GetPtr());
+      non_persistent_notification_listener_.GetRemote());
 
   EXPECT_EQ(1u, GetDisplayedNotifications().size());
 }
@@ -544,7 +548,7 @@
   resources.image = CreateBitmap(200, 100, SK_ColorMAGENTA);
   DisplayNonPersistentNotification(
       "token", blink::PlatformNotificationData(), resources,
-      non_persistent_notification_listener_.GetPtr());
+      non_persistent_notification_listener_.GetRemote());
   EXPECT_EQ(1u, bad_messages_.size());
   EXPECT_EQ(kBadMessageImproperNotificationImage, bad_messages_[0]);
 }
diff --git a/content/browser/notifications/notification_event_dispatcher_impl.cc b/content/browser/notifications/notification_event_dispatcher_impl.cc
index ba2bb907..4caf49c 100644
--- a/content/browser/notifications/notification_event_dispatcher_impl.cc
+++ b/content/browser/notifications/notification_event_dispatcher_impl.cc
@@ -434,11 +434,15 @@
 
 void NotificationEventDispatcherImpl::RegisterNonPersistentNotificationListener(
     const std::string& notification_id,
-    blink::mojom::NonPersistentNotificationListenerPtr event_listener_ptr) {
+    mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+        event_listener_remote) {
+  mojo::Remote<blink::mojom::NonPersistentNotificationListener> bound_remote(
+      std::move(event_listener_remote));
+
   // Observe connection errors, which occur when the JavaScript object or the
   // renderer hosting them goes away. (For example through navigation.) The
   // listener gets freed together with |this|, thus the Unretained is safe.
-  event_listener_ptr.set_connection_error_handler(base::BindOnce(
+  bound_remote.set_disconnect_handler(base::BindOnce(
       &NotificationEventDispatcherImpl::
           HandleConnectionErrorForNonPersistentNotificationListener,
       base::Unretained(this), notification_id));
@@ -455,8 +459,8 @@
     non_persistent_notification_listeners_.erase(notification_id);
   }
 
-  non_persistent_notification_listeners_.emplace(notification_id,
-                                                 std::move(event_listener_ptr));
+  non_persistent_notification_listeners_.insert(
+      {notification_id, std::move(bound_remote)});
 }
 
 void NotificationEventDispatcherImpl::DispatchNonPersistentShowEvent(
diff --git a/content/browser/notifications/notification_event_dispatcher_impl.h b/content/browser/notifications/notification_event_dispatcher_impl.h
index 92de011..4c96848 100644
--- a/content/browser/notifications/notification_event_dispatcher_impl.h
+++ b/content/browser/notifications/notification_event_dispatcher_impl.h
@@ -6,6 +6,8 @@
 #define CONTENT_BROWSER_NOTIFICATIONS_NOTIFICATION_EVENT_DISPATCHER_IMPL_H_
 
 #include <map>
+#include <string>
+#include <utility>
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
@@ -13,6 +15,8 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/notification_database_data.h"
 #include "content/public/browser/notification_event_dispatcher.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom.h"
 
 namespace content {
@@ -51,7 +55,8 @@
   // non-persistent notification identified by |notification_id|.
   void RegisterNonPersistentNotificationListener(
       const std::string& notification_id,
-      blink::mojom::NonPersistentNotificationListenerPtr event_listener_ptr);
+      mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+          event_listener_remote);
 
  private:
   friend class NotificationEventDispatcherImplTest;
@@ -74,7 +79,8 @@
       const std::string& notification_id);
 
   // Notification Id -> listener.
-  std::map<std::string, blink::mojom::NonPersistentNotificationListenerPtr>
+  std::map<std::string,
+           mojo::Remote<blink::mojom::NonPersistentNotificationListener>>
       non_persistent_notification_listeners_;
 
   DISALLOW_COPY_AND_ASSIGN(NotificationEventDispatcherImpl);
diff --git a/content/browser/notifications/notification_event_dispatcher_impl_unittest.cc b/content/browser/notifications/notification_event_dispatcher_impl_unittest.cc
index 4643bdca..cca73db 100644
--- a/content/browser/notifications/notification_event_dispatcher_impl_unittest.cc
+++ b/content/browser/notifications/notification_event_dispatcher_impl_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <stdint.h>
 #include <memory>
+#include <utility>
 #include <vector>
 
 #include "base/bind_helpers.h"
@@ -13,8 +14,8 @@
 #include "base/test/scoped_task_environment.h"
 #include "base/test/test_simple_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
-#include "mojo/public/cpp/bindings/binding.h"
-#include "mojo/public/cpp/bindings/interface_request.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom.h"
 
@@ -28,17 +29,18 @@
 class TestNotificationListener
     : public blink::mojom::NonPersistentNotificationListener {
  public:
-  TestNotificationListener() : binding_(this) {}
+  TestNotificationListener() = default;
   ~TestNotificationListener() override = default;
 
   // Closes the bindings associated with this listener.
-  void Close() { binding_.Close(); }
+  void Close() { receiver_.reset(); }
 
   // Returns an InterfacePtr to this listener.
-  blink::mojom::NonPersistentNotificationListenerPtr GetPtr() {
-    blink::mojom::NonPersistentNotificationListenerPtr ptr;
-    binding_.Bind(mojo::MakeRequest(&ptr));
-    return ptr;
+  mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener>
+  GetRemote() {
+    mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener> remote;
+    receiver_.Bind(remote.InitWithNewPipeAndPassReceiver());
+    return remote;
   }
 
   // Returns the number of OnShow events received by this listener.
@@ -65,7 +67,8 @@
   int on_show_count_ = 0;
   int on_click_count_ = 0;
   int on_close_count_ = 0;
-  mojo::Binding<blink::mojom::NonPersistentNotificationListener> binding_;
+  mojo::Receiver<blink::mojom::NonPersistentNotificationListener> receiver_{
+      this};
 
   DISALLOW_COPY_AND_ASSIGN(TestNotificationListener);
 };
@@ -97,10 +100,10 @@
        DispatchNonPersistentShowEvent_NotifiesCorrectRegisteredListener) {
   auto listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(kPrimaryUniqueId,
-                                                         listener->GetPtr());
+                                                         listener->GetRemote());
   auto other_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kSomeOtherUniqueId, other_listener->GetPtr());
+      kSomeOtherUniqueId, other_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentShowEvent(kPrimaryUniqueId);
 
@@ -121,7 +124,7 @@
        RegisterNonPersistentListener_FirstListenerGetsOnClose) {
   auto original_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kPrimaryUniqueId, original_listener->GetPtr());
+      kPrimaryUniqueId, original_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentShowEvent(kPrimaryUniqueId);
 
@@ -129,7 +132,7 @@
 
   auto replacement_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kPrimaryUniqueId, replacement_listener->GetPtr());
+      kPrimaryUniqueId, replacement_listener->GetRemote());
 
   WaitForMojoTasksToComplete();
 
@@ -141,7 +144,7 @@
        RegisterNonPersistentListener_SecondListenerGetsOnShow) {
   auto original_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kPrimaryUniqueId, original_listener->GetPtr());
+      kPrimaryUniqueId, original_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentShowEvent(kPrimaryUniqueId);
 
@@ -151,7 +154,7 @@
 
   auto replacement_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kPrimaryUniqueId, replacement_listener->GetPtr());
+      kPrimaryUniqueId, replacement_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentShowEvent(kPrimaryUniqueId);
 
@@ -165,13 +168,13 @@
        RegisterNonPersistentListener_ReplacedListenerGetsOnClick) {
   auto original_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kPrimaryUniqueId, original_listener->GetPtr());
+      kPrimaryUniqueId, original_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentShowEvent(kPrimaryUniqueId);
 
   auto replacement_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kPrimaryUniqueId, replacement_listener->GetPtr());
+      kPrimaryUniqueId, replacement_listener->GetRemote());
 
   WaitForMojoTasksToComplete();
 
@@ -190,10 +193,10 @@
        DispatchNonPersistentClickEvent_NotifiesCorrectRegisteredListener) {
   auto listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(kPrimaryUniqueId,
-                                                         listener->GetPtr());
+                                                         listener->GetRemote());
   auto other_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kSomeOtherUniqueId, other_listener->GetPtr());
+      kSomeOtherUniqueId, other_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentClickEvent(kPrimaryUniqueId,
                                                base::DoNothing());
@@ -216,10 +219,10 @@
        DispatchNonPersistentCloseEvent_NotifiesCorrectRegisteredListener) {
   auto listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(kPrimaryUniqueId,
-                                                         listener->GetPtr());
+                                                         listener->GetRemote());
   auto other_listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(
-      kSomeOtherUniqueId, other_listener->GetPtr());
+      kSomeOtherUniqueId, other_listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentCloseEvent(kPrimaryUniqueId,
                                                base::DoNothing());
@@ -242,7 +245,7 @@
        DispatchMultipleNonPersistentEvents_StopsNotifyingAfterClose) {
   auto listener = std::make_unique<TestNotificationListener>();
   dispatcher_->RegisterNonPersistentNotificationListener(kPrimaryUniqueId,
-                                                         listener->GetPtr());
+                                                         listener->GetRemote());
 
   dispatcher_->DispatchNonPersistentShowEvent(kPrimaryUniqueId);
   dispatcher_->DispatchNonPersistentClickEvent(kPrimaryUniqueId,
diff --git a/content/browser/notifications/platform_notification_context_impl.cc b/content/browser/notifications/platform_notification_context_impl.cc
index 74b9766d..564d4fdb 100644
--- a/content/browser/notifications/platform_notification_context_impl.cc
+++ b/content/browser/notifications/platform_notification_context_impl.cc
@@ -208,11 +208,11 @@
 
 void PlatformNotificationContextImpl::CreateService(
     const url::Origin& origin,
-    blink::mojom::NotificationServiceRequest request) {
+    mojo::PendingReceiver<blink::mojom::NotificationService> receiver) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   services_.push_back(std::make_unique<BlinkNotificationServiceImpl>(
       this, browser_context_, service_worker_context_, origin,
-      std::move(request)));
+      std::move(receiver)));
 }
 
 void PlatformNotificationContextImpl::RemoveService(
diff --git a/content/browser/notifications/platform_notification_context_impl.h b/content/browser/notifications/platform_notification_context_impl.h
index f1710b6..83d7c93 100644
--- a/content/browser/notifications/platform_notification_context_impl.h
+++ b/content/browser/notifications/platform_notification_context_impl.h
@@ -24,6 +24,7 @@
 #include "content/common/content_export.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/platform_notification_context.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom.h"
 
 class GURL;
@@ -65,8 +66,9 @@
   void Shutdown();
 
   // Creates a BlinkNotificationServiceImpl that is owned by this context.
-  void CreateService(const url::Origin& origin,
-                     blink::mojom::NotificationServiceRequest request);
+  void CreateService(
+      const url::Origin& origin,
+      mojo::PendingReceiver<blink::mojom::NotificationService> receiver);
 
   // Removes |service| from the list of owned services, for example because the
   // Mojo pipe disconnected. Must be called on the UI thread.
diff --git a/content/browser/renderer_interface_binders.cc b/content/browser/renderer_interface_binders.cc
index c7c8e49..cfdfae7 100644
--- a/content/browser/renderer_interface_binders.cc
+++ b/content/browser/renderer_interface_binders.cc
@@ -4,6 +4,7 @@
 
 #include "content/browser/renderer_interface_binders.h"
 
+#include <memory>
 #include <utility>
 
 #include "base/bind.h"
@@ -190,9 +191,9 @@
             ->CreateService(std::move(request), origin);
       }));
 
-  parameterized_binder_registry_.AddInterface(
-      base::Bind([](blink::mojom::NotificationServiceRequest request,
-                    RenderProcessHost* host, const url::Origin& origin) {
+  parameterized_binder_registry_.AddInterface(base::BindRepeating(
+      [](blink::mojom::NotificationServiceRequest request,
+         RenderProcessHost* host, const url::Origin& origin) {
         static_cast<StoragePartitionImpl*>(host->GetStoragePartition())
             ->GetPlatformNotificationContext()
             ->CreateService(origin, std::move(request));
diff --git a/content/browser/service_worker/service_worker_context_wrapper.cc b/content/browser/service_worker/service_worker_context_wrapper.cc
index a932cd5..3db775f 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.cc
+++ b/content/browser/service_worker/service_worker_context_wrapper.cc
@@ -290,6 +290,11 @@
   process_manager_->set_storage_partition(storage_partition_);
 }
 
+BrowserContext* ServiceWorkerContextWrapper::browser_context() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  return process_manager()->browser_context();
+}
+
 ResourceContext* ServiceWorkerContextWrapper::resource_context() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   return resource_context_;
@@ -1118,19 +1123,6 @@
   core_observer_list_->RemoveObserver(observer);
 }
 
-base::WeakPtr<ServiceWorkerProviderHost>
-ServiceWorkerContextWrapper::PreCreateHostForWorker(
-    int process_id,
-    blink::mojom::ServiceWorkerProviderType provider_type,
-    blink::mojom::ServiceWorkerProviderInfoForClientPtr* out_provider_info) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
-
-  if (!context_core_)
-    return nullptr;
-  return ServiceWorkerProviderHost::PreCreateForWebWorker(
-      context_core_->AsWeakPtr(), process_id, provider_type, out_provider_info);
-}
-
 ServiceWorkerContextWrapper::~ServiceWorkerContextWrapper() {
   for (auto& observer : observer_list_)
     observer.OnDestruct(static_cast<ServiceWorkerContext*>(this));
diff --git a/content/browser/service_worker/service_worker_context_wrapper.h b/content/browser/service_worker/service_worker_context_wrapper.h
index f636d99..3f0e309 100644
--- a/content/browser/service_worker/service_worker_context_wrapper.h
+++ b/content/browser/service_worker/service_worker_context_wrapper.h
@@ -96,6 +96,9 @@
 
   void set_storage_partition(StoragePartitionImpl* storage_partition);
 
+  // UI thread.
+  BrowserContext* browser_context();
+
   // The ResourceContext for the associated BrowserContext. This should only
   // be accessed on the IO thread, and can be null during initialization and
   // shutdown.
@@ -296,20 +299,6 @@
 
   bool is_incognito() const { return is_incognito_; }
 
-  // Used for starting a web worker (dedicated worker or shared worker). Returns
-  // a provider host for the worker and fills |out_provider_info| with info to
-  // send to the renderer to connect to the host. The host stays alive as long
-  // as this info stays alive (namely, as long as
-  // |out_provider_info->host_ptr_info| stays alive).
-  //
-  // Returns null if context() is null.
-  //
-  // Must be called on the IO thread.
-  base::WeakPtr<ServiceWorkerProviderHost> PreCreateHostForWorker(
-      int process_id,
-      blink::mojom::ServiceWorkerProviderType provider_type,
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr* out_provider_info);
-
   // The core context is only for use on the IO thread.
   // Can be null before/during init, during/after shutdown, and after
   // DeleteAndStartOver fails.
diff --git a/content/browser/service_worker/service_worker_navigation_handle.cc b/content/browser/service_worker/service_worker_navigation_handle.cc
index ca3a020..d25a8650 100644
--- a/content/browser/service_worker/service_worker_navigation_handle.cc
+++ b/content/browser/service_worker/service_worker_navigation_handle.cc
@@ -55,4 +55,12 @@
   *out_provider_info = std::move(provider_info_);
 }
 
+void ServiceWorkerNavigationHandle::OnBeginWorkerCommit() {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  base::PostTaskWithTraits(
+      FROM_HERE, {BrowserThread::IO},
+      base::BindOnce(&ServiceWorkerNavigationHandleCore::OnBeginWorkerCommit,
+                     base::Unretained(core_)));
+}
+
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_navigation_handle.h b/content/browser/service_worker/service_worker_navigation_handle.h
index d2e0708..20588a4 100644
--- a/content/browser/service_worker/service_worker_navigation_handle.h
+++ b/content/browser/service_worker/service_worker_navigation_handle.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "content/common/content_export.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 
 namespace content {
@@ -15,8 +16,9 @@
 class ServiceWorkerNavigationHandleCore;
 
 // This class is used to manage the lifetime of ServiceWorkerProviderHosts
-// created during navigation. This is a UI thread class, with a pendant class
-// on the IO thread, the ServiceWorkerNavigationHandleCore.
+// created for main resource requests (navigations and web workers). This is a
+// UI thread class, with a pendant class on the IO thread, the
+// ServiceWorkerNavigationHandleCore.
 //
 // The lifetime of the ServiceWorkerNavigationHandle, the
 // ServiceWorkerNavigationHandleCore and the ServiceWorkerProviderHost are the
@@ -45,7 +47,9 @@
 //   the provider info which in turn leads to the destruction of an unclaimed
 //   ServiceWorkerProviderHost, and posts a task to destroy the
 //   ServiceWorkerNavigationHandleCore on the IO thread.
-class ServiceWorkerNavigationHandle {
+//
+// TODO(falken): Rename ServiceWorkerMainResourceHandle.
+class CONTENT_EXPORT ServiceWorkerNavigationHandle {
  public:
   explicit ServiceWorkerNavigationHandle(
       ServiceWorkerContextWrapper* context_wrapper);
@@ -67,14 +71,25 @@
       int render_frame_id,
       blink::mojom::ServiceWorkerProviderInfoForClientPtr* out_provider_info);
 
+  void OnBeginWorkerCommit();
+
+  blink::mojom::ServiceWorkerProviderInfoForClientPtr TakeProviderInfo() {
+    return std::move(provider_info_);
+  }
+
   ServiceWorkerNavigationHandleCore* core() const { return core_; }
 
   const ServiceWorkerContextWrapper* context_wrapper() const {
     return context_wrapper_.get();
   }
 
+  base::WeakPtr<ServiceWorkerNavigationHandle> AsWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
  private:
   blink::mojom::ServiceWorkerProviderInfoForClientPtr provider_info_;
+
   // TODO(leonhsl): Use std::unique_ptr<ServiceWorkerNavigationHandleCore,
   // BrowserThread::DeleteOnIOThread> instead.
   ServiceWorkerNavigationHandleCore* core_;
diff --git a/content/browser/service_worker/service_worker_navigation_handle_core.cc b/content/browser/service_worker/service_worker_navigation_handle_core.cc
index 52d177d..e23d3e6 100644
--- a/content/browser/service_worker/service_worker_navigation_handle_core.cc
+++ b/content/browser/service_worker/service_worker_navigation_handle_core.cc
@@ -51,4 +51,10 @@
     provider_host_->OnBeginNavigationCommit(render_process_id, render_frame_id);
 }
 
+void ServiceWorkerNavigationHandleCore::OnBeginWorkerCommit() {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  if (provider_host_)
+    provider_host_->CompleteWebWorkerPreparation();
+}
+
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_navigation_handle_core.h b/content/browser/service_worker/service_worker_navigation_handle_core.h
index 2e4d0b47..f3ab8705 100644
--- a/content/browser/service_worker/service_worker_navigation_handle_core.h
+++ b/content/browser/service_worker/service_worker_navigation_handle_core.h
@@ -32,7 +32,7 @@
       ServiceWorkerContextWrapper* context_wrapper);
   ~ServiceWorkerNavigationHandleCore();
 
-  // Called when a ServiceWorkerProviderHost was created for the navigation.
+  // Called when a ServiceWorkerProviderHost was created.
   void OnCreatedProviderHost(
       base::WeakPtr<ServiceWorkerProviderHost> provider_host,
       blink::mojom::ServiceWorkerProviderInfoForClientPtr provider_info);
@@ -41,6 +41,8 @@
   // pre-created provider host.
   void OnBeginNavigationCommit(int render_process_id, int render_frame_id);
 
+  void OnBeginWorkerCommit();
+
   ServiceWorkerContextWrapper* context_wrapper() const {
     return context_wrapper_.get();
   }
@@ -65,6 +67,11 @@
   ServiceWorkerControlleeRequestHandler* interceptor() {
     return interceptor_.get();
   }
+
+  base::WeakPtr<ServiceWorkerNavigationHandleCore> AsWeakPtr() {
+    return weak_factory_.GetWeakPtr();
+  }
+
   /////////////////////////////////////////////////////////////////////////////
 
  private:
@@ -75,6 +82,8 @@
   // NavigationLoaderOnUI:
   std::unique_ptr<ServiceWorkerControlleeRequestHandler> interceptor_;
 
+  base::WeakPtrFactory<ServiceWorkerNavigationHandleCore> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerNavigationHandleCore);
 };
 
diff --git a/content/browser/service_worker/service_worker_navigation_loader_interceptor.cc b/content/browser/service_worker/service_worker_navigation_loader_interceptor.cc
index 7c3d34b..e3b66d96 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_interceptor.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_interceptor.cc
@@ -78,10 +78,7 @@
 void MaybeCreateLoaderOnIO(
     base::WeakPtr<ServiceWorkerNavigationLoaderInterceptor> interceptor_on_ui,
     ServiceWorkerNavigationHandleCore* handle_core,
-    bool are_ancestors_secure,
-    int frame_tree_node_id,
-    ResourceType resource_type,
-    bool skip_service_worker,
+    const ServiceWorkerNavigationLoaderInterceptorParams& params,
     const network::ResourceRequest& tentative_resource_request,
     BrowserContext* browser_context,
     NavigationLoaderInterceptor::LoaderCallback loader_callback,
@@ -110,17 +107,30 @@
     // ServiceWorkerNavigationHandle on the UI thread, and finally passed to the
     // renderer when the navigation commits.
     provider_info = blink::mojom::ServiceWorkerProviderInfoForClient::New();
-    provider_host = ServiceWorkerProviderHost::PreCreateNavigationHost(
-        context_core->AsWeakPtr(), are_ancestors_secure, frame_tree_node_id,
-        &provider_info);
+    if (params.resource_type == ResourceType::kMainFrame ||
+        params.resource_type == ResourceType::kSubFrame) {
+      provider_host = ServiceWorkerProviderHost::PreCreateNavigationHost(
+          context_core->AsWeakPtr(), params.are_ancestors_secure,
+          params.frame_tree_node_id, &provider_info);
+    } else {
+      DCHECK(params.resource_type == ResourceType::kWorker ||
+             params.resource_type == ResourceType::kSharedWorker);
+      auto provider_type =
+          params.resource_type == ResourceType::kWorker
+              ? blink::mojom::ServiceWorkerProviderType::kForDedicatedWorker
+              : blink::mojom::ServiceWorkerProviderType::kForSharedWorker;
+      provider_host = ServiceWorkerProviderHost::PreCreateForWebWorker(
+          context_core->AsWeakPtr(), params.process_id, provider_type,
+          &provider_info);
+    }
     handle_core->set_provider_host(provider_host);
 
     // Also make the inner interceptor.
     DCHECK(!handle_core->interceptor());
     handle_core->set_interceptor(
         std::make_unique<ServiceWorkerControlleeRequestHandler>(
-            context_core->AsWeakPtr(), provider_host, resource_type,
-            skip_service_worker));
+            context_core->AsWeakPtr(), provider_host, params.resource_type,
+            params.skip_service_worker));
   }
 
   // If |initialize_provider_only| is true, we have already determined there is
@@ -152,14 +162,9 @@
 
 ServiceWorkerNavigationLoaderInterceptor::
     ServiceWorkerNavigationLoaderInterceptor(
-        const NavigationRequestInfo& request_info,
+        const ServiceWorkerNavigationLoaderInterceptorParams& params,
         ServiceWorkerNavigationHandle* handle)
-    : handle_(handle),
-      are_ancestors_secure_(request_info.are_ancestors_secure),
-      frame_tree_node_id_(request_info.frame_tree_node_id),
-      resource_type_(request_info.is_main_frame ? ResourceType::kMainFrame
-                                                : ResourceType::kSubFrame),
-      skip_service_worker_(request_info.begin_params->skip_service_worker) {
+    : handle_(handle), params_(params) {
   DCHECK(NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled());
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 }
@@ -194,10 +199,9 @@
   base::PostTaskWithTraits(
       FROM_HERE, {BrowserThread::IO},
       base::BindOnce(&MaybeCreateLoaderOnIO, GetWeakPtr(), handle_->core(),
-                     are_ancestors_secure_, frame_tree_node_id_, resource_type_,
-                     skip_service_worker_, tentative_resource_request,
-                     browser_context, std::move(loader_callback),
-                     std::move(fallback_callback), initialize_provider_only));
+                     params_, tentative_resource_request, browser_context,
+                     std::move(loader_callback), std::move(fallback_callback),
+                     initialize_provider_only));
 }
 
 base::Optional<SubresourceLoaderParams>
diff --git a/content/browser/service_worker/service_worker_navigation_loader_interceptor.h b/content/browser/service_worker/service_worker_navigation_loader_interceptor.h
index 302af35..9fa5afa 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_interceptor.h
+++ b/content/browser/service_worker/service_worker_navigation_loader_interceptor.h
@@ -11,19 +11,36 @@
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/navigation_subresource_loader_params.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/common/child_process_host.h"
 #include "content/public/common/resource_type.h"
 #include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 
 namespace content {
 
-struct NavigationRequestInfo;
 class ServiceWorkerNavigationHandle;
 
+struct ServiceWorkerNavigationLoaderInterceptorParams {
+  // For all clients:
+  ResourceType resource_type = ResourceType::kMainFrame;
+  bool skip_service_worker = false;
+
+  // For windows:
+  bool is_main_frame = false;
+  // Whether all ancestor frames of the frame that is navigating have a secure
+  // origin. True for main frames.
+  bool are_ancestors_secure = false;
+  int frame_tree_node_id = RenderFrameHost::kNoFrameTreeNodeId;
+
+  // For web workers:
+  int process_id = ChildProcessHost::kInvalidUniqueID;
+};
+
 // This class is a work in progress for https://crbug.com/824858. It is used
 // only when NavigationLoaderOnUI is enabled.
 //
-// Handles navigations for service worker clients (for now just documents, not
-// web workers). Lives on the UI thread.
+// Handles navigations for service worker clients (windows and web workers).
+// Lives on the UI thread.
 //
 // The corresponding legacy class is ServiceWorkerControlleeRequestHandler which
 // lives on the IO thread. Currently, this class just delegates to the
@@ -32,7 +49,7 @@
     : public NavigationLoaderInterceptor {
  public:
   ServiceWorkerNavigationLoaderInterceptor(
-      const NavigationRequestInfo& request_info,
+      const ServiceWorkerNavigationLoaderInterceptorParams& params,
       ServiceWorkerNavigationHandle* handle);
   ~ServiceWorkerNavigationLoaderInterceptor() override;
 
@@ -74,10 +91,7 @@
   // |handle_| owns |this|.
   ServiceWorkerNavigationHandle* const handle_;
 
-  const bool are_ancestors_secure_;
-  const int frame_tree_node_id_;
-  const ResourceType resource_type_;
-  const bool skip_service_worker_;
+  const ServiceWorkerNavigationLoaderInterceptorParams params_;
 
   base::Optional<SubresourceLoaderParams> subresource_loader_params_;
 
diff --git a/content/browser/service_worker/service_worker_request_handler.cc b/content/browser/service_worker/service_worker_request_handler.cc
index 5f7efd7..fa33b20 100644
--- a/content/browser/service_worker/service_worker_request_handler.cc
+++ b/content/browser/service_worker/service_worker_request_handler.cc
@@ -52,8 +52,16 @@
     return nullptr;
   }
 
+  ServiceWorkerNavigationLoaderInterceptorParams params;
+  params.resource_type = request_info.is_main_frame ? ResourceType::kMainFrame
+                                                    : ResourceType::kSubFrame;
+  params.skip_service_worker = request_info.begin_params->skip_service_worker;
+  params.is_main_frame = request_info.is_main_frame;
+  params.are_ancestors_secure = request_info.are_ancestors_secure;
+  params.frame_tree_node_id = request_info.frame_tree_node_id;
+
   return std::make_unique<ServiceWorkerNavigationLoaderInterceptor>(
-      request_info, navigation_handle);
+      params, navigation_handle);
 }
 
 // static
@@ -98,14 +106,16 @@
 
 // static
 std::unique_ptr<NavigationLoaderInterceptor>
-ServiceWorkerRequestHandler::CreateForWorker(
+ServiceWorkerRequestHandler::CreateForWorkerUI(
     const network::ResourceRequest& resource_request,
-    ServiceWorkerProviderHost* host) {
-  DCHECK(host);
-  DCHECK(resource_request.resource_type ==
-             static_cast<int>(ResourceType::kWorker) ||
-         resource_request.resource_type ==
-             static_cast<int>(ResourceType::kSharedWorker))
+    int process_id,
+    ServiceWorkerNavigationHandle* navigation_handle) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  auto resource_type =
+      static_cast<ResourceType>(resource_request.resource_type);
+  DCHECK(resource_type == ResourceType::kWorker ||
+         resource_type == ResourceType::kSharedWorker)
       << resource_request.resource_type;
 
   // Create the handler even for insecure HTTP since it's used in the
@@ -115,9 +125,62 @@
     return nullptr;
   }
 
+  ServiceWorkerNavigationLoaderInterceptorParams params;
+  params.resource_type = resource_type;
+  params.skip_service_worker = resource_request.skip_service_worker;
+  params.process_id = process_id;
+
+  return std::make_unique<ServiceWorkerNavigationLoaderInterceptor>(
+      params, navigation_handle);
+}
+
+// static
+std::unique_ptr<NavigationLoaderInterceptor>
+ServiceWorkerRequestHandler::CreateForWorkerIO(
+    const network::ResourceRequest& resource_request,
+    int process_id,
+    ServiceWorkerNavigationHandleCore* navigation_handle_core) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+
+  // Create the handler even for insecure HTTP since it's used in the
+  // case of redirect to HTTPS.
+  if (!resource_request.url.SchemeIsHTTPOrHTTPS() &&
+      !OriginCanAccessServiceWorkers(resource_request.url)) {
+    return nullptr;
+  }
+
+  auto provider_info = blink::mojom::ServiceWorkerProviderInfoForClient::New();
+  if (!navigation_handle_core->context_wrapper())
+    return nullptr;
+  ServiceWorkerContextCore* context =
+      navigation_handle_core->context_wrapper()->context();
+  if (!context)
+    return nullptr;
+
+  auto resource_type =
+      static_cast<ResourceType>(resource_request.resource_type);
+  auto provider_type = blink::mojom::ServiceWorkerProviderType::kUnknown;
+  switch (resource_type) {
+    case ResourceType::kWorker:
+      provider_type =
+          blink::mojom::ServiceWorkerProviderType::kForDedicatedWorker;
+      break;
+    case ResourceType::kSharedWorker:
+      provider_type = blink::mojom::ServiceWorkerProviderType::kForSharedWorker;
+      break;
+    default:
+      NOTREACHED() << resource_request.resource_type;
+      return nullptr;
+  }
+
+  // Initialize the SWProviderHost.
+  base::WeakPtr<ServiceWorkerProviderHost> host =
+      ServiceWorkerProviderHost::PreCreateForWebWorker(
+          context->AsWeakPtr(), process_id, provider_type, &provider_info);
+  navigation_handle_core->OnCreatedProviderHost(host, std::move(provider_info));
+
   return std::make_unique<ServiceWorkerControlleeRequestHandler>(
-      host->context(), host->AsWeakPtr(),
-      static_cast<ResourceType>(resource_request.resource_type),
+      context->AsWeakPtr(), host, resource_type,
       resource_request.skip_service_worker);
 }
 
diff --git a/content/browser/service_worker/service_worker_request_handler.h b/content/browser/service_worker/service_worker_request_handler.h
index 25b886eb..399eda9 100644
--- a/content/browser/service_worker/service_worker_request_handler.h
+++ b/content/browser/service_worker/service_worker_request_handler.h
@@ -46,11 +46,21 @@
       const NavigationRequestInfo& request_info,
       base::WeakPtr<ServiceWorkerProviderHost>* out_provider_host);
 
-  // Same as above but for a dedicated worker or shared worker. Called on the IO
+  // Returns a loader interceptor for a dedicated worker or shared worker. May
+  // return nullptr if the worker cannot use service workers. Called on the IO
   // thread.
-  static std::unique_ptr<NavigationLoaderInterceptor> CreateForWorker(
+  static std::unique_ptr<NavigationLoaderInterceptor> CreateForWorkerUI(
       const network::ResourceRequest& resource_request,
-      ServiceWorkerProviderHost* host);
+      int process_id,
+      ServiceWorkerNavigationHandle* navigation_handle);
+
+  // Returns a loader interceptor for a dedicated worker or shared worker. May
+  // return nullptr if the worker cannot use service workers. Called on the UI
+  // thread.
+  static std::unique_ptr<NavigationLoaderInterceptor> CreateForWorkerIO(
+      const network::ResourceRequest& resource_request,
+      int process_id,
+      ServiceWorkerNavigationHandleCore* navigation_handle_core);
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ServiceWorkerRequestHandler);
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 86a38ab5..6b5be14 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -1422,8 +1422,8 @@
       GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
       GenURL("a.com", "/title1.html"), true));
 
-  // Navigate to the same subframe document from a different top frame top frame
-  // origin. It should be a cache miss.
+  // Navigate to the same subframe document from a different top frame origin.
+  // It should be a cache miss.
   EXPECT_FALSE(NavigationResourceCached(
       GenURL("b.com", "/navigation_controller/page_with_iframe.html"),
       GenURL("a.com", "/title1.html"), false));
@@ -1431,10 +1431,48 @@
 
 // See: http://crbug.com/983931
 #if defined(OS_ANDROID)
+#define MAYBE_SubframeNavigationResources DISABLED_SubframeNavigationResources
+#else
+#define MAYBE_SubframeNavigationResources SubframeNavigationResources
+#endif
+IN_PROC_BROWSER_TEST_F(WebContentsSplitCacheWithFrameOriginBrowserTest,
+                       MAYBE_SubframeNavigationResources) {
+  // Navigate for the first time, and it's not cached.
+  NavigationResourceCached(
+      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
+      GenURL("a.com", "/title1.html"), false);
+
+  // The second time it should be a cache hit.
+  NavigationResourceCached(
+      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
+      GenURL("a.com", "/title1.html"), true);
+
+  // Navigate to the same subframe document from a different top frame origin.
+  // It should be a cache miss.
+  NavigationResourceCached(
+      GenURL("b.com", "/navigation_controller/page_with_iframe.html"),
+      GenURL("a.com", "/title1.html"), false);
+
+  // Navigate the subframe to a.com/redirect_to_d which redirects to
+  // d.com/title1.html.
+  NavigationResourceCached(
+      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
+      GenURL("a.com", "/redirect_to_d"), false);
+
+  // Navigate to d.com directly. The resource should be cached due to the
+  // earlier redirected navigation.
+  NavigationResourceCached(
+      GenURL("a.com", "/navigation_controller/page_with_iframe.html"),
+      GenURL("d.com", "/title1.html"), true);
+}
+
+// See: http://crbug.com/983931
+#if defined(OS_ANDROID)
 #define MAYBE_SplitCacheDedicatedWorkers DISABLED_SplitCacheDedicatedWorkers
 #else
 #define MAYBE_SplitCacheDedicatedWorkers SplitCacheDedicatedWorkers
 #endif
+
 IN_PROC_BROWSER_TEST_P(WebContentsSplitCacheBrowserTestEnabled,
                        MAYBE_SplitCacheDedicatedWorkers) {
   // Load 3p.com/script from a.com's worker. The first time it's loaded from the
diff --git a/content/browser/worker_host/dedicated_worker_host.cc b/content/browser/worker_host/dedicated_worker_host.cc
index 4dcbdaa..7ef1dfd 100644
--- a/content/browser/worker_host/dedicated_worker_host.cc
+++ b/content/browser/worker_host/dedicated_worker_host.cc
@@ -15,6 +15,7 @@
 #include "content/browser/frame_host/render_frame_host_impl.h"
 #include "content/browser/interface_provider_filtering.h"
 #include "content/browser/renderer_interface_binders.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/websockets/websocket_connector_impl.h"
 #include "content/browser/worker_host/worker_script_fetch_initiator.h"
@@ -108,13 +109,16 @@
     appcache_handle_ = std::make_unique<AppCacheNavigationHandle>(
         storage_partition_impl->GetAppCacheService(), worker_process_id_);
 
+    service_worker_handle_ = std::make_unique<ServiceWorkerNavigationHandle>(
+        storage_partition_impl->GetServiceWorkerContext());
+
     WorkerScriptFetchInitiator::Start(
         worker_process_id_, script_url, request_initiator_origin,
         credentials_mode, std::move(outside_fetch_client_settings_object),
         ResourceType::kWorker,
         storage_partition_impl->GetServiceWorkerContext(),
-        appcache_handle_->core(), std::move(blob_url_loader_factory), nullptr,
-        storage_partition_impl,
+        service_worker_handle_.get(), appcache_handle_->core(),
+        std::move(blob_url_loader_factory), nullptr, storage_partition_impl,
         base::BindOnce(&DedicatedWorkerHost::DidStartScriptLoad,
                        weak_factory_.GetWeakPtr()));
   }
@@ -136,10 +140,6 @@
   // Called from WorkerScriptFetchInitiator. Continues starting the dedicated
   // worker in the renderer process.
   //
-  // |service_worker_provider_info| is sent to the renderer process and contains
-  // information about its ServiceWorkerProviderHost, the browser-side host for
-  // supporting the dedicated worker as a service worker client.
-  //
   // |main_script_load_params| is sent to the renderer process and to be used to
   // load the dedicated worker main script pre-requested by the browser process.
   //
@@ -153,8 +153,6 @@
   // a ServiceWorker object about the controller is prepared, it is registered
   // to |controller_service_worker_object_host|.
   void DidStartScriptLoad(
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr
-          service_worker_provider_info,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
           subresource_loader_factories,
       blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
@@ -170,8 +168,11 @@
       return;
     }
 
-    auto* worker_process_host = RenderProcessHost::FromID(worker_process_id_);
-    if (!worker_process_host) {
+    // TODO(cammie): Change this approach when we support shared workers
+    // creating dedicated workers, as there might be no ancestor frame.
+    RenderFrameHostImpl* ancestor_render_frame_host =
+        GetAncestorRenderFrameHost();
+    if (!ancestor_render_frame_host) {
       client_->OnScriptLoadStartFailed();
       return;
     }
@@ -181,7 +182,7 @@
         pending_default_factory;
     CreateNetworkFactory(
         pending_default_factory.InitWithNewPipeAndPassReceiver(),
-        worker_process_host);
+        ancestor_render_frame_host);
     subresource_loader_factories->pending_default_factory() =
         std::move(pending_default_factory);
 
@@ -197,7 +198,7 @@
       service_worker_state = controller->object_info->state;
     }
 
-    client_->OnScriptLoadStarted(std::move(service_worker_provider_info),
+    client_->OnScriptLoadStarted(service_worker_handle_->TakeProviderInfo(),
                                  std::move(main_script_load_params),
                                  std::move(subresource_loader_factories),
                                  std::move(controller));
@@ -217,20 +218,20 @@
   }
 
   void CreateNetworkFactory(network::mojom::URLLoaderFactoryRequest request,
-                            RenderProcessHost* worker_process_host) {
+                            RenderFrameHostImpl* render_frame_host) {
     DCHECK_CURRENTLY_ON(BrowserThread::UI);
+    DCHECK(render_frame_host);
     network::mojom::TrustedURLLoaderHeaderClientPtrInfo no_header_client;
 
     // Get the origin of the frame tree's root to use as top-frame origin.
-    // TODO(cammie): Change this approach when we support shared workers
-    // creating dedicated workers, as there might be no ancestor frame.
-    RenderFrameHostImpl* ancestor_render_frame_host =
-        GetAncestorRenderFrameHost();
-    url::Origin top_frame_origin(ancestor_render_frame_host->frame_tree_node()
+    // TODO(crbug.com/986167): Resolve issue of potential race condition.
+    url::Origin top_frame_origin(render_frame_host->frame_tree_node()
                                      ->frame_tree()
                                      ->root()
                                      ->current_origin());
 
+    RenderProcessHost* worker_process_host = render_frame_host->GetProcess();
+    DCHECK(worker_process_host);
     worker_process_host->CreateURLLoaderFactory(
         origin_, nullptr /* preferences */,
         net::NetworkIsolationKey(top_frame_origin, origin_),
@@ -314,6 +315,7 @@
   blink::mojom::DedicatedWorkerHostFactoryClientPtr client_;
 
   std::unique_ptr<AppCacheNavigationHandle> appcache_handle_;
+  std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle_;
 
   service_manager::BinderRegistry registry_;
 
diff --git a/content/browser/worker_host/shared_worker_host.cc b/content/browser/worker_host/shared_worker_host.cc
index 37e9b00..c11c026 100644
--- a/content/browser/worker_host/shared_worker_host.cc
+++ b/content/browser/worker_host/shared_worker_host.cc
@@ -16,6 +16,7 @@
 #include "content/browser/devtools/shared_worker_devtools_manager.h"
 #include "content/browser/interface_provider_filtering.h"
 #include "content/browser/renderer_interface_binders.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/worker_host/shared_worker_content_settings_proxy_impl.h"
 #include "content/browser/worker_host/shared_worker_instance.h"
@@ -166,8 +167,6 @@
 
 void SharedWorkerHost::Start(
     blink::mojom::SharedWorkerFactoryPtr factory,
-    blink::mojom::ServiceWorkerProviderInfoForClientPtr
-        service_worker_provider_info,
     blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
     std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
         subresource_loader_factories,
@@ -175,7 +174,6 @@
     base::WeakPtr<ServiceWorkerObjectHost>
         controller_service_worker_object_host) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
-  DCHECK(service_worker_provider_info);
   DCHECK(main_script_load_params);
   DCHECK(subresource_loader_factories);
   DCHECK(!subresource_loader_factories->pending_default_factory());
@@ -246,7 +244,7 @@
   factory_->CreateSharedWorker(
       std::move(info), pause_on_start, devtools_worker_token,
       std::move(renderer_preferences), std::move(preference_watcher_request),
-      std::move(content_settings), std::move(service_worker_provider_info),
+      std::move(content_settings), service_worker_handle_->TakeProviderInfo(),
       appcache_handle_
           ? base::make_optional(appcache_handle_->appcache_host_id())
           : base::nullopt,
@@ -520,6 +518,12 @@
   appcache_handle_ = std::move(appcache_handle);
 }
 
+void SharedWorkerHost::SetServiceWorkerHandle(
+    std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+  service_worker_handle_ = std::move(service_worker_handle);
+}
+
 void SharedWorkerHost::OnClientConnectionLost() {
   // We'll get a notification for each dropped connection.
   for (auto it = clients_.begin(); it != clients_.end(); ++it) {
diff --git a/content/browser/worker_host/shared_worker_host.h b/content/browser/worker_host/shared_worker_host.h
index 05f2ade..9395da7 100644
--- a/content/browser/worker_host/shared_worker_host.h
+++ b/content/browser/worker_host/shared_worker_host.h
@@ -38,6 +38,7 @@
 namespace content {
 
 class AppCacheNavigationHandle;
+class ServiceWorkerNavigationHandle;
 class ServiceWorkerObjectHost;
 class SharedWorkerContentSettingsProxyImpl;
 class SharedWorkerInstance;
@@ -69,10 +70,6 @@
 
   // Starts the SharedWorker in the renderer process.
   //
-  // |service_worker_provider_info| is sent to the renderer process and contains
-  // information about its ServiceWorkerProviderHost, the browser-side host for
-  // supporting the shared worker as a service worker client.
-  //
   // |main_script_load_params| is sent to the renderer process and to be used to
   // load the shared worker main script pre-requested by the browser process.
   //
@@ -86,8 +83,6 @@
   // to |controller_service_worker_object_host|.
   void Start(
       blink::mojom::SharedWorkerFactoryPtr factory,
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr
-          service_worker_provider_info,
       blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
           subresource_loader_factories,
@@ -114,6 +109,8 @@
 
   void SetAppCacheHandle(
       std::unique_ptr<AppCacheNavigationHandle> appcache_handle);
+  void SetServiceWorkerHandle(
+      std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle);
 
   SharedWorkerInstance* instance() { return instance_.get(); }
   int worker_process_id() const { return worker_process_id_; }
@@ -204,6 +201,8 @@
   // renderer after main script loading finishes.
   std::unique_ptr<AppCacheNavigationHandle> appcache_handle_;
 
+  std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle_;
+
   Phase phase_ = Phase::kInitial;
 
   base::WeakPtrFactory<SharedWorkerHost> weak_factory_{this};
diff --git a/content/browser/worker_host/shared_worker_host_unittest.cc b/content/browser/worker_host/shared_worker_host_unittest.cc
index 25d7af3..221e4f4 100644
--- a/content/browser/worker_host/shared_worker_host_unittest.cc
+++ b/content/browser/worker_host/shared_worker_host_unittest.cc
@@ -13,6 +13,7 @@
 #include "content/browser/appcache/chrome_appcache_service.h"
 #include "content/browser/navigation_subresource_loader_params.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
 #include "content/browser/worker_host/mock_shared_worker.h"
 #include "content/browser/worker_host/shared_worker_connector_impl.h"
 #include "content/browser/worker_host/shared_worker_instance.h"
@@ -76,13 +77,6 @@
 
   void StartWorker(SharedWorkerHost* host,
                    blink::mojom::SharedWorkerFactoryPtr factory) {
-    auto provider_info =
-        blink::mojom::ServiceWorkerProviderInfoForClient::New();
-    ServiceWorkerProviderHost::PreCreateForWebWorker(
-        helper_->context()->AsWeakPtr(), mock_render_process_host_.GetID(),
-        blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
-        &provider_info);
-
     auto main_script_load_params =
         blink::mojom::WorkerMainScriptLoadParams::New();
     auto subresource_loader_factories =
@@ -97,8 +91,21 @@
     subresource_loader_params->pending_appcache_loader_factory =
         loader_factory_ptr.PassInterface();
 
-    host->Start(std::move(factory), std::move(provider_info),
-                std::move(main_script_load_params),
+    // Set up for service worker.
+    auto service_worker_handle =
+        std::make_unique<ServiceWorkerNavigationHandle>(
+            helper_->context_wrapper());
+    auto provider_info =
+        blink::mojom::ServiceWorkerProviderInfoForClient::New();
+    base::WeakPtr<ServiceWorkerProviderHost> service_worker_host =
+        ServiceWorkerProviderHost::PreCreateForWebWorker(
+            helper_->context()->AsWeakPtr(), mock_render_process_host_.GetID(),
+            blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
+            &provider_info);
+    service_worker_handle->OnCreatedProviderHost(std::move(provider_info));
+    host->SetServiceWorkerHandle(std::move(service_worker_handle));
+
+    host->Start(std::move(factory), std::move(main_script_load_params),
                 std::move(subresource_loader_factories),
                 nullptr /* controller */,
                 nullptr /* controller_service_worker_object_host */);
diff --git a/content/browser/worker_host/shared_worker_service_impl.cc b/content/browser/worker_host/shared_worker_service_impl.cc
index c8af1e9a..d473c56 100644
--- a/content/browser/worker_host/shared_worker_service_impl.cc
+++ b/content/browser/worker_host/shared_worker_service_impl.cc
@@ -19,6 +19,7 @@
 #include "content/browser/appcache/appcache_navigation_handle.h"
 #include "content/browser/appcache/appcache_navigation_handle_core.h"
 #include "content/browser/file_url_loader_factory.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/url_loader_factory_getter.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -38,7 +39,6 @@
 #include "services/network/public/cpp/features.h"
 #include "third_party/blink/public/common/messaging/message_port_channel.h"
 #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/mojom/worker/shared_worker_client.mojom.h"
 #include "third_party/blink/public/mojom/worker/shared_worker_info.mojom.h"
 #include "url/origin.h"
@@ -213,6 +213,11 @@
       appcache_handle->core();
   weak_host->SetAppCacheHandle(std::move(appcache_handle));
 
+  auto service_worker_handle = std::make_unique<ServiceWorkerNavigationHandle>(
+      storage_partition_->GetServiceWorkerContext());
+  auto* service_worker_handle_raw = service_worker_handle.get();
+  weak_host->SetServiceWorkerHandle(std::move(service_worker_handle));
+
   // Fetch classic shared worker script with "same-origin" credentials mode.
   // https://html.spec.whatwg.org/C/#fetch-a-classic-worker-script
   //
@@ -225,8 +230,9 @@
       weak_host->instance()->constructor_origin(), credentials_mode,
       std::move(outside_fetch_client_settings_object),
       ResourceType::kSharedWorker, service_worker_context_,
-      appcache_handle_core, std::move(blob_url_loader_factory),
-      url_loader_factory_override_, storage_partition_,
+      service_worker_handle_raw, appcache_handle_core,
+      std::move(blob_url_loader_factory), url_loader_factory_override_,
+      storage_partition_,
       base::BindOnce(&SharedWorkerServiceImpl::DidCreateScriptLoader,
                      weak_factory_.GetWeakPtr(), std::move(instance), weak_host,
                      std::move(client), client_process_id, frame_id,
@@ -240,8 +246,6 @@
     int process_id,
     int frame_id,
     const blink::MessagePortChannel& message_port,
-    blink::mojom::ServiceWorkerProviderInfoForClientPtr
-        service_worker_provider_info,
     std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
         subresource_loader_factories,
     blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
@@ -260,7 +264,6 @@
 
   StartWorker(std::move(instance), std::move(host), std::move(client),
               process_id, frame_id, message_port,
-              std::move(service_worker_provider_info),
               std::move(subresource_loader_factories),
               std::move(main_script_load_params), std::move(controller),
               std::move(controller_service_worker_object_host));
@@ -273,8 +276,6 @@
     int client_process_id,
     int frame_id,
     const blink::MessagePortChannel& message_port,
-    blink::mojom::ServiceWorkerProviderInfoForClientPtr
-        service_worker_provider_info,
     std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
         subresource_loader_factories,
     blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
@@ -303,8 +304,7 @@
   blink::mojom::SharedWorkerFactoryPtr factory;
   BindInterface(worker_process_host, &factory);
 
-  host->Start(std::move(factory), std::move(service_worker_provider_info),
-              std::move(main_script_load_params),
+  host->Start(std::move(factory), std::move(main_script_load_params),
               std::move(subresource_loader_factories), std::move(controller),
               std::move(controller_service_worker_object_host));
   host->AddClient(std::move(client), client_process_id, frame_id, message_port);
diff --git a/content/browser/worker_host/shared_worker_service_impl.h b/content/browser/worker_host/shared_worker_service_impl.h
index c4d2653..b9a650f 100644
--- a/content/browser/worker_host/shared_worker_service_impl.h
+++ b/content/browser/worker_host/shared_worker_service_impl.h
@@ -18,7 +18,6 @@
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 #include "third_party/blink/public/mojom/loader/fetch_client_settings_object.mojom.h"
-#include "third_party/blink/public/mojom/service_worker/service_worker_provider.mojom.h"
 #include "third_party/blink/public/mojom/worker/shared_worker_connector.mojom.h"
 #include "third_party/blink/public/mojom/worker/shared_worker_factory.mojom.h"
 #include "third_party/blink/public/mojom/worker/worker_main_script_load_params.mojom.h"
@@ -97,8 +96,6 @@
       int client_process_id,
       int frame_id,
       const blink::MessagePortChannel& message_port,
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr
-          service_worker_provider_info,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
           subresource_loader_factories,
       blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
@@ -113,8 +110,6 @@
       int client_process_id,
       int frame_id,
       const blink::MessagePortChannel& message_port,
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr
-          service_worker_provider_info,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
           subresource_loader_factories,
       blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
diff --git a/content/browser/worker_host/worker_script_fetch_initiator.cc b/content/browser/worker_host/worker_script_fetch_initiator.cc
index 503d26a9..3ca89a1 100644
--- a/content/browser/worker_host/worker_script_fetch_initiator.cc
+++ b/content/browser/worker_host/worker_script_fetch_initiator.cc
@@ -19,8 +19,11 @@
 #include "content/browser/data_url_loader_factory.h"
 #include "content/browser/file_url_loader_factory.h"
 #include "content/browser/loader/browser_initiated_resource_request.h"
+#include "content/browser/loader/navigation_url_loader_impl.h"
 #include "content/browser/navigation_subresource_loader_params.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
+#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
 #include "content/browser/storage_partition_impl.h"
 #include "content/browser/url_loader_factory_getter.h"
 #include "content/browser/web_contents/web_contents_impl.h"
@@ -51,6 +54,24 @@
 
 namespace content {
 
+namespace {
+
+// Runs |task| on the thread specified by |thread_id| if already on that thread,
+// otherwise posts a task to that thread.
+void RunOrPostTask(const base::Location& from_here,
+                   BrowserThread::ID thread_id,
+                   base::OnceClosure task) {
+  if (BrowserThread::CurrentlyOn(thread_id)) {
+    std::move(task).Run();
+    return;
+  }
+
+  base::PostTaskWithTraits(from_here, {thread_id}, std::move(task));
+}
+
+}  // namespace
+
+// static
 void WorkerScriptFetchInitiator::Start(
     int worker_process_id,
     const GURL& script_url,
@@ -60,6 +81,7 @@
         outside_fetch_client_settings_object,
     ResourceType resource_type,
     scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+    ServiceWorkerNavigationHandle* service_worker_handle,
     AppCacheNavigationHandleCore* appcache_handle_core,
     scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
     scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_override,
@@ -155,8 +177,22 @@
 
   AddAdditionalRequestHeaders(resource_request.get(), browser_context);
 
-  // Bounce to the IO thread to setup service worker and appcache support in
-  // case the request for the worker script will need to be intercepted by them.
+  // When navigation on UI is enabled, service worker and appcache work on the
+  // UI thread.
+  if (NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    CreateScriptLoaderOnUI(
+        worker_process_id, std::move(resource_request), storage_partition,
+        std::move(factory_bundle_for_browser),
+        std::move(subresource_loader_factories),
+        std::move(service_worker_context), service_worker_handle,
+        appcache_handle_core, std::move(blob_url_loader_factory),
+        std::move(url_loader_factory_override), std::move(callback));
+    return;
+  }
+
+  // Otherwise, bounce to the IO thread to setup service worker and appcache
+  // support in case the request for the worker script will need to be
+  // intercepted by them.
   //
   // This passes |resource_context| to the IO thread. |resource_context| will
   // not be destroyed before the task runs, because the shutdown sequence is:
@@ -172,13 +208,20 @@
           storage_partition->url_loader_factory_getter(),
           std::move(factory_bundle_for_browser),
           std::move(subresource_loader_factories), resource_context,
-          std::move(service_worker_context), appcache_handle_core,
+          std::move(service_worker_context), service_worker_handle->core(),
+          appcache_handle_core,
           blob_url_loader_factory ? blob_url_loader_factory->Clone() : nullptr,
           url_loader_factory_override ? url_loader_factory_override->Clone()
                                       : nullptr,
           std::move(callback)));
 }
 
+BrowserThread::ID WorkerScriptFetchInitiator::GetLoaderThreadID() {
+  return NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()
+             ? BrowserThread::UI
+             : BrowserThread::IO;
+}
+
 std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
 WorkerScriptFetchInitiator::CreateFactoryBundle(
     int worker_process_id,
@@ -249,6 +292,89 @@
   SetFetchMetadataHeadersForBrowserInitiatedRequest(resource_request);
 }
 
+void WorkerScriptFetchInitiator::CreateScriptLoaderOnUI(
+    int worker_process_id,
+    std::unique_ptr<network::ResourceRequest> resource_request,
+    StoragePartitionImpl* storage_partition,
+    std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
+        factory_bundle_for_browser_info,
+    std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
+        subresource_loader_factories,
+    scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+    ServiceWorkerNavigationHandle* service_worker_handle,
+    AppCacheNavigationHandleCore* appcache_handle_core,
+    scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_override,
+    CompletionCallback callback) {
+  DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  // Create the URL loader factory for WorkerScriptLoaderFactory to use to load
+  // the main script.
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
+  if (blob_url_loader_factory) {
+    // If we have a blob_url_loader_factory just use that directly rather than
+    // creating a new URLLoaderFactoryBundle.
+    url_loader_factory = std::move(blob_url_loader_factory);
+  } else if (url_loader_factory_override) {
+    // For unit tests.
+    url_loader_factory = std::move(url_loader_factory_override);
+  } else {
+    // Add the default factory to the bundle for browser.
+    DCHECK(factory_bundle_for_browser_info);
+
+    // Get the direct network factory. This doesn't support reconnection to the
+    // network service after a crash, but it's OK since it's used only for a
+    // single request to fetch the worker's main script during startup. If the
+    // network service crashes, worker startup should simply fail.
+    network::mojom::URLLoaderFactoryPtr network_factory_ptr;
+    auto network_factory =
+        storage_partition->GetURLLoaderFactoryForBrowserProcess();
+    network_factory->Clone(mojo::MakeRequest(&network_factory_ptr));
+
+    factory_bundle_for_browser_info->pending_default_factory() =
+        network_factory_ptr.PassInterface();
+    url_loader_factory = base::MakeRefCounted<blink::URLLoaderFactoryBundle>(
+        std::move(factory_bundle_for_browser_info));
+  }
+
+  base::WeakPtr<AppCacheHost> appcache_host =
+      appcache_handle_core ? appcache_handle_core->host()->GetWeakPtr()
+                           : nullptr;
+
+  // Start loading a web worker main script.
+  // TODO(nhiroki): Figure out what we should do in |wc_getter| for loading web
+  // worker's main script. Returning the WebContents of the closest ancestor's
+  // frame is a possible option, but it doesn't work when a shared worker
+  // creates a dedicated worker after the closest ancestor's frame is gone. The
+  // frame tree node ID has the same issue.
+  base::RepeatingCallback<WebContents*()> wc_getter =
+      base::BindRepeating([]() -> WebContents* { return nullptr; });
+  std::vector<std::unique_ptr<URLLoaderThrottle>> throttles =
+      GetContentClient()->browser()->CreateURLLoaderThrottles(
+          *resource_request, storage_partition->browser_context(), wc_getter,
+          nullptr /* navigation_ui_data */,
+          RenderFrameHost::kNoFrameTreeNodeId);
+
+  // Create a BrowserContext getter using |service_worker_context|.
+  // This context is aware of shutdown and safely returns a nullptr
+  // instead of a destroyed BrowserContext in that case.
+  auto browser_context_getter =
+      base::BindRepeating(&ServiceWorkerContextWrapper::browser_context,
+                          std::move(service_worker_context));
+
+  WorkerScriptFetcher::CreateAndStart(
+      std::make_unique<WorkerScriptLoaderFactory>(
+          worker_process_id, service_worker_handle,
+          /*service_worker_handle_core=*/nullptr, std::move(appcache_host),
+          browser_context_getter,
+          base::RepeatingCallback<ResourceContext*(void)>(),
+          std::move(url_loader_factory)),
+      std::move(throttles), std::move(resource_request),
+      base::BindOnce(WorkerScriptFetchInitiator::DidCreateScriptLoader,
+                     std::move(callback),
+                     std::move(subresource_loader_factories)));
+}
+
 void WorkerScriptFetchInitiator::CreateScriptLoaderOnIO(
     int worker_process_id,
     std::unique_ptr<network::ResourceRequest> resource_request,
@@ -259,6 +385,7 @@
         subresource_loader_factories,
     ResourceContext* resource_context,
     scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+    ServiceWorkerNavigationHandleCore* service_worker_handle_core,
     AppCacheNavigationHandleCore* appcache_handle_core,
     std::unique_ptr<network::SharedURLLoaderFactoryInfo>
         blob_url_loader_factory_info,
@@ -267,6 +394,7 @@
     CompletionCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   DCHECK(resource_context);
+  DCHECK(service_worker_handle_core);
 
   auto resource_type =
       static_cast<ResourceType>(resource_request->resource_type);
@@ -284,12 +412,6 @@
       break;
   }
 
-  // Set up for service worker.
-  auto provider_info = blink::mojom::ServiceWorkerProviderInfoForClient::New();
-  base::WeakPtr<ServiceWorkerProviderHost> service_worker_host =
-      service_worker_context->PreCreateHostForWorker(
-          worker_process_id, provider_type, &provider_info);
-
   // Create the URL loader factory for WorkerScriptLoaderFactory to use to load
   // the main script.
   scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory;
@@ -350,25 +472,26 @@
 
   WorkerScriptFetcher::CreateAndStart(
       std::make_unique<WorkerScriptLoaderFactory>(
-          worker_process_id, std::move(service_worker_host),
-          std::move(appcache_host), resource_context_getter,
-          std::move(url_loader_factory)),
+          worker_process_id,
+          /*service_worker_handle=*/nullptr, service_worker_handle_core,
+          std::move(appcache_host),
+          base::RepeatingCallback<BrowserContext*(void)>(),
+          resource_context_getter, std::move(url_loader_factory)),
       std::move(throttles), std::move(resource_request),
-      base::BindOnce(WorkerScriptFetchInitiator::DidCreateScriptLoaderOnIO,
-                     std::move(callback), std::move(provider_info),
+      base::BindOnce(WorkerScriptFetchInitiator::DidCreateScriptLoader,
+                     std::move(callback),
                      std::move(subresource_loader_factories)));
 }
 
-void WorkerScriptFetchInitiator::DidCreateScriptLoaderOnIO(
+void WorkerScriptFetchInitiator::DidCreateScriptLoader(
     CompletionCallback callback,
-    blink::mojom::ServiceWorkerProviderInfoForClientPtr
-        service_worker_provider_info,
     std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
         subresource_loader_factories,
     blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
     base::Optional<SubresourceLoaderParams> subresource_loader_params,
     bool success) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  // This can be the UI thread or IO thread.
+  DCHECK_CURRENTLY_ON(GetLoaderThreadID());
 
   // If a URLLoaderFactory for AppCache is supplied, use that.
   if (subresource_loader_params &&
@@ -388,11 +511,10 @@
         subresource_loader_params->controller_service_worker_object_host;
   }
 
-  base::PostTaskWithTraits(
-      FROM_HERE, {BrowserThread::UI},
+  RunOrPostTask(
+      FROM_HERE, BrowserThread::UI,
       base::BindOnce(
-          std::move(callback), std::move(service_worker_provider_info),
-          std::move(subresource_loader_factories),
+          std::move(callback), std::move(subresource_loader_factories),
           std::move(main_script_load_params), std::move(controller),
           std::move(controller_service_worker_object_host), success));
 }
diff --git a/content/browser/worker_host/worker_script_fetch_initiator.h b/content/browser/worker_host/worker_script_fetch_initiator.h
index bc3f1bb..c2a1382 100644
--- a/content/browser/worker_host/worker_script_fetch_initiator.h
+++ b/content/browser/worker_host/worker_script_fetch_initiator.h
@@ -11,6 +11,7 @@
 
 #include "base/compiler_specific.h"
 #include "base/macros.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/common/resource_type.h"
 #include "content/public/common/url_loader_throttle.h"
 #include "services/network/public/cpp/resource_response.h"
@@ -36,6 +37,8 @@
 class BrowserContext;
 class ResourceContext;
 class ServiceWorkerContextWrapper;
+class ServiceWorkerNavigationHandle;
+class ServiceWorkerNavigationHandleCore;
 class ServiceWorkerObjectHost;
 class StoragePartitionImpl;
 class URLLoaderFactoryGetter;
@@ -44,10 +47,11 @@
 // PlzWorker:
 // WorkerScriptFetchInitiator is the entry point of browser-side script fetch
 // for WorkerScriptFetcher.
+// TODO(falken): These are all static functions, it should just be a namespace
+// or merged elsewhere.
 class WorkerScriptFetchInitiator {
  public:
   using CompletionCallback = base::OnceCallback<void(
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>,
       blink::mojom::WorkerMainScriptLoadParamsPtr,
       blink::mojom::ControllerServiceWorkerInfoPtr,
@@ -65,6 +69,7 @@
           outside_fetch_client_settings_object,
       ResourceType resource_type,
       scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+      ServiceWorkerNavigationHandle* service_worker_handle,
       AppCacheNavigationHandleCore* appcache_handle_core,
       scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
       scoped_refptr<network::SharedURLLoaderFactory>
@@ -72,6 +77,12 @@
       StoragePartitionImpl* storage_partition,
       CompletionCallback callback);
 
+  // Returns the BrowserThread::ID that the WorkerScriptLoaderFactory will be
+  // running on.
+  // TODO(crbug.com/824840): Remove this when non-NavigationLoaderOnUI code is
+  // removed.
+  static BrowserThread::ID GetLoaderThreadID();
+
  private:
   // Creates a loader factory bundle. Must be called on the UI thread.
   static std::unique_ptr<blink::URLLoaderFactoryBundleInfo> CreateFactoryBundle(
@@ -95,16 +106,32 @@
           subresource_loader_factories,
       ResourceContext* resource_context,
       scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+      ServiceWorkerNavigationHandleCore* service_worker_handle_core,
       AppCacheNavigationHandleCore* appcache_handle_core,
       std::unique_ptr<network::SharedURLLoaderFactoryInfo>
           blob_url_loader_factory_info,
       std::unique_ptr<network::SharedURLLoaderFactoryInfo>
           url_loader_factory_override_info,
       CompletionCallback callback);
-  static void DidCreateScriptLoaderOnIO(
+
+  static void CreateScriptLoaderOnUI(
+      int worker_process_id,
+      std::unique_ptr<network::ResourceRequest> resource_request,
+      StoragePartitionImpl* storage_partition,
+      std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
+          factory_bundle_for_browser_info,
+      std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
+          subresource_loader_factories,
+      scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
+      ServiceWorkerNavigationHandle* service_worker_handle,
+      AppCacheNavigationHandleCore* appcache_handle_core,
+      scoped_refptr<network::SharedURLLoaderFactory> blob_url_loader_factory,
+      scoped_refptr<network::SharedURLLoaderFactory>
+          url_loader_factory_override,
+      CompletionCallback callback);
+
+  static void DidCreateScriptLoader(
       CompletionCallback callback,
-      blink::mojom::ServiceWorkerProviderInfoForClientPtr
-          service_worker_provider_info,
       std::unique_ptr<blink::URLLoaderFactoryBundleInfo>
           subresource_loader_factories,
       blink::mojom::WorkerMainScriptLoadParamsPtr main_script_load_params,
diff --git a/content/browser/worker_host/worker_script_fetcher.cc b/content/browser/worker_host/worker_script_fetcher.cc
index 782d4c0..76315dc 100644
--- a/content/browser/worker_host/worker_script_fetcher.cc
+++ b/content/browser/worker_host/worker_script_fetcher.cc
@@ -6,6 +6,7 @@
 
 #include "base/feature_list.h"
 #include "content/browser/loader/navigation_url_loader_impl.h"
+#include "content/browser/worker_host/worker_script_fetch_initiator.h"
 #include "content/browser/worker_host/worker_script_loader.h"
 #include "content/browser/worker_host/worker_script_loader_factory.h"
 #include "content/common/throttling_url_loader.h"
@@ -55,7 +56,7 @@
     std::vector<std::unique_ptr<URLLoaderThrottle>> throttles,
     std::unique_ptr<network::ResourceRequest> resource_request,
     CreateAndStartCallback callback) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
   // This fetcher will delete itself. See the class level comment.
   (new WorkerScriptFetcher(std::move(script_loader_factory),
@@ -71,16 +72,16 @@
       resource_request_(std::move(resource_request)),
       callback_(std::move(callback)),
       response_url_loader_binding_(this) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
 }
 
 WorkerScriptFetcher::~WorkerScriptFetcher() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
 }
 
 void WorkerScriptFetcher::Start(
     std::vector<std::unique_ptr<URLLoaderThrottle>> throttles) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
 
   auto shared_url_loader_factory =
       base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
@@ -104,13 +105,13 @@
 
 void WorkerScriptFetcher::OnReceiveResponse(
     const network::ResourceResponseHead& response_head) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   response_head_ = response_head;
 }
 
 void WorkerScriptFetcher::OnStartLoadingResponseBody(
     mojo::ScopedDataPipeConsumerHandle response_body) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
 
   base::WeakPtr<WorkerScriptLoader> script_loader =
       script_loader_factory_->GetScriptLoader();
@@ -172,6 +173,7 @@
 void WorkerScriptFetcher::OnReceiveRedirect(
     const net::RedirectInfo& redirect_info,
     const network::ResourceResponseHead& response_head) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   redirect_infos_.push_back(redirect_info);
   redirect_response_heads_.push_back(response_head);
   url_loader_->FollowRedirect({}, /* removed_headers */
@@ -194,6 +196,7 @@
 
 void WorkerScriptFetcher::OnComplete(
     const network::URLLoaderCompletionStatus& status) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   // We can reach here only when loading fails before receiving a response_head.
   DCHECK_NE(net::OK, status.error_code);
   std::move(callback_).Run(nullptr /* main_script_load_params */,
diff --git a/content/browser/worker_host/worker_script_fetcher.h b/content/browser/worker_host/worker_script_fetcher.h
index 68305e4..f8b6c7e 100644
--- a/content/browser/worker_host/worker_script_fetcher.h
+++ b/content/browser/worker_host/worker_script_fetcher.h
@@ -32,7 +32,8 @@
 // resource loader in the renderer process will take them over.
 //
 // WorkerScriptFetcher deletes itself when the ownership of the loader and
-// client is passed to the renderer, or on failure. It lives on the IO thread.
+// client is passed to the renderer, or on failure. It lives on the IO or UI
+// thread.
 class WorkerScriptFetcher : public network::mojom::URLLoaderClient {
  public:
   using CreateAndStartCallback =
diff --git a/content/browser/worker_host/worker_script_loader.cc b/content/browser/worker_host/worker_script_loader.cc
index 62899f1..72ddfc9 100644
--- a/content/browser/worker_host/worker_script_loader.cc
+++ b/content/browser/worker_host/worker_script_loader.cc
@@ -5,18 +5,36 @@
 #include "content/browser/worker_host/worker_script_loader.h"
 
 #include "base/bind.h"
+#include "base/task/post_task.h"
 #include "content/browser/appcache/appcache_request_handler.h"
 #include "content/browser/loader/navigation_loader_interceptor.h"
 #include "content/browser/loader/navigation_url_loader_impl.h"
 #include "content/browser/loader/resource_dispatcher_host_impl.h"
-#include "content/browser/service_worker/service_worker_provider_host.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
+#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
 #include "content/browser/service_worker/service_worker_request_handler.h"
+#include "content/browser/worker_host/worker_script_fetch_initiator.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
 #include "content/public/browser/resource_context.h"
 #include "net/url_request/redirect_util.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace content {
 
+namespace {
+void CancelRequestOnIO(int process_id,
+                       int request_id,
+                       base::OnceClosure ui_continuation) {
+  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK(ResourceDispatcherHostImpl::Get());
+  ResourceDispatcherHostImpl::Get()->CancelRequest(process_id, request_id);
+  if (ui_continuation)
+    base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
+                             std::move(ui_continuation));
+}
+}  // namespace
+
 WorkerScriptLoader::WorkerScriptLoader(
     int process_id,
     int32_t routing_id,
@@ -24,8 +42,11 @@
     uint32_t options,
     const network::ResourceRequest& resource_request,
     network::mojom::URLLoaderClientPtr client,
-    base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
+    base::WeakPtr<ServiceWorkerNavigationHandle> service_worker_handle /* UI */,
+    base::WeakPtr<ServiceWorkerNavigationHandleCore>
+        service_worker_handle_core /* IO */,
     base::WeakPtr<AppCacheHost> appcache_host,
+    const BrowserContextGetter& browser_context_getter,
     const ResourceContextGetter& resource_context_getter,
     scoped_refptr<network::SharedURLLoaderFactory> default_loader_factory,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
@@ -35,22 +56,38 @@
       options_(options),
       resource_request_(resource_request),
       client_(std::move(client)),
-      service_worker_provider_host_(service_worker_provider_host),
+      service_worker_handle_(std::move(service_worker_handle)),
+      service_worker_handle_core_(std::move(service_worker_handle_core)),
+      browser_context_getter_(browser_context_getter),
       resource_context_getter_(resource_context_getter),
       default_loader_factory_(std::move(default_loader_factory)),
       traffic_annotation_(traffic_annotation),
       url_loader_client_binding_(this) {
-  if (service_worker_provider_host_) {
-    std::unique_ptr<NavigationLoaderInterceptor> service_worker_interceptor =
-        ServiceWorkerRequestHandler::CreateForWorker(
-            resource_request_, service_worker_provider_host_.get());
-    if (service_worker_interceptor)
-      interceptors_.push_back(std::move(service_worker_interceptor));
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
+
+  std::unique_ptr<NavigationLoaderInterceptor> service_worker_interceptor;
+  if (NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    if (!service_worker_handle_) {
+      // The DedicatedWorkerHost or SharedWorkerHost is already destroyed.
+      Abort();
+      return;
+    }
+    service_worker_interceptor = ServiceWorkerRequestHandler::CreateForWorkerUI(
+        resource_request_, process_id, service_worker_handle_.get());
+  } else {
+    if (!service_worker_handle_core_) {
+      // The DedicatedWorkerHost or SharedWorkerHost is already destroyed.
+      Abort();
+      return;
+    }
+    service_worker_interceptor = ServiceWorkerRequestHandler::CreateForWorkerIO(
+        resource_request_, process_id_, service_worker_handle_core_.get());
   }
 
-  // TODO(http://crbug.com/824858): Run interceptors on UI thread.
-  if (!NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled() &&
-      appcache_host) {
+  if (service_worker_interceptor)
+    interceptors_.push_back(std::move(service_worker_interceptor));
+
+  if (appcache_host) {
     std::unique_ptr<NavigationLoaderInterceptor> appcache_interceptor =
         AppCacheRequestHandler::InitializeForMainResourceNetworkService(
             resource_request_, appcache_host);
@@ -61,26 +98,46 @@
   Start();
 }
 
-WorkerScriptLoader::~WorkerScriptLoader() = default;
+WorkerScriptLoader::~WorkerScriptLoader() {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
+}
 
 base::WeakPtr<WorkerScriptLoader> WorkerScriptLoader::GetWeakPtr() {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   return weak_factory_.GetWeakPtr();
 }
 
+void WorkerScriptLoader::Abort() {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
+  CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
+}
+
 void WorkerScriptLoader::Start() {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(!completed_);
 
-  ResourceContext* resource_context = resource_context_getter_.Run();
-  if (!resource_context) {
-    CommitCompleted(network::URLLoaderCompletionStatus(net::ERR_ABORTED));
-    return;
+  BrowserContext* browser_context = nullptr;
+  if (NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    browser_context = browser_context_getter_.Run();
+    if (!browser_context) {
+      Abort();
+      return;
+    }
   }
 
-  // TODO(http://crbug.com/824840): Support interceptors on the UI thread.
+  ResourceContext* resource_context = nullptr;
+  if (!NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    resource_context = resource_context_getter_.Run();
+    if (!resource_context) {
+      Abort();
+      return;
+    }
+  }
+
   if (interceptor_index_ < interceptors_.size()) {
     auto* interceptor = interceptors_[interceptor_index_++].get();
     interceptor->MaybeCreateLoader(
-        resource_request_, nullptr, resource_context,
+        resource_request_, browser_context, resource_context,
         base::BindOnce(&WorkerScriptLoader::MaybeStartLoader,
                        weak_factory_.GetWeakPtr(), interceptor),
         base::BindOnce(&WorkerScriptLoader::LoadFromNetwork,
@@ -94,6 +151,7 @@
 void WorkerScriptLoader::MaybeStartLoader(
     NavigationLoaderInterceptor* interceptor,
     SingleRequestURLLoaderFactory::RequestHandler single_request_handler) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(!completed_);
   DCHECK(interceptor);
 
@@ -127,6 +185,7 @@
 }
 
 void WorkerScriptLoader::LoadFromNetwork(bool reset_subresource_loader_params) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(!completed_);
 
   default_loader_used_ = true;
@@ -149,6 +208,7 @@
     const std::vector<std::string>& removed_headers,
     const net::HttpRequestHeaders& modified_headers,
     const base::Optional<GURL>& new_url) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(!new_url.has_value()) << "Redirect with modified URL was not "
                                   "supported yet. crbug.com/845683";
   DCHECK(redirect_info_);
@@ -174,9 +234,17 @@
 
   // Cancel the request on ResourceDispatcherHost so that we can fall back
   // to network again.
-  DCHECK(ResourceDispatcherHostImpl::Get());
-  ResourceDispatcherHostImpl::Get()->CancelRequest(process_id_, request_id_);
-
+  if (NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    // Hop to the IO thread to touch ResourceDispatcherHost. We continue on
+    // the UI thread in Start().
+    base::PostTaskWithTraits(
+        FROM_HERE, {BrowserThread::IO},
+        base::BindOnce(&CancelRequestOnIO, process_id_, request_id_,
+                       base::BindOnce(&WorkerScriptLoader::Start,
+                                      weak_factory_.GetWeakPtr())));
+    return;
+  }
+  CancelRequestOnIO(process_id_, request_id_, /*ui_continuation=*/{});
   Start();
 }
 
@@ -190,16 +258,19 @@
 // state or propagating state to a new URLLoader upon redirect.
 void WorkerScriptLoader::SetPriority(net::RequestPriority priority,
                                      int32_t intra_priority_value) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   if (url_loader_)
     url_loader_->SetPriority(priority, intra_priority_value);
 }
 
 void WorkerScriptLoader::PauseReadingBodyFromNet() {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   if (url_loader_)
     url_loader_->PauseReadingBodyFromNet();
 }
 
 void WorkerScriptLoader::ResumeReadingBodyFromNet() {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   if (url_loader_)
     url_loader_->ResumeReadingBodyFromNet();
 }
@@ -212,12 +283,14 @@
 
 void WorkerScriptLoader::OnReceiveResponse(
     const network::ResourceResponseHead& response_head) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   client_->OnReceiveResponse(response_head);
 }
 
 void WorkerScriptLoader::OnReceiveRedirect(
     const net::RedirectInfo& redirect_info,
     const network::ResourceResponseHead& response_head) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   if (--redirect_limit_ == 0) {
     CommitCompleted(
         network::URLLoaderCompletionStatus(net::ERR_TOO_MANY_REDIRECTS));
@@ -232,25 +305,30 @@
     int64_t current_position,
     int64_t total_size,
     OnUploadProgressCallback ack_callback) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   client_->OnUploadProgress(current_position, total_size,
                             std::move(ack_callback));
 }
 
 void WorkerScriptLoader::OnReceiveCachedMetadata(mojo_base::BigBuffer data) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   client_->OnReceiveCachedMetadata(std::move(data));
 }
 
 void WorkerScriptLoader::OnTransferSizeUpdated(int32_t transfer_size_diff) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   client_->OnTransferSizeUpdated(transfer_size_diff);
 }
 
 void WorkerScriptLoader::OnStartLoadingResponseBody(
     mojo::ScopedDataPipeConsumerHandle consumer) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   client_->OnStartLoadingResponseBody(std::move(consumer));
 }
 
 void WorkerScriptLoader::OnComplete(
     const network::URLLoaderCompletionStatus& status) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   CommitCompleted(status);
 }
 
@@ -262,6 +340,8 @@
     network::mojom::URLLoaderPtr* response_url_loader,
     network::mojom::URLLoaderClientRequest* response_client_request,
     ThrottlingURLLoader* url_loader) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
+
   // TODO(crbug/898755): This is odd that NavigationLoaderInterceptor::
   // MaybeCreateLoader() is called directly from WorkerScriptLoader. But
   // NavigationLoaderInterceptor::MaybeCreateLoaderForResponse() is called from
@@ -288,11 +368,18 @@
 
 void WorkerScriptLoader::CommitCompleted(
     const network::URLLoaderCompletionStatus& status) {
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(!completed_);
   completed_ = true;
 
-  if (service_worker_provider_host_ && status.error_code == net::OK)
-    service_worker_provider_host_->CompleteWebWorkerPreparation();
+  if (status.error_code == net::OK) {
+    if (service_worker_handle_) {
+      service_worker_handle_->OnBeginWorkerCommit();
+    } else if (service_worker_handle_core_) {
+      service_worker_handle_core_->OnBeginWorkerCommit();
+    }
+  }
+
   client_->OnComplete(status);
 
   // We're done. Ensure we no longer send messages to our client, and no longer
diff --git a/content/browser/worker_host/worker_script_loader.h b/content/browser/worker_host/worker_script_loader.h
index 6d0edd2..5328ef2 100644
--- a/content/browser/worker_host/worker_script_loader.h
+++ b/content/browser/worker_host/worker_script_loader.h
@@ -26,10 +26,12 @@
 namespace content {
 
 class AppCacheHost;
+class BrowserContext;
 class ThrottlingURLLoader;
 class NavigationLoaderInterceptor;
 class ResourceContext;
-class ServiceWorkerProviderHost;
+class ServiceWorkerNavigationHandle;
+class ServiceWorkerNavigationHandleCore;
 
 // The URLLoader for loading a shared worker script. Only used for the main
 // script request.
@@ -41,7 +43,8 @@
 // client. On redirects, it starts over with the new request URL, possibly
 // starting a new loader and becoming the client of that.
 //
-// Lives on the IO thread.
+// Lives on the UI thread when NavigationLoaderOnUI is enabled, and the IO
+// thread otherwise.
 class WorkerScriptLoader : public network::mojom::URLLoader,
                            public network::mojom::URLLoaderClient {
  public:
@@ -49,6 +52,10 @@
   // the IO thread.
   using ResourceContextGetter = base::RepeatingCallback<ResourceContext*(void)>;
 
+  // Returns the browser context, or nullptr during shutdown. Must be called on
+  // the UI thread.
+  using BrowserContextGetter = base::RepeatingCallback<BrowserContext*(void)>;
+
   // |default_loader_factory| is used to load the script if the load is not
   // intercepted by a feature like service worker. Typically it will load the
   // script from the NetworkService. However, it may internally contain
@@ -61,8 +68,12 @@
       uint32_t options,
       const network::ResourceRequest& resource_request,
       network::mojom::URLLoaderClientPtr client,
-      base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
+      base::WeakPtr<ServiceWorkerNavigationHandle>
+          service_worker_handle /* UI */,
+      base::WeakPtr<ServiceWorkerNavigationHandleCore>
+          service_worker_handle_core /* IO */,
       base::WeakPtr<AppCacheHost> appcache_host,
+      const BrowserContextGetter& browser_context_getter,
       const ResourceContextGetter& resource_context_getter,
       scoped_refptr<network::SharedURLLoaderFactory> default_loader_factory,
       const net::MutableNetworkTrafficAnnotationTag& traffic_annotation);
@@ -114,6 +125,7 @@
   bool default_loader_used_ = false;
 
  private:
+  void Abort();
   void Start();
   void MaybeStartLoader(
       NavigationLoaderInterceptor* interceptor,
@@ -134,7 +146,9 @@
   const uint32_t options_;
   network::ResourceRequest resource_request_;
   network::mojom::URLLoaderClientPtr client_;
-  base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_;
+  base::WeakPtr<ServiceWorkerNavigationHandle> service_worker_handle_;
+  base::WeakPtr<ServiceWorkerNavigationHandleCore> service_worker_handle_core_;
+  BrowserContextGetter browser_context_getter_;
   ResourceContextGetter resource_context_getter_;
   scoped_refptr<network::SharedURLLoaderFactory> default_loader_factory_;
   net::MutableNetworkTrafficAnnotationTag traffic_annotation_;
@@ -157,4 +171,5 @@
 };
 
 }  // namespace content
+
 #endif  // CONTENT_BROWSER_WORKER_HOST_WORKER_SCRIPT_LOADER_H_
diff --git a/content/browser/worker_host/worker_script_loader_factory.cc b/content/browser/worker_host/worker_script_loader_factory.cc
index 8e2383b91..c72ef92 100644
--- a/content/browser/worker_host/worker_script_loader_factory.cc
+++ b/content/browser/worker_host/worker_script_loader_factory.cc
@@ -5,9 +5,13 @@
 #include "content/browser/worker_host/worker_script_loader_factory.h"
 
 #include <memory>
+
 #include "base/feature_list.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
+#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
 #include "content/browser/service_worker/service_worker_provider_host.h"
 #include "content/browser/service_worker/service_worker_version.h"
+#include "content/browser/worker_host/worker_script_fetch_initiator.h"
 #include "content/browser/worker_host/worker_script_loader.h"
 #include "content/public/browser/browser_thread.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
@@ -20,26 +24,30 @@
 
 WorkerScriptLoaderFactory::WorkerScriptLoaderFactory(
     int process_id,
-    base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
+    ServiceWorkerNavigationHandle* service_worker_handle /* UI only */,
+    ServiceWorkerNavigationHandleCore* service_worker_handle_core /* IO only */,
     base::WeakPtr<AppCacheHost> appcache_host,
+    const BrowserContextGetter& browser_context_getter,
     const ResourceContextGetter& resource_context_getter,
     scoped_refptr<network::SharedURLLoaderFactory> loader_factory)
     : process_id_(process_id),
-      service_worker_provider_host_(std::move(service_worker_provider_host)),
       appcache_host_(std::move(appcache_host)),
+      browser_context_getter_(browser_context_getter),
       resource_context_getter_(resource_context_getter),
       loader_factory_(std::move(loader_factory)) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(base::FeatureList::IsEnabled(network::features::kNetworkService));
-  DCHECK(!service_worker_provider_host_ ||
-         service_worker_provider_host_->provider_type() ==
-             blink::mojom::ServiceWorkerProviderType::kForDedicatedWorker ||
-         service_worker_provider_host_->provider_type() ==
-             blink::mojom::ServiceWorkerProviderType::kForSharedWorker);
+
+  if (service_worker_handle) {
+    service_worker_handle_ = service_worker_handle->AsWeakPtr();
+  }
+  if (service_worker_handle_core) {
+    service_worker_handle_core_ = service_worker_handle_core->AsWeakPtr();
+  }
 }
 
 WorkerScriptLoaderFactory::~WorkerScriptLoaderFactory() {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
 }
 
 void WorkerScriptLoaderFactory::CreateLoaderAndStart(
@@ -50,7 +58,7 @@
     const network::ResourceRequest& resource_request,
     network::mojom::URLLoaderClientPtr client,
     const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
-  DCHECK_CURRENTLY_ON(BrowserThread::IO);
+  DCHECK_CURRENTLY_ON(WorkerScriptFetchInitiator::GetLoaderThreadID());
   DCHECK(resource_request.resource_type ==
              static_cast<int>(ResourceType::kWorker) ||
          resource_request.resource_type ==
@@ -61,8 +69,9 @@
   // Create a WorkerScriptLoader to load the script.
   auto script_loader = std::make_unique<WorkerScriptLoader>(
       process_id_, routing_id, request_id, options, resource_request,
-      std::move(client), service_worker_provider_host_, appcache_host_,
-      resource_context_getter_, loader_factory_, traffic_annotation);
+      std::move(client), service_worker_handle_, service_worker_handle_core_,
+      appcache_host_, browser_context_getter_, resource_context_getter_,
+      loader_factory_, traffic_annotation);
   script_loader_ = script_loader->GetWeakPtr();
   mojo::MakeStrongBinding(std::move(script_loader), std::move(request));
 }
diff --git a/content/browser/worker_host/worker_script_loader_factory.h b/content/browser/worker_host/worker_script_loader_factory.h
index 61b3165..afd3fef 100644
--- a/content/browser/worker_host/worker_script_loader_factory.h
+++ b/content/browser/worker_host/worker_script_loader_factory.h
@@ -6,6 +6,7 @@
 #define CONTENT_BROWSER_WORKER_HOST_WORKER_SCRIPT_LOADER_FACTORY_H_
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "content/browser/navigation_subresource_loader_params.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
 
@@ -16,7 +17,9 @@
 namespace content {
 
 class AppCacheHost;
-class ServiceWorkerProviderHost;
+class BrowserContext;
+class ServiceWorkerNavigationHandle;
+class ServiceWorkerNavigationHandleCore;
 class ResourceContext;
 class WorkerScriptLoader;
 
@@ -26,8 +29,8 @@
 // It's an error to call CreateLoaderAndStart() more than a total of one time
 // across this object or any of its clones.
 //
-// This is created per one web worker. All functions of this class must be
-// called on the IO thread.
+// This is created per one web worker. It lives on the UI thread when
+// NavigationLoaderOnUI is enabled, and the IO thread otherwise.
 class CONTENT_EXPORT WorkerScriptLoaderFactory
     : public network::mojom::URLLoaderFactory {
  public:
@@ -35,14 +38,28 @@
   // the IO thread.
   using ResourceContextGetter = base::RepeatingCallback<ResourceContext*(void)>;
 
+  // Returns the browser context, or nullptr during shutdown. Must be called on
+  // the UI thread.
+  using BrowserContextGetter = base::RepeatingCallback<BrowserContext*(void)>;
+
   // |loader_factory| is used to load the script if the load is not intercepted
   // by a feature like service worker. Typically it will load the script from
   // the NetworkService. However, it may internally contain non-NetworkService
   // factories used for non-http(s) URLs, e.g., a chrome-extension:// URL.
+  //
+  // NavigationLoaderOnUI:
+  // |service_worker_handle| and |browser_context_getter| can be
+  // used.
+  //
+  // Non-NavigationLoaderOnUI:
+  // |service_worker_handle_core| and |resource_context_getter| can
+  // be used.
   WorkerScriptLoaderFactory(
       int process_id,
-      base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host,
+      ServiceWorkerNavigationHandle* service_worker_handle,
+      ServiceWorkerNavigationHandleCore* service_worker_handle_core,
       base::WeakPtr<AppCacheHost> appcache_host,
+      const BrowserContextGetter& browser_context_getter,
       const ResourceContextGetter& resource_context_getter,
       scoped_refptr<network::SharedURLLoaderFactory> loader_factory);
   ~WorkerScriptLoaderFactory() override;
@@ -62,8 +79,10 @@
 
  private:
   const int process_id_;
-  base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_;
+  base::WeakPtr<ServiceWorkerNavigationHandle> service_worker_handle_;
+  base::WeakPtr<ServiceWorkerNavigationHandleCore> service_worker_handle_core_;
   base::WeakPtr<AppCacheHost> appcache_host_;
+  BrowserContextGetter browser_context_getter_;
   ResourceContextGetter resource_context_getter_;
   scoped_refptr<network::SharedURLLoaderFactory> loader_factory_;
 
diff --git a/content/browser/worker_host/worker_script_loader_factory_unittest.cc b/content/browser/worker_host/worker_script_loader_factory_unittest.cc
index 1b1e314..7f73d13c 100644
--- a/content/browser/worker_host/worker_script_loader_factory_unittest.cc
+++ b/content/browser/worker_host/worker_script_loader_factory_unittest.cc
@@ -6,9 +6,12 @@
 
 #include "base/bind_helpers.h"
 #include "base/run_loop.h"
+#include "content/browser/loader/navigation_url_loader_impl.h"
 #include "content/browser/service_worker/embedded_worker_test_helper.h"
 #include "content/browser/service_worker/service_worker_context_core.h"
 #include "content/browser/service_worker/service_worker_context_wrapper.h"
+#include "content/browser/service_worker/service_worker_navigation_handle.h"
+#include "content/browser/service_worker/service_worker_navigation_handle_core.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "content/test/fake_network_url_loader_factory.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
@@ -37,6 +40,10 @@
     context->storage()->LazyInitializeForTest(base::DoNothing());
     base::RunLoop().RunUntilIdle();
 
+    browser_context_getter_ =
+        base::BindRepeating(&ServiceWorkerContextWrapper::browser_context,
+                            helper_->context_wrapper());
+
     resource_context_getter_ =
         base::BindRepeating(&ServiceWorkerContextWrapper::resource_context,
                             helper_->context_wrapper());
@@ -52,13 +59,8 @@
         network::SharedURLLoaderFactory::Create(std::move(info));
 
     // Set up a service worker host for the shared worker.
-    service_worker_provider_info_ =
-        blink::mojom::ServiceWorkerProviderInfoForClient::New();
-    service_worker_provider_host_ =
-        ServiceWorkerProviderHost::PreCreateForWebWorker(
-            helper_->context()->AsWeakPtr(), kProcessId,
-            blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
-            &service_worker_provider_info_);
+    service_worker_handle_ = std::make_unique<ServiceWorkerNavigationHandle>(
+        helper_->context_wrapper());
   }
 
  protected:
@@ -83,18 +85,17 @@
   std::unique_ptr<EmbeddedWorkerTestHelper> helper_;
   std::unique_ptr<FakeNetworkURLLoaderFactory> network_loader_factory_instance_;
   scoped_refptr<network::SharedURLLoaderFactory> network_loader_factory_;
+  std::unique_ptr<ServiceWorkerNavigationHandle> service_worker_handle_;
 
-  blink::mojom::ServiceWorkerProviderInfoForClientPtr
-      service_worker_provider_info_;
-  base::WeakPtr<ServiceWorkerProviderHost> service_worker_provider_host_;
-
+  WorkerScriptLoaderFactory::BrowserContextGetter browser_context_getter_;
   WorkerScriptLoaderFactory::ResourceContextGetter resource_context_getter_;
 };
 
 TEST_F(WorkerScriptLoaderFactoryTest, ServiceWorkerProviderHost) {
   // Make the factory.
   auto factory = std::make_unique<WorkerScriptLoaderFactory>(
-      kProcessId, service_worker_provider_host_, nullptr /* appcache_host */,
+      kProcessId, service_worker_handle_.get(), service_worker_handle_->core(),
+      /*appcache_host=*/nullptr, browser_context_getter_,
       resource_context_getter_, network_loader_factory_);
 
   // Load the script.
@@ -106,19 +107,26 @@
   EXPECT_EQ(net::OK, client.completion_status().error_code);
 
   // The provider host should be set up.
-  EXPECT_TRUE(service_worker_provider_host_->is_response_committed());
-  EXPECT_TRUE(service_worker_provider_host_->is_execution_ready());
-  EXPECT_EQ(url, service_worker_provider_host_->url());
+  base::WeakPtr<ServiceWorkerProviderHost> host =
+      service_worker_handle_->core()->provider_host();
+  EXPECT_TRUE(host->is_response_committed());
+  EXPECT_TRUE(host->is_execution_ready());
+  EXPECT_EQ(url, host->url());
 }
 
-// Test a null service worker provider host. This typically only happens during
+// Test a null service worker handle. This typically only happens during
 // shutdown or after a fatal error occurred in the service worker system.
-TEST_F(WorkerScriptLoaderFactoryTest, NullServiceWorkerProviderHost) {
-  // Make the factory with null provider host.
+TEST_F(WorkerScriptLoaderFactoryTest, NullServiceWorkerHandle) {
+  // Make the factory.
   auto factory = std::make_unique<WorkerScriptLoaderFactory>(
-      kProcessId, nullptr /* service_worker_provider_host */,
-      nullptr /* appcache_host */, resource_context_getter_,
-      network_loader_factory_);
+      kProcessId, service_worker_handle_.get(), service_worker_handle_->core(),
+      nullptr /* appcache_host */, browser_context_getter_,
+      resource_context_getter_, network_loader_factory_);
+
+  // Destroy the handle.
+  service_worker_handle_.reset();
+  // Let the IO thread task run to destroy the handle core.
+  base::RunLoop().RunUntilIdle();
 
   // Load the script.
   GURL url("https://www.example.com/worker.js");
@@ -126,16 +134,22 @@
   network::mojom::URLLoaderPtr loader =
       CreateTestLoaderAndStart(url, factory.get(), &client);
   client.RunUntilComplete();
-  EXPECT_EQ(net::OK, client.completion_status().error_code);
+  EXPECT_EQ(net::ERR_ABORTED, client.completion_status().error_code);
 }
 
 // Test a null resource context when the request starts. This happens when
 // shutdown starts between the constructor and when CreateLoaderAndStart is
 // invoked.
 TEST_F(WorkerScriptLoaderFactoryTest, NullResourceContext) {
+  if (NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    // Resource context is irrelevant.
+    return;
+  }
+
   // Make the factory.
   auto factory = std::make_unique<WorkerScriptLoaderFactory>(
-      kProcessId, service_worker_provider_host_, nullptr /* appcache_host */,
+      kProcessId, service_worker_handle_.get(), service_worker_handle_->core(),
+      nullptr /* appcache_host */, browser_context_getter_,
       resource_context_getter_, network_loader_factory_);
 
   // Set a null resource context.
@@ -150,6 +164,33 @@
   EXPECT_EQ(net::ERR_ABORTED, client.completion_status().error_code);
 }
 
+// Test a null browser context when the request starts. This happens when
+// shutdown starts between the constructor and when CreateLoaderAndStart is
+// invoked.
+TEST_F(WorkerScriptLoaderFactoryTest, NullBrowserContext) {
+  if (!NavigationURLLoaderImpl::IsNavigationLoaderOnUIEnabled()) {
+    // Browser context is irrelevant.
+    return;
+  }
+
+  // Make the factory.
+  auto factory = std::make_unique<WorkerScriptLoaderFactory>(
+      kProcessId, service_worker_handle_.get(), service_worker_handle_->core(),
+      nullptr /* appcache_host */, browser_context_getter_,
+      resource_context_getter_, network_loader_factory_);
+
+  // Set a null browser context.
+  helper_->context_wrapper()->Shutdown();
+
+  // Load the script.
+  GURL url("https://www.example.com/worker.js");
+  network::TestURLLoaderClient client;
+  network::mojom::URLLoaderPtr loader =
+      CreateTestLoaderAndStart(url, factory.get(), &client);
+  client.RunUntilComplete();
+  EXPECT_EQ(net::ERR_ABORTED, client.completion_status().error_code);
+}
+
 // TODO(falken): Add a test for a shared worker that's controlled by a service
 // worker.
 
diff --git a/content/public/browser/background_sync_controller.h b/content/public/browser/background_sync_controller.h
index f168c74..7b024f8 100644
--- a/content/public/browser/background_sync_controller.h
+++ b/content/public/browser/background_sync_controller.h
@@ -7,6 +7,8 @@
 
 #include <stdint.h>
 
+#include <set>
+
 #include "base/time/time.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/background_sync_registration.h"
@@ -91,6 +93,13 @@
   // to finish firing one sync event.
   virtual std::unique_ptr<BackgroundSyncEventKeepAlive>
   CreateBackgroundSyncEventKeepAlive() = 0;
+
+  // Updates its internal list of origins for which we have suspended periodic
+  // Background Sync registrations. This is compiled from each
+  // BackgroundSyncManager when they are initialized. This list used to ignore
+  // changes concerning origins we don't care about.
+  virtual void NoteSuspendedPeriodicSyncOrigins(
+      std::set<url::Origin> suspended_origins) = 0;
 };
 
 }  // namespace content
diff --git a/content/public/browser/background_sync_registration.h b/content/public/browser/background_sync_registration.h
index db1d5eb..e43efbd 100644
--- a/content/public/browser/background_sync_registration.h
+++ b/content/public/browser/background_sync_registration.h
@@ -62,6 +62,12 @@
 
   void set_origin(const url::Origin& origin) { origin_ = origin; }
 
+  bool is_suspended() const {
+    if (sync_type() == blink::mojom::BackgroundSyncType::ONE_SHOT)
+      return false;
+    return delay_until_.is_max();
+  }
+
  private:
   blink::mojom::SyncRegistrationOptions options_;
   blink::mojom::BackgroundSyncState sync_state_ =
diff --git a/content/public/browser/devtools_background_services_context.h b/content/public/browser/devtools_background_services_context.h
index c84b1a01..864af759 100644
--- a/content/public/browser/devtools_background_services_context.h
+++ b/content/public/browser/devtools_background_services_context.h
@@ -24,9 +24,10 @@
   kPushMessaging = 4,
   kNotifications = 5,
   kPaymentHandler = 6,
+  kPeriodicBackgroundSync = 7,
 
   // Keep at the end.
-  kMaxValue = kPaymentHandler,
+  kMaxValue = kPeriodicBackgroundSync,
 };
 
 // This class is responsible for persisting the debugging events for the
diff --git a/content/renderer/compositor/layer_tree_view.cc b/content/renderer/compositor/layer_tree_view.cc
index 6ba2e4d..e9fc1fb 100644
--- a/content/renderer/compositor/layer_tree_view.cc
+++ b/content/renderer/compositor/layer_tree_view.cc
@@ -188,14 +188,6 @@
   layer_tree_host_->SetNeedsAnimate();
 }
 
-void LayerTreeView::SetHaveScrollEventHandlers(bool has_handlers) {
-  layer_tree_host_->SetHaveScrollEventHandlers(has_handlers);
-}
-
-bool LayerTreeView::HaveScrollEventHandlers() const {
-  return layer_tree_host_->have_scroll_event_handlers();
-}
-
 void LayerTreeView::SetLayerTreeFrameSink(
     std::unique_ptr<cc::LayerTreeFrameSink> layer_tree_frame_sink) {
   if (!layer_tree_frame_sink) {
diff --git a/content/renderer/compositor/layer_tree_view.h b/content/renderer/compositor/layer_tree_view.h
index 764bc34..f43510d 100644
--- a/content/renderer/compositor/layer_tree_view.h
+++ b/content/renderer/compositor/layer_tree_view.h
@@ -127,8 +127,6 @@
   // blink::WebLayerTreeView implementation.
   viz::FrameSinkId GetFrameSinkId() override;
   void SetNonBlinkManagedRootLayer(scoped_refptr<cc::Layer> layer);
-  void SetHaveScrollEventHandlers(bool) override;
-  bool HaveScrollEventHandlers() const override;
   int LayerTreeId() const override;
 
   void UpdateBrowserControlsState(cc::BrowserControlsState constraints,
diff --git a/content/renderer/loader/web_worker_fetch_context_impl.cc b/content/renderer/loader/web_worker_fetch_context_impl.cc
index d9393f6a..e659a2c 100644
--- a/content/renderer/loader/web_worker_fetch_context_impl.cc
+++ b/content/renderer/loader/web_worker_fetch_context_impl.cc
@@ -282,11 +282,18 @@
     std::unique_ptr<network::SharedURLLoaderFactoryInfo> fallback_factory_info,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   DCHECK(blink::features::IsPlzDedicatedWorkerEnabled());
-  DCHECK(service_worker_provider_context);
   DCHECK(loader_factory_info);
   DCHECK(fallback_factory_info);
   DCHECK(task_runner);
 
+  if (!service_worker_provider_context) {
+    return CloneForNestedWorkerInternal(
+        /*service_worker_client_request=*/nullptr,
+        /*service_worker_worker_client_registry_ptr_info=*/nullptr,
+        /*container_host_ptr_info=*/nullptr, std::move(loader_factory_info),
+        std::move(fallback_factory_info), std::move(task_runner));
+  }
+
   blink::mojom::ServiceWorkerWorkerClientRegistryPtrInfo
       service_worker_worker_client_registry_ptr_info;
   service_worker_provider_context->CloneWorkerClientRegistry(
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 370773c..734c561 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -3303,6 +3303,11 @@
   Send(new WidgetHostMsg_HasTouchEventHandlers(routing_id_, has_handlers));
 }
 
+void RenderWidget::SetHaveScrollEventHandlers(bool have_handlers) {
+  layer_tree_view_->layer_tree_host()->SetHaveScrollEventHandlers(
+      have_handlers);
+}
+
 void RenderWidget::SetNeedsLowLatencyInput(bool needs_low_latency) {
   if (input_event_queue_)
     input_event_queue_->SetNeedsLowLatency(needs_low_latency);
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 75b8ef6..20f02c0b 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -432,6 +432,7 @@
   void RequestUnbufferedInputEvents() override;
   void SetHasPointerRawUpdateEventHandlers(bool has_handlers) override;
   void SetHasTouchEventHandlers(bool has_handlers) override;
+  void SetHaveScrollEventHandlers(bool have_handlers) override;
   void SetNeedsLowLatencyInput(bool) override;
   void SetNeedsUnbufferedInputForDebugger(bool) override;
   void AnimateDoubleTapZoomInMainFrame(const blink::WebPoint& point,
diff --git a/content/renderer/worker/dedicated_worker_host_factory_client.cc b/content/renderer/worker/dedicated_worker_host_factory_client.cc
index ee076c5..ba0bfc5 100644
--- a/content/renderer/worker/dedicated_worker_host_factory_client.cc
+++ b/content/renderer/worker/dedicated_worker_host_factory_client.cc
@@ -120,7 +120,6 @@
         subresource_loader_factory_bundle_info,
     blink::mojom::ControllerServiceWorkerInfoPtr controller_info) {
   DCHECK(blink::features::IsPlzDedicatedWorkerEnabled());
-  DCHECK(service_worker_provider_info);
   DCHECK(main_script_load_params);
   DCHECK(subresource_loader_factory_bundle_info);
 
@@ -132,12 +131,14 @@
               std::move(subresource_loader_factory_bundle_info)));
 
   DCHECK(!service_worker_provider_context_);
-  service_worker_provider_context_ =
-      base::MakeRefCounted<ServiceWorkerProviderContext>(
-          blink::mojom::ServiceWorkerProviderType::kForDedicatedWorker,
-          std::move(service_worker_provider_info->client_request),
-          std::move(service_worker_provider_info->host_ptr_info),
-          std::move(controller_info), subresource_loader_factory_bundle_);
+  if (service_worker_provider_info) {
+    service_worker_provider_context_ =
+        base::MakeRefCounted<ServiceWorkerProviderContext>(
+            blink::mojom::ServiceWorkerProviderType::kForDedicatedWorker,
+            std::move(service_worker_provider_info->client_request),
+            std::move(service_worker_provider_info->host_ptr_info),
+            std::move(controller_info), subresource_loader_factory_bundle_);
+  }
 
   // Initialize the response override for the main worker script loaded by the
   // browser process.
diff --git a/content/renderer/worker/service_worker_network_provider_for_shared_worker.cc b/content/renderer/worker/service_worker_network_provider_for_shared_worker.cc
index 1422870..0984b816 100644
--- a/content/renderer/worker/service_worker_network_provider_for_shared_worker.cc
+++ b/content/renderer/worker/service_worker_network_provider_for_shared_worker.cc
@@ -24,14 +24,15 @@
     scoped_refptr<network::SharedURLLoaderFactory> fallback_loader_factory,
     bool is_secure_context,
     std::unique_ptr<NavigationResponseOverrideParameters> response_override) {
-  DCHECK(info);
   auto provider =
       base::WrapUnique(new ServiceWorkerNetworkProviderForSharedWorker(
           is_secure_context, std::move(response_override)));
-  provider->context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
-      blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
-      std::move(info->client_request), std::move(info->host_ptr_info),
-      std::move(controller_info), std::move(fallback_loader_factory));
+  if (info) {
+    provider->context_ = base::MakeRefCounted<ServiceWorkerProviderContext>(
+        blink::mojom::ServiceWorkerProviderType::kForSharedWorker,
+        std::move(info->client_request), std::move(info->host_ptr_info),
+        std::move(controller_info), std::move(fallback_loader_factory));
+  }
   return provider;
 }
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
index 5ab0027..d35002a 100644
--- a/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/pixel_expectations.txt
@@ -136,14 +136,6 @@
 # context loss which results in hardware decoder loss.
 crbug.com/580386 [ android ] Pixel_Video_Context_Loss_MP4 [ Skip ]
 
-# Fails on Mac Pro FYI Release (AMD)
-crbug.com/925744 [ mac amd-0x679e ] Pixel_Video_MP4 [ Skip ]
-crbug.com/925744 [ mac amd-0x679e ] Pixel_Video_Context_Loss_MP4 [ Skip ]
-crbug.com/911413 [ mac amd-0x679e ] Pixel_Video_MP4_FourColors_Aspect_4x3 [ Skip ]
-crbug.com/911413 [ mac amd-0x679e ] Pixel_Video_MP4_FourColors_Rot_90 [ Skip ]
-crbug.com/911413 [ mac amd-0x679e ] Pixel_Video_MP4_FourColors_Rot_180 [ Skip ]
-crbug.com/911413 [ mac amd-0x679e ] Pixel_Video_MP4_FourColors_Rot_270 [ Skip ]
-
 # Fails on multiple Android devices.
 crbug.com/927107 [ android no-skia-renderer ] Pixel_CSS3DBlueBox [ Failure ]
 
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 53bc624..940cd706 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -182,7 +182,6 @@
 crbug.com/963205 [ win nvidia opengl passthrough ] conformance/extensions/webgl-compressed-texture-s3tc-srgb.html [ RetryOnFailure ]
 crbug.com/715001 [ opengl win nvidia                    ] conformance/limits/gl-max-texture-dimensions.html [ Failure ]
 crbug.com/703779 [ opengl win nvidia                    ] conformance/textures/misc/texture-size.html [ Failure ]
-crbug.com/830046 [ opengl win passthrough nvidia        ] conformance2/rendering/blitframebuffer-size-overflow.html [ Skip ]
 crbug.com/781668 [ opengl win nvidia-0x1cb3             ] conformance2/textures/canvas_sub_rectangle/tex-2d-rgb565-rgb-unsigned_byte.html [ Failure ]
 crbug.com/921055 [ opengl win passthrough nvidia-0x1cb3 ] conformance2/textures/image_bitmap_from_image_data/tex-2d-rgb9_e5-rgb-float.html [ RetryOnFailure ]
 crbug.com/905003 [ opengl win passthrough nvidia        ] conformance2/textures/misc/integer-cubemap-specification-order-bug.html [ Failure ]
@@ -548,9 +547,6 @@
 crbug.com/680282 [ linux nvidia-0xf02 ] conformance2/textures/image/tex-3d-rg8ui-rg_integer-unsigned_byte.html [ Failure ]
 crbug.com/694354 [ linux no-passthrough nvidia ] conformance2/textures/image_bitmap_from_image_data/tex-2d-srgb8-rgb-unsigned_byte.html [ RetryOnFailure ]
 
-# Linux NVIDIA Quadro P400
-crbug.com/830046 [ linux passthrough nvidia-0x1cb3 ] conformance2/rendering/blitframebuffer-size-overflow.html [ Skip ]
-
 # Observed flaky on Swarmed bots. Some of these were directly
 # observed, some not. We can't afford any flakes on the tryservers
 # so mark them all flaky.
@@ -590,7 +586,6 @@
 # It looks like AMD shader compiler rejects many valid ES3 semantics.
 crbug.com/844311 [ linux amd ] conformance/glsl/misc/fragcolor-fragdata-invariant.html [ Failure ]
 crbug.com/766776 [ linux amd ] conformance2/attribs/gl-vertex-attrib-normalized-int.html [ Failure ]
-crbug.com/483282 [ linux amd ] conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
 crbug.com/981070 [ linux amd ] conformance2/glsl3/matrix-row-major.html [ Failure ]
 crbug.com/709351 [ linux amd ] conformance2/glsl3/vector-dynamic-indexing-swizzled-lvalue.html [ Failure ]
 crbug.com/617290 [ linux amd ] deqp/functional/gles3/multisample.html [ Failure ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index 39cdb0a..5df13fe 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -196,7 +196,6 @@
 # Win / OpenGL / AMD failures
 crbug.com/649824 [ win amd opengl ] conformance/attribs/gl-bindAttribLocation-aliasing.html [ Skip ]
 crbug.com/angleproject/1007 [ win amd opengl ] conformance/glsl/misc/shader-struct-scope.html [ Skip ]
-crbug.com/angleproject/1007 [ win amd opengl no-passthrough ] conformance/glsl/misc/shaders-with-invariance.html [ Skip ]
 crbug.com/angleproject/1007 [ win amd opengl ] conformance/glsl/misc/struct-nesting-of-variable-names.html [ Failure ]
 crbug.com/angleproject/1506 [ win amd opengl ] conformance/rendering/clipping-wide-points.html [ Failure ]
 
@@ -375,13 +374,11 @@
 
 # AMD Radeon 6450 and/or R7 240
 crbug.com/479260 [ linux amd no-angle ] conformance/extensions/angle-instanced-arrays.html [ Failure ]
-crbug.com/479952 [ linux amd no-passthrough ] conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
 
 # Linux passthrough AMD
 crbug.com/960808 [ linux amd passthrough ] conformance/glsl/misc/shader-with-non-reserved-words.html [ Failure ]
 
 # Linux passthrough AMD OpenGL
-crbug.com/angleproject/1007 [ linux amd opengl passthrough ] conformance/glsl/misc/shaders-with-invariance.html [ Skip ]
 crbug.com/965594 [ linux amd opengl passthrough ] conformance/more/conformance/quickCheckAPI-S_V.html [ RetryOnFailure ]
 crbug.com/965594 [ linux amd opengl passthrough ] conformance/more/conformance/webGLArrays.html [ RetryOnFailure ]
 
@@ -437,7 +434,6 @@
 crbug.com/611943 [ android qualcomm-adreno-(tm)-330 ] conformance/glsl/matrices/glsl-mat4-to-mat3.html [ Failure ]
 crbug.com/611943 [ android qualcomm-adreno-(tm)-330 ] conformance/glsl/misc/shader-struct-scope.html [ Failure ]
 crbug.com/611943 [ android qualcomm-adreno-(tm)-330 ] conformance/glsl/misc/shader-with-vec4-vec3-vec4-conditional.html [ Failure ]
-crbug.com/611943 [ android qualcomm-adreno-(tm)-330 no-passthrough ] conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
 crbug.com/611943 [ android qualcomm-adreno-(tm)-330 ] conformance/glsl/misc/struct-equals.html [ Failure ]
 crbug.com/478572 [ android qualcomm-adreno-(tm)-330 ] deqp/data/gles2/shaders/linkage.html [ Failure ]
 [ android qualcomm-adreno-(tm)-330 no-passthrough ] WebglExtension_OES_texture_float_linear [ Failure ]
@@ -495,7 +491,6 @@
 # The list of tests which may be that future test is very long. It is
 # almost (but not quite) every webgl conformance test.
 crbug.com/614550 [ android qualcomm-adreno-(tm)-420 ] conformance/glsl/misc/shader-struct-scope.html [ Skip ]
-crbug.com/611945 [ android qualcomm-adreno-(tm)-420 no-passthrough ] conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
 
 # bindBufferBadArgs is causing the GPU thread to crash, taking
 # down the WebView shell, causing the next test to fail and
diff --git a/content/test/mock_background_sync_controller.cc b/content/test/mock_background_sync_controller.cc
index 7df871e4..d97a2677 100644
--- a/content/test/mock_background_sync_controller.cc
+++ b/content/test/mock_background_sync_controller.cc
@@ -6,6 +6,9 @@
 
 namespace content {
 
+MockBackgroundSyncController::MockBackgroundSyncController() = default;
+MockBackgroundSyncController::~MockBackgroundSyncController() = default;
+
 void MockBackgroundSyncController::NotifyOneShotBackgroundSyncRegistered(
     const url::Origin& origin,
     bool can_fire,
@@ -59,4 +62,11 @@
   return nullptr;
 }
 
+void MockBackgroundSyncController::NoteSuspendedPeriodicSyncOrigins(
+    std::set<url::Origin> suspended_origins) {
+  for (auto& origin : suspended_origins) {
+    suspended_periodic_sync_origins_.insert(std::move(origin));
+  }
+}
+
 }  // namespace content
diff --git a/content/test/mock_background_sync_controller.h b/content/test/mock_background_sync_controller.h
index ce42a07..36d2b1b3 100644
--- a/content/test/mock_background_sync_controller.h
+++ b/content/test/mock_background_sync_controller.h
@@ -20,8 +20,8 @@
 // Mocks a BackgroundSyncController, tracking state for use in tests.
 class MockBackgroundSyncController : public BackgroundSyncController {
  public:
-  MockBackgroundSyncController() = default;
-  ~MockBackgroundSyncController() override = default;
+  MockBackgroundSyncController();
+  ~MockBackgroundSyncController() override;
 
   // BackgroundSyncController:
   void NotifyOneShotBackgroundSyncRegistered(const url::Origin& origin,
@@ -35,6 +35,8 @@
       BackgroundSyncParameters* parameters) override;
   std::unique_ptr<BackgroundSyncController::BackgroundSyncEventKeepAlive>
   CreateBackgroundSyncEventKeepAlive() override;
+  void NoteSuspendedPeriodicSyncOrigins(
+      std::set<url::Origin> suspended_registrations) override;
 
   int registration_count() const { return registration_count_; }
   const url::Origin& registration_origin() const {
@@ -57,6 +59,7 @@
   int run_in_background_for_one_shot_sync_count_ = 0;
   int run_in_background_for_periodic_sync_count_ = 0;
   BackgroundSyncParameters background_sync_parameters_;
+  std::set<url::Origin> suspended_periodic_sync_origins_;
 
   DISALLOW_COPY_AND_ASSIGN(MockBackgroundSyncController);
 };
diff --git a/device/fido/make_credential_handler_unittest.cc b/device/fido/make_credential_handler_unittest.cc
index 57222e9..04cb0b3 100644
--- a/device/fido/make_credential_handler_unittest.cc
+++ b/device/fido/make_credential_handler_unittest.cc
@@ -606,12 +606,11 @@
 // keys in AuthenicatorSelectionCriteria.
 TEST_F(FidoMakeCredentialHandlerTest,
        SuccessfulMakeCredentialWithResidentKeyOption) {
-  auto device = std::make_unique<VirtualCtap2Device>();
-  AuthenticatorSupportedOptions option;
-  option.supports_resident_key = true;
-  option.user_verification_availability = AuthenticatorSupportedOptions::
-      UserVerificationAvailability::kSupportedAndConfigured;
-  device->SetAuthenticatorSupportedOptions(std::move(option));
+  VirtualCtap2Device::Config config;
+  config.resident_key_support = true;
+  config.internal_uv_support = true;
+  auto state = base::MakeRefCounted<VirtualFidoDevice::State>();
+  state->fingerprints_enrolled = true;
 
   auto request_handler =
       CreateMakeCredentialHandlerWithAuthenticatorSelectionCriteria(
@@ -620,7 +619,8 @@
               UserVerificationRequirement::kPreferred));
 
   discovery()->WaitForCallToStartAndSimulateSuccess();
-  discovery()->AddDevice(std::move(device));
+  discovery()->AddDevice(std::make_unique<VirtualCtap2Device>(
+      std::move(state), std::move(config)));
 
   scoped_task_environment_.FastForwardUntilNoTasksRemain();
   callback().WaitForCallback();
diff --git a/device/fido/virtual_ctap2_device.cc b/device/fido/virtual_ctap2_device.cc
index 9af9f17..843bd0f 100644
--- a/device/fido/virtual_ctap2_device.cc
+++ b/device/fido/virtual_ctap2_device.cc
@@ -699,11 +699,6 @@
   return weak_factory_.GetWeakPtr();
 }
 
-void VirtualCtap2Device::SetAuthenticatorSupportedOptions(
-    const AuthenticatorSupportedOptions& options) {
-  device_info_->options = options;
-}
-
 base::Optional<CtapDeviceResponseCode> VirtualCtap2Device::OnMakeCredential(
     base::span<const uint8_t> request_bytes,
     std::vector<uint8_t>* response) {
diff --git a/device/fido/virtual_ctap2_device.h b/device/fido/virtual_ctap2_device.h
index 983671e9..0dc4d33b9 100644
--- a/device/fido/virtual_ctap2_device.h
+++ b/device/fido/virtual_ctap2_device.h
@@ -84,11 +84,11 @@
     // sequence. This is used to simulate a security key that truncates strings
     // at a pre-defined byte length without concern for UTF-8 validity of the
     // result.
-    bool allow_invalid_utf8_in_credential_entities = true;
+    bool allow_invalid_utf8_in_credential_entities = false;
   };
 
   VirtualCtap2Device();
-  explicit VirtualCtap2Device(scoped_refptr<State> state, const Config& config);
+  VirtualCtap2Device(scoped_refptr<State> state, const Config& config);
   ~VirtualCtap2Device() override;
 
   // FidoDevice:
@@ -97,9 +97,6 @@
                              DeviceCallback cb) override;
   base::WeakPtr<FidoDevice> GetWeakPtr() override;
 
-  void SetAuthenticatorSupportedOptions(
-      const AuthenticatorSupportedOptions& options);
-
  private:
   base::Optional<CtapDeviceResponseCode> OnMakeCredential(
       base::span<const uint8_t> request,
diff --git a/docs/accessibility/select_to_speak.md b/docs/accessibility/select_to_speak.md
new file mode 100644
index 0000000..da1b1b6
--- /dev/null
+++ b/docs/accessibility/select_to_speak.md
@@ -0,0 +1,220 @@
+# Select to Speak (for developers)
+
+Select to Speak is a Chrome OS feature to read text on the screen out loud.
+
+
+There are millions of users who greatly benefit from some text-to-speech but
+don’t quite need a full screen reading experience where everything is read
+aloud each step of the way. For these users, whether they are low vision, 
+dyslexic, neurologically diverse, or simply prefer to listen to text read
+aloud instead of visually reading it, we have built Select-to-Speak. 
+
+## Using Select to Speak
+
+Go to Chrome settings, Accessibility settings, “Manage accessibility Features”,
+and enable “Select to Speak”. You can adjust the preferred voice, highlight
+color, and access text-to-speech preferences from the settings page.
+
+With this feature enabled, you can read text on the screen in one of three ways:
+
+- Hold down the Search key, then use the touchpad or external mouse to tap or
+drag a region to be spoken
+
+- Tap the Select-to-Speak icon in the status tray and use the mouse or
+touchscreen to select a region to be spoken
+
+- Highlight text and use Search+S to speak only the selected text.
+
+Read more on the
+[Chrome help page](https://support.google.com/chromebook/answer/9032490?hl=en)
+under “Listen to part of a page”.
+
+## Reporting bugs
+
+Use bugs.chromium.org, filing bugs under the component
+[UI>Accessibility>SelectToSpeak](https://bugs.chromium.org/p/chromium/issues/list?sort=-opened&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified&q=component%3AUI%3EAccessibility%3ESelectToSpeak%20&can=2).
+
+## Developing
+
+*Select to Speak will be abbreviated STS in this section.*
+
+### Code location
+
+STS code lives mainly in three places:
+
+- A component extension to do the bulk of the logic and processing,
+chrome/browser/resources/chromeos/select_to_speak/
+
+- An event handler, ash/events/select_to_speak_event_handler.h
+
+- The status tray button, ash/system/accessibility/select_to_speak_tray.h
+
+In addition, there are settings for STS in 
+chrome/browser/resources/settings/a11y_page/manage_a11y_page.*
+
+### Tests
+
+Tests are in ash_unittests and in browser_tests:
+
+```
+out/Release/ash_unittests --gtest_filter=”SelectToSpeak*”
+out/Release/browser_tests --gtest_filter=”SelectToSpeak*”
+```
+### Debugging
+
+Developers can add log lines to any of the C++ files and see output in the
+console. To debug the STS extension, the easiest way is from an external
+browser. Start Chrome OS on Linux with this command-line flag:
+
+```
+out/Release/chrome --remote-debugging-port=9222
+```
+
+Now open http://localhost:9222 in a separate instance of the browser, and
+debug the Select to Speak extension background page from there.
+
+## How it works
+
+Like [Chromevox](chromevox.md), STS is implemented mainly as a component
+Chrome extension which is always loaded and running in the background when
+enabled, and unloaded when disabled. The only STS code outside of the
+extension is an EventRewriter which forwards keyboard and mouse events to
+the extension as needed, so that the extension can get events systemwide.
+
+The STS extension does the following, at a high level:
+
+1. Tracks key and mouse events to determine when a user has either:
+
+    a. Held down “search” and clicked & dragged a rectangle to specify a
+    selection
+
+    b. Used “search” + “s” to indicate that selected text should be read
+
+    c. Has requested speech to be canceled by tapping ‘control’ or ‘search’
+    alone
+
+2. Determines the Accessibility nodes that make up the selected region
+
+3. Sends utterances to the Chrome Text-to-Speech extension to be spoken
+
+4. Tracks utterance progress and updates the focus ring and highlight as needed.
+
+### Select to Speak extension structure
+
+Most STS logic takes place in
+[select_to_speak.js](https://cs.chromium.org/chromium/src/chrome/browser/resources/chromeos/select_to_speak/select_to_speak.js).
+
+#### User input
+
+Input to the extension is handled by input_handler.js, which handles user
+input from mouse, keyboard, and touchscreen events. Most logic here revolves
+around keeping track of state to see if the user has requested text using
+one of the three ways to activate the feature, search + mouse, tray button 
++ mouse, or search + s.
+
+#### Determining selected content
+
+Once input_handler determines that the user did request text to be spoken,
+STS must determine which part of the page to read. To do this it requests
+information from the Automation API, and then generates a list of
+AutomationNodes to be read.
+
+##### With mouse or touchpad
+
+select_to_speak.js fires a HitTest to the Automation API at the center of
+the rect selected by the user. When the API gets a result it returns via
+SelectToSpeak.onAutomationHitTest_. This function walks up from the hit
+test node to the nearest container to find a root, then back down through
+all the root’s children to find ones that overlap with the selected rect.
+Walking back down through the children occurs in NodeUtils.findAllMatching,
+and results in a list of AutomationNodes that can be sent for speech.
+
+##### With search + s
+
+select_to_speak.js requests focus information from the Automation API. The
+focus result is sent to SelectToSpeak.requestSpeakSelectedText_, which
+uses Automation selection to determine which nodes are selected. The 
+complexity of logic here is converting between Automation selection and
+its deep equivalent, i.e. from parent nodes and offsets to their leaves.
+This occurs in NodeUtils.getDeepEquivalentForSelection. When the first and
+last nodes in selection are found, SelectToSpeak.readNodesInSelection_ is
+used to determine the entire list of AutomationNodes which should be sent 
+for speech.
+
+#### Speaking selected content
+
+SelectToSpeak.startSpeechQueue_ takes a list of AutomationNodes, determines
+their text content, and sends the result to the Text to Speech API for
+speech. It begins by mapping the text content of the nodes to the nodes 
+themselves, so that STS can speak smoothly across node boundaries (i.e.
+across line breaks) and follow speech progress with a highlight. The mapping
+between text and nodes occurs in repeated calls to 
+ParagraphUtils.buildNodeGroup to build lists of nodes that should be spoken 
+smoothly.
+
+
+Each node group is sent to the Text to Speech API, with callbacks to allow 
+for speech progress tracking, enabling the highlight to be dynamically 
+updated with each word.
+
+#### Highlighting content during speech
+
+On each word boundary event, the TTS API sends a callback which is handled
+by SelectToSpeak.onTtsWordEvent_. This is used to check against the list of
+nodes being spoken to see which node is currently being spoken, and further 
+check against the words in the node to see which word is spoken.
+
+#### Edge cases
+
+STS must also handle cases where:
+
+- Nodes become invalid during speech, i.e. if a page was closed. Speech
+should continue, but highlight stops.
+
+- Nodes disappear and re-appear during speech (a user may have switched
+tabs and switched back, or scrolled). Highlight should resume.
+
+This occurs in SelectToSpeak.updateFromNodeState_.
+
+### Communication with SelectToSpeakTray
+
+STS runs in the extension process, but needs to communicate its three states 
+(Inactive, Selecting, and Speaking) to the STS button in the status tray.
+It also needs to listen for users requesting state change using the
+SelectToSpeakTray button. The STS extension uses the AccessibitilityPrivate
+method onSelectToSpeakStateChanged to inform the SelectToSpeakTray of a
+status change, and listens to onSelectToSpeakStateChangeRequested to know 
+when a user wants to change state. The STS extension is the source of truth
+for STS state.
+
+### Special case: Google Drive apps
+
+Google Drive apps require a few work-arounds to work correctly with STS. 
+
+- Any time a Google Drive document is loaded (such as a Doc, Sheet or Slides
+document), the script
+[select_to_speak_gdocs_script](https://cs.chromium.org/chromium/src/chrome/browser/resources/chromeos/select_to_speak/select_to_speak_gdocs_script.js?q=select_to_speak_gdocs_script.js+file:%5Esrc/chrome/browser/resources/chromeos/select_to_speak/+package:%5Echromium$&dr)
+must be executed to remove aria-hidden from the content container.
+
+- Using search+s to read highlighted text uses the clipboard to get text data
+from Google Docs, as selection information may not be available in the
+Automation API. This happens mostly in input_handler.js.
+
+## For Googlers
+
+For more, Googlers could check out the Select to Speak feature design docs
+for more details on design as well as UMA.
+
+- Overall product design, [go/select-to-speak-design](go/select-to-speak-design)
+
+- On-Screen UI for touch and tablet modes, 
+[go/chromeos-sts-on-screen-ui](go/chromeos-sts-on-screen-ui)
+
+- Reading text at keystroke,
+[go/chromeos-sts-selection-keystroke](go/chromeos-sts-selection-keystroke)
+
+- Reading text at keystroke in Google Drive apps, [go/sts-selection-in-drive](go/sts-selection-in-drive)
+
+- Per word highlighting,
+[go/chrome-sts-sentences-and-words](go/chrome-sts-sentences-and-words) and
+[go/chromeos-sts-highlight](go/chromeos-sts-highlight)
diff --git a/extensions/renderer/runtime_hooks_delegate.cc b/extensions/renderer/runtime_hooks_delegate.cc
index 78197f1..6dc92ed 100644
--- a/extensions/renderer/runtime_hooks_delegate.cc
+++ b/extensions/renderer/runtime_hooks_delegate.cc
@@ -89,6 +89,28 @@
     : messaging_service_(messaging_service) {}
 RuntimeHooksDelegate::~RuntimeHooksDelegate() {}
 
+// static
+RequestResult RuntimeHooksDelegate::GetURL(
+    ScriptContext* script_context,
+    const std::vector<v8::Local<v8::Value>>& arguments) {
+  DCHECK_EQ(1u, arguments.size());
+  DCHECK(arguments[0]->IsString());
+  DCHECK(script_context->extension());
+
+  v8::Isolate* isolate = script_context->isolate();
+  std::string path = gin::V8ToString(isolate, arguments[0]);
+
+  RequestResult result(RequestResult::HANDLED);
+  std::string url = base::StringPrintf(
+      "chrome-extension://%s%s%s", script_context->extension()->id().c_str(),
+      !path.empty() && path[0] == '/' ? "" : "/", path.c_str());
+  result.return_value = gin::StringToV8(isolate, url);
+  // TODO(tjudkins): Gather data on how often this is passed a bad URL (i.e. not
+  // a relative file path for the extension), so we can decide if it's fine to
+  // throw an error in those cases.
+  return result;
+}
+
 RequestResult RuntimeHooksDelegate::HandleRequest(
     const std::string& method_name,
     const APISignature* signature,
@@ -174,20 +196,7 @@
 RequestResult RuntimeHooksDelegate::HandleGetURL(
     ScriptContext* script_context,
     const std::vector<v8::Local<v8::Value>>& arguments) {
-  DCHECK_EQ(1u, arguments.size());
-  DCHECK(arguments[0]->IsString());
-  DCHECK(script_context->extension());
-
-  v8::Isolate* isolate = script_context->isolate();
-  std::string path = gin::V8ToString(isolate, arguments[0]);
-
-  RequestResult result(RequestResult::HANDLED);
-  std::string url = base::StringPrintf(
-      "chrome-extension://%s%s%s", script_context->extension()->id().c_str(),
-      !path.empty() && path[0] == '/' ? "" : "/", path.c_str());
-  result.return_value = gin::StringToV8(isolate, url);
-
-  return result;
+  return GetURL(script_context, arguments);
 }
 
 RequestResult RuntimeHooksDelegate::HandleSendMessage(
diff --git a/extensions/renderer/runtime_hooks_delegate.h b/extensions/renderer/runtime_hooks_delegate.h
index c4a3b49e..41cda74 100644
--- a/extensions/renderer/runtime_hooks_delegate.h
+++ b/extensions/renderer/runtime_hooks_delegate.h
@@ -22,6 +22,14 @@
       NativeRendererMessagingService* messaging_service);
   ~RuntimeHooksDelegate() override;
 
+  // Returns an absolute url for a path inside of an extension, as requested
+  // through the getURL API call.
+  // NOTE: Static as the logic is used by both the runtime and extension
+  // hooks.
+  static APIBindingHooks::RequestResult GetURL(
+      ScriptContext* script_context,
+      const std::vector<v8::Local<v8::Value>>& arguments);
+
   // APIBindingHooksDelegate:
   APIBindingHooks::RequestResult HandleRequest(
       const std::string& method_name,
diff --git a/extensions/renderer/runtime_hooks_delegate_unittest.cc b/extensions/renderer/runtime_hooks_delegate_unittest.cc
index bf26381..582c14f 100644
--- a/extensions/renderer/runtime_hooks_delegate_unittest.cc
+++ b/extensions/renderer/runtime_hooks_delegate_unittest.cc
@@ -168,6 +168,8 @@
   get_url("''", extension()->url());
   get_url("'foo'", extension()->GetResourceURL("foo"));
   get_url("'/foo'", extension()->GetResourceURL("foo"));
+  get_url("'https://www.google.com'",
+          GURL(extension()->url().spec() + "https://www.google.com"));
 }
 
 TEST_F(RuntimeHooksDelegateTest, Connect) {
@@ -253,15 +255,12 @@
   tester.TestSendMessage("null, {data: 'hello'}, function() {}",
                          kStandardMessage, self_target, false,
                          SendMessageTester::OPEN);
-  tester.TestSendMessage("null, 'test', function() {}",
-                         R"("test")", self_target, false,
-                         SendMessageTester::OPEN);
-  tester.TestSendMessage("null, 'test'",
-                         R"("test")", self_target, false,
+  tester.TestSendMessage("null, 'test', function() {}", R"("test")",
+                         self_target, false, SendMessageTester::OPEN);
+  tester.TestSendMessage("null, 'test'", R"("test")", self_target, false,
                          SendMessageTester::CLOSED);
-  tester.TestSendMessage("undefined, 'test', function() {}",
-                         R"("test")", self_target, false,
-                         SendMessageTester::OPEN);
+  tester.TestSendMessage("undefined, 'test', function() {}", R"("test")",
+                         self_target, false, SendMessageTester::OPEN);
 
   // Funny case. The only required argument is `message`, which can be any type.
   // This means that if an extension provides a <string, object> pair for the
diff --git a/ios/chrome/browser/app_launcher/BUILD.gn b/ios/chrome/browser/app_launcher/BUILD.gn
index 3d59ccb..02f73a9 100644
--- a/ios/chrome/browser/app_launcher/BUILD.gn
+++ b/ios/chrome/browser/app_launcher/BUILD.gn
@@ -23,6 +23,7 @@
     "//ios/chrome/browser/browser_state",
     "//ios/chrome/browser/reading_list",
     "//ios/chrome/browser/u2f",
+    "//ios/web/common",
     "//ios/web/public",
     "//url",
   ]
diff --git a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
index a9923dfe..2ca5b88 100644
--- a/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
+++ b/ios/chrome/browser/app_launcher/app_launcher_tab_helper.mm
@@ -17,9 +17,9 @@
 #import "ios/chrome/browser/chrome_url_util.h"
 #include "ios/chrome/browser/reading_list/reading_list_model_factory.h"
 #import "ios/chrome/browser/u2f/u2f_tab_helper.h"
+#import "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/navigation/navigation_item.h"
 #import "ios/web/public/navigation/navigation_manager.h"
-#import "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_client.h"
 #import "net/base/mac/url_conversions.h"
 #include "url/gurl.h"
diff --git a/ios/chrome/browser/autofill/BUILD.gn b/ios/chrome/browser/autofill/BUILD.gn
index c69f901..24a16bf 100644
--- a/ios/chrome/browser/autofill/BUILD.gn
+++ b/ios/chrome/browser/autofill/BUILD.gn
@@ -72,6 +72,7 @@
     "//ios/chrome/browser/webdata_services",
     "//ios/chrome/common/colors",
     "//ios/chrome/common/ui_util",
+    "//ios/web/common",
     "//ios/web/public/deprecated",
     "//ios/web/public/js_messaging",
     "//third_party/leveldatabase",
diff --git a/ios/chrome/browser/autofill/form_suggestion_controller.mm b/ios/chrome/browser/autofill/form_suggestion_controller.mm
index 0489bda7..2d5b2b90 100644
--- a/ios/chrome/browser/autofill/form_suggestion_controller.mm
+++ b/ios/chrome/browser/autofill/form_suggestion_controller.mm
@@ -20,9 +20,9 @@
 #import "ios/chrome/browser/autofill/form_suggestion_view.h"
 #import "ios/chrome/browser/passwords/password_generation_utils.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
+#import "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #import "ios/web/public/js_messaging/web_frames_manager.h"
-#import "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
 #import "ios/web/public/web_state/web_state.h"
 
diff --git a/ios/chrome/browser/browsing_data/cache_counter.cc b/ios/chrome/browser/browsing_data/cache_counter.cc
index 1b4d67e..fadbec1e 100644
--- a/ios/chrome/browser/browsing_data/cache_counter.cc
+++ b/ios/chrome/browser/browsing_data/cache_counter.cc
@@ -43,13 +43,12 @@
     STEP_GET_BACKEND,  // Get the disk_cache::Backend instance.
     STEP_COUNT,        // Run CalculateSizeOfAllEntries() on it.
     STEP_CALLBACK,     // Respond on the UI thread.
-    STEP_DONE          // Calculation completed.
   };
 
   void CountInternal(int64_t rv) {
     DCHECK_CURRENTLY_ON(web::WebThread::IO);
 
-    while (rv != net::ERR_IO_PENDING && next_step_ != STEP_DONE) {
+    while (rv != net::ERR_IO_PENDING) {
       // In case of an error, skip to the last step.
       if (rv < 0)
         next_step_ = STEP_CALLBACK;
@@ -83,7 +82,6 @@
         }
 
         case STEP_CALLBACK: {
-          next_step_ = STEP_DONE;
           result_ = rv;
 
           base::PostTaskWithTraits(
@@ -91,11 +89,10 @@
               base::BindOnce(&IOThreadCacheCounter::OnCountingFinished,
                              base::Unretained(this)));
 
-          break;
-        }
-
-        case STEP_DONE: {
-          NOTREACHED();
+          // Return instead of break.
+          // The task above deletes this object; app would crash if this object
+          // is deleted before reentrance of the loop.
+          return;
         }
       }
     }
diff --git a/ios/chrome/browser/passwords/password_controller.mm b/ios/chrome/browser/passwords/password_controller.mm
index fddf59e6..d968917 100644
--- a/ios/chrome/browser/passwords/password_controller.mm
+++ b/ios/chrome/browser/passwords/password_controller.mm
@@ -61,9 +61,10 @@
 #include "ios/chrome/browser/web/tab_id_tab_helper.h"
 #include "ios/chrome/grit/ios_strings.h"
 #import "ios/web/common/origin_util.h"
+#include "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #include "ios/web/public/js_messaging/web_frame.h"
-#include "ios/web/public/url_scheme_util.h"
+#include "ios/web/public/js_messaging/web_frame_util.h"
 #import "ios/web/public/web_state/web_state.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "ui/base/l10n/l10n_util_mac.h"
diff --git a/ios/chrome/browser/send_tab_to_self/ios_send_tab_to_self_infobar_delegate.cc b/ios/chrome/browser/send_tab_to_self/ios_send_tab_to_self_infobar_delegate.cc
index 6bf918b3b..52978bd9 100644
--- a/ios/chrome/browser/send_tab_to_self/ios_send_tab_to_self_infobar_delegate.cc
+++ b/ios/chrome/browser/send_tab_to_self/ios_send_tab_to_self_infobar_delegate.cc
@@ -66,7 +66,7 @@
   model_->MarkEntryOpened(entry_->GetGUID());
   RecordNotificationHistogram(SendTabToSelfNotification::kOpened);
   infobar()->owner()->OpenURL(entry_->GetURL(),
-                              WindowOpenDisposition::CURRENT_TAB);
+                              WindowOpenDisposition::NEW_FOREGROUND_TAB);
   return true;
 }
 
diff --git a/ios/chrome/browser/ui/autofill/BUILD.gn b/ios/chrome/browser/ui/autofill/BUILD.gn
index 3e35495..d54d0ac1 100644
--- a/ios/chrome/browser/ui/autofill/BUILD.gn
+++ b/ios/chrome/browser/ui/autofill/BUILD.gn
@@ -58,6 +58,7 @@
     "//ios/public/provider/chrome/browser",
     "//ios/third_party/material_components_ios",
     "//ios/third_party/material_roboto_font_loader_ios",
+    "//ios/web/common",
     "//ios/web/public/deprecated",
     "//ios/web/public/js_messaging",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm b/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm
index 4ad983d..36d6d49 100644
--- a/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm
+++ b/ios/chrome/browser/ui/autofill/form_input_accessory_mediator.mm
@@ -26,10 +26,10 @@
 #import "ios/chrome/browser/ui/util/keyboard_observer_helper.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/web_state_list/web_state_list.h"
+#import "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #include "ios/web/public/js_messaging/web_frame.h"
 #include "ios/web/public/js_messaging/web_frames_manager.h"
-#import "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_state/web_state.h"
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
diff --git a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
index a48d433..02a21d1f 100644
--- a/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view/browser_view_controller.mm
@@ -182,13 +182,13 @@
 #include "ios/public/provider/chrome/browser/voice/voice_search_controller.h"
 #include "ios/public/provider/chrome/browser/voice/voice_search_provider.h"
 #import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
+#include "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #import "ios/web/public/deprecated/crw_native_content_holder.h"
 #import "ios/web/public/deprecated/crw_native_content_provider.h"
 #import "ios/web/public/deprecated/crw_web_controller_util.h"
 #include "ios/web/public/navigation/navigation_item.h"
 #include "ios/web/public/thread/web_thread.h"
-#include "ios/web/public/url_scheme_util.h"
 #include "ios/web/public/web_client.h"
 #import "ios/web/public/web_state/context_menu_params.h"
 #import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
diff --git a/ios/chrome/browser/ui/favicon/resources/BUILD.gn b/ios/chrome/browser/ui/favicon/resources/BUILD.gn
index aa6b381..3d7c12a 100644
--- a/ios/chrome/browser/ui/favicon/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/favicon/resources/BUILD.gn
@@ -39,12 +39,3 @@
     "default_favicon.imageset/default_favicon@3x.png",
   ]
 }
-
-imageset("default_favicon_incognito") {
-  sources = [
-    "default_favicon_incognito.imageset/Contents.json",
-    "default_favicon_incognito.imageset/default_favicon_incognito.png",
-    "default_favicon_incognito.imageset/default_favicon_incognito@2x.png",
-    "default_favicon_incognito.imageset/default_favicon_incognito@3x.png",
-  ]
-}
diff --git a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/Contents.json b/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/Contents.json
deleted file mode 100644
index d76f15b5..0000000
--- a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/Contents.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-    "images": [
-        {
-            "idiom": "universal",
-            "scale": "1x",
-            "filename": "default_favicon_incognito.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "2x",
-            "filename": "default_favicon_incognito@2x.png"
-        },
-        {
-            "idiom": "universal",
-            "scale": "3x",
-            "filename": "default_favicon_incognito@3x.png"
-        }
-    ],
-    "info": {
-        "version": 1,
-        "author": "xcode"
-    }
-}
diff --git a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito.png b/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito.png
deleted file mode 100644
index 5d59a8b6..0000000
--- a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito@2x.png b/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito@2x.png
deleted file mode 100644
index 39620f2..0000000
--- a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito@2x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito@3x.png b/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito@3x.png
deleted file mode 100644
index 87c9560..0000000
--- a/ios/chrome/browser/ui/favicon/resources/default_favicon_incognito.imageset/default_favicon_incognito@3x.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/payments/payment_request_manager.mm b/ios/chrome/browser/ui/payments/payment_request_manager.mm
index 879056b..d122353e 100644
--- a/ios/chrome/browser/ui/payments/payment_request_manager.mm
+++ b/ios/chrome/browser/ui/payments/payment_request_manager.mm
@@ -57,6 +57,7 @@
 #import "ios/chrome/browser/ui/payments/payment_request_coordinator.h"
 #import "ios/chrome/browser/ui/payments/payment_request_error_coordinator.h"
 #include "ios/web/common/origin_util.h"
+#import "ios/web/common/url_scheme_util.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
 #include "ios/web/public/deprecated/url_verification_constants.h"
 #include "ios/web/public/favicon/favicon_status.h"
@@ -66,7 +67,6 @@
 #include "ios/web/public/navigation/navigation_item.h"
 #include "ios/web/public/navigation/navigation_manager.h"
 #include "ios/web/public/security/ssl_status.h"
-#import "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_state/ui/crw_web_view_proxy.h"
 #import "ios/web/public/web_state/web_state.h"
 #import "ios/web/public/web_state/web_state_observer_bridge.h"
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h b/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h
index 2bd0fca..4833e07 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h
+++ b/ios/chrome/browser/ui/reading_list/reading_list_list_item_accessibility_delegate.h
@@ -14,7 +14,6 @@
 // Returns whether the entry is read.
 - (BOOL)isItemRead:(id<ReadingListListItem>)item;
 
-- (void)deleteItem:(id<ReadingListListItem>)item;
 - (void)openItemInNewTab:(id<ReadingListListItem>)item;
 - (void)openItemInNewIncognitoTab:(id<ReadingListListItem>)item;
 - (void)openItemOffline:(id<ReadingListListItem>)item;
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_list_item_custom_action_factory.mm b/ios/chrome/browser/ui/reading_list/reading_list_list_item_custom_action_factory.mm
index 47e4fd9..d93eca7 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_list_item_custom_action_factory.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_list_item_custom_action_factory.mm
@@ -55,11 +55,6 @@
 
 - (NSArray<UIAccessibilityCustomAction*>*)customActionsForItem:
     (id<ReadingListListItem>)item {
-  ReadingListCustomAction* deleteAction = [[ReadingListCustomAction alloc]
-      initWithName:l10n_util::GetNSString(IDS_IOS_READING_LIST_DELETE_BUTTON)
-            target:self
-          selector:@selector(deleteItem:)
-              item:item];
   ReadingListCustomAction* toggleReadStatus = nil;
   if ([self.accessibilityDelegate isItemRead:item]) {
     toggleReadStatus = [[ReadingListCustomAction alloc]
@@ -97,7 +92,7 @@
               item:item];
 
   NSMutableArray* customActions = [NSMutableArray
-      arrayWithObjects:deleteAction, toggleReadStatus, openInNewTabAction,
+      arrayWithObjects:toggleReadStatus, openInNewTabAction,
                        openInNewIncognitoTabAction, copyURLAction, nil];
 
   if (item.distillationState == ReadingListUIDistillationStatusSuccess) {
@@ -117,11 +112,6 @@
   return customActions;
 }
 
-- (BOOL)deleteItem:(ReadingListCustomAction*)action {
-  [self.accessibilityDelegate deleteItem:action.item];
-  return YES;
-}
-
 - (BOOL)markRead:(ReadingListCustomAction*)action {
   [self.accessibilityDelegate markItemRead:action.item];
   return YES;
diff --git a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
index f0753dba..f9d5403 100644
--- a/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
+++ b/ios/chrome/browser/ui/reading_list/reading_list_table_view_controller.mm
@@ -359,13 +359,6 @@
   return [self.dataSource isItemRead:item];
 }
 
-- (void)deleteItem:(id<ReadingListListItem>)item {
-  TableViewModel* model = self.tableViewModel;
-  TableViewItem* tableViewItem = base::mac::ObjCCastStrict<TableViewItem>(item);
-  if ([model hasItem:tableViewItem])
-    [self deleteItemsAtIndexPaths:@[ [model indexPathForItem:tableViewItem] ]];
-}
-
 - (void)openItemInNewTab:(id<ReadingListListItem>)item {
   [self.delegate readingListListViewController:self
                               openItemInNewTab:item
diff --git a/ios/chrome/browser/ui/tabs/BUILD.gn b/ios/chrome/browser/ui/tabs/BUILD.gn
index 5271a7b..ee46488 100644
--- a/ios/chrome/browser/ui/tabs/BUILD.gn
+++ b/ios/chrome/browser/ui/tabs/BUILD.gn
@@ -31,11 +31,10 @@
     "resources:tabstrip_background_tab",
     "resources:tabstrip_foreground_tab",
     "resources:tabstrip_inactive_tab_close_button_color",
+    "resources:tabstrip_inactive_tab_text_color",
     "resources:tabstrip_incognito_background_tab",
     "resources:tabstrip_incognito_foreground_tab",
     "resources:tabstrip_new_tab",
-    "resources:tabstrip_new_tab_incognito",
-    "resources:tabstrip_new_tab_incognito_pressed",
     "resources:tabstrip_new_tab_pressed",
     "resources:tabstrip_tab_switcher_count_button",
     "resources:tabstrip_tab_switcher_count_button_pressed",
@@ -52,7 +51,6 @@
     "//ios/chrome/browser/ui/colors",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/favicon/resources:default_favicon",
-    "//ios/chrome/browser/ui/favicon/resources:default_favicon_incognito",
     "//ios/chrome/browser/ui/fullscreen",
     "//ios/chrome/browser/ui/image_util",
     "//ios/chrome/browser/ui/ntp:util",
diff --git a/ios/chrome/browser/ui/tabs/resources/BUILD.gn b/ios/chrome/browser/ui/tabs/resources/BUILD.gn
index 89aadda..b088fd30 100644
--- a/ios/chrome/browser/ui/tabs/resources/BUILD.gn
+++ b/ios/chrome/browser/ui/tabs/resources/BUILD.gn
@@ -17,6 +17,7 @@
   sources = [
     "tabstrip_background_tab.imageset/Contents.json",
     "tabstrip_background_tab.imageset/tabstrip_background_tab@2x~ipad.png",
+    "tabstrip_background_tab.imageset/tabstrip_background_tab_dark@2x~ipad.png",
   ]
 }
 
@@ -24,6 +25,7 @@
   sources = [
     "tabstrip_foreground_tab.imageset/Contents.json",
     "tabstrip_foreground_tab.imageset/tabstrip_foreground_tab@2x~ipad.png",
+    "tabstrip_foreground_tab.imageset/tabstrip_foreground_tab_dark@2x~ipad.png",
   ]
 }
 
@@ -48,20 +50,6 @@
   ]
 }
 
-imageset("tabstrip_new_tab_incognito") {
-  sources = [
-    "tabstrip_new_tab_incognito.imageset/Contents.json",
-    "tabstrip_new_tab_incognito.imageset/tabstrip_new_tab_incognito@2x~ipad.png",
-  ]
-}
-
-imageset("tabstrip_new_tab_incognito_pressed") {
-  sources = [
-    "tabstrip_new_tab_incognito_pressed.imageset/Contents.json",
-    "tabstrip_new_tab_incognito_pressed.imageset/tabstrip_new_tab_incognito_pressed@2x~ipad.png",
-  ]
-}
-
 imageset("tabstrip_new_tab_pressed") {
   sources = [
     "tabstrip_new_tab_pressed.imageset/Contents.json",
@@ -110,3 +98,9 @@
     "tabstrip_inactive_tab_close_button_color.colorset/Contents.json",
   ]
 }
+
+colorset("tabstrip_inactive_tab_text_color") {
+  sources = [
+    "tabstrip_inactive_tab_text_color.colorset/Contents.json",
+  ]
+}
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/Contents.json b/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/Contents.json
index 8ea30cd..3154c21 100644
--- a/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/Contents.json
+++ b/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/Contents.json
@@ -4,8 +4,19 @@
             "idiom": "ipad",
             "scale": "2x",
             "filename": "tabstrip_background_tab@2x~ipad.png"
-        }
-    ],
+        },
+        {
+          "idiom" : "universal",
+          "filename" : "tabstrip_background_tab_dark@2x~ipad.png",
+          "appearances" : [
+            {
+              "appearance" : "luminosity",
+              "value" : "dark"
+          }
+      ],
+          "scale" : "2x"
+      }
+  ],
     "info": {
         "version": 1,
         "author": "xcode"
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/tabstrip_background_tab_dark@2x~ipad.png b/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/tabstrip_background_tab_dark@2x~ipad.png
new file mode 100644
index 0000000..cd601ff
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/resources/tabstrip_background_tab.imageset/tabstrip_background_tab_dark@2x~ipad.png
Binary files differ
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/Contents.json b/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/Contents.json
index 02c1a19..f489b46 100644
--- a/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/Contents.json
+++ b/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/Contents.json
@@ -4,7 +4,18 @@
             "idiom": "ipad",
             "scale": "2x",
             "filename": "tabstrip_foreground_tab@2x~ipad.png"
-        }
+        },
+        {
+          "idiom" : "universal",
+          "filename" : "tabstrip_foreground_tab_dark@2x~ipad.png",
+          "appearances" : [
+            {
+              "appearance" : "luminosity",
+              "value" : "dark"
+            }
+          ],
+          "scale" : "2x"
+      }
     ],
     "info": {
         "version": 1,
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/tabstrip_foreground_tab_dark@2x~ipad.png b/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/tabstrip_foreground_tab_dark@2x~ipad.png
new file mode 100644
index 0000000..e3bab5ee
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/resources/tabstrip_foreground_tab.imageset/tabstrip_foreground_tab_dark@2x~ipad.png
Binary files differ
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_inactive_tab_text_color.colorset/Contents.json b/ios/chrome/browser/ui/tabs/resources/tabstrip_inactive_tab_text_color.colorset/Contents.json
new file mode 100644
index 0000000..be1320b3
--- /dev/null
+++ b/ios/chrome/browser/ui/tabs/resources/tabstrip_inactive_tab_text_color.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  },
+  "colors" : [
+    {
+      "idiom" : "universal",
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "red" : "0x5F",
+          "alpha" : "1.000",
+          "blue" : "0x67",
+          "green" : "0x63"
+        }
+      }
+    },
+    {
+      "idiom" : "universal",
+      "appearances" : [
+        {
+          "appearance" : "luminosity",
+          "value" : "dark"
+        }
+      ],
+      "color" : {
+        "color-space" : "display-p3",
+        "components" : {
+          "red" : "0x9A",
+          "alpha" : "1.000",
+          "blue" : "0xA6",
+          "green" : "0xA0"
+        }
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito.imageset/Contents.json b/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito.imageset/Contents.json
deleted file mode 100644
index 104b951..0000000
--- a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito.imageset/Contents.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "images": [
-        {
-            "idiom": "ipad",
-            "scale": "2x",
-            "filename": "tabstrip_new_tab_incognito@2x~ipad.png"
-        }
-    ],
-    "info": {
-        "version": 1,
-        "author": "xcode"
-    }
-}
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito.imageset/tabstrip_new_tab_incognito@2x~ipad.png b/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito.imageset/tabstrip_new_tab_incognito@2x~ipad.png
deleted file mode 100644
index 1817e4e..0000000
--- a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito.imageset/tabstrip_new_tab_incognito@2x~ipad.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito_pressed.imageset/Contents.json b/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito_pressed.imageset/Contents.json
deleted file mode 100644
index 7788e94..0000000
--- a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito_pressed.imageset/Contents.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-    "images": [
-        {
-            "idiom": "ipad",
-            "scale": "2x",
-            "filename": "tabstrip_new_tab_incognito_pressed@2x~ipad.png"
-        }
-    ],
-    "info": {
-        "version": 1,
-        "author": "xcode"
-    }
-}
diff --git a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito_pressed.imageset/tabstrip_new_tab_incognito_pressed@2x~ipad.png b/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito_pressed.imageset/tabstrip_new_tab_incognito_pressed@2x~ipad.png
deleted file mode 100644
index 1f822d0..0000000
--- a/ios/chrome/browser/ui/tabs/resources/tabstrip_new_tab_incognito_pressed.imageset/tabstrip_new_tab_incognito_pressed@2x~ipad.png
+++ /dev/null
Binary files differ
diff --git a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
index afd36f5..8387bc0f 100644
--- a/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
+++ b/ios/chrome/browser/ui/tabs/tab_strip_controller.mm
@@ -454,21 +454,15 @@
     _buttonNewTab.autoresizingMask = (UIViewAutoresizingFlexibleRightMargin |
                                       UIViewAutoresizingFlexibleBottomMargin);
     _buttonNewTab.imageView.contentMode = UIViewContentModeCenter;
-    UIImage* buttonNewTabImage = nil;
-    UIImage* buttonNewTabPressedImage = nil;
 
-    if (_style == INCOGNITO) {
-      buttonNewTabImage = [UIImage imageNamed:@"tabstrip_new_tab_incognito"];
-      buttonNewTabPressedImage =
-          [UIImage imageNamed:@"tabstrip_new_tab_incognito_pressed"];
-    } else {
-      buttonNewTabImage = [UIImage imageNamed:@"tabstrip_new_tab"];
-      buttonNewTabPressedImage =
-          [UIImage imageNamed:@"tabstrip_new_tab_pressed"];
-    }
+    UIImage* buttonNewTabImage = [UIImage imageNamed:@"tabstrip_new_tab"];
     [_buttonNewTab setImage:buttonNewTabImage forState:UIControlStateNormal];
+
+    UIImage* buttonNewTabPressedImage =
+        [UIImage imageNamed:@"tabstrip_new_tab_pressed"];
     [_buttonNewTab setImage:buttonNewTabPressedImage
                    forState:UIControlStateHighlighted];
+
     UIEdgeInsets imageInsets = UIEdgeInsetsMake(
         kNewTabButtonTopImageInset, kNewTabButtonHorizontalImageInset,
         kNewTabButtonBottomImageInset, kNewTabButtonHorizontalImageInset);
diff --git a/ios/chrome/browser/ui/tabs/tab_view.mm b/ios/chrome/browser/ui/tabs/tab_view.mm
index f95d90cf..d01a3aa 100644
--- a/ios/chrome/browser/ui/tabs/tab_view.mm
+++ b/ios/chrome/browser/ui/tabs/tab_view.mm
@@ -17,6 +17,7 @@
 #import "ios/chrome/browser/ui/image_util/image_util.h"
 #include "ios/chrome/browser/ui/util/rtl_geometry.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
+#import "ios/chrome/common/colors/semantic_color_names.h"
 #import "ios/chrome/common/highlight_button.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -54,6 +55,11 @@
 const CGFloat kCloseButtonSize = 24.0;
 const CGFloat kFaviconSize = 16.0;
 
+// Returns a default favicon with |UIImageRenderingModeAlwaysTemplate|.
+UIImage* DefaultFaviconImage() {
+  return [[UIImage imageNamed:@"default_favicon"]
+      imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
+}
 }
 
 @interface TabView ()<DropAndNavigateDelegate> {
@@ -89,9 +95,6 @@
 // Creates the close button, favicon button, and title.
 - (void)createButtonsAndLabel;
 
-// Return the default favicon image based on the current incognito style.
-- (UIImage*)defaultFaviconImage;
-
 // Returns the rect in which to draw the favicon.
 - (CGRect)faviconRectForBounds:(CGRect)bounds;
 
@@ -170,15 +173,27 @@
 
 - (void)setFavicon:(UIImage*)favicon {
   if (!favicon)
-    favicon = [self defaultFaviconImage];
+    favicon = DefaultFaviconImage();
   [_faviconView setImage:favicon];
 }
 
 - (void)setIncognitoStyle:(BOOL)incognitoStyle {
+  if (_incognitoStyle == incognitoStyle) {
+    return;
+  }
   _incognitoStyle = incognitoStyle;
-  _titleLabel.textColor =
-      incognitoStyle ? [UIColor whiteColor] : [UIColor blackColor];
-  [_faviconView setImage:[self defaultFaviconImage]];
+
+#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
+  if (@available(iOS 13, *)) {
+    // When iOS 12 is dropped, only the next line is needed for styling.
+    // Every other check for |incognitoStyle| can be removed, as well as the
+    // incognito specific assets.
+    self.overrideUserInterfaceStyle = _incognitoStyle
+                                          ? UIUserInterfaceStyleDark
+                                          : UIUserInterfaceStyleUnspecified;
+    return;
+  }
+#endif
   [self updateStyleForSelected:self.selected];
 }
 
@@ -230,6 +245,24 @@
   return CGRectContainsPoint(CGRectInset([self bounds], inset, 0), point);
 }
 
+- (void)traitCollectionDidChange:(UITraitCollection*)previousTraitCollection {
+  [super traitCollectionDidChange:previousTraitCollection];
+
+#if defined(__IPHONE_13_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0)
+  if (@available(iOS 13, *)) {
+    // As of iOS 13 Beta 4, resizable images are flaky for dark mode.
+    // This triggers the styling again, where the image is resolved instead of
+    // relying in the system's magic. Radar filled:
+    // b/137942721.hasDifferentColorAppearanceComparedToTraitCollection
+    if ([self.traitCollection
+            hasDifferentColorAppearanceComparedToTraitCollection:
+                previousTraitCollection]) {
+      [self updateStyleForSelected:self.selected];
+    }
+  }
+#endif
+}
+
 #pragma mark - Private
 
 - (void)createCommonViews {
@@ -290,7 +323,7 @@
   _faviconView = [[UIImageView alloc] initWithFrame:faviconFrame];
   [_faviconView setTranslatesAutoresizingMaskIntoConstraints:NO];
   [_faviconView setContentMode:UIViewContentModeScaleAspectFit];
-  [_faviconView setImage:[self defaultFaviconImage]];
+  [_faviconView setImage:DefaultFaviconImage()];
   [_faviconView setAccessibilityIdentifier:@"Favicon"];
   [self addSubview:_faviconView];
 
@@ -341,29 +374,44 @@
 // Updates this tab's style based on the value of |selected| and the current
 // incognito style.
 - (void)updateStyleForSelected:(BOOL)selected {
+  // Style the background image first.
   NSString* state = (selected ? @"foreground" : @"background");
-  NSString* incognito = _incognitoStyle ? @"incognito_" : @"";
+  NSString* incognito = self.incognitoStyle ? @"incognito_" : @"";
   NSString* imageName =
       [NSString stringWithFormat:@"tabstrip_%@%@_tab", incognito, state];
   CGFloat leftInset = kTabBackgroundLeftCapInset;
+  // As of iOS 13 Beta 4, resizable images are flaky for dark mode.
+  // Radar filled: b/137942721.
+  UIImage* resolvedImage = [UIImage imageNamed:imageName
+                                      inBundle:nil
+                 compatibleWithTraitCollection:self.traitCollection];
   UIImage* backgroundImage =
-      StretchableImageFromUIImage([UIImage imageNamed:imageName], leftInset, 0);
-  [_backgroundImageView setImage:backgroundImage];
+      StretchableImageFromUIImage(resolvedImage, leftInset, 0);
+  _backgroundImageView.image = backgroundImage;
 
-  NSString* colorName;
+  // Style the close button tint color.
+  NSString* closeButtonColorName;
   if (selected) {
-    colorName = _incognitoStyle
-                    ? @"tabstrip_active_tab_incognito_close_button_color"
-                    : @"tabstrip_active_tab_close_button_color";
+    closeButtonColorName =
+        self.incognitoStyle
+            ? @"tabstrip_active_tab_incognito_close_button_color"
+            : @"tabstrip_active_tab_close_button_color";
   } else {
-    colorName = @"tabstrip_inactive_tab_close_button_color";
+    closeButtonColorName = @"tabstrip_inactive_tab_close_button_color";
   }
-  _closeButton.tintColor = [UIColor colorNamed:colorName];
-}
+  _closeButton.tintColor = [UIColor colorNamed:closeButtonColorName];
 
-- (UIImage*)defaultFaviconImage {
-  return self.incognitoStyle ? [UIImage imageNamed:@"default_favicon_incognito"]
-                             : [UIImage imageNamed:@"default_favicon"];
+  // Style the favicon tint color and the title label.
+  NSString* faviconColorName;
+  if (selected) {
+    faviconColorName = kTextPrimaryColor;
+  } else {
+    faviconColorName = @"tabstrip_inactive_tab_text_color";
+  }
+  _faviconView.tintColor = self.incognitoStyle && selected
+                               ? [UIColor whiteColor]
+                               : [UIColor colorNamed:faviconColorName];
+  self.titleLabel.textColor = _faviconView.tintColor;
 }
 
 #pragma mark - DropAndNavigateDelegate
diff --git a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
index d5647b8e..fe918cb 100644
--- a/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
+++ b/ios/chrome/browser/ui/toolbar/buttons/toolbar_button_factory.mm
@@ -211,7 +211,7 @@
                                : [UIColor whiteColor];
   [cancelButton setTitle:l10n_util::GetNSString(IDS_CANCEL)
                 forState:UIControlStateNormal];
-  [cancelButton setContentHuggingPriority:UILayoutPriorityDefaultHigh
+  [cancelButton setContentHuggingPriority:UILayoutPriorityRequired
                                   forAxis:UILayoutConstraintAxisHorizontal];
   [cancelButton
       setContentCompressionResistancePriority:UILayoutPriorityRequired
diff --git a/ios/chrome/browser/upgrade/BUILD.gn b/ios/chrome/browser/upgrade/BUILD.gn
index d805333..905c5d7 100644
--- a/ios/chrome/browser/upgrade/BUILD.gn
+++ b/ios/chrome/browser/upgrade/BUILD.gn
@@ -16,7 +16,7 @@
     "//components/version_info",
     "//ios/chrome/app/strings",
     "//ios/chrome/browser/ui/commands",
-    "//ios/web",
+    "//ios/web/common",
     "//net",
     "//ui/base",
     "//ui/gfx",
diff --git a/ios/chrome/browser/upgrade/upgrade_center.mm b/ios/chrome/browser/upgrade/upgrade_center.mm
index 90b0096..3a42859 100644
--- a/ios/chrome/browser/upgrade/upgrade_center.mm
+++ b/ios/chrome/browser/upgrade/upgrade_center.mm
@@ -20,7 +20,7 @@
 #import "ios/chrome/browser/ui/commands/open_new_tab_command.h"
 #include "ios/chrome/grit/ios_chromium_strings.h"
 #include "ios/chrome/grit/ios_strings.h"
-#import "ios/web/public/url_scheme_util.h"
+#import "ios/web/common/url_scheme_util.h"
 #import "net/base/mac/url_conversions.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/gfx/image/image.h"
diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn
index c09fbb3e..483459e2 100644
--- a/ios/web/BUILD.gn
+++ b/ios/web/BUILD.gn
@@ -70,7 +70,6 @@
     "browser_url_rewriter_impl.mm",
     "crw_navigation_item_storage.mm",
     "network_context_owner.cc",
-    "url_scheme_util.mm",
     "web_client.mm",
   ]
 
@@ -283,7 +282,6 @@
     "network_context_owner_unittest.cc",
     "service_manager_connection_impl_unittest.cc",
     "test/web_test_unittest.mm",
-    "url_scheme_util_unittest.mm",
     "web_client_unittest.mm",
     "web_thread_unittest.cc",
   ]
diff --git a/ios/web/common/BUILD.gn b/ios/web/common/BUILD.gn
index 9103251..9e56cb40 100644
--- a/ios/web/common/BUILD.gn
+++ b/ios/web/common/BUILD.gn
@@ -15,6 +15,8 @@
     "origin_util.mm",
     "referrer_util.cc",
     "referrer_util.h",
+    "url_scheme_util.h",
+    "url_scheme_util.mm",
     "url_util.cc",
     "url_util.h",
   ]
@@ -64,6 +66,7 @@
   sources = [
     "origin_util_unittest.mm",
     "referrer_util_unittest.cc",
+    "url_scheme_util_unittest.mm",
     "url_util_unittest.cc",
   ]
 }
diff --git a/ios/web/public/url_scheme_util.h b/ios/web/common/url_scheme_util.h
similarity index 84%
rename from ios/web/public/url_scheme_util.h
rename to ios/web/common/url_scheme_util.h
index 7300b2f..a64b785 100644
--- a/ios/web/public/url_scheme_util.h
+++ b/ios/web/common/url_scheme_util.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 IOS_WEB_PUBLIC_URL_SCHEME_UTIL_H_
-#define IOS_WEB_PUBLIC_URL_SCHEME_UTIL_H_
+#ifndef IOS_WEB_COMMON_URL_SCHEME_UTIL_H_
+#define IOS_WEB_COMMON_URL_SCHEME_UTIL_H_
 
 class GURL;
 @class NSURL;
@@ -22,4 +22,4 @@
 
 }  // namespace web
 
-#endif  // IOS_WEB_PUBLIC_URL_SCHEME_UTIL_H_
+#endif  // IOS_WEB_COMMON_URL_SCHEME_UTIL_H_
diff --git a/ios/web/url_scheme_util.mm b/ios/web/common/url_scheme_util.mm
similarity index 86%
rename from ios/web/url_scheme_util.mm
rename to ios/web/common/url_scheme_util.mm
index 6c60e52..ce2fde28 100644
--- a/ios/web/url_scheme_util.mm
+++ b/ios/web/common/url_scheme_util.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/web/public/url_scheme_util.h"
+#import "ios/web/common/url_scheme_util.h"
 
 #import <Foundation/Foundation.h>
 
@@ -16,8 +16,7 @@
 namespace web {
 
 bool UrlHasWebScheme(const GURL& url) {
-  return url.SchemeIs(url::kHttpScheme) ||
-         url.SchemeIs(url::kHttpsScheme) ||
+  return url.SchemeIs(url::kHttpScheme) || url.SchemeIs(url::kHttpsScheme) ||
          url.SchemeIs(url::kDataScheme);
 }
 
diff --git a/ios/web/url_scheme_util_unittest.mm b/ios/web/common/url_scheme_util_unittest.mm
similarity index 96%
rename from ios/web/url_scheme_util_unittest.mm
rename to ios/web/common/url_scheme_util_unittest.mm
index c1f171b..569e006 100644
--- a/ios/web/url_scheme_util_unittest.mm
+++ b/ios/web/common/url_scheme_util_unittest.mm
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#import "ios/web/public/url_scheme_util.h"
+#import "ios/web/common/url_scheme_util.h"
 
 #import <Foundation/Foundation.h>
 
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index 2589f00..dc3f3cb 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -10,6 +10,7 @@
 #include "base/timer/timer.h"
 #import "ios/net/http_response_headers_util.h"
 #include "ios/web/common/features.h"
+#import "ios/web/common/url_scheme_util.h"
 #import "ios/web/js_messaging/crw_js_injector.h"
 #import "ios/web/js_messaging/web_frames_manager_impl.h"
 #import "ios/web/navigation/crw_pending_navigation_info.h"
@@ -26,7 +27,6 @@
 #import "ios/web/navigation/wk_navigation_util.h"
 #include "ios/web/public/browser_state.h"
 #import "ios/web/public/download/download_controller.h"
-#import "ios/web/public/url_scheme_util.h"
 #import "ios/web/public/web_client.h"
 #import "ios/web/security/crw_cert_verification_controller.h"
 #import "ios/web/security/wk_web_view_security_util.h"
diff --git a/ios/web/public/BUILD.gn b/ios/web/public/BUILD.gn
index d5f228e8..1ebc6ea 100644
--- a/ios/web/public/BUILD.gn
+++ b/ios/web/public/BUILD.gn
@@ -30,7 +30,6 @@
     "java_script_dialog_presenter.h",
     "java_script_dialog_type.h",
     "service_manager_connection.h",
-    "url_scheme_util.h",
     "url_schemes.mm",
     "web_client.h",
     "web_state/page_display_state.mm",
diff --git a/ios/web_view/internal/passwords/cwv_password_controller.mm b/ios/web_view/internal/passwords/cwv_password_controller.mm
index 29e06e2..6a3ad2b 100644
--- a/ios/web_view/internal/passwords/cwv_password_controller.mm
+++ b/ios/web_view/internal/passwords/cwv_password_controller.mm
@@ -16,8 +16,9 @@
 #import "components/password_manager/ios/password_form_helper.h"
 #import "components/password_manager/ios/password_suggestion_helper.h"
 #import "ios/web/common/origin_util.h"
+#include "ios/web/common/url_scheme_util.h"
 #include "ios/web/public/js_messaging/web_frame.h"
-#include "ios/web/public/url_scheme_util.h"
+#include "ios/web/public/js_messaging/web_frame_util.h"
 #import "ios/web/public/web_state/web_state_observer_bridge.h"
 #import "ios/web_view/internal/autofill/cwv_autofill_suggestion_internal.h"
 #import "ios/web_view/internal/passwords/web_view_password_manager_client.h"
diff --git a/media/renderers/paint_canvas_video_renderer.cc b/media/renderers/paint_canvas_video_renderer.cc
index 9ca3c31f..ca67530 100644
--- a/media/renderers/paint_canvas_video_renderer.cc
+++ b/media/renderers/paint_canvas_video_renderer.cc
@@ -14,6 +14,7 @@
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_image_builder.h"
 #include "components/viz/common/gpu/context_provider.h"
+#include "components/viz/common/resources/resource_format.h"
 #include "gpu/GLES2/gl2extchromium.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
@@ -90,7 +91,7 @@
     gfx::ColorSpace video_color_space,
     VideoPixelFormat video_format,
     GrBackendTexture* yuv_textures,
-    GrBackendTexture* result_texture = nullptr) {
+    const GrBackendTexture& result_texture) {
   // TODO(hubbe): This should really default to rec709.
   // https://crbug.com/828599
   SkYUVColorSpace color_space = kRec601_SkYUVColorSpace;
@@ -98,34 +99,62 @@
 
   switch (video_format) {
     case PIXEL_FORMAT_NV12:
-      if (result_texture) {
-        return SkImage::MakeFromNV12TexturesCopyWithExternalBackend(
-            gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin,
-            result_texture[0]);
-      } else {
-        return SkImage::MakeFromNV12TexturesCopy(
-            gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin);
-      }
+      return SkImage::MakeFromNV12TexturesCopyWithExternalBackend(
+          gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin,
+          result_texture);
     case PIXEL_FORMAT_I420:
-      if (result_texture) {
-        return SkImage::MakeFromYUVTexturesCopyWithExternalBackend(
-            gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin,
-            result_texture[0]);
-      } else {
-        return SkImage::MakeFromYUVTexturesCopy(
-            gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin);
-      }
+      return SkImage::MakeFromYUVTexturesCopyWithExternalBackend(
+          gr_context, color_space, yuv_textures, kTopLeft_GrSurfaceOrigin,
+          result_texture);
     default:
       NOTREACHED();
       return nullptr;
   }
 }
 
+// Helper class that begins/ends access to a mailbox within a scope. The mailbox
+// must have been imported into |texture|.
+class ScopedSharedImageAccess {
+ public:
+  ScopedSharedImageAccess(
+      gpu::gles2::GLES2Interface* gl,
+      GLuint texture,
+      const gpu::Mailbox& mailbox,
+      GLenum access = GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM)
+      : gl(gl), texture(texture), is_shared_image(mailbox.IsSharedImage()) {
+    if (is_shared_image)
+      gl->BeginSharedImageAccessDirectCHROMIUM(texture, access);
+  }
+
+  ~ScopedSharedImageAccess() {
+    if (is_shared_image)
+      gl->EndSharedImageAccessDirectCHROMIUM(texture);
+  }
+
+ private:
+  gpu::gles2::GLES2Interface* gl;
+  GLuint texture;
+  bool is_shared_image;
+};
+
+// Waits for a sync token and import the mailbox as texture.
+GLuint SynchronizeAndImportMailbox(gpu::gles2::GLES2Interface* gl,
+                                   const gpu::SyncToken& sync_token,
+                                   const gpu::Mailbox& mailbox) {
+  gl->WaitSyncTokenCHROMIUM(sync_token.GetConstData());
+  if (mailbox.IsSharedImage()) {
+    return gl->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.name);
+  } else {
+    return gl->CreateAndConsumeTextureCHROMIUM(mailbox.name);
+  }
+}
+
 static constexpr size_t kNumYUVPlanes = 3;
 struct YUVPlaneTextureInfo {
   GrGLTextureInfo texture = {0, 0};
   GLint minFilter = 0;
   GLint magFilter = 0;
+  bool is_shared_image = false;
 };
 using YUVTexturesInfo = std::array<YUVPlaneTextureInfo, kNumYUVPlanes>;
 
@@ -147,9 +176,15 @@
            mailbox_holder.texture_target == GL_TEXTURE_RECTANGLE_ARB)
         << "Unsupported texture target " << std::hex << std::showbase
         << mailbox_holder.texture_target;
-    gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
-    yuv_textures_info[i].texture.fID =
-        gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
+    yuv_textures_info[i].texture.fID = SynchronizeAndImportMailbox(
+        gl, mailbox_holder.sync_token, mailbox_holder.mailbox);
+    if (mailbox_holder.mailbox.IsSharedImage()) {
+      yuv_textures_info[i].is_shared_image = true;
+      gl->BeginSharedImageAccessDirectCHROMIUM(
+          yuv_textures_info[i].texture.fID,
+          GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
+    }
+
     yuv_textures_info[i].texture.fTarget = mailbox_holder.texture_target;
     yuv_textures_info[i].texture.fFormat = skia_texture_format;
 
@@ -177,47 +212,12 @@
                       GL_TEXTURE_MIN_FILTER, yuv_textures_info[i].minFilter);
     gl->TexParameteri(yuv_textures_info[i].texture.fTarget,
                       GL_TEXTURE_MAG_FILTER, yuv_textures_info[i].magFilter);
+    if (yuv_textures_info[i].is_shared_image)
+      gl->EndSharedImageAccessDirectCHROMIUM(yuv_textures_info[i].texture.fID);
     gl->DeleteTextures(1, &yuv_textures_info[i].texture.fID);
   }
 }
 
-sk_sp<SkImage> NewSkImageFromVideoFrameYUVTextures(
-    const VideoFrame* video_frame,
-    viz::ContextProvider* context_provider) {
-  DCHECK(video_frame->HasTextures());
-  GrContext* gr_context = context_provider->GrContext();
-  DCHECK(gr_context);
-  // TODO: We should compare the DCHECK vs when UpdateLastImage calls this
-  // function. (crbug.com/674185)
-  DCHECK(video_frame->format() == PIXEL_FORMAT_I420 ||
-         video_frame->format() == PIXEL_FORMAT_NV12);
-
-  gfx::Size ya_tex_size = video_frame->coded_size();
-  gfx::Size uv_tex_size((ya_tex_size.width() + 1) / 2,
-                        (ya_tex_size.height() + 1) / 2);
-
-  YUVTexturesInfo yuv_textures_info =
-      GetYUVTexturesInfo(video_frame, context_provider);
-
-  GrBackendTexture yuv_textures[3] = {
-      GrBackendTexture(ya_tex_size.width(), ya_tex_size.height(),
-                       GrMipMapped::kNo, yuv_textures_info[0].texture),
-      GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(),
-                       GrMipMapped::kNo, yuv_textures_info[1].texture),
-      GrBackendTexture(uv_tex_size.width(), uv_tex_size.height(),
-                       GrMipMapped::kNo, yuv_textures_info[2].texture),
-  };
-
-  sk_sp<SkImage> img =
-      YUVGrBackendTexturesToSkImage(gr_context, video_frame->ColorSpace(),
-                                    video_frame->format(), yuv_textures);
-  gr_context->flush();
-
-  DeleteYUVTextures(video_frame, context_provider, yuv_textures_info);
-
-  return img;
-}
-
 sk_sp<SkImage> NewSkImageFromVideoFrameYUVTexturesWithExternalBackend(
     const VideoFrame* video_frame,
     viz::ContextProvider* context_provider,
@@ -251,11 +251,9 @@
   backend_texture.fID = texture_id;
   backend_texture.fTarget = texture_target;
   backend_texture.fFormat = GL_RGBA8;
-  GrBackendTexture result_texture[1] = {
-      GrBackendTexture(video_frame->coded_size().width(),
-                       video_frame->coded_size().height(), GrMipMapped::kNo,
-                       backend_texture),
-  };
+  GrBackendTexture result_texture(video_frame->coded_size().width(),
+                                  video_frame->coded_size().height(),
+                                  GrMipMapped::kNo, backend_texture);
 
   sk_sp<SkImage> img = YUVGrBackendTexturesToSkImage(
       gr_context, video_frame->ColorSpace(), video_frame->format(),
@@ -267,12 +265,15 @@
   return img;
 }
 
-// Creates a SkImage from a |video_frame| backed by native resources.
-// The SkImage will take ownership of the underlying resource.
-sk_sp<SkImage> NewSkImageFromVideoFrameNative(
-    VideoFrame* video_frame,
-    viz::ContextProvider* context_provider,
-    bool wrap_texture) {
+// Imports a VideoFrame that contains a single mailbox into a newly created GL
+// texture, after synchronization with the sync token. Returns the GL texture.
+// |mailbox| is set to the imported mailbox.
+GLuint ImportVideoFrameSingleMailbox(gpu::gles2::GLES2Interface* gl,
+                                     VideoFrame* video_frame,
+                                     gpu::Mailbox* mailbox) {
+  DCHECK(video_frame->HasTextures());
+  DCHECK_EQ(video_frame->NumTextures(), 1u);
+
   DCHECK(PIXEL_FORMAT_ARGB == video_frame->format() ||
          PIXEL_FORMAT_XRGB == video_frame->format() ||
          PIXEL_FORMAT_RGB24 == video_frame->format() ||
@@ -285,43 +286,31 @@
   DCHECK(mailbox_holder.texture_target == GL_TEXTURE_2D ||
          mailbox_holder.texture_target == GL_TEXTURE_RECTANGLE_ARB ||
          mailbox_holder.texture_target == GL_TEXTURE_EXTERNAL_OES)
-      << "Unsupported texture target " << std::hex << std::showbase
       << mailbox_holder.texture_target;
 
-  gpu::gles2::GLES2Interface* gl = context_provider->ContextGL();
-  gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
-  GLuint frame_texture =
-      gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
-  unsigned source_texture = 0;
-  gfx::ColorSpace color_space_for_skia;
-  if (wrap_texture) {
-    // Fast path where we can avoid a copy, by having last_image_ directly wrap
-    // the VideoFrame texture.
-    source_texture = frame_texture;
-    color_space_for_skia = video_frame->ColorSpace();
-  } else {
-    gl->GenTextures(1, &source_texture);
-    DCHECK(source_texture);
-    gl->BindTexture(GL_TEXTURE_2D, source_texture);
-    gl->CopyTextureCHROMIUM(frame_texture, 0, GL_TEXTURE_2D, source_texture, 0,
-                            GL_RGBA, GL_UNSIGNED_BYTE, false, false, false);
-    gl->DeleteTextures(1, &frame_texture);
-  }
-  GrGLTextureInfo source_texture_info;
-  source_texture_info.fID = source_texture;
-  source_texture_info.fTarget = GL_TEXTURE_2D;
-  // TODO(bsalomon): GrGLTextureInfo::fFormat and SkColorType passed to SkImage
-  // factory should reflect video_frame->format(). Update once Skia supports
-  // GL_RGB.
-  // skbug.com/7533
-  source_texture_info.fFormat = GL_RGBA8_OES;
-  GrBackendTexture source_backend_texture(
-      video_frame->coded_size().width(), video_frame->coded_size().height(),
-      GrMipMapped::kNo, source_texture_info);
-  return SkImage::MakeFromAdoptedTexture(
-      context_provider->GrContext(), source_backend_texture,
-      kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
-      color_space_for_skia.ToSkColorSpace());
+  *mailbox = mailbox_holder.mailbox;
+  return SynchronizeAndImportMailbox(gl, mailbox_holder.sync_token, *mailbox);
+}
+
+// Wraps a GL RGBA texture into a SkImage.
+sk_sp<SkImage> WrapGLTexture(GLenum target,
+                             GLuint texture_id,
+                             const gfx::Size& size,
+                             const gfx::ColorSpace& color_space,
+                             viz::ContextProvider* context_provider) {
+  GrGLTextureInfo texture_info;
+  texture_info.fID = texture_id;
+  texture_info.fTarget = target;
+  // TODO(bsalomon): GrGLTextureInfo::fFormat and SkColorType passed to
+  // SkImage factory should reflect video_frame->format(). Update once
+  // Skia supports GL_RGB. skbug.com/7533
+  texture_info.fFormat = GL_RGBA8_OES;
+  GrBackendTexture backend_texture(size.width(), size.height(),
+                                   GrMipMapped::kNo, texture_info);
+  return SkImage::MakeFromTexture(
+      context_provider->GrContext(), backend_texture, kTopLeft_GrSurfaceOrigin,
+      kRGBA_8888_SkColorType, kPremul_SkAlphaType, color_space.ToSkColorSpace(),
+      nullptr, nullptr);
 }
 
 void VideoFrameCopyTextureOrSubTexture(gpu::gles2::GLES2Interface* gl,
@@ -593,6 +582,14 @@
   cc::PaintImage image = cache_->paint_image;
   DCHECK(image);
 
+  base::Optional<ScopedSharedImageAccess> source_access;
+  if (video_frame->HasTextures()) {
+    DCHECK(!cache_->source_mailbox.IsZero());
+    DCHECK(cache_->source_texture);
+    source_access.emplace(context_provider->ContextGL(), cache_->source_texture,
+                          cache_->source_mailbox);
+  }
+
   cc::PaintFlags video_flags;
   video_flags.setAlpha(flags.getAlpha());
   video_flags.setBlendMode(flags.getBlendMode());
@@ -659,6 +656,7 @@
   canvas->flush();
 
   if (video_frame->HasTextures()) {
+    source_access.reset();
     // Synchronize |video_frame| with the read operations in UpdateLastImage(),
     // which are triggered by canvas->flush().
     SynchronizeVideoFrameRead(std::move(video_frame),
@@ -1056,19 +1054,16 @@
   DCHECK(video_frame);
   DCHECK(video_frame->HasTextures());
 
-  const gpu::MailboxHolder& mailbox_holder = video_frame->mailbox_holder(0);
-  DCHECK(mailbox_holder.texture_target == GL_TEXTURE_2D ||
-         mailbox_holder.texture_target == GL_TEXTURE_RECTANGLE_ARB ||
-         mailbox_holder.texture_target == GL_TEXTURE_EXTERNAL_OES)
-      << mailbox_holder.texture_target;
-
-  gl->WaitSyncTokenCHROMIUM(mailbox_holder.sync_token.GetConstData());
+  gpu::Mailbox mailbox;
   uint32_t source_texture =
-      gl->CreateAndConsumeTextureCHROMIUM(mailbox_holder.mailbox.name);
-  VideoFrameCopyTextureOrSubTexture(gl, video_frame->coded_size(),
-                                    video_frame->visible_rect(), source_texture,
-                                    target, texture, internal_format, format,
-                                    type, level, premultiply_alpha, flip_y);
+      ImportVideoFrameSingleMailbox(gl, video_frame, &mailbox);
+  {
+    ScopedSharedImageAccess access(gl, source_texture, mailbox);
+    VideoFrameCopyTextureOrSubTexture(
+        gl, video_frame->coded_size(), video_frame->visible_rect(),
+        source_texture, target, texture, internal_format, format, type, level,
+        premultiply_alpha, flip_y);
+  }
   gl->DeleteTextures(1, &source_texture);
   gl->ShallowFlushCHROMIUM();
   // The caller must call SynchronizeVideoFrameRead() after this operation, but
@@ -1104,36 +1099,24 @@
     }
 
     DCHECK(cache_);
-    DCHECK(cache_->source_image);
-    GrBackendTexture backend_texture =
-        cache_->source_image->getBackendTexture(true);
-    if (!backend_texture.isValid())
-      return false;
-    GrGLTextureInfo texture_info;
-    if (!backend_texture.getGLTextureInfo(&texture_info))
-      return false;
-
+    DCHECK(!cache_->source_mailbox.IsZero());
     gpu::gles2::GLES2Interface* canvas_gl = context_provider->ContextGL();
-    gpu::MailboxHolder mailbox_holder;
-    mailbox_holder.texture_target = texture_info.fTarget;
-    canvas_gl->ProduceTextureDirectCHROMIUM(texture_info.fID,
-                                            mailbox_holder.mailbox.name);
 
+    gpu::SyncToken sync_token;
     // Wait for mailbox creation on canvas context before consuming it and
     // copying from it on the consumer context.
-    canvas_gl->GenUnverifiedSyncTokenCHROMIUM(
-        mailbox_holder.sync_token.GetData());
+    canvas_gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
 
-    destination_gl->WaitSyncTokenCHROMIUM(
-        mailbox_holder.sync_token.GetConstData());
-    uint32_t intermediate_texture =
-        destination_gl->CreateAndConsumeTextureCHROMIUM(
-            mailbox_holder.mailbox.name);
-
-    VideoFrameCopyTextureOrSubTexture(
-        destination_gl, cache_->coded_size, cache_->visible_rect,
-        intermediate_texture, target, texture, internal_format, format, type,
-        level, premultiply_alpha, flip_y);
+    uint32_t intermediate_texture = SynchronizeAndImportMailbox(
+        destination_gl, sync_token, cache_->source_mailbox);
+    {
+      ScopedSharedImageAccess access(destination_gl, intermediate_texture,
+                                     cache_->source_mailbox);
+      VideoFrameCopyTextureOrSubTexture(
+          destination_gl, cache_->coded_size, cache_->visible_rect,
+          intermediate_texture, target, texture, internal_format, format, type,
+          level, premultiply_alpha, flip_y);
+    }
 
     destination_gl->DeleteTextures(1, &intermediate_texture);
 
@@ -1322,10 +1305,8 @@
     yuv_cache_.mailbox = sii->CreateSharedImage(
         viz::ResourceFormat::RGBA_8888, video_frame.coded_size(),
         gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
-    auto creation_sync_token = sii->GenUnverifiedSyncToken();
-    source_gl->WaitSyncTokenCHROMIUM(creation_sync_token.GetConstData());
-    yuv_cache_.texture = source_gl->CreateAndTexStorage2DSharedImageCHROMIUM(
-        yuv_cache_.mailbox.name);
+    yuv_cache_.texture = SynchronizeAndImportMailbox(
+        source_gl, sii->GenUnverifiedSyncToken(), yuv_cache_.mailbox);
   }
 
   // On the source GL context, do the YUV->RGB conversion using Skia.
@@ -1344,7 +1325,7 @@
 
     sk_sp<SkImage> yuv_image = YUVGrBackendTexturesToSkImage(
         gr_context, video_frame.ColorSpace(), video_frame.format(),
-        yuv_textures, &result_texture);
+        yuv_textures, result_texture);
 
     gr_context->flush();
     source_gl->EndSharedImageAccessDirectCHROMIUM(yuv_cache_.texture);
@@ -1362,25 +1343,19 @@
 
   // On the destination GL context, do a copy (with cropping) into the
   // destination texture.
+  GLuint intermediate_texture = SynchronizeAndImportMailbox(
+      destination_gl, post_conversion_sync_token, yuv_cache_.mailbox);
   {
-    destination_gl->WaitSyncTokenCHROMIUM(
-        post_conversion_sync_token.GetConstData());
-    GLuint intermediate_texture =
-        destination_gl->CreateAndTexStorage2DSharedImageCHROMIUM(
-            yuv_cache_.mailbox.name);
-    destination_gl->BeginSharedImageAccessDirectCHROMIUM(
-        intermediate_texture, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM);
-
+    ScopedSharedImageAccess access(destination_gl, intermediate_texture,
+                                   yuv_cache_.mailbox);
     VideoFrameCopyTextureOrSubTexture(
         destination_gl, video_frame.coded_size(), video_frame.visible_rect(),
         intermediate_texture, target, texture, internal_format, format, type,
         level, premultiply_alpha, flip_y);
-
-    destination_gl->EndSharedImageAccessDirectCHROMIUM(intermediate_texture);
-    destination_gl->DeleteTextures(1, &intermediate_texture);
-    destination_gl->GenUnverifiedSyncTokenCHROMIUM(
-        yuv_cache_.sync_token.GetData());
   }
+  destination_gl->DeleteTextures(1, &intermediate_texture);
+  destination_gl->GenUnverifiedSyncTokenCHROMIUM(
+      yuv_cache_.sync_token.GetData());
 
   // video_frame->UpdateReleaseSyncToken is not necessary since the video frame
   // data we used was CPU-side (IsMappable) to begin with. If there were any
@@ -1473,7 +1448,21 @@
 
 PaintCanvasVideoRenderer::Cache::Cache(int frame_id) : frame_id(frame_id) {}
 
-PaintCanvasVideoRenderer::Cache::~Cache() = default;
+PaintCanvasVideoRenderer::Cache::~Cache() {
+  if (!context_provider)
+    return;
+
+  DCHECK(!source_mailbox.IsZero());
+  DCHECK(source_texture);
+  auto* gl = context_provider->ContextGL();
+  gl->DeleteTextures(1, &source_texture);
+  if (!wraps_video_frame_texture) {
+    gpu::SyncToken sync_token;
+    gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData());
+    auto* sii = context_provider->SharedImageInterface();
+    sii->DestroySharedImage(sync_token, source_mailbox);
+  }
+}
 
 bool PaintCanvasVideoRenderer::UpdateLastImage(
     scoped_refptr<VideoFrame> video_frame,
@@ -1481,14 +1470,13 @@
     bool allow_wrap_texture) {
   DCHECK(!cache_ || !cache_->wraps_video_frame_texture);
   if (!cache_ || video_frame->unique_id() != cache_->frame_id ||
-      !cache_->source_image) {
-    cache_.emplace(video_frame->unique_id());
-
+      cache_->source_mailbox.IsZero()) {
     auto paint_image_builder =
         cc::PaintImageBuilder::WithDefault()
             .set_id(renderer_stable_id_)
             .set_animation_type(cc::PaintImage::AnimationType::VIDEO)
             .set_completion_state(cc::PaintImage::CompletionState::DONE);
+
     // Generate a new image.
     // Note: Skia will hold onto |video_frame| via |video_generator| only when
     // |video_frame| is software.
@@ -1497,27 +1485,73 @@
     if (video_frame->HasTextures()) {
       DCHECK(context_provider);
       DCHECK(context_provider->GrContext());
-      DCHECK(context_provider->ContextGL());
-      if (video_frame->NumTextures() > 1) {
-        cache_->source_image = NewSkImageFromVideoFrameYUVTextures(
-            video_frame.get(), context_provider);
+      auto* gl = context_provider->ContextGL();
+      DCHECK(gl);
+
+      sk_sp<SkImage> source_image;
+
+      if (allow_wrap_texture && video_frame->NumTextures() == 1) {
+        cache_.emplace(video_frame->unique_id());
+        cache_->source_texture = ImportVideoFrameSingleMailbox(
+            gl, video_frame.get(), &cache_->source_mailbox);
+        cache_->wraps_video_frame_texture = true;
+        source_image =
+            WrapGLTexture(video_frame->mailbox_holder(0).texture_target,
+                          cache_->source_texture, video_frame->coded_size(),
+                          video_frame->ColorSpace(), context_provider);
       } else {
-        cache_->source_image = NewSkImageFromVideoFrameNative(
-            video_frame.get(), context_provider, allow_wrap_texture);
-        cache_->wraps_video_frame_texture = allow_wrap_texture;
+        if (cache_ && cache_->context_provider == context_provider &&
+            cache_->coded_size == video_frame->coded_size()) {
+          // We can reuse the shared image from the previous cache.
+          cache_->frame_id = video_frame->unique_id();
+        } else {
+          cache_.emplace(video_frame->unique_id());
+          auto* sii = context_provider->SharedImageInterface();
+          cache_->source_mailbox = sii->CreateSharedImage(
+              viz::ResourceFormat::RGBA_8888, video_frame->coded_size(),
+              gfx::ColorSpace(), gpu::SHARED_IMAGE_USAGE_GLES2);
+          cache_->source_texture = SynchronizeAndImportMailbox(
+              gl, sii->GenUnverifiedSyncToken(), cache_->source_mailbox);
+        }
+        ScopedSharedImageAccess dest_access(
+            gl, cache_->source_texture, cache_->source_mailbox,
+            GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM);
+        if (video_frame->NumTextures() == 1) {
+          gpu::Mailbox mailbox;
+          GLuint frame_texture =
+              ImportVideoFrameSingleMailbox(gl, video_frame.get(), &mailbox);
+          {
+            ScopedSharedImageAccess access(gl, frame_texture, mailbox);
+            gl->CopySubTextureCHROMIUM(frame_texture, 0, GL_TEXTURE_2D,
+                                       cache_->source_texture, 0, 0, 0, 0, 0,
+                                       video_frame->coded_size().width(),
+                                       video_frame->coded_size().height(),
+                                       GL_FALSE, GL_FALSE, GL_FALSE);
+          }
+          gl->DeleteTextures(1, &frame_texture);
+          source_image = WrapGLTexture(GL_TEXTURE_2D, cache_->source_texture,
+                                       video_frame->coded_size(),
+                                       gfx::ColorSpace(), context_provider);
+        } else {
+          source_image = NewSkImageFromVideoFrameYUVTexturesWithExternalBackend(
+              video_frame.get(), context_provider, GL_TEXTURE_2D,
+              cache_->source_texture);
+        }
+        context_provider->GrContext()->flush();
       }
-      if (!cache_->source_image) {
+      if (!source_image) {
         // Couldn't create the SkImage.
         cache_.reset();
         return false;
       }
+      cache_->context_provider = context_provider;
       cache_->coded_size = video_frame->coded_size();
       cache_->visible_rect = video_frame->visible_rect();
       paint_image_builder.set_image(
-          cache_->source_image->makeSubset(
-              gfx::RectToSkIRect(cache_->visible_rect)),
+          source_image->makeSubset(gfx::RectToSkIRect(cache_->visible_rect)),
           cc::PaintImage::GetNextContentId());
     } else {
+      cache_.emplace(video_frame->unique_id());
       paint_image_builder.set_paint_image_generator(
           sk_make_sp<VideoImageGenerator>(video_frame));
     }
@@ -1555,11 +1589,11 @@
     DCHECK(context_provider);
     DCHECK(context_provider->GrContext());
     DCHECK(context_provider->ContextGL());
+    sk_sp<SkImage> source_image;
     if (video_frame->NumTextures() > 1) {
-      cache_->source_image =
-          NewSkImageFromVideoFrameYUVTexturesWithExternalBackend(
-              video_frame.get(), context_provider, textureTarget, texture);
-      if (!cache_->source_image) {
+      source_image = NewSkImageFromVideoFrameYUVTexturesWithExternalBackend(
+          video_frame.get(), context_provider, textureTarget, texture);
+      if (!source_image) {
         // Couldn't create the SkImage.
         cache_.reset();
         return false;
@@ -1571,9 +1605,9 @@
     }
     cache_->coded_size = video_frame->coded_size();
     cache_->visible_rect = video_frame->visible_rect();
-    paint_image_builder.set_image(cache_->source_image->makeSubset(
-                                      gfx::RectToSkIRect(cache_->visible_rect)),
-                                  cc::PaintImage::GetNextContentId());
+    paint_image_builder.set_image(
+        source_image->makeSubset(gfx::RectToSkIRect(cache_->visible_rect)),
+        cc::PaintImage::GetNextContentId());
   } else {
     paint_image_builder.set_paint_image_generator(
         sk_make_sp<VideoImageGenerator>(video_frame));
diff --git a/media/renderers/paint_canvas_video_renderer.h b/media/renderers/paint_canvas_video_renderer.h
index 4664b93f..2628f28 100644
--- a/media/renderers/paint_canvas_video_renderer.h
+++ b/media/renderers/paint_canvas_video_renderer.h
@@ -17,6 +17,7 @@
 #include "cc/paint/paint_canvas.h"
 #include "cc/paint/paint_flags.h"
 #include "cc/paint/paint_image.h"
+#include "gpu/command_buffer/common/mailbox.h"
 #include "media/base/media_export.h"
 #include "media/base/timestamp_constants.h"
 #include "media/base/video_frame.h"
@@ -198,14 +199,22 @@
     // to the visible size of the VideoFrame. Its contents are generated lazily.
     cc::PaintImage paint_image;
 
-    // A SkImage that contain the source texture for |paint_image|. This can be
-    // either the source VideoFrame's texture (if wraps_video_frame_texture is
-    // true) or a newly allocated texture (if wraps_video_frame_texture is
-    // false) if a copy or conversion was necessary.
-    // This is only set if the VideoFrame was texture-backed.
-    sk_sp<SkImage> source_image;
+    // The context provider used to generate |source_mailbox| and
+    // |source_texture|. This is only set if the VideoFrame was texture-backed.
+    scoped_refptr<viz::ContextProvider> context_provider;
 
-    // The allocated size of |source_image|.
+    // The mailbox for the source texture. This can be either the source
+    // VideoFrame's texture (if |wraps_video_frame_texture| is true) or a newly
+    // allocated shared image (if |wraps_video_frame_texture| is false) if a
+    // copy or conversion was necessary.
+    // This is only set if the VideoFrame was texture-backed.
+    gpu::Mailbox source_mailbox;
+
+    // The texture ID created when importing |source_mailbox|.
+    // This is only set if the VideoFrame was texture-backed.
+    uint32_t source_texture = 0;
+
+    // The allocated size of |source_mailbox|.
     // This is only set if the VideoFrame was texture-backed.
     gfx::Size coded_size;
 
@@ -214,8 +223,8 @@
     // This is only set if the VideoFrame was texture-backed.
     gfx::Rect visible_rect;
 
-    // Whether |source_image| directly points to a texture of the VideoFrame
-    // (if true), or to an allocated texture (if false).
+    // Whether |source_mailbox| directly points to a texture of the VideoFrame
+    // (if true), or to an allocated shared image (if false).
     bool wraps_video_frame_texture = false;
   };
 
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 0a9727a..be5b930f 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -80,7 +80,7 @@
     "ENABLE_WEBSOCKETS=$enable_websockets",
     "INCLUDE_TRANSPORT_SECURITY_STATE_PRELOAD_LIST=$include_transport_security_state_preload_list",
     "USE_KERBEROS=$use_kerberos",
-    "DLOPEN_KERBEROS=$use_external_gssapi",
+    "USE_EXTERNAL_GSSAPI=$use_external_gssapi",
     "TRIAL_COMPARISON_CERT_VERIFIER_SUPPORTED=$trial_comparison_cert_verifier_supported",
     "BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED=$builtin_cert_verifier_feature_supported",
   ]
diff --git a/net/android/dummy_spnego_authenticator.h b/net/android/dummy_spnego_authenticator.h
index 5525c62..ae51b02 100644
--- a/net/android/dummy_spnego_authenticator.h
+++ b/net/android/dummy_spnego_authenticator.h
@@ -137,6 +137,9 @@
 };
 
 }  // namespace android
+
+using MockAuthLibrary = android::DummySpnegoAuthenticator;
+
 }  // namespace net
 
 #endif  // NET_ANDROID_DUMMY_SPNEGO_AUTHENTICATOR_H_
diff --git a/net/android/http_auth_negotiate_android.cc b/net/android/http_auth_negotiate_android.cc
index f8263c8..4beca16 100644
--- a/net/android/http_auth_negotiate_android.cc
+++ b/net/android/http_auth_negotiate_android.cc
@@ -16,6 +16,7 @@
 #include "net/http/http_auth_challenge_tokenizer.h"
 #include "net/http/http_auth_multi_round_parse.h"
 #include "net/http/http_auth_preferences.h"
+#include "net/log/net_log_with_source.h"
 #include "net/net_jni_headers/HttpNegotiateAuthenticator_jni.h"
 
 using base::android::AttachCurrentThread;
@@ -69,7 +70,7 @@
 HttpAuthNegotiateAndroid::~HttpAuthNegotiateAndroid() {
 }
 
-bool HttpAuthNegotiateAndroid::Init() {
+bool HttpAuthNegotiateAndroid::Init(const NetLogWithSource& net_log) {
   return true;
 }
 
@@ -92,11 +93,22 @@
                                        &decoded_auth_token);
 }
 
+int HttpAuthNegotiateAndroid::GenerateAuthTokenAndroid(
+    const AuthCredentials* credentials,
+    const std::string& spn,
+    const std::string& channel_bindings,
+    std::string* auth_token,
+    net::CompletionOnceCallback callback) {
+  return GenerateAuthToken(credentials, spn, channel_bindings, auth_token,
+                           NetLogWithSource(), std::move(callback));
+}
+
 int HttpAuthNegotiateAndroid::GenerateAuthToken(
     const AuthCredentials* credentials,
     const std::string& spn,
     const std::string& channel_bindings,
     std::string* auth_token,
+    const NetLogWithSource& net_log,
     net::CompletionOnceCallback callback) {
   if (GetAuthAndroidNegotiateAccountType().empty()) {
     // This can happen if there is a policy change, removing the account type,
diff --git a/net/android/http_auth_negotiate_android.h b/net/android/http_auth_negotiate_android.h
index 2bd89fc..78e29a1 100644
--- a/net/android/http_auth_negotiate_android.h
+++ b/net/android/http_auth_negotiate_android.h
@@ -74,7 +74,7 @@
   ~HttpAuthNegotiateAndroid() override;
 
   // HttpNegotiateAuthSystem implementation:
-  bool Init() override;
+  bool Init(const NetLogWithSource& net_log) override;
   bool NeedsIdentity() const override;
   bool AllowsExplicitCredentials() const override;
   HttpAuth::AuthorizationResult ParseChallenge(
@@ -83,9 +83,20 @@
                         const std::string& spn,
                         const std::string& channel_bindings,
                         std::string* auth_token,
+                        const NetLogWithSource& net_log,
                         CompletionOnceCallback callback) override;
   void SetDelegation(HttpAuth::DelegationType delegation_type) override;
 
+  // Unlike the platform agnostic GenerateAuthToken(), the Android specific
+  // version doesn't require a NetLogWithSource. The call is made across service
+  // boundaries, so currently the goings-on within the GenerateAuthToken()
+  // handler is outside the scope of the NetLog.
+  int GenerateAuthTokenAndroid(const AuthCredentials* credentials,
+                               const std::string& spn,
+                               const std::string& channel_bindings,
+                               std::string* auth_token,
+                               CompletionOnceCallback callback);
+
   bool can_delegate() const { return can_delegate_; }
   void set_can_delegate(bool can_delegate) { can_delegate_ = can_delegate; }
 
diff --git a/net/android/http_auth_negotiate_android_unittest.cc b/net/android/http_auth_negotiate_android_unittest.cc
index 154084b..e88265d0 100644
--- a/net/android/http_auth_negotiate_android_unittest.cc
+++ b/net/android/http_auth_negotiate_android_unittest.cc
@@ -11,6 +11,7 @@
 #include "net/base/test_completion_callback.h"
 #include "net/http/http_auth_challenge_tokenizer.h"
 #include "net/http/mock_allow_http_auth_preferences.h"
+#include "net/log/net_log_with_source.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace net {
@@ -32,12 +33,12 @@
   prefs.set_auth_android_negotiate_account_type(
       "org.chromium.test.DummySpnegoAuthenticator");
   HttpAuthNegotiateAndroid auth(&prefs);
-  EXPECT_TRUE(auth.Init());
+  EXPECT_TRUE(auth.Init(NetLogWithSource()));
 
   TestCompletionCallback callback;
-  EXPECT_EQ(OK, callback.GetResult(
-                    auth.GenerateAuthToken(nullptr, "Dummy", std::string(),
-                                           &auth_token, callback.callback())));
+  EXPECT_EQ(OK, callback.GetResult(auth.GenerateAuthToken(
+                    nullptr, "Dummy", std::string(), &auth_token,
+                    NetLogWithSource(), callback.callback())));
 
   EXPECT_EQ("Negotiate DummyToken", auth_token);
 
diff --git a/net/dns/context_host_resolver_unittest.cc b/net/dns/context_host_resolver_unittest.cc
index 7f00e24..6109aee 100644
--- a/net/dns/context_host_resolver_unittest.cc
+++ b/net/dns/context_host_resolver_unittest.cc
@@ -51,6 +51,7 @@
         std::make_unique<MockDnsClient>(DnsConfig(), std::move(rules));
     dns_client_ = dns_client.get();
     manager_->SetDnsClientForTesting(std::move(dns_client));
+    manager_->SetInsecureDnsClientEnabled(true);
 
     scoped_refptr<HostResolverProc> proc = CreateCatchAllHostResolverProc();
     manager_->set_proc_params_for_test(ProcTaskParams(proc.get(), 1u));
diff --git a/net/dns/fuzzed_host_resolver_util.cc b/net/dns/fuzzed_host_resolver_util.cc
index c384abf..b76c8809 100644
--- a/net/dns/fuzzed_host_resolver_util.cc
+++ b/net/dns/fuzzed_host_resolver_util.cc
@@ -85,6 +85,62 @@
   return FuzzIPv6Address(data_provider);
 }
 
+DnsConfig GetFuzzedDnsConfig(FuzzedDataProvider* data_provider) {
+  // Fuzz DNS configuration.
+  DnsConfig config;
+
+  // Fuzz name servers.
+  uint32_t num_nameservers = data_provider->ConsumeIntegralInRange(0, 4);
+  for (uint32_t i = 0; i < num_nameservers; ++i) {
+    config.nameservers.push_back(
+        IPEndPoint(FuzzIPAddress(data_provider), FuzzPort(data_provider)));
+  }
+
+  // Fuzz suffix search list.
+  switch (data_provider->ConsumeIntegralInRange(0, 3)) {
+    case 3:
+      config.search.push_back("foo.com");
+      FALLTHROUGH;
+    case 2:
+      config.search.push_back("bar");
+      FALLTHROUGH;
+    case 1:
+      config.search.push_back("com");
+      FALLTHROUGH;
+    default:
+      break;
+  }
+
+  net::DnsHosts hosts;
+  // Fuzz hosts file.
+  uint8_t num_hosts_entries = data_provider->ConsumeIntegral<uint8_t>();
+  for (uint8_t i = 0; i < num_hosts_entries; ++i) {
+    const char* kHostnames[] = {"foo", "foo.com",   "a.foo.com",
+                                "bar", "localhost", "localhost6"};
+    const char* hostname = data_provider->PickValueInArray(kHostnames);
+    net::IPAddress address = FuzzIPAddress(data_provider);
+    config.hosts[net::DnsHostsKey(hostname, net::GetAddressFamily(address))] =
+        address;
+  }
+
+  config.unhandled_options = data_provider->ConsumeBool();
+  config.append_to_multi_label_name = data_provider->ConsumeBool();
+  config.randomize_ports = data_provider->ConsumeBool();
+  config.ndots = data_provider->ConsumeIntegralInRange(0, 3);
+  config.attempts = data_provider->ConsumeIntegralInRange(1, 3);
+
+  // Timeouts don't really work for fuzzing. Even a timeout of 0 milliseconds
+  // will be increased after the first timeout, resulting in inconsistent
+  // behavior.
+  config.timeout = base::TimeDelta::FromDays(10);
+
+  config.rotate = data_provider->ConsumeBool();
+
+  config.use_local_ipv6 = data_provider->ConsumeBool();
+
+  return config;
+}
+
 // HostResolverProc that returns a random set of results, and can succeed or
 // fail. Must only be run on the thread it's created on.
 class FuzzedHostResolverProc : public HostResolverProc {
@@ -311,9 +367,6 @@
         socket_factory_(data_provider_),
         net_log_(net_log),
         data_provider_weak_factory_(data_provider) {
-    // Use SetDnsClientEnabled() to ensure fuzzed client is used.
-    DCHECK(!options.dns_client_enabled);
-
     ProcTaskParams proc_task_params(
         new FuzzedHostResolverProc(data_provider_weak_factory_.GetWeakPtr()),
         // Retries are only used when the original request hangs, which this
@@ -323,16 +376,20 @@
     SetTaskRunnerForTesting(base::SequencedTaskRunnerHandle::Get());
     SetMdnsSocketFactoryForTesting(
         std::make_unique<FuzzedMdnsSocketFactory>(data_provider_));
+    std::unique_ptr<DnsClient> dns_client = DnsClient::CreateClientForTesting(
+        net_log_, &socket_factory_,
+        base::Bind(&FuzzedDataProvider::ConsumeIntegralInRange<int32_t>,
+                   base::Unretained(data_provider_)));
+    dns_client->SetConfig(GetFuzzedDnsConfig(data_provider_));
+    HostResolverManager::SetDnsClientForTesting(std::move(dns_client));
   }
 
   ~FuzzedHostResolverManager() override = default;
 
-  // Enable / disable the async resolver. When enabled, installs a
-  // DnsClient with fuzzed UDP and TCP sockets.
-  void SetDnsClientEnabled(bool enabled) override;
-
   void SetDnsClientForTesting(std::unique_ptr<DnsClient> dns_client) {
-    // Should only call SetDnsClientEnabled() to ensure a fuzzed client is used.
+    // The only DnsClient that is supported is the one created by the
+    // FuzzedHostResolverManager since that DnsClient contains the necessary
+    // fuzzing logic.
     NOTREACHED();
   }
 
@@ -362,73 +419,6 @@
   DISALLOW_COPY_AND_ASSIGN(FuzzedHostResolverManager);
 };
 
-void FuzzedHostResolverManager::SetDnsClientEnabled(bool enabled) {
-  if (!enabled) {
-    HostResolverManager::SetDnsClientEnabled(false);
-    return;
-  }
-
-  // Fuzz DNS configuration.
-
-  DnsConfig config;
-
-  // Fuzz name servers.
-  uint32_t num_nameservers = data_provider_->ConsumeIntegralInRange(0, 4);
-  for (uint32_t i = 0; i < num_nameservers; ++i) {
-    config.nameservers.push_back(
-        IPEndPoint(FuzzIPAddress(data_provider_), FuzzPort(data_provider_)));
-  }
-
-  // Fuzz suffix search list.
-  switch (data_provider_->ConsumeIntegralInRange(0, 3)) {
-    case 3:
-      config.search.push_back("foo.com");
-      FALLTHROUGH;
-    case 2:
-      config.search.push_back("bar");
-      FALLTHROUGH;
-    case 1:
-      config.search.push_back("com");
-      FALLTHROUGH;
-    default:
-      break;
-  }
-
-  net::DnsHosts hosts;
-  // Fuzz hosts file.
-  uint8_t num_hosts_entries = data_provider_->ConsumeIntegral<uint8_t>();
-  for (uint8_t i = 0; i < num_hosts_entries; ++i) {
-    const char* kHostnames[] = {"foo", "foo.com",   "a.foo.com",
-                                "bar", "localhost", "localhost6"};
-    const char* hostname = data_provider_->PickValueInArray(kHostnames);
-    net::IPAddress address = FuzzIPAddress(data_provider_);
-    config.hosts[net::DnsHostsKey(hostname, net::GetAddressFamily(address))] =
-        address;
-  }
-
-  config.unhandled_options = data_provider_->ConsumeBool();
-  config.append_to_multi_label_name = data_provider_->ConsumeBool();
-  config.randomize_ports = data_provider_->ConsumeBool();
-  config.ndots = data_provider_->ConsumeIntegralInRange(0, 3);
-  config.attempts = data_provider_->ConsumeIntegralInRange(1, 3);
-
-  // Timeouts don't really work for fuzzing. Even a timeout of 0 milliseconds
-  // will be increased after the first timeout, resulting in inconsistent
-  // behavior.
-  config.timeout = base::TimeDelta::FromDays(10);
-
-  config.rotate = data_provider_->ConsumeBool();
-
-  config.use_local_ipv6 = data_provider_->ConsumeBool();
-
-  std::unique_ptr<DnsClient> dns_client = DnsClient::CreateClientForTesting(
-      net_log_, &socket_factory_,
-      base::Bind(&FuzzedDataProvider::ConsumeIntegralInRange<int32_t>,
-                 base::Unretained(data_provider_)));
-  dns_client->SetConfig(config);
-  HostResolverManager::SetDnsClientForTesting(std::move(dns_client));
-}
-
 }  // namespace
 
 std::unique_ptr<ContextHostResolver> CreateFuzzedContextHostResolver(
@@ -436,16 +426,8 @@
     NetLog* net_log,
     FuzzedDataProvider* data_provider,
     bool enable_caching) {
-  // FuzzedHostResolverManager only handles fuzzing DnsClient when enabled
-  // through SetDnsClientEnabled().
-  bool enable_dns_client = options.dns_client_enabled;
-  HostResolver::ManagerOptions filtered_options(options);
-  filtered_options.dns_client_enabled = false;
-
-  auto manager = std::make_unique<FuzzedHostResolverManager>(
-      filtered_options, net_log, data_provider);
-  manager->SetDnsClientEnabled(enable_dns_client);
-
+  auto manager = std::make_unique<FuzzedHostResolverManager>(options, net_log,
+                                                             data_provider);
   return std::make_unique<ContextHostResolver>(
       std::move(manager),
       enable_caching ? HostCache::CreateDefaultCache() : nullptr);
diff --git a/net/dns/fuzzed_host_resolver_util.h b/net/dns/fuzzed_host_resolver_util.h
index 11ac006..337955e 100644
--- a/net/dns/fuzzed_host_resolver_util.h
+++ b/net/dns/fuzzed_host_resolver_util.h
@@ -20,9 +20,11 @@
 // return. It inherits from ContextHostResolver, unlike MockHostResolver, so
 // more closely matches real behavior.
 //
-// By default uses a mocked out system resolver, though can be configured (using
-// SetDnsClientEnabled() on the underlying manager) to use the built-in async
-// resolver (Built in DNS stub resolver) with a fuzzed set of UDP/TCP sockets.
+// By default uses a mocked out system resolver, though can be configured to use
+// the built-in async resolver (Built in DNS stub resolver) with a fuzzed set
+// of UDP/TCP sockets by setting ManagerOptions.insecure_dns_client_enabled to
+// true or calling SetInsecureDnsClientEnabled on the underlying
+// HostResolverManager.
 //
 // To make behavior most deterministic, does not use the WorkerPool to run its
 // simulated platform host resolver calls, instead runs them on the thread it is
diff --git a/net/dns/host_resolver.h b/net/dns/host_resolver.h
index 46550c3..d445710 100644
--- a/net/dns/host_resolver.h
+++ b/net/dns/host_resolver.h
@@ -69,6 +69,9 @@
     // an incompatible IP literal (e.g. IPv6 is disabled and it is an IPv6
     // literal).
     //
+    // Results in ERR_DNS_CACHE_MISS if only fast local sources are to be
+    // queried and a cache lookup attempt fails.
+    //
     // The parent HostResolver must still be alive when Start() is called,  but
     // if it is destroyed before an asynchronous result completes, the request
     // will be automatically cancelled.
@@ -131,10 +134,10 @@
     // |kDefaultRetryAttempts| for the resolver to choose a default value.
     size_t max_system_retry_attempts = kDefaultRetryAttempts;
 
-    // Initial setting for whether the built-in asynchronous DnsClient is
-    // enabled or disabled. See HostResolverManager::SetDnsClientEnabled() for
-    // details.
-    bool dns_client_enabled = false;
+    // Initial setting for whether the insecure portion of the built-in
+    // asynchronous DnsClient is enabled or disabled. See HostResolverManager::
+    // SetInsecureDnsClientEnabled() for details.
+    bool insecure_dns_client_enabled = false;
 
     // Initial configuration overrides for the built-in asynchronous DnsClient.
     // See HostResolverManager::SetDnsConfigOverrides() for details.
diff --git a/net/dns/host_resolver_manager.cc b/net/dns/host_resolver_manager.cc
index b629c8b9..9a81b86 100644
--- a/net/dns/host_resolver_manager.cc
+++ b/net/dns/host_resolver_manager.cc
@@ -535,7 +535,7 @@
   return true;
 }
 
-const unsigned HostResolverManager::kMaximumDnsFailures = 16;
+const unsigned HostResolverManager::kMaximumInsecureDnsTaskFailures = 16;
 
 // Holds the callback and request parameters for an outstanding request.
 //
@@ -568,30 +568,7 @@
         priority_(parameters_.initial_priority),
         job_(nullptr),
         resolver_(resolver),
-        complete_(false) {
-    // If the query name matches one of the DoH server names, set the
-    // secure_dns_mode_override field in ResolveHostParameters to OFF to avoid
-    // infinite recursion.
-    // TODO(crbug.com/878582): Add a URLRequest-level parameter to skip DoH that
-    // can be set when a URLRequest to a DoH server is built. This will avoid
-    // unnecessarily skipping DoH when a connection to the DoH server has been
-    // established but the query happens to be for a DoH server hostname.
-    DCHECK(resolver_);
-    if (resolver_->HaveDnsConfig()) {
-      std::unique_ptr<base::Value> dns_config =
-          resolver_->GetDnsConfigAsValue();
-      for (const base::Value& doh_server :
-           dns_config->FindKey("doh_servers")->GetList()) {
-        if (request_host_.host().compare(
-                GURL(GetURLFromTemplateWithoutParameters(
-                         doh_server.FindKey("server_template")->GetString()))
-                    .host()) == 0) {
-          parameters_.secure_dns_mode_override = DnsConfig::SecureDnsMode::OFF;
-          break;
-        }
-      }
-    }
-  }
+        complete_(false) {}
 
   ~RequestImpl() override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -1047,6 +1024,8 @@
     return needs_two_transactions() && !transaction2_;
   }
 
+  bool secure() const { return secure_; }
+
   void StartFirstTransaction() {
     DCHECK(client_);
     DCHECK_EQ(0u, num_completed_transactions_);
@@ -1467,15 +1446,17 @@
 
 struct HostResolverManager::JobKey {
   bool operator<(const JobKey& other) const {
-    return std::tie(query_type, flags, source, request_context, hostname) <
-           std::tie(other.query_type, other.flags, other.source,
-                    other.request_context, other.hostname);
+    return std::tie(query_type, flags, source, secure_dns_mode, request_context,
+                    hostname) < std::tie(other.query_type, other.flags,
+                                         other.source, other.secure_dns_mode,
+                                         other.request_context, other.hostname);
   }
 
   std::string hostname;
   DnsQueryType query_type;
   HostResolverFlags flags;
   HostResolverSource source;
+  DnsConfig::SecureDnsMode secure_dns_mode;
   URLRequestContext* request_context;
 };
 
@@ -1639,30 +1620,32 @@
     CompleteRequestsWithError(ERR_NETWORK_CHANGED);
   }
 
-  // Gets a closure that will abort a DnsTask (see AbortDnsTask()) iff |this| is
-  // still valid. Useful if aborting a list of Jobs as some may be cancelled
-  // while aborting others.
-  base::OnceClosure GetAbortDnsTaskClosure(int error, bool fallback_only) {
-    return base::BindOnce(&Job::AbortDnsTask, weak_ptr_factory_.GetWeakPtr(),
-                          error, fallback_only);
+  // Gets a closure that will abort an insecure DnsTask (see
+  // AbortInsecureDnsTask()) iff |this| is still valid. Useful if aborting a
+  // list of Jobs as some may be cancelled while aborting others.
+  base::OnceClosure GetAbortInsecureDnsTaskClosure(int error,
+                                                   bool fallback_only) {
+    return base::BindOnce(&Job::AbortInsecureDnsTask,
+                          weak_ptr_factory_.GetWeakPtr(), error, fallback_only);
   }
 
-  // Aborts or removes any current/future DnsTasks if a ProcTask is available
-  // for fallback. If no fallback is available and |fallback_only| is false, a
-  // job that is currently running a DnsTask will be completed with |error|.
-  void AbortDnsTask(int error, bool fallback_only) {
+  // Aborts or removes any current/future insecure DnsTasks if a ProcTask is
+  // available for fallback. If no fallback is available and |fallback_only| is
+  // false, a job that is currently running an insecure DnsTask will be
+  // completed with |error|.
+  void AbortInsecureDnsTask(int error, bool fallback_only) {
     bool has_proc_fallback =
         std::find(tasks_.begin(), tasks_.end(), TaskType::PROC) != tasks_.end();
     if (has_proc_fallback) {
       for (auto it = tasks_.begin(); it != tasks_.end();) {
-        if (*it == TaskType::DNS || *it == TaskType::SECURE_DNS)
+        if (*it == TaskType::DNS)
           it = tasks_.erase(it);
         else
           ++it;
       }
     }
 
-    if (dns_task_) {
+    if (dns_task_ && !dns_task_->secure()) {
       if (has_proc_fallback) {
         KillDnsTask();
         dns_task_error_ = OK;
@@ -1707,7 +1690,8 @@
     DCHECK_GT(num_active_requests(), 0u);
     base::Optional<HostCache::Entry> results = resolver_->ServeFromHosts(
         hostname_, query_type_,
-        host_resolver_flags_ & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6);
+        host_resolver_flags_ & HOST_RESOLVER_DEFAULT_FAMILY_SET_DUE_TO_NO_IPV6,
+        tasks_);
     if (results) {
       // This will destroy the Job.
       CompleteRequests(results.value(), base::TimeDelta(),
@@ -1868,7 +1852,8 @@
     DCHECK(proc_task_);
 
     if (dns_task_error_ != OK) {
-      // This ProcTask was a fallback resolution after a failed DnsTask.
+      // This ProcTask was a fallback resolution after a failed insecure
+      // DnsTask.
       if (net_error == OK) {
         resolver_->OnFallbackResolve(dns_task_error_);
       }
@@ -1914,28 +1899,17 @@
 
   void StartDnsTask(bool secure) {
     DCHECK_EQ(1u, num_occupied_job_slots_);
+    DCHECK(resolver_->HaveDnsConfig());
+    DCHECK(!resolver_->HaveTestProcOverride());
     // Need to create the task even if we're going to post a failure instead of
     // running it, as a "started" job needs a task to be properly cleaned up.
     dns_task_.reset(new DnsTask(resolver_->dns_client_.get(), hostname_,
                                 query_type_, request_context_, secure, this,
                                 net_log_, tick_clock_));
-
-    if (resolver_->HaveDnsConfig()) {
-      dns_task_->StartFirstTransaction();
-      // Schedule a second transaction, if needed.
-      if (dns_task_->needs_two_transactions())
-        Schedule(true);
-    } else {
-      // Cannot start a DNS task when DnsClient or config is not available.
-      // Since we cannot complete synchronously from here, post a failure.
-      base::SequencedTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE,
-          base::BindOnce(
-              &Job::OnDnsTaskFailure, weak_ptr_factory_.GetWeakPtr(),
-              dns_task_->AsWeakPtr(), base::TimeDelta(),
-              HostCache::Entry(ERR_FAILED, HostCache::Entry::SOURCE_UNKNOWN),
-              secure));
-    }
+    dns_task_->StartFirstTransaction();
+    // Schedule a second transaction, if needed.
+    if (dns_task_->needs_two_transactions())
+      Schedule(true);
   }
 
   void StartSecondDnsTransaction() {
@@ -1997,7 +1971,10 @@
 
     UMA_HISTOGRAM_LONG_TIMES_100("Net.DNS.DnsTask.SuccessTime", duration);
 
-    resolver_->OnDnsTaskResolve();
+    // Reset the insecure DNS failure counter if an insecure DnsTask completed
+    // successfully.
+    if (!secure)
+      resolver_->num_insecure_dns_task_failures_ = 0;
 
     base::TimeDelta bounded_ttl = std::max(
         results.ttl(), base::TimeDelta::FromSeconds(kMinimumTTLSeconds));
@@ -2349,22 +2326,20 @@
 
 HostResolverManager::HostResolverManager(
     const HostResolver::ManagerOptions& options,
-    NetLog* net_log,
-    DnsClientFactory dns_client_factory_for_testing)
+    NetLog* net_log)
     : max_queued_jobs_(0),
       proc_params_(nullptr, options.max_system_retry_attempts),
       net_log_(net_log),
-      dns_client_factory_for_testing_(
-          std::move(dns_client_factory_for_testing)),
       received_dns_config_(false),
       dns_config_overrides_(options.dns_config_overrides),
-      num_dns_failures_(0),
+      num_insecure_dns_task_failures_(0),
       check_ipv6_on_wifi_(options.check_ipv6_on_wifi),
       use_local_ipv6_(false),
       last_ipv6_probe_result_(true),
       additional_resolver_flags_(0),
-      use_proctask_by_default_(false),
       allow_fallback_to_proctask_(true),
+      bypass_insecure_dns_client_(false),
+      insecure_dns_client_enabled_(false),
       tick_clock_(base::DefaultTickClock::GetInstance()),
       invalidation_in_progress_(false) {
   PrioritizedDispatcher::Limits job_limits = GetDispatcherLimits(options);
@@ -2394,15 +2369,17 @@
 
   OnConnectionTypeChanged(NetworkChangeNotifier::GetConnectionType());
 
-  SetDnsClientEnabled(options.dns_client_enabled);
+  DnsConfig dns_config = GetBaseDnsConfig(false);
 
-  {
-    DnsConfig dns_config = GetBaseDnsConfig(false);
-    // Conservatively assume local IPv6 is needed when DnsConfig is not valid.
-    use_local_ipv6_ = !dns_config.IsValid() || dns_config.use_local_ipv6;
-    UpdateModeForHistogram(dns_config);
-  }
+#if defined(ENABLE_BUILT_IN_DNS)
+  dns_client_ = DnsClient::CreateClient(net_log_);
+  DCHECK(dns_client_);
+  if (!dns_client_->GetConfig())
+    dns_client_->SetConfig(dns_config);
+#endif
 
+  use_local_ipv6_ = !dns_config.IsValid() || dns_config.use_local_ipv6;
+  SetInsecureDnsClientEnabled(options.insecure_dns_client_enabled);
   allow_fallback_to_proctask_ = !ConfigureAsyncDnsNoFallbackFieldTrial();
 }
 
@@ -2461,34 +2438,23 @@
   return listener;
 }
 
-void HostResolverManager::SetDnsClientEnabled(bool enabled) {
+void HostResolverManager::SetInsecureDnsClientEnabled(bool enabled) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  if (enabled && !dns_client_) {
-    if (dns_client_factory_for_testing_) {
-      SetDnsClient(dns_client_factory_for_testing_.Run(net_log_));
-    } else {
-#if defined(ENABLE_BUILT_IN_DNS)
-      SetDnsClient(DnsClient::CreateClient(net_log_));
-#endif
-    }
+  if (insecure_dns_client_enabled_ == enabled)
     return;
-  }
 
-  if (!enabled && dns_client_) {
-    SetDnsClient(nullptr);
-  }
+  insecure_dns_client_enabled_ = enabled;
+  AbortInsecureDnsTasks(ERR_NETWORK_CHANGED, false /* fallback_only */);
+
+  DnsConfig dns_config = GetBaseDnsConfig(false);
+  UpdateModeForHistogram(dns_config);
 }
 
 std::unique_ptr<base::Value> HostResolverManager::GetDnsConfigAsValue() const {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  // Check if async DNS is disabled.
   if (!dns_client_.get())
     return nullptr;
 
-  // Check if async DNS is enabled, but we currently have no configuration
-  // for it.
   const DnsConfig* dns_config = dns_client_->GetConfig();
   if (!dns_config)
     return std::make_unique<base::DictionaryValue>();
@@ -2518,6 +2484,17 @@
   host_cache_invalidators_.RemoveObserver(invalidator);
 }
 
+bool HostResolverManager::GetInsecureDnsClientEnabledForTesting() {
+  return insecure_dns_client_enabled_;
+}
+
+DnsConfig::SecureDnsMode HostResolverManager::GetSecureDnsModeForTesting() {
+  if (!HaveDnsConfig())
+    return DnsConfig::SecureDnsMode::OFF;
+
+  return dns_client_->GetConfig()->secure_dns_mode;
+}
+
 const std::vector<DnsConfig::DnsOverHttpsServerConfig>*
 HostResolverManager::GetDnsOverHttpsServersForTesting() const {
   if (!dns_config_overrides_.dns_over_https_servers ||
@@ -2565,10 +2542,8 @@
 
 void HostResolverManager::SetDnsClientForTesting(
     std::unique_ptr<DnsClient> dns_client) {
-  // Use SetDnsClientEnabled(false) to disable.
   DCHECK(dns_client);
-
-  SetDnsClient(std::move(dns_client));
+  dns_client_ = std::move(dns_client);
 }
 
 void HostResolverManager::SetTaskRunnerForTesting(
@@ -2596,6 +2571,7 @@
 
   DnsQueryType effective_query_type;
   HostResolverFlags effective_host_resolver_flags;
+  DnsConfig::SecureDnsMode effective_secure_dns_mode;
   std::deque<TaskType> tasks;
   base::Optional<HostCache::EntryStaleness> stale_info;
   HostCache::Entry results = ResolveLocally(
@@ -2604,9 +2580,11 @@
       request->parameters().secure_dns_mode_override,
       request->parameters().cache_usage, request->source_net_log(),
       request->host_cache(), &effective_query_type,
-      &effective_host_resolver_flags, &tasks, &stale_info);
+      &effective_host_resolver_flags, &effective_secure_dns_mode, &tasks,
+      &stale_info);
   if (results.error() != ERR_DNS_CACHE_MISS ||
-      request->parameters().source == HostResolverSource::LOCAL_ONLY) {
+      request->parameters().source == HostResolverSource::LOCAL_ONLY ||
+      tasks.empty()) {
     if (results.error() == OK && !request->parameters().is_speculative) {
       request->set_results(
           results.CopyWithDefaultPort(request->request_host().port()));
@@ -2621,7 +2599,7 @@
 
   int rv =
       CreateAndStartJob(effective_query_type, effective_host_resolver_flags,
-                        std::move(tasks), request);
+                        effective_secure_dns_mode, std::move(tasks), request);
   // At this point, expect only async or errors.
   DCHECK_NE(OK, rv);
 
@@ -2639,6 +2617,7 @@
     HostCache* cache,
     DnsQueryType* out_effective_query_type,
     HostResolverFlags* out_effective_host_resolver_flags,
+    DnsConfig::SecureDnsMode* out_effective_secure_dns_mode,
     std::deque<TaskType>* out_tasks,
     base::Optional<HostCache::EntryStaleness>* out_stale_info) {
   DCHECK(out_stale_info);
@@ -2665,7 +2644,8 @@
   GetEffectiveParametersForRequest(
       hostname, dns_query_type, source, flags, secure_dns_mode_override,
       cache_usage, ip_address_ptr, source_net_log, out_effective_query_type,
-      out_effective_host_resolver_flags, out_tasks);
+      out_effective_host_resolver_flags, out_effective_secure_dns_mode,
+      out_tasks);
 
   bool resolve_canonname =
       *out_effective_host_resolver_flags & HOST_RESOLVER_CANONNAME;
@@ -2727,7 +2707,7 @@
   // TODO(szym): Do not do this if nsswitch.conf instructs not to.
   // http://crbug.com/117655
   resolved = ServeFromHosts(hostname, *out_effective_query_type,
-                            default_family_due_to_no_ipv6);
+                            default_family_due_to_no_ipv6, *out_tasks);
   if (resolved) {
     NetLogHostCacheEntry(source_net_log,
                          NetLogEventType::HOST_RESOLVER_IMPL_HOSTS_HIT,
@@ -2741,12 +2721,13 @@
 int HostResolverManager::CreateAndStartJob(
     DnsQueryType effective_query_type,
     HostResolverFlags effective_host_resolver_flags,
+    DnsConfig::SecureDnsMode effective_secure_dns_mode,
     std::deque<TaskType> tasks,
     RequestImpl* request) {
   DCHECK(!tasks.empty());
   JobKey key = {request->request_host().host(), effective_query_type,
-                effective_host_resolver_flags, request->parameters().source,
-                request->request_context()};
+                effective_host_resolver_flags,  request->parameters().source,
+                effective_secure_dns_mode,      request->request_context()};
 
   auto jobit = jobs_.find(key);
   Job* job;
@@ -2858,8 +2839,12 @@
 base::Optional<HostCache::Entry> HostResolverManager::ServeFromHosts(
     base::StringPiece hostname,
     DnsQueryType query_type,
-    bool default_family_due_to_no_ipv6) {
-  if (!HaveDnsConfig() || !IsAddressType(query_type))
+    bool default_family_due_to_no_ipv6,
+    const std::deque<TaskType>& tasks) {
+  // Don't attempt a HOSTS lookup if there is no DnsConfig or the HOSTS lookup
+  // is going to be done next as part of a system lookup.
+  if (!HaveDnsConfig() || !IsAddressType(query_type) ||
+      (!tasks.empty() && tasks.front() == TaskType::PROC))
     return base::nullopt;
 
   // HOSTS lookups are case-insensitive.
@@ -2890,7 +2875,7 @@
   // If got only loopback addresses and the family was restricted, resolve
   // again, without restrictions. See SystemHostResolverCall for rationale.
   if (default_family_due_to_no_ipv6 && IsAllIPv4Loopback(addresses)) {
-    return ServeFromHosts(hostname, DnsQueryType::UNSPECIFIED, false);
+    return ServeFromHosts(hostname, DnsQueryType::UNSPECIFIED, false, tasks);
   }
 
   if (!addresses.empty()) {
@@ -2989,12 +2974,13 @@
 }
 
 bool HostResolverManager::HasAvailableDohServer() {
-  // TODO(crbug.com/878582): Once DoH probes are sent, update this such that a
+  // TODO(crbug.com/985589): Once DoH probes are sent, update this such that a
   // DoH server is considered successful if it has a successful probe state.
   return dns_client_->GetConfig()->dns_over_https_servers.size() > 0;
 }
 
 DnsConfig::SecureDnsMode HostResolverManager::GetEffectiveSecureDnsMode(
+    const std::string& hostname,
     base::Optional<DnsConfig::SecureDnsMode> secure_dns_mode_override) {
   DnsConfig::SecureDnsMode secure_dns_mode = DnsConfig::SecureDnsMode::OFF;
   if (secure_dns_mode_override) {
@@ -3002,64 +2988,107 @@
   } else if (HaveDnsConfig()) {
     secure_dns_mode = dns_client_->GetConfig()->secure_dns_mode;
   }
+
+  // If the query name matches one of the DoH server names, downgrade to OFF to
+  // avoid infinite recursion.
+  // TODO(crbug.com/985589): Add a URLRequest-level parameter to skip DoH that
+  // can be set when a URLRequest to a DoH server is built, and use this
+  // parameters to set |secure_dns_mode_override| in ResolveHostParameters. This
+  // improvement will prevent us from unnecessarily skipping DoH when a
+  // connection to the DoH server has been established but the query happens to
+  // be for a DoH server hostname.
+  if (HaveDnsConfig()) {
+    for (auto& server : dns_client_->GetConfig()->dns_over_https_servers) {
+      if (hostname.compare(
+              GURL(GetURLFromTemplateWithoutParameters(server.server_template))
+                  .host()) == 0) {
+        secure_dns_mode = DnsConfig::SecureDnsMode::OFF;
+        break;
+      }
+    }
+  }
+
   return secure_dns_mode;
 }
 
+bool HostResolverManager::HaveTestProcOverride() {
+  return !proc_params_.resolver_proc && HostResolverProc::GetDefault();
+}
+
 void HostResolverManager::PushDnsTasks(
-    bool allow_proc_fallback,
+    bool proc_task_allowed,
     DnsConfig::SecureDnsMode secure_dns_mode,
+    bool insecure_tasks_allowed,
     ResolveHostParameters::CacheUsage cache_usage,
     std::deque<TaskType>* out_tasks) {
+  DCHECK(HaveDnsConfig());
   bool allow_cache =
       cache_usage != ResolveHostParameters::CacheUsage::DISALLOWED;
+  // If a catch-all DNS block has been set for unit tests, we shouldn't send
+  // DnsTasks. It is still necessary to call this method, however, so that the
+  // correct cache tasks for the secure dns mode are added.
+  bool dns_tasks_allowed = !HaveTestProcOverride();
   // Upgrade the insecure DnsTask depending on the secure dns mode.
   switch (secure_dns_mode) {
     case DnsConfig::SecureDnsMode::SECURE:
       DCHECK(dns_client_->GetConfig()->dns_over_https_servers.size() != 0);
-      // Replace the insecure DnsTask with a secure cache lookup followed
-      // by a secure DnsTask.
       if (allow_cache)
         out_tasks->push_back(TaskType::SECURE_CACHE_LOOKUP);
-      out_tasks->push_back(TaskType::SECURE_DNS);
+      if (dns_tasks_allowed)
+        out_tasks->push_back(TaskType::SECURE_DNS);
       break;
     case DnsConfig::SecureDnsMode::AUTOMATIC:
-      // TODO(crbug.com/878582): For a DnsTask in AUTOMATIC mode, the async
+      // TODO(crbug.com/985589): For a DnsTask in AUTOMATIC mode, the async
       // resolver should only send insecure requests if it is enabled on this
       // platform.
       if (!HasAvailableDohServer()) {
         // Don't run a secure DnsTask if there are no available DoH servers.
         if (allow_cache)
           out_tasks->push_back(TaskType::CACHE_LOOKUP);
-        out_tasks->push_back(TaskType::DNS);
+        if (dns_tasks_allowed && insecure_tasks_allowed)
+          out_tasks->push_back(TaskType::DNS);
       } else if (cache_usage == HostResolver::ResolveHostParameters::
                                     CacheUsage::STALE_ALLOWED) {
         // If stale results are allowed, the cache should be checked for both
         // secure and insecure results prior to running a secure DnsTask.
         out_tasks->push_back(TaskType::CACHE_LOOKUP);
-        out_tasks->push_back(TaskType::SECURE_DNS);
-        out_tasks->push_back(TaskType::DNS);
+        if (dns_tasks_allowed) {
+          out_tasks->push_back(TaskType::SECURE_DNS);
+          if (insecure_tasks_allowed)
+            out_tasks->push_back(TaskType::DNS);
+        }
       } else {
         if (allow_cache)
           out_tasks->push_back(TaskType::SECURE_CACHE_LOOKUP);
-        out_tasks->push_back(TaskType::SECURE_DNS);
+        if (dns_tasks_allowed)
+          out_tasks->push_back(TaskType::SECURE_DNS);
         if (allow_cache)
           out_tasks->push_back(TaskType::INSECURE_CACHE_LOOKUP);
-        out_tasks->push_back(TaskType::DNS);
+        if (dns_tasks_allowed && insecure_tasks_allowed)
+          out_tasks->push_back(TaskType::DNS);
       }
       break;
     case DnsConfig::SecureDnsMode::OFF:
       if (allow_cache)
         out_tasks->push_back(TaskType::CACHE_LOOKUP);
-      out_tasks->push_back(TaskType::DNS);
+      if (dns_tasks_allowed && insecure_tasks_allowed)
+        out_tasks->push_back(TaskType::DNS);
       break;
     default:
       NOTREACHED();
       break;
   }
 
-  // The system resolver can be used as a fallback if allowed by the request
-  // parameters.
-  if (allow_proc_fallback && allow_fallback_to_proctask_)
+  bool added_dns_task = false;
+  for (auto it = out_tasks->begin(); it != out_tasks->end(); ++it) {
+    if (*it == TaskType::DNS || *it == TaskType::SECURE_DNS) {
+      added_dns_task = true;
+      break;
+    }
+  }
+  // The system resolver can be used as a fallback for a non-existent or
+  // failing DnsTask if allowed by the request parameters.
+  if (proc_task_allowed && (!added_dns_task || allow_fallback_to_proctask_))
     out_tasks->push_back(TaskType::PROC);
 }
 
@@ -3070,10 +3099,11 @@
     HostResolverFlags flags,
     base::Optional<DnsConfig::SecureDnsMode> secure_dns_mode_override,
     ResolveHostParameters::CacheUsage cache_usage,
+    DnsConfig::SecureDnsMode* out_effective_secure_dns_mode,
     std::deque<TaskType>* out_tasks) {
   DCHECK(out_tasks->empty());
-  DnsConfig::SecureDnsMode secure_dns_mode =
-      GetEffectiveSecureDnsMode(secure_dns_mode_override);
+  *out_effective_secure_dns_mode =
+      GetEffectiveSecureDnsMode(hostname, secure_dns_mode_override);
 
   // A cache lookup should generally be performed first. For jobs involving a
   // DnsTask, this task will be removed before DnsTasks and other related tasks
@@ -3094,22 +3124,24 @@
       if ((flags & HOST_RESOLVER_CANONNAME) && IsAddressType(dns_query_type)) {
         out_tasks->push_back(TaskType::PROC);
       } else if (!ResemblesMulticastDNSName(hostname)) {
-        bool allow_proc_fallback =
+        bool proc_task_allowed =
             IsAddressType(dns_query_type) &&
-            secure_dns_mode != DnsConfig::SecureDnsMode::SECURE;
-        // DnsClient or config is not available, but we're allowed to switch to
-        // ProcTask instead.
-        if ((!HaveDnsConfig() || use_proctask_by_default_) &&
-            allow_proc_fallback) {
-          out_tasks->push_back(TaskType::PROC);
-        } else {
+            *out_effective_secure_dns_mode != DnsConfig::SecureDnsMode::SECURE;
+        if (HaveDnsConfig()) {
           // Remove the initial cache lookup task.
           if (!out_tasks->empty())
             out_tasks->pop_front();
-          PushDnsTasks(allow_proc_fallback, secure_dns_mode, cache_usage,
-                       out_tasks);
+          PushDnsTasks(
+              proc_task_allowed, *out_effective_secure_dns_mode,
+              insecure_dns_client_enabled_ && !bypass_insecure_dns_client_,
+              cache_usage, out_tasks);
+        } else if (proc_task_allowed) {
+          out_tasks->push_back(TaskType::PROC);
         }
       } else if (IsAddressType(dns_query_type)) {
+        // For *.local address queries, try the system resolver even if the
+        // secure dns mode is SECURE. Public recursive resolvers aren't expected
+        // to handle these queries.
         out_tasks->push_back(TaskType::PROC);
       } else {
         out_tasks->push_back(TaskType::MDNS);
@@ -3119,11 +3151,14 @@
       out_tasks->push_back(TaskType::PROC);
       break;
     case HostResolverSource::DNS:
-      // Remove the initial cache lookup task.
-      if (!out_tasks->empty())
-        out_tasks->pop_front();
-      PushDnsTasks(false /* allow_proc_fallback */, secure_dns_mode,
-                   cache_usage, out_tasks);
+      if (HaveDnsConfig()) {
+        // Remove the initial cache lookup task.
+        if (!out_tasks->empty())
+          out_tasks->pop_front();
+        PushDnsTasks(false /* proc_task_allowed */,
+                     *out_effective_secure_dns_mode,
+                     insecure_dns_client_enabled_, cache_usage, out_tasks);
+      }
       break;
     case HostResolverSource::MULTICAST_DNS:
       out_tasks->push_back(TaskType::MDNS);
@@ -3145,6 +3180,7 @@
     const NetLogWithSource& net_log,
     DnsQueryType* out_effective_type,
     HostResolverFlags* out_effective_flags,
+    DnsConfig::SecureDnsMode* out_effective_secure_dns_mode,
     std::deque<TaskType>* out_tasks) {
   *out_effective_flags = flags | additional_resolver_flags_;
   *out_effective_type = dns_query_type;
@@ -3162,7 +3198,7 @@
 
   CreateTaskSequence(hostname, *out_effective_type, source,
                      *out_effective_flags, secure_dns_mode_override,
-                     cache_usage, out_tasks);
+                     cache_usage, out_effective_secure_dns_mode, out_tasks);
 }
 
 bool HostResolverManager::IsIPv6Reachable(const NetLogWithSource& net_log) {
@@ -3261,35 +3297,13 @@
     dispatcher_->SetLimits(limits);
 }
 
-void HostResolverManager::SetDnsClient(std::unique_ptr<DnsClient> dns_client) {
-  DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-
-  // DnsClient and config must be updated before aborting DnsTasks, since doing
-  // so may start new jobs.
-  dns_client_ = std::move(dns_client);
-  if (dns_client_ && !dns_client_->GetConfig() &&
-      num_dns_failures_ < kMaximumDnsFailures) {
-    dns_client_->SetConfig(GetBaseDnsConfig(false));
-    num_dns_failures_ = 0;
-  }
-
-  AbortDnsTasks(ERR_NETWORK_CHANGED, false /* fallback_only */);
-  DnsConfig dns_config;
-  if (!HaveDnsConfig()) {
-    // UpdateModeForHistogram() needs to know the DnsConfig when
-    // !HaveDnsConfig()
-    dns_config = GetBaseDnsConfig(false);
-  }
-  UpdateModeForHistogram(dns_config);
-}
-
-void HostResolverManager::AbortDnsTasks(int error, bool fallback_only) {
+void HostResolverManager::AbortInsecureDnsTasks(int error, bool fallback_only) {
   // Aborting jobs potentially modifies |jobs_| and may even delete some jobs.
   // Create safe closures of all current jobs.
   std::vector<base::OnceClosure> job_abort_closures;
   for (auto& job : jobs_) {
     job_abort_closures.push_back(
-        job.second->GetAbortDnsTaskClosure(error, fallback_only));
+        job.second->GetAbortInsecureDnsTaskClosure(error, fallback_only));
   }
 
   // Pause the dispatcher so it won't start any new dispatcher jobs while
@@ -3378,6 +3392,7 @@
     received_dns_config_ = dns_config.IsValid();
   }
 
+  // TODO(crbug.com/985589): Upgrade DoH servers for automatic mode here.
   return dns_config_overrides_.ApplyOverrides(dns_config);
 }
 
@@ -3387,8 +3402,6 @@
   // Conservatively assume local IPv6 is needed when DnsConfig is not valid.
   use_local_ipv6_ = !dns_config.IsValid() || dns_config.use_local_ipv6;
 
-  num_dns_failures_ = 0;
-
   // We want a new DnsSession in place, before we Abort running Jobs, so that
   // the newly started jobs use the new config.
   if (dns_client_.get()) {
@@ -3398,11 +3411,14 @@
            dns_client_->GetConfig()->Equals(dns_config));
     dns_client_->SetConfig(dns_config);
   }
-  use_proctask_by_default_ = false;
 
   if (config_changed) {
     InvalidateCaches();
 
+    // Reset the insecure DNS task failure counter.
+    num_insecure_dns_task_failures_ = 0;
+    bypass_insecure_dns_client_ = false;
+
     // Life check to bail once |this| is deleted.
     base::WeakPtr<HostResolverManager> self = weak_ptr_factory_.GetWeakPtr();
 
@@ -3419,35 +3435,25 @@
 }
 
 bool HostResolverManager::HaveDnsConfig() const {
-  // Use DnsClient only if it's fully configured and there is no override by
-  // ScopedDefaultHostResolverProc.
-  // The alternative is to use NetworkChangeNotifier to override DnsConfig,
-  // but that would introduce construction order requirements for NCN and SDHRP.
-  return dns_client_ && dns_client_->GetConfig() &&
-         (proc_params_.resolver_proc || !HostResolverProc::GetDefault());
-}
-
-void HostResolverManager::OnDnsTaskResolve() {
-  DCHECK(dns_client_);
-  num_dns_failures_ = 0;
+  return dns_client_ && dns_client_->GetConfig();
 }
 
 void HostResolverManager::OnFallbackResolve(int dns_task_error) {
   DCHECK(dns_client_);
   DCHECK_NE(OK, dns_task_error);
 
-  ++num_dns_failures_;
-  if (num_dns_failures_ < kMaximumDnsFailures)
+  ++num_insecure_dns_task_failures_;
+  if (num_insecure_dns_task_failures_ < kMaximumInsecureDnsTaskFailures)
     return;
 
-  // Force fallback until the next DNS change.  Must be done before aborting
-  // DnsTasks, since doing so may start new jobs.  Do not fully clear out or
-  // disable the DnsClient as some requests (e.g. those specifying DNS source)
-  // are not allowed to fallback and will continue using DnsTask.
-  use_proctask_by_default_ = true;
+  // Skip insecure DNS lookups until the next DNS config change.  Must be done
+  // before aborting DnsTasks, since doing so may start new jobs.  Do not fully
+  // clear out or disable the DnsClient as some requests (e.g. those specifying
+  // DNS source) are not allowed to fallback and will continue using DnsTask.
+  bypass_insecure_dns_client_ = true;
 
-  // Fallback all fallback-allowed DnsTasks to ProcTasks.
-  AbortDnsTasks(ERR_FAILED, true /* fallback_only */);
+  // Fallback all fallback-allowed insecure DnsTasks to ProcTasks.
+  AbortInsecureDnsTasks(ERR_FAILED, true /* fallback_only */);
 }
 
 int HostResolverManager::GetOrCreateMdnsClient(MDnsClient** out_client) {
@@ -3474,9 +3480,10 @@
 #endif
 }
 
+// TODO(crbug.com/985589): Update these metrics for DoH.
 void HostResolverManager::UpdateModeForHistogram(const DnsConfig& dns_config) {
   // Resolving with Async DNS resolver?
-  if (HaveDnsConfig()) {
+  if (HaveDnsConfig() && insecure_dns_client_enabled_) {
     mode_for_histogram_ = MODE_FOR_HISTOGRAM_ASYNC_DNS;
     for (const auto& dns_server : dns_client_->GetConfig()->nameservers) {
       if (DnsServerSupportsDoh(dns_server.address())) {
diff --git a/net/dns/host_resolver_manager.h b/net/dns/host_resolver_manager.h
index 8f904491..b83f4f8 100644
--- a/net/dns/host_resolver_manager.h
+++ b/net/dns/host_resolver_manager.h
@@ -89,8 +89,6 @@
   using MdnsListener = HostResolver::MdnsListener;
   using ResolveHostRequest = HostResolver::ResolveHostRequest;
   using ResolveHostParameters = HostResolver::ResolveHostParameters;
-  using DnsClientFactory =
-      base::RepeatingCallback<std::unique_ptr<DnsClient>(NetLog*)>;
   using SecureDnsMode = DnsConfig::SecureDnsMode;
 
   class CancellableRequest : public ResolveHostRequest {
@@ -112,14 +110,8 @@
   // outstanding DNS transactions (not counting retransmissions and retries).
   //
   // |net_log| must remain valid for the life of the HostResolverManager.
-  //
-  // |dns_client_factory_for_testing| may be used to inject a factory to be used
-  // for ManagerOptions::dns_client_enabled and SetDnsClientEnabled(). If not
-  // set, standard DnsClient::CreateClient() will be used.
-  HostResolverManager(
-      const HostResolver::ManagerOptions& options,
-      NetLog* net_log,
-      DnsClientFactory dns_client_factory_for_testing = base::NullCallback());
+  HostResolverManager(const HostResolver::ManagerOptions& options,
+                      NetLog* net_log);
 
   // If any completion callbacks are pending when the resolver is destroyed,
   // the host resolutions are cancelled, and the completion callbacks will not
@@ -148,7 +140,7 @@
   // DnsConfig, a new config is fetched from NetworkChangeNotifier.
   //
   // Setting to |true| has no effect if |ENABLE_BUILT_IN_DNS| not defined.
-  virtual void SetDnsClientEnabled(bool enabled);
+  virtual void SetInsecureDnsClientEnabled(bool enabled);
 
   std::unique_ptr<base::Value> GetDnsConfigAsValue() const;
 
@@ -170,6 +162,13 @@
   void AddHostCacheInvalidator(HostCache::Invalidator* invalidator);
   void RemoveHostCacheInvalidator(const HostCache::Invalidator* invalidator);
 
+  // Returns the state of the insecure part of the DnsClient.
+  bool GetInsecureDnsClientEnabledForTesting();
+
+  // Returns the currently configured secure dns mode.  Returns OFF if there is
+  // not a valid config.
+  SecureDnsMode GetSecureDnsModeForTesting();
+
   // Returns the currently configured DNS over HTTPS servers. Returns nullptr if
   // DNS over HTTPS is not enabled.
   const std::vector<DnsConfig::DnsOverHttpsServerConfig>*
@@ -193,8 +192,6 @@
 
   void SetBaseDnsConfigForTesting(const DnsConfig& base_config);
 
-  // Similar to SetDnsClientEnabled(true) except allows setting |dns_client|
-  // as the instance to be used.
   void SetDnsClientForTesting(std::unique_ptr<DnsClient> dns_client);
 
   // Allows the tests to catch slots leaking out of the dispatcher.  One
@@ -217,6 +214,7 @@
 
  private:
   friend class HostResolverManagerTest;
+  friend class HostResolverManagerDnsTest;
   FRIEND_TEST_ALL_PREFIXES(HostResolverManagerDnsTest, ModeForHistogram);
   class Job;
   struct JobKey;
@@ -253,9 +251,10 @@
     SECURE_CACHE_LOOKUP,
   };
 
-  // Number of consecutive failures of DnsTask (with successful fallback to
-  // ProcTask) before the DnsClient is disabled until the next DNS change.
-  static const unsigned kMaximumDnsFailures;
+  // Number of consecutive failures of an insecure DnsTask (with successful
+  // fallback to ProcTask) before the insecure portion of the DnsClient is
+  // disabled until the next DNS change.
+  static const unsigned kMaximumInsecureDnsTaskFailures;
 
   // Attempts host resolution for |request|. Generally only expected to be
   // called from RequestImpl::Start().
@@ -268,9 +267,9 @@
   // sources.
   //
   // On ERR_DNS_CACHE_MISS and OK, effective request parameters are written to
-  // |out_effective_query_type| and |out_effective_host_resolver_flags|.
-  // |out_tasks| contains the tentative sequence of tasks that a future job
-  // should run.
+  // |out_effective_query_type|, |out_effective_host_resolver_flags|, and
+  // |out_effective_secure_dns_mode|. |out_tasks| contains the tentative
+  // sequence of tasks that a future job should run.
   //
   // If results are returned from the host cache, |out_stale_info| will be
   // filled in with information on how stale or fresh the result is. Otherwise,
@@ -289,6 +288,7 @@
       HostCache* cache,
       DnsQueryType* out_effective_query_type,
       HostResolverFlags* out_effective_host_resolver_flags,
+      DnsConfig::SecureDnsMode* out_effective_secure_dns_mode,
       std::deque<TaskType>* out_tasks,
       base::Optional<HostCache::EntryStaleness>* out_stale_info);
 
@@ -297,6 +297,7 @@
   // |request|. On error, marks |request| completed and returns the error.
   int CreateAndStartJob(DnsQueryType effective_query_type,
                         HostResolverFlags effective_host_resolver_flags,
+                        DnsConfig::SecureDnsMode effective_secure_dns_mode,
                         std::deque<TaskType> tasks,
                         RequestImpl* request);
 
@@ -318,12 +319,14 @@
       const NetLogWithSource& source_net_log,
       base::Optional<HostCache::EntryStaleness>* out_stale_info);
 
-  // Iff we have a DnsClient with a valid DnsConfig, and |key| can be resolved
-  // from the HOSTS file, return the results.
+  // Iff we have a DnsClient with a valid DnsConfig and we're not about to
+  // attempt a system lookup, then try to resolve the query using the HOSTS
+  // file.
   base::Optional<HostCache::Entry> ServeFromHosts(
       base::StringPiece hostname,
       DnsQueryType query_type,
-      bool default_family_due_to_no_ipv6);
+      bool default_family_due_to_no_ipv6,
+      const std::deque<TaskType>& tasks);
 
   // Iff |key| is for a localhost name (RFC 6761) and address DNS query type,
   // returns a results entry with the loopback IP.
@@ -337,14 +340,21 @@
   bool HasAvailableDohServer();
 
   // Returns the secure dns mode to use for a job, taking into account the
-  // global DnsConfig mode and any per-request override.
+  // global DnsConfig mode and any per-request override. Requests matching DoH
+  // server hostnames are downgraded to off mode to avoid infinite loops.
   SecureDnsMode GetEffectiveSecureDnsMode(
+      const std::string& hostname,
       base::Optional<SecureDnsMode> secure_dns_mode_override);
 
+  // Returns true if a catch-all DNS block has been set for unit tests. No
+  // DnsTasks should be issued in this case.
+  bool HaveTestProcOverride();
+
   // Helper method to add DnsTasks and related tasks based on the SecureDnsMode
   // and fallback parameters.
-  void PushDnsTasks(bool allow_proc_fallback,
+  void PushDnsTasks(bool proc_task_allowed,
                     SecureDnsMode secure_dns_mode,
+                    bool insecure_tasks_allowed,
                     ResolveHostParameters::CacheUsage cache_usage,
                     std::deque<TaskType>* out_tasks);
 
@@ -357,6 +367,7 @@
       HostResolverFlags flags,
       base::Optional<SecureDnsMode> secure_dns_mode_override,
       ResolveHostParameters::CacheUsage cache_usage,
+      DnsConfig::SecureDnsMode* out_effective_secure_dns_mode,
       std::deque<TaskType>* out_tasks);
 
   // Determines "effective" request parameters using manager properties and IPv6
@@ -372,6 +383,7 @@
       const NetLogWithSource& net_log,
       DnsQueryType* out_effective_type,
       HostResolverFlags* out_effective_flags,
+      DnsConfig::SecureDnsMode* out_effective_secure_dns_mode,
       std::deque<TaskType>* out_tasks);
 
   // Probes IPv6 support and returns true if IPv6 support is enabled.
@@ -405,14 +417,13 @@
   // true. Might start new jobs.
   void AbortAllJobs(bool in_progress_only);
 
-  void SetDnsClient(std::unique_ptr<DnsClient> dns_client);
-
-  // Aborts all in progress DnsTasks. In-progress jobs will fall back to
-  // ProcTasks if able and otherwise abort with |error|. Might start new jobs,
-  // if any jobs were taking up two dispatcher slots.
+  // Aborts all in progress insecure DnsTasks. In-progress jobs will fall back
+  // to ProcTasks if able and otherwise abort with |error|. Might start new
+  // jobs, if any jobs were taking up two dispatcher slots.
   //
-  // If |fallback_only|, tasks will only abort if they can fallback to ProcTask.
-  void AbortDnsTasks(int error, bool fallback_only);
+  // If |fallback_only|, insecure DnsTasks will only abort if they can fallback
+  // to ProcTask.
+  void AbortInsecureDnsTasks(int error, bool fallback_only);
 
   // Attempts to serve each Job in |jobs_| from the HOSTS file if we have
   // a DnsClient with a valid DnsConfig.
@@ -437,8 +448,6 @@
   // True if have a DnsClient with a valid DnsConfig.
   bool HaveDnsConfig() const;
 
-  // Called on successful DnsTask resolve.
-  void OnDnsTaskResolve();
   // Called on successful resolve after falling back to ProcTask after a failed
   // DnsTask resolve.
   void OnFallbackResolve(int dns_task_error);
@@ -470,9 +479,6 @@
 
   NetLog* net_log_;
 
-  // If set, used for construction of DnsClients for SetDnsClientEnabled().
-  const DnsClientFactory dns_client_factory_for_testing_;
-
   // If present, used by DnsTask and ServeFromHosts to resolve requests.
   std::unique_ptr<DnsClient> dns_client_;
 
@@ -489,8 +495,9 @@
   // resolution.
   DnsConfigOverrides dns_config_overrides_;
 
-  // Number of consecutive failures of DnsTask, counted when fallback succeeds.
-  unsigned num_dns_failures_;
+  // Number of consecutive failures of insecure DnsTask, counted when fallback
+  // succeeds.
+  unsigned num_insecure_dns_task_failures_;
 
   // False if IPv6 should not be attempted and assumed unreachable when on a
   // WiFi connection. See https://crbug.com/696569 for further context.
@@ -506,15 +513,14 @@
   // Any resolver flags that should be added to a request by default.
   HostResolverFlags additional_resolver_flags_;
 
-  // |true| if requests that would otherwise be handled via DnsTask should
-  // instead use ProcTask when able.  Used in cases where there have been
-  // multiple failures in DnsTask that succeeded in ProcTask, leading to the
-  // conclusion that the resolver has a bad DNS configuration.
-  bool use_proctask_by_default_;
-
   // Allow fallback to ProcTask if DnsTask fails.
   bool allow_fallback_to_proctask_;
 
+  // Whether insecure DnsTasks should not be added to the task sequence even if
+  // the insecure part of the DnsClient is enabled. This is set to true when
+  // there are too many successive insecure DnsTask failures.
+  bool bypass_insecure_dns_client_;
+
   // Task runner used for DNS lookups using the system resolver. Normally a
   // ThreadPool task runner, but can be overridden for tests.
   scoped_refptr<base::TaskRunner> proc_task_runner_;
@@ -522,6 +528,13 @@
   // Current resolver mode, useful for breaking down histogram data.
   ModeForHistogram mode_for_histogram_;
 
+  // Whether insecure requests should be issued by DnsClient. This field is
+  // complementary to the SecureDnsMode in DnsConfig. If we're in AUTOMATIC
+  // mode, |insecure_dns_client_enabled_| will determine whether or not we
+  // fallback to the insecure part of DnsClient before falling back to the
+  // system resolver.
+  bool insecure_dns_client_enabled_;
+
   // Shared tick clock, overridden for testing.
   const base::TickClock* tick_clock_;
 
diff --git a/net/dns/host_resolver_manager_fuzzer.cc b/net/dns/host_resolver_manager_fuzzer.cc
index 0606e0a..23795a6 100644
--- a/net/dns/host_resolver_manager_fuzzer.cc
+++ b/net/dns/host_resolver_manager_fuzzer.cc
@@ -205,7 +205,7 @@
     net::HostResolver::ManagerOptions options;
     options.max_concurrent_resolves =
         data_provider.ConsumeIntegralInRange(1, 8);
-    options.dns_client_enabled = data_provider.ConsumeBool();
+    options.insecure_dns_client_enabled = data_provider.ConsumeBool();
     bool enable_caching = data_provider.ConsumeBool();
     std::unique_ptr<net::ContextHostResolver> host_resolver =
         net::CreateFuzzedContextHostResolver(options, &net_log, &data_provider,
diff --git a/net/dns/host_resolver_manager_unittest.cc b/net/dns/host_resolver_manager_unittest.cc
index f332b9c..860fca3 100644
--- a/net/dns/host_resolver_manager_unittest.cc
+++ b/net/dns/host_resolver_manager_unittest.cc
@@ -426,12 +426,10 @@
                           NetLog* net_log)
       : TestHostResolverManager(options, net_log, true) {}
 
-  TestHostResolverManager(
-      const HostResolver::ManagerOptions& options,
-      NetLog* net_log,
-      bool ipv6_reachable,
-      DnsClientFactory dns_client_factory_for_testing = base::NullCallback())
-      : HostResolverManager(options, net_log, dns_client_factory_for_testing),
+  TestHostResolverManager(const HostResolver::ManagerOptions& options,
+                          NetLog* net_log,
+                          bool ipv6_reachable)
+      : HostResolverManager(options, net_log),
         ipv6_reachable_(ipv6_reachable) {}
 
   ~TestHostResolverManager() override = default;
@@ -541,7 +539,7 @@
       const ProcTaskParams& params,
       bool ipv6_reachable) {
     // Use HostResolverManagerDnsTest if enabling DNS client.
-    DCHECK(!options.dns_client_enabled);
+    DCHECK(!options.insecure_dns_client_enabled);
 
     DestroyResolver();
 
@@ -564,8 +562,8 @@
     resolver_->allow_fallback_to_proctask_ = allow_fallback_to_proctask;
   }
 
-  static unsigned maximum_dns_failures() {
-    return HostResolverManager::kMaximumDnsFailures;
+  static unsigned maximum_insecure_dns_task_failures() {
+    return HostResolverManager::kMaximumInsecureDnsTaskFailures;
   }
 
   bool IsIPv6Reachable(const NetLogWithSource& net_log) {
@@ -3406,28 +3404,21 @@
   HostResolver::ManagerOptions DefaultOptions() override {
     HostResolver::ManagerOptions options =
         HostResolverManagerTest::DefaultOptions();
-    options.dns_client_enabled = true;
+    options.insecure_dns_client_enabled = true;
     return options;
   }
 
-  // Implements HostResolverManager::DnsClientFactory to create a MockDnsClient
-  // with empty config and default rules.
-  std::unique_ptr<DnsClient> CreateMockClient(NetLog* net_log) {
-    auto dns_client =
-        std::make_unique<MockDnsClient>(DnsConfig(), CreateDefaultDnsRules());
-    dns_client_ = dns_client.get();
-    return dns_client;
-  }
-
   void CreateResolverWithOptionsAndParams(HostResolver::ManagerOptions options,
                                           const ProcTaskParams& params,
                                           bool ipv6_reachable) override {
     DestroyResolver();
 
     resolver_ = std::make_unique<TestHostResolverManager>(
-        options, nullptr /* net_log */, ipv6_reachable,
-        base::BindRepeating(&HostResolverManagerDnsTest::CreateMockClient,
-                            base::Unretained(this)));
+        options, nullptr /* net_log */, ipv6_reachable);
+    auto dns_client =
+        std::make_unique<MockDnsClient>(DnsConfig(), CreateDefaultDnsRules());
+    dns_client_ = dns_client.get();
+    resolver_->SetDnsClientForTesting(std::move(dns_client));
     resolver_->set_proc_params_for_test(params);
 
     if (host_cache_)
@@ -3443,6 +3434,7 @@
         std::make_unique<MockDnsClient>(DnsConfig(), std::move(rules));
     dns_client_ = dns_client.get();
     resolver_->SetDnsClientForTesting(std::move(dns_client));
+    resolver_->SetInsecureDnsClientEnabled(true);
     if (!config.Equals(DnsConfig()))
       ChangeDnsConfig(config);
   }
@@ -3537,6 +3529,11 @@
     AddDnsRule(&rules, "insecure_automatic", dns_protocol::kTypeAAAA,
                MockDnsClientRule::OK, false /* delay */);
 
+    AddSecureDnsRule(&rules, "secure", dns_protocol::kTypeA,
+                     MockDnsClientRule::OK, false /* delay */);
+    AddSecureDnsRule(&rules, "secure", dns_protocol::kTypeAAAA,
+                     MockDnsClientRule::OK, false /* delay */);
+
     return rules;
   }
 
@@ -3600,7 +3597,7 @@
   MockDnsClient* dns_client_;
 };
 
-TEST_F(HostResolverManagerDnsTest, DisableAndEnableDnsClient) {
+TEST_F(HostResolverManagerDnsTest, DisableAndEnableInsecureDnsClient) {
   // Disable fallback to allow testing how requests are initially handled.
   set_allow_fallback_to_proctask(false);
 
@@ -3608,7 +3605,7 @@
   proc_->AddRuleForAllFamilies("nx_succeed", "192.168.2.47");
   proc_->SignalMultiple(1u);
 
-  resolver_->SetDnsClientEnabled(false);
+  resolver_->SetInsecureDnsClientEnabled(false);
   ResolveHostResponseHelper response_proc(resolver_->CreateRequest(
       HostPortPair("nx_succeed", 1212), NetLogWithSource(), base::nullopt,
       request_context_.get(), host_cache_.get()));
@@ -3616,7 +3613,7 @@
   EXPECT_THAT(response_proc.request()->GetAddressResults().value().endpoints(),
               testing::ElementsAre(CreateExpected("192.168.2.47", 1212)));
 
-  resolver_->SetDnsClientEnabled(true);
+  resolver_->SetInsecureDnsClientEnabled(true);
   ResolveHostResponseHelper response_dns_client(resolver_->CreateRequest(
       HostPortPair("ok_fail", 1212), NetLogWithSource(), base::nullopt,
       request_context_.get(), host_cache_.get()));
@@ -3757,32 +3754,18 @@
       initial_response1.request()->GetAddressResults().value().endpoints(),
       testing::ElementsAre(CreateExpected("192.168.1.102", 80)));
 
+  // Switch to a valid config.
   ChangeDnsConfig(CreateValidDnsConfig());
-
-  ResolveHostResponseHelper abort_response0(resolver_->CreateRequest(
-      HostPortPair("ok_abort", 80), NetLogWithSource(), base::nullopt,
-      request_context_.get(), host_cache_.get()));
-  ResolveHostResponseHelper abort_response1(resolver_->CreateRequest(
-      HostPortPair("nx_abort", 80), NetLogWithSource(), base::nullopt,
-      request_context_.get(), host_cache_.get()));
-
-  // Simulate the case when the preference or policy has disabled the DNS
-  // client causing AbortDnsTasks.
-  UseMockDnsClient(CreateValidDnsConfig(), CreateDefaultDnsRules());
-
   // First request is resolved by MockDnsClient, others should fail due to
   // disabled fallback to ProcTask.
   ResolveHostResponseHelper response0(resolver_->CreateRequest(
       HostPortPair("ok_fail", 80), NetLogWithSource(), base::nullopt,
       request_context_.get(), host_cache_.get()));
   ResolveHostResponseHelper response1(resolver_->CreateRequest(
-      HostPortPair("nx_fail", 80), NetLogWithSource(), base::nullopt,
+      HostPortPair("nx_succeed", 80), NetLogWithSource(), base::nullopt,
       request_context_.get(), host_cache_.get()));
   proc_->SignalMultiple(6u);
 
-  // Aborted due to Network Change.
-  EXPECT_THAT(abort_response0.result_error(), IsError(ERR_NETWORK_CHANGED));
-  EXPECT_THAT(abort_response1.result_error(), IsError(ERR_NETWORK_CHANGED));
   // Resolved by MockDnsClient.
   EXPECT_THAT(response0.result_error(), IsOk());
   EXPECT_THAT(response0.request()->GetAddressResults().value().endpoints(),
@@ -3889,9 +3872,9 @@
       request_context_.get(), host_cache_.get()));
   proc_->SignalMultiple(2u);
 
-  // Simulate the case when the preference or policy has disabled the DNS client
-  // causing AbortDnsTasks.
-  resolver_->SetDnsClientEnabled(false);
+  // Simulate the case when the preference or policy has disabled the insecure
+  // DNS client causing AbortInsecureDnsTasks.
+  resolver_->SetInsecureDnsClientEnabled(false);
 
   // All requests should fallback to proc resolver.
   EXPECT_THAT(response0.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
@@ -3922,15 +3905,45 @@
   // instead of hanging.
   proc_->SignalMultiple(2u);
 
-  // Simulate the case when the preference or policy has disabled the DNS client
-  // causing AbortDnsTasks.
-  resolver_->SetDnsClientEnabled(false);
+  // Simulate the case when the preference or policy has disabled the insecure
+  // DNS client causing AbortInsecureDnsTasks.
+  resolver_->SetInsecureDnsClientEnabled(false);
 
   // No fallback expected.  All requests should fail.
   EXPECT_THAT(response0.result_error(), IsError(ERR_NETWORK_CHANGED));
   EXPECT_THAT(response1.result_error(), IsError(ERR_NETWORK_CHANGED));
 }
 
+// Insecure DnsClient change shouldn't affect secure DnsTasks.
+TEST_F(HostResolverManagerDnsTest,
+       DisableInsecureDnsClient_SecureDnsTasksUnaffected) {
+  // Ensure fallback is otherwise allowed by resolver settings.
+  set_allow_fallback_to_proctask(true);
+
+  proc_->AddRuleForAllFamilies("automatic", "192.168.1.102");
+  // All other hostnames will fail in proc_.
+
+  ChangeDnsConfig(CreateValidDnsConfig());
+
+  HostResolver::ResolveHostParameters secure_parameters;
+  secure_parameters.secure_dns_mode_override =
+      DnsConfig::SecureDnsMode::AUTOMATIC;
+  ResolveHostResponseHelper response_secure(resolver_->CreateRequest(
+      HostPortPair("automatic", 80), NetLogWithSource(), secure_parameters,
+      request_context_.get(), host_cache_.get()));
+  EXPECT_FALSE(response_secure.complete());
+
+  // Simulate the case when the preference or policy has disabled the insecure
+  // DNS client causing AbortInsecureDnsTasks.
+  resolver_->SetInsecureDnsClientEnabled(false);
+
+  EXPECT_THAT(response_secure.result_error(), IsOk());
+  EXPECT_THAT(
+      response_secure.request()->GetAddressResults().value().endpoints(),
+      testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 80),
+                                    CreateExpected("::1", 80)));
+}
+
 TEST_F(HostResolverManagerDnsTest, DnsTaskUnspec) {
   ChangeDnsConfig(CreateValidDnsConfig());
 
@@ -4077,6 +4090,28 @@
               testing::ElementsAre(CreateExpected("127.0.0.1", 80)));
 }
 
+TEST_F(HostResolverManagerDnsTest, SkipHostsWithUpcomingProcTask) {
+  // Disable the DnsClient.
+  resolver_->SetInsecureDnsClientEnabled(false);
+
+  proc_->AddRuleForAllFamilies(std::string(),
+                               std::string());  // Default to failures.
+  proc_->SignalMultiple(1u);  // For the first request which misses.
+
+  DnsConfig config = CreateValidDnsConfig();
+  DnsHosts hosts;
+  hosts[DnsHostsKey("hosts", ADDRESS_FAMILY_IPV4)] = IPAddress::IPv4Localhost();
+
+  // Update HOSTS file.
+  config.hosts = hosts;
+  ChangeDnsConfig(config);
+
+  ResolveHostResponseHelper response(resolver_->CreateRequest(
+      HostPortPair("hosts", 80), NetLogWithSource(), base::nullopt,
+      request_context_.get(), host_cache_.get()));
+  EXPECT_THAT(response.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
+}
+
 // Test that hosts ending in ".local" or ".local." are resolved using the system
 // resolver.
 TEST_F(HostResolverManagerDnsTest, BypassDnsTask) {
@@ -4202,7 +4237,8 @@
   EXPECT_THAT(system_response.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
 }
 
-TEST_F(HostResolverManagerDnsTest, DisableDnsClientOnPersistentFailure) {
+TEST_F(HostResolverManagerDnsTest,
+       DisableInsecureDnsClientOnPersistentFailure) {
   ChangeDnsConfig(CreateValidDnsConfig());
 
   proc_->AddRuleForAllFamilies(std::string(),
@@ -4215,7 +4251,7 @@
   EXPECT_THAT(initial_response.result_error(), IsOk());
 
   std::vector<std::unique_ptr<ResolveHostResponseHelper>> responses;
-  for (unsigned i = 0; i < maximum_dns_failures(); ++i) {
+  for (unsigned i = 0; i < maximum_insecure_dns_task_failures(); ++i) {
     // Use custom names to require separate Jobs.
     std::string hostname = base::StringPrintf("nx_%u", i);
     // Ensure fallback to ProcTask succeeds.
@@ -4233,7 +4269,8 @@
 
   ASSERT_FALSE(proc_->HasBlockedRequests());
 
-  // DnsTask should be disabled by now unless explictly requested via |source|.
+  // Insecure DnsTasks should be disabled by now unless explicitly requested via
+  // |source|.
   ResolveHostResponseHelper fail_response(resolver_->CreateRequest(
       HostPortPair("ok_2", 80), NetLogWithSource(), base::nullopt,
       request_context_.get(), host_cache_.get()));
@@ -4246,6 +4283,15 @@
   EXPECT_THAT(fail_response.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
   EXPECT_THAT(dns_response.result_error(), IsOk());
 
+  // Secure DnsTasks should not be affected.
+  HostResolver::ResolveHostParameters secure_parameters;
+  secure_parameters.secure_dns_mode_override =
+      DnsConfig::SecureDnsMode::AUTOMATIC;
+  ResolveHostResponseHelper secure_response(resolver_->CreateRequest(
+      HostPortPair("automatic", 80), NetLogWithSource(), secure_parameters,
+      request_context_.get(), host_cache_.get()));
+  EXPECT_THAT(secure_response.result_error(), IsOk());
+
   // Check that it is re-enabled after DNS change.
   ChangeDnsConfig(CreateValidDnsConfig());
   ResolveHostResponseHelper reenabled_response(resolver_->CreateRequest(
@@ -4298,7 +4344,7 @@
   proc_->AddRuleForAllFamilies(std::string(), std::string());
 
   // Try without DnsClient.
-  resolver_->SetDnsClientEnabled(false);
+  resolver_->SetInsecureDnsClientEnabled(false);
   ResolveHostResponseHelper system_response(resolver_->CreateRequest(
       HostPortPair("localhost", 80), NetLogWithSource(), base::nullopt,
       request_context_.get(), host_cache_.get()));
@@ -4334,6 +4380,63 @@
                                     CreateExpected("::1", 80)));
 }
 
+TEST_F(HostResolverManagerDnsTest, SeparateJobsBySecureDnsMode) {
+  MockDnsClientRuleList rules;
+  rules.emplace_back("a", dns_protocol::kTypeA, true /* secure */,
+                     MockDnsClientRule::Result(MockDnsClientRule::FAIL),
+                     true /* delay */);
+  rules.emplace_back("a", dns_protocol::kTypeAAAA, true /* secure */,
+                     MockDnsClientRule::Result(MockDnsClientRule::FAIL),
+                     true /* delay */);
+  rules.emplace_back("a", dns_protocol::kTypeA, false /* secure */,
+                     MockDnsClientRule::Result(MockDnsClientRule::OK),
+                     false /* delay */);
+  rules.emplace_back("a", dns_protocol::kTypeAAAA, false /* secure */,
+                     MockDnsClientRule::Result(MockDnsClientRule::OK),
+                     false /* delay */);
+  UseMockDnsClient(CreateValidDnsConfig(), std::move(rules));
+  DnsConfigOverrides overrides;
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::AUTOMATIC;
+  resolver_->SetDnsConfigOverrides(overrides);
+
+  // Create three requests. One with a SECURE mode override, one with no
+  // mode override, and one with an AUTOMATIC mode override (which is a no-op
+  // since the DnsConfig uses AUTOMATIC).
+  HostResolver::ResolveHostParameters parameters_secure_override;
+  parameters_secure_override.secure_dns_mode_override =
+      DnsConfig::SecureDnsMode::SECURE;
+  ResolveHostResponseHelper secure_response(resolver_->CreateRequest(
+      HostPortPair("a", 80), NetLogWithSource(), parameters_secure_override,
+      request_context_.get(), host_cache_.get()));
+  EXPECT_EQ(1u, resolver_->num_jobs_for_testing());
+
+  ResolveHostResponseHelper automatic_response0(resolver_->CreateRequest(
+      HostPortPair("a", 80), NetLogWithSource(), base::nullopt,
+      request_context_.get(), host_cache_.get()));
+  EXPECT_EQ(2u, resolver_->num_jobs_for_testing());
+
+  HostResolver::ResolveHostParameters parameters_automatic_override;
+  parameters_automatic_override.secure_dns_mode_override =
+      DnsConfig::SecureDnsMode::AUTOMATIC;
+  ResolveHostResponseHelper automatic_response1(resolver_->CreateRequest(
+      HostPortPair("a", 80), NetLogWithSource(), parameters_automatic_override,
+      request_context_.get(), host_cache_.get()));
+  // The AUTOMATIC mode requests should be joined into the same job.
+  EXPECT_EQ(2u, resolver_->num_jobs_for_testing());
+
+  // All requests should be blocked on the secure transactions.
+  base::RunLoop().RunUntilIdle();
+  EXPECT_FALSE(secure_response.complete());
+  EXPECT_FALSE(automatic_response0.complete());
+  EXPECT_FALSE(automatic_response1.complete());
+
+  // Complete secure transactions.
+  dns_client_->CompleteDelayedTransactions();
+  EXPECT_THAT(secure_response.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
+  EXPECT_THAT(automatic_response0.result_error(), IsOk());
+  EXPECT_THAT(automatic_response1.result_error(), IsOk());
+}
+
 // Cancel a request with a single DNS transaction active.
 TEST_F(HostResolverManagerDnsTest, CancelWithOneTransactionActive) {
   // Disable ipv6 to ensure we'll only try a single transaction for the host.
@@ -4862,18 +4965,73 @@
   EXPECT_TRUE(response_stale.request()->GetStaleInfo()->is_stale());
 }
 
+TEST_F(HostResolverManagerDnsTest,
+       SecureDnsMode_Automatic_InsecureAsyncDisabled) {
+  proc_->AddRuleForAllFamilies("insecure_automatic", "192.168.1.100");
+  ChangeDnsConfig(CreateValidDnsConfig());
+  resolver_->SetInsecureDnsClientEnabled(false);
+  DnsConfigOverrides overrides;
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::AUTOMATIC;
+  resolver_->SetDnsConfigOverrides(overrides);
+
+  const std::pair<const HostCache::Key, HostCache::Entry>* cache_result;
+
+  // The secure part of the dns client should be enabled.
+  ResolveHostResponseHelper response_secure(resolver_->CreateRequest(
+      HostPortPair("automatic", 80), NetLogWithSource(), base::nullopt,
+      request_context_.get(), host_cache_.get()));
+  ASSERT_THAT(response_secure.result_error(), IsOk());
+  EXPECT_THAT(
+      response_secure.request()->GetAddressResults().value().endpoints(),
+      testing::UnorderedElementsAre(CreateExpected("127.0.0.1", 80),
+                                    CreateExpected("::1", 80)));
+  HostCache::Key secure_key =
+      HostCache::Key("automatic", DnsQueryType::UNSPECIFIED,
+                     0 /* host_resolver_flags */, HostResolverSource::ANY);
+  secure_key.secure = true;
+  cache_result = GetCacheHit(secure_key);
+  EXPECT_TRUE(!!cache_result);
+
+  // The insecure part of the dns client is disabled so insecure requests
+  // should be skipped.
+  ResolveHostResponseHelper response_insecure(resolver_->CreateRequest(
+      HostPortPair("insecure_automatic", 80), NetLogWithSource(), base::nullopt,
+      request_context_.get(), host_cache_.get()));
+  proc_->SignalMultiple(1u);
+  ASSERT_THAT(response_insecure.result_error(), IsOk());
+  EXPECT_THAT(
+      response_insecure.request()->GetAddressResults().value().endpoints(),
+      testing::ElementsAre(CreateExpected("192.168.1.100", 80)));
+  HostCache::Key insecure_key =
+      HostCache::Key("insecure_automatic", DnsQueryType::UNSPECIFIED,
+                     0 /* host_resolver_flags */, HostResolverSource::ANY);
+  cache_result = GetCacheHit(insecure_key);
+  EXPECT_TRUE(!!cache_result);
+
+  HostCache::Key cached_insecure_key =
+      HostCache::Key("insecure_automatic_cached", DnsQueryType::UNSPECIFIED,
+                     0 /* host_resolver_flags */, HostResolverSource::ANY);
+  IPEndPoint kExpectedInsecureIP = CreateExpected("192.168.1.101", 80);
+  PopulateCache(cached_insecure_key, kExpectedInsecureIP);
+
+  // The insecure cache should still be checked even if the insecure part of
+  // the dns client is disabled.
+  ResolveHostResponseHelper response_insecure_cached(resolver_->CreateRequest(
+      HostPortPair("insecure_automatic_cached", 80), NetLogWithSource(),
+      base::nullopt, request_context_.get(), host_cache_.get()));
+  EXPECT_THAT(response_insecure_cached.result_error(), IsOk());
+  EXPECT_THAT(response_insecure_cached.request()
+                  ->GetAddressResults()
+                  .value()
+                  .endpoints(),
+              testing::ElementsAre(kExpectedInsecureIP));
+}
+
 TEST_F(HostResolverManagerDnsTest, SecureDnsMode_Secure) {
   proc_->AddRuleForAllFamilies("nx_succeed", "192.168.1.100");
   set_allow_fallback_to_proctask(true);
 
-  MockDnsClientRuleList rules;
-  rules.emplace_back("secure", dns_protocol::kTypeA, true /* secure */,
-                     MockDnsClientRule::Result(MockDnsClientRule::OK),
-                     false /* delay */);
-  rules.emplace_back("secure", dns_protocol::kTypeAAAA, true /* secure */,
-                     MockDnsClientRule::Result(MockDnsClientRule::OK),
-                     false /* delay */);
-  UseMockDnsClient(CreateValidDnsConfig(), std::move(rules));
+  ChangeDnsConfig(CreateValidDnsConfig());
   DnsConfigOverrides overrides;
   overrides.secure_dns_mode = DnsConfig::SecureDnsMode::SECURE;
   resolver_->SetDnsConfigOverrides(overrides);
@@ -4908,6 +5066,30 @@
   EXPECT_THAT(response_proc.result_error(), IsError(ERR_NAME_NOT_RESOLVED));
 }
 
+TEST_F(HostResolverManagerDnsTest, SecureDnsMode_Secure_InsecureAsyncDisabled) {
+  proc_->AddRuleForAllFamilies("nx_succeed", "192.168.1.100");
+  set_allow_fallback_to_proctask(true);
+  resolver_->SetInsecureDnsClientEnabled(false);
+
+  ChangeDnsConfig(CreateValidDnsConfig());
+  DnsConfigOverrides overrides;
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::SECURE;
+  resolver_->SetDnsConfigOverrides(overrides);
+  const std::pair<const HostCache::Key, HostCache::Entry>* cache_result;
+
+  // The secure part of the dns client should be enabled.
+  ResolveHostResponseHelper response_secure(resolver_->CreateRequest(
+      HostPortPair("secure", 80), NetLogWithSource(), base::nullopt,
+      request_context_.get(), host_cache_.get()));
+  ASSERT_THAT(response_secure.result_error(), IsOk());
+  HostCache::Key secure_key =
+      HostCache::Key("secure", DnsQueryType::UNSPECIFIED,
+                     0 /* host_resolver_flags */, HostResolverSource::ANY);
+  secure_key.secure = true;
+  cache_result = GetCacheHit(secure_key);
+  EXPECT_TRUE(!!cache_result);
+}
+
 // Test the case where only a single transaction slot is available.
 TEST_F(HostResolverManagerDnsTest, SerialResolver) {
   CreateSerialResolver();
@@ -5090,10 +5272,10 @@
   EXPECT_THAT(response.result_error(), IsOk());
 }
 
-// Tests the case that DnsClient is automatically disabled due to failures
-// while there are active DnsTasks.
+// Tests the case that the insecure part of the DnsClient is automatically
+// disabled due to failures while there are active DnsTasks.
 TEST_F(HostResolverManagerDnsTest,
-       AutomaticallyDisableDnsClientWithPendingRequests) {
+       AutomaticallyDisableInsecureDnsClientWithPendingRequests) {
   // Trying different limits is important for this test:  Different limits
   // result in different behavior when aborting in-progress DnsTasks.  Having
   // a DnsTask that has one job active and one in the queue when another job
@@ -5106,10 +5288,10 @@
 
     ChangeDnsConfig(CreateValidDnsConfig());
 
-    // Queue up enough failures to disable DnsTasks.  These will all fall back
-    // to ProcTasks, and succeed there.
+    // Queue up enough failures to disable insecure DnsTasks.  These will all
+    // fall back to ProcTasks, and succeed there.
     std::vector<std::unique_ptr<ResolveHostResponseHelper>> failure_responses;
-    for (unsigned i = 0u; i < maximum_dns_failures(); ++i) {
+    for (unsigned i = 0u; i < maximum_insecure_dns_task_failures(); ++i) {
       std::string host = base::StringPrintf("nx%u", i);
       proc_->AddRuleForAllFamilies(host, "192.168.0.1");
       failure_responses.emplace_back(
@@ -5119,8 +5301,8 @@
       EXPECT_FALSE(failure_responses[i]->complete());
     }
 
-    // These requests should all bypass DnsTasks, due to the above failures,
-    // so should end up using ProcTasks.
+    // These requests should all bypass insecure DnsTasks, due to the above
+    // failures, so should end up using ProcTasks.
     proc_->AddRuleForAllFamilies("slow_ok1", "192.168.0.2");
     ResolveHostResponseHelper response0(resolver_->CreateRequest(
         HostPortPair("slow_ok1", 80), NetLogWithSource(), base::nullopt,
@@ -5155,9 +5337,18 @@
         request_context_.get(), host_cache_.get()));
     EXPECT_FALSE(response_system.complete());
 
-    proc_->SignalMultiple(maximum_dns_failures() + 5);
+    // Secure DnsTasks should not be affected.
+    HostResolver::ResolveHostParameters secure_parameters;
+    secure_parameters.secure_dns_mode_override =
+        DnsConfig::SecureDnsMode::AUTOMATIC;
+    ResolveHostResponseHelper response_secure(resolver_->CreateRequest(
+        HostPortPair("automatic", 80), NetLogWithSource(), secure_parameters,
+        request_context_.get(), host_cache_.get()));
+    EXPECT_FALSE(response_secure.complete());
 
-    for (size_t i = 0u; i < maximum_dns_failures(); ++i) {
+    proc_->SignalMultiple(maximum_insecure_dns_task_failures() + 6);
+
+    for (size_t i = 0u; i < maximum_insecure_dns_task_failures(); ++i) {
       EXPECT_THAT(failure_responses[i]->result_error(), IsOk());
       EXPECT_THAT(failure_responses[i]
                       ->request()
@@ -5184,6 +5375,8 @@
     EXPECT_THAT(
         response_system.request()->GetAddressResults().value().endpoints(),
         testing::ElementsAre(CreateExpected("192.168.0.5", 80)));
+
+    EXPECT_THAT(response_secure.result_error(), IsOk());
   }
 }
 
@@ -5228,7 +5421,7 @@
 
   // Clear DnsClient.  The two in-progress jobs should fall back to a ProcTask,
   // and the next one should be started with a ProcTask.
-  resolver_->SetDnsClientEnabled(false);
+  resolver_->SetInsecureDnsClientEnabled(false);
 
   // All three in-progress requests should now be running a ProcTask.
   EXPECT_EQ(3u, num_running_dispatcher_jobs());
@@ -5249,7 +5442,7 @@
 // DnsClient disabled should result in an error.
 TEST_F(HostResolverManagerDnsTest, DnsCallsWithDisabledDnsClient) {
   ChangeDnsConfig(CreateValidDnsConfig());
-  resolver_->SetDnsClientEnabled(false);
+  resolver_->SetInsecureDnsClientEnabled(false);
 
   HostResolver::ResolveHostParameters params;
   params.source = HostResolverSource::DNS;
@@ -5257,13 +5450,13 @@
       HostPortPair("host", 80), NetLogWithSource(), params,
       request_context_.get(), host_cache_.get()));
 
-  EXPECT_THAT(response.result_error(), IsError(ERR_FAILED));
+  EXPECT_THAT(response.result_error(), IsError(ERR_DNS_CACHE_MISS));
 }
 
 TEST_F(HostResolverManagerDnsTest,
        DnsCallsWithDisabledDnsClient_DisabledAtConstruction) {
   HostResolver::ManagerOptions options = DefaultOptions();
-  options.dns_client_enabled = false;
+  options.insecure_dns_client_enabled = false;
   CreateResolverWithOptionsAndParams(std::move(options),
                                      DefaultParams(proc_.get()),
                                      true /* ipv6_reachable */);
@@ -5275,7 +5468,7 @@
       HostPortPair("host", 80), NetLogWithSource(), params,
       request_context_.get(), host_cache_.get()));
 
-  EXPECT_THAT(response.result_error(), IsError(ERR_FAILED));
+  EXPECT_THAT(response.result_error(), IsError(ERR_DNS_CACHE_MISS));
 }
 
 // Same as DnsClient disabled, requests with source=DNS and no usable DnsConfig
@@ -5289,7 +5482,7 @@
       HostPortPair("host", 80), NetLogWithSource(), params,
       request_context_.get(), host_cache_.get()));
 
-  EXPECT_THAT(response.result_error(), IsError(ERR_FAILED));
+  EXPECT_THAT(response.result_error(), IsError(ERR_DNS_CACHE_MISS));
 }
 
 TEST_F(HostResolverManagerDnsTest, NoCheckIpv6OnWifi) {
@@ -5741,6 +5934,7 @@
   DnsConfigOverrides overrides;
   overrides.dns_over_https_servers.emplace(
       {DnsConfig::DnsOverHttpsServerConfig(server, true)});
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::AUTOMATIC;
   resolver_->SetDnsConfigOverrides(overrides);
   base::DictionaryValue* config;
 
@@ -5763,6 +5957,8 @@
   std::string server_template;
   EXPECT_TRUE(server_method->GetString("server_template", &server_template));
   EXPECT_EQ(server_template, server);
+  EXPECT_EQ(config->FindKey("secure_dns_mode")->GetInt(),
+            static_cast<int>(DnsConfig::SecureDnsMode::AUTOMATIC));
 }
 
 TEST_F(HostResolverManagerDnsTest, AddDnsOverHttpsServerBeforeConfig) {
@@ -5773,6 +5969,7 @@
   DnsConfigOverrides overrides;
   overrides.dns_over_https_servers.emplace(
       {DnsConfig::DnsOverHttpsServerConfig(server, true)});
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::AUTOMATIC;
   resolver_->SetDnsConfigOverrides(overrides);
 
   notifier.mock_network_change_notifier()->SetConnectionType(
@@ -5799,6 +5996,8 @@
   std::string server_template;
   EXPECT_TRUE(server_method->GetString("server_template", &server_template));
   EXPECT_EQ(server_template, server);
+  EXPECT_EQ(config->FindKey("secure_dns_mode")->GetInt(),
+            static_cast<int>(DnsConfig::SecureDnsMode::AUTOMATIC));
 }
 
 TEST_F(HostResolverManagerDnsTest, AddDnsOverHttpsServerBeforeClient) {
@@ -5809,6 +6008,7 @@
   DnsConfigOverrides overrides;
   overrides.dns_over_https_servers.emplace(
       {DnsConfig::DnsOverHttpsServerConfig(server, true)});
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::AUTOMATIC;
   resolver_->SetDnsConfigOverrides(overrides);
 
   notifier.mock_network_change_notifier()->SetConnectionType(
@@ -5835,6 +6035,8 @@
   std::string server_template;
   EXPECT_TRUE(server_method->GetString("server_template", &server_template));
   EXPECT_EQ(server_template, server);
+  EXPECT_EQ(config->FindKey("secure_dns_mode")->GetInt(),
+            static_cast<int>(DnsConfig::SecureDnsMode::AUTOMATIC));
 }
 
 TEST_F(HostResolverManagerDnsTest, AddDnsOverHttpsServerAndThenRemove) {
@@ -5845,6 +6047,7 @@
   DnsConfigOverrides overrides;
   overrides.dns_over_https_servers.emplace(
       {DnsConfig::DnsOverHttpsServerConfig(server, true)});
+  overrides.secure_dns_mode = DnsConfig::SecureDnsMode::AUTOMATIC;
   resolver_->SetDnsConfigOverrides(overrides);
 
   notifier.mock_network_change_notifier()->SetConnectionType(
@@ -5873,6 +6076,8 @@
   std::string server_template;
   EXPECT_TRUE(server_method->GetString("server_template", &server_template));
   EXPECT_EQ(server_template, server);
+  EXPECT_EQ(config->FindKey("secure_dns_mode")->GetInt(),
+            static_cast<int>(DnsConfig::SecureDnsMode::AUTOMATIC));
 
   resolver_->SetDnsConfigOverrides(DnsConfigOverrides());
   value = resolver_->GetDnsConfigAsValue();
@@ -5885,6 +6090,8 @@
   if (!doh_servers)
     return;
   EXPECT_EQ(doh_servers->GetSize(), 0u);
+  EXPECT_EQ(config->FindKey("secure_dns_mode")->GetInt(),
+            static_cast<int>(DnsConfig::SecureDnsMode::OFF));
 }
 
 TEST_F(HostResolverManagerDnsTest, SetDnsConfigOverrides) {
@@ -6217,7 +6424,7 @@
   }
 
   // Test system resolver is detected.
-  resolver_->SetDnsClientEnabled(false);
+  resolver_->SetInsecureDnsClientEnabled(false);
   ChangeDnsConfig(CreateValidDnsConfig());
   EXPECT_EQ(resolver_->mode_for_histogram_,
             HostResolverManager::MODE_FOR_HISTOGRAM_SYSTEM);
@@ -6274,6 +6481,20 @@
                                        bar_records.begin(), bar_records.end()));
 }
 
+TEST_F(HostResolverManagerDnsTest, TxtQuery_InvalidConfig) {
+  set_allow_fallback_to_proctask(false);
+  // Set empty DnsConfig.
+  ChangeDnsConfig(DnsConfig());
+
+  HostResolver::ResolveHostParameters parameters;
+  parameters.dns_query_type = DnsQueryType::TXT;
+
+  ResolveHostResponseHelper response(resolver_->CreateRequest(
+      HostPortPair("host", 108), NetLogWithSource(), parameters,
+      request_context_.get(), host_cache_.get()));
+  EXPECT_THAT(response.result_error(), IsError(ERR_DNS_CACHE_MISS));
+}
+
 TEST_F(HostResolverManagerDnsTest, TxtQuery_NonexistentDomain) {
   // Setup fallback to confirm it is not used for non-address results.
   set_allow_fallback_to_proctask(true);
diff --git a/net/http/http_auth_gssapi_posix.cc b/net/http/http_auth_gssapi_posix.cc
index 4428629..f100b62 100644
--- a/net/http/http_auth_gssapi_posix.cc
+++ b/net/http/http_auth_gssapi_posix.cc
@@ -18,10 +18,13 @@
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/threading/thread_restrictions.h"
-#include "net/base/hex_utils.h"
+#include "base/values.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_auth_gssapi_posix.h"
 #include "net/http/http_auth_multi_round_parse.h"
+#include "net/log/net_log_event_type.h"
+#include "net/log/net_log_values.h"
+#include "net/log/net_log_with_source.h"
 #include "net/net_buildflags.h"
 
 namespace net {
@@ -39,9 +42,6 @@
 gss_OID CHROME_GSS_SPNEGO_MECH_OID_DESC =
     &CHROME_GSS_SPNEGO_MECH_OID_DESC_VAL;
 
-// Debugging helpers.
-namespace {
-
 OM_uint32 DelegationTypeToFlag(DelegationType delegation_type) {
   switch (delegation_type) {
     case DelegationType::kNone:
@@ -66,10 +66,9 @@
       OM_uint32 minor_status = 0;
       OM_uint32 major_status =
           gssapi_lib_->release_buffer(&minor_status, buffer_);
-      if (major_status != GSS_S_COMPLETE) {
-        DLOG(WARNING) << "Problem releasing buffer. major=" << major_status
-                      << ", minor=" << minor_status;
-      }
+      DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
+          << "Problem releasing buffer. major=" << major_status
+          << ", minor=" << minor_status;
       buffer_ = GSS_C_NO_BUFFER;
     }
   }
@@ -94,9 +93,10 @@
       OM_uint32 minor_status = 0;
       OM_uint32 major_status = gssapi_lib_->release_name(&minor_status, &name_);
       if (major_status != GSS_S_COMPLETE) {
-        DLOG(WARNING) << "Problem releasing name. "
-                      << GetGssStatusValue(nullptr, "gss_release_name",
-                                           major_status, minor_status);
+        DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
+            << "Problem releasing name. "
+            << GetGssStatusValue(nullptr, "gss_release_name", major_status,
+                                 minor_status);
       }
       name_ = GSS_C_NO_NAME;
     }
@@ -115,8 +115,6 @@
   return 0 == memcmp(left->elements, right->elements, right->length);
 }
 
-}  // namespace
-
 base::Value GetGssStatusCodeValue(GSSAPILibrary* gssapi_lib,
                                   OM_uint32 status,
                                   OM_uint32 status_code_type) {
@@ -205,10 +203,9 @@
 
   // Cap OID content at arbitrary limit 1k.
   constexpr OM_uint32 kMaxOidDataSize = 1024;
-  const base::StringPiece oid_contents(reinterpret_cast<char*>(oid->elements),
-                                       std::min(kMaxOidDataSize, oid->length));
-
-  params.SetStringKey("bytes", HexDump(oid_contents));
+  params.SetKey(
+      "bytes",
+      NetLogBinaryValue(oid->elements, std::min(kMaxOidDataSize, oid->length)));
 
   // Based on RFC 2744 Appendix A. Hardcoding the OIDs in the list below to
   // avoid having a static dependency on the library.
@@ -256,9 +253,9 @@
   }
   auto name_string =
       base::StringPiece(reinterpret_cast<const char*>(name.value), name.length);
-  rv.SetStringKey("name", base::IsStringUTF8(name_string)
-                              ? name_string
-                              : HexDump(name_string));
+  rv.SetKey("name", base::IsStringUTF8(name_string)
+                        ? NetLogStringValue(name_string)
+                        : NetLogBinaryValue(name.value, name.length));
   rv.SetKey("type", OidToValue(name_type));
   return rv;
 }
@@ -315,6 +312,20 @@
   return rv;
 }
 
+namespace {
+
+// NetLogParametersCallback for the result of loading a library.
+base::Value LibraryLoadResultCallback(base::StringPiece library_name,
+                                      base::StringPiece load_result) {
+  base::Value params{base::Value::Type::DICTIONARY};
+  params.SetStringKey("library_name", library_name);
+  if (!load_result.empty())
+    params.SetStringKey("load_result", load_result);
+  return params;
+}
+
+}  // namespace
+
 GSSAPISharedLibrary::GSSAPISharedLibrary(const std::string& gssapi_library_name)
     : gssapi_library_name_(gssapi_library_name) {}
 
@@ -325,24 +336,23 @@
   }
 }
 
-bool GSSAPISharedLibrary::Init() {
+bool GSSAPISharedLibrary::Init(const NetLogWithSource& net_log) {
   if (!initialized_)
-    InitImpl();
+    InitImpl(net_log);
   return initialized_;
 }
 
-bool GSSAPISharedLibrary::InitImpl() {
+bool GSSAPISharedLibrary::InitImpl(const NetLogWithSource& net_log) {
   DCHECK(!initialized_);
-#if BUILDFLAG(DLOPEN_KERBEROS)
-  gssapi_library_ = LoadSharedLibrary();
+  gssapi_library_ = LoadSharedLibrary(net_log);
   if (gssapi_library_ == nullptr)
     return false;
-#endif  // BUILDFLAG(DLOPEN_KERBEROS)
   initialized_ = true;
   return true;
 }
 
-base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary() {
+base::NativeLibrary GSSAPISharedLibrary::LoadSharedLibrary(
+    const NetLogWithSource& net_log) {
   const char* const* library_names;
   size_t num_lib_names;
   const char* user_specified_library[1];
@@ -367,64 +377,104 @@
     num_lib_names = base::size(kDefaultLibraryNames);
   }
 
+  net_log.BeginEvent(NetLogEventType::AUTH_LIBRARY_LOAD);
+
+  // There has to be at least one candidate.
+  DCHECK_NE(0u, num_lib_names);
+
+  const char* library_name = nullptr;
+  base::NativeLibraryLoadError load_error;
+
   for (size_t i = 0; i < num_lib_names; ++i) {
-    const char* library_name = library_names[i];
+    load_error = base::NativeLibraryLoadError();
+    library_name = library_names[i];
     base::FilePath file_path(library_name);
 
     // TODO(asanka): Move library loading to a separate thread.
     //               http://crbug.com/66702
     base::ThreadRestrictions::ScopedAllowIO allow_io_temporarily;
-    base::NativeLibraryLoadError load_error;
     base::NativeLibrary lib = base::LoadNativeLibrary(file_path, &load_error);
     if (lib) {
-      // Only return this library if we can bind the functions we need.
-      if (BindMethods(lib))
+      if (BindMethods(lib, library_name, net_log)) {
+        net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_LOAD, [&] {
+          return LibraryLoadResultCallback(library_name, "");
+        });
         return lib;
+      }
       base::UnloadNativeLibrary(lib);
-    } else {
-      // If this is the only library available, log the reason for failure.
-      DLOG_IF(WARNING, num_lib_names == 1) << load_error.ToString();
     }
   }
-  DLOG(WARNING) << "Unable to find a compatible GSSAPI library";
+
+  // If loading failed, then log the result of the final attempt. Doing so
+  // is specially important on platforms where there's only one possible
+  // library. Doing so also always logs the failure when the GSSAPI library
+  // name is explicitly specified.
+  net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_LOAD, [&] {
+    return LibraryLoadResultCallback(library_name, load_error.ToString());
+  });
   return nullptr;
 }
 
-#if BUILDFLAG(DLOPEN_KERBEROS)
-
 namespace {
 
-template <typename T>
-bool BindGssMethod(base::NativeLibrary lib, const char* method, T* receiver) {
-  *receiver = reinterpret_cast<T>(
-      base::GetFunctionPointerFromNativeLibrary(lib, method));
-  if (*receiver == nullptr) {
-    DLOG(WARNING) << "Unable to bind function \"" << method << "\"";
-    return false;
+base::Value BindFailureCallback(base::StringPiece library_name,
+                                base::StringPiece method) {
+  base::Value params{base::Value::Type::DICTIONARY};
+  params.SetStringKey("library_name", library_name);
+  params.SetStringKey("method", method);
+  return params;
+}
+
+void* BindUntypedMethod(base::NativeLibrary lib,
+                        base::StringPiece library_name,
+                        base::StringPiece method,
+                        const NetLogWithSource& net_log) {
+  void* ptr = base::GetFunctionPointerFromNativeLibrary(lib, method);
+  if (ptr == nullptr) {
+    std::string method_string = method.as_string();
+    net_log.AddEvent(NetLogEventType::AUTH_LIBRARY_BIND_FAILED,
+                     [&] { return BindFailureCallback(library_name, method); });
   }
-  return true;
+  return ptr;
+}
+
+template <typename T>
+bool BindMethod(base::NativeLibrary lib,
+                base::StringPiece library_name,
+                base::StringPiece method,
+                T* receiver,
+                const NetLogWithSource& net_log) {
+  *receiver = reinterpret_cast<T>(
+      BindUntypedMethod(lib, library_name, method, net_log));
+  return *receiver != nullptr;
 }
 
 }  // namespace
 
-bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) {
-  bool rv = true;
-  // It's unlikely for BindMethods() to fail if LoadNativeLibrary() succeeded.
-  // A failure in this function indicates an interoperability issue whose
-  // diagnosis requires knowing all the methods that are missing. Hence |rv| is
+bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib,
+                                      base::StringPiece name,
+                                      const NetLogWithSource& net_log) {
+  bool ok = true;
+  // It's unlikely for BindMethods() to fail if LoadNativeLibrary() succeeded. A
+  // failure in this function indicates an interoperability issue whose
+  // diagnosis requires knowing all the methods that are missing. Hence |ok| is
   // updated in a manner that prevents short-circuiting the BindGssMethod()
   // invocations.
-  rv = BindGssMethod(lib, "gss_delete_sec_context", &delete_sec_context_) && rv;
-  rv = BindGssMethod(lib, "gss_display_name", &display_name_) && rv;
-  rv = BindGssMethod(lib, "gss_display_status", &display_status_) && rv;
-  rv = BindGssMethod(lib, "gss_import_name", &import_name_) && rv;
-  rv = BindGssMethod(lib, "gss_init_sec_context", &init_sec_context_) && rv;
-  rv = BindGssMethod(lib, "gss_inquire_context", &inquire_context_) && rv;
-  rv = BindGssMethod(lib, "gss_release_buffer", &release_buffer_) && rv;
-  rv = BindGssMethod(lib, "gss_release_name", &release_name_) && rv;
-  rv = BindGssMethod(lib, "gss_wrap_size_limit", &wrap_size_limit_) && rv;
+  ok &= BindMethod(lib, name, "gss_delete_sec_context", &delete_sec_context_,
+                   net_log);
+  ok &= BindMethod(lib, name, "gss_display_name", &display_name_, net_log);
+  ok &= BindMethod(lib, name, "gss_display_status", &display_status_, net_log);
+  ok &= BindMethod(lib, name, "gss_import_name", &import_name_, net_log);
+  ok &= BindMethod(lib, name, "gss_init_sec_context", &init_sec_context_,
+                   net_log);
+  ok &=
+      BindMethod(lib, name, "gss_inquire_context", &inquire_context_, net_log);
+  ok &= BindMethod(lib, name, "gss_release_buffer", &release_buffer_, net_log);
+  ok &= BindMethod(lib, name, "gss_release_name", &release_name_, net_log);
+  ok &=
+      BindMethod(lib, name, "gss_wrap_size_limit", &wrap_size_limit_, net_log);
 
-  if (LIKELY(rv))
+  if (LIKELY(ok))
     return true;
 
   delete_sec_context_ = nullptr;
@@ -439,24 +489,6 @@
   return false;
 }
 
-#else  // DLOPEN_KERBEROS
-
-bool GSSAPISharedLibrary::BindMethods(base::NativeLibrary lib) {
-  // When not using dlopen(), statically bind to libgssapi methods.
-  import_name_ = gss_import_name;
-  release_name_ = gss_release_name;
-  release_buffer_ = gss_release_buffer;
-  display_name_ = gss_display_name;
-  display_status_ = gss_display_status;
-  init_sec_context_ = gss_init_sec_context;
-  wrap_size_limit_ = gss_wrap_size_limit;
-  delete_sec_context_ = gss_delete_sec_context;
-  inquire_context_ = gss_inquire_context;
-  return true;
-}
-
-#endif  // DLOPEN_KERBEROS
-
 OM_uint32 GSSAPISharedLibrary::import_name(
     OM_uint32* minor_status,
     const gss_buffer_t input_name_buffer,
@@ -603,11 +635,10 @@
     OM_uint32 minor_status = 0;
     OM_uint32 major_status = gssapi_lib_->delete_sec_context(
         &minor_status, &security_context_, &output_token);
-    if (major_status != GSS_S_COMPLETE) {
-      LOG(WARNING) << "Problem releasing security_context. "
-                   << GetGssStatusValue(gssapi_lib_, "gss_delete_sec_context",
-                                        major_status, minor_status);
-    }
+    DLOG_IF(WARNING, major_status != GSS_S_COMPLETE)
+        << "Problem releasing security_context. "
+        << GetGssStatusValue(gssapi_lib_, "delete_sec_context", major_status,
+                             minor_status);
     security_context_ = GSS_C_NO_CONTEXT;
   }
 }
@@ -624,10 +655,10 @@
 
 HttpAuthGSSAPI::~HttpAuthGSSAPI() = default;
 
-bool HttpAuthGSSAPI::Init() {
+bool HttpAuthGSSAPI::Init(const NetLogWithSource& net_log) {
   if (!library_)
     return false;
-  return library_->Init();
+  return library_->Init(net_log);
 }
 
 bool HttpAuthGSSAPI::NeedsIdentity() const {
@@ -656,6 +687,7 @@
                                       const std::string& spn,
                                       const std::string& channel_bindings,
                                       std::string* auth_token,
+                                      const NetLogWithSource& net_log,
                                       CompletionOnceCallback /*callback*/) {
   DCHECK(auth_token);
 
@@ -666,8 +698,8 @@
                           : nullptr;
   gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
   ScopedBuffer scoped_output_token(&output_token, library_);
-  int rv =
-      GetNextSecurityToken(spn, channel_bindings, &input_token, &output_token);
+  int rv = GetNextSecurityToken(spn, channel_bindings, &input_token,
+                                &output_token, net_log);
   if (rv != OK)
     return rv;
 
@@ -680,7 +712,6 @@
   return OK;
 }
 
-
 namespace {
 
 // GSSAPI status codes consist of a calling error (essentially, a programmer
@@ -689,7 +720,6 @@
 // This means a simple switch on the return codes is not sufficient.
 
 int MapImportNameStatusToError(OM_uint32 major_status) {
-  VLOG(1) << "import_name returned 0x" << std::hex << major_status;
   if (major_status == GSS_S_COMPLETE)
     return OK;
   if (GSS_CALLING_ERROR(major_status) != 0)
@@ -716,7 +746,6 @@
 }
 
 int MapInitSecContextStatusToError(OM_uint32 major_status) {
-  VLOG(1) << "init_sec_context returned 0x" << std::hex << major_status;
   // Although GSS_S_CONTINUE_NEEDED is an additional bit, it seems like
   // other code just checks if major_status is equivalent to it to indicate
   // that there are no other errors included.
@@ -772,12 +801,38 @@
   return ERR_UNDOCUMENTED_SECURITY_LIBRARY_STATUS;
 }
 
+base::Value ImportNameErrorCallback(GSSAPILibrary* library,
+                                    base::StringPiece spn,
+                                    OM_uint32 major_status,
+                                    OM_uint32 minor_status) {
+  base::Value params{base::Value::Type::DICTIONARY};
+  params.SetStringKey("spn", spn);
+  if (major_status != GSS_S_COMPLETE)
+    params.SetKey("status", GetGssStatusValue(library, "import_name",
+                                              major_status, minor_status));
+  return params;
+}
+
+base::Value InitSecContextErrorCallback(GSSAPILibrary* library,
+                                        gss_ctx_id_t context,
+                                        OM_uint32 major_status,
+                                        OM_uint32 minor_status) {
+  base::Value params{base::Value::Type::DICTIONARY};
+  if (major_status != GSS_S_COMPLETE)
+    params.SetKey("status", GetGssStatusValue(library, "gss_init_sec_context",
+                                              major_status, minor_status));
+  if (context != GSS_C_NO_CONTEXT)
+    params.SetKey("context", GetContextStateAsValue(library, context));
+  return params;
+}
+
 }  // anonymous namespace
 
 int HttpAuthGSSAPI::GetNextSecurityToken(const std::string& spn,
                                          const std::string& channel_bindings,
                                          gss_buffer_t in_token,
-                                         gss_buffer_t out_token) {
+                                         gss_buffer_t out_token,
+                                         const NetLogWithSource& net_log) {
   // GSSAPI header files, to this day, require OIDs passed in as non-const
   // pointers. Rather than const casting, let's just leave this as non-const.
   // Even if the OID pointer is const, the inner |elements| pointer is still
@@ -793,38 +848,33 @@
   spn_buffer.length = spn_principal.size() + 1;
   OM_uint32 minor_status = 0;
   gss_name_t principal_name = GSS_C_NO_NAME;
+
   OM_uint32 major_status =
       library_->import_name(&minor_status, &spn_buffer,
                             &kGSS_C_NT_HOSTBASED_SERVICE, &principal_name);
+  net_log.AddEvent(NetLogEventType::AUTH_LIBRARY_IMPORT_NAME, [&] {
+    return ImportNameErrorCallback(library_, spn, major_status, minor_status);
+  });
   int rv = MapImportNameStatusToError(major_status);
-  if (rv != OK) {
-    LOG(ERROR) << "Problem importing name from "
-               << "spn \"" << spn_principal << "\"\n"
-               << GetGssStatusValue(library_, "gss_import_name", major_status,
-                                    minor_status);
+  if (rv != OK)
     return rv;
-  }
   ScopedName scoped_name(principal_name, library_);
 
   // Continue creating a security context.
-  OM_uint32 req_flags = DelegationTypeToFlag(delegation_type_);
+  net_log.BeginEvent(NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX);
   major_status = library_->init_sec_context(
       &minor_status, GSS_C_NO_CREDENTIAL, scoped_sec_context_.receive(),
-      principal_name, gss_oid_, req_flags, GSS_C_INDEFINITE,
-      GSS_C_NO_CHANNEL_BINDINGS, in_token,
+      principal_name, gss_oid_, DelegationTypeToFlag(delegation_type_),
+      GSS_C_INDEFINITE, GSS_C_NO_CHANNEL_BINDINGS, in_token,
       nullptr,  // actual_mech_type
       out_token,
       nullptr,  // ret flags
       nullptr);
-  rv = MapInitSecContextStatusToError(major_status);
-  if (rv != OK) {
-    LOG(ERROR) << "Problem initializing context. \n"
-               << GetGssStatusValue(library_, "gss_init_sec_context",
-                                    major_status, minor_status)
-               << '\n'
-               << GetContextStateAsValue(library_, scoped_sec_context_.get());
-  }
-  return rv;
+  net_log.EndEvent(NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX, [&] {
+    return InitSecContextErrorCallback(library_, scoped_sec_context_.get(),
+                                       major_status, minor_status);
+  });
+  return MapInitSecContextStatusToError(major_status);
 }
 
 }  // namespace net
diff --git a/net/http/http_auth_gssapi_posix.h b/net/http/http_auth_gssapi_posix.h
index c0966e9..c118e5b 100644
--- a/net/http/http_auth_gssapi_posix.h
+++ b/net/http/http_auth_gssapi_posix.h
@@ -43,7 +43,7 @@
   // Initializes the library, including any necessary dynamic libraries.
   // This is done separately from construction (which happens at startup time)
   // in order to delay work until the class is actually needed.
-  virtual bool Init() = 0;
+  virtual bool Init(const NetLogWithSource& net_log) = 0;
 
   // These methods match the ones in the GSSAPI library.
   virtual OM_uint32 import_name(
@@ -116,7 +116,7 @@
   ~GSSAPISharedLibrary() override;
 
   // GSSAPILibrary methods:
-  bool Init() override;
+  bool Init(const NetLogWithSource& net_log) override;
   OM_uint32 import_name(OM_uint32* minor_status,
                         const gss_buffer_t input_name_buffer,
                         const gss_OID input_name_type,
@@ -171,12 +171,14 @@
  private:
   FRIEND_TEST_ALL_PREFIXES(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup);
 
-  bool InitImpl();
+  bool InitImpl(const NetLogWithSource& net_log);
   // Finds a usable dynamic library for GSSAPI and loads it.  The criteria are:
   //   1. The library must exist.
   //   2. The library must export the functions we need.
-  base::NativeLibrary LoadSharedLibrary();
-  bool BindMethods(base::NativeLibrary lib);
+  base::NativeLibrary LoadSharedLibrary(const NetLogWithSource& net_log);
+  bool BindMethods(base::NativeLibrary lib,
+                   base::StringPiece library_name,
+                   const NetLogWithSource& net_log);
 
   bool initialized_ = false;
 
@@ -207,7 +209,7 @@
   gss_ctx_id_t* receive() { return &security_context_; }
 
  private:
-  gss_ctx_id_t security_context_;
+  gss_ctx_id_t security_context_ = GSS_C_NO_CONTEXT;
   GSSAPILibrary* gssapi_lib_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedSecurityContext);
@@ -223,7 +225,7 @@
   ~HttpAuthGSSAPI() override;
 
   // HttpNegotiateAuthSystem implementation:
-  bool Init() override;
+  bool Init(const NetLogWithSource& net_log) override;
   bool NeedsIdentity() const override;
   bool AllowsExplicitCredentials() const override;
   HttpAuth::AuthorizationResult ParseChallenge(
@@ -232,6 +234,7 @@
                         const std::string& spn,
                         const std::string& channel_bindings,
                         std::string* auth_token,
+                        const NetLogWithSource& net_log,
                         CompletionOnceCallback callback) override;
   void SetDelegation(HttpAuth::DelegationType delegation_type) override;
 
@@ -239,7 +242,8 @@
   int GetNextSecurityToken(const std::string& spn,
                            const std::string& channel_bindings,
                            gss_buffer_t in_token,
-                           gss_buffer_t out_token);
+                           gss_buffer_t out_token,
+                           const NetLogWithSource& net_log);
 
   std::string scheme_;
   gss_OID gss_oid_;
diff --git a/net/http/http_auth_gssapi_posix_unittest.cc b/net/http/http_auth_gssapi_posix_unittest.cc
index c9b277c9..be8f549 100644
--- a/net/http/http_auth_gssapi_posix_unittest.cc
+++ b/net/http/http_auth_gssapi_posix_unittest.cc
@@ -16,6 +16,9 @@
 #include "net/base/net_errors.h"
 #include "net/http/http_auth_challenge_tokenizer.h"
 #include "net/http/mock_gssapi_library_posix.h"
+#include "net/log/net_log_with_source.h"
+#include "net/log/test_net_log.h"
+#include "net/log/test_net_log_util.h"
 #include "net/net_buildflags.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -86,31 +89,74 @@
 }  // namespace
 
 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPIStartup) {
+  BoundTestNetLog log;
   // TODO(ahendrickson): Manipulate the libraries and paths to test each of the
   // libraries we expect, and also whether or not they have the interface
   // functions we want.
   std::unique_ptr<GSSAPILibrary> gssapi(new GSSAPISharedLibrary(std::string()));
   DCHECK(gssapi.get());
-  EXPECT_TRUE(gssapi.get()->Init());
+  EXPECT_TRUE(gssapi.get()->Init(log.bound()));
+
+  // Should've logged a AUTH_LIBRARY_LOAD event, but not
+  // AUTH_LIBRARY_BIND_FAILED.
+  auto entries = log.GetEntries();
+  auto offset = ExpectLogContainsSomewhere(
+      entries, 0u, NetLogEventType::AUTH_LIBRARY_LOAD, NetLogEventPhase::BEGIN);
+  offset = ExpectLogContainsSomewhereAfter(entries, offset,
+                                           NetLogEventType::AUTH_LIBRARY_LOAD,
+                                           NetLogEventPhase::END);
+  ASSERT_LT(offset, entries.size());
+
+  auto& entry = entries[offset];
+  const std::string* library_name = entry.params.FindStringKey("library_name");
+  const std::string* load_result = entry.params.FindStringPath("load_result");
+  ASSERT_TRUE(library_name);
+  EXPECT_FALSE(library_name->empty());
+  EXPECT_FALSE(load_result);  // No load_result since it succeeded.
 }
 
-#if BUILDFLAG(DLOPEN_KERBEROS)
 TEST(HttpAuthGSSAPIPOSIXTest, CustomLibraryMissing) {
+  BoundTestNetLog log;
+
   std::unique_ptr<GSSAPILibrary> gssapi(
       new GSSAPISharedLibrary("/this/library/does/not/exist"));
-  EXPECT_FALSE(gssapi.get()->Init());
+  EXPECT_FALSE(gssapi.get()->Init(log.bound()));
+
+  auto entries = log.GetEntries();
+  auto offset = ExpectLogContainsSomewhere(
+      entries, 0, NetLogEventType::AUTH_LIBRARY_LOAD, NetLogEventPhase::END);
+  ASSERT_LT(offset, entries.size());
+
+  auto& entry = entries[offset];
+  const std::string* load_result = entry.params.FindStringKey("load_result");
+  ASSERT_TRUE(load_result);
+  EXPECT_FALSE(load_result->empty());
 }
 
 TEST(HttpAuthGSSAPIPOSIXTest, CustomLibraryExists) {
+  BoundTestNetLog log;
   base::FilePath module;
   ASSERT_TRUE(base::PathService::Get(base::DIR_MODULE, &module));
   auto basename = base::GetNativeLibraryName("test_gssapi");
   module = module.AppendASCII(basename);
   auto gssapi = std::make_unique<GSSAPISharedLibrary>(module.value());
-  EXPECT_TRUE(gssapi.get()->Init());
+  EXPECT_TRUE(gssapi.get()->Init(log.bound()));
+
+  auto entries = log.GetEntries();
+  auto offset = ExpectLogContainsSomewhere(
+      entries, 0, NetLogEventType::AUTH_LIBRARY_LOAD, NetLogEventPhase::END);
+  ASSERT_LT(offset, entries.size());
+
+  auto& entry = entries[offset];
+  const std::string* load_result = entry.params.FindStringKey("load_result");
+  const std::string* library_name = entry.params.FindStringKey("library_name");
+  EXPECT_FALSE(load_result);
+  ASSERT_TRUE(library_name);
+  EXPECT_EQ(*library_name, module.AsUTF8Unsafe());
 }
 
 TEST(HttpAuthGSSAPIPOSIXTest, CustomLibraryMethodsMissing) {
+  BoundTestNetLog log;
   base::FilePath module;
   ASSERT_TRUE(base::PathService::Get(base::DIR_MODULE, &module));
   auto basename = base::GetNativeLibraryName("test_badgssapi");
@@ -125,19 +171,25 @@
   //
   // To resolve this issue, make sure that //net:test_badgssapi target in
   // //net/BUILD.gn should have an empty `deps` and an empty `libs`.
-  EXPECT_FALSE(gssapi.get()->Init());
+  EXPECT_FALSE(gssapi.get()->Init(log.bound()));
 
-  // Logs something like "gss_import_name" during loading process.
-  // TODO(asanka): Once GSSAPI library loading starts emitting NetLogs verify
-  // that the missing method is correctly identified.
+  auto entries = log.GetEntries();
+  auto offset = ExpectLogContainsSomewhere(
+      entries, 0, NetLogEventType::AUTH_LIBRARY_BIND_FAILED,
+      NetLogEventPhase::NONE);
+  ASSERT_LT(offset, entries.size());
+
+  auto& entry = entries[offset];
+  const std::string* method = entry.params.FindStringKey("method");
+  ASSERT_TRUE(method);
+  EXPECT_EQ(*method, "gss_import_name");
 }
-#endif  // DLOPEN_KERBEROS
 
 TEST(HttpAuthGSSAPIPOSIXTest, GSSAPICycle) {
   std::unique_ptr<test::MockGSSAPILibrary> mock_library(
       new test::MockGSSAPILibrary);
   DCHECK(mock_library.get());
-  mock_library->Init();
+  mock_library->Init(NetLogWithSource());
   const char kAuthResponse[] = "Mary had a little lamb";
   test::GssContextMockImpl context1(
       "localhost",                         // Source name
@@ -233,6 +285,7 @@
 }
 
 TEST(HttpAuthGSSAPITest, ParseChallenge_TwoRounds) {
+  BoundTestNetLog log;
   // The first round should just have "Negotiate", and the second round should
   // have a valid base64 token associated with it.
   test::MockGSSAPILibrary mock_library;
@@ -247,15 +300,30 @@
   // Generate an auth token and create another thing.
   EstablishInitialContext(&mock_library);
   std::string auth_token;
-  EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(
-                    nullptr, "HTTP/intranet.google.com", std::string(),
-                    &auth_token, base::BindOnce(&UnexpectedCallback)));
+  EXPECT_EQ(
+      OK, auth_gssapi.GenerateAuthToken(nullptr, "HTTP/intranet.google.com",
+                                        std::string(), &auth_token, log.bound(),
+                                        base::BindOnce(&UnexpectedCallback)));
 
   std::string second_challenge_text = "Negotiate Zm9vYmFy";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
   EXPECT_EQ(HttpAuth::AUTHORIZATION_RESULT_ACCEPT,
             auth_gssapi.ParseChallenge(&second_challenge));
+
+  auto entries = log.GetEntries();
+  auto offset = ExpectLogContainsSomewhere(
+      entries, 0, NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX,
+      NetLogEventPhase::END);
+  // There should be two of these.
+  offset = ExpectLogContainsSomewhere(
+      entries, offset, NetLogEventType::AUTH_LIBRARY_INIT_SEC_CTX,
+      NetLogEventPhase::END);
+  ASSERT_LT(offset, entries.size());
+  const std::string* source =
+      entries[offset].params.FindStringPath("context.source.name");
+  ASSERT_TRUE(source);
+  EXPECT_EQ("localhost", *source);
 }
 
 TEST(HttpAuthGSSAPITest, ParseChallenge_UnexpectedTokenFirstRound) {
@@ -285,9 +353,10 @@
 
   EstablishInitialContext(&mock_library);
   std::string auth_token;
-  EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(
-                    nullptr, "HTTP/intranet.google.com", std::string(),
-                    &auth_token, base::BindOnce(&UnexpectedCallback)));
+  EXPECT_EQ(OK,
+            auth_gssapi.GenerateAuthToken(
+                nullptr, "HTTP/intranet.google.com", std::string(), &auth_token,
+                NetLogWithSource(), base::BindOnce(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
@@ -309,9 +378,10 @@
 
   EstablishInitialContext(&mock_library);
   std::string auth_token;
-  EXPECT_EQ(OK, auth_gssapi.GenerateAuthToken(
-                    nullptr, "HTTP/intranet.google.com", std::string(),
-                    &auth_token, base::BindOnce(&UnexpectedCallback)));
+  EXPECT_EQ(OK,
+            auth_gssapi.GenerateAuthToken(
+                nullptr, "HTTP/intranet.google.com", std::string(), &auth_token,
+                NetLogWithSource(), base::BindOnce(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate =happyjoy=";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
@@ -334,7 +404,7 @@
       {
         "oid"   : "GSS_C_NT_ANONYMOUS",
         "length": 6,
-        "bytes" : "0x0000:  2b06 0105 0603                           +.....\n"
+        "bytes" : "KwYBBQYD"
       }
   )");
   ASSERT_TRUE(expected.has_value());
@@ -347,7 +417,7 @@
   auto expected = base::JSONReader::Read(R"(
       {
         "length": 6,
-        "bytes" : "0x0000:  2b06 0105 0605                           +.....\n"
+        "bytes" : "KwYBBQYF"
       }
   )");
   ASSERT_TRUE(expected.has_value());
diff --git a/net/http/http_auth_handler_factory.cc b/net/http/http_auth_handler_factory.cc
index d3242e0..9961e044 100644
--- a/net/http/http_auth_handler_factory.cc
+++ b/net/http/http_auth_handler_factory.cc
@@ -103,8 +103,7 @@
 std::unique_ptr<HttpAuthHandlerRegistryFactory>
 HttpAuthHandlerFactory::CreateDefault(
     const HttpAuthPreferences* prefs
-#if (defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)) || \
-    defined(OS_FUCHSIA)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
     ,
     const std::string& gssapi_library_name
 #endif
@@ -116,7 +115,7 @@
   std::vector<std::string> auth_types(std::begin(kDefaultAuthSchemes),
                                       std::end(kDefaultAuthSchemes));
   return HttpAuthHandlerRegistryFactory::Create(prefs, auth_types
-#if defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
                                                 ,
                                                 gssapi_library_name
 #endif
@@ -132,8 +131,7 @@
 HttpAuthHandlerRegistryFactory::Create(
     const HttpAuthPreferences* prefs,
     const std::vector<std::string>& auth_schemes
-#if (defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)) || \
-    defined(OS_FUCHSIA)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
     ,
     const std::string& gssapi_library_name
 #endif
@@ -172,11 +170,9 @@
         new HttpAuthHandlerNegotiate::Factory(negotiate_auth_system_factory);
 #if defined(OS_WIN)
     negotiate_factory->set_library(std::make_unique<SSPILibraryDefault>());
-#elif defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)
+#elif BUILDFLAG(USE_EXTERNAL_GSSAPI)
     negotiate_factory->set_library(
         std::make_unique<GSSAPISharedLibrary>(gssapi_library_name));
-#elif defined(OS_CHROMEOS)
-    negotiate_factory->set_library(std::make_unique<GSSAPISharedLibrary>(""));
 #endif
     registry_factory->RegisterSchemeFactory(kNegotiateAuthScheme,
                                             negotiate_factory);
diff --git a/net/http/http_auth_handler_factory.h b/net/http/http_auth_handler_factory.h
index d7215cc1..f4f985e3 100644
--- a/net/http/http_auth_handler_factory.h
+++ b/net/http/http_auth_handler_factory.h
@@ -141,8 +141,7 @@
   // used by the Negotiate authentication handler.
   static std::unique_ptr<HttpAuthHandlerRegistryFactory> CreateDefault(
       const HttpAuthPreferences* prefs = nullptr
-#if (defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)) || \
-    defined(OS_FUCHSIA)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
       ,
       const std::string& gssapi_library_name = ""
 #endif
@@ -204,8 +203,7 @@
   static std::unique_ptr<HttpAuthHandlerRegistryFactory> Create(
       const HttpAuthPreferences* prefs,
       const std::vector<std::string>& auth_schemes
-#if (defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)) || \
-    defined(OS_FUCHSIA)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
       ,
       const std::string& gssapi_library_name = ""
 #endif
diff --git a/net/http/http_auth_handler_negotiate.cc b/net/http/http_auth_handler_negotiate.cc
index 4ca1a27..83e8872 100644
--- a/net/http/http_auth_handler_negotiate.cc
+++ b/net/http/http_auth_handler_negotiate.cc
@@ -131,7 +131,7 @@
   if (!http_auth_preferences()->AllowGssapiLibraryLoad())
     return ERR_UNSUPPORTED_AUTH_SCHEME;
 #endif
-  if (!auth_library_->Init()) {
+  if (!auth_library_->Init(net_log)) {
     is_unsupported_ = true;
     return ERR_UNSUPPORTED_AUTH_SCHEME;
   }
@@ -185,7 +185,7 @@
 bool HttpAuthHandlerNegotiate::Init(HttpAuthChallengeTokenizer* challenge,
                                     const SSLInfo& ssl_info) {
 #if defined(OS_POSIX)
-  if (!auth_system_->Init()) {
+  if (!auth_system_->Init(net_log())) {
     VLOG(1) << "can't initialize GSSAPI library";
     return false;
   }
@@ -387,7 +387,7 @@
   next_state_ = STATE_GENERATE_AUTH_TOKEN_COMPLETE;
   AuthCredentials* credentials = has_credentials_ ? &credentials_ : nullptr;
   return auth_system_->GenerateAuthToken(
-      credentials, spn_, channel_bindings_, auth_token_,
+      credentials, spn_, channel_bindings_, auth_token_, net_log(),
       base::BindOnce(&HttpAuthHandlerNegotiate::OnIOComplete,
                      base::Unretained(this)));
 }
diff --git a/net/http/http_auth_handler_negotiate_unittest.cc b/net/http/http_auth_handler_negotiate_unittest.cc
index 1a85397..e4111c8a 100644
--- a/net/http/http_auth_handler_negotiate_unittest.cc
+++ b/net/http/http_auth_handler_negotiate_unittest.cc
@@ -26,12 +26,18 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/platform_test.h"
 
+#if !BUILDFLAG(USE_KERBEROS)
+#error "use_kerberos should be true to use Negotiate authentication scheme."
+#endif
+
 #if defined(OS_ANDROID)
 #include "net/android/dummy_spnego_authenticator.h"
 #elif defined(OS_WIN)
 #include "net/http/mock_sspi_library_win.h"
-#elif defined(OS_POSIX)
+#elif BUILDFLAG(USE_EXTERNAL_GSSAPI)
 #include "net/http/mock_gssapi_library_posix.h"
+#else
+#error "use_kerberos is true, but no Kerberos implementation available."
 #endif
 
 using net::test::IsError;
@@ -41,14 +47,6 @@
 
 constexpr char kFakeToken[] = "FakeToken";
 
-#if defined(OS_ANDROID)
-typedef net::android::DummySpnegoAuthenticator MockAuthLibrary;
-#elif defined(OS_WIN)
-typedef MockSSPILibrary MockAuthLibrary;
-#elif defined(OS_POSIX)
-typedef test::MockGSSAPILibrary MockAuthLibrary;
-#endif
-
 class HttpAuthHandlerNegotiateTest : public PlatformTest,
                                      public WithScopedTaskEnvironment {
  public:
@@ -66,10 +64,9 @@
     http_auth_preferences_->set_auth_android_negotiate_account_type(
         "org.chromium.test.DummySpnegoAuthenticator");
     MockAuthLibrary::EnsureTestAccountExists();
-#endif
-#if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_ANDROID))
+#else
     factory_->set_library(base::WrapUnique(auth_library_));
-#endif
+#endif  // !OS_ANDROID
   }
 
 #if defined(OS_ANDROID)
@@ -83,7 +80,7 @@
     security_package_->cbMaxToken = 1337;
     mock_library->ExpectQuerySecurityPackageInfo(
         L"Negotiate", SEC_E_OK, security_package_.get());
-#elif defined(OS_POSIX)
+#else
     // Copied from an actual transaction!
     static const char kAuthResponse[] =
         "\x60\x82\x02\xCA\x06\x09\x2A\x86\x48\x86\xF7\x12\x01\x02\x02\x01"
@@ -172,7 +169,7 @@
                                           queries[i].expected_input_token,
                                           queries[i].output_token);
     }
-#endif  // defined(OS_POSIX)
+#endif  // !OS_WIN
   }
 
 #if defined(OS_POSIX)
@@ -203,7 +200,6 @@
                                         query.expected_input_token,
                                         query.output_token);
   }
-
 #endif  // defined(OS_POSIX)
 
   int CreateHandler(bool disable_cname_lookup,
@@ -268,7 +264,7 @@
                     nullptr, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/alias", auth_handler->spn_for_testing());
-#elif defined(OS_POSIX)
+#else
   EXPECT_EQ("HTTP@alias", auth_handler->spn_for_testing());
 #endif
 }
@@ -286,7 +282,7 @@
                     nullptr, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/alias", auth_handler->spn_for_testing());
-#elif defined(OS_POSIX)
+#else
   EXPECT_EQ("HTTP@alias", auth_handler->spn_for_testing());
 #endif
 }
@@ -304,7 +300,7 @@
                     nullptr, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/alias:500", auth_handler->spn_for_testing());
-#elif defined(OS_POSIX)
+#else
   EXPECT_EQ("HTTP@alias:500", auth_handler->spn_for_testing());
 #endif
 }
@@ -322,7 +318,7 @@
                     nullptr, &request_info, callback.callback(), &token)));
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn_for_testing());
-#elif defined(OS_POSIX)
+#else
   EXPECT_EQ("HTTP@canonical.example.com", auth_handler->spn_for_testing());
 #endif
 }
@@ -342,7 +338,7 @@
   EXPECT_THAT(callback.WaitForResult(), IsOk());
 #if defined(OS_WIN)
   EXPECT_EQ("HTTP/canonical.example.com", auth_handler->spn_for_testing());
-#elif defined(OS_POSIX)
+#else
   EXPECT_EQ("HTTP@canonical.example.com", auth_handler->spn_for_testing());
 #endif
 }
@@ -383,7 +379,7 @@
   EXPECT_THAT(callback.WaitForResult(), IsError(ERR_MISSING_AUTH_CREDENTIALS));
 }
 
-#if BUILDFLAG(DLOPEN_KERBEROS)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
 TEST_F(HttpAuthHandlerNegotiateTest, MissingGSSAPI) {
   MockAllowHttpAuthPreferences http_auth_preferences;
   std::unique_ptr<HttpAuthHandlerNegotiate::Factory> negotiate_factory(
@@ -401,7 +397,7 @@
   EXPECT_THAT(rv, IsError(ERR_UNSUPPORTED_AUTH_SCHEME));
   EXPECT_TRUE(generic_handler.get() == nullptr);
 }
-#endif  // BUILDFLAG(DLOPEN_KERBEROS)
+#endif  // BUILDFLAG(USE_EXTERNAL_GSSAPI)
 
 // AllowGssapiLibraryLoad() is only supported on Chrome OS.
 #if defined(OS_CHROMEOS)
@@ -430,7 +426,7 @@
   ~TestAuthSystem() override = default;
 
   // HttpNegotiateAuthSystem implementation:
-  bool Init() override { return true; }
+  bool Init(const NetLogWithSource&) override { return true; }
   bool NeedsIdentity() const override { return true; }
   bool AllowsExplicitCredentials() const override { return true; }
 
@@ -443,6 +439,7 @@
                         const std::string& spn,
                         const std::string& channel_bindings,
                         std::string* auth_token,
+                        const NetLogWithSource& net_log,
                         net::CompletionOnceCallback callback) override {
     *auth_token = kFakeToken;
     return net::OK;
diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc
index 926f62c..5dd7093 100644
--- a/net/http/http_auth_handler_ntlm.cc
+++ b/net/http/http_auth_handler_ntlm.cc
@@ -41,7 +41,7 @@
     std::string* auth_token) {
 #if defined(NTLM_SSPI)
   return auth_sspi_.GenerateAuthToken(credentials, CreateSPN(origin_),
-                                      channel_bindings_, auth_token,
+                                      channel_bindings_, auth_token, net_log(),
                                       std::move(callback));
 #else  // !defined(NTLM_SSPI)
   // TODO(cbentzel): Shouldn't be hitting this case.
diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc
index d9c098f..8bc2665 100644
--- a/net/http/http_auth_sspi_win.cc
+++ b/net/http/http_auth_sspi_win.cc
@@ -263,7 +263,7 @@
   }
 }
 
-bool HttpAuthSSPI::Init() {
+bool HttpAuthSSPI::Init(const NetLogWithSource&) {
   return true;
 }
 
@@ -300,6 +300,7 @@
                                     const std::string& spn,
                                     const std::string& channel_bindings,
                                     std::string* auth_token,
+                                    const NetLogWithSource&,
                                     CompletionOnceCallback /*callback*/) {
   // Initial challenge.
   if (!SecIsValidHandle(&cred_)) {
diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h
index e672c89f..408ac59 100644
--- a/net/http/http_auth_sspi_win.h
+++ b/net/http/http_auth_sspi_win.h
@@ -116,7 +116,7 @@
   ~HttpAuthSSPI() override;
 
   // HttpNegotiateAuthSystem implementation:
-  bool Init() override;
+  bool Init(const NetLogWithSource& net_log) override;
   bool NeedsIdentity() const override;
   bool AllowsExplicitCredentials() const override;
   HttpAuth::AuthorizationResult ParseChallenge(
@@ -125,6 +125,7 @@
                         const std::string& spn,
                         const std::string& channel_bindings,
                         std::string* auth_token,
+                        const NetLogWithSource& net_log,
                         CompletionOnceCallback callback) override;
   void SetDelegation(HttpAuth::DelegationType delegation_type) override;
 
diff --git a/net/http/http_auth_sspi_win_unittest.cc b/net/http/http_auth_sspi_win_unittest.cc
index 3c42767..d9031fa7 100644
--- a/net/http/http_auth_sspi_win_unittest.cc
+++ b/net/http/http_auth_sspi_win_unittest.cc
@@ -3,10 +3,12 @@
 // found in the LICENSE file.
 
 #include "net/http/http_auth_sspi_win.h"
+
 #include "base/bind.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_auth_challenge_tokenizer.h"
 #include "net/http/mock_sspi_library_win.h"
+#include "net/log/net_log_with_source.h"
 #include "net/test/gtest_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -96,9 +98,10 @@
 
   // Generate an auth token and create another thing.
   std::string auth_token;
-  EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(
-                    nullptr, "HTTP/intranet.google.com", std::string(),
-                    &auth_token, base::BindOnce(&UnexpectedCallback)));
+  EXPECT_EQ(OK,
+            auth_sspi.GenerateAuthToken(
+                nullptr, "HTTP/intranet.google.com", std::string(), &auth_token,
+                NetLogWithSource(), base::BindOnce(&UnexpectedCallback)));
 
   std::string second_challenge_text = "Negotiate Zm9vYmFy";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
@@ -133,9 +136,10 @@
             auth_sspi.ParseChallenge(&first_challenge));
 
   std::string auth_token;
-  EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(
-                    nullptr, "HTTP/intranet.google.com", std::string(),
-                    &auth_token, base::BindOnce(&UnexpectedCallback)));
+  EXPECT_EQ(OK,
+            auth_sspi.GenerateAuthToken(
+                nullptr, "HTTP/intranet.google.com", std::string(), &auth_token,
+                NetLogWithSource(), base::BindOnce(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
@@ -156,9 +160,10 @@
             auth_sspi.ParseChallenge(&first_challenge));
 
   std::string auth_token;
-  EXPECT_EQ(OK, auth_sspi.GenerateAuthToken(
-                    nullptr, "HTTP/intranet.google.com", std::string(),
-                    &auth_token, base::BindOnce(&UnexpectedCallback)));
+  EXPECT_EQ(OK,
+            auth_sspi.GenerateAuthToken(
+                nullptr, "HTTP/intranet.google.com", std::string(), &auth_token,
+                NetLogWithSource(), base::BindOnce(&UnexpectedCallback)));
   std::string second_challenge_text = "Negotiate =happyjoy=";
   HttpAuthChallengeTokenizer second_challenge(second_challenge_text.begin(),
                                               second_challenge_text.end());
diff --git a/net/http/http_negotiate_auth_system.h b/net/http/http_negotiate_auth_system.h
index cf90cd6..15dcc6c4 100644
--- a/net/http/http_negotiate_auth_system.h
+++ b/net/http/http_negotiate_auth_system.h
@@ -13,12 +13,13 @@
 
 class AuthCredentials;
 class HttpAuthChallengeTokenizer;
+class NetLogWithSource;
 
 class NET_EXPORT_PRIVATE HttpNegotiateAuthSystem {
  public:
   virtual ~HttpNegotiateAuthSystem() = default;
 
-  virtual bool Init() = 0;
+  virtual bool Init(const NetLogWithSource& net_log) = 0;
 
   // True if authentication needs the identity of the user from Chrome.
   virtual bool NeedsIdentity() const = 0;
@@ -57,6 +58,7 @@
                                 const std::string& spn,
                                 const std::string& channel_bindings,
                                 std::string* auth_token,
+                                const NetLogWithSource& net_log,
                                 CompletionOnceCallback callback) = 0;
 
   // Sets the delegation type allowed on the Kerberos ticket. This allows
diff --git a/net/http/mock_gssapi_library_posix.cc b/net/http/mock_gssapi_library_posix.cc
index 6766602..e492bd99 100644
--- a/net/http/mock_gssapi_library_posix.cc
+++ b/net/http/mock_gssapi_library_posix.cc
@@ -253,7 +253,7 @@
   expected_security_queries_.push_back(security_query);
 }
 
-bool MockGSSAPILibrary::Init() {
+bool MockGSSAPILibrary::Init(const NetLogWithSource&) {
   return true;
 }
 
diff --git a/net/http/mock_gssapi_library_posix.h b/net/http/mock_gssapi_library_posix.h
index 9ed109b..d04ca82 100644
--- a/net/http/mock_gssapi_library_posix.h
+++ b/net/http/mock_gssapi_library_posix.h
@@ -113,7 +113,7 @@
   // Initializes the library, including any necessary dynamic libraries.
   // This is done separately from construction (which happens at startup time)
   // in order to delay work until the class is actually needed.
-  bool Init() override;
+  bool Init(const NetLogWithSource& net_log) override;
 
   // These methods match the ones in the GSSAPI library.
   OM_uint32 import_name(OM_uint32* minor_status,
@@ -203,6 +203,8 @@
 
 }  // namespace test
 
+using MockAuthLibrary = test::MockGSSAPILibrary;
+
 }  // namespace net
 
 #endif  // NET_HTTP_MOCK_GSSAPI_LIBRARY_POSIX_H_
diff --git a/net/http/mock_sspi_library_win.h b/net/http/mock_sspi_library_win.h
index b042ebf..c0b7692 100644
--- a/net/http/mock_sspi_library_win.h
+++ b/net/http/mock_sspi_library_win.h
@@ -106,6 +106,8 @@
   std::set<PSecPkgInfoW> expected_freed_packages_;
 };
 
+using MockAuthLibrary = MockSSPILibrary;
+
 }  // namespace net
 
 #endif  // NET_HTTP_MOCK_SSPI_LIBRARY_WIN_H_
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index 6bf9428d..b6db4a7 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -2169,6 +2169,79 @@
 // ------------------------------------------------------------------------
 // HTTP Authentication
 // ------------------------------------------------------------------------
+//
+// Structure of common GSSAPI / SSPI values.
+// -----------------------------------------
+// For convenience some structured GSSAPI/SSPI values are serialized
+// consistently across different events. They are explained below.
+//
+// ** GSSAPI Status:
+//
+// A major/minor status code returned by a GSSAPI function. The major status
+// code indicates the GSSAPI level error, while the minor code provides a
+// mechanism specific error code if a specific GSSAPI mechanism was involved in
+// the error.
+//
+// The status value has the following structure:
+//   {
+//     "function": <name of GSSAPI function that returned the error>
+//     "major_status": {
+//       "status" : <status value as a number>,
+//       "message": [
+//          <list of strings hopefully explaining what that number means>
+//       ]
+//     },
+//     "minor_status": {
+//       "status" : <status value as a number>,
+//       "message": [
+//          <list of strings hopefully explaining what that number means>
+//       ]
+//     }
+//   }
+//
+// ** OID:
+//
+// An ASN.1 OID that's used for GSSAPI is serialized thusly:
+//   {
+//     "oid":    <symbolic name of OID if it is known>
+//     "length": <length in bytes of serialized OID>,
+//     "bytes":  <serialized bytes of OID encoded via NetLogBinaryValue()>
+//   }
+//
+// ** GSS Display Name:
+//
+// A serialization of GSSAPI principal name to something that can be consumed by
+// humans. If the encoding of the string is not UTF-8 (since there's no
+// requirement that they use any specific encoding) the field is serialized
+// using NetLogBinaryValue().
+//   {
+//     "name" : <GSSAPI principal name>
+//     "type" : <OID indicating type of name. See OID above.>
+//     "error": <If the display name lookup operation failed, then this field
+//               contains the error in the form of a GSSAPI Status.>
+//   }
+//
+// ** GSSAPI Context Description
+//
+// A serialization of the GSSAPI context. It takes the following form:
+//   {
+//     "source"  : <GSS Display Name for the source of the authentication
+//                  attempt.  In practice this is always the user's identity.>
+//     "target"  : <GSS Display Name for the target of the authentication
+//                  attempt.  This the target server or proxy service
+//                  principal.>
+//     "open"    : <Boolean indicating whether the context is "open", which
+//                  means that the handshake is still in progress. In
+//                  particular, the flags, lifetime, and mechanism fields are
+//                  not considered final until "open" is false.
+//     "lifetime": <A decimal string indicating the lifetime in seconds of the
+//                  authentication context. The identity as established by this
+//                  handshake is only valid for this long since the time at
+//                  which it was established.>
+//     "mechanism":<OID indicating inner authentication mechanism.>
+//     "flags"    :<Flags. See RFC 2744 Section 5.19 for meanings. Flag
+//                  bitmasks can be found in RFC 2744 Appendix A.>
+//   }
 
 // Lifetime event for HttpAuthController.
 //
@@ -2212,11 +2285,56 @@
 //  }
 EVENT_TYPE(AUTH_HANDLE_CHALLENGE)
 
+// An attempt was made to load an authentication library.
+//
+// If the request succeeded, the parameters are:
+//   {
+//     "library_name": <Name of library>
+//   }
+// Otherwise, the parameters are:
+//   {
+//     "library_name": <Name of library>
+//     "load_error": <An error string>
+//   }
+EVENT_TYPE(AUTH_LIBRARY_LOAD)
+
+// A required method was not found while attempting to load an authentication
+// library.
+//
+// Parameters are:
+//   {
+//     "library_name": <Name of the library where the method lookup failed>
+//     "method": <Name of method that was not found>
+//   }
+EVENT_TYPE(AUTH_LIBRARY_BIND_FAILED)
+
+// Construction of the GSSAPI service principal name.
+//
+// Parameters:
+//   {
+//     "spn": <Service principal name as a string>
+//     "status":  <GSSAPI Status. See GSSAPI Status above. This is field is only
+//                 logged if the operation failed.>
+//   }
+EVENT_TYPE(AUTH_LIBRARY_IMPORT_NAME)
+
+// Initialize security context.
+//
+// This operation involves invoking an external library which may perform disk,
+// IPC, and network IO as a part of its work.
+//
+// The END phase has the following parameters.
+//   {
+//     "context": <GSSAPI Context Description>,
+//     "status":  <GSSAPI Status if the operation failed>
+//   }
+EVENT_TYPE(AUTH_LIBRARY_INIT_SEC_CTX)
+
 // The channel bindings generated for the connection.
 //  {
-//       "token": <Hex encoded RFC 5929 'tls-server-endpoint' channel binding
-//                 token. Could be empty if one could not be generated (e.g.
-//                 because the underlying channel was not TLS>
+//     "token": <Hex encoded RFC 5929 'tls-server-endpoint' channel binding
+//               token. Could be empty if one could not be generated (e.g.
+//               because the underlying channel was not TLS.)>
 //  }
 EVENT_TYPE(AUTH_CHANNEL_BINDINGS)
 
diff --git a/net/network_error_logging/OWNERS b/net/network_error_logging/OWNERS
new file mode 100644
index 0000000..7117a80a
--- /dev/null
+++ b/net/network_error_logging/OWNERS
@@ -0,0 +1 @@
+chlily@chromium.org
diff --git a/net/reporting/OWNERS b/net/reporting/OWNERS
new file mode 100644
index 0000000..7117a80a
--- /dev/null
+++ b/net/reporting/OWNERS
@@ -0,0 +1 @@
+chlily@chromium.org
diff --git a/net/test/embedded_test_server/default_handlers.cc b/net/test/embedded_test_server/default_handlers.cc
index 68d19bb7..3a7c68be 100644
--- a/net/test/embedded_test_server/default_handlers.cc
+++ b/net/test/embedded_test_server/default_handlers.cc
@@ -206,6 +206,22 @@
   return http_response;
 }
 
+// /set-invalid-cookie
+// Sets invalid response cookies "\x01" (chosen via fuzzer to not be a parsable
+// cookie).
+std::unique_ptr<HttpResponse> HandleSetInvalidCookie(
+    const HttpRequest& request) {
+  auto http_response = std::make_unique<BasicHttpResponse>();
+  http_response->set_content_type("text/html");
+  std::string content;
+  GURL request_url = request.GetURL();
+
+  http_response->AddCustomHeader("Set-Cookie", "\x01");
+
+  http_response->set_content("TEST");
+  return http_response;
+}
+
 // /set-many-cookies?N
 // Sets N cookies in the response.
 std::unique_ptr<HttpResponse> HandleSetManyCookies(const HttpRequest& request) {
@@ -365,6 +381,9 @@
                                    "Basic realm=\"" + realm + "\"");
     if (query.find("set-cookie-if-challenged") != query.end())
       http_response->AddCustomHeader("Set-Cookie", "got_challenged=true");
+    if (query.find("set-secure-cookie-if-challenged") != query.end())
+      http_response->AddCustomHeader("Set-Cookie",
+                                     "got_challenged=true;Secure");
     http_response->set_content(base::StringPrintf(
         "<html><head><title>Denied: %s</title></head>"
         "<body>auth=%s<p>b64str=%s<p>username: %s<p>userpass: %s<p>"
@@ -542,6 +561,27 @@
   return http_response;
 }
 
+// /server-redirect-with-secure-cookie?URL
+// Returns a server redirect to URL, and sets the cookie
+// server-redirect=true;Secure.
+std::unique_ptr<HttpResponse> HandleServerRedirectWithSecureCookie(
+    HttpStatusCode redirect_code,
+    const HttpRequest& request) {
+  GURL request_url = request.GetURL();
+  std::string dest = UnescapeBinaryURLComponent(request_url.query_piece());
+  RequestQuery query = ParseQuery(request_url);
+
+  auto http_response = std::make_unique<BasicHttpResponse>();
+  http_response->set_code(redirect_code);
+  http_response->AddCustomHeader("Location", dest);
+  http_response->AddCustomHeader("Set-Cookie", "server-redirect=true;Secure");
+  http_response->set_content_type("text/html");
+  http_response->set_content(base::StringPrintf(
+      "<html><head></head><body>Redirecting to %s</body></html>",
+      dest.c_str()));
+  return http_response;
+}
+
 // /cross-site?URL
 // Returns a cross-site redirect to URL.
 std::unique_ptr<HttpResponse> HandleCrossSiteRedirect(
@@ -753,6 +793,8 @@
   server->RegisterDefaultHandler(
       PREFIXED_HANDLER("/set-cookie", &HandleSetCookie));
   server->RegisterDefaultHandler(
+      PREFIXED_HANDLER("/set-invalid-cookie", &HandleSetInvalidCookie));
+  server->RegisterDefaultHandler(
       PREFIXED_HANDLER("/set-many-cookies", &HandleSetManyCookies));
   server->RegisterDefaultHandler(
       PREFIXED_HANDLER("/expect-and-set-cookie", &HandleExpectAndSetCookie));
@@ -783,6 +825,9 @@
   server->RegisterDefaultHandler(SERVER_REDIRECT_HANDLER(
       "/server-redirect-with-cookie", &HandleServerRedirectWithCookie,
       HTTP_MOVED_PERMANENTLY));
+  server->RegisterDefaultHandler(SERVER_REDIRECT_HANDLER(
+      "/server-redirect-with-secure-cookie",
+      &HandleServerRedirectWithSecureCookie, HTTP_MOVED_PERMANENTLY));
 
   server->RegisterDefaultHandler(
       base::BindRepeating(&HandleCrossSiteRedirect, server));
diff --git a/services/network/host_resolver_unittest.cc b/services/network/host_resolver_unittest.cc
index 42296b9..a87fc24 100644
--- a/services/network/host_resolver_unittest.cc
+++ b/services/network/host_resolver_unittest.cc
@@ -1177,6 +1177,7 @@
       net::HostResolver::CreateStandaloneContextResolver(&net_log);
   inner_resolver->GetManagerForTesting()->SetDnsClientForTesting(
       std::move(dns_client));
+  inner_resolver->GetManagerForTesting()->SetInsecureDnsClientEnabled(true);
   inner_resolver->SetBaseDnsConfigForTesting(CreateValidDnsConfig());
 
   HostResolver resolver(inner_resolver.get(), &net_log);
@@ -1216,6 +1217,7 @@
       net::HostResolver::CreateStandaloneContextResolver(&net_log);
   inner_resolver->GetManagerForTesting()->SetDnsClientForTesting(
       std::move(dns_client));
+  inner_resolver->GetManagerForTesting()->SetInsecureDnsClientEnabled(true);
   inner_resolver->SetBaseDnsConfigForTesting(CreateValidDnsConfig());
 
   HostResolver resolver(inner_resolver.get(), &net_log);
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 0e3fb93f..ded9272 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -1324,7 +1324,7 @@
     // different overrides.  But since this is only used for special cases for
     // now, much easier to create entirely separate net::HostResolver instances.
     net::HostResolver::ManagerOptions options;
-    options.dns_client_enabled = true;
+    options.insecure_dns_client_enabled = true;
     options.dns_config_overrides = config_overrides.value();
     private_internal_resolver =
         network_service_->host_resolver_factory()->CreateStandaloneResolver(
diff --git a/services/network/network_context_unittest.cc b/services/network/network_context_unittest.cc
index 3dcc6d2b..7d803bb 100644
--- a/services/network/network_context_unittest.cc
+++ b/services/network/network_context_unittest.cc
@@ -5690,6 +5690,274 @@
   EXPECT_EQ(base::ASCIIToUTF16(kPassword), entry->credentials().password());
 }
 
+static ResourceRequest CreateResourceRequest(const char* method,
+                                             const GURL& url) {
+  ResourceRequest request;
+  request.method = std::string(method);
+  request.url = url;
+  request.site_for_cookies = url;  // bypass third-party cookie blocking
+  request.request_initiator =
+      url::Origin::Create(url);  // ensure initiator is set
+  request.allow_download = true;
+  return request;
+}
+
+class NetworkContextSplitCacheTest : public NetworkContextTest {
+ protected:
+  NetworkContextSplitCacheTest() {
+    feature_list_.InitAndEnableFeature(
+        net::features::kSplitCacheByNetworkIsolationKey);
+    test_server_.AddDefaultHandlers(
+        base::FilePath(FILE_PATH_LITERAL("services/test/data")));
+    EXPECT_TRUE(test_server_.Start());
+
+    // Set up a scoped host resolver to access other origins.
+    scoped_refptr<net::RuleBasedHostResolverProc> mock_resolver_proc =
+        base::MakeRefCounted<net::RuleBasedHostResolverProc>(nullptr);
+    mock_resolver_proc->AddRule("*", "127.0.0.1");
+    mock_host_resolver_ = std::make_unique<net::ScopedDefaultHostResolverProc>(
+        mock_resolver_proc.get());
+
+    mojom::NetworkContextParamsPtr context_params = CreateContextParams();
+    network_context_ = CreateContextWithParams(std::move(context_params));
+  }
+
+  net::EmbeddedTestServer* test_server() { return &test_server_; }
+
+  void LoadAndVerifyCached(
+      const GURL& url,
+      const net::NetworkIsolationKey& key,
+      bool was_cached,
+      bool is_navigation,
+      mojom::UpdateNetworkIsolationKeyOnRedirect
+          update_network_isolation_key_on_redirect =
+              mojom::UpdateNetworkIsolationKeyOnRedirect::kDoNotUpdate,
+      bool expect_redirect = false,
+      base::Optional<GURL> new_url = base::nullopt) {
+    ResourceRequest request = CreateResourceRequest("GET", url);
+    request.load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
+    request.update_network_isolation_key_on_redirect =
+        update_network_isolation_key_on_redirect;
+
+    mojom::URLLoaderFactoryPtr loader_factory;
+    auto params = mojom::URLLoaderFactoryParams::New();
+    params->process_id = mojom::kBrowserProcessId;
+    params->is_corb_enabled = false;
+    if (is_navigation) {
+      request.trusted_network_isolation_key = key;
+    } else {
+      params->network_isolation_key = key;
+    }
+    network_context_->CreateURLLoaderFactory(mojo::MakeRequest(&loader_factory),
+                                             std::move(params));
+    auto client = std::make_unique<TestURLLoaderClient>();
+    mojom::URLLoaderPtr loader;
+    loader_factory->CreateLoaderAndStart(
+        mojo::MakeRequest(&loader), 0 /* routing_id */, 0 /* request_id */,
+        mojom::kURLLoadOptionNone, request, client->CreateInterfacePtr(),
+        net::MutableNetworkTrafficAnnotationTag(TRAFFIC_ANNOTATION_FOR_TESTS));
+
+    if (expect_redirect) {
+      client->RunUntilRedirectReceived();
+      loader->FollowRedirect({}, {}, new_url);
+      client->ClearHasReceivedRedirect();
+    }
+
+    if (new_url) {
+      client->RunUntilRedirectReceived();
+      loader->FollowRedirect({}, {}, base::nullopt);
+    }
+
+    client->RunUntilComplete();
+
+    EXPECT_EQ(net::OK, client->completion_status().error_code);
+    EXPECT_EQ(was_cached, client->completion_status().exists_in_cache);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+  net::EmbeddedTestServer test_server_;
+  std::unique_ptr<net::ScopedDefaultHostResolverProc> mock_host_resolver_;
+  std::unique_ptr<NetworkContext> network_context_;
+};
+
+TEST_F(NetworkContextSplitCacheTest, CachedUsingNetworkIsolationKey) {
+  GURL url = test_server()->GetURL("/resource");
+  url::Origin origin_a = url::Origin::Create(GURL("http://a.test/"));
+  net::NetworkIsolationKey key_a(origin_a, origin_a);
+  LoadAndVerifyCached(url, key_a, false /* was_cached */,
+                      false /* is_navigation */);
+
+  // Load again with a different isolation key. The cached entry should not be
+  // loaded.
+  url::Origin origin_b = url::Origin::Create(GURL("http://b.test/"));
+  net::NetworkIsolationKey key_b(origin_b, origin_b);
+  LoadAndVerifyCached(url, key_b, false /* was_cached */,
+                      false /* is_navigation */);
+
+  // Load again with the same isolation key. The cached entry should be loaded.
+  LoadAndVerifyCached(url, key_b, true /* was_cached */,
+                      false /* is_navigation */);
+}
+
+TEST_F(NetworkContextSplitCacheTest,
+       NavigationResourceCachedUsingNetworkIsolationKey) {
+  GURL url = test_server()->GetURL("othersite.test", "/main.html");
+  url::Origin origin_a = url::Origin::Create(url);
+  net::NetworkIsolationKey key_a(origin_a, origin_a);
+  LoadAndVerifyCached(url, key_a, false /* was_cached */,
+                      true /* is_navigation */);
+
+  // Load again with a different isolation key. The cached entry should not be
+  // loaded.
+  GURL url_b = test_server()->GetURL("/main.html");
+  url::Origin origin_b = url::Origin::Create(url_b);
+  net::NetworkIsolationKey key_b(origin_b, origin_b);
+  LoadAndVerifyCached(url_b, key_b, false /* was_cached */,
+                      true /* is_navigation */);
+
+  // Load again with the same isolation key. The cached entry should be loaded.
+  LoadAndVerifyCached(url_b, key_b, true /* was_cached */,
+                      true /* is_navigation */);
+}
+
+TEST_F(NetworkContextSplitCacheTest,
+       CachedUsingNetworkIsolationKeyWithFrameOrigin) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {net::features::kSplitCacheByNetworkIsolationKey,
+       net::features::kAppendFrameOriginToNetworkIsolationKey},
+      {});
+
+  GURL url = test_server()->GetURL("/resource");
+  url::Origin origin_a = url::Origin::Create(GURL("http://a.test/"));
+  net::NetworkIsolationKey key_a(origin_a, origin_a);
+  LoadAndVerifyCached(url, key_a, false /* was_cached */,
+                      false /* is_navigation */);
+
+  // Load again with a different isolation key. The cached entry should not be
+  // loaded.
+  url::Origin origin_b = url::Origin::Create(GURL("http://b.test/"));
+  net::NetworkIsolationKey key_b(origin_a, origin_b);
+  LoadAndVerifyCached(url, key_b, false /* was_cached */,
+                      false /* is_navigation */);
+}
+
+TEST_F(NetworkContextSplitCacheTest,
+       NavigationResourceRedirectNetworkIsolationKey) {
+  // Create a request that redirects.
+  GURL url = test_server()->GetURL(
+      "/server-redirect?" +
+      test_server()->GetURL("othersite.test", "/title1.html").spec());
+  url::Origin origin = url::Origin::Create(url);
+  net::NetworkIsolationKey key(origin, origin);
+  LoadAndVerifyCached(
+      url, key, false /* was_cached */, true /* is_navigation */,
+      mojom::UpdateNetworkIsolationKeyOnRedirect::kUpdateTopFrameAndFrameOrigin,
+      true /* expect_redirect */);
+
+  // Now directly load with the key using the redirected URL. This should be a
+  // cache hit.
+  GURL redirected_url = test_server()->GetURL("othersite.test", "/title1.html");
+  url::Origin redirected_origin = url::Origin::Create(redirected_url);
+  LoadAndVerifyCached(
+      redirected_url,
+      net::NetworkIsolationKey(redirected_origin, redirected_origin),
+      true /* was_cached */, true /* is_navigation */);
+
+  // A non-navigation resource with the same key and url should also be cached.
+  LoadAndVerifyCached(
+      redirected_url,
+      net::NetworkIsolationKey(redirected_origin, redirected_origin),
+      true /* was_cached */, false /* is_navigation */);
+}
+
+TEST_F(NetworkContextSplitCacheTest,
+       NavigationResourceRedirectNetworkIsolationKeyWithNewUrl) {
+  // Create a request that redirects to othersite.test/title1.html.
+  GURL url = test_server()->GetURL(
+      "/server-redirect?" +
+      test_server()->GetURL("othersite.test", "/title1.html").spec());
+  url::Origin origin = url::Origin::Create(url);
+  net::NetworkIsolationKey key(origin, origin);
+
+  // Create a new url that should be used in the network isolation key computed
+  // in FollowRedirect instead of the redirected url.
+  GURL new_url = test_server()->GetURL("othersite.test", "/title2.html");
+  LoadAndVerifyCached(
+      url, key, false /* was_cached */, true /* is_navigation */,
+      mojom::UpdateNetworkIsolationKeyOnRedirect::kUpdateTopFrameAndFrameOrigin,
+      true /* expect_redirect */, new_url);
+
+  // Load with the key using the new url should be a cache hit.
+  origin = url::Origin::Create(new_url);
+  LoadAndVerifyCached(new_url, net::NetworkIsolationKey(origin, origin),
+                      true /* was_cached */, true /* is_navigation */);
+
+  // Now directly load with the key using the redirected URL. This should be a
+  // cache miss.
+  GURL redirected_url = test_server()->GetURL("othersite.test", "/title1.html");
+  origin = url::Origin::Create(redirected_url);
+  LoadAndVerifyCached(redirected_url, net::NetworkIsolationKey(origin, origin),
+                      false /* was_cached */, true /* is_navigation */);
+}
+
+TEST_F(NetworkContextSplitCacheTest,
+       NavigationResourceCachedUsingNetworkIsolationKeyWithFrameOrigin) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {net::features::kSplitCacheByNetworkIsolationKey,
+       net::features::kAppendFrameOriginToNetworkIsolationKey},
+      {});
+  GURL url = test_server()->GetURL("othersite.test", "/main.html");
+  url::Origin origin_a = url::Origin::Create(url);
+  net::NetworkIsolationKey key_a(origin_a, origin_a);
+  LoadAndVerifyCached(url, key_a, false /* was_cached */,
+                      true /* is_navigation */);
+
+  // Load again with a isolation key using a different subframe origin. The
+  // cached entry should not be loaded.
+  url::Origin origin_b = url::Origin::Create(test_server()->base_url());
+  net::NetworkIsolationKey key_b(origin_a, origin_b);
+  LoadAndVerifyCached(url, key_b, false /* was_cached */,
+                      true /* is_navigation */);
+
+  // Load again with the same isolation key. The cached entry should be loaded.
+  LoadAndVerifyCached(url, key_b, true /* was_cached */,
+                      true /* is_navigation */);
+
+  // Same for a non-navigation entry.
+  LoadAndVerifyCached(url, key_b, true /* was_cached */,
+                      false /* is_navigation */);
+}
+
+TEST_F(NetworkContextSplitCacheTest,
+       NavigationResourceRedirectNetworkIsolationKeyWithFrameOrigin) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitWithFeatures(
+      {net::features::kSplitCacheByNetworkIsolationKey,
+       net::features::kAppendFrameOriginToNetworkIsolationKey},
+      {});
+  // Create a request that redirects to othersite.test/title1.html.
+  GURL url = test_server()->GetURL(
+      "/server-redirect?" +
+      test_server()->GetURL("othersite.test", "/title1.html").spec());
+  url::Origin origin = url::Origin::Create(url);
+  net::NetworkIsolationKey key(origin, origin);
+  LoadAndVerifyCached(
+      url, key, false /* was_cached */, true /* is_navigation */,
+      mojom::UpdateNetworkIsolationKeyOnRedirect::kUpdateFrameOrigin,
+      true /* expect_redirect */);
+
+  // Now directly load with the key using the redirected URL. This should be a
+  // cache hit.
+  GURL redirected_url = test_server()->GetURL("othersite.test", "/title1.html");
+  LoadAndVerifyCached(
+      redirected_url,
+      net::NetworkIsolationKey(origin, url::Origin::Create(redirected_url)),
+      true /* was_cached */, true /* is_navigation */);
+}
+
 }  // namespace
 
 }  // namespace network
diff --git a/services/network/network_service.cc b/services/network/network_service.cc
index e5291c8b..3cf5a41 100644
--- a/services/network/network_service.cc
+++ b/services/network/network_service.cc
@@ -150,7 +150,9 @@
   ~NetworkServiceAuthNegotiateAndroid() override = default;
 
   // HttpNegotiateAuthSystem implementation:
-  bool Init() override { return auth_negotiate_.Init(); }
+  bool Init(const net::NetLogWithSource& net_log) override {
+    return auth_negotiate_.Init(net_log);
+  }
 
   bool NeedsIdentity() const override {
     return auth_negotiate_.NeedsIdentity();
@@ -169,6 +171,7 @@
                         const std::string& spn,
                         const std::string& channel_bindings,
                         std::string* auth_token,
+                        const net::NetLogWithSource& net_log,
                         net::CompletionOnceCallback callback) override {
     network_service_->client()->OnGenerateHttpNegotiateAuthToken(
         auth_negotiate_.server_auth_token(), auth_negotiate_.can_delegate(),
@@ -456,9 +459,9 @@
   DCHECK(stub_resolver_enabled || !dns_over_https_servers);
   DCHECK(!dns_over_https_servers || !dns_over_https_servers->empty());
 
-  // Enable or disable the stub resolver, as needed. "DnsClient" is class that
-  // implements the stub resolver.
-  host_resolver_manager_->SetDnsClientEnabled(stub_resolver_enabled);
+  // Enable or disable the insecure part of DnsClient. "DnsClient" is the class
+  // that implements the stub resolver.
+  host_resolver_manager_->SetInsecureDnsClientEnabled(stub_resolver_enabled);
 
   // Configure DNS over HTTPS.
   if (!dns_over_https_servers || dns_over_https_servers.value().empty()) {
@@ -472,7 +475,8 @@
     overrides.dns_over_https_servers.value().emplace_back(
         doh_server->server_template, doh_server->use_post);
   }
-  // TODO(dalyk): Allow the secure dns mode to be set.
+  // TODO(crbug.com/985589): Allow the secure dns mode to be set independently
+  // of the insecure part of the stub resolver.
   overrides.secure_dns_mode = net::DnsConfig::SecureDnsMode::AUTOMATIC;
   host_resolver_manager_->SetDnsConfigOverrides(overrides);
 }
@@ -491,8 +495,7 @@
 
   http_auth_handler_factory_ = net::HttpAuthHandlerRegistryFactory::Create(
       &http_auth_preferences_, http_auth_static_params->supported_schemes
-#if (defined(OS_POSIX) && !defined(OS_ANDROID) && !defined(OS_CHROMEOS)) || \
-    defined(OS_FUCHSIA)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
       ,
       http_auth_static_params->gssapi_library_name
 #endif
diff --git a/services/network/network_service_unittest.cc b/services/network/network_service_unittest.cc
index ce8f35df..7f5af33 100644
--- a/services/network/network_service_unittest.cc
+++ b/services/network/network_service_unittest.cc
@@ -230,9 +230,7 @@
   EXPECT_FALSE(auth_handler_factory->GetSchemeFactory(net::kNtlmAuthScheme));
 }
 
-// |gssapi_library_name| is only supported on certain POSIX platforms.
-#if BUILDFLAG(USE_KERBEROS) && defined(OS_POSIX) && !defined(OS_ANDROID) && \
-    !defined(OS_CHROMEOS)
+#if BUILDFLAG(USE_EXTERNAL_GSSAPI)
 TEST_F(NetworkServiceTest, AuthGssapiLibraryName) {
   const std::string kGssapiLibraryName = "Jim";
   mojom::HttpAuthStaticParamsPtr auth_params =
@@ -249,7 +247,7 @@
   EXPECT_EQ(kGssapiLibraryName,
             GetNegotiateFactory(&network_context)->GetLibraryNameForTesting());
 }
-#endif
+#endif  // BUILDFLAG(USE_EXTERNAL_GSSAPI)
 
 TEST_F(NetworkServiceTest, AuthServerWhitelist) {
   // Add one server to the whitelist before creating any NetworkContexts.
@@ -447,15 +445,21 @@
 TEST_F(NetworkServiceTest, DnsClientEnableDisable) {
   // HostResolver::GetDnsConfigAsValue() returns nullptr if the stub resolver is
   // disabled.
-  EXPECT_FALSE(service()->host_resolver_manager()->GetDnsConfigAsValue());
+  EXPECT_FALSE(service()
+                   ->host_resolver_manager()
+                   ->GetInsecureDnsClientEnabledForTesting());
   service()->ConfigureStubHostResolver(
       true /* stub_resolver_enabled */,
       base::nullopt /* dns_over_https_servers */);
-  EXPECT_TRUE(service()->host_resolver_manager()->GetDnsConfigAsValue());
+  EXPECT_TRUE(service()
+                  ->host_resolver_manager()
+                  ->GetInsecureDnsClientEnabledForTesting());
   service()->ConfigureStubHostResolver(
       false /* stub_resolver_enabled */,
       base::nullopt /* dns_over_https_servers */);
-  EXPECT_FALSE(service()->host_resolver_manager()->GetDnsConfigAsValue());
+  EXPECT_FALSE(service()
+                   ->host_resolver_manager()
+                   ->GetInsecureDnsClientEnabledForTesting());
 }
 
 TEST_F(NetworkServiceTest, DnsOverHttpsEnableDisable) {
@@ -466,10 +470,6 @@
   const std::string kServer3 = "https://grapefruit/resolver/query{?dns}";
   const bool kServer3UsePost = false;
 
-  // HostResolver::GetDnsClientForTesting() returns nullptr if the stub resolver
-  // is disabled.
-  EXPECT_FALSE(service()->host_resolver_manager()->GetDnsConfigAsValue());
-
   // Create the primary NetworkContext before enabling DNS over HTTPS.
   mojom::NetworkContextPtr network_context;
   mojom::NetworkContextParamsPtr context_params = CreateContextParams();
diff --git a/services/network/public/cpp/url_request_mojom_traits_unittest.cc b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
index fa111e6c..b71e5cf 100644
--- a/services/network/public/cpp/url_request_mojom_traits_unittest.cc
+++ b/services/network/public/cpp/url_request_mojom_traits_unittest.cc
@@ -53,9 +53,8 @@
   original.top_frame_origin = origin;
   original.trusted_network_isolation_key =
       net::NetworkIsolationKey(origin, origin);
-  original.update_network_isolation_key_on_redirect =
-      network::mojom::UpdateNetworkIsolationKeyOnRedirect::
-          kUpdateTopFrameAndInitiatingFrameOrigin;
+  original.update_network_isolation_key_on_redirect = network::mojom::
+      UpdateNetworkIsolationKeyOnRedirect::kUpdateTopFrameAndFrameOrigin;
   original.attach_same_site_cookies = true;
   original.update_first_party_url_on_redirect = false;
   original.request_initiator = url::Origin::Create(original.url);
diff --git a/services/network/public/mojom/BUILD.gn b/services/network/public/mojom/BUILD.gn
index c88ed7b..012e70b9 100644
--- a/services/network/public/mojom/BUILD.gn
+++ b/services/network/public/mojom/BUILD.gn
@@ -85,6 +85,7 @@
     "digitally_signed.mojom",
     "fetch_api.mojom",
     "host_resolver.mojom",
+    "http_raw_headers.mojom",
     "http_request_headers.mojom",
     "mdns_responder.mojom",
     "net_log.mojom",
diff --git a/services/network/public/mojom/http_raw_headers.mojom b/services/network/public/mojom/http_raw_headers.mojom
new file mode 100644
index 0000000..ec601fbb
--- /dev/null
+++ b/services/network/public/mojom/http_raw_headers.mojom
@@ -0,0 +1,10 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module network.mojom;
+
+struct HttpRawHeaderPair {
+  string key;
+  string value;
+};
\ No newline at end of file
diff --git a/services/network/public/mojom/network_service.mojom b/services/network/public/mojom/network_service.mojom
index 85c34c1..c51da43 100644
--- a/services/network/public/mojom/network_service.mojom
+++ b/services/network/public/mojom/network_service.mojom
@@ -13,6 +13,7 @@
 import "mojo/public/mojom/base/values.mojom";
 import "services/network/public/mojom/cookie_manager.mojom";
 import "services/network/public/mojom/host_resolver.mojom";
+import "services/network/public/mojom/http_raw_headers.mojom";
 import "services/network/public/mojom/net_log.mojom";
 import "services/network/public/mojom/network_change_manager.mojom";
 import "services/network/public/mojom/network_context.mojom";
@@ -171,6 +172,27 @@
                                    string auth_negotiate_android_account_type,
                                    string spn) =>
                                    (int32 result, string auth_token);
+
+  // Called to send raw header information and information about excluded
+  // cookies. Only called when |devtool_request_id| is available to the
+  // URLLoader.
+  OnRawRequest(
+    int32 process_id,
+    int32 routing_id,
+    string devtool_request_id,
+    array<CookieWithStatus> cookies_with_status,
+    array<HttpRawHeaderPair> headers);
+
+  // Called to send information about the cookies blocked from storage from a
+  // received response. Only called when |devtool_request_id| is available to
+  // the URLLoader.
+  OnRawResponse(
+    int32 process_id,
+    int32 routing_id,
+    string devtool_request_id,
+    array<CookieAndLineWithStatus> cookies_with_status,
+    array<HttpRawHeaderPair> headers,
+    string? raw_response_headers);
 };
 
 // Values for configuring HTTP authentication that can only be set once.
@@ -180,8 +202,10 @@
   // behavior of NetworkService when no HttpAuthStaticParams is specified.
   array<string> supported_schemes;
 
-  // File name the GSSAPI library to load. Only supported on
-  // (OS_POSIX && !OS_ANDROID && !OS_CHROMEOS && OS_IOS) platforms.
+  // File name the GSSAPI library to load. Only supported on platforms where an
+  // external GSSAPI library is necessary for Kerberos/SPNEGO support. See the
+  // |use_external_gssapi| variable definition in //net/BUILD.gn for details on
+  // platforms where this setting is applicable.
   string gssapi_library_name;
 };
 
diff --git a/services/network/public/mojom/url_loader.mojom b/services/network/public/mojom/url_loader.mojom
index c31aab4..bffe40f 100644
--- a/services/network/public/mojom/url_loader.mojom
+++ b/services/network/public/mojom/url_loader.mojom
@@ -77,13 +77,13 @@
   kDoNotUpdate,
 
   // The updated network isolation key will take the redirected url's origin as
-  // the top frame origin and initiating frame origin.
-  kUpdateTopFrameAndInitiatingFrameOrigin,
+  // the top frame origin and frame origin.
+  kUpdateTopFrameAndFrameOrigin,
 
   // The updated network isolation key will take existing
   // |trusted_network_isolation_key|'s top frame origin and redirected url's
-  // origin as the initiating frame origin.
-  kUpdateInitiatingFrameOrigin
+  // origin as the frame origin.
+  kUpdateFrameOrigin
 };
 
 // Typemapped to network::ResourceRequest.
diff --git a/services/network/test/test_network_service_client.cc b/services/network/test/test_network_service_client.cc
index e02cc608..721da42 100644
--- a/services/network/test/test_network_service_client.cc
+++ b/services/network/test/test_network_service_client.cc
@@ -112,4 +112,19 @@
 }
 #endif
 
+void TestNetworkServiceClient::OnRawRequest(
+    int32_t process_id,
+    int32_t routing_id,
+    const std::string& devtools_request_id,
+    const net::CookieStatusList& cookies_with_status,
+    std::vector<network::mojom::HttpRawHeaderPairPtr> headers) {}
+
+void TestNetworkServiceClient::OnRawResponse(
+    int32_t process_id,
+    int32_t routing_id,
+    const std::string& devtools_request_id,
+    const net::CookieAndLineStatusList& cookies_with_status,
+    std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
+    const base::Optional<std::string>& raw_response_headers) {}
+
 }  // namespace network
diff --git a/services/network/test/test_network_service_client.h b/services/network/test/test_network_service_client.h
index 454f481..3d517d2 100644
--- a/services/network/test/test_network_service_client.h
+++ b/services/network/test/test_network_service_client.h
@@ -76,6 +76,19 @@
       const std::string& spn,
       OnGenerateHttpNegotiateAuthTokenCallback callback) override;
 #endif
+  void OnRawRequest(
+      int32_t process_id,
+      int32_t routing_id,
+      const std::string& devtools_request_id,
+      const net::CookieStatusList& cookies_with_status,
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers) override;
+  void OnRawResponse(
+      int32_t process_id,
+      int32_t routing_id,
+      const std::string& devtools_request_id,
+      const net::CookieAndLineStatusList& cookies_with_status,
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
+      const base::Optional<std::string>& raw_response_headers) override;
 
  private:
   bool upload_files_invalid_ = false;
diff --git a/services/network/url_loader.cc b/services/network/url_loader.cc
index 423fcb0..7b65fe39 100644
--- a/services/network/url_loader.cc
+++ b/services/network/url_loader.cc
@@ -18,6 +18,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
+#include "base/strings/strcat.h"
 #include "base/task/post_task.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
@@ -699,15 +700,25 @@
   // See if network isolation key needs to be updated.
   // TODO(crbug.com/979296): Consider changing this code to copy an origin
   // instead of creating one from a URL which lacks opacity information
-  // TODO(crbug.com/950069): Also add the case for kUpdateInitiatingFrameOrigin.
-  if ((update_network_isolation_key_on_redirect_ ==
-       mojom::UpdateNetworkIsolationKeyOnRedirect::
-           kUpdateTopFrameAndInitiatingFrameOrigin) &&
-      !url_request_->network_isolation_key().IsEmpty()) {
-    url::Origin new_origin = url::Origin::Create(*(deferred_redirect_url_));
-    url_request_->set_network_isolation_key(
-        net::NetworkIsolationKey(new_origin /* top frame origin */,
-                                 new_origin /* initiating frame origin */));
+  if (url_request_->network_isolation_key().IsFullyPopulated() &&
+      update_network_isolation_key_on_redirect_ !=
+          mojom::UpdateNetworkIsolationKeyOnRedirect::kDoNotUpdate) {
+    const GURL& url = new_url ? new_url.value() : *deferred_redirect_url_;
+    const url::Origin& new_origin = url::Origin::Create(url);
+
+    if (update_network_isolation_key_on_redirect_ ==
+        mojom::UpdateNetworkIsolationKeyOnRedirect::
+            kUpdateTopFrameAndFrameOrigin) {
+      url_request_->set_network_isolation_key(net::NetworkIsolationKey(
+          new_origin /* top frame origin */, new_origin /* frame origin */));
+    } else if (update_network_isolation_key_on_redirect_ ==
+               mojom::UpdateNetworkIsolationKeyOnRedirect::kUpdateFrameOrigin) {
+      base::Optional<url::Origin> top_frame_origin =
+          url_request_->network_isolation_key().GetTopFrameOrigin();
+      DCHECK(top_frame_origin);
+      url_request_->set_network_isolation_key(
+          net::NetworkIsolationKey(top_frame_origin.value(), new_origin));
+    }
   }
 
   deferred_redirect_url_.reset();
@@ -1397,6 +1408,23 @@
 
 void URLLoader::SetRawRequestHeadersAndNotify(
     net::HttpRawRequestHeaders headers) {
+  if (network_service_client_ && devtools_request_id()) {
+    std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
+    header_array.reserve(headers.headers().size());
+
+    for (const auto& header : headers.headers()) {
+      network::mojom::HttpRawHeaderPairPtr pair =
+          network::mojom::HttpRawHeaderPair::New();
+      pair->key = header.first;
+      pair->value = header.second;
+      header_array.push_back(std::move(pair));
+    }
+
+    network_service_client_->OnRawRequest(
+        GetProcessId(), GetRenderFrameId(), devtools_request_id().value(),
+        url_request_->maybe_sent_cookies(), std::move(header_array));
+  }
+
   if (network_context_client_) {
     net::CookieStatusList reported_cookies;
     for (const auto& cookie_and_status : url_request_->maybe_sent_cookies()) {
@@ -1414,8 +1442,6 @@
     }
   }
 
-  // TODO(crbug.com/856777): Add OnRawRequest once implemented
-
   if (want_raw_headers_)
     raw_request_headers_.Assign(std::move(headers));
 }
@@ -1578,6 +1604,39 @@
 }
 
 void URLLoader::ReportFlaggedResponseCookies() {
+  if (network_service_client_ && devtools_request_id() &&
+      url_request_->response_headers()) {
+    std::vector<network::mojom::HttpRawHeaderPairPtr> header_array;
+
+    size_t iterator = 0;
+    std::string name, value;
+    while (url_request_->response_headers()->EnumerateHeaderLines(
+        &iterator, &name, &value)) {
+      network::mojom::HttpRawHeaderPairPtr pair =
+          network::mojom::HttpRawHeaderPair::New();
+      pair->key = name;
+      pair->value = value;
+      header_array.push_back(std::move(pair));
+    }
+
+    // Only send the "raw" header text when the headers were actually send in
+    // text form (i.e. not QUIC or SPDY)
+    base::Optional<std::string> raw_response_headers;
+
+    const net::HttpResponseInfo& response_info = url_request_->response_info();
+
+    if (!response_info.DidUseQuic() && !response_info.was_fetched_via_spdy) {
+      raw_response_headers =
+          base::make_optional(net::HttpUtil::ConvertHeadersBackToHTTPResponse(
+              url_request_->response_headers()->raw_headers()));
+    }
+
+    network_service_client_->OnRawResponse(
+        GetProcessId(), GetRenderFrameId(), devtools_request_id().value(),
+        url_request_->maybe_stored_cookies(), std::move(header_array),
+        raw_response_headers);
+  }
+
   if (network_context_client_) {
     net::CookieStatusList reported_cookies;
     for (const auto& cookie_line_and_status :
@@ -1596,9 +1655,6 @@
           reported_cookies);
     }
   }
-
-  // TODO(crbug.com/856777): add OnRawResponse once implemented.
-  // (might want to change method name at that point).
 }
 
 }  // namespace network
diff --git a/services/network/url_loader_unittest.cc b/services/network/url_loader_unittest.cc
index d606496..52be1d67 100644
--- a/services/network/url_loader_unittest.cc
+++ b/services/network/url_loader_unittest.cc
@@ -41,6 +41,7 @@
 #include "net/base/load_flags.h"
 #include "net/base/mime_sniffer.h"
 #include "net/base/net_errors.h"
+#include "net/cert/test_root_certs.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/http/http_response_info.h"
 #include "net/ssl/client_cert_identity_test_util.h"
@@ -48,6 +49,7 @@
 #include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "net/test/quic_simple_test_server.h"
 #include "net/test/test_data_directory.h"
 #include "net/test/url_request/url_request_failed_job.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
@@ -385,7 +387,19 @@
       : scoped_task_environment_(
             base::test::ScopedTaskEnvironment::MainThreadType::IO),
         resource_scheduler_(true) {
+    net::TestRootCerts* root_certs = net::TestRootCerts::GetInstance();
+    root_certs->AddFromFile(
+        net::GetTestCertsDirectory().AppendASCII("quic-root.pem"));
+
+    net::QuicSimpleTestServer::Start();
+    net::HttpNetworkSession::Params params;
+    params.quic_params.origins_to_force_quic_on.insert(
+        net::HostPortPair(net::QuicSimpleTestServer::GetHost(),
+                          net::QuicSimpleTestServer::GetPort()));
+    params.enable_quic = true;
+
     net::URLRequestContextBuilder context_builder;
+    context_builder.set_http_network_session_params(params);
     context_builder.set_proxy_resolution_service(
         net::ProxyResolutionService::CreateDirect());
     auto test_network_delegate = std::make_unique<net::TestNetworkDelegate>();
@@ -413,11 +427,13 @@
     // the loopback address and will let us access |test_server_|.
     scoped_refptr<net::RuleBasedHostResolverProc> mock_resolver_proc =
         base::MakeRefCounted<net::RuleBasedHostResolverProc>(nullptr);
-    mock_resolver_proc->AddRule(kInsecureHost, "127.0.0.1");
+    mock_resolver_proc->AddRule("*", "127.0.0.1");
     mock_host_resolver_ = std::make_unique<net::ScopedDefaultHostResolverProc>(
         mock_resolver_proc.get());
   }
 
+  void TearDown() override { net::QuicSimpleTestServer::Shutdown(); }
+
   // Attempts to load |url| and returns the resulting error code. If |body| is
   // non-NULL, also attempts to read the response body. The advantage of using
   // |body| instead of calling ReadBody() after Load is that it will load the
@@ -746,63 +762,6 @@
   TestURLLoaderClient client_;
 };
 
-class URLLoaderNetworkIsolationTest : public URLLoaderTest {
- protected:
-  void SetUp() override {
-    feature_list_.InitAndEnableFeature(
-        net::features::kSplitCacheByNetworkIsolationKey);
-    URLLoaderTest::SetUp();
-  }
-
-  void LoadAndVerifyCached(const GURL& url,
-                           const net::NetworkIsolationKey& key,
-                           bool was_cached,
-                           bool is_navigation,
-                           bool expect_redirect = false) {
-    ResourceRequest request = CreateResourceRequest("GET", url);
-    request.load_flags |= net::LOAD_SKIP_CACHE_VALIDATION;
-
-    TestURLLoaderClient client;
-    base::RunLoop delete_run_loop;
-    mojom::URLLoaderPtr loader;
-    std::unique_ptr<URLLoader> url_loader;
-    mojom::URLLoaderFactoryParams params;
-    params.process_id = mojom::kBrowserProcessId;
-    params.is_corb_enabled = false;
-
-    if (is_navigation) {
-      request.trusted_network_isolation_key = key;
-      request.update_network_isolation_key_on_redirect =
-          mojom::UpdateNetworkIsolationKeyOnRedirect::
-              kUpdateTopFrameAndInitiatingFrameOrigin;
-    } else {
-      params.network_isolation_key = key;
-    }
-    url_loader = std::make_unique<URLLoader>(
-        context(), nullptr /* network_service_client */,
-        nullptr /* network_context_client */,
-        DeleteLoaderCallback(&delete_run_loop, &url_loader),
-        mojo::MakeRequest(&loader), 0, request, client.CreateInterfacePtr(),
-        TRAFFIC_ANNOTATION_FOR_TESTS, &params, 0 /* request_id */,
-        resource_scheduler_client(), nullptr,
-        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
-
-    if (expect_redirect) {
-      client.RunUntilRedirectReceived();
-      loader->FollowRedirect({}, {}, base::nullopt);
-    }
-
-    client.RunUntilComplete();
-    delete_run_loop.Run();
-
-    EXPECT_EQ(net::OK, client.completion_status().error_code);
-    EXPECT_EQ(was_cached, client.completion_status().exists_in_cache);
-  }
-
- private:
-  base::test::ScopedFeatureList feature_list_;
-};
-
 constexpr int URLLoaderTest::kProcessId;
 constexpr int URLLoaderTest::kRouteId;
 
@@ -2559,6 +2518,65 @@
     ++on_certificate_requested_counter_;
   }
 
+  void OnRawRequest(
+      int32_t process_id,
+      int32_t routing_id,
+      const std::string& devtools_request_id,
+      const net::CookieStatusList& cookies_with_status,
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers) override {
+    raw_request_cookies_.insert(raw_request_cookies_.end(),
+                                cookies_with_status.begin(),
+                                cookies_with_status.end());
+
+    devtools_request_id_ = devtools_request_id;
+
+    if (wait_for_raw_request_ &&
+        raw_request_cookies_.size() >= wait_for_raw_request_goal_) {
+      std::move(wait_for_raw_request_).Run();
+    }
+  }
+
+  void OnRawResponse(
+      int32_t process_id,
+      int32_t routing_id,
+      const std::string& devtools_request_id,
+      const net::CookieAndLineStatusList& cookies_with_status,
+      std::vector<network::mojom::HttpRawHeaderPairPtr> headers,
+      const base::Optional<std::string>& raw_response_headers) override {
+    raw_response_cookies_.insert(raw_response_cookies_.end(),
+                                 cookies_with_status.begin(),
+                                 cookies_with_status.end());
+
+    devtools_request_id_ = devtools_request_id;
+
+    raw_response_headers_ = raw_response_headers;
+
+    if (wait_for_raw_response_ &&
+        raw_response_cookies_.size() >= wait_for_raw_response_goal_) {
+      std::move(wait_for_raw_response_).Run();
+    }
+  }
+
+  void WaitUntilRawResponse(size_t goal) {
+    if (raw_response_cookies_.size() < goal) {
+      wait_for_raw_response_goal_ = goal;
+      base::RunLoop run_loop;
+      wait_for_raw_response_ = run_loop.QuitClosure();
+      run_loop.Run();
+    }
+    EXPECT_EQ(goal, raw_response_cookies_.size());
+  }
+
+  void WaitUntilRawRequest(size_t goal) {
+    if (raw_request_cookies_.size() < goal) {
+      wait_for_raw_request_goal_ = goal;
+      base::RunLoop run_loop;
+      wait_for_raw_request_ = run_loop.QuitClosure();
+      run_loop.Run();
+    }
+    EXPECT_EQ(goal, raw_request_cookies_.size());
+  }
+
   void set_credentials_response(CredentialsResponse credentials_response) {
     credentials_response_ = credentials_response;
   }
@@ -2595,6 +2613,20 @@
     return on_certificate_requested_counter_;
   }
 
+  const net::CookieAndLineStatusList& raw_response_cookies() const {
+    return raw_response_cookies_;
+  }
+
+  const net::CookieStatusList& raw_request_cookies() const {
+    return raw_request_cookies_;
+  }
+
+  const std::string devtools_request_id() { return devtools_request_id_; }
+
+  const base::Optional<std::string> raw_response_headers() {
+    return raw_response_headers_;
+  }
+
  private:
   CredentialsResponse credentials_response_ =
       CredentialsResponse::NO_CREDENTIALS;
@@ -2609,6 +2641,15 @@
   std::string provider_name_;
   std::vector<uint16_t> algorithm_preferences_;
   int on_certificate_requested_counter_ = 0;
+  net::CookieAndLineStatusList raw_response_cookies_;
+  base::OnceClosure wait_for_raw_response_;
+  size_t wait_for_raw_response_goal_ = 0u;
+  std::string devtools_request_id_;
+  base::Optional<std::string> raw_response_headers_;
+
+  net::CookieStatusList raw_request_cookies_;
+  base::OnceClosure wait_for_raw_request_;
+  size_t wait_for_raw_request_goal_ = 0u;
 
   DISALLOW_COPY_AND_ASSIGN(MockNetworkServiceClient);
 };
@@ -3066,95 +3107,6 @@
   delete_run_loop.Run();
 }
 
-TEST_F(URLLoaderNetworkIsolationTest, CachedUsingNetworkIsolationKey) {
-  GURL url = test_server()->GetURL("/resource");
-  url::Origin origin_a = url::Origin::Create(GURL("http://a.test/"));
-  net::NetworkIsolationKey key_a(origin_a, origin_a);
-  LoadAndVerifyCached(url, key_a, false /* was_cached */,
-                      false /* is_navigation */);
-
-  // Load again with a different isolation key. The cached entry should not be
-  // loaded.
-  url::Origin origin_b = url::Origin::Create(GURL("http://b.test/"));
-  net::NetworkIsolationKey key_b(origin_b, origin_b);
-  LoadAndVerifyCached(url, key_b, false /* was_cached */,
-                      false /* is_navigation */);
-
-  // Load again with the same isolation key. The cached entry should be loaded.
-  LoadAndVerifyCached(url, key_b, true /* was_cached */,
-                      false /* is_navigation */);
-}
-
-TEST_F(URLLoaderNetworkIsolationTest,
-       NavigationResourceCachedUsingNetworkIsolationKey) {
-  GURL url = test_server()->GetURL("othersite.test", "/main.html");
-  url::Origin origin_a = url::Origin::Create(url);
-  net::NetworkIsolationKey key_a(origin_a, origin_a);
-  LoadAndVerifyCached(url, key_a, false /* was_cached */,
-                      true /* is_navigation */);
-
-  // Load again with a different isolation key. The cached entry should not be
-  // loaded.
-  GURL url_b = test_server()->GetURL("/main.html");
-  url::Origin origin_b = url::Origin::Create(url_b);
-  net::NetworkIsolationKey key_b(origin_b, origin_b);
-  LoadAndVerifyCached(url_b, key_b, false /* was_cached */,
-                      true /* is_navigation */);
-
-  // Load again with the same isolation key. The cached entry should be loaded.
-  LoadAndVerifyCached(url_b, key_b, true /* was_cached */,
-                      true /* is_navigation */);
-}
-
-TEST_F(URLLoaderNetworkIsolationTest,
-       CachedUsingNetworkIsolationKeyWithFrameOrigin) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitWithFeatures(
-      {net::features::kSplitCacheByNetworkIsolationKey,
-       net::features::kAppendFrameOriginToNetworkIsolationKey},
-      {});
-
-  GURL url = test_server()->GetURL("/resource");
-  url::Origin origin_a = url::Origin::Create(GURL("http://a.test/"));
-  net::NetworkIsolationKey key_a(origin_a, origin_a);
-  LoadAndVerifyCached(url, key_a, false /* was_cached */,
-                      false /* is_navigation */);
-
-  // Load again with a different isolation key. The cached entry should not be
-  // loaded.
-  url::Origin origin_b = url::Origin::Create(GURL("http://b.test/"));
-  net::NetworkIsolationKey key_b(origin_a, origin_b);
-  LoadAndVerifyCached(url, key_b, false /* was_cached */,
-                      false /* is_navigation */);
-}
-
-TEST_F(URLLoaderNetworkIsolationTest,
-       NavigationResourceRedirectNetworkIsolationKey) {
-  // Create a request that redirects to d.com/title1.html.
-  GURL url = test_server()->GetURL(
-      "/server-redirect-301?" +
-      test_server()->GetURL("othersite.test", "/title1.html").spec());
-  url::Origin origin = url::Origin::Create(url);
-  net::NetworkIsolationKey key(origin, origin);
-  LoadAndVerifyCached(url, key, false /* was_cached */,
-                      true /* is_navigation */, true /* expect_redirect */);
-
-  // Now directly load now with the key using the redirected URL. This should be
-  // a cache hit.
-  GURL redirected_url = test_server()->GetURL("othersite.test", "/title1.html");
-  url::Origin redirected_origin = url::Origin::Create(redirected_url);
-  LoadAndVerifyCached(
-      redirected_url,
-      net::NetworkIsolationKey(redirected_origin, redirected_origin),
-      true /* was_cached */, true /* is_navigation */);
-
-  // A non-navigation resource with the same key and url should also be cached.
-  LoadAndVerifyCached(
-      redirected_url,
-      net::NetworkIsolationKey(redirected_origin, redirected_origin),
-      true /* was_cached */, false /* is_navigation */);
-}
-
 class TestSSLPrivateKey : public net::SSLPrivateKey {
  public:
   explicit TestSSLPrivateKey(scoped_refptr<net::SSLPrivateKey> key)
@@ -3783,6 +3735,384 @@
   }
 }
 
+TEST_F(URLLoaderTest, RawRequestCookies) {
+  MockNetworkServiceClient network_service_client;
+  MockNetworkContextClient network_context_client;
+  {
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest(
+        "GET", test_server()->GetURL("/echoheader?cookie"));
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    context()->cookie_store()->SetCookieWithOptionsAsync(
+        test_server()->GetURL("/"), "a=b", net::CookieOptions(),
+        base::DoNothing());
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    delete_run_loop.Run();
+    loader_client.RunUntilComplete();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawRequest(1u);
+    EXPECT_EQ("a",
+              network_service_client.raw_request_cookies()[0].cookie.Name());
+    EXPECT_EQ("b",
+              network_service_client.raw_request_cookies()[0].cookie.Value());
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::INCLUDE,
+              network_service_client.raw_request_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+  }
+}
+
+TEST_F(URLLoaderTest, RawRequestCookiesFlagged) {
+  MockNetworkServiceClient network_service_client;
+  MockNetworkContextClient network_context_client;
+  {
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest(
+        "GET", test_server()->GetURL("/echoheader?cookie"));
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    // Set the path to an irrelevant url to block the cookie from sending
+    context()->cookie_store()->SetCookieWithOptionsAsync(
+        test_server()->GetURL("/"), "a=b;Path=/something-else",
+        net::CookieOptions(), base::DoNothing());
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    delete_run_loop.Run();
+    loader_client.RunUntilComplete();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawRequest(1u);
+    EXPECT_EQ("a",
+              network_service_client.raw_request_cookies()[0].cookie.Name());
+    EXPECT_EQ("b",
+              network_service_client.raw_request_cookies()[0].cookie.Value());
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_NOT_ON_PATH,
+              network_service_client.raw_request_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+  }
+}
+
+TEST_F(URLLoaderTest, RawResponseCookies) {
+  MockNetworkServiceClient network_service_client;
+  MockNetworkContextClient network_context_client;
+  {
+    TestURLLoaderClient loader_client;
+    ResourceRequest request =
+        CreateResourceRequest("GET", test_server()->GetURL("/set-cookie?a=b"));
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    delete_run_loop.Run();
+    loader_client.RunUntilComplete();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawResponse(1u);
+    EXPECT_EQ("a",
+              network_service_client.raw_response_cookies()[0].cookie->Name());
+    EXPECT_EQ("b",
+              network_service_client.raw_response_cookies()[0].cookie->Value());
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::INCLUDE,
+              network_service_client.raw_response_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+
+    ASSERT_TRUE(network_service_client.raw_response_headers());
+    EXPECT_NE(
+        network_service_client.raw_response_headers()->find("Set-Cookie: a=b"),
+        std::string::npos);
+  }
+}
+
+TEST_F(URLLoaderTest, RawResponseCookiesInvalid) {
+  MockNetworkServiceClient network_service_client;
+  MockNetworkContextClient network_context_client;
+  {
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest(
+        "GET", test_server()->GetURL("/set-invalid-cookie"));
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    delete_run_loop.Run();
+    loader_client.RunUntilComplete();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawResponse(1u);
+    // On these failures the cookie object is not created
+    EXPECT_FALSE(network_service_client.raw_response_cookies()[0].cookie);
+    EXPECT_EQ(
+        net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE,
+        network_service_client.raw_response_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+  }
+}
+
+TEST_F(URLLoaderTest, RawResponseCookiesRedirect) {
+  // Check a valid cookie
+  {
+    MockNetworkServiceClient network_service_client;
+    MockNetworkContextClient network_context_client;
+    GURL dest_url = test_server()->GetURL("/nocontent");
+    GURL redirecting_url = test_server()->GetURL(
+        "/server-redirect-with-cookie?" + dest_url.spec());
+
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest("GET", redirecting_url);
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    loader_client.RunUntilRedirectReceived();
+
+    ASSERT_TRUE(network_service_client.raw_response_headers());
+    EXPECT_NE(network_service_client.raw_response_headers()->find(
+                  "Set-Cookie: server-redirect=true"),
+              std::string::npos);
+
+    loader->FollowRedirect({}, {}, base::nullopt);
+    loader_client.RunUntilComplete();
+    delete_run_loop.Run();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawResponse(1u);
+    EXPECT_EQ("server-redirect",
+              network_service_client.raw_response_cookies()[0].cookie->Name());
+    EXPECT_EQ("true",
+              network_service_client.raw_response_cookies()[0].cookie->Value());
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::INCLUDE,
+              network_service_client.raw_response_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+  }
+
+  // Check a flagged cookie (secure cookie over an insecure connection)
+  {
+    MockNetworkServiceClient network_service_client;
+    MockNetworkContextClient network_context_client;
+    GURL dest_url = test_server()->GetURL("/nocontent");
+    GURL redirecting_url = test_server()->GetURL(
+        "/server-redirect-with-secure-cookie?" + dest_url.spec());
+
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest("GET", redirecting_url);
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    loader_client.RunUntilRedirectReceived();
+    loader->FollowRedirect({}, {}, base::nullopt);
+    loader_client.RunUntilComplete();
+    delete_run_loop.Run();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawResponse(1u);
+    // On these failures the cookie object is not created
+    EXPECT_FALSE(network_service_client.raw_response_cookies()[0].cookie);
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_SECURE_ONLY,
+              network_service_client.raw_response_cookies()[0].status);
+  }
+}
+
+TEST_F(URLLoaderTest, RawResponseCookiesAuth) {
+  // Check a valid cookie
+  {
+    MockNetworkServiceClient network_service_client;
+    network_service_client.set_credentials_response(
+        MockNetworkServiceClient::CredentialsResponse::NO_CREDENTIALS);
+    MockNetworkContextClient network_context_client;
+
+    GURL url = test_server()->GetURL(
+        "/auth-basic?set-cookie-if-challenged&password=PASS");
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest("GET", url);
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    loader_client.RunUntilComplete();
+    delete_run_loop.Run();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawResponse(1u);
+    EXPECT_EQ("got_challenged",
+              network_service_client.raw_response_cookies()[0].cookie->Name());
+    EXPECT_EQ("true",
+              network_service_client.raw_response_cookies()[0].cookie->Value());
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::INCLUDE,
+              network_service_client.raw_response_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+  }
+
+  // Check a flagged cookie (secure cookie from insecure connection)
+  {
+    MockNetworkServiceClient network_service_client;
+    network_service_client.set_credentials_response(
+        MockNetworkServiceClient::CredentialsResponse::NO_CREDENTIALS);
+    MockNetworkContextClient network_context_client;
+
+    GURL url = test_server()->GetURL(
+        "/auth-basic?set-secure-cookie-if-challenged&password=PASS");
+    TestURLLoaderClient loader_client;
+    ResourceRequest request = CreateResourceRequest("GET", url);
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    loader_client.RunUntilComplete();
+    delete_run_loop.Run();
+    EXPECT_EQ(net::OK, loader_client.completion_status().error_code);
+    network_service_client.WaitUntilRawResponse(1u);
+    // On these failures the cookie object is not created
+    EXPECT_FALSE(network_service_client.raw_response_cookies()[0].cookie);
+    EXPECT_EQ(net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_SECURE_ONLY,
+              network_service_client.raw_response_cookies()[0].status);
+
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+  }
+}
+
+TEST_F(URLLoaderTest, RawResponseQUIC) {
+  MockNetworkServiceClient network_service_client;
+  MockNetworkContextClient network_context_client;
+  {
+    TestURLLoaderClient loader_client;
+    ResourceRequest request =
+        CreateResourceRequest("GET", net::QuicSimpleTestServer::GetFileURL(""));
+
+    // Set the devtools id to trigger the RawResponse call
+    request.devtools_request_id = "TEST";
+
+    base::RunLoop delete_run_loop;
+    mojom::URLLoaderPtr loader;
+    mojom::URLLoaderFactoryParams params;
+    params.process_id = kProcessId;
+    params.is_corb_enabled = false;
+    std::unique_ptr<URLLoader> url_loader = std::make_unique<URLLoader>(
+        context(), &network_service_client, &network_context_client,
+        DeleteLoaderCallback(&delete_run_loop, &url_loader),
+        mojo::MakeRequest(&loader), mojom::kURLLoadOptionNone, request,
+        loader_client.CreateInterfacePtr(), TRAFFIC_ANNOTATION_FOR_TESTS,
+        &params, 0 /* request_id */, resource_scheduler_client(), nullptr,
+        nullptr /* network_usage_accumulator */, nullptr /* header_client */);
+
+    delete_run_loop.Run();
+    loader_client.RunUntilComplete();
+    ASSERT_EQ(net::OK, loader_client.completion_status().error_code);
+
+    network_service_client.WaitUntilRawResponse(0u);
+    EXPECT_EQ("TEST", network_service_client.devtools_request_id());
+
+    // QUIC responses don't have raw header text, so there shouldn't be any here
+    EXPECT_FALSE(network_service_client.raw_response_headers());
+  }
+}
+
 TEST_F(URLLoaderTest, CookieReportingCategories) {
   MockNetworkServiceClient network_service_client;
 
diff --git a/testing/libfuzzer/getting_started.md b/testing/libfuzzer/getting_started.md
index 8de58a84..ee68f2b 100644
--- a/testing/libfuzzer/getting_started.md
+++ b/testing/libfuzzer/getting_started.md
@@ -117,6 +117,10 @@
 For more information about libFuzzer's output, please refer to [its own
 documentation].
 
+*Note*: if you observe an `odr-violation` error in the log, please try setting
+the following environment variable: `ASAN_OPTIONS=detect_odr_violation=0` and
+running the fuzz target again.
+
 ### Symbolize Stacktrace
 
 If your fuzz target crashes when running locally and you see non-symbolized
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index b18b23e..babae7e 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -498,7 +498,6 @@
     "web/web_settings.h",
     "web/web_shared_worker.h",
     "web/web_shared_worker_client.h",
-    "web/web_surrounding_text.h",
     "web/web_testing_support.h",
     "web/web_text_check_client.h",
     "web/web_text_checking_completion.h",
diff --git a/third_party/blink/public/mojom/notifications/notification_service.mojom b/third_party/blink/public/mojom/notifications/notification_service.mojom
index c471479..fac9b0c 100644
--- a/third_party/blink/public/mojom/notifications/notification_service.mojom
+++ b/third_party/blink/public/mojom/notifications/notification_service.mojom
@@ -47,7 +47,7 @@
         string token,
         NotificationData notification_data,
         NotificationResources notification_resources,
-        NonPersistentNotificationListener event_listener);
+        pending_remote<NonPersistentNotificationListener> event_listener);
 
   // Closes a notification that is not associated with a service worker.
   // |token| identifies which notification should be closed (must be non-empty).
diff --git a/third_party/blink/public/mojom/worker/dedicated_worker_host_factory.mojom b/third_party/blink/public/mojom/worker/dedicated_worker_host_factory.mojom
index 741f242..70f2cb8 100644
--- a/third_party/blink/public/mojom/worker/dedicated_worker_host_factory.mojom
+++ b/third_party/blink/public/mojom/worker/dedicated_worker_host_factory.mojom
@@ -35,7 +35,11 @@
   OnScriptLoadStarted(
       // The info about the service worker provider in the browser process that
       // provides support for this worker to be a service worker client.
-      ServiceWorkerProviderInfoForClient service_worker_provider_info,
+      //
+      // This is null if the dedicated worker cannot be a service
+      // worker client, because for example, the worker's URL is
+      // not http(s) or another service worker supported scheme.
+      ServiceWorkerProviderInfoForClient? service_worker_provider_info,
 
       // Used for passing the main script pre-requested by the browser process
       // and its redirect information.
diff --git a/third_party/blink/public/mojom/worker/shared_worker_factory.mojom b/third_party/blink/public/mojom/worker/shared_worker_factory.mojom
index 78989a0..c05ed08 100644
--- a/third_party/blink/public/mojom/worker/shared_worker_factory.mojom
+++ b/third_party/blink/public/mojom/worker/shared_worker_factory.mojom
@@ -40,7 +40,11 @@
 
       // The info about the service worker provider in the browser process that
       // provides support for this worker to be a service worker client.
-      ServiceWorkerProviderInfoForClient service_worker_provider_info,
+      //
+      // This is null if the shared worker cannot be a service
+      // worker client, because for example, the worker's URL is
+      // not http(s) or another service worker supported scheme.
+      ServiceWorkerProviderInfoForClient? service_worker_provider_info,
 
       // The ID of the AppCacheHost in the browser process that serves resources
       // for this shared worker. This is not specified when AppCache doesn't
diff --git a/third_party/blink/public/platform/web_layer_tree_view.h b/third_party/blink/public/platform/web_layer_tree_view.h
index ad83def8..30eb30c 100644
--- a/third_party/blink/public/platform/web_layer_tree_view.h
+++ b/third_party/blink/public/platform/web_layer_tree_view.h
@@ -68,15 +68,12 @@
                                         bool shrink_viewport) {}
 
   // Input properties ---------------------------------------------------
-  virtual void SetHaveScrollEventHandlers(bool) {}
 
   // Returns the FrameSinkId of the widget associated with this layer tree view.
   virtual viz::FrameSinkId GetFrameSinkId() { return viz::FrameSinkId(); }
 
   // Debugging / dangerous ---------------------------------------------
 
-  virtual bool HaveScrollEventHandlers() const { return false; }
-
   virtual int LayerTreeId() const { return 0; }
 
   virtual void RequestBeginMainFrameNotExpected(bool new_state) {}
diff --git a/third_party/blink/public/web/web_widget_client.h b/third_party/blink/public/web/web_widget_client.h
index c8b46daa..94d0749 100644
--- a/third_party/blink/public/web/web_widget_client.h
+++ b/third_party/blink/public/web/web_widget_client.h
@@ -193,6 +193,9 @@
   // Called to update if touch events should be sent.
   virtual void SetHasTouchEventHandlers(bool) {}
 
+  // Called to update if scroll events should be sent.
+  virtual void SetHaveScrollEventHandlers(bool) {}
+
   // Called to update whether low latency input mode is enabled or not.
   virtual void SetNeedsLowLatencyInput(bool) {}
 
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 9785869e..3b859313 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -1165,7 +1165,6 @@
     "exported/web_scoped_window_focus_allowed_indicator_test.cc",
     "exported/web_searchable_form_data_test.cc",
     "exported/web_selector_test.cc",
-    "exported/web_surrounding_text_test.cc",
     "exported/web_user_gesture_token_test.cc",
     "exported/web_view_test.cc",
     "feature_policy/feature_policy_test.cc",
@@ -1454,6 +1453,7 @@
     "page/touch_adjustment_test.cc",
     "page/viewport_test.cc",
     "page/window_features_test.cc",
+    "page/zoom_test.cc",
     "paint/block_painter_test.cc",
     "paint/box_paint_invalidator_test.cc",
     "paint/box_painter_test.cc",
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.cc b/third_party/blink/renderer/core/animation/compositor_animations.cc
index dd07a8d..ad9f756c 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations.cc
@@ -35,6 +35,7 @@
 #include <memory>
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/core/animation/animation_effect.h"
+#include "third_party/blink/renderer/core/animation/css/compositor_keyframe_color.h"
 #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_double.h"
 #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_filter_operations.h"
 #include "third_party/blink/renderer/core/animation/css/compositor_keyframe_transform.h"
@@ -51,6 +52,7 @@
 #include "third_party/blink/renderer/core/paint/paint_layer.h"
 #include "third_party/blink/renderer/platform/animation/animation_translation_util.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation.h"
+#include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h"
 #include "third_party/blink/renderer/platform/animation/compositor_filter_animation_curve.h"
 #include "third_party/blink/renderer/platform/animation/compositor_filter_keyframe.h"
 #include "third_party/blink/renderer/platform/animation/compositor_float_animation_curve.h"
@@ -265,14 +267,20 @@
           // Backdrop-filter pixel moving filters do not change the layer bounds
           // like regular filters do, so they can still be composited.
           break;
-        case CSSPropertyID::kVariable:
+        case CSSPropertyID::kVariable: {
           // Custom properties are supported only in the case of
           // OffMainThreadCSSPaintEnabled, and even then only for some specific
           // property types. Otherwise they are treated as unsupported.
-          if (keyframe->GetCompositorKeyframeValue()) {
+          const CompositorKeyframeValue* keyframe_value =
+              keyframe->GetCompositorKeyframeValue();
+          if (keyframe_value) {
             DCHECK(RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled());
-            DCHECK(keyframe->GetCompositorKeyframeValue()->IsDouble() ||
-                   keyframe->GetCompositorKeyframeValue()->IsColor());
+            DCHECK(keyframe_value->IsDouble() || keyframe_value->IsColor());
+            // TODO: Add support for keyframes containing different types
+            if (keyframes.front()->GetCompositorKeyframeValue()->GetType() !=
+                keyframe_value->GetType()) {
+              reasons |= kMixedKeyframeValueTypes;
+            }
           } else {
             // We skip the rest of the loop in this case for the same reason as
             // unsupported CSS properties - see below.
@@ -280,6 +288,7 @@
             continue;
           }
           break;
+        }
         default:
           // We skip the rest of the loop in this case for two reasons:
           //   i.  Getting a CompositorElementId below will DCHECK if we pass it
@@ -587,6 +596,16 @@
   curve.AddKeyframe(float_keyframe);
 }
 
+void AddKeyframeToCurve(CompositorColorAnimationCurve& curve,
+                        Keyframe::PropertySpecificKeyframe* keyframe,
+                        const CompositorKeyframeValue* value,
+                        const TimingFunction& keyframe_timing_function) {
+  CompositorColorKeyframe color_keyframe(
+      keyframe->Offset(), ToCompositorKeyframeColor(value)->ToColor(),
+      keyframe_timing_function);
+  curve.AddKeyframe(color_keyframe);
+}
+
 void AddKeyframeToCurve(CompositorTransformAnimationCurve& curve,
                         Keyframe::PropertySpecificKeyframe* keyframe,
                         const CompositorKeyframeValue* value,
@@ -693,12 +712,21 @@
         DCHECK(RuntimeEnabledFeatures::OffMainThreadCSSPaintEnabled());
         custom_property_name = property.CustomPropertyName();
         target_property = compositor_target_property::CSS_CUSTOM_PROPERTY;
-        // TODO(kevers): Extend support to non-float types.
-        auto float_curve = std::make_unique<CompositorFloatAnimationCurve>();
-        AddKeyframesToCurve(*float_curve, values);
-        float_curve->SetTimingFunction(*timing.timing_function);
-        float_curve->SetScaledDuration(scale);
-        curve = std::move(float_curve);
+
+        // Create curve based on the keyframe value type
+        if (values.front()->GetCompositorKeyframeValue()->IsColor()) {
+          auto color_curve = std::make_unique<CompositorColorAnimationCurve>();
+          AddKeyframesToCurve(*color_curve, values);
+          color_curve->SetTimingFunction(*timing.timing_function);
+          color_curve->SetScaledDuration(scale);
+          curve = std::move(color_curve);
+        } else {
+          auto float_curve = std::make_unique<CompositorFloatAnimationCurve>();
+          AddKeyframesToCurve(*float_curve, values);
+          float_curve->SetTimingFunction(*timing.timing_function);
+          float_curve->SetScaledDuration(scale);
+          curve = std::move(float_curve);
+        }
         break;
       }
       default:
diff --git a/third_party/blink/renderer/core/animation/compositor_animations.h b/third_party/blink/renderer/core/animation/compositor_animations.h
index 1609de2..3b222c9 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations.h
+++ b/third_party/blink/renderer/core/animation/compositor_animations.h
@@ -90,12 +90,13 @@
     kFilterRelatedPropertyMayMovePixels = 1 << 12,
     kUnsupportedCSSProperty = 1 << 13,
     kMultipleTransformAnimationsOnSameTarget = 1 << 14,
+    kMixedKeyframeValueTypes = 1 << 15,
 
     // The maximum number of flags in this enum (excluding itself). New flags
     // should increment this number but it should never be decremented because
     // the values are used in UMA histograms. It should also be noted that it
     // excludes the kNoFailure value.
-    kFailureReasonCount = 15,
+    kFailureReasonCount = 16,
   };
 
   static FailureReasons CheckCanStartAnimationOnCompositor(
diff --git a/third_party/blink/renderer/core/animation/compositor_animations_test.cc b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
index f594f0e..f3ce839 100644
--- a/third_party/blink/renderer/core/animation/compositor_animations_test.cc
+++ b/third_party/blink/renderer/core/animation/compositor_animations_test.cc
@@ -60,6 +60,7 @@
 #include "third_party/blink/renderer/core/style/filter_operations.h"
 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
 #include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
+#include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h"
 #include "third_party/blink/renderer/platform/animation/compositor_float_animation_curve.h"
 #include "third_party/blink/renderer/platform/animation/compositor_float_keyframe.h"
 #include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
@@ -1585,6 +1586,54 @@
 }
 
 TEST_P(AnimationCompositorAnimationsTest,
+       CreateSimpleCustomColorPropertyAnimation) {
+  ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
+
+  RegisterProperty(GetDocument(), "--foo", "<color>", "rgb(0, 0, 0)", false);
+  SetCustomProperty("--foo", "rgb(0, 0, 0)");
+
+  StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
+      CreateReplaceOpKeyframe("--foo", "rgb(0, 0, 0)", 0),
+      CreateReplaceOpKeyframe("--foo", "rgb(0, 255, 0)", 1.0));
+
+  std::unique_ptr<CompositorKeyframeModel> keyframe_model =
+      ConvertToCompositorAnimation(*effect);
+  EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY,
+            keyframe_model->TargetProperty());
+
+  std::unique_ptr<CompositorColorAnimationCurve> keyframed_color_curve =
+      keyframe_model->ColorCurveForTesting();
+
+  CompositorColorAnimationCurve::Keyframes keyframes =
+      keyframed_color_curve->KeyframesForTesting();
+  ASSERT_EQ(2UL, keyframes.size());
+
+  EXPECT_EQ(0, keyframes[0]->Time());
+  EXPECT_EQ(SkColorSetRGB(0, 0, 0), keyframes[0]->Value());
+  EXPECT_EQ(TimingFunction::Type::LINEAR,
+            keyframes[0]->GetTimingFunctionForTesting()->GetType());
+
+  EXPECT_EQ(1.0, keyframes[1]->Time());
+  EXPECT_EQ(SkColorSetRGB(0, 0xFF, 0), keyframes[1]->Value());
+  EXPECT_EQ(TimingFunction::Type::LINEAR,
+            keyframes[1]->GetTimingFunctionForTesting()->GetType());
+}
+
+TEST_P(AnimationCompositorAnimationsTest, MixedCustomPropertyAnimation) {
+  ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
+
+  RegisterProperty(GetDocument(), "--foo", "<number> | <color>", "0", false);
+  SetCustomProperty("--foo", "0");
+
+  StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
+      CreateReplaceOpKeyframe("--foo", "20", 0),
+      CreateReplaceOpKeyframe("--foo", "rgb(0, 255, 0)", 1.0));
+
+  EXPECT_TRUE(CanStartEffectOnCompositor(timing_, *effect) &
+              CompositorAnimations::kMixedKeyframeValueTypes);
+}
+
+TEST_P(AnimationCompositorAnimationsTest,
        CancelIncompatibleCompositorAnimations) {
   Persistent<HeapVector<Member<StringKeyframe>>> key_frames =
       MakeGarbageCollected<HeapVector<Member<StringKeyframe>>>();
diff --git a/third_party/blink/renderer/core/animation/css/compositor_keyframe_value.h b/third_party/blink/renderer/core/animation/css/compositor_keyframe_value.h
index 879ff95..26bef21a 100644
--- a/third_party/blink/renderer/core/animation/css/compositor_keyframe_value.h
+++ b/third_party/blink/renderer/core/animation/css/compositor_keyframe_value.h
@@ -25,7 +25,6 @@
 
   virtual void Trace(Visitor*) {}
 
- protected:
   enum class Type {
     kDouble,
     kFilterOperations,
@@ -33,7 +32,6 @@
     kColor,
   };
 
- private:
   virtual Type GetType() const = 0;
 };
 
diff --git a/third_party/blink/renderer/core/editing/BUILD.gn b/third_party/blink/renderer/core/editing/BUILD.gn
index b9cd4355..0e4f2edd 100644
--- a/third_party/blink/renderer/core/editing/BUILD.gn
+++ b/third_party/blink/renderer/core/editing/BUILD.gn
@@ -299,6 +299,8 @@
     "suggestion/text_suggestion_controller.cc",
     "suggestion/text_suggestion_controller.h",
     "suggestion/text_suggestion_info.h",
+    "surrounding_text.cc",
+    "surrounding_text.h",
     "text_affinity.cc",
     "text_affinity.h",
     "text_granularity.h",
@@ -410,6 +412,7 @@
     "state_machines/state_machine_test_util.h",
     "state_machines/state_machine_util_test.cc",
     "suggestion/text_suggestion_controller_test.cc",
+    "surrounding_text_test.cc",
     "testing/editing_test_base.cc",
     "testing/editing_test_base.h",
     "testing/editing_test_base_test.cc",
diff --git a/third_party/blink/renderer/core/exported/web_surrounding_text.cc b/third_party/blink/renderer/core/editing/surrounding_text.cc
similarity index 86%
rename from third_party/blink/renderer/core/exported/web_surrounding_text.cc
rename to third_party/blink/renderer/core/editing/surrounding_text.cc
index 37f1698..cb9d29a 100644
--- a/third_party/blink/renderer/core/exported/web_surrounding_text.cc
+++ b/third_party/blink/renderer/core/editing/surrounding_text.cc
@@ -23,7 +23,7 @@
  * DAMAGE.
  */
 
-#include "third_party/blink/public/web/web_surrounding_text.h"
+#include "third_party/blink/renderer/core/editing/surrounding_text.h"
 
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
@@ -33,32 +33,28 @@
 #include "third_party/blink/renderer/core/editing/iterators/character_iterator.h"
 #include "third_party/blink/renderer/core/editing/visible_selection.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
-#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h"
 
 namespace blink {
 
 namespace {
 
-EphemeralRange ComputeRangeFromFrameSelection(WebLocalFrame* frame) {
-  LocalFrame* web_frame = To<WebLocalFrameImpl>(frame)->GetFrame();
-
+EphemeralRange ComputeRangeFromFrameSelection(LocalFrame* frame) {
   // TODO(editing-dev): The use of UpdateStyleAndLayout
   // needs to be audited.  See http://crbug.com/590369 for more details.
-  web_frame->GetDocument()->UpdateStyleAndLayout();
+  frame->GetDocument()->UpdateStyleAndLayout();
 
-  return web_frame->Selection()
+  return frame->Selection()
       .ComputeVisibleSelectionInDOMTree()
       .ToNormalizedEphemeralRange();
 }
 
 }  // namespace
 
-WebSurroundingText::WebSurroundingText(WebLocalFrame* frame, size_t max_length)
-    : WebSurroundingText(ComputeRangeFromFrameSelection(frame), max_length) {}
+SurroundingText::SurroundingText(LocalFrame* frame, size_t max_length)
+    : SurroundingText(ComputeRangeFromFrameSelection(frame), max_length) {}
 
-WebSurroundingText::WebSurroundingText(const EphemeralRange& range,
-                                       size_t max_length)
+SurroundingText::SurroundingText(const EphemeralRange& range, size_t max_length)
     : start_offset_in_text_content_(0), end_offset_in_text_content_(0) {
   const Position start_position = range.StartPosition();
   const Position end_position = range.EndPosition();
@@ -126,19 +122,19 @@
       TextIteratorBehavior::EmitsObjectReplacementCharacterBehavior());
 }
 
-WebString WebSurroundingText::TextContent() const {
+String SurroundingText::TextContent() const {
   return text_content_;
 }
 
-size_t WebSurroundingText::StartOffsetInTextContent() const {
+size_t SurroundingText::StartOffsetInTextContent() const {
   return start_offset_in_text_content_;
 }
 
-size_t WebSurroundingText::EndOffsetInTextContent() const {
+size_t SurroundingText::EndOffsetInTextContent() const {
   return end_offset_in_text_content_;
 }
 
-bool WebSurroundingText::IsEmpty() const {
+bool SurroundingText::IsEmpty() const {
   return text_content_.IsEmpty();
 }
 
diff --git a/third_party/blink/public/web/web_surrounding_text.h b/third_party/blink/renderer/core/editing/surrounding_text.h
similarity index 73%
rename from third_party/blink/public/web/web_surrounding_text.h
rename to third_party/blink/renderer/core/editing/surrounding_text.h
index ae03edd..1891bad 100644
--- a/third_party/blink/public/web/web_surrounding_text.h
+++ b/third_party/blink/renderer/core/editing/surrounding_text.h
@@ -22,48 +22,50 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-#ifndef THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SURROUNDING_TEXT_H_
-#define THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SURROUNDING_TEXT_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_SURROUNDING_TEXT_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_SURROUNDING_TEXT_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
 
 #if INSIDE_BLINK
 #include "third_party/blink/renderer/core/editing/forward.h"  // nogncheck
 #endif
-#include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
 
 namespace blink {
 
-class WebLocalFrame;
+class LocalFrame;
 
-// WebSurroundingText is a Blink API that gives access to the SurroundingText
+// SurroundingText is a Blink API that gives access to the SurroundingText
 // API. It allows caller to know the text surrounding a point or a range.
-class WebSurroundingText {
+class CORE_EXPORT SurroundingText {
  public:
   // Initializes the object with the current selection in a given frame.
   // The maximum length of the contents retrieved is defined by max_length.
   // It does not include the text inside the range.
-  BLINK_EXPORT WebSurroundingText(WebLocalFrame*, size_t max_length);
+  SurroundingText(LocalFrame*, size_t max_length);
 
 #if INSIDE_BLINK
-  BLINK_EXPORT WebSurroundingText(const EphemeralRange&, size_t max_length);
+  SurroundingText(const EphemeralRange&, size_t max_length);
 #endif
 
-  BLINK_EXPORT bool IsEmpty() const;
+  bool IsEmpty() const;
 
   // Surrounding text content retrieved.
-  BLINK_EXPORT WebString TextContent() const;
+  String TextContent() const;
 
   // Start offset of the initial text in the text content.
-  BLINK_EXPORT size_t StartOffsetInTextContent() const;
+  size_t StartOffsetInTextContent() const;
 
   // End offset of the initial text in the text content.
-  BLINK_EXPORT size_t EndOffsetInTextContent() const;
+  size_t EndOffsetInTextContent() const;
 
  private:
-  WebString text_content_;
+  String text_content_;
   size_t start_offset_in_text_content_;
   size_t end_offset_in_text_content_;
 };
 
 }  // namespace blink
 
-#endif
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_EDITING_SURROUNDING_TEXT_H_
diff --git a/third_party/blink/renderer/core/exported/web_surrounding_text_test.cc b/third_party/blink/renderer/core/editing/surrounding_text_test.cc
similarity index 79%
rename from third_party/blink/renderer/core/exported/web_surrounding_text_test.cc
rename to third_party/blink/renderer/core/editing/surrounding_text_test.cc
index 4a087bc0..4809afa 100644
--- a/third_party/blink/renderer/core/exported/web_surrounding_text_test.cc
+++ b/third_party/blink/renderer/core/editing/surrounding_text_test.cc
@@ -1,8 +1,8 @@
-// Copyright 2014 The Chromium Authors. All rights reserved.
+// Copyright 2019 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "third_party/blink/public/web/web_surrounding_text.h"
+#include "third_party/blink/renderer/core/editing/surrounding_text.h"
 
 #include <memory>
 #include "testing/gtest/include/gtest/gtest.h"
@@ -18,7 +18,7 @@
 
 namespace blink {
 
-class WebSurroundingTextTest : public testing::Test {
+class SurroundingTextTest : public testing::Test {
  protected:
   Document& GetDocument() const { return dummy_page_holder_->GetDocument(); }
   void SetHTML(const String&);
@@ -31,27 +31,27 @@
   std::unique_ptr<DummyPageHolder> dummy_page_holder_;
 };
 
-void WebSurroundingTextTest::SetUp() {
+void SurroundingTextTest::SetUp() {
   dummy_page_holder_ = std::make_unique<DummyPageHolder>(IntSize(800, 600));
 }
 
-void WebSurroundingTextTest::SetHTML(const String& content) {
+void SurroundingTextTest::SetHTML(const String& content) {
   GetDocument().body()->SetInnerHTMLFromString(content);
   GetDocument().UpdateStyleAndLayout();
 }
 
-EphemeralRange WebSurroundingTextTest::Select(int start, int end) {
+EphemeralRange SurroundingTextTest::Select(int start, int end) {
   Element* element = GetDocument().getElementById("selection");
   return EphemeralRange(Position(element->firstChild(), start),
                         Position(element->firstChild(), end));
 }
 
-TEST_F(WebSurroundingTextTest, BasicCaretSelection) {
+TEST_F(SurroundingTextTest, BasicCaretSelection) {
   SetHTML(String("<p id='selection'>foo bar</p>"));
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 1);
+    SurroundingText surrounding_text(selection, 1);
 
     EXPECT_EQ("f", surrounding_text.TextContent());
     EXPECT_EQ(0u, surrounding_text.StartOffsetInTextContent());
@@ -60,7 +60,7 @@
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 5);
+    SurroundingText surrounding_text(selection, 5);
 
     // maxlength/2 is used on the left and right.
     EXPECT_EQ("foo",
@@ -71,7 +71,7 @@
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 42);
+    SurroundingText surrounding_text(selection, 42);
 
     EXPECT_EQ("foo bar",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -81,7 +81,7 @@
 
   {
     EphemeralRange selection = Select(7);
-    WebSurroundingText surrounding_text(selection, 42);
+    SurroundingText surrounding_text(selection, 42);
 
     EXPECT_EQ("foo bar",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -91,7 +91,7 @@
 
   {
     EphemeralRange selection = Select(6);
-    WebSurroundingText surrounding_text(selection, 2);
+    SurroundingText surrounding_text(selection, 2);
 
     EXPECT_EQ("ar", surrounding_text.TextContent());
     EXPECT_EQ(1u, surrounding_text.StartOffsetInTextContent());
@@ -100,7 +100,7 @@
 
   {
     EphemeralRange selection = Select(6);
-    WebSurroundingText surrounding_text(selection, 42);
+    SurroundingText surrounding_text(selection, 42);
 
     EXPECT_EQ("foo bar",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -109,12 +109,12 @@
   }
 }
 
-TEST_F(WebSurroundingTextTest, BasicRangeSelection) {
+TEST_F(SurroundingTextTest, BasicRangeSelection) {
   SetHTML(String("<p id='selection'>Lorem ipsum dolor sit amet</p>"));
 
   {
     EphemeralRange selection = Select(0, 5);
-    WebSurroundingText surrounding_text(selection, 1);
+    SurroundingText surrounding_text(selection, 1);
 
     EXPECT_EQ("Lorem ", surrounding_text.TextContent());
     EXPECT_EQ(0u, surrounding_text.StartOffsetInTextContent());
@@ -123,7 +123,7 @@
 
   {
     EphemeralRange selection = Select(0, 5);
-    WebSurroundingText surrounding_text(selection, 5);
+    SurroundingText surrounding_text(selection, 5);
 
     EXPECT_EQ("Lorem ip",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -133,7 +133,7 @@
 
   {
     EphemeralRange selection = Select(0, 5);
-    WebSurroundingText surrounding_text(selection, 42);
+    SurroundingText surrounding_text(selection, 42);
 
     EXPECT_EQ("Lorem ipsum dolor sit amet",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -143,7 +143,7 @@
 
   {
     EphemeralRange selection = Select(6, 11);
-    WebSurroundingText surrounding_text(selection, 2);
+    SurroundingText surrounding_text(selection, 2);
 
     EXPECT_EQ(" ipsum ", surrounding_text.TextContent());
     EXPECT_EQ(1u, surrounding_text.StartOffsetInTextContent());
@@ -152,7 +152,7 @@
 
   {
     EphemeralRange selection = Select(6, 11);
-    WebSurroundingText surrounding_text(selection, 42);
+    SurroundingText surrounding_text(selection, 42);
 
     EXPECT_EQ("Lorem ipsum dolor sit amet",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -163,7 +163,7 @@
   {
     // Last word.
     EphemeralRange selection = Select(22, 26);
-    WebSurroundingText surrounding_text(selection, 8);
+    SurroundingText surrounding_text(selection, 8);
 
     EXPECT_EQ("sit amet", surrounding_text.TextContent());
     EXPECT_EQ(4u, surrounding_text.StartOffsetInTextContent());
@@ -171,14 +171,14 @@
   }
 }
 
-TEST_F(WebSurroundingTextTest, TreeCaretSelection) {
+TEST_F(SurroundingTextTest, TreeCaretSelection) {
   SetHTML(
       String("<div>This is outside of <p id='selection'>foo bar</p> the "
              "selected node</div>"));
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 1);
+    SurroundingText surrounding_text(selection, 1);
 
     EXPECT_EQ("f", surrounding_text.TextContent());
     EXPECT_EQ(0u, surrounding_text.StartOffsetInTextContent());
@@ -187,7 +187,7 @@
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 5);
+    SurroundingText surrounding_text(selection, 5);
 
     EXPECT_EQ("foo",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -197,7 +197,7 @@
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 1337);
+    SurroundingText surrounding_text(selection, 1337);
 
     EXPECT_EQ("This is outside of foo bar the selected node",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -207,7 +207,7 @@
 
   {
     EphemeralRange selection = Select(6);
-    WebSurroundingText surrounding_text(selection, 2);
+    SurroundingText surrounding_text(selection, 2);
 
     EXPECT_EQ("ar", surrounding_text.TextContent());
     EXPECT_EQ(1u, surrounding_text.StartOffsetInTextContent());
@@ -216,7 +216,7 @@
 
   {
     EphemeralRange selection = Select(6);
-    WebSurroundingText surrounding_text(selection, 1337);
+    SurroundingText surrounding_text(selection, 1337);
 
     EXPECT_EQ("This is outside of foo bar the selected node",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -225,14 +225,14 @@
   }
 }
 
-TEST_F(WebSurroundingTextTest, TreeRangeSelection) {
+TEST_F(SurroundingTextTest, TreeRangeSelection) {
   SetHTML(
       String("<div>This is outside of <p id='selection'>foo bar</p> the "
              "selected node</div>"));
 
   {
     EphemeralRange selection = Select(0, 1);
-    WebSurroundingText surrounding_text(selection, 1);
+    SurroundingText surrounding_text(selection, 1);
 
     EXPECT_EQ("fo",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -242,7 +242,7 @@
 
   {
     EphemeralRange selection = Select(0, 3);
-    WebSurroundingText surrounding_text(selection, 12);
+    SurroundingText surrounding_text(selection, 12);
 
     EXPECT_EQ("e of foo bar",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -252,7 +252,7 @@
 
   {
     EphemeralRange selection = Select(0, 3);
-    WebSurroundingText surrounding_text(selection, 1337);
+    SurroundingText surrounding_text(selection, 1337);
 
     EXPECT_EQ("This is outside of foo bar the selected node",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -262,7 +262,7 @@
 
   {
     EphemeralRange selection = Select(4, 7);
-    WebSurroundingText surrounding_text(selection, 12);
+    SurroundingText surrounding_text(selection, 12);
 
     EXPECT_EQ("foo bar the se",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -272,7 +272,7 @@
 
   {
     EphemeralRange selection = Select(0, 7);
-    WebSurroundingText surrounding_text(selection, 1337);
+    SurroundingText surrounding_text(selection, 1337);
 
     EXPECT_EQ("This is outside of foo bar the selected node",
               String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -281,19 +281,19 @@
   }
 }
 
-TEST_F(WebSurroundingTextTest, TextAreaSelection) {
+TEST_F(SurroundingTextTest, TextAreaSelection) {
   SetHTML(
       String("<p>First paragraph</p>"
              "<textarea id='selection'>abc def ghi</textarea>"
              "<p>Second paragraph</p>"));
 
-  TextControlElement* text_ctrl =
-      (TextControlElement*)GetDocument().getElementById("selection");
+  TextControlElement* text_ctrl = reinterpret_cast<TextControlElement*>(
+      GetDocument().getElementById("selection"));
 
   text_ctrl->SetSelectionRange(4, 7);
   EphemeralRange selection = text_ctrl->Selection().ComputeRange();
 
-  WebSurroundingText surrounding_text(selection, 20);
+  SurroundingText surrounding_text(selection, 20);
 
   EXPECT_EQ("abc def ghi",
             String(surrounding_text.TextContent()).SimplifyWhiteSpace());
@@ -301,11 +301,11 @@
   EXPECT_EQ(7u, surrounding_text.EndOffsetInTextContent());
 }
 
-TEST_F(WebSurroundingTextTest, EmptyInputElementWithChild) {
+TEST_F(SurroundingTextTest, EmptyInputElementWithChild) {
   SetHTML(String("<input type=\"text\" id=\"input_name\"/>"));
 
-  TextControlElement* input_element =
-      (TextControlElement*)GetDocument().getElementById("input_name");
+  TextControlElement* input_element = reinterpret_cast<TextControlElement*>(
+      GetDocument().getElementById("input_name"));
   input_element->SetInnerEditorValue("John Smith");
   GetDocument().UpdateStyleAndLayout();
 
@@ -320,11 +320,11 @@
   const Position end = Position(inner_editor, 0);
 
   // Surrounding text should not crash. See http://crbug.com/758438.
-  WebSurroundingText surrounding_text(EphemeralRange(start, end), 8);
+  SurroundingText surrounding_text(EphemeralRange(start, end), 8);
   EXPECT_TRUE(surrounding_text.TextContent().IsEmpty());
 }
 
-TEST_F(WebSurroundingTextTest, ButtonsAndParagraph) {
+TEST_F(SurroundingTextTest, ButtonsAndParagraph) {
   SetHTML(
       String("<button>.</button>12345"
              "<p id='selection'>6789 12345</p>"
@@ -332,7 +332,7 @@
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 100);
+    SurroundingText surrounding_text(selection, 100);
 
     EXPECT_EQ("12345\n6789 12345\n\n6789", surrounding_text.TextContent());
     EXPECT_EQ(6u, surrounding_text.StartOffsetInTextContent());
@@ -341,7 +341,7 @@
 
   {
     EphemeralRange selection = Select(5);
-    WebSurroundingText surrounding_text(selection, 6);
+    SurroundingText surrounding_text(selection, 6);
 
     EXPECT_EQ("89 123", surrounding_text.TextContent());
     EXPECT_EQ(3u, surrounding_text.StartOffsetInTextContent());
@@ -350,14 +350,14 @@
 
   {
     EphemeralRange selection = Select(0);
-    WebSurroundingText surrounding_text(selection, 0);
+    SurroundingText surrounding_text(selection, 0);
 
     EXPECT_TRUE(surrounding_text.TextContent().IsEmpty());
   }
 
   {
     EphemeralRange selection = Select(5);
-    WebSurroundingText surrounding_text(selection, 1);
+    SurroundingText surrounding_text(selection, 1);
 
     EXPECT_EQ("1", surrounding_text.TextContent());
     EXPECT_EQ(0u, surrounding_text.StartOffsetInTextContent());
@@ -366,7 +366,7 @@
 
   {
     EphemeralRange selection = Select(6);
-    WebSurroundingText surrounding_text(selection, 2);
+    SurroundingText surrounding_text(selection, 2);
 
     EXPECT_EQ("12", surrounding_text.TextContent());
     EXPECT_EQ(1u, surrounding_text.StartOffsetInTextContent());
@@ -374,7 +374,7 @@
   }
 }
 
-TEST_F(WebSurroundingTextTest, SelectElementAndText) {
+TEST_F(SurroundingTextTest, SelectElementAndText) {
   SetHTML(String(
       "<select>.</select>"
       "<div>57th Street and Lake Shore Drive</div>"
@@ -382,7 +382,7 @@
       "<select>.</select>"));
 
   EphemeralRange selection = Select(0);
-  WebSurroundingText surrounding_text(selection, 100);
+  SurroundingText surrounding_text(selection, 100);
 
   EXPECT_EQ("\xEF\xBF\xBC\n57th Street and Lake Shore Drive\nChicago IL 60637",
             surrounding_text.TextContent().Utf8());
@@ -390,21 +390,21 @@
   EXPECT_EQ(43u, surrounding_text.EndOffsetInTextContent());
 }
 
-TEST_F(WebSurroundingTextTest, FieldsetElementAndText) {
+TEST_F(SurroundingTextTest, FieldsetElementAndText) {
   SetHTML(
       String("<fieldset>.</fieldset>12345<button>abc</button>"
              "<p>6789<br><span id='selection'>12345</span></p>"
              "6789<textarea>abc</textarea>0123<fieldset>.</fieldset>"));
 
   EphemeralRange selection = Select(0);
-  WebSurroundingText surrounding_text(selection, 100);
+  SurroundingText surrounding_text(selection, 100);
 
   EXPECT_EQ("\n6789\n12345\n\n6789", surrounding_text.TextContent());
   EXPECT_EQ(6u, surrounding_text.StartOffsetInTextContent());
   EXPECT_EQ(6u, surrounding_text.EndOffsetInTextContent());
 }
 
-TEST_F(WebSurroundingTextTest, ButtonScriptAndComment) {
+TEST_F(SurroundingTextTest, ButtonScriptAndComment) {
   SetHTML(
       String("<button>.</button>"
              "<div id='selection'>This is <!-- comment --!>a test "
@@ -412,28 +412,28 @@
              "example<button>.</button>"));
 
   EphemeralRange selection = Select(0);
-  WebSurroundingText surrounding_text(selection, 100);
+  SurroundingText surrounding_text(selection, 100);
 
   EXPECT_EQ("\nThis is a test example", surrounding_text.TextContent());
   EXPECT_EQ(1u, surrounding_text.StartOffsetInTextContent());
   EXPECT_EQ(1u, surrounding_text.EndOffsetInTextContent());
 }
 
-TEST_F(WebSurroundingTextTest, ButtonAndLongDiv) {
+TEST_F(SurroundingTextTest, ButtonAndLongDiv) {
   SetHTML(
       String("<button>.</button>"
              "<div id='selection'>012345678901234567890123456789</div>"
              "<button>.</button>"));
 
   EphemeralRange selection = Select(15);
-  WebSurroundingText surrounding_text(selection, 12);
+  SurroundingText surrounding_text(selection, 12);
 
   EXPECT_EQ("901234567890", surrounding_text.TextContent());
   EXPECT_EQ(6u, surrounding_text.StartOffsetInTextContent());
   EXPECT_EQ(6u, surrounding_text.EndOffsetInTextContent());
 }
 
-TEST_F(WebSurroundingTextTest, EmptyWebSurroundingTextInOptionsAndButton) {
+TEST_F(SurroundingTextTest, EmptySurroundingTextInOptionsAndButton) {
   SetHTML(
       String("<option>.</option>12345"
              "<button id='selection'>test</button>"
@@ -441,24 +441,24 @@
 
   {
     EphemeralRange selection = Select(1);
-    WebSurroundingText surrounding_text(selection, 100);
+    SurroundingText surrounding_text(selection, 100);
 
     EXPECT_TRUE(surrounding_text.TextContent().IsEmpty());
   }
 
   {
     EphemeralRange selection = Select(3);
-    WebSurroundingText surrounding_text(selection, 100);
+    SurroundingText surrounding_text(selection, 100);
 
     EXPECT_TRUE(surrounding_text.TextContent().IsEmpty());
   }
 }
 
-TEST_F(WebSurroundingTextTest, SingleDotParagraph) {
+TEST_F(SurroundingTextTest, SingleDotParagraph) {
   SetHTML(String("<p id='selection'>.</p>"));
 
   EphemeralRange selection = Select(0);
-  WebSurroundingText surrounding_text(selection, 2);
+  SurroundingText surrounding_text(selection, 2);
 
   EXPECT_EQ("\n.", surrounding_text.TextContent());
   EXPECT_EQ(1u, surrounding_text.StartOffsetInTextContent());
diff --git a/third_party/blink/renderer/core/exported/BUILD.gn b/third_party/blink/renderer/core/exported/BUILD.gn
index b0afed24..e28dd92 100644
--- a/third_party/blink/renderer/core/exported/BUILD.gn
+++ b/third_party/blink/renderer/core/exported/BUILD.gn
@@ -73,7 +73,6 @@
     "web_settings_impl.h",
     "web_shared_worker_impl.cc",
     "web_shared_worker_impl.h",
-    "web_surrounding_text.cc",
     "web_text_checking_result.cc",
     "web_user_gesture_indicator.cc",
     "web_user_gesture_token.cc",
diff --git a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
index 40ab28f..975d53a 100644
--- a/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_page_popup_impl.cc
@@ -175,9 +175,8 @@
 
   void SetHasScrollEventHandlers(LocalFrame* frame,
                                  bool has_event_handlers) override {
-    DCHECK(frame->IsMainFrame());
-    if (popup_->layer_tree_view_)
-      popup_->layer_tree_view_->SetHaveScrollEventHandlers(has_event_handlers);
+    // WebPagePopup's compositor does not handle compositor thread input (set up
+    // in RenderWidget) so there is no need to signal this.
   }
 
   void SetTouchAction(LocalFrame* frame, TouchAction touch_action) override {
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.cc b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
index dd3ba27..d728705 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.cc
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.cc
@@ -672,6 +672,10 @@
   injected_scroll_gesture_data_.push_back(data);
 }
 
+void TestWebWidgetClient::SetHaveScrollEventHandlers(bool have_handlers) {
+  have_scroll_event_handlers_ = have_handlers;
+}
+
 void TestWebWidgetClient::SetEventListenerProperties(
     cc::EventListenerClass event_class,
     cc::EventListenerProperties properties) {
diff --git a/third_party/blink/renderer/core/frame/frame_test_helpers.h b/third_party/blink/renderer/core/frame/frame_test_helpers.h
index 725df37..d2a3b9130 100644
--- a/third_party/blink/renderer/core/frame/frame_test_helpers.h
+++ b/third_party/blink/renderer/core/frame/frame_test_helpers.h
@@ -210,7 +210,7 @@
   explicit TestWebWidgetClient(content::LayerTreeViewDelegate* = nullptr);
   ~TestWebWidgetClient() override = default;
 
-  // WebWidgetClient:
+  // WebWidgetClient implementation.
   void ScheduleAnimation() override { animation_scheduled_ = true; }
   void SetRootLayer(scoped_refptr<cc::Layer> layer) override;
   void RegisterViewportLayers(const cc::ViewportLayers& layOAers) override;
@@ -226,6 +226,7 @@
                                 ScrollGranularity granularity,
                                 cc::ElementId scrollable_area_element_id,
                                 WebInputEvent::Type injected_type) override;
+  void SetHaveScrollEventHandlers(bool) override;
   void SetEventListenerProperties(
       cc::EventListenerClass event_class,
       cc::EventListenerProperties properties) override;
@@ -235,6 +236,7 @@
       override;
   void StartDeferringCommits(base::TimeDelta timeout) override;
   void StopDeferringCommits(cc::PaintHoldingCommitTrigger) override;
+  void DidMeaningfulLayout(WebMeaningfulLayout) override;
 
   content::LayerTreeView* layer_tree_view() { return layer_tree_view_; }
   cc::LayerTreeHost* layer_tree_host() {
@@ -248,7 +250,8 @@
   bool AnimationScheduled() { return animation_scheduled_; }
   void ClearAnimationScheduled() { animation_scheduled_ = false; }
 
-  void DidMeaningfulLayout(WebMeaningfulLayout) override;
+  // Returns the last value given to SetHaveScrollEventHandlers().
+  bool HaveScrollEventHandlers() const { return have_scroll_event_handlers_; }
 
   int VisuallyNonEmptyLayoutCount() const {
     return visually_non_empty_layout_count_;
@@ -270,6 +273,7 @@
   LayerTreeViewFactory layer_tree_view_factory_;
   Vector<InjectedScrollGestureData> injected_scroll_gesture_data_;
   bool animation_scheduled_ = false;
+  bool have_scroll_event_handlers_ = false;
   int visually_non_empty_layout_count_ = 0;
   int finished_parsing_layout_count_ = 0;
   int finished_loading_layout_count_ = 0;
@@ -365,6 +369,9 @@
   content::LayerTreeView* GetLayerTreeView() const {
     return test_web_widget_client_->layer_tree_view();
   }
+  TestWebWidgetClient* GetWebWidgetClient() const {
+    return test_web_widget_client_;
+  }
 
   WebLocalFrameImpl* LocalMainFrame() const;
   WebRemoteFrameImpl* RemoteMainFrame() const;
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 1985d03..54ca4aa1 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -990,9 +990,14 @@
 }
 
 FloatSize LocalFrameView::ViewportSizeForMediaQueries() const {
-  FloatSize viewport_size(GetLayoutSize());
-  if (!frame_->GetDocument() || !frame_->GetDocument()->Printing())
-    viewport_size.Scale(1 / GetFrame().PageZoomFactor());
+  FloatSize viewport_size(layout_size_);
+  if (!frame_->GetDocument()->Printing()) {
+    float zoom = GetFrame().PageZoomFactor();
+    viewport_size.SetWidth(
+        AdjustForAbsoluteZoom::AdjustInt(layout_size_.Width(), zoom));
+    viewport_size.SetHeight(
+        AdjustForAbsoluteZoom::AdjustInt(layout_size_.Height(), zoom));
+  }
   return viewport_size;
 }
 
diff --git a/third_party/blink/renderer/core/frame/surrounding_text_impl.cc b/third_party/blink/renderer/core/frame/surrounding_text_impl.cc
index 9817788..43516b1 100644
--- a/third_party/blink/renderer/core/frame/surrounding_text_impl.cc
+++ b/third_party/blink/renderer/core/frame/surrounding_text_impl.cc
@@ -7,7 +7,7 @@
 #include <utility>
 
 #include "third_party/blink/public/platform/task_type.h"
-#include "third_party/blink/public/web/web_surrounding_text.h"
+#include "third_party/blink/renderer/core/editing/surrounding_text.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
@@ -35,7 +35,7 @@
 void SurroundingTextImpl::GetTextSurroundingSelection(
     uint32_t max_length,
     GetTextSurroundingSelectionCallback callback) {
-  blink::WebSurroundingText surrounding_text(frame_, max_length);
+  blink::SurroundingText surrounding_text(frame_->GetFrame(), max_length);
 
   if (surrounding_text.IsEmpty()) {
     // |surrounding_text| might not be correctly initialized, for example if
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 9ddb242..f3b5dc3 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -3513,9 +3513,21 @@
   ImageChanged(static_cast<WrappedImagePtr>(image), defer);
 }
 
-void LayoutObject::ImageNotifyFinished(ImageResourceContent*) {
+void LayoutObject::ImageNotifyFinished(ImageResourceContent* image) {
   if (AXObjectCache* cache = GetDocument().ExistingAXObjectCache())
     cache->ImageLoaded(this);
+
+  if (RuntimeEnabledFeatures::ElementTimingEnabled(&GetDocument())) {
+    LocalDOMWindow* window = GetDocument().domWindow();
+    if (window) {
+      ImageElementTiming::From(*window).NotifyImageFinished(*this, image);
+    }
+  }
+  if (RuntimeEnabledFeatures::FirstContentfulPaintPlusPlusEnabled()) {
+    if (LocalFrameView* frame_view = GetFrameView()) {
+      frame_view->GetPaintTimingDetector().NotifyImageFinished(*this, image);
+    }
+  }
 }
 
 Element* LayoutObject::OffsetParent(const Element* base) const {
diff --git a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
index 885a1b3..1b483aa 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_layout_utils.cc
@@ -21,60 +21,6 @@
 // kMainSize - width / height
 enum class LengthResolveType { kMinSize, kMaxSize, kMainSize };
 
-bool ContentShrinkToFitMayChange(const ComputedStyle& style,
-                                 const NGConstraintSpace& new_space,
-                                 const NGConstraintSpace& old_space,
-                                 const NGLayoutResult& layout_result) {
-  if (old_space.AvailableSize().inline_size ==
-      new_space.AvailableSize().inline_size)
-    return false;
-
-  NGBoxStrut margins = ComputeMarginsForSelf(new_space, style);
-
-#if DCHECK_IS_ON()
-  // The margins must be the same, as this function won't be called if we have
-  // percentage inline margins, and the percentage resolution size changes.
-  NGBoxStrut old_margins = ComputeMarginsForSelf(old_space, style);
-  DCHECK_EQ(margins.inline_start, old_margins.inline_start);
-  DCHECK_EQ(margins.inline_end, old_margins.inline_end);
-#endif
-
-  LayoutUnit old_available_inline_size =
-      std::max(LayoutUnit(),
-               old_space.AvailableSize().inline_size - margins.InlineSum());
-  LayoutUnit new_available_inline_size =
-      std::max(LayoutUnit(),
-               new_space.AvailableSize().inline_size - margins.InlineSum());
-
-  LayoutUnit inline_size =
-      NGFragment(style.GetWritingMode(), layout_result.PhysicalFragment())
-          .InlineSize();
-
-  // If the previous fragment was at its min-content size (indicated by the old
-  // available size being smaller than the fragment), we may be able to skip
-  // layout if the new available size is also smaller.
-  bool unaffected_as_min_content_size =
-      old_available_inline_size < inline_size &&
-      new_available_inline_size <= inline_size;
-
-  // If the previous fragment was at its max-content size (indicated by the old
-  // available size being larger than the fragment), we may be able to skip
-  // layout if the new available size is also larger.
-  bool unaffected_as_max_content_size =
-      old_available_inline_size > inline_size &&
-      new_available_inline_size >= inline_size;
-
-  // TODO(crbug.com/935634): There is an additional optimization where if we
-  // detect (by setting a flag in the layout result) that the
-  // min-content == max-content we can simply just skip layout, as the
-  // available size won't have any effect.
-
-  if (unaffected_as_min_content_size || unaffected_as_max_content_size)
-    return false;
-
-  return true;
-}
-
 inline bool InlineLengthMayChange(const ComputedStyle& style,
                                   const Length& length,
                                   LengthResolveType type,
@@ -100,20 +46,6 @@
        old_space.PercentageResolutionInlineSize()))
     return true;
 
-  // For elements which shrink to fit, we can perform a specific optimization
-  // where we can skip relayout if the element was sized to its min-content or
-  // max-content size.
-  bool is_content_shrink_to_fit =
-      type == LengthResolveType::kMainSize &&
-      (new_space.IsShrinkToFit() || length.IsFitContent());
-
-  // TODO(ikilpatrick): Test if we can remove this optimization now that we
-  // compute the initial size of the fragment.
-  if (is_content_shrink_to_fit) {
-    return ContentShrinkToFitMayChange(style, new_space, old_space,
-                                       layout_result);
-  }
-
   if (is_unspecified) {
     if (new_space.AvailableSize().inline_size !=
         old_space.AvailableSize().inline_size)
diff --git a/third_party/blink/renderer/core/page/chrome_client_impl.cc b/third_party/blink/renderer/core/page/chrome_client_impl.cc
index d4fc2b1..6005d8c 100644
--- a/third_party/blink/renderer/core/page/chrome_client_impl.cc
+++ b/third_party/blink/renderer/core/page/chrome_client_impl.cc
@@ -1059,17 +1059,16 @@
 
 void ChromeClientImpl::SetHasScrollEventHandlers(LocalFrame* frame,
                                                  bool has_event_handlers) {
-  // |frame| might be null if called via TreeScopeAdopter::
-  // moveNodeToNewDocument() and the new document has no frame attached.
-  // Since a document without a frame cannot attach one later, it is safe to
-  // exit early.
+  // |frame| might be null if called via
+  // TreeScopeAdopter::MoveNodeToNewDocument() and the new document has no frame
+  // attached. Since a document without a frame cannot attach one later, it is
+  // safe to exit early.
   if (!frame)
     return;
 
-  WebFrameWidgetBase* widget =
-      WebLocalFrameImpl::FromFrame(frame)->LocalRootFrameWidget();
-  if (widget && widget->GetLayerTreeView())
-    widget->GetLayerTreeView()->SetHaveScrollEventHandlers(has_event_handlers);
+  WebWidgetClient* client =
+      WebLocalFrameImpl::FromFrame(frame)->LocalRootFrameWidget()->Client();
+  client->SetHaveScrollEventHandlers(has_event_handlers);
 }
 
 void ChromeClientImpl::SetNeedsLowLatencyInput(LocalFrame* frame,
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc
index d9952417..26038093 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_coordinator_test.cc
@@ -127,13 +127,8 @@
 
   WebViewImpl* GetWebView() const { return helper_.GetWebView(); }
   LocalFrame* GetFrame() const { return helper_.LocalMainFrame()->GetFrame(); }
-
-  WebLayerTreeView* GetWebLayerTreeView() const {
-    return GetWebView()->LayerTreeView();
-  }
-
-  WebWidgetClient* GetWidgetClient() const {
-    return GetWebView()->WidgetClient();
+  frame_test_helpers::TestWebWidgetClient* GetWidgetClient() const {
+    return helper_.GetWebWidgetClient();
   }
 
   void LoadAhem() { helper_.LoadAhem(); }
@@ -572,7 +567,7 @@
   NavigateTo(base_url_ + "scroll-event-handler.html");
   ForceFullCompositingUpdate();
 
-  ASSERT_TRUE(GetWebLayerTreeView()->HaveScrollEventHandlers());
+  ASSERT_TRUE(GetWidgetClient()->HaveScrollEventHandlers());
 }
 
 TEST_P(ScrollingCoordinatorTest, updateEventHandlersDuringTeardown) {
diff --git a/third_party/blink/renderer/core/page/zoom_test.cc b/third_party/blink/renderer/core/page/zoom_test.cc
new file mode 100644
index 0000000..487d9586
--- /dev/null
+++ b/third_party/blink/renderer/core/page/zoom_test.cc
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/renderer/core/dom/document.h"
+#include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/local_frame_view.h"
+#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
+
+namespace blink {
+
+class FractionalZoomSimTest : public SimTest {};
+
+TEST_F(FractionalZoomSimTest, CheckCSSMediaQueryWidthEqualsWindowInnerWidth) {
+  WebView().MainFrameWidget()->Resize(WebSize(1081, 1921));
+
+  // 1081/2.75 = 393.091
+  // 1081/2.00 = 540.500
+  // 1081/1.50 = 720.667
+  std::vector<float> factors = {2.75f, 2.00f, 1.50f};
+  for (auto factor : factors) {
+    WebView().SetZoomFactorForDeviceScaleFactor(factor);
+    EXPECT_EQ(GetDocument().View()->ViewportSizeForMediaQueries().Width(),
+              GetDocument().GetFrame()->DomWindow()->innerWidth());
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.cc b/third_party/blink/renderer/core/paint/image_element_timing.cc
index 9541f0b7..25a4ce2 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.cc
+++ b/third_party/blink/renderer/core/paint/image_element_timing.cc
@@ -65,6 +65,16 @@
       GetSupplementable()->document()));
 }
 
+void ImageElementTiming::NotifyImageFinished(
+    const LayoutObject& layout_object,
+    const ImageResourceContent* cached_image) {
+  if (!internal::IsExplicitlyRegisteredForTiming(&layout_object))
+    return;
+
+  images_notified_.Set(std::make_pair(&layout_object, cached_image),
+                       ImageInfo(base::TimeTicks::Now()));
+}
+
 void ImageElementTiming::NotifyImagePainted(
     const LayoutObject* layout_object,
     const ImageResourceContent* cached_image,
@@ -74,11 +84,13 @@
   if (!internal::IsExplicitlyRegisteredForTiming(layout_object))
     return;
 
-  auto result =
-      images_notified_.insert(std::make_pair(layout_object, cached_image));
-  if (result.is_new_entry && cached_image) {
+  auto it = images_notified_.find(std::make_pair(layout_object, cached_image));
+  DCHECK(it != images_notified_.end());
+  if (!it->value.is_painted_ && cached_image) {
+    it->value.is_painted_ = true;
     NotifyImagePaintedInternal(layout_object->GetNode(), *layout_object,
-                               *cached_image, current_paint_chunk_properties);
+                               *cached_image, current_paint_chunk_properties,
+                               it->value.load_time_);
   }
 }
 
@@ -86,7 +98,8 @@
     Node* node,
     const LayoutObject& layout_object,
     const ImageResourceContent& cached_image,
-    const PropertyTreeState& current_paint_chunk_properties) {
+    const PropertyTreeState& current_paint_chunk_properties,
+    base::TimeTicks load_time) {
   LocalFrame* frame = GetSupplementable()->GetFrame();
   DCHECK(frame == layout_object.GetDocument().GetFrame());
   DCHECK(node);
@@ -134,7 +147,7 @@
       // Create an entry with a |startTime| of 0.
       performance->AddElementTiming(
           ImagePaintString(), url.GetString(), intersection_rect,
-          base::TimeTicks(), cached_image.LoadResponseEnd(), attr,
+          base::TimeTicks(), load_time, attr,
           cached_image.IntrinsicSize(kDoNotRespectImageOrientation), id,
           element);
     }
@@ -149,7 +162,7 @@
                                 ? url.GetString().Left(kInlineImageMaxChars)
                                 : url.GetString();
   element_timings_.emplace_back(MakeGarbageCollected<ElementTimingInfo>(
-      image_url, intersection_rect, cached_image.LoadResponseEnd(), attr,
+      image_url, intersection_rect, load_time, attr,
       cached_image.IntrinsicSize(kDoNotRespectImageOrientation), id, element));
   // Only queue a swap promise when |element_timings_| was empty. All of the
   // records in |element_timings_| will be processed when the promise succeeds
@@ -180,11 +193,20 @@
   if (!cached_image || !cached_image->IsLoaded())
     return;
 
-  auto result =
-      images_notified_.insert(std::make_pair(layout_object, cached_image));
-  if (result.is_new_entry) {
-    NotifyImagePaintedInternal(node, *layout_object, *cached_image,
-                               current_paint_chunk_properties);
+  std::pair<const LayoutObject*, const ImageResourceContent*> pair =
+      std::make_pair(layout_object, cached_image);
+  auto it = images_notified_.find(pair);
+  // TODO(crbug.com/986891): ideally |images_notified_| would always be able to
+  // find the pair here. However, for some background images that is currently
+  // not possible. Therefore, in those cases we create the entry here with
+  // loadTime of 0. Once the bug is fixed, we should replace that with a DCHECK.
+  if (it == images_notified_.end())
+    images_notified_.Set(pair, ImageInfo(base::TimeTicks()));
+  if (!it->value.is_painted_ && cached_image) {
+    it->value.is_painted_ = true;
+    NotifyImagePaintedInternal(layout_object->GetNode(), *layout_object,
+                               *cached_image, current_paint_chunk_properties,
+                               it->value.load_time_);
   }
 }
 
diff --git a/third_party/blink/renderer/core/paint/image_element_timing.h b/third_party/blink/renderer/core/paint/image_element_timing.h
index ea5b0e57..b0cd009c 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing.h
+++ b/third_party/blink/renderer/core/paint/image_element_timing.h
@@ -40,6 +40,8 @@
 
   static ImageElementTiming& From(LocalDOMWindow&);
 
+  void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*);
+
   // Called when the LayoutObject has been painted. This method might queue a
   // swap promise to compute and report paint timestamps.
   void NotifyImagePainted(
@@ -64,7 +66,8 @@
       Node*,
       const LayoutObject&,
       const ImageResourceContent& cached_image,
-      const PropertyTreeState& current_paint_chunk_properties);
+      const PropertyTreeState& current_paint_chunk_properties,
+      base::TimeTicks load_time);
 
   // Callback for the swap promise. Reports paint timestamps.
   void ReportImagePaintSwapTime(WebWidgetClient::SwapResult,
@@ -107,11 +110,20 @@
   // Vector containing the element timing infos that will be reported during the
   // next swap promise callback.
   HeapVector<Member<ElementTimingInfo>> element_timings_;
+  struct ImageInfo {
+    // HashMap values require default constructor so we set default value for
+    // |load_time|.
+    ImageInfo(base::TimeTicks load_time = base::TimeTicks())
+        : load_time_(load_time), is_painted_(false) {}
+
+    base::TimeTicks load_time_;
+    bool is_painted_;
+  };
+  typedef std::pair<const LayoutObject*, const ImageResourceContent*> RecordId;
   // Hashmap of pairs of elements, LayoutObjects (for the elements) and
   // ImageResourceContent (for the src) which correspond to either images or
   // background images whose paint has been observed.
-  WTF::HashSet<std::pair<const LayoutObject*, const ImageResourceContent*>>
-      images_notified_;
+  WTF::HashMap<RecordId, ImageInfo> images_notified_;
 
   DISALLOW_COPY_AND_ASSIGN(ImageElementTiming);
 };
diff --git a/third_party/blink/renderer/core/paint/image_element_timing_test.cc b/third_party/blink/renderer/core/paint/image_element_timing_test.cc
index 7ab9bc9..bebc1d83 100644
--- a/third_party/blink/renderer/core/paint/image_element_timing_test.cc
+++ b/third_party/blink/renderer/core/paint/image_element_timing_test.cc
@@ -55,10 +55,16 @@
     return layout_image;
   }
 
-  const WTF::HashSet<
-      std::pair<const LayoutObject*, const ImageResourceContent*>>&
-  GetImagesNotified() {
-    return ImageElementTiming::From(*GetDoc()->domWindow()).images_notified_;
+  bool ImagesNotifiedContains(
+      const std::pair<const LayoutObject*, const ImageResourceContent*>&
+          record_id) {
+    return ImageElementTiming::From(*GetDoc()->domWindow())
+        .images_notified_.Contains(record_id);
+  }
+
+  unsigned ImagesNotifiedSize() {
+    return ImageElementTiming::From(*GetDoc()->domWindow())
+        .images_notified_.size();
   }
 
   Document* GetDoc() {
@@ -143,7 +149,7 @@
   LayoutImage* layout_image = SetImageResource("target", 5, 5);
   ASSERT_TRUE(layout_image);
   UpdateAllLifecyclePhases();
-  EXPECT_FALSE(GetImagesNotified().Contains(
+  EXPECT_FALSE(ImagesNotifiedContains(
       std::make_pair(layout_image, layout_image->CachedImage())));
 }
 
@@ -165,7 +171,7 @@
   UpdateAllLifecyclePhases();
 
   // |layout_image| should have had its paint notified to ImageElementTiming.
-  EXPECT_TRUE(GetImagesNotified().Contains(
+  EXPECT_TRUE(ImagesNotifiedContains(
       std::make_pair(layout_image, layout_image->CachedImage())));
 }
 
@@ -178,13 +184,13 @@
   LayoutImage* layout_image = SetImageResource("target", 5, 5);
   ASSERT_TRUE(layout_image);
   UpdateAllLifecyclePhases();
-  EXPECT_TRUE(GetImagesNotified().Contains(
+  EXPECT_TRUE(ImagesNotifiedContains(
       std::make_pair(layout_image, layout_image->CachedImage())));
 
   GetDoc()->getElementById("target")->remove();
   // |layout_image| should no longer be part of |images_notified| since it will
   // be destroyed.
-  EXPECT_TRUE(GetImagesNotified().IsEmpty());
+  EXPECT_EQ(ImagesNotifiedSize(), 0u);
 }
 
 TEST_F(ImageElementTimingTest, SVGImageRemoved) {
@@ -198,13 +204,13 @@
   LayoutSVGImage* layout_image = SetSVGImageResource("target", 5, 5);
   ASSERT_TRUE(layout_image);
   UpdateAllLifecyclePhases();
-  EXPECT_TRUE(GetImagesNotified().Contains(std::make_pair(
+  EXPECT_TRUE(ImagesNotifiedContains(std::make_pair(
       layout_image, layout_image->ImageResource()->CachedImage())));
 
   GetDoc()->getElementById("target")->remove();
   // |layout_image| should no longer be part of |images_notified| since it will
   // be destroyed.
-  EXPECT_TRUE(GetImagesNotified().IsEmpty());
+  EXPECT_EQ(ImagesNotifiedSize(), 0u);
 }
 
 TEST_F(ImageElementTimingTest, BackgroundImageRemoved) {
@@ -224,11 +230,11 @@
   ImageResourceContent* content =
       object->Style()->BackgroundLayers().GetImage()->CachedImage();
   UpdateAllLifecyclePhases();
-  EXPECT_EQ(GetImagesNotified().size(), 1u);
-  EXPECT_TRUE(GetImagesNotified().Contains(std::make_pair(object, content)));
+  EXPECT_EQ(ImagesNotifiedSize(), 1u);
+  EXPECT_TRUE(ImagesNotifiedContains(std::make_pair(object, content)));
 
   GetDoc()->getElementById("target")->remove();
-  EXPECT_TRUE(GetImagesNotified().IsEmpty());
+  EXPECT_EQ(ImagesNotifiedSize(), 0u);
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc
index 09ce324b..25be884 100644
--- a/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/image_paint_timing_detector.cc
@@ -169,6 +169,7 @@
   if (!is_recording_)
     return;
   RecordId record_id = std::make_pair(&object, cached_image);
+  records_manager_.RemoveImageFinishedRecord(record_id);
   if (!records_manager_.IsRecordedVisibleImage(record_id))
     return;
   records_manager_.RemoveVisibleRecord(record_id);
@@ -280,12 +281,25 @@
   }
 }
 
+void ImagePaintTimingDetector::NotifyImageFinished(
+    const LayoutObject& object,
+    const ImageResourceContent* cached_image) {
+  RecordId record_id = std::make_pair(&object, cached_image);
+  records_manager_.NotifyImageFinished(record_id);
+}
+
 ImageRecordsManager::ImageRecordsManager()
     : size_ordered_set_(&LargeImageFirst) {}
 
 void ImageRecordsManager::OnImageLoaded(const RecordId& record_id,
                                         unsigned current_frame_index) {
   base::WeakPtr<ImageRecord> record = FindVisibleRecord(record_id);
+  DCHECK(record);
+  // TODO(crbug.com/986891): some background images are not being tracked
+  // properly, so we cannot add a DCHECK that |image_finished_times| contains
+  // |record_id|. Once that bug is fixed, we should add that check, as otherwise
+  // we'll be exposing a loadTime of 0.
+  record->load_time = image_finished_times_.at(record_id);
   OnImageLoadedInternal(record, current_frame_index);
 }
 
diff --git a/third_party/blink/renderer/core/paint/image_paint_timing_detector.h b/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
index 84be249f..9d310649 100644
--- a/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/image_paint_timing_detector.h
@@ -49,6 +49,7 @@
   unsigned insertion_index;
   // The time of the first paint after fully loaded. 0 means not painted yet.
   base::TimeTicks paint_time = base::TimeTicks();
+  base::TimeTicks load_time = base::TimeTicks();
   bool loaded = false;
 };
 
@@ -75,6 +76,10 @@
     invisible_images_.erase(&object);
   }
 
+  inline void RemoveImageFinishedRecord(const RecordId& record_id) {
+    image_finished_times_.erase(record_id);
+  }
+
   inline void RemoveVisibleRecord(const RecordId& record_id) {
     base::WeakPtr<ImageRecord> record =
         visible_images_.find(record_id)->value->AsWeakPtr();
@@ -95,6 +100,16 @@
     return invisible_images_.Contains(&object);
   }
 
+  void NotifyImageFinished(const RecordId& record_id) {
+    // TODO(npm): Ideally NotifyImageFinished() would only be called when the
+    // record has not yet been inserted in |image_finished_times_| but that's
+    // not currently the case. If we plumb some information from
+    // ImageResourceContent we may be able to ensure that this call does not
+    // require the Contains() check, which would save time.
+    if (!image_finished_times_.Contains(record_id))
+      image_finished_times_.insert(record_id, base::TimeTicks::Now());
+  }
+
   inline bool IsVisibleImageLoaded(const RecordId& record_id) const {
     DCHECK(visible_images_.Contains(record_id));
     return visible_images_.at(record_id)->loaded;
@@ -156,6 +171,9 @@
   // |ImageRecord|s waiting for paint time are stored in this queue
   // until they get a swap time.
   Deque<base::WeakPtr<ImageRecord>> images_queued_for_paint_time_;
+  // Map containing timestamps of when LayoutObject::ImageNotifyFinished is
+  // first called.
+  HashMap<RecordId, base::TimeTicks> image_finished_times_;
 
   DISALLOW_COPY_AND_ASSIGN(ImageRecordsManager);
 };
@@ -190,11 +208,7 @@
                    const IntSize& intrinsic_size,
                    const ImageResourceContent&,
                    const PropertyTreeState& current_paint_chunk_properties);
-  void RecordBackgroundImage(
-      const LayoutObject&,
-      const IntSize& intrinsic_size,
-      const ImageResourceContent& cached_image,
-      const PropertyTreeState& current_paint_chunk_properties);
+  void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*);
   void OnPaintFinished();
   void LayoutObjectWillBeDestroyed(const LayoutObject&);
   void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*);
diff --git a/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc
index 08b42087..9af0352 100644
--- a/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/image_paint_timing_detector_test.cc
@@ -128,7 +128,10 @@
                ->records_manager_.size_ordered_set_.size() +
            GetPaintTimingDetector()
                .GetImagePaintTimingDetector()
-               ->records_manager_.images_queued_for_paint_time_.size();
+               ->records_manager_.images_queued_for_paint_time_.size() +
+           GetPaintTimingDetector()
+               .GetImagePaintTimingDetector()
+               ->records_manager_.image_finished_times_.size();
   }
 
   size_t CountChildFrameRecords() {
@@ -629,7 +632,7 @@
   )HTML");
   SetImageAndPaint("target", 5, 5);
   UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
-  EXPECT_EQ(ContainerTotalSize(), 2u);
+  EXPECT_EQ(ContainerTotalSize(), 3u);
 
   GetDocument().getElementById("parent")->RemoveChild(
       GetDocument().getElementById("target"));
@@ -657,7 +660,7 @@
   )HTML");
   SetImageAndPaint("target", 5, 5);
   UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
-  EXPECT_EQ(ContainerTotalSize(), 1u);
+  EXPECT_EQ(ContainerTotalSize(), 2u);
   EXPECT_EQ(CountInvisibleRecords(), 1u);
 
   GetDocument().body()->RemoveChild(GetDocument().getElementById("parent"));
@@ -680,7 +683,7 @@
     </div>
   )HTML");
   UpdateAllLifecyclePhasesAndInvokeCallbackIfAny();
-  EXPECT_EQ(ContainerTotalSize(), 2u);
+  EXPECT_EQ(ContainerTotalSize(), 3u);
 
   GetDocument().getElementById("parent")->RemoveChild(
       GetDocument().getElementById("target"));
@@ -696,7 +699,7 @@
   )HTML");
   SetImageAndPaint("target", 5, 5);
   UpdateAllLifecyclePhases();
-  EXPECT_EQ(ContainerTotalSize(), 3u);
+  EXPECT_EQ(ContainerTotalSize(), 4u);
 
   GetDocument().getElementById("parent")->RemoveChild(
       GetDocument().getElementById("target"));
diff --git a/third_party/blink/renderer/core/paint/largest_contentful_paint_calculator.cc b/third_party/blink/renderer/core/paint/largest_contentful_paint_calculator.cc
index 692efaf..9ce51de3 100644
--- a/third_party/blink/renderer/core/paint/largest_contentful_paint_calculator.cc
+++ b/third_party/blink/renderer/core/paint/largest_contentful_paint_calculator.cc
@@ -21,6 +21,7 @@
     largest_image_->first_size = largest_image->first_size;
     largest_image_->paint_time = largest_image->paint_time;
     largest_image_->cached_image = largest_image->cached_image;
+    largest_image_->load_time = largest_image->load_time;
   }
 
   if (LargestImageSize() > LargestTextSize()) {
@@ -95,7 +96,7 @@
         image_element ? image_element->GetIdAttribute() : AtomicString();
     window_performance_->OnLargestContentfulPaintUpdated(
         largest_image_->paint_time, largest_image_->first_size,
-        cached_image->LoadResponseEnd(), image_id, image_url, image_element);
+        largest_image_->load_time, image_id, image_url, image_element);
   } else {
     Node* text_node = DOMNodeIds::NodeForId(largest_text_->node_id);
     // |text_node| could be null and |largest_text_| should be ignored in this
diff --git a/third_party/blink/renderer/core/paint/paint_timing_detector.cc b/third_party/blink/renderer/core/paint/paint_timing_detector.cc
index d9be144e..88c7503 100644
--- a/third_party/blink/renderer/core/paint/paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/paint_timing_detector.cc
@@ -118,6 +118,13 @@
       object, intrinsic_size, *cached_image, current_paint_chunk_properties);
 }
 
+void PaintTimingDetector::NotifyImageFinished(
+    const LayoutObject& object,
+    const ImageResourceContent* cached_image) {
+  if (image_paint_timing_detector_)
+    image_paint_timing_detector_->NotifyImageFinished(object, cached_image);
+}
+
 void PaintTimingDetector::LayoutObjectWillBeDestroyed(
     const LayoutObject& object) {
   if (text_paint_timing_detector_)
diff --git a/third_party/blink/renderer/core/paint/paint_timing_detector.h b/third_party/blink/renderer/core/paint/paint_timing_detector.h
index 8f1c04d..92f2bb5 100644
--- a/third_party/blink/renderer/core/paint/paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/paint_timing_detector.h
@@ -50,6 +50,7 @@
       const PropertyTreeState& current_paint_chunk_properties);
   inline static void NotifyTextPaint(const IntRect& text_visual_rect);
 
+  void NotifyImageFinished(const LayoutObject&, const ImageResourceContent*);
   void LayoutObjectWillBeDestroyed(const LayoutObject&);
   void NotifyImageRemoved(const LayoutObject&, const ImageResourceContent*);
   void NotifyPaintFinished();
diff --git a/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs b/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
index 1eaff8a..18b48cc8 100644
--- a/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
+++ b/third_party/blink/renderer/core/script/resources/layered_api/elements/toast/index.mjs
@@ -13,6 +13,7 @@
 import * as reflection from '../internal/reflection.mjs';
 
 const DEFAULT_DURATION = 3000;
+const TYPES = new Set(['success', 'warning', 'error']);
 
 function stylesheetFactory() {
   let stylesheet;
@@ -38,6 +39,18 @@
         .default-closebutton {
           user-select: none;
         }
+
+        :host([type=success i]) {
+          border-color: green;
+        }
+
+        :host([type=warning i]) {
+          border-color: orange;
+        }
+
+        :host([type=error i]) {
+          border-color: red;
+        }
       `);
       // TODO(jacksteinberg): use offset-block-end: / offset-inline-end: over bottom: / right:
       // when implemented https://bugs.chromium.org/p/chromium/issues/detail?id=538475
@@ -127,6 +140,25 @@
     }
   }
 
+  get type() {
+    const typeAttr = this.getAttribute('type');
+    if (typeAttr === null) {
+      return '';
+    }
+
+    const typeAttrLower = typeAttr.toLowerCase();
+
+    if (TYPES.has(typeAttrLower)) {
+      return typeAttrLower;
+    }
+
+    return '';
+  }
+
+  set type(val) {
+    this.setAttribute('type', val);
+  }
+
   show({duration = DEFAULT_DURATION} = {}) {
     this.setAttribute('open', '');
     clearTimeout(this.#timeoutID);
diff --git a/third_party/blink/renderer/core/timing/largest_contentful_paint.cc b/third_party/blink/renderer/core/timing/largest_contentful_paint.cc
index 09824cf..40d2483 100644
--- a/third_party/blink/renderer/core/timing/largest_contentful_paint.cc
+++ b/third_party/blink/renderer/core/timing/largest_contentful_paint.cc
@@ -12,14 +12,14 @@
 
 LargestContentfulPaint::LargestContentfulPaint(double render_time,
                                                uint64_t size,
-                                               double response_end,
+                                               double load_time,
                                                const AtomicString& id,
                                                const String& url,
                                                Element* element)
     : PerformanceEntry(g_empty_atom, 0, 0),
       size_(size),
       render_time_(render_time),
-      response_end_(response_end),
+      load_time_(load_time),
       id_(id),
       url_(url),
       element_(element) {}
@@ -44,7 +44,8 @@
 void LargestContentfulPaint::BuildJSONValue(V8ObjectBuilder& builder) const {
   PerformanceEntry::BuildJSONValue(builder);
   builder.Add("size", size_);
-  builder.Add("responseEnd", response_end_);
+  builder.Add("renderTime", render_time_);
+  builder.Add("loadTime", load_time_);
   builder.Add("id", id_);
   builder.Add("url", url_);
   builder.Add("element", element_);
diff --git a/third_party/blink/renderer/core/timing/largest_contentful_paint.h b/third_party/blink/renderer/core/timing/largest_contentful_paint.h
index 8dc5ccb..ba0847d 100644
--- a/third_party/blink/renderer/core/timing/largest_contentful_paint.h
+++ b/third_party/blink/renderer/core/timing/largest_contentful_paint.h
@@ -19,7 +19,7 @@
  public:
   LargestContentfulPaint(double render_time,
                          uint64_t size,
-                         double response_end,
+                         double load_time,
                          const AtomicString& id,
                          const String& url,
                          Element*);
@@ -30,7 +30,7 @@
 
   uint64_t size() const { return size_; }
   DOMHighResTimeStamp renderTime() const { return render_time_; }
-  DOMHighResTimeStamp responseEnd() const { return response_end_; }
+  DOMHighResTimeStamp loadTime() const { return load_time_; }
   const AtomicString& id() const { return id_; }
   const String& url() const { return url_; }
   Element* element() const;
@@ -42,7 +42,7 @@
 
   uint64_t size_;
   DOMHighResTimeStamp render_time_;
-  DOMHighResTimeStamp response_end_;
+  DOMHighResTimeStamp load_time_;
   AtomicString id_;
   String url_;
   WeakMember<Element> element_;
diff --git a/third_party/blink/renderer/core/timing/largest_contentful_paint.idl b/third_party/blink/renderer/core/timing/largest_contentful_paint.idl
index d50a313..54cdaef 100644
--- a/third_party/blink/renderer/core/timing/largest_contentful_paint.idl
+++ b/third_party/blink/renderer/core/timing/largest_contentful_paint.idl
@@ -6,7 +6,7 @@
 [Exposed=Window, RuntimeEnabled=LargestContentfulPaint]
 interface LargestContentfulPaint : PerformanceEntry {
     readonly attribute DOMHighResTimeStamp renderTime;
-    readonly attribute DOMHighResTimeStamp responseEnd;
+    readonly attribute DOMHighResTimeStamp loadTime;
     readonly attribute unsigned long long size;
     readonly attribute DOMString id;
     readonly attribute DOMString url;
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.cc b/third_party/blink/renderer/core/timing/performance_element_timing.cc
index b369d6f4..1a03016 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.cc
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.cc
@@ -15,7 +15,7 @@
     const String& url,
     const FloatRect& intersection_rect,
     DOMHighResTimeStamp render_time,
-    DOMHighResTimeStamp response_end,
+    DOMHighResTimeStamp load_time,
     const AtomicString& identifier,
     int naturalWidth,
     int naturalHeight,
@@ -27,7 +27,7 @@
   DCHECK_GE(naturalHeight, 0);
   DCHECK(element);
   return MakeGarbageCollected<PerformanceElementTiming>(
-      name, url, intersection_rect, render_time, response_end, identifier,
+      name, url, intersection_rect, render_time, load_time, identifier,
       naturalWidth, naturalHeight, id, element);
 }
 
@@ -36,7 +36,7 @@
     const String& url,
     const FloatRect& intersection_rect,
     DOMHighResTimeStamp render_time,
-    DOMHighResTimeStamp response_end,
+    DOMHighResTimeStamp load_time,
     const AtomicString& identifier,
     int naturalWidth,
     int naturalHeight,
@@ -46,7 +46,7 @@
       element_(element),
       intersection_rect_(DOMRectReadOnly::FromFloatRect(intersection_rect)),
       render_time_(render_time),
-      response_end_(response_end),
+      load_time_(load_time),
       identifier_(identifier),
       naturalWidth_(naturalWidth),
       naturalHeight_(naturalHeight),
@@ -72,7 +72,15 @@
 
 void PerformanceElementTiming::BuildJSONValue(V8ObjectBuilder& builder) const {
   PerformanceEntry::BuildJSONValue(builder);
+  builder.Add("renderTime", render_time_);
+  builder.Add("loadTime", load_time_);
   builder.Add("intersectionRect", intersection_rect_);
+  builder.Add("identifier", identifier_);
+  builder.Add("naturalWidth", naturalWidth_);
+  builder.Add("naturalHeight", naturalHeight_);
+  builder.Add("id", id_);
+  builder.Add("element", element());
+  builder.Add("url", url_);
 }
 
 void PerformanceElementTiming::Trace(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.h b/third_party/blink/renderer/core/timing/performance_element_timing.h
index 575b09a..f60fc2e0 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.h
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.h
@@ -25,7 +25,7 @@
                                           const String& url,
                                           const FloatRect& intersection_rect,
                                           DOMHighResTimeStamp render_time,
-                                          DOMHighResTimeStamp response_end,
+                                          DOMHighResTimeStamp load_time,
                                           const AtomicString& identifier,
                                           int naturalWidth,
                                           int naturalHeight,
@@ -35,7 +35,7 @@
                            const String& url,
                            const FloatRect& intersection_rect,
                            DOMHighResTimeStamp render_time,
-                           DOMHighResTimeStamp response_end,
+                           DOMHighResTimeStamp load_time,
                            const AtomicString& identifier,
                            int naturalWidth,
                            int naturalHeight,
@@ -49,7 +49,7 @@
 
   DOMRectReadOnly* intersectionRect() const { return intersection_rect_; }
   DOMHighResTimeStamp renderTime() const { return render_time_; }
-  DOMHighResTimeStamp responseEnd() const { return response_end_; }
+  DOMHighResTimeStamp loadTime() const { return load_time_; }
   AtomicString identifier() const { return identifier_; }
   unsigned naturalWidth() const { return naturalWidth_; }
   unsigned naturalHeight() const { return naturalHeight_; }
@@ -65,7 +65,7 @@
   WeakMember<Element> element_;
   Member<DOMRectReadOnly> intersection_rect_;
   DOMHighResTimeStamp render_time_;
-  DOMHighResTimeStamp response_end_;
+  DOMHighResTimeStamp load_time_;
   AtomicString identifier_;
   unsigned naturalWidth_;
   unsigned naturalHeight_;
diff --git a/third_party/blink/renderer/core/timing/performance_element_timing.idl b/third_party/blink/renderer/core/timing/performance_element_timing.idl
index 0d7c088..e691f56 100644
--- a/third_party/blink/renderer/core/timing/performance_element_timing.idl
+++ b/third_party/blink/renderer/core/timing/performance_element_timing.idl
@@ -6,7 +6,7 @@
 [RuntimeEnabled=ElementTiming]
 interface PerformanceElementTiming : PerformanceEntry {
     readonly attribute DOMHighResTimeStamp renderTime;
-    readonly attribute DOMHighResTimeStamp responseEnd;
+    readonly attribute DOMHighResTimeStamp loadTime;
     readonly attribute DOMRectReadOnly intersectionRect;
     readonly attribute DOMString identifier;
     readonly attribute unsigned long naturalWidth;
diff --git a/third_party/blink/renderer/core/timing/window_performance.cc b/third_party/blink/renderer/core/timing/window_performance.cc
index a7c70f6..2bc6e072 100644
--- a/third_party/blink/renderer/core/timing/window_performance.cc
+++ b/third_party/blink/renderer/core/timing/window_performance.cc
@@ -391,7 +391,7 @@
                                          const String& url,
                                          const FloatRect& rect,
                                          base::TimeTicks start_time,
-                                         base::TimeTicks response_end,
+                                         base::TimeTicks load_time,
                                          const AtomicString& identifier,
                                          const IntSize& intrinsic_size,
                                          const AtomicString& id,
@@ -399,7 +399,7 @@
   DCHECK(RuntimeEnabledFeatures::ElementTimingEnabled(GetExecutionContext()));
   PerformanceElementTiming* entry = PerformanceElementTiming::Create(
       name, url, rect, MonotonicTimeToDOMHighResTimeStamp(start_time),
-      MonotonicTimeToDOMHighResTimeStamp(response_end), identifier,
+      MonotonicTimeToDOMHighResTimeStamp(load_time), identifier,
       intrinsic_size.Width(), intrinsic_size.Height(), id, element);
   if (HasObserverFor(PerformanceEntry::kElement)) {
     UseCounter::Count(GetExecutionContext(),
@@ -442,13 +442,13 @@
 void WindowPerformance::OnLargestContentfulPaintUpdated(
     base::TimeTicks paint_time,
     uint64_t paint_size,
-    base::TimeTicks response_end,
+    base::TimeTicks load_time,
     const AtomicString& id,
     const String& url,
     Element* element) {
   auto* entry = MakeGarbageCollected<LargestContentfulPaint>(
       MonotonicTimeToDOMHighResTimeStamp(paint_time), paint_size,
-      MonotonicTimeToDOMHighResTimeStamp(response_end), id, url, element);
+      MonotonicTimeToDOMHighResTimeStamp(load_time), id, url, element);
   if (HasObserverFor(PerformanceEntry::kLargestContentfulPaint))
     NotifyObserversOfEntry(*entry);
   AddLargestContentfulPaint(entry);
diff --git a/third_party/blink/renderer/core/timing/window_performance.h b/third_party/blink/renderer/core/timing/window_performance.h
index 383c55c..43fe1f4 100644
--- a/third_party/blink/renderer/core/timing/window_performance.h
+++ b/third_party/blink/renderer/core/timing/window_performance.h
@@ -78,7 +78,7 @@
                         const String& url,
                         const FloatRect& rect,
                         base::TimeTicks start_time,
-                        base::TimeTicks response_end,
+                        base::TimeTicks load_time,
                         const AtomicString& identifier,
                         const IntSize& intrinsic_size,
                         const AtomicString& id,
@@ -90,7 +90,7 @@
 
   void OnLargestContentfulPaintUpdated(base::TimeTicks paint_time,
                                        uint64_t paint_size,
-                                       base::TimeTicks response_end,
+                                       base::TimeTicks load_time,
                                        const AtomicString& id,
                                        const String& url,
                                        Element*);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
index d36cb8f..ca69ced 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/base_rendering_context_2d.cc
@@ -69,6 +69,7 @@
 
 void BaseRenderingContext2D::save() {
   state_stack_.back()->Save();
+  ValidateStateStack();
 }
 
 void BaseRenderingContext2D::restore() {
@@ -124,6 +125,7 @@
         sk_canvas->restore();
     }
   }
+  ValidateStateStack();
 }
 
 void BaseRenderingContext2D::Reset() {
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
index bcbf7096..39c7b13 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc
@@ -443,6 +443,8 @@
 }
 
 cc::PaintCanvas* CanvasRenderingContext2D::ExistingDrawingCanvas() const {
+  if (isContextLost())
+    return nullptr;
   if (IsPaintable())
     return canvas()->GetCanvas2DLayerBridge()->Canvas();
   return nullptr;
diff --git a/third_party/blink/renderer/modules/notifications/notification.cc b/third_party/blink/renderer/modules/notifications/notification.cc
index 1806be1..88db765 100644
--- a/third_party/blink/renderer/modules/notifications/notification.cc
+++ b/third_party/blink/renderer/modules/notifications/notification.cc
@@ -30,7 +30,11 @@
 
 #include "third_party/blink/renderer/modules/notifications/notification.h"
 
+#include <memory>
+#include <utility>
+
 #include "base/unguessable_token.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "third_party/blink/public/platform/modules/notifications/web_notification_constants.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/bindings/core/v8/serialization/serialized_script_value_factory.h"
@@ -165,8 +169,7 @@
       data_(std::move(data)),
       prepare_show_timer_(context->GetTaskRunner(TaskType::kMiscPlatformAPI),
                           this,
-                          &Notification::PrepareShow),
-      listener_binding_(this) {
+                          &Notification::PrepareShow) {
   if (data_->show_trigger_timestamp.has_value()) {
     show_trigger_ = TimestampTrigger::Create(static_cast<DOMTimeStamp>(
         data_->show_trigger_timestamp.value().ToJsTime()));
@@ -202,12 +205,13 @@
 void Notification::DidLoadResources(NotificationResourcesLoader* loader) {
   DCHECK_EQ(loader, loader_.Get());
 
-  mojom::blink::NonPersistentNotificationListenerPtr event_listener;
+  mojo::PendingRemote<mojom::blink::NonPersistentNotificationListener>
+      event_listener;
 
   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
       GetExecutionContext()->GetTaskRunner(blink::TaskType::kInternalDefault);
-  listener_binding_.Bind(mojo::MakeRequest(&event_listener, task_runner),
-                         task_runner);
+  listener_receiver_.Bind(event_listener.InitWithNewPipeAndPassReceiver(),
+                          task_runner);
 
   NotificationManager::From(GetExecutionContext())
       ->DisplayNonPersistentNotification(token_, data_->Clone(),
@@ -478,7 +482,7 @@
 }
 
 void Notification::ContextDestroyed(ExecutionContext* context) {
-  listener_binding_.Close();
+  listener_receiver_.reset();
 
   state_ = State::kClosed;
 
diff --git a/third_party/blink/renderer/modules/notifications/notification.h b/third_party/blink/renderer/modules/notifications/notification.h
index b7492dd3..11347c6 100644
--- a/third_party/blink/renderer/modules/notifications/notification.h
+++ b/third_party/blink/renderer/modules/notifications/notification.h
@@ -31,7 +31,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_NOTIFICATIONS_NOTIFICATION_H_
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_NOTIFICATIONS_NOTIFICATION_H_
 
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-blink.h"
@@ -192,8 +192,8 @@
 
   Member<NotificationResourcesLoader> loader_;
 
-  mojo::Binding<mojom::blink::NonPersistentNotificationListener>
-      listener_binding_;
+  mojo::Receiver<mojom::blink::NonPersistentNotificationListener>
+      listener_receiver_{this};
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/notifications/notification_manager.cc b/third_party/blink/renderer/modules/notifications/notification_manager.cc
index 2e0a282..6aa4311 100644
--- a/third_party/blink/renderer/modules/notifications/notification_manager.cc
+++ b/third_party/blink/renderer/modules/notifications/notification_manager.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/renderer/modules/notifications/notification_manager.h"
 
+#include <utility>
+
 #include "base/numerics/safe_conversions.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/mojom/notifications/notification.mojom-blink.h"
@@ -117,7 +119,8 @@
     const String& token,
     mojom::blink::NotificationDataPtr notification_data,
     mojom::blink::NotificationResourcesPtr notification_resources,
-    mojom::blink::NonPersistentNotificationListenerPtr event_listener) {
+    mojo::PendingRemote<mojom::blink::NonPersistentNotificationListener>
+        event_listener) {
   DCHECK(!token.IsEmpty());
   DCHECK(notification_resources);
   GetNotificationService()->DisplayNonPersistentNotification(
@@ -224,7 +227,7 @@
   resolver->Resolve(notifications);
 }
 
-const mojom::blink::NotificationServicePtr&
+const mojo::Remote<mojom::blink::NotificationService>&
 NotificationManager::GetNotificationService() {
   if (!notification_service_) {
     if (auto* provider = GetSupplementable()->GetInterfaceProvider()) {
@@ -232,9 +235,9 @@
       scoped_refptr<base::SingleThreadTaskRunner> task_runner =
           GetSupplementable()->GetTaskRunner(TaskType::kMiscPlatformAPI);
       provider->GetInterface(
-          mojo::MakeRequest(&notification_service_, std::move(task_runner)));
+          notification_service_.BindNewPipeAndPassReceiver(task_runner));
 
-      notification_service_.set_connection_error_handler(
+      notification_service_.set_disconnect_handler(
           WTF::Bind(&NotificationManager::OnNotificationServiceConnectionError,
                     WrapWeakPersistent(this)));
     }
diff --git a/third_party/blink/renderer/modules/notifications/notification_manager.h b/third_party/blink/renderer/modules/notifications/notification_manager.h
index b0f90bc..abea5e77 100644
--- a/third_party/blink/renderer/modules/notifications/notification_manager.h
+++ b/third_party/blink/renderer/modules/notifications/notification_manager.h
@@ -6,6 +6,8 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_NOTIFICATIONS_NOTIFICATION_MANAGER_H_
 
 #include "base/macros.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/notifications/notification_service.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -53,7 +55,8 @@
       const String& token,
       mojom::blink::NotificationDataPtr notification_data,
       mojom::blink::NotificationResourcesPtr notification_resources,
-      mojom::blink::NonPersistentNotificationListenerPtr event_listener);
+      mojo::PendingRemote<mojom::blink::NonPersistentNotificationListener>
+          event_listener);
 
   // Closes the notification that was most recently displayed with this token.
   void CloseNonPersistentNotification(const String& token);
@@ -89,9 +92,10 @@
       const Vector<String>& notification_ids,
       Vector<mojom::blink::NotificationDataPtr> notification_datas);
 
-  // Returns an initialized NotificationServicePtr. A connection will be
+  // Returns an initialized NotificationService remote. A connection will be
   // established the first time this method is called.
-  const mojom::blink::NotificationServicePtr& GetNotificationService();
+  const mojo::Remote<mojom::blink::NotificationService>&
+  GetNotificationService();
 
   void OnPermissionRequestComplete(
       ScriptPromiseResolver* resolver,
@@ -101,7 +105,7 @@
   void OnNotificationServiceConnectionError();
   void OnPermissionServiceConnectionError();
 
-  mojom::blink::NotificationServicePtr notification_service_;
+  mojo::Remote<mojom::blink::NotificationService> notification_service_;
   mojom::blink::PermissionServicePtr permission_service_;
 
   DISALLOW_COPY_AND_ASSIGN(NotificationManager);
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 1fe682f9..a764b5e 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -257,6 +257,10 @@
     "animation/compositor_animation_delegate.h",
     "animation/compositor_animation_timeline.cc",
     "animation/compositor_animation_timeline.h",
+    "animation/compositor_color_animation_curve.cc",
+    "animation/compositor_color_animation_curve.h",
+    "animation/compositor_color_keyframe.cc",
+    "animation/compositor_color_keyframe.h",
     "animation/compositor_filter_animation_curve.cc",
     "animation/compositor_filter_animation_curve.h",
     "animation/compositor_filter_keyframe.cc",
@@ -1612,6 +1616,7 @@
     "animation/animation_translation_util_test.cc",
     "animation/compositor_animation_test.cc",
     "animation/compositor_animation_timeline_test.cc",
+    "animation/compositor_color_animation_curve_test.cc",
     "animation/compositor_float_animation_curve_test.cc",
     "animation/compositor_keyframe_model_test.cc",
     "animation/timing_function_test.cc",
diff --git a/third_party/blink/renderer/platform/animation/compositor_color_animation_curve.cc b/third_party/blink/renderer/platform/animation/compositor_color_animation_curve.cc
new file mode 100644
index 0000000..5e9b97c
--- /dev/null
+++ b/third_party/blink/renderer/platform/animation/compositor_color_animation_curve.cc
@@ -0,0 +1,65 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/memory/ptr_util.h"
+#include "cc/animation/animation_curve.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "cc/animation/timing_function.h"
+
+namespace blink {
+
+CompositorColorAnimationCurve::CompositorColorAnimationCurve()
+    : curve_(cc::KeyframedColorAnimationCurve::Create()) {}
+
+CompositorColorAnimationCurve::CompositorColorAnimationCurve(
+    std::unique_ptr<cc::KeyframedColorAnimationCurve> curve)
+    : curve_(std::move(curve)) {}
+
+CompositorColorAnimationCurve::~CompositorColorAnimationCurve() = default;
+
+std::unique_ptr<CompositorColorAnimationCurve>
+CompositorColorAnimationCurve::CreateForTesting(
+    std::unique_ptr<cc::KeyframedColorAnimationCurve> curve) {
+  return base::WrapUnique(new CompositorColorAnimationCurve(std::move(curve)));
+}
+
+CompositorColorAnimationCurve::Keyframes
+CompositorColorAnimationCurve::KeyframesForTesting() const {
+  Keyframes keyframes;
+  for (const auto& cc_keyframe : curve_->keyframes_for_testing()) {
+    keyframes.push_back(
+        base::WrapUnique(new CompositorColorKeyframe(cc_keyframe->Clone())));
+  }
+  return keyframes;
+}
+
+void CompositorColorAnimationCurve::AddKeyframe(
+    const CompositorColorKeyframe& keyframe) {
+  curve_->AddKeyframe(keyframe.CloneToCC());
+}
+
+void CompositorColorAnimationCurve::SetTimingFunction(
+    const TimingFunction& timing_function) {
+  curve_->SetTimingFunction(timing_function.CloneToCC());
+}
+
+void CompositorColorAnimationCurve::SetScaledDuration(double scaled_duration) {
+  curve_->set_scaled_duration(scaled_duration);
+}
+
+SkColor CompositorColorAnimationCurve::GetValue(double time) const {
+  return curve_->GetValue(base::TimeDelta::FromSecondsD(time));
+}
+
+std::unique_ptr<cc::AnimationCurve>
+CompositorColorAnimationCurve::CloneToAnimationCurve() const {
+  return curve_->Clone();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h b/third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h
new file mode 100644
index 0000000..bbdbc9f
--- /dev/null
+++ b/third_party/blink/renderer/platform/animation/compositor_color_animation_curve.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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_ANIMATION_COMPOSITOR_COLOR_ANIMATION_CURVE_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_ANIMATION_COMPOSITOR_COLOR_ANIMATION_CURVE_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/scoped_refptr.h"
+#include "third_party/blink/renderer/platform/animation/compositor_animation_curve.h"
+#include "third_party/blink/renderer/platform/animation/compositor_color_keyframe.h"
+#include "third_party/blink/renderer/platform/animation/timing_function.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace cc {
+class KeyframedColorAnimationCurve;
+}
+
+namespace blink {
+
+class CompositorColorKeyframe;
+
+// A keyframed color animation curve.
+class PLATFORM_EXPORT CompositorColorAnimationCurve
+    : public CompositorAnimationCurve {
+ public:
+  CompositorColorAnimationCurve();
+  ~CompositorColorAnimationCurve() override;
+
+  void AddKeyframe(const CompositorColorKeyframe&);
+  void SetTimingFunction(const TimingFunction&);
+  void SetScaledDuration(double);
+  SkColor GetValue(double time) const;
+
+  // CompositorAnimationCurve implementation.
+  std::unique_ptr<cc::AnimationCurve> CloneToAnimationCurve() const override;
+
+  static std::unique_ptr<CompositorColorAnimationCurve> CreateForTesting(
+      std::unique_ptr<cc::KeyframedColorAnimationCurve>);
+
+  using Keyframes = Vector<std::unique_ptr<CompositorColorKeyframe>>;
+  Keyframes KeyframesForTesting() const;
+
+ private:
+  CompositorColorAnimationCurve(
+      std::unique_ptr<cc::KeyframedColorAnimationCurve>);
+
+  std::unique_ptr<cc::KeyframedColorAnimationCurve> curve_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompositorColorAnimationCurve);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_ANIMATION_COMPOSITOR_COLOR_ANIMATION_CURVE_H_
diff --git a/third_party/blink/renderer/platform/animation/compositor_color_animation_curve_test.cc b/third_party/blink/renderer/platform/animation/compositor_color_animation_curve_test.cc
new file mode 100644
index 0000000..e0f27cb
--- /dev/null
+++ b/third_party/blink/renderer/platform/animation/compositor_color_animation_curve_test.cc
@@ -0,0 +1,275 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h"
+
+#include <memory>
+
+#include "cc/animation/timing_function.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using blink::CompositorAnimationCurve;
+using blink::CompositorColorAnimationCurve;
+using blink::CompositorColorKeyframe;
+
+namespace blink {
+
+// Tests that a color animation with one keyframe works as expected.
+TEST(WebColorAnimationCurveTest, OneColorKeyframe) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(0, SK_ColorGREEN,
+                                             *LinearTimingFunction::Shared()));
+  EXPECT_EQ(SK_ColorGREEN, curve->GetValue(-1));
+  EXPECT_EQ(SK_ColorGREEN, curve->GetValue(0));
+  EXPECT_EQ(SK_ColorGREEN, curve->GetValue(0.5));
+  EXPECT_EQ(SK_ColorGREEN, curve->GetValue(1));
+  EXPECT_EQ(SK_ColorGREEN, curve->GetValue(2));
+}
+
+// Tests that a color animation with two keyframes works as expected.
+TEST(WebColorAnimationCurveTest, TwoColorKeyframe) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(0, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 200, 100),
+                                             *LinearTimingFunction::Shared()));
+  EXPECT_EQ(SkColorSetRGB(0, 100, 0), curve->GetValue(-1));
+  EXPECT_EQ(SkColorSetRGB(0, 100, 0), curve->GetValue(0));
+  EXPECT_EQ(SkColorSetRGB(0, 150, 50), curve->GetValue(0.5));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 100), curve->GetValue(1));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 100), curve->GetValue(2));
+}
+
+// Tests that a color animation with changing alpha channel
+TEST(WebColorAnimationCurveTest, TwoAlphaKeyframe) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(0, SkColorSetARGB(100, 0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(
+      1, SkColorSetARGB(255, 0, 200, 100), *LinearTimingFunction::Shared()));
+  EXPECT_EQ(SkColorSetARGB(100, 0, 100, 0), curve->GetValue(-1));
+  EXPECT_EQ(SkColorSetARGB(100, 0, 100, 0), curve->GetValue(0));
+  EXPECT_EQ(SkColorSetARGB(178, 0, 172, 72), curve->GetValue(0.5));
+  EXPECT_EQ(SkColorSetARGB(255, 0, 200, 100), curve->GetValue(1));
+  EXPECT_EQ(SkColorSetARGB(255, 0, 200, 100), curve->GetValue(2));
+}
+
+// Tests that a color animation with three keyframes works as expected.
+TEST(WebColorAnimationCurveTest, ThreeColorKeyframe) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(0, SkColorSetRGB(0, 50, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 50),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(2, SkColorSetRGB(0, 200, 200),
+                                             *LinearTimingFunction::Shared()));
+  EXPECT_EQ(SkColorSetRGB(0, 50, 0), curve->GetValue(-1));
+  EXPECT_EQ(SkColorSetRGB(0, 50, 0), curve->GetValue(0));
+  EXPECT_EQ(SkColorSetRGB(0, 75, 25), curve->GetValue(0.5));
+  EXPECT_EQ(SkColorSetRGB(0, 100, 50), curve->GetValue(1));
+  EXPECT_EQ(SkColorSetRGB(0, 150, 125), curve->GetValue(1.5));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 200), curve->GetValue(2));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 200), curve->GetValue(3));
+}
+
+// Tests that a color animation with multiple keys at a given time works sanely.
+TEST(WebColorAnimationCurveTest, RepeatedColorKeyTimes) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(0, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 200, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(2, SkColorSetRGB(0, 200, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  EXPECT_EQ(SkColorSetRGB(0, 100, 0), curve->GetValue(-1));
+  EXPECT_EQ(SkColorSetRGB(0, 100, 0), curve->GetValue(0));
+  EXPECT_EQ(SkColorSetRGB(0, 100, 0), curve->GetValue(0.5));
+
+  // There is a discontinuity at 1. Any value between 100 and 200 in the green
+  // channel is valid
+  SkColor value = curve->GetValue(1);
+  EXPECT_EQ(SkColorGetR(value), 0U);
+  EXPECT_TRUE(SkColorGetG(value) >= 100U && SkColorGetG(value) <= 200U);
+  EXPECT_EQ(SkColorGetB(value), 0U);
+
+  EXPECT_EQ(SkColorSetRGB(0, 200, 0), curve->GetValue(1.5));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 0), curve->GetValue(2));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 0), curve->GetValue(3));
+}
+
+// Tests that the keyframes may be added out of order.
+TEST(WebColorAnimationCurveTest, UnsortedKeyframes) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(2, SkColorSetRGB(0, 200, 200),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(0, SkColorSetRGB(0, 50, 0),
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 50),
+                                             *LinearTimingFunction::Shared()));
+  EXPECT_EQ(SkColorSetRGB(0, 50, 0), curve->GetValue(-1));
+  EXPECT_EQ(SkColorSetRGB(0, 50, 0), curve->GetValue(0));
+  EXPECT_EQ(SkColorSetRGB(0, 75, 25), curve->GetValue(0.5));
+  EXPECT_EQ(SkColorSetRGB(0, 100, 50), curve->GetValue(1));
+  EXPECT_EQ(SkColorSetRGB(0, 150, 125), curve->GetValue(1.5));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 200), curve->GetValue(2));
+  EXPECT_EQ(SkColorSetRGB(0, 200, 200), curve->GetValue(3));
+}
+
+// Tests that a cubic bezier timing function works as expected.
+TEST(WebColorAnimationCurveTest, CubicBezierTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  scoped_refptr<CubicBezierTimingFunction> cubic =
+      CubicBezierTimingFunction::Create(0.25, 0, 0.75, 1);
+  curve->AddKeyframe(CompositorColorKeyframe(0, SK_ColorBLACK, *cubic));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  EXPECT_EQ(0U, SkColorGetG(curve->GetValue(0)));
+  EXPECT_LT(0U, SkColorGetG(curve->GetValue(0.25)));
+  EXPECT_GT(25U, SkColorGetG(curve->GetValue(0.25)));
+  EXPECT_EQ(50U, SkColorGetG(curve->GetValue(0.5)));
+  EXPECT_LT(75U, SkColorGetG(curve->GetValue(0.75)));
+  EXPECT_GT(100U, SkColorGetG(curve->GetValue(0.75)));
+  EXPECT_EQ(100U, SkColorGetG(curve->GetValue(1)));
+}
+
+// Tests that an ease timing function works as expected.
+TEST(WebColorAnimationCurveTest, EaseTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(
+      CompositorColorKeyframe(0, SK_ColorBLACK,
+                              *CubicBezierTimingFunction::Preset(
+                                  CubicBezierTimingFunction::EaseType::EASE)));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  std::unique_ptr<cc::TimingFunction> timing_function(
+      cc::CubicBezierTimingFunction::CreatePreset(
+          CubicBezierTimingFunction::EaseType::EASE));
+  for (int i = 0; i <= 4; ++i) {
+    const double time = i * 0.25;
+    EXPECT_EQ((unsigned)round(timing_function->GetValue(time) * 100),
+              SkColorGetG(curve->GetValue(time)));
+  }
+}
+
+// Tests using a linear timing function.
+TEST(WebColorAnimationCurveTest, LinearTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(0, SK_ColorBLACK,
+                                             *LinearTimingFunction::Shared()));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  for (int i = 0; i <= 4; ++i) {
+    EXPECT_EQ(i * 25U, SkColorGetG(curve->GetValue(i * 0.25)));
+  }
+}
+
+// Tests that an ease in timing function works as expected.
+TEST(WebColorAnimationCurveTest, EaseInTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(
+      0, SK_ColorBLACK,
+      *CubicBezierTimingFunction::Preset(
+          CubicBezierTimingFunction::EaseType::EASE_IN)));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  std::unique_ptr<cc::TimingFunction> timing_function(
+      cc::CubicBezierTimingFunction::CreatePreset(
+          CubicBezierTimingFunction::EaseType::EASE_IN));
+  for (int i = 0; i <= 4; ++i) {
+    const double time = i * 0.25;
+    EXPECT_EQ((unsigned)round(timing_function->GetValue(time) * 100),
+              SkColorGetG(curve->GetValue(time)));
+  }
+}
+
+// Tests that an ease in timing function works as expected.
+TEST(WebColorAnimationCurveTest, EaseOutTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(
+      0, SK_ColorBLACK,
+      *CubicBezierTimingFunction::Preset(
+          CubicBezierTimingFunction::EaseType::EASE_OUT)));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  std::unique_ptr<cc::TimingFunction> timing_function(
+      cc::CubicBezierTimingFunction::CreatePreset(
+          CubicBezierTimingFunction::EaseType::EASE_OUT));
+  for (int i = 0; i <= 4; ++i) {
+    const double time = i * 0.25;
+    EXPECT_EQ((unsigned)round(timing_function->GetValue(time) * 100),
+              SkColorGetG(curve->GetValue(time)));
+  }
+}
+
+// Tests that an ease in timing function works as expected.
+TEST(WebColorAnimationCurveTest, EaseInOutTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(CompositorColorKeyframe(
+      0, SK_ColorBLACK,
+      *CubicBezierTimingFunction::Preset(
+          CubicBezierTimingFunction::EaseType::EASE_IN_OUT)));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  std::unique_ptr<cc::TimingFunction> timing_function(
+      cc::CubicBezierTimingFunction::CreatePreset(
+          CubicBezierTimingFunction::EaseType::EASE_IN_OUT));
+  for (int i = 0; i <= 4; ++i) {
+    const double time = i * 0.25;
+    EXPECT_EQ((unsigned)round(timing_function->GetValue(time) * 100),
+              SkColorGetG(curve->GetValue(time)));
+  }
+}
+
+// Tests that an ease in timing function works as expected.
+TEST(WebColorAnimationCurveTest, CustomBezierTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  double x1 = 0.3;
+  double y1 = 0.2;
+  double x2 = 0.8;
+  double y2 = 0.7;
+  scoped_refptr<CubicBezierTimingFunction> cubic =
+      CubicBezierTimingFunction::Create(x1, y1, x2, y2);
+  curve->AddKeyframe(CompositorColorKeyframe(0, SK_ColorBLACK, *cubic));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  std::unique_ptr<cc::TimingFunction> timing_function(
+      cc::CubicBezierTimingFunction::Create(x1, y1, x2, y2));
+  for (int i = 0; i <= 4; ++i) {
+    const double time = i * 0.25;
+    EXPECT_EQ((unsigned)round(timing_function->GetValue(time) * 100),
+              SkColorGetG(curve->GetValue(time)));
+  }
+}
+
+// Tests that the default timing function is indeed ease.
+TEST(WebColorAnimationCurveTest, DefaultTimingFunction) {
+  auto curve = std::make_unique<CompositorColorAnimationCurve>();
+  curve->AddKeyframe(
+      CompositorColorKeyframe(0, SK_ColorBLACK,
+                              *CubicBezierTimingFunction::Preset(
+                                  CubicBezierTimingFunction::EaseType::EASE)));
+  curve->AddKeyframe(CompositorColorKeyframe(1, SkColorSetRGB(0, 100, 0),
+                                             *LinearTimingFunction::Shared()));
+
+  std::unique_ptr<cc::TimingFunction> timing_function(
+      cc::CubicBezierTimingFunction::CreatePreset(
+          CubicBezierTimingFunction::EaseType::EASE));
+  for (int i = 0; i <= 4; ++i) {
+    const double time = i * 0.25;
+    EXPECT_EQ((unsigned)round(timing_function->GetValue(time) * 100),
+              SkColorGetG(curve->GetValue(time)));
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/animation/compositor_color_keyframe.cc b/third_party/blink/renderer/platform/animation/compositor_color_keyframe.cc
new file mode 100644
index 0000000..1f11e4d
--- /dev/null
+++ b/third_party/blink/renderer/platform/animation/compositor_color_keyframe.cc
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/animation/compositor_color_keyframe.h"
+
+#include "third_party/blink/renderer/platform/animation/timing_function.h"
+
+namespace blink {
+
+CompositorColorKeyframe::CompositorColorKeyframe(
+    double time,
+    SkColor value,
+    const TimingFunction& timing_function)
+    : color_keyframe_(
+          cc::ColorKeyframe::Create(base::TimeDelta::FromSecondsD(time),
+                                    value,
+                                    timing_function.CloneToCC())) {}
+
+CompositorColorKeyframe::CompositorColorKeyframe(
+    std::unique_ptr<cc::ColorKeyframe> color_keyframe)
+    : color_keyframe_(std::move(color_keyframe)) {}
+
+CompositorColorKeyframe::~CompositorColorKeyframe() = default;
+
+double CompositorColorKeyframe::Time() const {
+  return color_keyframe_->Time().InSecondsF();
+}
+
+const cc::TimingFunction* CompositorColorKeyframe::CcTimingFunction() const {
+  return color_keyframe_->timing_function();
+}
+
+std::unique_ptr<cc::ColorKeyframe> CompositorColorKeyframe::CloneToCC() const {
+  return color_keyframe_->Clone();
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/animation/compositor_color_keyframe.h b/third_party/blink/renderer/platform/animation/compositor_color_keyframe.h
new file mode 100644
index 0000000..beb4fc1a
--- /dev/null
+++ b/third_party/blink/renderer/platform/animation/compositor_color_keyframe.h
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_ANIMATION_COMPOSITOR_COLOR_KEYFRAME_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_ANIMATION_COMPOSITOR_COLOR_KEYFRAME_H_
+
+#include "base/macros.h"
+#include "cc/animation/keyframed_animation_curve.h"
+#include "third_party/blink/renderer/platform/animation/compositor_keyframe.h"
+#include "third_party/blink/renderer/platform/platform_export.h"
+
+namespace blink {
+
+class TimingFunction;
+
+class PLATFORM_EXPORT CompositorColorKeyframe : public CompositorKeyframe {
+ public:
+  CompositorColorKeyframe(double time, SkColor value, const TimingFunction&);
+  CompositorColorKeyframe(std::unique_ptr<cc::ColorKeyframe>);
+  ~CompositorColorKeyframe() override;
+
+  // CompositorKeyframe implementation.
+  double Time() const override;
+  const cc::TimingFunction* CcTimingFunction() const override;
+
+  SkColor Value() { return color_keyframe_->Value(); }
+  std::unique_ptr<cc::ColorKeyframe> CloneToCC() const;
+
+ private:
+  std::unique_ptr<cc::ColorKeyframe> color_keyframe_;
+
+  DISALLOW_COPY_AND_ASSIGN(CompositorColorKeyframe);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_ANIMATION_COMPOSITOR_COLOR_KEYFRAME_H_
diff --git a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc
index 60a8fb9..656f2f2 100644
--- a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc
+++ b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.cc
@@ -10,6 +10,7 @@
 #include "cc/animation/animation_id_provider.h"
 #include "cc/animation/keyframed_animation_curve.h"
 #include "third_party/blink/renderer/platform/animation/compositor_animation_curve.h"
+#include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h"
 #include "third_party/blink/renderer/platform/animation/compositor_float_animation_curve.h"
 
 using cc::KeyframeModel;
@@ -137,4 +138,15 @@
       std::move(keyframed_curve));
 }
 
+std::unique_ptr<CompositorColorAnimationCurve>
+CompositorKeyframeModel::ColorCurveForTesting() const {
+  const cc::AnimationCurve* curve = keyframe_model_->curve();
+  DCHECK_EQ(cc::AnimationCurve::COLOR, curve->Type());
+
+  auto keyframed_curve = base::WrapUnique(
+      static_cast<cc::KeyframedColorAnimationCurve*>(curve->Clone().release()));
+  return CompositorColorAnimationCurve::CreateForTesting(
+      std::move(keyframed_curve));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h
index 8c6af44e9..9a3f4b8 100644
--- a/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h
+++ b/third_party/blink/renderer/platform/animation/compositor_keyframe_model.h
@@ -24,6 +24,7 @@
 
 class CompositorAnimationCurve;
 class CompositorFloatAnimationCurve;
+class CompositorColorAnimationCurve;
 
 // A compositor driven animation.
 class PLATFORM_EXPORT CompositorKeyframeModel {
@@ -79,6 +80,7 @@
   std::unique_ptr<cc::KeyframeModel> ReleaseCcKeyframeModel();
 
   std::unique_ptr<CompositorFloatAnimationCurve> FloatCurveForTesting() const;
+  std::unique_ptr<CompositorColorAnimationCurve> ColorCurveForTesting() const;
 
   const std::string& GetCustomPropertyNameForTesting() const {
     return keyframe_model_->GetCustomPropertyNameForTesting();
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-features=NavigationLoaderOnUI b/third_party/blink/web_tests/FlagExpectations/enable-features=NavigationLoaderOnUI
index edc53d75..9df6181 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-features=NavigationLoaderOnUI
+++ b/third_party/blink/web_tests/FlagExpectations/enable-features=NavigationLoaderOnUI
@@ -1,9 +1,2 @@
 # These tests currently fail when run with --enable-features=NavigationLoaderOnUI
 # See https://crbug.com/824840
-
-# service worker
-Bug(none) external/wpt/html/browsers/offline/appcache/workers/appcache-worker.https.html [ Skip ]
-Bug(none) virtual/not-omt-sw-fetch/external/wpt/html/browsers/offline/appcache/workers/appcache-worker.https.html [ Skip ]
-Bug(none) virtual/omt-worker-fetch/external/wpt/html/browsers/offline/appcache/workers/appcache-worker.https.html [ Skip ]
-
-Bug(none) http/tests/misc/xhtml.php [ Failure ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index ad644e4..34eae48 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -3400,7 +3400,6 @@
 crbug.com/626703 [ Retina ] virtual/blink-cors/external/wpt/referrer-policy/no-referrer-when-downgrade/http-rp/same-origin/http-https/iframe-tag/no-redirect/upgrade-protocol.http.html [ Timeout ]
 crbug.com/626703 [ Retina ] virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/http-rp/same-origin/http-http/a-tag/no-redirect/same-origin-insecure.http.html [ Timeout ]
 crbug.com/626703 [ Retina ] virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/http-rp/cross-origin/http-https/iframe-tag/swap-origin-redirect/cross-origin.http.html [ Timeout ]
-crbug.com/626703 [ Retina ] virtual/at-property/external/wpt/css/css-properties-values-api/idlharness.html [ Timeout ]
 crbug.com/626703 [ Retina ] virtual/blink-cors/external/wpt/referrer-policy/no-referrer/attr-referrer/same-origin/http-https/script-tag/keep-origin-redirect/generic.http.html [ Timeout ]
 crbug.com/626703 [ Retina ] virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/http-rp/same-origin/http-https/iframe-tag/swap-origin-redirect/same-origin-insecure.http.html [ Timeout ]
 crbug.com/626703 [ Retina ] virtual/blink-cors/external/wpt/referrer-policy/origin-when-cross-origin/meta-referrer/cross-origin/http-http/iframe-tag/keep-origin-redirect/cross-origin.http.html [ Timeout ]
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 64c1ecc4..90554a1 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
@@ -139782,6 +139782,9 @@
    "css/css-properties-values-api/OWNERS": [
     []
    ],
+   "css/css-properties-values-api/idlharness-expected.txt": [
+    []
+   ],
    "css/css-properties-values-api/registered-property-computation-expected.txt": [
     []
    ],
@@ -369102,6 +369105,10 @@
    "b4de63045f339d163830921e4e201e698edae47c",
    "testharness"
   ],
+  "css/css-properties-values-api/idlharness-expected.txt": [
+   "a1c521155355a9f7e3e315af5d5dd9dfc56172e3",
+   "support"
+  ],
   "css/css-properties-values-api/idlharness.html": [
    "6f053757c3cef099f0cea41716a942dfa7e66100",
    "testharness"
@@ -423047,11 +423054,11 @@
    "support"
   ],
   "html/dom/interfaces.https.html": [
-   "47254a4361c46ad5fdc46efd2165da1bbab4c9b5",
+   "6ca7721253200d4511758f5ba15a61f756509370",
    "testharness"
   ],
   "html/dom/interfaces.https_exclude=(Document_Window_HTML._)-expected.txt": [
-   "efc72ac6e85df27db2bee1d3de2d0524ece4421c",
+   "6f2011e1588d0ff0d5ba675c9b6e32ddfec0a556",
    "support"
   ],
   "html/dom/interfaces.https_include=(Document_Window)-expected.txt": [
@@ -439919,7 +439926,7 @@
    "support"
   ],
   "interfaces/css-properties-values-api.idl": [
-   "4ba38970e30ac2610e35c266e07f381e3a6b4ec5",
+   "ee444ebb29d8b5b15c96d259bb8a1f2bdd280d5f",
    "support"
   ],
   "interfaces/css-pseudo.idl": [
@@ -440007,7 +440014,7 @@
    "support"
   ],
   "interfaces/geometry.idl": [
-   "5d5fe4fc2c48305c0ea7e9ce0859af3700d9b14c",
+   "1b83959465cf5f76bf52d2f8db51426281c07470",
    "support"
   ],
   "interfaces/gyroscope.idl": [
@@ -483735,7 +483742,7 @@
    "testharness"
   ],
   "webrtc/RTCPeerConnection-createDataChannel-expected.txt": [
-   "8d92a639d7d1dc0e642a262d781ea70d510d7478",
+   "74dac5eded6acf2de528ce5336c2ab23adb2bcea",
    "support"
   ],
   "webrtc/RTCPeerConnection-createDataChannel.html": [
@@ -483807,7 +483814,7 @@
    "testharness"
   ],
   "webrtc/RTCPeerConnection-ondatachannel-expected.txt": [
-   "d04de3ba27102ce318f9cd68e1cb91b1b76da7b6",
+   "1e0c99b89ba14704f795efd5e43f8d7a7d448c40",
    "support"
   ],
   "webrtc/RTCPeerConnection-ondatachannel.html": [
@@ -484155,7 +484162,7 @@
    "testharness"
   ],
   "webrtc/historical-expected.txt": [
-   "4353e8649e10e9f6c79895c7ff82bbbe2324c2c4",
+   "68050e46488fde9f9ec27a45e978ca73790faf18",
    "support"
   ],
   "webrtc/historical.html": [
@@ -484163,7 +484170,7 @@
    "testharness"
   ],
   "webrtc/idlharness.https.window-expected.txt": [
-   "1f26f0d506c51105c2e769caa4270ebc6d3e472a",
+   "2dd1e4d624cedabafd9df11798efa63e9334690a",
    "support"
   ],
   "webrtc/idlharness.https.window.js": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/idlharness-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/idlharness-expected.txt
new file mode 100644
index 0000000..a1c52115
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/idlharness-expected.txt
@@ -0,0 +1,20 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS Partial namespace CSS: original namespace defined
+PASS Partial interface CSSRule: original interface defined
+FAIL CSSPropertyRule interface: existence and properties of interface object assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface object length assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface object name assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute name assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute syntax assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute inherits assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute initialValue assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSRule interface: constant PROPERTY_RULE on interface object assert_own_property: expected property "PROPERTY_RULE" missing
+FAIL CSSRule interface: constant PROPERTY_RULE on interface prototype object assert_own_property: expected property "PROPERTY_RULE" missing
+PASS CSS namespace: operation escape(CSSOMString)
+PASS CSS namespace: operation registerProperty(PropertyDefinition)
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-tables/percent-width-cell-dynamic.html b/third_party/blink/web_tests/external/wpt/css/css-tables/percent-width-cell-dynamic.html
new file mode 100644
index 0000000..5c7ef3b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-tables/percent-width-cell-dynamic.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<link rel="help" href="https://crbug.com/984642" />
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<style>
+html { overflow: hidden; }
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div id="target">
+  <div style="width: 10%;">
+    <div style="display: inline-table;">
+      <div style="display: table-cell; width: 100%;">
+        <span style="display: inline-block; width: 100%; height: 100px; background: green;"></span>
+      </div>
+      <div style="display: table-cell;">
+        <span style="display: inline-block; width: 10px; height: 100px; background: green;"></span>
+      </div>
+     </div>
+  </div>
+</div>
+<script>
+document.body.offsetTop;
+document.getElementById('target').style.width = '1000px';
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-plus-opacity-ref.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-plus-opacity-ref.html
new file mode 100644
index 0000000..347b7a6
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-plus-opacity-ref.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>backdrop-filter: Correctly apply backdrop-filter with opacity</title>
+<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
+
+
+
+<p>Expected: A green box.</p>
+
+<div class="greenbox"></div>
+
+
+<style>
+.greenbox {
+  position: absolute;
+  background: green;
+  width: 100px;
+  height: 100px;
+  top: 100px;
+  left: 60px;
+}
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-plus-opacity.html b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-plus-opacity.html
new file mode 100644
index 0000000..cb189f9
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/filter-effects/backdrop-filter-plus-opacity.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>backdrop-filter: Correctly apply backdrop-filter with opacity</title>
+<link rel="author" title="Mason Freed" href="mailto:masonfreed@chromium.org">
+<link rel="help" href="https://drafts.fxtf.org/filter-effects-2/#BackdropFilterProperty">
+<link rel="match"  href="backdrop-filter-plus-opacity-ref.html">
+
+<p>Expected: A green box.</p>
+
+<div class="greenbox"></div>
+<div class="filter"></div>
+
+<style>
+.greenbox {
+  position: absolute;
+  background: green;
+  width: 100px;
+  height: 100px;
+  top: 100px;
+  left: 60px;
+}
+.filter {
+  position: absolute;
+  width: 200px;
+  height: 200px;
+  top: 50px;
+  left: 10px;
+  backdrop-filter: invert(1);
+  opacity: 0;
+}
+</style>
diff --git a/third_party/blink/web_tests/external/wpt/css/mediaqueries/width-equals-window-inner-width.html b/third_party/blink/web_tests/external/wpt/css/mediaqueries/width-equals-window-inner-width.html
new file mode 100644
index 0000000..775c111
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/mediaqueries/width-equals-window-inner-width.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>CSS Test: CSS media query width equals window innerWidth</title>
+
+<link rel="author" title="Jinfeng Ma" href="mailto:majinfeng1@xiaomi.org">
+<link rel="help" href="https://www.w3.org/TR/css3-mediaqueries/#width">
+<link rel="help" href="https://drafts.csswg.org/cssom-view/#dom-window-innerwidth">
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<!--
+It'd be best to programmatically change device scale factor so that the document
+width becomes non integral but for now this test is only effective when run on
+devices with a fractional device scale factor.
+-->
+<script type="text/javascript">
+    'use strict';
+    test(() => {
+      assert_true(window.matchMedia('(width: ' + window.innerWidth + 'px)').matches);
+    }, 'CSS media query width equals window innerWidth.');
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html b/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html
index 9bc8b5f4..a6ad7ace 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html
+++ b/third_party/blink/web_tests/external/wpt/element-timing/images-repeated-resource.html
@@ -13,8 +13,10 @@
 <script>
   let beforeRender;
   let numEntries = 0;
-  let responseEnd1;
-  let responseEnd2;
+  let loadTime1;
+  let loadTime2;
+  let renderTime1;
+  let renderTime2;
   let img;
   let img2;
   const index = window.location.href.lastIndexOf('/');
@@ -26,23 +28,33 @@
     }
     const observer = new PerformanceObserver(
       t.step_func(function(entryList) {
-        entryList.getEntries().forEach(entry => {
-          // Easier to check the |element| attribute here since element ID is the same for both images.
-          checkElement(entry, pathname, entry.identifier, 'image_id', beforeRender, null);
-          checkNaturalSize(entry, 100, 100);
-          if (entry.identifier === 'my_image') {
-            ++numEntries;
-            responseEnd1 = entry.responseEnd;
-            assert_equals(entry.element, img);
-          }
-          else if (entry.identifier === 'my_image2') {
-            ++numEntries;
-            responseEnd2 = entry.responseEnd;
-            assert_equals(entry.element, img2);
-          }
-        });
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        // Easier to check the |element| attribute here since element ID is the same for both images.
+        checkElement(entry, pathname, entry.identifier, 'image_id', beforeRender, null);
+        checkNaturalSize(entry, 100, 100);
+        if (entry.identifier === 'my_image') {
+          ++numEntries;
+          loadTime1 = entry.loadTime;
+          renderTime1 = entry.renderTime;
+          assert_equals(entry.element, img);
+
+          img2 = document.createElement('img');
+          img2.src = 'resources/square100.png';
+          img2.setAttribute('elementtiming', 'my_image2');
+          img2.setAttribute('id', 'image_id');
+          document.body.appendChild(img2);
+          beforeRender = performance.now();
+        }
+        else if (entry.identifier === 'my_image2') {
+          ++numEntries;
+          loadTime2 = entry.loadTime;
+          renderTime2 = entry.renderTime;
+          assert_equals(entry.element, img2);
+        }
         if (numEntries == 2) {
-          assert_equals(responseEnd1, responseEnd2);
+          assert_greater_than(loadTime2, loadTime1, 'Second image loads after first.');
+          assert_greater_than(renderTime2, renderTime1, 'Second image renders after first');
           t.done();
         }
       })
@@ -57,16 +69,9 @@
       img.setAttribute('elementtiming', 'my_image');
       img.setAttribute('id', 'image_id');
       document.body.appendChild(img);
-
-      img2 = document.createElement('img');
-      img2.src = 'resources/square100.png';
-      img2.setAttribute('elementtiming', 'my_image2');
-      img2.setAttribute('id', 'image_id');
-      document.body.appendChild(img2);
-
       beforeRender = performance.now();
     };
-  }, 'Element with elementtiming attribute is observable.');
+  }, 'Elements with elementtiming and same src are observable.');
 </script>
 
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js b/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js
index f98f9b2..8933732 100644
--- a/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js
+++ b/third_party/blink/web_tests/external/wpt/element-timing/resources/element-timing-helpers.js
@@ -22,7 +22,8 @@
   assert_equals(entry.name, 'image-paint');
   const rt_entries = performance.getEntriesByName(expectedUrl, 'resource');
   assert_equals(rt_entries.length, 1);
-  assert_equals(rt_entries[0].responseEnd, entry.responseEnd);
+  assert_greater_than_equal(entry.loadTime, rt_entries[0].responseEnd,
+    'Image loadTime is after the resource responseEnd');
 }
 
 function checkElementWithoutResourceTiming(entry, expectedUrl, expectedIdentifier,
@@ -30,8 +31,8 @@
   checkElementInternal(entry, expectedUrl, expectedIdentifier, expectedID, beforeRender,
       expectedElement);
   assert_equals(entry.name, 'image-paint');
-  // No associated resource from ResourceTiming, so the responseEnd should be 0.
-  assert_equals(entry.responseEnd, 0);
+  // No associated resource from ResourceTiming, so not much to compare loadTime with.
+  assert_greater_than(entry.loadTime, 0);
 }
 
 // Checks that the rect matches the desired values [left right top bottom].
@@ -57,5 +58,5 @@
   checkElementInternal(entry, '', expectedIdentifier, expectedID, beforeRender,
       expectedElement);
   assert_equals(entry.name, 'text-paint');
-  assert_equals(entry.responseEnd, 0);
+  assert_equals(entry.loadTime, 0);
 }
diff --git a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https.html b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https.html
index 47254a4..6ca7721 100644
--- a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https.html
+++ b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https.html
@@ -8,6 +8,7 @@
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script src=/common/subset-tests-by-key.js></script>
+<script src=/common/get-host-info.sub.js></script>
 <script src=/resources/WebIDLParser.js></script>
 <script src=/resources/idlharness.js></script>
 
@@ -200,7 +201,8 @@
       PeerConnection: [],
       MediaStreamEvent: [],
       ErrorEvent: [],
-      WebSocket: ['new WebSocket("wss://foo")'],
+      // https://web-platform-tests.org/writing-tests/server-features.html?tests-involving-multiple-origins
+      WebSocket: ['new WebSocket("wss://nonexistent.' + get_host_info().ORIGINAL_HOST + '")'],
       CloseEvent: ['new CloseEvent("close")'],
       AbstractWorker: [],
       Worker: [],
diff --git "a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt" "b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt"
index efc72ac6..6f2011e1 100644
--- "a/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt"
+++ "b/third_party/blink/web_tests/external/wpt/html/dom/interfaces.https_exclude=\050Document_Window_HTML._\051-expected.txt"
@@ -1031,32 +1031,32 @@
 PASS WebSocket interface: operation send(Blob)
 PASS WebSocket interface: operation send(ArrayBuffer)
 PASS WebSocket interface: operation send(ArrayBufferView)
-PASS WebSocket must be primary interface of new WebSocket("wss://foo")
-PASS Stringification of new WebSocket("wss://foo")
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "url" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "CONNECTING" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "OPEN" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "CLOSING" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "CLOSED" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "readyState" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "bufferedAmount" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "onopen" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "onerror" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "onclose" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "extensions" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "protocol" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "close(unsigned short, USVString)" with the proper type
-PASS WebSocket interface: calling close(unsigned short, USVString) on new WebSocket("wss://foo") with too few arguments must throw TypeError
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "onmessage" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "binaryType" with the proper type
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "send(USVString)" with the proper type
-PASS WebSocket interface: calling send(USVString) on new WebSocket("wss://foo") with too few arguments must throw TypeError
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "send(Blob)" with the proper type
-PASS WebSocket interface: calling send(Blob) on new WebSocket("wss://foo") with too few arguments must throw TypeError
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "send(ArrayBuffer)" with the proper type
-PASS WebSocket interface: calling send(ArrayBuffer) on new WebSocket("wss://foo") with too few arguments must throw TypeError
-PASS WebSocket interface: new WebSocket("wss://foo") must inherit property "send(ArrayBufferView)" with the proper type
-PASS WebSocket interface: calling send(ArrayBufferView) on new WebSocket("wss://foo") with too few arguments must throw TypeError
+PASS WebSocket must be primary interface of new WebSocket("wss://nonexistent.web-platform.test")
+PASS Stringification of new WebSocket("wss://nonexistent.web-platform.test")
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "url" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "CONNECTING" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "OPEN" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "CLOSING" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "CLOSED" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "readyState" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "bufferedAmount" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "onopen" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "onerror" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "onclose" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "extensions" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "protocol" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "close(unsigned short, USVString)" with the proper type
+PASS WebSocket interface: calling close(unsigned short, USVString) on new WebSocket("wss://nonexistent.web-platform.test") with too few arguments must throw TypeError
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "onmessage" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "binaryType" with the proper type
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "send(USVString)" with the proper type
+PASS WebSocket interface: calling send(USVString) on new WebSocket("wss://nonexistent.web-platform.test") with too few arguments must throw TypeError
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "send(Blob)" with the proper type
+PASS WebSocket interface: calling send(Blob) on new WebSocket("wss://nonexistent.web-platform.test") with too few arguments must throw TypeError
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "send(ArrayBuffer)" with the proper type
+PASS WebSocket interface: calling send(ArrayBuffer) on new WebSocket("wss://nonexistent.web-platform.test") with too few arguments must throw TypeError
+PASS WebSocket interface: new WebSocket("wss://nonexistent.web-platform.test") must inherit property "send(ArrayBufferView)" with the proper type
+PASS WebSocket interface: calling send(ArrayBufferView) on new WebSocket("wss://nonexistent.web-platform.test") with too few arguments must throw TypeError
 PASS CloseEvent interface: existence and properties of interface object
 PASS CloseEvent interface object length
 PASS CloseEvent interface object name
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/css-properties-values-api.idl b/third_party/blink/web_tests/external/wpt/interfaces/css-properties-values-api.idl
index 4ba38970e..ee444eb 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/css-properties-values-api.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/css-properties-values-api.idl
@@ -3,7 +3,7 @@
 // (https://github.com/tidoust/reffy-reports)
 // Source: CSS Properties and Values API Level 1 (https://drafts.css-houdini.org/css-properties-values-api-1/)
 
-dictionary PropertyDescriptor {
+dictionary PropertyDefinition {
   required DOMString name;
            DOMString syntax       = "*";
   required boolean   inherits;
@@ -11,5 +11,17 @@
 };
 
 partial namespace CSS {
-  void registerProperty(PropertyDescriptor descriptor);
+  void registerProperty(PropertyDefinition definition);
+};
+
+partial interface CSSRule {
+    const unsigned short PROPERTY_RULE = 18;
+};
+
+[Exposed=Window]
+interface CSSPropertyRule : CSSRule {
+    readonly attribute CSSOMString name;
+    readonly attribute CSSOMString syntax;
+    readonly attribute boolean inherits;
+    readonly attribute CSSOMString? initialValue;
 };
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/geometry.idl b/third_party/blink/web_tests/external/wpt/interfaces/geometry.idl
index 5d5fe4fc2..1b83959 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/geometry.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/geometry.idl
@@ -15,7 +15,7 @@
     readonly attribute unrestricted double z;
     readonly attribute unrestricted double w;
 
-    DOMPoint matrixTransform(optional DOMMatrixInit matrix);
+    [NewObject] DOMPoint matrixTransform(optional DOMMatrixInit matrix);
 
     [Default] object toJSON();
 };
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/cross-origin-image.sub.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/cross-origin-image.sub.html
index 6e86f13..88775b8 100644
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/cross-origin-image.sub.html
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/cross-origin-image.sub.html
@@ -9,6 +9,7 @@
     if (!window.LargestContentfulPaint) {
       assert_unreached("LargestContentfulPaint is not implemented");
     }
+    const beforeLoad = performance.now();
     const observer = new PerformanceObserver(
       t.step_func_done(function(entryList) {
         assert_equals(entryList.getEntries().length, 1);
@@ -22,8 +23,8 @@
         assert_equals(entry.id, 'image_id');
         const pathname = 'http://{{domains[www]}}:{{ports[http][1]}}/images/blue.png';
         assert_equals(entry.url, pathname);
-        assert_equals(entry.responseEnd,
-            performance.getEntriesByName(pathname, 'resource')[0].responseEnd);
+        assert_greater_than_equal(entry.loadTime, beforeLoad);
+        assert_less_than(entry.loadTime, performance.now());
         assert_equals(entry.element, document.getElementById('image_id'));
       })
     );
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/loadTime-after-appendChild.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/loadTime-after-appendChild.html
new file mode 100644
index 0000000..fb0eddb
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/loadTime-after-appendChild.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Largest Contentful Paint: delayed appended image.</title>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  async_test(function (t) {
+    if (!window.LargestContentfulPaint) {
+      assert_unreached("LargestContentfulPaint is not implemented");
+    }
+    let beforeLoad;
+    const observer = new PerformanceObserver(
+      t.step_func_done(entryList => {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        assert_equals(entry.entryType, 'largest-contentful-paint');
+        assert_equals(entry.startTime, 0);
+        assert_equals(entry.duration, 0);
+        assert_equals(entry.url, window.location.origin + '/images/black-rectangle.png');
+        assert_greater_than(entry.renderTime, entry.loadTime,
+          'The image render time should occur after it is appended to the div.');
+        assert_greater_than(entry.loadTime, beforeLoad,
+          'The image load timestamp should occur after script starts running.');
+        assert_less_than(entry.renderTime, performance.now(),
+          'Image render time should be before the observer callback is executed.')
+      })
+    );
+    observer.observe({type: 'largest-contentful-paint', buffered: true});
+    const img = document.createElement('img');
+    img.src = '/images/black-rectangle.png';
+    t.step_timeout(() => {
+      beforeLoad = performance.now();
+      document.getElementById('image_div').appendChild(img);
+    }, 200)
+  }, 'Image loadTime occurs after appendChild is called.');
+</script>
+<div id='image_div'></div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-image.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-image.html
index 4d56cc2..16b3502 100644
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-image.html
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-image.html
@@ -9,7 +9,7 @@
     if (!window.LargestContentfulPaint) {
       assert_unreached("LargestContentfulPaint is not implemented");
     }
-    let beforeRender = performance.now();
+    const beforeRender = performance.now();
     const observer = new PerformanceObserver(
       t.step_func_done(function(entryList) {
         assert_equals(entryList.getEntries().length, 1);
@@ -28,8 +28,10 @@
         const index = window.location.href.lastIndexOf('/') - 25;
         const pathname = window.location.href.substring(0, index) + '/images/blue.png';
         assert_equals(entry.url, pathname);
-        assert_equals(entry.responseEnd,
-            performance.getEntriesByName(pathname, 'resource')[0].responseEnd);
+        assert_greater_than(entry.loadTime, beforeRender,
+          'The load timestamp should occur after script starts running.');
+        assert_less_than(entry.loadTime, entry.renderTime,
+          'The load timestamp should occur before the render timestamp.')
         assert_equals(entry.element, document.getElementById('image_id'));
       })
     );
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-text.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-text.html
index 7dbfbe52..2cf1344f 100644
--- a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-text.html
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/observe-text.html
@@ -10,11 +10,11 @@
 }
 </style>
 <script>
-  let beforeRender;
   async_test(function (t) {
     if (!window.LargestContentfulPaint) {
       assert_unreached("LargestContentfulPaint is not implemented");
     }
+    let beforeRender;
     const observer = new PerformanceObserver(
       t.step_func_done(function(entryList) {
         assert_equals(entryList.getEntries().length, 1);
@@ -28,7 +28,7 @@
         // Width of at least 100 px.
         // TODO: find a good way to bound text width.
         assert_greater_than_equal(entry.size, 1200);
-        assert_equals(entry.responseEnd, 0);
+        assert_equals(entry.loadTime, 0);
         assert_equals(entry.id, 'my_text');
         assert_equals(entry.url, '');
         assert_equals(entry.element, document.getElementById('my_text'));
diff --git a/third_party/blink/web_tests/external/wpt/largest-contentful-paint/repeated-image.html b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/repeated-image.html
new file mode 100644
index 0000000..94406b20
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/largest-contentful-paint/repeated-image.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<meta charset=utf-8>
+<title>Largest Contentful Paint: repeated image.</title>
+<style>
+  #image_id {
+    width: 10px;
+    height: 10px;
+  }
+</style>
+<body>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  async_test(function (t) {
+    if (!window.LargestContentfulPaint) {
+      assert_unreached("LargestContentfulPaint is not implemented");
+    }
+    const beforeFirstLoad = performance.now();
+    let firstCallback = true;
+    const path = window.location.origin + '/images/black-rectangle.png';
+    let beforeSecondLoad;
+    const observer = new PerformanceObserver(
+      t.step_func(entryList => {
+        assert_equals(entryList.getEntries().length, 1);
+        const entry = entryList.getEntries()[0];
+        assert_equals(entry.entryType, 'largest-contentful-paint');
+        assert_equals(entry.startTime, 0);
+        assert_equals(entry.duration, 0);
+        assert_equals(entry.url, path);
+        assert_less_than(entry.renderTime, performance.now(),
+          'Image render time should be before the observer callback is executed.')
+        if (firstCallback) {
+          assert_equals(entry.id, 'image_id');
+          assert_greater_than(entry.renderTime, entry.loadTime,
+            'The first image render time should occur after its load time.');
+          assert_greater_than(entry.loadTime, beforeFirstLoad,
+            'The first image load timestamp should occur after script starts running.');
+          // Image is shrunk to be 10 x 10.
+          assert_equals(entry.size, 100);
+          const img = document.createElement('img');
+          img.src = '/images/black-rectangle.png';
+          beforeSecondLoad = performance.now();
+          document.getElementById('image_div').appendChild(img);
+          firstCallback = false;
+          return;
+        }
+        // The second image is added at its natural size: 100 x 50.
+        assert_equals(entry.size, 5000);
+        assert_greater_than(entry.loadTime, beforeSecondLoad,
+          'The second image load time should occur after adding it to the document body.');
+        assert_greater_than(entry.renderTime, entry.loadTime,
+          'The second image render time should occur after its load time.');
+        t.done();
+      })
+    );
+    observer.observe({type: 'largest-contentful-paint', buffered: true});
+  }, 'Repeated image produces different timestamps.');
+</script>
+<img src='/images/black-rectangle.png' id='image_id'/>
+<div id='image_div'></div>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/loading/lazyload/picture-loading-lazy.tentative.html b/third_party/blink/web_tests/external/wpt/loading/lazyload/picture-loading-lazy.tentative.html
new file mode 100644
index 0000000..58f8c3a4
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/loading/lazyload/picture-loading-lazy.tentative.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<head>
+  <title>Images with loading='lazy' in picture elements load when near the viewport</title>
+  <link rel="author" title="Raj T" href="mailto:rajendrant@chromium.org">
+  <link rel="help" href="https://github.com/scott-little/lazyload">
+  <script src="/resources/testharness.js"></script>
+  <script src="/resources/testharnessreport.js"></script>
+  <script src="common.js"></script>
+</head>
+
+<!--
+Marked as tentative until https://github.com/whatwg/html/pull/3752 is landed.
+-->
+
+<script>
+const in_viewport_img = new ElementLoadPromise("in_viewport_img");
+const lazy_attribute_img = new ElementLoadPromise("lazy_attribute_img");
+const eager_attribute_img = new ElementLoadPromise("eager_attribute_img");
+
+const document_load_promise = new Promise(resolve => {
+  window.addEventListener("load", resolve);
+});
+
+async_test(function(t) {
+  document_load_promise.then(t.step_func_done(function() {
+    assert_false(lazy_attribute_img.element().complete);
+    lazy_attribute_img.element().scrollIntoView();
+  }));
+}, "Test that the loading=lazy <picture> element below viewport was deferred, on document load.");
+
+async_test(function(t) {
+  in_viewport_img.promise.then(t.step_func_done());
+}, "Test that in viewport <picture> element was loaded");
+
+async_test(function(t) {
+  eager_attribute_img.promise.then(t.step_func_done());
+}, "Test that eager <picture> element was loaded");
+
+async_test(function(t) {
+  lazy_attribute_img.promise.then(t.step_func_done());
+}, "Test that deferred <picture> element was loaded-in as well, after scrolled down");
+
+</script>
+
+<body>
+<picture>
+  <source sizes='50vw' srcset='resources/image.png?in_viewport_img'>
+  <img id='in_viewport_img' src='img-not-loaded.png' loading="lazy" onload="in_viewport_img.resolve();">
+</picture>
+<div style="height:10000px;"></div>
+<picture>
+  <source sizes='50vw' srcset='resources/image.png?lazy_attribute_img'>
+  <img id='lazy_attribute_img' src='img-not-loaded.png' loading="lazy" onload="lazy_attribute_img.resolve();">
+</picture>
+<picture>
+  <source sizes='50vw' srcset='resources/image.png?eager_attribute_img'>
+  <img id='eager_attribute_img' src='img-not-loaded.png' loading="eager" onload="eager_attribute_img.resolve();">
+</picture>
+
+<!--
+  This async script loads very slowly in order to ensure that, if the
+  below_viewport image has started loading, it has a chance to finish
+  loading before window.load() happens, so that the test will dependably fail
+  in that case instead of potentially passing depending on how long different
+  resource fetches take.
+-->
+<script async src="/common/slow.py"></script>
+</body>
diff --git a/third_party/blink/web_tests/external/wpt/std-toast/attributes.html b/third_party/blink/web_tests/external/wpt/std-toast/attributes.html
index 97bab58..6b6aca624 100644
--- a/third_party/blink/web_tests/external/wpt/std-toast/attributes.html
+++ b/third_party/blink/web_tests/external/wpt/std-toast/attributes.html
@@ -115,7 +115,48 @@
 }, 'toggling open attribute does not start timeout');
 
 testToastElement((toast) => {
-    const permitted_properties = ['constructor', 'show', 'hide', 'toggle', 'open', 'action', 'closeButton'];
+    const permitted_properties = ['constructor', 'show', 'hide', 'toggle', 'open', 'action', 'closeButton', 'type'];
     assert_array_equals(permitted_properties.sort(), Object.getOwnPropertyNames(toast.__proto__).sort());
 }, 'toast only exposes certain properties');
+
+testToastElement((toast) => {
+    assert_false(toast.hasAttribute('type'));
+    assert_equals(toast.type, '');
+}, 'default type is empty string without attribute present');
+
+testToastElement((toast) => {
+    toast.type = 'warning';
+    assert_equals(toast.getAttribute('type'), 'warning');
+    assert_equals(toast.type, 'warning');
+}, 'setting type property to an enumerated value changes the type attribute to that value');
+
+testToastElement((toast) => {
+    toast.type = 'WaRnInG';
+    assert_equals(toast.getAttribute('type'), 'WaRnInG');
+    assert_equals(toast.type, 'warning');
+}, 'setting type property to an enumerated value is case-insensitive');
+
+testToastElement((toast) => {
+    toast.type = '  WaRnInG ';
+    assert_equals(toast.getAttribute('type'), '  WaRnInG ');
+    assert_equals(toast.type, '');
+}, 'setting type property to an enumerated value with whitespace does not work');
+
+testToastElement((toast) => {
+    toast.type = 'test';
+    assert_equals(toast.type, '');
+    assert_equals(toast.getAttribute('type'), 'test');
+}, 'setting type to a non-enumerated value sets the type property to empty string');
+
+testToastElement((toast) => {
+    toast.setAttribute('type', 'test');
+    assert_equals(toast.type, '');
+    assert_equals(toast.getAttribute('type'), 'test');
+}, 'setting type attribute to a non-enumerated value sets the type property to empty string');
+
+testToastElement((toast) => {
+    toast.type = 'info';
+    assert_equals(toast.type, '');
+    assert_equals(toast.getAttribute('type'), 'info');
+}, 'info was briefly a valid type, but no longer is, so it will return empty string');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/std-toast/styles.html b/third_party/blink/web_tests/external/wpt/std-toast/styles.html
index 98e6723..1db8620 100644
--- a/third_party/blink/web_tests/external/wpt/std-toast/styles.html
+++ b/third_party/blink/web_tests/external/wpt/std-toast/styles.html
@@ -61,4 +61,40 @@
 
     assertComputedStyleMapsEqual(toast, mockToast);
 }, 'the computed style map of a closed unstyled toast is the same as a span given toast defaults');
-</script>
\ No newline at end of file
+
+testToastElement((toast) => {
+  toast.type = 'error';
+
+  const styles = window.getComputedStyle(toast);
+  assert_equals(styles.borderColor, 'rgb(255, 0, 0)');
+}, 'changing type to error changes the border color to red');
+
+testToastElement((toast) => {
+  toast.type = 'warning';
+
+  const styles = window.getComputedStyle(toast);
+  assert_equals(styles.borderColor, 'rgb(255, 165, 0)');
+}, 'changing type to warning changes the border color to orange');
+
+testToastElement((toast) => {
+  toast.type = 'success';
+
+  const styles = window.getComputedStyle(toast);
+  assert_equals(styles.borderColor, 'rgb(0, 128, 0)');
+}, 'changing type to success changes the border color to green');
+
+testToastElement((toast) => {
+  const styler = document.createElement('style');
+  styler.append(`
+    [type=error i] {
+      border-color: pink;
+    }
+  `);
+  document.querySelector('main').appendChild(styler);
+
+  toast.type = 'error';
+
+  const styles = window.getComputedStyle(toast);
+  assert_equals(styles.borderColor, 'rgb(255, 192, 203)');
+}, 'outside styles can set type styles');
+</script>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/background-services/background-services-events-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/background-services/background-services-events-expected.txt
index 304db523..698d7a5e 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/background-services/background-services-events-expected.txt
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/background-services/background-services-events-expected.txt
@@ -244,7 +244,7 @@
                 value : 300000
             }
         ]
-        eventName : Sync event failed
+        eventName : sync event failed
         instanceId : background-sync-reject
         origin : http://127.0.0.1:8000/
         service : backgroundSync
diff --git a/third_party/blink/web_tests/scrollingcoordinator/donot-compute-non-fast-scrollable-region-for-hidden-frames.html b/third_party/blink/web_tests/scrollingcoordinator/donot-compute-non-fast-scrollable-region-for-hidden-frames.html
index 8e62385b..e1cc76d 100644
--- a/third_party/blink/web_tests/scrollingcoordinator/donot-compute-non-fast-scrollable-region-for-hidden-frames.html
+++ b/third_party/blink/web_tests/scrollingcoordinator/donot-compute-non-fast-scrollable-region-for-hidden-frames.html
@@ -22,15 +22,21 @@
   async_test((t) => {
     var iframeWindow = document.querySelector("iframe").contentWindow;
     iframeWindow.addEventListener("load", () => {
-      nonFastScrollableRects = internals.nonFastScrollableRects(document);
-      assert_equals(nonFastScrollableRects.length, 3);
-
-      var iframeElement = document.querySelector("iframe");
-      iframeElement.style.visibility = 'hidden';
+      t.step(function() {
+        nonFastScrollableRects = internals.nonFastScrollableRects(document);
+        assert_equals(nonFastScrollableRects.length, 3);
+        assert_equals(rectToString(nonFastScrollableRects[0]), '[51, 102, 200, 200]');
+        assert_equals(rectToString(nonFastScrollableRects[1]), '[51, 402, 211, 211]');
+        assert_equals(rectToString(nonFastScrollableRects[2]), '[51, 702, 222, 222]');
+        var iframeElement = document.querySelector("iframe");
+        iframeElement.style.visibility = 'hidden';
+      });
 
       runAfterLayoutAndPaint(() => {
-        nonFastScrollableRects = internals.nonFastScrollableRects(document);
-        assert_equals(nonFastScrollableRects.length, 0);
+        t.step(function() {
+          nonFastScrollableRects = internals.nonFastScrollableRects(document);
+          assert_equals(nonFastScrollableRects.length, 0);
+        });
         t.done();
       });
     });
diff --git a/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler-expected.txt b/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler-expected.txt
index 6d70833b..ff20003 100644
--- a/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler-expected.txt
+++ b/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler-expected.txt
@@ -2,8 +2,10 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS internals.nonFastScrollableRects(document).length is 1
+PASS nonFastScrollableRects.length is 1
+PASS rectToString(nonFastScrollableRects[0]) is "[8, 8, 300, 150]"
 PASS successfullyParsed is true
 
 TEST COMPLETE
 
+
diff --git a/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler.html b/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler.html
index b6d74933..cd8cf2e 100644
--- a/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler.html
+++ b/third_party/blink/web_tests/scrollingcoordinator/plugin-with-wheel-handler.html
@@ -1,18 +1,25 @@
 <!DOCTYPE html>
 <script src="../resources/js-test.js"></script>
 <script src="../resources/run-after-layout-and-paint.js"></script>
+<script src="resources/non-fast-scrollable-region-testing.js"></script>
 
 <script>
+// Perform the test without any description text (with the plugin at the top
+// left) so the non-fast rect is not affected by platform-specific differences
+// in text height.
+setPrintTestResultsLazily();
 description('This test ensures that a plugin which wants to receive wheel ' +
             'events is included in the non-fast scrollable region.');
 window.jsTestIsAsync = true;
 
 onload = function() {
   runAfterLayoutAndPaint(function() {
+    nonFastScrollableRects = internals.nonFastScrollableRects(document);
     if (window.internals) {
-        shouldBe('internals.nonFastScrollableRects(document).length', '1');
+      shouldBe('nonFastScrollableRects.length', '1');
+      shouldBeEqualToString('rectToString(nonFastScrollableRects[0])', '[8, 8, 300, 150]');
     } else {
-        debug('This test requires access to internals.nonFastScrollableRects.');
+      debug('This test requires access to internals.nonFastScrollableRects.');
     }
 
     finishJSTest();
diff --git a/third_party/blink/web_tests/virtual/at-property/external/wpt/css/css-properties-values-api/idlharness-expected.txt b/third_party/blink/web_tests/virtual/at-property/external/wpt/css/css-properties-values-api/idlharness-expected.txt
new file mode 100644
index 0000000..97f17b0
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/at-property/external/wpt/css/css-properties-values-api/idlharness-expected.txt
@@ -0,0 +1,20 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS Partial namespace CSS: original namespace defined
+PASS Partial interface CSSRule: original interface defined
+PASS CSSPropertyRule interface: existence and properties of interface object
+PASS CSSPropertyRule interface object length
+PASS CSSPropertyRule interface object name
+PASS CSSPropertyRule interface: existence and properties of interface prototype object
+PASS CSSPropertyRule interface: existence and properties of interface prototype object's "constructor" property
+PASS CSSPropertyRule interface: existence and properties of interface prototype object's @@unscopables property
+FAIL CSSPropertyRule interface: attribute name assert_true: The prototype object must have a property "name" expected true got false
+FAIL CSSPropertyRule interface: attribute syntax assert_true: The prototype object must have a property "syntax" expected true got false
+FAIL CSSPropertyRule interface: attribute inherits assert_true: The prototype object must have a property "inherits" expected true got false
+FAIL CSSPropertyRule interface: attribute initialValue assert_true: The prototype object must have a property "initialValue" expected true got false
+FAIL CSSRule interface: constant PROPERTY_RULE on interface object assert_equals: property has wrong value expected 18 but got 1001
+FAIL CSSRule interface: constant PROPERTY_RULE on interface prototype object assert_equals: property has wrong value expected 18 but got 1001
+PASS CSS namespace: operation escape(CSSOMString)
+PASS CSS namespace: operation registerProperty(PropertyDefinition)
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/virtual/cascade/external/wpt/css/css-properties-values-api/idlharness-expected.txt b/third_party/blink/web_tests/virtual/cascade/external/wpt/css/css-properties-values-api/idlharness-expected.txt
new file mode 100644
index 0000000..a1c52115
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/cascade/external/wpt/css/css-properties-values-api/idlharness-expected.txt
@@ -0,0 +1,20 @@
+This is a testharness.js-based test.
+PASS idl_test setup
+PASS Partial namespace CSS: original namespace defined
+PASS Partial interface CSSRule: original interface defined
+FAIL CSSPropertyRule interface: existence and properties of interface object assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface object length assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface object name assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: existence and properties of interface prototype object assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute name assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute syntax assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute inherits assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSPropertyRule interface: attribute initialValue assert_own_property: self does not have own property "CSSPropertyRule" expected property "CSSPropertyRule" missing
+FAIL CSSRule interface: constant PROPERTY_RULE on interface object assert_own_property: expected property "PROPERTY_RULE" missing
+FAIL CSSRule interface: constant PROPERTY_RULE on interface prototype object assert_own_property: expected property "PROPERTY_RULE" missing
+PASS CSS namespace: operation escape(CSSOMString)
+PASS CSS namespace: operation registerProperty(PropertyDefinition)
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index c38208eb8..fcb21cc 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -4421,8 +4421,8 @@
     attribute @@toStringTag
     getter element
     getter id
+    getter loadTime
     getter renderTime
-    getter responseEnd
     getter size
     getter url
     method constructor
@@ -5439,10 +5439,10 @@
     getter id
     getter identifier
     getter intersectionRect
+    getter loadTime
     getter naturalHeight
     getter naturalWidth
     getter renderTime
-    getter responseEnd
     getter url
     method constructor
     method toJSON
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index 1d1c2e1..a5120181 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -1309,11 +1309,6 @@
       self.WriteFailureAndRaise('No command line for %s found (test type %s).'
                                 % (target, test_type), output_path=None)
 
-    if is_win and asan:
-      # Sandbox is not yet supported by ASAN for Windows.
-      # Perhaps this is only needed for tests that use the sandbox?
-      cmdline.append('--no-sandbox')
-
     cmdline += isolate_map[target].get('args', [])
 
     return cmdline, extra_files
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 69b065c..be0f71d3 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -860,7 +860,7 @@
       'win7-rel': 'gpu_tests_release_trybot_x86_resource_whitelisting',
       'win_x64_archive': 'release_trybot',
       'win_archive': 'release_trybot_x86',
-      'win_chromium_compile_dbg_ng': 'gpu_tests_debug_trybot_x86',
+      'win_chromium_compile_dbg_ng': 'gpu_tests_debug_trybot_x86_compile_only',
       'win_chromium_compile_rel_ng': 'gpu_tests_release_trybot_x86_resource_whitelisting',
       'win_chromium_dbg_ng': 'gpu_tests_debug_trybot_x86',
       'win_chromium_x64_rel_ng': 'gpu_tests_release_trybot',
@@ -1551,8 +1551,8 @@
       'gpu_tests', 'debug_trybot', 'x86',
     ],
 
-    'gpu_tests_debug_trybot_x86': [
-      'gpu_tests', 'debug_trybot', 'x86',
+    'gpu_tests_debug_trybot_x86_compile_only': [
+      'gpu_tests', 'debug_trybot', 'x86', 'compile_only',
     ],
 
     'gpu_tests_release_bot_minimal_symbols': [
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index dc95f3c..498dec7 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -9101,6 +9101,9 @@
   <int value="14" label="Animation has an unsupported CSS property"/>
   <int value="15"
       label="There are multiple transform animations on the target element"/>
+  <int value="16"
+      label="Custom property contains different value types in keyframe
+             animation"/>
 </enum>
 
 <enum name="CompositorFrameSinkSubmitResult">
@@ -12994,6 +12997,7 @@
   <int value="4" label="Push Messaging"/>
   <int value="5" label="Notifications"/>
   <int value="6" label="Payment Handler"/>
+  <int value="7" label="Periodic Background Sync"/>
 </enum>
 
 <enum name="DevToolsPanel">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 17be21c..6a3f6584 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -10161,10 +10161,9 @@
 </histogram>
 
 <histogram name="Autofill.ManageCardsPrompt" enum="AutofillManageCardsPrompt"
-    expires_after="2019-09-01">
+    expires_after="2020-09-01">
   <owner>manasverma@google.com</owner>
   <owner>jsaul@google.com</owner>
-  <owner>sebsg@chromium.org</owner>
   <summary>
     The frequency of user interactions with the Manage Cards prompt.
   </summary>
@@ -17082,6 +17081,9 @@
 
 <histogram name="CachedImageFetcher.ImageLoadFromCacheTime" units="ms"
     expires_after="2019-12-01">
+  <obsolete>
+    Renamed to ImageFetcher.ImageLoadFromCacheTime on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -17092,6 +17094,9 @@
 
 <histogram name="CachedImageFetcher.ImageLoadFromCacheTimeJava" units="ms"
     expires_after="2019-12-01">
+  <obsolete>
+    Renamed to ImageFetcher.ImageLoadFromCacheTimeJava on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -17102,6 +17107,9 @@
 
 <histogram name="CachedImageFetcher.ImageLoadFromNativeTimeJava" units="ms"
     expires_after="2019-12-01">
+  <obsolete>
+    Renamed to ImageFetcher.ImageLoadFromNativeTimeJava on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -17112,6 +17120,9 @@
 
 <histogram name="CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit"
     units="ms" expires_after="2019-12-01">
+  <obsolete>
+    Renamed to ImageFetcher.ImageLoadFromNetworkAfterCacheHit on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -17122,6 +17133,9 @@
 
 <histogram name="CachedImageFetcher.ImageLoadFromNetworkTime" units="ms"
     expires_after="2019-12-01">
+  <obsolete>
+    Renamed to ImageFetcher.ImageLoadFromNetworkTime on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -17132,6 +17146,9 @@
 
 <histogram name="CachedImageFetcher.LoadImageMetadata" units="ms"
     expires_after="2020-06-30">
+  <obsolete>
+    Renamed to ImageFetcher.LoadImageMetadata on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -17141,6 +17158,9 @@
 
 <histogram name="CachedImageFetcher.TimeSinceLastCacheLRUEviction" units="ms"
     expires_after="2019-12-01">
+  <obsolete>
+    Renamed to ImageFetcher.TimeSinceLastCacheLRUEviction on 06/2019.
+  </obsolete>
   <owner>fgorski@chromium.org</owner>
   <owner>wylieb@chromium.org</owner>
   <summary>
@@ -31055,6 +31075,9 @@
 
 <histogram name="Download.InterruptedAtEndReason" enum="InterruptReason"
     expires_after="M77">
+  <obsolete>
+    Removed in 07/2019.
+  </obsolete>
   <owner>dtrainor@chromium.org</owner>
   <summary>
     The reason that a download was interrupted at the *end* of a download (when
@@ -31072,6 +31095,9 @@
 </histogram>
 
 <histogram name="Download.InterruptedOverrunBytes" expires_after="M77">
+  <obsolete>
+    Removed in 07/2019.
+  </obsolete>
   <owner>dtrainor@chromium.org</owner>
   <summary>
     The total number of bytes minus the received number of bytes at the time
@@ -31090,6 +31116,9 @@
 
 <histogram name="Download.InterruptedReceivedSizeK" units="KB"
     expires_after="M77">
+  <obsolete>
+    Removed in 07/2019.
+  </obsolete>
   <owner>dtrainor@chromium.org</owner>
   <summary>
     The number of kilobytes received for a download at the time it is
@@ -31109,6 +31138,9 @@
 </histogram>
 
 <histogram name="Download.InterruptedUnderrunBytes">
+  <obsolete>
+    Removed in 07/2019.
+  </obsolete>
   <owner>dtrainor@chromium.org</owner>
   <summary>
     The excessive number of bytes which have been received at the time that a
@@ -45656,6 +45688,15 @@
   </summary>
 </histogram>
 
+<histogram name="GCM.SendWebPushMessagePayloadSize" expires_after="2020-02-02">
+  <owner>alexchau@chromium.org</owner>
+  <owner>peter@chromium.org</owner>
+  <summary>
+    Size of web push messages payload. Recorded right before the message is
+    sent.
+  </summary>
+</histogram>
+
 <histogram name="GCM.SendWebPushMessageResult" enum="SendWebPushMessageResult"
     expires_after="2020-02-02">
   <owner>alexchau@chromium.org</owner>
@@ -50062,6 +50103,76 @@
   </summary>
 </histogram>
 
+<histogram name="ImageFetcher.ImageLoadFromCacheTime" units="ms"
+    expires_after="2020-06-06">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time it takes for cached_image_fetcher to load an image from the cache
+    in native.
+  </summary>
+</histogram>
+
+<histogram name="ImageFetcher.ImageLoadFromCacheTimeJava" units="ms"
+    expires_after="2020-06-06">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time it takes for cached_image_fetcher to load an image from the cache
+    in Java.
+  </summary>
+</histogram>
+
+<histogram name="ImageFetcher.ImageLoadFromNativeTimeJava" units="ms"
+    expires_after="2020-06-06">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time it takes for cached_image_fetcher to load an image from native
+    code. Only recorded on successful loads.
+  </summary>
+</histogram>
+
+<histogram name="ImageFetcher.ImageLoadFromNetworkAfterCacheHit" units="ms"
+    expires_after="2020-06-06">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time it takes for cached_image_fetcher to load an image from the network
+    after a cache hit.
+  </summary>
+</histogram>
+
+<histogram name="ImageFetcher.ImageLoadFromNetworkTime" units="ms"
+    expires_after="2020-06-06">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time it takes for cached_image_fetcher to load an image from the
+    network.
+  </summary>
+</histogram>
+
+<histogram name="ImageFetcher.LoadImageMetadata" units="ms"
+    expires_after="2020-06-30">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time it takes to load an image's metadata from the metadata store.
+  </summary>
+</histogram>
+
+<histogram name="ImageFetcher.TimeSinceLastCacheLRUEviction" units="ms"
+    expires_after="2019-12-01">
+  <owner>fgorski@chromium.org</owner>
+  <owner>wylieb@chromium.org</owner>
+  <summary>
+    The time since the last LRU eviction from the image cache. Recorded when two
+    LRU evictions occur within closure proximity to one another. Will be used to
+    determine if LRU eviction is happening too frequently.
+  </summary>
+</histogram>
+
 <histogram name="ImageLoader.Client.Cache.HitMiss" enum="BooleanCacheHit"
     expires_after="2019-01-01">
   <owner>chromeos-files-app@google.com</owner>
@@ -83630,6 +83741,9 @@
 
 <histogram name="NewTabPage.TileFaviconFetchStatus.Server"
     enum="GoogleFaviconServerRequestStatus" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 because the histogram was unused and close to expiration.
+  </obsolete>
   <owner>jkrcal@chromium.org</owner>
   <summary>
     Mobile only. The result of fetching a favicon for a tile on the New Tab
@@ -83640,6 +83754,9 @@
 
 <histogram name="NewTabPage.TileFaviconFetchSuccess.Popular"
     enum="BooleanSuccess" expires_after="M77">
+  <obsolete>
+    Deprecated 07/2019 because the histogram was unused and close to expiration.
+  </obsolete>
   <owner>jkrcal@chromium.org</owner>
   <summary>
     Mobile only. The result of fetching a favicon for a tile on the New Tab
@@ -85824,7 +85941,7 @@
 </histogram>
 
 <histogram name="OfflinePages.AutoFetch.CompleteNotificationAction"
-    enum="OfflinePagesAutoFetchNotificationAction" expires_after="2019-08-30">
+    enum="OfflinePagesAutoFetchNotificationAction" expires_after="2020-03-30">
   <owner>harringtond@google.com</owner>
   <owner>carlosk@chromium.org</owner>
   <summary>
@@ -85834,7 +85951,7 @@
 </histogram>
 
 <histogram name="OfflinePages.AutoFetch.InProgressNotificationAction"
-    enum="OfflinePagesAutoFetchNotificationAction" expires_after="2019-08-30">
+    enum="OfflinePagesAutoFetchNotificationAction" expires_after="2020-03-30">
   <owner>harringtond@google.com</owner>
   <owner>carlosk@chromium.org</owner>
   <summary>
@@ -102358,6 +102475,15 @@
   </summary>
 </histogram>
 
+<histogram name="Power.Mac.IsOnBattery" enum="BooleanOnBattery"
+    expires_after="2020-07-19">
+  <owner>lgrey@chromium.org</owner>
+  <owner>sdy@chromium.org</owner>
+  <summary>
+    Whether the user's machine is on battery power. Sampled once per minute.
+  </summary>
+</histogram>
+
 <histogram name="Power.Mac.ThermalState" enum="MacThermalState"
     expires_after="2020-05-13">
   <owner>lgrey@chromium.org</owner>
@@ -113019,7 +113145,11 @@
 </histogram>
 
 <histogram name="SafeBrowsing.V4GetHash.Parse.Result"
-    enum="SafeBrowsingParseV4HashResult" expires_after="M77">
+    enum="SafeBrowsingParseV4HashResult" expires_after="never">
+<!-- expires-never: This reports the reason for the failure to parse the response
+from Safe Browsing. Keeping track of these errors is critical to ensure that
+Safe Browsing lookup mechanism is working as expected -->
+
   <owner>vakh@chromium.org</owner>
   <owner>kcarattini@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
@@ -115146,8 +115276,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.DownloadFileHasDetachedSignatures"
-    enum="Boolean" expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    enum="Boolean" expires_after="M78">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     A Mac-only metric that records whether a given download contains a detached
@@ -115559,8 +115689,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.ZipFileFailureByType"
-    enum="SBClientDownloadExtensions" expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    enum="SBClientDownloadExtensions" expires_after="M78">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     Counts of ZIP-like file types that failed to be successfully analyzed by the
@@ -115608,8 +115738,8 @@
 </histogram>
 
 <histogram name="SBClientDownload.ZipFileSuccess" enum="BooleanSuccess"
-    expires_after="M77">
-  <owner>vakh@chromium.org</owner>
+    expires_after="M78">
+  <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
     For each zip file analyzed by the SafeBrowsing download service, records if
@@ -145430,7 +145560,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.FailureEventSource"
-    enum="VoiceInteractionEventSource" expires_after="M77">
+    enum="VoiceInteractionEventSource" expires_after="M81">
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
   <summary>
@@ -145479,7 +145609,7 @@
 </histogram>
 
 <histogram name="VoiceInteraction.VoiceResultConfidenceValue" units="%"
-    expires_after="M77">
+    expires_after="M81">
   <owner>tedchoc@chromium.org</owner>
   <owner>yusufo@chromium.org</owner>
   <summary>
@@ -155334,7 +155464,7 @@
   <suffix name="SendBeginMainFrameToCommit"
       label="The time from when the BeginMainFrame is sent to the beginning
              of the commit."/>
-  <suffix name="SubmitCompositorFrameToPresentCompositorFrame"
+  <suffix name="SubmitCompositorFrameToPresentationCompositorFrame"
       label="The time from when the a compositor frame is submitted to the
              display compositor to when it is presented."/>
   <suffix name="TotalLatency"
@@ -158507,19 +158637,20 @@
       label="Showing cache patterns only for AssistantDetails."/>
   <suffix name="ContextualSuggestions"
       label="Showing cache patterns only for ContextualSuggestions."/>
+  <suffix name="EntitySuggestions"
+      label="Showing cache patterns only for EntitySuggestions."/>
   <suffix name="Feed" label="Showing cache patterns only for Feed."/>
   <suffix name="Internal" label="Showing cache patterns only for Internal."/>
   <suffix name="NewTabPageAnimatedLogo"
       label="Showing cache patterns only for NewTabPageAnimatedLogo."/>
   <suffix name="OfflinePages"
       label="Showing cache patterns only for OfflinePages."/>
-  <affected-histogram name="CachedImageFetcher.ImageLoadFromCacheTime"/>
-  <affected-histogram name="CachedImageFetcher.ImageLoadFromCacheTimeJava"/>
-  <affected-histogram name="CachedImageFetcher.ImageLoadFromNativeTimeJava"/>
-  <affected-histogram
-      name="CachedImageFetcher.ImageLoadFromNetworkAfterCacheHit"/>
-  <affected-histogram name="CachedImageFetcher.ImageLoadFromNetworkTime"/>
   <affected-histogram name="ImageFetcher.Events"/>
+  <affected-histogram name="ImageFetcher.ImageLoadFromCacheTime"/>
+  <affected-histogram name="ImageFetcher.ImageLoadFromCacheTimeJava"/>
+  <affected-histogram name="ImageFetcher.ImageLoadFromNativeTimeJava"/>
+  <affected-histogram name="ImageFetcher.ImageLoadFromNetworkAfterCacheHit"/>
+  <affected-histogram name="ImageFetcher.ImageLoadFromNetworkTime"/>
 </histogram_suffixes>
 
 <histogram_suffixes name="IMEAutoCorrect" separator=".">
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index 65b0af4..114de02 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -89,6 +89,13 @@
     with non-zero bytes. Recorded when a page is destroyed or when the app is
     backgrounded on mobile.
   </summary>
+  <metric name="CpuTime.PeakWindowedPercent">
+    <summary>
+      The peak percentage of CPU utilization attributed to the frame in any 30
+      second window prior to user activation. Frames that exist for less than 30
+      seconds still use 30 seconds as the denominator for the calculation.
+    </summary>
+  </metric>
   <metric name="CpuTime.PreActivation">
     <summary>
       Wall time of tasks attributed to the frame after the frame recieved user
diff --git a/tools/perf/core/minidump_unittest.py b/tools/perf/core/minidump_unittest.py
index 23cf7a7..4085a37 100644
--- a/tools/perf/core/minidump_unittest.py
+++ b/tools/perf/core/minidump_unittest.py
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import logging
+import os
 import time
 
 from telemetry.testing import tab_test_case
@@ -16,12 +17,12 @@
   # ChromeOS and Android are currently hard coded to return None for minidump
   # paths, so disable on those platforms. Windows 7 doesn't find any minidump
   # paths for some reason.
-  @decorators.Disabled('chromeos', 'android', 'win7', 'linux')
+  @decorators.Disabled('chromeos', 'android', 'win7')
   def testSymbolizeMinidump(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var sam = "car";', 'sam')
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
-    crash_minidump_path = self._browser.GetMostRecentMinidumpPath()
+    crash_minidump_path = self._browser.GetRecentMinidumpPathWithTimeout()
     self.assertIsNotNone(crash_minidump_path)
 
     if crash_minidump_path is not None:
@@ -91,13 +92,12 @@
   # Disabled on Mac 10.12 (Sierra) due to it not getting a stack trace to
   # symbolize from the second crash.
   # Test is flaky on 10.13 (HighSierra). See https://crbug.com/986644.
-  @decorators.Disabled('chromeos', 'android', 'win7', 'sierra', 'linux',
-                       'highsierra')
+  @decorators.Disabled('chromeos', 'android', 'win7', 'sierra', 'highsierra')
   def testMultipleCrashMinidumps(self):
     # Wait for the browser to restart fully before crashing
     self._LoadPageThenWait('var cat = "dog";', 'cat')
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
-    first_crash_path = self._browser.GetMostRecentMinidumpPath()
+    first_crash_path = self._browser.GetRecentMinidumpPathWithTimeout()
 
     self.assertIsNotNone(first_crash_path)
     if first_crash_path is not None:
@@ -122,7 +122,11 @@
     self._LoadPageThenWait('var foo = "bar";', 'foo')
 
     self._browser.tabs.New().Navigate('chrome://gpucrash', timeout=5)
-    second_crash_path = self._browser.GetMostRecentMinidumpPath()
+    # Make the oldest allowable timestamp slightly after the first dump's
+    # timestamp so we don't get the first one returned to us again
+    oldest_ts = os.path.getmtime(first_crash_path) + 1
+    second_crash_path = self._browser.GetRecentMinidumpPathWithTimeout(
+        oldest_ts=oldest_ts)
     self.assertIsNotNone(second_crash_path)
     if second_crash_path is not None:
       logging.info('testMultipleCrashMinidumps: second crash most recent path'
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index 1739e75..6af53ea 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -14,9 +14,11 @@
 namespace ui {
 namespace {
 
-bool IsLiveRegion(const AXTreeObserver::Change& change) {
+bool IsActiveLiveRegion(const AXTreeObserver::Change& change) {
   return change.node->data().HasStringAttribute(
-      ax::mojom::StringAttribute::kLiveStatus);
+             ax::mojom::StringAttribute::kLiveStatus) &&
+         change.node->data().GetStringAttribute(
+             ax::mojom::StringAttribute::kLiveStatus) != "off";
 }
 
 bool IsContainedInLiveRegion(const AXTreeObserver::Change& change) {
@@ -517,16 +519,12 @@
       continue;
     }
 
-    if (IsLiveRegion(change)) {
-      if (IsAlert(change.node->data().role)) {
-        AddEvent(change.node, Event::ALERT);
-      } else if (change.node->data().GetStringAttribute(
-                     ax::mojom::StringAttribute::kLiveStatus) != "off") {
-        AddEvent(change.node, Event::LIVE_REGION_CREATED);
-      }
-    } else if (IsContainedInLiveRegion(change)) {
+    if (IsAlert(change.node->data().role))
+      AddEvent(change.node, Event::ALERT);
+    else if (IsActiveLiveRegion(change))
+      AddEvent(change.node, Event::LIVE_REGION_CREATED);
+    else if (IsContainedInLiveRegion(change))
       FireLiveRegionEvents(change.node);
-    }
   }
 
   FireActiveDescendantEvents();
diff --git a/ui/accessibility/ax_event_generator_unittest.cc b/ui/accessibility/ax_event_generator_unittest.cc
index d1df0fa8..a676881 100644
--- a/ui/accessibility/ax_event_generator_unittest.cc
+++ b/ui/accessibility/ax_event_generator_unittest.cc
@@ -401,26 +401,36 @@
 
   AXEventGenerator event_generator(&tree);
   AXTreeUpdate update = initial_state;
-  update.nodes.resize(3);
+  update.nodes.resize(4);
   update.nodes[0].child_ids.push_back(2);
   update.nodes[0].child_ids.push_back(3);
+  update.nodes[0].child_ids.push_back(4);
+
   update.nodes[1].id = 2;
   update.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus,
                                      "polite");
+
+  // Blink should automatically add aria-live="assertive" to elements with role
+  // kAlert, but we should fire an alert event regardless.
   update.nodes[2].id = 3;
   update.nodes[2].role = ax::mojom::Role::kAlert;
-  update.nodes[2].AddStringAttribute(ax::mojom::StringAttribute::kLiveStatus,
-                                     "polite");
+
+  // Elements with role kAlertDialog will *not* usually have a live region
+  // status, but again, we should always fire an alert event.
+  update.nodes[3].id = 4;
+  update.nodes[3].role = ax::mojom::Role::kAlertDialog;
 
   ASSERT_TRUE(tree.Unserialize(update));
   EXPECT_THAT(
       event_generator,
       UnorderedElementsAre(
           HasEventAtNode(AXEventGenerator::Event::ALERT, 3),
+          HasEventAtNode(AXEventGenerator::Event::ALERT, 4),
           HasEventAtNode(AXEventGenerator::Event::CHILDREN_CHANGED, 1),
           HasEventAtNode(AXEventGenerator::Event::LIVE_REGION_CREATED, 2),
           HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 2),
-          HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3)));
+          HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 3),
+          HasEventAtNode(AXEventGenerator::Event::SUBTREE_CREATED, 4)));
 }
 
 TEST(AXEventGeneratorTest, LiveRegionChanged) {
diff --git a/ui/compositor/test/in_process_context_factory.cc b/ui/compositor/test/in_process_context_factory.cc
index 0d994c5e..b88ff7b 100644
--- a/ui/compositor/test/in_process_context_factory.cc
+++ b/ui/compositor/test/in_process_context_factory.cc
@@ -16,6 +16,7 @@
 #include "build/build_config.h"
 #include "cc/base/switches.h"
 #include "cc/test/pixel_test_output_surface.h"
+#include "components/viz/common/features.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
 #include "components/viz/common/frame_sinks/delay_based_time_source.h"
 #include "components/viz/common/gpu/context_provider.h"
@@ -25,8 +26,11 @@
 #include "components/viz/service/display/display_scheduler.h"
 #include "components/viz/service/display/output_surface_client.h"
 #include "components/viz/service/display/output_surface_frame.h"
+#include "components/viz/service/display_embedder/skia_output_surface_dependency_impl.h"
+#include "components/viz/service/display_embedder/skia_output_surface_impl.h"
 #include "components/viz/service/frame_sinks/direct_layer_tree_frame_sink.h"
 #include "components/viz/service/frame_sinks/frame_sink_manager_impl.h"
+#include "components/viz/test/test_gpu_service_holder.h"
 #include "gpu/command_buffer/client/context_support.h"
 #include "gpu/command_buffer/client/gles2_interface.h"
 #include "gpu/command_buffer/common/context_creation_attribs.h"
@@ -156,6 +160,14 @@
 InProcessContextFactory::InProcessContextFactory(
     viz::HostFrameSinkManager* host_frame_sink_manager,
     viz::FrameSinkManagerImpl* frame_sink_manager)
+    : InProcessContextFactory(host_frame_sink_manager,
+                              frame_sink_manager,
+                              features::IsUsingSkiaRenderer()) {}
+
+InProcessContextFactory::InProcessContextFactory(
+    viz::HostFrameSinkManager* host_frame_sink_manager,
+    viz::FrameSinkManagerImpl* frame_sink_manager,
+    bool use_skia_renderer)
     : frame_sink_id_allocator_(kDefaultClientId),
       use_test_surface_(true),
       disable_vsync_(base::CommandLine::ForCurrentProcess()->HasSwitch(
@@ -166,6 +178,8 @@
   DCHECK_NE(gl::GetGLImplementation(), gl::kGLImplementationNone)
       << "If running tests, ensure that main() is calling "
       << "gl::GLSurfaceTestSupport::InitializeOneOff()";
+  if (use_skia_renderer)
+    renderer_settings_.use_skia_renderer = true;
 #if defined(OS_MACOSX)
   renderer_settings_.release_overlay_resources_after_gpu_query = true;
   // Ensure that tests don't wait for frames that will never come.
@@ -229,7 +243,14 @@
                                        "UICompositor", support_locking);
 
   std::unique_ptr<viz::OutputSurface> display_output_surface;
-  if (use_test_surface_) {
+
+  if (renderer_settings_.use_skia_renderer) {
+    display_output_surface = viz::SkiaOutputSurfaceImpl::Create(
+        std::make_unique<viz::SkiaOutputSurfaceDependencyImpl>(
+            viz::TestGpuServiceHolder::GetInstance()->gpu_service(),
+            gpu::kNullSurfaceHandle),
+        renderer_settings_);
+  } else if (use_test_surface_) {
     bool flipped_output_surface = false;
     display_output_surface = std::make_unique<cc::PixelTestOutputSurface>(
         context_provider, flipped_output_surface);
diff --git a/ui/compositor/test/in_process_context_factory.h b/ui/compositor/test/in_process_context_factory.h
index 74bcae6..8456536c 100644
--- a/ui/compositor/test/in_process_context_factory.h
+++ b/ui/compositor/test/in_process_context_factory.h
@@ -32,11 +32,15 @@
                                 public ContextFactoryPrivate {
  public:
   // Both |host_frame_sink_manager| and |frame_sink_manager| must outlive the
-  // ContextFactory.
+  // ContextFactory. The constructor without |use_skia_renderer| will use
+  // SkiaRenderer if the feature is enabled.
   // TODO(crbug.com/657959): |frame_sink_manager| should go away and we should
   // use the LayerTreeFrameSink from the HostFrameSinkManager.
   InProcessContextFactory(viz::HostFrameSinkManager* host_frame_sink_manager,
                           viz::FrameSinkManagerImpl* frame_sink_manager);
+  InProcessContextFactory(viz::HostFrameSinkManager* host_frame_sink_manager,
+                          viz::FrameSinkManagerImpl* frame_sink_manager,
+                          bool use_skia_renderer);
   ~InProcessContextFactory() override;
 
   viz::FrameSinkManagerImpl* GetFrameSinkManager() {
diff --git a/ui/display/manager/configure_displays_task.cc b/ui/display/manager/configure_displays_task.cc
index 6ef399a..6db2868 100644
--- a/ui/display/manager/configure_displays_task.cc
+++ b/ui/display/manager/configure_displays_task.cc
@@ -77,15 +77,9 @@
       size_t index = pending_request_indexes_.front();
       DisplayConfigureRequest* request = &requests_[index];
       pending_request_indexes_.pop();
-      // Non-native displays do not require configuration through the
-      // NativeDisplayDelegate.
-      if (!IsPhysicalDisplayType(request->display->type())) {
-        OnConfigured(index, true);
-      } else {
-        delegate_->Configure(*request->display, request->mode, request->origin,
-                             base::Bind(&ConfigureDisplaysTask::OnConfigured,
-                                        weak_ptr_factory_.GetWeakPtr(), index));
-      }
+      delegate_->Configure(*request->display, request->mode, request->origin,
+                           base::Bind(&ConfigureDisplaysTask::OnConfigured,
+                                      weak_ptr_factory_.GetWeakPtr(), index));
     }
   }
 
diff --git a/ui/display/manager/content_protection_manager.cc b/ui/display/manager/content_protection_manager.cc
index 59e8613b..986d961 100644
--- a/ui/display/manager/content_protection_manager.cc
+++ b/ui/display/manager/content_protection_manager.cc
@@ -12,7 +12,6 @@
 #include "base/stl_util.h"
 #include "ui/display/manager/apply_content_protection_task.h"
 #include "ui/display/manager/display_layout_manager.h"
-#include "ui/display/manager/display_util.h"
 #include "ui/display/manager/query_content_protection_task.h"
 #include "ui/display/types/display_constants.h"
 #include "ui/display/types/display_snapshot.h"
@@ -79,10 +78,7 @@
     QueryContentProtectionCallback callback) {
   DCHECK(disabled() || GetContentProtections(client_id));
 
-  // Exclude virtual displays so that protected content will not be recaptured
-  // through the cast stream.
-  const DisplaySnapshot* display = GetDisplay(display_id);
-  if (disabled() || !display || !IsPhysicalDisplayType(display->type())) {
+  if (disabled() || !GetDisplay(display_id)) {
     std::move(callback).Run(/*success=*/false, DISPLAY_CONNECTION_TYPE_NONE,
                             CONTENT_PROTECTION_METHOD_NONE);
     return;
@@ -277,11 +273,6 @@
   for (DisplaySnapshot* display : layout_manager_->GetDisplayStates()) {
     int64_t display_id = display->display_id();
 
-    if (!IsPhysicalDisplayType(display->type())) {
-      NotifyDisplaySecurityObservers(display_id, /*secure=*/false);
-      continue;
-    }
-
     QueueTask(std::make_unique<QueryContentProtectionTask>(
         layout_manager_, native_display_delegate_, display_id,
         base::BindOnce(&ContentProtectionManager::OnDisplaySecurityQueried,
@@ -300,18 +291,12 @@
                         (protection_mask != CONTENT_PROTECTION_METHOD_NONE ||
                          connection_mask == DISPLAY_CONNECTION_TYPE_INTERNAL);
 
-    NotifyDisplaySecurityObservers(display_id, secure);
+    for (Observer& observer : observers_)
+      observer.OnDisplaySecurityChanged(display_id, secure);
   }
 
   if (status != Task::Status::KILLED)
     DequeueTask();
 }
 
-void ContentProtectionManager::NotifyDisplaySecurityObservers(
-    int64_t display_id,
-    bool secure) {
-  for (Observer& observer : observers_)
-    observer.OnDisplaySecurityChanged(display_id, secure);
-}
-
 }  // namespace display
diff --git a/ui/display/manager/content_protection_manager.h b/ui/display/manager/content_protection_manager.h
index 6364e93..0d4af4cb 100644
--- a/ui/display/manager/content_protection_manager.h
+++ b/ui/display/manager/content_protection_manager.h
@@ -158,7 +158,6 @@
                                 Task::Status status,
                                 uint32_t connection_mask,
                                 uint32_t protection_mask);
-  void NotifyDisplaySecurityObservers(int64_t display_id, bool secure);
 
   DisplayLayoutManager* const layout_manager_;                // Not owned.
   NativeDisplayDelegate* native_display_delegate_ = nullptr;  // Not owned.
diff --git a/ui/display/manager/content_protection_manager_unittest.cc b/ui/display/manager/content_protection_manager_unittest.cc
index 0a05e3ce..ee4c0fc 100644
--- a/ui/display/manager/content_protection_manager_unittest.cc
+++ b/ui/display/manager/content_protection_manager_unittest.cc
@@ -18,7 +18,7 @@
 
 namespace {
 
-constexpr int64_t kDisplayIds[] = {123, 456};
+constexpr int64_t kDisplayIds[] = {123, 456, 789};
 const DisplayMode kDisplayMode{gfx::Size(1366, 768), false, 60.0f};
 
 }  // namespace
@@ -67,6 +67,12 @@
                        .SetCurrentMode(kDisplayMode.Clone())
                        .Build();
 
+    displays_[2] = FakeDisplaySnapshot::Builder()
+                       .SetId(kDisplayIds[2])
+                       .SetType(DISPLAY_CONNECTION_TYPE_VGA)
+                       .SetCurrentMode(kDisplayMode.Clone())
+                       .Build();
+
     UpdateDisplays(2);
   }
 
@@ -122,7 +128,7 @@
   uint32_t connection_mask_ = DISPLAY_CONNECTION_TYPE_NONE;
   uint32_t protection_mask_ = CONTENT_PROTECTION_METHOD_NONE;
 
-  std::unique_ptr<DisplaySnapshot> displays_[2];
+  std::unique_ptr<DisplaySnapshot> displays_[3];
 
   DISALLOW_COPY_AND_ASSIGN(ContentProtectionManagerTest);
 };
@@ -674,5 +680,81 @@
             observer.security_changes());
 }
 
+TEST_F(ContentProtectionManagerTest, AnalogDisplaySecurity) {
+  UpdateDisplays(3);
+  TestObserver observer(&manager_);
+
+  EXPECT_EQ(SecurityChanges({{kDisplayIds[0], true},
+                             {kDisplayIds[1], false},
+                             {kDisplayIds[2], false}}),
+            observer.security_changes());
+  observer.Reset();
+
+  auto id = manager_.RegisterClient();
+  EXPECT_TRUE(id);
+
+  native_display_delegate_.set_run_async(true);
+
+  for (int64_t display_id : kDisplayIds) {
+    manager_.ApplyContentProtection(
+        id, display_id, CONTENT_PROTECTION_METHOD_HDCP,
+        base::BindOnce(
+            &ContentProtectionManagerTest::ApplyContentProtectionCallback,
+            base::Unretained(this)));
+  }
+
+  base::RunLoop().RunUntilIdle();
+  EXPECT_TRUE(observer.security_changes().empty());
+
+  EXPECT_TRUE(TriggerDisplaySecurityTimeout());
+  base::RunLoop().RunUntilIdle();
+
+  // Analog display is never secure.
+  EXPECT_EQ(SecurityChanges({{kDisplayIds[0], true},
+                             {kDisplayIds[1], true},
+                             {kDisplayIds[2], false}}),
+            observer.security_changes());
+  observer.Reset();
+
+  layout_manager_.set_display_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
+  TriggerDisplayConfiguration();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(TriggerDisplaySecurityTimeout());
+  base::RunLoop().RunUntilIdle();
+
+  // Internal display is not secure if mirrored to an analog display.
+  EXPECT_EQ(SecurityChanges({{kDisplayIds[0], false},
+                             {kDisplayIds[1], false},
+                             {kDisplayIds[2], false}}),
+            observer.security_changes());
+  observer.Reset();
+
+  layout_manager_.set_display_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
+  TriggerDisplayConfiguration();
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(TriggerDisplaySecurityTimeout());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_EQ(SecurityChanges({{kDisplayIds[0], true},
+                             {kDisplayIds[1], true},
+                             {kDisplayIds[2], false}}),
+            observer.security_changes());
+  observer.Reset();
+
+  manager_.UnregisterClient(id);
+
+  // Timer should be stopped when no client requests protection.
+  EXPECT_FALSE(TriggerDisplaySecurityTimeout());
+  base::RunLoop().RunUntilIdle();
+
+  // Observer should be notified when client unregisters.
+  EXPECT_EQ(SecurityChanges({{kDisplayIds[0], true},
+                             {kDisplayIds[1], false},
+                             {kDisplayIds[2], false}}),
+            observer.security_changes());
+}
+
 }  // namespace test
 }  // namespace display
diff --git a/ui/display/manager/display_util.cc b/ui/display/manager/display_util.cc
index 5f299ae..c8c273c4 100644
--- a/ui/display/manager/display_util.cc
+++ b/ui/display/manager/display_util.cc
@@ -119,10 +119,6 @@
   return "INVALID";
 }
 
-bool IsPhysicalDisplayType(DisplayConnectionType type) {
-  return !(type & DISPLAY_CONNECTION_TYPE_NETWORK);
-}
-
 bool GetContentProtectionMethods(DisplayConnectionType type,
                                  uint32_t* protection_mask) {
   switch (type) {
diff --git a/ui/display/manager/display_util.h b/ui/display/manager/display_util.h
index 2f2a3a7..e90f055 100644
--- a/ui/display/manager/display_util.h
+++ b/ui/display/manager/display_util.h
@@ -37,11 +37,6 @@
 // Returns a string describing |state|.
 std::string MultipleDisplayStateToString(MultipleDisplayState state);
 
-// Returns whether the DisplayConnectionType |type| is a physically connected
-// display. Currently only DISPLAY_CONNECTION_TYPE_NETWORK return false.
-// All other types return true.
-bool IsPhysicalDisplayType(DisplayConnectionType type);
-
 // Sets bits in |protection_mask| for each ContentProtectionMethod supported by
 // the display |type|. Returns false for unknown display types.
 bool GetContentProtectionMethods(DisplayConnectionType type,
diff --git a/ui/display/manager/query_content_protection_task.cc b/ui/display/manager/query_content_protection_task.cc
index 8488b96..a627324 100644
--- a/ui/display/manager/query_content_protection_task.cc
+++ b/ui/display/manager/query_content_protection_task.cc
@@ -48,8 +48,14 @@
       return;
     }
 
+    // Collect displays to be queried based on HDCP capability. For unprotected
+    // displays not inherently secure through an internal connection, record the
+    // existence of an unsecure display to report no protection for all displays
+    // in mirroring mode.
     if (protection_mask & CONTENT_PROTECTION_METHOD_HDCP)
       hdcp_capable_displays.push_back(display);
+    else if (display->type() != DISPLAY_CONNECTION_TYPE_INTERNAL)
+      no_protection_mask_ |= CONTENT_PROTECTION_METHOD_HDCP;
   }
 
   pending_requests_ = hdcp_capable_displays.size();
diff --git a/ui/display/manager/query_content_protection_task_unittest.cc b/ui/display/manager/query_content_protection_task_unittest.cc
index 69b2759c..7404e3b8 100644
--- a/ui/display/manager/query_content_protection_task_unittest.cc
+++ b/ui/display/manager/query_content_protection_task_unittest.cc
@@ -81,7 +81,7 @@
   ASSERT_TRUE(response_);
   EXPECT_EQ(Status::SUCCESS, response_->status);
   EXPECT_EQ(DISPLAY_CONNECTION_TYPE_INTERNAL, response_->connection_mask);
-  EXPECT_EQ(0u, response_->protection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
 }
 
 TEST_F(QueryContentProtectionTaskTest, QueryUnknownDisplay) {
@@ -99,7 +99,7 @@
   ASSERT_TRUE(response_);
   EXPECT_EQ(Status::FAILURE, response_->status);
   EXPECT_EQ(DISPLAY_CONNECTION_TYPE_UNKNOWN, response_->connection_mask);
-  EXPECT_EQ(0u, response_->protection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
 }
 
 TEST_F(QueryContentProtectionTaskTest, QueryDisplayThatCannotGetHdcp) {
@@ -135,7 +135,7 @@
   ASSERT_TRUE(response_);
   EXPECT_EQ(Status::SUCCESS, response_->status);
   EXPECT_EQ(DISPLAY_CONNECTION_TYPE_HDMI, response_->connection_mask);
-  EXPECT_EQ(0u, response_->protection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
 }
 
 TEST_F(QueryContentProtectionTaskTest, QueryDisplayWithHdcpEnabled) {
@@ -173,7 +173,7 @@
   ASSERT_TRUE(response_);
   EXPECT_EQ(Status::SUCCESS, response_->status);
   EXPECT_EQ(DISPLAY_CONNECTION_TYPE_HDMI, response_->connection_mask);
-  EXPECT_EQ(0u, response_->protection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
 }
 
 TEST_F(QueryContentProtectionTaskTest, QueryInMirroringMode) {
@@ -194,7 +194,63 @@
   EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI |
                                   DISPLAY_CONNECTION_TYPE_DVI),
             response_->connection_mask);
-  EXPECT_EQ(0u, response_->protection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
+}
+
+TEST_F(QueryContentProtectionTaskTest, QueryAnalogDisplay) {
+  std::vector<std::unique_ptr<DisplaySnapshot>> displays;
+  displays.push_back(CreateDisplaySnapshot(1, DISPLAY_CONNECTION_TYPE_VGA));
+  TestDisplayLayoutManager layout_manager(std::move(displays),
+                                          MULTIPLE_DISPLAY_STATE_SINGLE);
+
+  QueryContentProtectionTask task(
+      &layout_manager, &display_delegate_, 1,
+      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
+                 base::Unretained(this)));
+  task.Run();
+
+  ASSERT_TRUE(response_);
+  EXPECT_EQ(Status::SUCCESS, response_->status);
+  EXPECT_EQ(DISPLAY_CONNECTION_TYPE_VGA, response_->connection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
+}
+
+TEST_F(QueryContentProtectionTaskTest, QueryAnalogDisplayMirror) {
+  std::vector<std::unique_ptr<DisplaySnapshot>> displays;
+  displays.push_back(CreateDisplaySnapshot(1, DISPLAY_CONNECTION_TYPE_HDMI));
+  displays.push_back(CreateDisplaySnapshot(2, DISPLAY_CONNECTION_TYPE_VGA));
+  TestDisplayLayoutManager layout_manager(std::move(displays),
+                                          MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
+
+  display_delegate_.set_hdcp_state(HDCP_STATE_ENABLED);
+
+  QueryContentProtectionTask task1(
+      &layout_manager, &display_delegate_, 1,
+      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
+                 base::Unretained(this)));
+  task1.Run();
+
+  ASSERT_TRUE(response_);
+  EXPECT_EQ(Status::SUCCESS, response_->status);
+  EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI |
+                                  DISPLAY_CONNECTION_TYPE_VGA),
+            response_->connection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
+
+  response_.reset();
+
+  QueryContentProtectionTask task2(
+      &layout_manager, &display_delegate_, 2,
+      base::Bind(&QueryContentProtectionTaskTest::ResponseCallback,
+                 base::Unretained(this)));
+  task2.Run();
+
+  ASSERT_TRUE(response_);
+  EXPECT_EQ(Status::SUCCESS, response_->status);
+  EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI |
+                                  DISPLAY_CONNECTION_TYPE_VGA),
+            response_->connection_mask);
+  EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE, response_->protection_mask);
 }
 
 }  // namespace test
diff --git a/ui/events/ozone/evdev/event_device_test_util.cc b/ui/events/ozone/evdev/event_device_test_util.cc
index 0cdb1f6a..2cdb518 100644
--- a/ui/events/ozone/evdev/event_device_test_util.cc
+++ b/ui/events/ozone/evdev/event_device_test_util.cc
@@ -211,6 +211,45 @@
     base::size(kEveTouchScreenAbsAxes),
 };
 
+// Captured from Pixel Slate.
+const DeviceAbsoluteAxis kNocturneTouchScreenAbsAxes[] = {
+    {ABS_X, {0, 0, 10404, 0, 0, 40}},
+    {ABS_Y, {0, 0, 6936, 0, 0, 40}},
+    {ABS_PRESSURE, {0, 0, 255, 0, 0, 0}},
+    {ABS_MT_SLOT, {0, 0, 9, 0, 0, 0}},
+    {ABS_MT_TOUCH_MAJOR, {0, 0, 255, 0, 0, 1}},
+    {ABS_MT_TOUCH_MINOR, {0, 0, 255, 0, 0, 1}},
+    {ABS_MT_ORIENTATION, {0, 0, 1, 0, 0, 0}},
+    {ABS_MT_POSITION_X, {0, 0, 10404, 0, 0, 40}},
+    {ABS_MT_POSITION_Y, {0, 0, 6936, 0, 0, 40}},
+    {ABS_MT_TOOL_TYPE, {0, 0, 2, 0, 0, 0}},
+    {ABS_MT_TRACKING_ID, {0, 0, 65535, 0, 0, 0}},
+    {ABS_MT_PRESSURE, {0, 0, 255, 0, 0, 0}},
+};
+const DeviceCapabilities kNocturneTouchScreen = {
+    /* path */
+    "/sys/devices/pci0000:00/0000:00:15.0/i2c_designware.0/i2c-6/"
+    "i2c-WCOM50C1:00/0018:2D1F:486C.0001/input/input2/event2",
+    /* name */ "WCOM50C1:00 2D1F:486C",
+    /* phys */ "i2c-WCOM50C1:00",
+    /* uniq */ "",
+    /* bustype */ "0018",
+    /* vendor */ "2d1f",
+    /* product */ "486c",
+    /* version */ "0100",
+    /* prop */ "2",
+    /* ev */ "1b",
+    /* key */ "400 0 0 0 0 0",
+    /* rel */ "0",
+    /* abs */ "6f3800001000003",
+    /* msc */ "20",
+    /* sw */ "0",
+    /* led */ "0",
+    /* ff */ "0",
+    kNocturneTouchScreenAbsAxes,
+    base::size(kNocturneTouchScreenAbsAxes),
+};
+
 // Captured from Chromebook Pixel.
 const DeviceCapabilities kLinkKeyboard = {
     /* path */ "/sys/devices/platform/i8042/serio0/input/input6/event6",
@@ -639,6 +678,36 @@
     base::size(kEveStylusAbsAxes),
 };
 
+// Captured from Pixel Slate
+const DeviceAbsoluteAxis kNocturneStylusAbsAxes[] = {
+    {ABS_X, {0, 0, 26010, 0, 0, 100}},     {ABS_Y, {0, 0, 17340, 0, 0, 100}},
+    {ABS_PRESSURE, {0, 0, 2047, 0, 0, 0}}, {ABS_TILT_X, {0, -90, 90, 0, 0, 57}},
+    {ABS_TILT_Y, {0, -90, 90, 0, 0, 57}},  {ABS_MISC, {0, 0, 65535, 0, 0, 0}},
+};
+const DeviceCapabilities kNocturneStylus = {
+    /* path */
+    "/sys/devices/pci0000:00/0000:00:15.0/i2c_designware.0/i2c-6/"
+    "i2c-WCOM50C1:00/0018:2D1F:486C.0001/input/input3/event3",
+    /* name */ "WCOM50C1:00 2D1F:486C Pen",
+    /* phys */ "",
+    /* uniq */ "",
+    /* bustype */ "0018",
+    /* vendor */ "2d1f",
+    /* product */ "486c",
+    /* version */ "0100",
+    /* prop */ "0",
+    /* ev */ "1b",
+    /* key */ "1c03 1 0 0 0 0",
+    /* rel */ "0",
+    /* abs */ "1000d000003",
+    /* msc */ "11",
+    /* sw */ "0",
+    /* led */ "0",
+    /* ff */ "0",
+    kNocturneStylusAbsAxes,
+    base::size(kNocturneStylusAbsAxes),
+};
+
 const DeviceCapabilities kHammerKeyboard = {
     /* path */
     "/sys/devices/pci0000:00/0000:00:14.0/usb1/1-7/1-7:1.0/0003:18D1:5030.0002/"
diff --git a/ui/events/ozone/evdev/event_device_test_util.h b/ui/events/ozone/evdev/event_device_test_util.h
index 203b8aa..bbb7c7cfa 100644
--- a/ui/events/ozone/evdev/event_device_test_util.h
+++ b/ui/events/ozone/evdev/event_device_test_util.h
@@ -82,6 +82,8 @@
 extern const DeviceCapabilities kIlitekTP_Mouse;
 extern const DeviceCapabilities kIlitekTP;
 extern const DeviceCapabilities kSideVolumeButton;
+extern const DeviceCapabilities kNocturneTouchScreen;
+extern const DeviceCapabilities kNocturneStylus;
 
 }  // namspace ui
 
diff --git a/ui/events/platform/x11/x11_event_source_libevent.cc b/ui/events/platform/x11/x11_event_source_libevent.cc
index 500a520..8ef771f 100644
--- a/ui/events/platform/x11/x11_event_source_libevent.cc
+++ b/ui/events/platform/x11/x11_event_source_libevent.cc
@@ -250,4 +250,12 @@
   NOTREACHED();
 }
 
+void XEventDispatcher::CheckCanDispatchNextPlatformEvent(XEvent* xev) {}
+
+void XEventDispatcher::PlatformEventDispatchFinished() {}
+
+PlatformEventDispatcher* XEventDispatcher::GetPlatformEventDispatcher() {
+  return nullptr;
+}
+
 }  // namespace ui
diff --git a/ui/events/platform/x11/x11_event_source_libevent.h b/ui/events/platform/x11/x11_event_source_libevent.h
index a7f1aba..bc8a848 100644
--- a/ui/events/platform/x11/x11_event_source_libevent.h
+++ b/ui/events/platform/x11/x11_event_source_libevent.h
@@ -20,25 +20,25 @@
 // X11 currently.
 class EVENTS_EXPORT XEventDispatcher {
  public:
+  // Sends XEvent to XEventDispatcher for handling. Returns true if the XEvent
+  // was dispatched, otherwise false. After the first XEventDispatcher returns
+  // true XEvent dispatching stops.
+  virtual bool DispatchXEvent(XEvent* xevent) = 0;
+
   // XEventDispatchers can be used to test if they are able to process next
   // translated event sent by a PlatformEventSource. If so, they must make a
   // promise internally to process next event sent by PlatformEventSource.
-  virtual void CheckCanDispatchNextPlatformEvent(XEvent* xev) = 0;
+  virtual void CheckCanDispatchNextPlatformEvent(XEvent* xev);
 
   // Tells that an event has been dispatched and an event handling promise must
   // be removed.
-  virtual void PlatformEventDispatchFinished() = 0;
+  virtual void PlatformEventDispatchFinished();
 
   // Returns PlatformEventDispatcher if this XEventDispatcher is associated with
   // a PlatformEventDispatcher as well. Used to explicitly add a
   // PlatformEventDispatcher during a call from an XEventDispatcher to
   // AddXEventDispatcher.
-  virtual PlatformEventDispatcher* GetPlatformEventDispatcher() = 0;
-
-  // Sends XEvent to XEventDispatcher for handling. Returns true if the XEvent
-  // was dispatched, otherwise false. After the first XEventDispatcher returns
-  // true XEvent dispatching stops.
-  virtual bool DispatchXEvent(XEvent* xevent) = 0;
+  virtual PlatformEventDispatcher* GetPlatformEventDispatcher();
 
  protected:
   virtual ~XEventDispatcher() {}
diff --git a/ui/ozone/platform/x11/gl_surface_glx_ozone.cc b/ui/ozone/platform/x11/gl_surface_glx_ozone.cc
index a4be7c87..583823d2 100644
--- a/ui/ozone/platform/x11/gl_surface_glx_ozone.cc
+++ b/ui/ozone/platform/x11/gl_surface_glx_ozone.cc
@@ -30,14 +30,6 @@
     event_source->RemoveXEventDispatcher(this);
 }
 
-void GLSurfaceGLXOzone::CheckCanDispatchNextPlatformEvent(XEvent* xev) {}
-
-void GLSurfaceGLXOzone::PlatformEventDispatchFinished() {}
-
-PlatformEventDispatcher* GLSurfaceGLXOzone::GetPlatformEventDispatcher() {
-  return nullptr;
-}
-
 bool GLSurfaceGLXOzone::DispatchXEvent(XEvent* event) {
   if (!CanHandleEvent(event))
     return false;
diff --git a/ui/ozone/platform/x11/gl_surface_glx_ozone.h b/ui/ozone/platform/x11/gl_surface_glx_ozone.h
index 0c468010..3a65b96 100644
--- a/ui/ozone/platform/x11/gl_surface_glx_ozone.h
+++ b/ui/ozone/platform/x11/gl_surface_glx_ozone.h
@@ -11,8 +11,6 @@
 
 namespace ui {
 
-class PlatformEventDispatcher;
-
 // Ozone specific implementation of GLX surface. Registers as a XEventDispatcher
 // to handle XEvents.
 class GLSurfaceGLXOzone : public gl::NativeViewGLSurfaceGLX,
@@ -28,9 +26,6 @@
   void UnregisterEvents() override;
 
   // XEventDispatcher:
-  void CheckCanDispatchNextPlatformEvent(XEvent* xev) override;
-  void PlatformEventDispatchFinished() override;
-  PlatformEventDispatcher* GetPlatformEventDispatcher() override;
   bool DispatchXEvent(XEvent* xevent) override;
 
  private: