diff --git a/DEPS b/DEPS
index 4eaa3d4..c96d90d 100644
--- a/DEPS
+++ b/DEPS
@@ -175,11 +175,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': '4036cb1f0b6e275009ff08681e878cde0763c3e9',
+  'skia_revision': 'c6d0fdfb40987231be61ba3a47f6b34013a04606',
   # 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': 'f796ed84539185f842e561ebac3e1d4e1fe862ba',
+  'v8_revision': '56ff573a6c1416dad552ed26b1724a30a632f4d3',
   # 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.
@@ -187,7 +187,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': '90019cea5ec957b268b8a5d30c40102df5a587a7',
+  'angle_revision': '9e9493f29dbf3afcd962c56f66cb752efe3c7ee8',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -195,7 +195,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': '57b83355c5155c2f8f9093b1e13ec12dd1bfe2f7',
+  'pdfium_revision': 'a40862f237fc1d14779e6f6924e954ebcc8d18a3',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -238,7 +238,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': 'f6edc90900fed2a0d5b89e49c0296171d8928eee',
+  'catapult_revision': 'bbd4f3e6052ca9088e8d4ac1ee7139ba8f1cacf1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -246,7 +246,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': 'e3e152bd3a2915aef5122b4187bc9214e0f8441e',
+  'devtools_frontend_revision': 'f92fd4849f47c9ad4a26c1864138f73418d60ddf',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -878,7 +878,7 @@
 
   # For Linux and Chromium OS.
   'src/third_party/cros_system_api': {
-      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + 'a4d130010ec6953e722978d879f2f96fa0e155d3',
+      'url': Var('chromium_git') + '/chromiumos/platform2/system_api.git' + '@' + '2302d21944c881abf8897f72319206277b505526',
       'condition': 'checkout_linux',
   },
 
@@ -888,7 +888,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e1318818e6abc275d4a346f28a32cb5b7654e660',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ec2a6ce2706a078d18fa758fe4e48cd71bb850a3',
 
   'src/third_party/devtools-frontend/src':
     Var('chromium_git') + '/devtools/devtools-frontend' + '@' + Var('devtools_frontend_revision'),
@@ -941,7 +941,7 @@
     Var('chromium_git') + '/external/github.com/google/emoji-segmenter.git' + '@' + Var('emoji_segmenter_revision'),
 
   'src/third_party/libgav1/src':
-    Var('chromium_git') + '/codecs/libgav1.git' + '@' + '8e8c13b9e821f4590761487c4e0b96f432eaf051',
+    Var('chromium_git') + '/codecs/libgav1.git' + '@' + 'fa1c3c4e673cf12ffa22b8fbe4a7c79314571f1b',
 
   'src/third_party/glslang/src':
     Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '8985fc91089f3117d338f0ebd683596a197b2d92',
@@ -1239,7 +1239,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + '83af90cf49dee96eecd99fcdbcd9a6f89b5ccb6f',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '024aa887785036dd7f1012cc97ce131ed40304b4',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1440,7 +1440,7 @@
     Var('chromium_git') + '/external/github.com/gpuweb/cts.git' + '@' + 'ec18cc3262922e7dcdbe70243c6f40606f979144',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + 'ccefde95b3bbc1129e1fd29fd73130f55e30b5c6',
+    Var('webrtc_git') + '/src.git' + '@' + '80636981052754dcdaaf90a5d83a1cd9eb786dfb',
 
   'src/third_party/libgifcodec':
      Var('skia_git') + '/libgifcodec' + '@'+  Var('libgifcodec_revision'),
@@ -1515,7 +1515,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@7f746ebe35854fca20d90ea9bafd82ab4d93c6f0',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@2fdaf67e552951e7d4c1d0fa1b688942caf25baa',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 56ed1c4..35a52e2 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -372,7 +372,6 @@
   '^device/bluetooth/',
   '^extensions/browser/',
   '^extensions/renderer/',
-  '^extensions/test/',
   '^google_apis/dive/',
   '^google_apis/gcm/',
   '^ios/chrome/',
diff --git a/android_webview/browser/aw_quota_manager_bridge.cc b/android_webview/browser/aw_quota_manager_bridge.cc
index 33fb5fe..16cff78 100644
--- a/android_webview/browser/aw_quota_manager_bridge.cc
+++ b/android_webview/browser/aw_quota_manager_bridge.cc
@@ -153,17 +153,6 @@
 }  // namespace
 
 // static
-jlong JNI_AwQuotaManagerBridge_GetDefaultNativeAwQuotaManagerBridge(
-    JNIEnv* env) {
-  AwBrowserContext* browser_context = AwBrowserContext::GetDefault();
-
-  AwQuotaManagerBridge* bridge = static_cast<AwQuotaManagerBridge*>(
-      browser_context->GetQuotaManagerBridge());
-  DCHECK(bridge);
-  return reinterpret_cast<intptr_t>(bridge);
-}
-
-// static
 scoped_refptr<AwQuotaManagerBridge> AwQuotaManagerBridge::Create(
     AwBrowserContext* browser_context) {
   return new AwQuotaManagerBridge(browser_context);
diff --git a/ash/public/cpp/shelf_config.h b/ash/public/cpp/shelf_config.h
index 16575fc..d3b1e07f 100644
--- a/ash/public/cpp/shelf_config.h
+++ b/ash/public/cpp/shelf_config.h
@@ -8,6 +8,7 @@
 #include "ash/ash_export.h"
 #include "ash/public/cpp/app_list/app_list_controller_observer.h"
 #include "ash/public/cpp/tablet_mode_observer.h"
+#include "ash/system/model/virtual_keyboard_model.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
 #include "base/observer_list_types.h"
@@ -21,7 +22,8 @@
 // values could change at runtime.
 class ASH_EXPORT ShelfConfig : public TabletModeObserver,
                                public AppListControllerObserver,
-                               public display::DisplayObserver {
+                               public display::DisplayObserver,
+                               public VirtualKeyboardModel::Observer {
  public:
   class Observer : public base::CheckedObserver {
    public:
@@ -51,6 +53,9 @@
   void OnDisplayMetricsChanged(const display::Display& display,
                                uint32_t changed_metrics) override;
 
+  // VirtualKeyboardModel::Observer:
+  void OnVirtualKeyboardVisibilityChanged() override;
+
   // AppListControllerObserver:
   void OnAppListVisibilityWillChange(bool shown, int64_t display_id) override;
 
@@ -158,6 +163,8 @@
 
   bool shelf_controls_shown() const { return shelf_controls_shown_; }
 
+  bool is_virtual_keyboard_shown() const { return is_virtual_keyboard_shown_; }
+
   // Gets the current color for the shelf control buttons.
   SkColor GetShelfControlButtonColor() const;
 
@@ -223,6 +230,9 @@
   // should be shown.
   bool shelf_controls_shown_;
 
+  // Whether virtual IME keyboard is shown.
+  bool is_virtual_keyboard_shown_;
+
   // Whether the app list (or home launcher in tablet mode) is visible.
   bool is_app_list_visible_;
 
diff --git a/ash/shelf/shelf_config.cc b/ash/shelf/shelf_config.cc
index ecd9ea2..264fe03 100644
--- a/ash/shelf/shelf_config.cc
+++ b/ash/shelf/shelf_config.cc
@@ -11,6 +11,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/style/ash_color_provider.h"
+#include "ash/system/model/system_tray_model.h"
 #include "ash/wallpaper/wallpaper_controller_impl.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/scoped_observer.h"
@@ -72,6 +73,7 @@
 ShelfConfig::ShelfConfig()
     : is_dense_(false),
       shelf_controls_shown_(true),
+      is_virtual_keyboard_shown_(false),
       is_app_list_visible_(false),
       shelf_button_icon_size_(44),
       shelf_button_icon_size_dense_(36),
@@ -129,6 +131,7 @@
   shell->tablet_mode_controller()->AddObserver(this);
   shell->app_list_controller()->AddObserver(this);
   display::Screen::GetScreen()->AddObserver(this);
+  shell->system_tray_model()->virtual_keyboard()->AddObserver(this);
 }
 
 void ShelfConfig::Shutdown() {
@@ -139,6 +142,7 @@
   display::Screen::GetScreen()->RemoveObserver(this);
   shell->app_list_controller()->RemoveObserver(this);
   shell->tablet_mode_controller()->RemoveObserver(this);
+  shell->system_tray_model()->virtual_keyboard()->RemoveObserver(this);
 }
 
 void ShelfConfig::OnTabletModeStarting() {
@@ -159,6 +163,10 @@
   UpdateConfig(is_app_list_visible_);
 }
 
+void ShelfConfig::OnVirtualKeyboardVisibilityChanged() {
+  UpdateConfig(is_app_list_visible_);
+}
+
 void ShelfConfig::OnAppListVisibilityWillChange(bool shown,
                                                 int64_t display_id) {
   // Let's check that the app visibility mechanism isn't mis-firing, which
@@ -255,7 +263,7 @@
   if (!session)
     return false;
   return session->GetSessionState() == session_manager::SessionState::ACTIVE &&
-         !is_app_list_visible_;
+         (!is_app_list_visible_ || is_virtual_keyboard_shown_);
 }
 
 void ShelfConfig::UpdateConfig(bool app_list_visible) {
@@ -275,14 +283,25 @@
       !(in_tablet_mode && features::IsHideShelfControlsInTabletModeEnabled()) ||
       ShelfControlsForcedShownForAccessibility();
 
+  // TODO(https://crbug.com/1058205): Test this behavior.
+  // If the virtual keyboard is shown, the back button and in-app shelf should
+  // be shown so users can exit the keyboard. SystemTrayModel may be null in
+  // tests.
+  const bool virtual_keyboard_shown =
+      Shell::Get()->system_tray_model()
+          ? Shell::Get()->system_tray_model()->virtual_keyboard()->visible()
+          : false;
+
   if (new_is_dense == is_dense_ &&
       shelf_controls_shown_ == new_shelf_controls_shown &&
+      is_virtual_keyboard_shown_ == virtual_keyboard_shown &&
       is_app_list_visible_ == app_list_visible) {
     return;
   }
 
   is_dense_ = new_is_dense;
   shelf_controls_shown_ = new_shelf_controls_shown;
+  is_virtual_keyboard_shown_ = virtual_keyboard_shown;
   is_app_list_visible_ = app_list_visible;
 
   OnShelfConfigUpdated();
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index f009dbe..08cf600 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -825,6 +825,13 @@
       Shell::Get()->overview_controller()->InOverviewSession();
   if (Shell::Get()->IsInTabletMode()) {
     if (app_list_is_visible) {
+      // TODO(https://crbug.com/1058205): Test this behavior.
+      // If the IME virtual keyboard is showing, the shelf should appear in-app.
+      // The workspace area in tablet mode is always the in-app workspace area,
+      // and the virtual keyboard places itself on screen based on workspace
+      // area.
+      if (ShelfConfig::Get()->is_virtual_keyboard_shown())
+        return ShelfBackgroundType::kInApp;
       // If the home launcher is shown or mostly shown, show the home launcher
       // background. If it is mostly hidden, show the in-app or overview
       // background.
@@ -1130,6 +1137,7 @@
 
 void ShelfLayoutManager::OnShelfConfigUpdated() {
   LayoutShelf(/*animate=*/true);
+  MaybeUpdateShelfBackground(AnimationChangeType::IMMEDIATE);
   UpdateContextualNudges();
 }
 
@@ -1290,6 +1298,10 @@
       !overview_controller->IsCompletingShutdownAnimations();
   const bool app_list_visible = app_list_controller->GetTargetVisibility();
 
+  // TODO(https://crbug.com/1058205): Test this behavior.
+  if (ShelfConfig::Get()->is_virtual_keyboard_shown())
+    return HotseatState::kHidden;
+
   // Only force to show if there is not a pending drag operation.
   if (shelf_widget_->is_hotseat_forced_to_show() && drag_status_ == kDragNone)
     return app_list_visible ? HotseatState::kShownHomeLauncher
diff --git a/ash/shelf/shelf_navigation_widget.cc b/ash/shelf/shelf_navigation_widget.cc
index 75031c25..93308236 100644
--- a/ash/shelf/shelf_navigation_widget.cc
+++ b/ash/shelf/shelf_navigation_widget.cc
@@ -24,6 +24,7 @@
 #include "ui/compositor/animation_metrics_reporter.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
+#include "ui/gfx/transform_util.h"
 #include "ui/views/animation/bounds_animator.h"
 #include "ui/views/background.h"
 #include "ui/views/view.h"
@@ -63,6 +64,10 @@
 }
 
 bool IsBackButtonShown() {
+  // TODO(https://crbug.com/1058205): Test this behavior.
+  if (ShelfConfig::Get()->is_virtual_keyboard_shown())
+    return true;
+
   if (!ShelfConfig::Get()->shelf_controls_shown())
     return false;
   return chromeos::switches::ShouldShowShelfHotseat()
@@ -595,6 +600,18 @@
   UpdateButtonVisibility(home_button, home_button_shown, animate,
                          home_button_metrics_reporter_.get());
 
+  if (back_button_shown) {
+    // TODO(https://crbug.com/1058205): Test this behavior.
+    gfx::Transform rotation;
+    // If the IME virtual keyboard is visible, rotate the back button downwards,
+    // this indicates it can be used to close the keyboard.
+    if (ShelfConfig::Get()->is_virtual_keyboard_shown())
+      rotation.Rotate(270.0);
+
+    delegate_->back_button()->layer()->SetTransform(TransformAboutPivot(
+        delegate_->back_button()->GetCenterPoint(), rotation));
+  }
+
   gfx::Rect home_button_bounds =
       back_button_shown ? GetSecondButtonBounds()
                         : GetFirstButtonBounds(shelf_->IsHorizontalAlignment());
diff --git a/ash/shell.cc b/ash/shell.cc
index 4c0787f..d5caeee 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -1085,9 +1085,6 @@
   app_list_controller_ = std::make_unique<AppListControllerImpl>();
   home_screen_controller_->SetDelegate(app_list_controller_.get());
 
-  // The |shelf_config_| needs |app_list_controller_| to initialize itself.
-  shelf_config_->Init();
-
   autoclick_controller_ = std::make_unique<AutoclickController>();
 
   high_contrast_controller_.reset(new HighContrastController);
@@ -1129,6 +1126,11 @@
   // |system_notification_controller_| is initialized and Shelf is created by
   // WindowTreeHostManager::InitHosts.
   system_tray_model_ = std::make_unique<SystemTrayModel>();
+
+  // The |shelf_config_| needs |app_list_controller_| and |system_tray_model_|
+  // to initialize itself.
+  shelf_config_->Init();
+
   system_notification_controller_ =
       std::make_unique<SystemNotificationController>();
 
diff --git a/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
index c735d7b..be0e79601 100644
--- a/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
+++ b/base/test/android/javatests/src/org/chromium/base/test/util/CallbackHelper.java
@@ -126,7 +126,7 @@
  */
 public class CallbackHelper {
     /** The default timeout (in seconds) for a callback to wait. */
-    public static final long WAIT_TIMEOUT_SECONDS = 5L;
+    public static final long WAIT_TIMEOUT_SECONDS = ScalableTimeout.scaleTimeout(5L);
 
     private final Object mLock = new Object();
     private int mCallCount;
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 2b00bbc..8f65230a 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200303.3.1
\ No newline at end of file
+0.20200303.4.1
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 2b00bbc..8f65230a 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200303.3.1
\ No newline at end of file
+0.20200303.4.1
\ No newline at end of file
diff --git a/chrome/VERSION b/chrome/VERSION
index d565ca6b..e5e86f5 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=82
 MINOR=0
-BUILD=4077
+BUILD=4078
 PATCH=0
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index ca42c3c..8e54ae60 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -229,7 +229,7 @@
 
     private boolean mPartnerBrowserRefreshNeeded;
 
-    protected IntentHandler mIntentHandler;
+    protected final IntentHandler mIntentHandler;
 
     /** Set if {@link #postDeferredStartupIfNeeded()} is called before native has loaded. */
     private boolean mDeferredStartupQueued;
@@ -312,6 +312,10 @@
     /** Controls tab reparenting for night mode. */
     NightModeReparentingController mNightModeReparentingController;
 
+    protected ChromeActivity() {
+        mIntentHandler = new IntentHandler(this, createIntentHandlerDelegate());
+    }
+
     @Override
     protected ActivityWindowAndroid createWindowAndroid() {
         return new ChromeWindow(this);
@@ -633,8 +637,8 @@
      */
     protected StartupTabPreloader getStartupTabPreloader() {
         if (mStartupTabPreloader == null) {
-            mStartupTabPreloader = new StartupTabPreloader(
-                    this::getIntent, getLifecycleDispatcher(), getWindowAndroid(), this);
+            mStartupTabPreloader = new StartupTabPreloader(this::getIntent,
+                    getLifecycleDispatcher(), getWindowAndroid(), this, mIntentHandler);
         }
         return mStartupTabPreloader;
     }
@@ -731,7 +735,6 @@
 
         IntentHandler.setTestIntentsEnabled(
                 CommandLine.getInstance().hasSwitch(ContentSwitches.ENABLE_TEST_INTENTS));
-        mIntentHandler = new IntentHandler(createIntentHandlerDelegate(), getPackageName());
     }
 
     @Override
@@ -962,7 +965,7 @@
         }
 
         super.onNewIntentWithNative(intent);
-        if (IntentHandler.shouldIgnoreIntent(intent)) return;
+        if (mIntentHandler.shouldIgnoreIntent(intent)) return;
 
         // We send this intent so that we can enter WebVr presentation mode if needed. This
         // call doesn't consume the intent because it also has the url that we need to load.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 17fbe02..a8f11ea 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -1082,7 +1082,7 @@
                     this.getLifecycleDispatcher());
             mIntentWithEffect = false;
             if (getSavedInstanceState() == null && intent != null) {
-                if (!IntentHandler.shouldIgnoreIntent(intent)) {
+                if (!mIntentHandler.shouldIgnoreIntent(intent)) {
                     mIntentWithEffect = mIntentHandler.onNewIntent(intent);
                 }
 
@@ -1793,7 +1793,7 @@
                 return true;
             }
         } else if (shouldCloseTab) {
-            getCurrentTabModel().closeTab(currentTab, true, false, false);
+            currentTab.getWebContents().dispatchBeforeUnload(false);
             return true;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandler.java
index ca81c107..2076ef60 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandler.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser;
 
+import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -12,6 +13,8 @@
 
 import org.chromium.base.ContextUtils;
 
+import java.lang.ref.WeakReference;
+
 /**
  * Intent handler that is specific for the situation when the screen is unlocked from pin, pattern,
  * or password.
@@ -21,15 +24,17 @@
 public class DelayedScreenLockIntentHandler extends BroadcastReceiver {
     private static final int VALID_DEFERRED_PERIOD_MS = 10000;
 
-    private final Handler mTaskHandler;
-    private final Runnable mUnregisterTask;
-
+    private final Handler mTaskHandler = new Handler();
+    private final Runnable mUnregisterTask = () -> updateDeferredIntent(null);
+    // Must be an Activity context for deferred intents without FLAG_NEW_TASK on Android P+.
+    // http://crbug.com/1034440
+    private final WeakReference<Activity> mActivity;
     private Intent mDeferredIntent;
-    private boolean mReceiverRegistered;
+    private boolean mEnabled;
 
-    public DelayedScreenLockIntentHandler() {
-        mTaskHandler = new Handler();
-        mUnregisterTask = () -> updateDeferredIntent(null);
+    public DelayedScreenLockIntentHandler(Activity activity) {
+        // Use a WeakReference so that Activity is not retained by call to postDelayed.
+        mActivity = new WeakReference<>(activity);
     }
 
     @Override
@@ -37,7 +42,10 @@
         assert Intent.ACTION_USER_PRESENT.equals(intent.getAction());
 
         if (Intent.ACTION_USER_PRESENT.equals(intent.getAction()) && mDeferredIntent != null) {
-            context.startActivity(mDeferredIntent);
+            Activity activity = mActivity.get();
+            if (activity != null) {
+                activity.startActivity(mDeferredIntent);
+            }
             // Prevent the broadcast receiver from firing intent unexpectedly.
             updateDeferredIntent(null);
         }
@@ -47,30 +55,12 @@
      * Update the deferred intent with the target intent, also reset the deferred intent's lifecycle
      * @param intent Target intent
      */
-    public void updateDeferredIntent(final Intent intent) {
+    public void updateDeferredIntent(Intent intent) {
         mTaskHandler.removeCallbacks(mUnregisterTask);
-
-        if (intent == null) {
-            unregisterReceiver();
-            mDeferredIntent = null;
-            return;
-        }
-
-        mDeferredIntent = intent;
-        registerReceiver();
         mTaskHandler.postDelayed(mUnregisterTask, VALID_DEFERRED_PERIOD_MS);
-    }
+        mDeferredIntent = intent;
 
-    /**
-     * Register to receive ACTION_USER_PRESENT when the screen is unlocked.
-     * The ACTION_USER_PRESENT is sent by platform to indicates when user is present.
-     */
-    private void registerReceiver() {
-        if (mReceiverRegistered) return;
-
-        ContextUtils.getApplicationContext()
-                .registerReceiver(this, new IntentFilter(Intent.ACTION_USER_PRESENT));
-        mReceiverRegistered = true;
+        setEnabled(intent != null);
     }
 
     /**
@@ -78,11 +68,17 @@
      * - When the deferred intent expires
      * - When updateDeferredIntent(null) called
      * - When the deferred intent has been fired
+     * Register to receive ACTION_USER_PRESENT when the screen is unlocked.
+     * The ACTION_USER_PRESENT is sent by platform to indicates when user is present.
      */
-    private void unregisterReceiver() {
-        if (!mReceiverRegistered) return;
-
-        ContextUtils.getApplicationContext().unregisterReceiver(this);
-        mReceiverRegistered = false;
+    private void setEnabled(boolean value) {
+        if (value == mEnabled) return;
+        mEnabled = value;
+        Context applicationContext = ContextUtils.getApplicationContext();
+        if (value) {
+            applicationContext.registerReceiver(this, new IntentFilter(Intent.ACTION_USER_PRESENT));
+        } else {
+            applicationContext.unregisterReceiver(this);
+        }
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
index f4fc976..e76b2d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java
@@ -274,7 +274,7 @@
     private static boolean sTestIntentsEnabled;
 
     private final IntentHandlerDelegate mDelegate;
-    private final String mPackageName;
+    private final Activity mActivity;
 
     /**
      * Receiver for screen unlock broadcast.
@@ -329,9 +329,9 @@
         sTestIntentsEnabled = enabled;
     }
 
-    public IntentHandler(IntentHandlerDelegate delegate, String packageName) {
+    public IntentHandler(Activity activity, IntentHandlerDelegate delegate) {
         mDelegate = delegate;
-        mPackageName = packageName;
+        mActivity = activity;
     }
 
     /**
@@ -420,9 +420,9 @@
         }
     }
 
-    private static void updateDeferredIntent(Intent intent) {
+    private void updateDeferredIntent(Intent intent) {
         if (sDelayedScreenIntentHandler == null && intent != null) {
-            sDelayedScreenIntentHandler = new DelayedScreenLockIntentHandler();
+            sDelayedScreenIntentHandler = new DelayedScreenLockIntentHandler(mActivity);
         }
 
         if (sDelayedScreenIntentHandler != null) {
@@ -850,7 +850,7 @@
      * @param intent Intent to check.
      * @return true if the intent should be ignored.
      */
-    public static boolean shouldIgnoreIntent(Intent intent) {
+    public boolean shouldIgnoreIntent(Intent intent) {
         // Although not documented to, many/most methods that retrieve values from an Intent may
         // throw. Because we can't control what packages might send to us, we should catch any
         // Throwable and then fail closed (safe). This is ugly, but resolves top crashers in the
@@ -1060,8 +1060,9 @@
 
         // Intents from chrome open in the same tab by default, all others only clobber
         // tabs created by the same app.
-        return mPackageName.equals(appId) ? TabOpenType.CLOBBER_CURRENT_TAB
-                                          : TabOpenType.REUSE_APP_ID_MATCHING_TAB_ELSE_NEW_TAB;
+        return mActivity.getPackageName().equals(appId)
+                ? TabOpenType.CLOBBER_CURRENT_TAB
+                : TabOpenType.REUSE_APP_ID_MATCHING_TAB_ELSE_NEW_TAB;
     }
 
     private static boolean isInvalidScheme(String scheme) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
index 1326f2f..ed24838e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/LaunchIntentDispatcher.java
@@ -161,7 +161,7 @@
                 mIntent.getBooleanExtra(IntentHandler.EXTRA_OPEN_NEW_INCOGNITO_TAB, false);
 
         // Check if a web search Intent is being handled.
-        IntentHandler intentHandler = new IntentHandler(this, mActivity.getPackageName());
+        IntentHandler intentHandler = new IntentHandler(mActivity, this);
         String url = IntentHandler.getUrlFromIntent(mIntent);
         if (url == null && tabId == Tab.INVALID_TAB_ID && !incognito
                 && intentHandler.handleWebSearchIntent(mIntent)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java
index 99e1179..d969b858 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClient.java
@@ -106,9 +106,10 @@
         Resources res = ContextUtils.getApplicationContext().getResources();
         String channelDisplayName = res.getString(R.string.notification_category_group_general);
 
-        return connectAndExecute(origin.uri(), service ->
-                callback.onPermissionCheck(service.getComponentName(),
-                        service.areNotificationsEnabled(channelDisplayName)));
+        return connectAndExecute(origin.uri(),
+                (originCopy, service)
+                        -> callback.onPermissionCheck(service.getComponentName(),
+                                service.areNotificationsEnabled(channelDisplayName)));
     }
 
     /**
@@ -124,9 +125,8 @@
             NotificationBuilderBase builder, NotificationUmaTracker notificationUmaTracker) {
         Resources res = ContextUtils.getApplicationContext().getResources();
         String channelDisplayName = res.getString(R.string.notification_category_group_general);
-        Origin origin = Origin.createOrThrow(scope);
 
-        connectAndExecute(scope, service -> {
+        connectAndExecute(scope, (origin, service) -> {
             if (!service.areNotificationsEnabled(channelDisplayName)) {
                 mDelegatesManager.updatePermission(origin,
                         service.getComponentName().getPackageName(), false);
@@ -189,15 +189,18 @@
      * @param platformId The id of the notification to cancel.
      */
     public void cancelNotification(Uri scope, String platformTag, int platformId) {
-        connectAndExecute(scope, service -> service.cancel(platformTag, platformId));
+        connectAndExecute(scope, (origin, service) -> service.cancel(platformTag, platformId));
     }
 
     private interface ExecutionCallback {
-        void onConnected(TrustedWebActivityServiceConnection service) throws RemoteException;
+        void onConnected(Origin origin, TrustedWebActivityServiceConnection service)
+                throws RemoteException;
     }
 
     private boolean connectAndExecute(Uri scope, ExecutionCallback callback) {
-        Origin origin = Origin.createOrThrow(scope);
+        Origin origin = Origin.create(scope);
+        if (origin == null) return false;
+
         Set<Token> possiblePackages = mDelegatesManager.getAllDelegateApps(origin);
         if (possiblePackages == null || possiblePackages.isEmpty()) return false;
 
@@ -205,7 +208,7 @@
                 mConnection.connect(scope, possiblePackages, AsyncTask.THREAD_POOL_EXECUTOR);
         connection.addListener(() -> {
             try {
-                callback.onConnected(connection.get());
+                callback.onConnected(origin, connection.get());
             } catch (RemoteException | ExecutionException | InterruptedException e) {
                 Log.w(TAG, "Failed to execute TWA command.");
             }
@@ -231,7 +234,8 @@
 
     private @Nullable Intent createLaunchIntentForTwaInternal(Context appContext, String url,
             List<ResolveInfo> resolveInfosForUrl) {
-        Origin origin = Origin.createOrThrow(url);
+        Origin origin = Origin.create(url);
+        if (origin == null) return null;
 
         // Trusted Web Activities only work with https so we can shortcut here.
         if (!UrlConstants.HTTPS_SCHEME.equals(origin.uri().getScheme())) return null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index da90935..0eb28b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -358,7 +358,7 @@
             ChromeActivityCommonsModule commonsModule) {
         // mIntentHandler comes from the base class.
         IntentIgnoringCriterion intentIgnoringCriterion =
-                (intent) -> IntentHandler.shouldIgnoreIntent(intent);
+                (intent) -> mIntentHandler.shouldIgnoreIntent(intent);
 
         CustomTabActivityModule customTabsModule = new CustomTabActivityModule(mIntentDataProvider,
                 mNightModeStateController, intentIgnoringCriterion, getStartupTabPreloader());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
index 09c725b..97c9311 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/StartupTabPreloader.java
@@ -45,17 +45,19 @@
     private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
     private final WindowAndroid mWindowAndroid;
     private final TabCreatorManager mTabCreatorManager;
+    private final IntentHandler mIntentHandler;
     private LoadUrlParams mLoadUrlParams;
     private Tab mTab;
     private StartupTabObserver mObserver;
 
     public StartupTabPreloader(Supplier<Intent> intentSupplier,
             ActivityLifecycleDispatcher activityLifecycleDispatcher, WindowAndroid windowAndroid,
-            TabCreatorManager tabCreatorManager) {
+            TabCreatorManager tabCreatorManager, IntentHandler intentHandler) {
         mIntentSupplier = intentSupplier;
         mActivityLifecycleDispatcher = activityLifecycleDispatcher;
         mWindowAndroid = windowAndroid;
         mTabCreatorManager = tabCreatorManager;
+        mIntentHandler = intentHandler;
 
         mActivityLifecycleDispatcher.register(this);
         ProfileManager.addObserver(this);
@@ -151,7 +153,7 @@
         if (mTab != null) return false;
 
         Intent intent = mIntentSupplier.get();
-        if (IntentHandler.shouldIgnoreIntent(intent)) return false;
+        if (mIntentHandler.shouldIgnoreIntent(intent)) return false;
         if (getUrlFromIntent(intent) == null) return false;
 
         // We don't support incognito tabs because only chrome can send new incognito tab
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
index 90ce96c..af1b0e4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/omnibox/status/StatusViewCoordinator.java
@@ -12,6 +12,7 @@
 
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
+import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils;
 import org.chromium.chrome.browser.omnibox.UrlBar.UrlTextChangeListener;
 import org.chromium.chrome.browser.omnibox.UrlBarEditingTextStateProvider;
 import org.chromium.chrome.browser.page_info.PageInfoController;
@@ -221,6 +222,7 @@
      */
     public void setShowIconsWhenUrlFocused(boolean showIconsWithUrlFocused) {
         mMediator.setShowIconsWhenUrlFocused(showIconsWithUrlFocused);
+        reconcileVisualState(showIconsWithUrlFocused);
     }
 
     /**
@@ -240,6 +242,37 @@
     }
 
     /**
+     * Temporary workaround for the divergent logic for status icon visibility changes for the dse
+     * icon experiment. Should be removed when the dse icon launches (crbug.com/1019488).
+     *
+     * When transitioning to incognito, the first visible view when focused will be assigned to
+     * UrlBar. When the UrlBar is the first visible view when focused, the StatusView's alpha
+     * will be set to 0 in LocationBarPhone#populateFadeAnimations. When transitioning back from
+     * incognito, StatusView's state needs to be reset to match the current state of the status view
+     * {@link org.chromium.chrome.browser.omnibox.LocationBarPhone#updateVisualsForState}.
+     * property model.
+     **/
+    private void reconcileVisualState(boolean showStatusIconWhenFocused) {
+        // State requirements:
+        // - The ToolbarDataProvider and views are not null.
+        // - The status icon will be shown when focused.
+        // - Incognito isn't active.
+        // - The search engine logo should be shown.
+        if (mToolbarDataProvider == null || mStatusView == null || getSecurityIconView() == null
+                || !showStatusIconWhenFocused || mToolbarDataProvider.isIncognito()
+                || !SearchEngineLogoUtils.shouldShowSearchEngineLogo(
+                        mToolbarDataProvider.isIncognito())) {
+            return;
+        }
+        View securityIconView = getSecurityIconView();
+
+        mStatusView.setAlpha(1f);
+        securityIconView.setAlpha(mModel.get(StatusProperties.STATUS_ICON_ALPHA));
+        securityIconView.setVisibility(
+                mModel.get(StatusProperties.SHOW_STATUS_ICON) ? View.VISIBLE : View.GONE);
+    }
+
+    /**
      * @return Width of the status icon including start/end margins.
      */
     public int getStatusIconWidth() {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java
index 5c83408..fc9932b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/StartupTabPreloaderUnitTest.java
@@ -159,12 +159,15 @@
 
     private StartupTabPreloader createStartupTabPreloader(
             Intent intent, TabCreatorManager tabCreatorManager) {
-        return new StartupTabPreloader(new Supplier<Intent>() {
-            @Override
-            public Intent get() {
-                return intent;
-            }
-        }, new ActivityLifecycleDispatcherImpl(), null, tabCreatorManager);
+        return new StartupTabPreloader(
+                new Supplier<Intent>() {
+                    @Override
+                    public Intent get() {
+                        return intent;
+                    }
+                },
+                new ActivityLifecycleDispatcherImpl(), null, tabCreatorManager,
+                new IntentHandler(null, null));
     }
 
     private static class ChromeTabCreatorManager implements TabCreatorManager {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
index f918dc9..407e000 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/omnibox/LocationBarLayoutTest.java
@@ -257,6 +257,26 @@
     @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
     @EnableFeatures(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO)
     @Feature({"OmniboxSearchEngineLogo"})
+    public void testOmniboxSearchEngineLogo_backAndForthFromIncognito() {
+        final LocationBarLayout locationBar = getLocationBar();
+        final View iconView = locationBar.getSecurityIconView();
+        updateSearchEngineLogoWithGoogle(locationBar);
+
+        loadUrlInNewTabAndUpdateModels(UrlConstants.NTP_URL, true);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { locationBar.updateVisualsForState(); });
+        loadUrlInNewTabAndUpdateModels(UrlConstants.ABOUT_URL, false);
+        TestThreadUtils.runOnUiThreadBlocking(() -> { locationBar.updateVisualsForState(); });
+
+        onView(withId(R.id.location_bar_status)).check((view, e) -> {
+            Assert.assertEquals(1.0f, view.getAlpha(), 0f);
+        });
+    }
+
+    @Test
+    @SmallTest
+    @Restriction(UiRestriction.RESTRICTION_TYPE_PHONE)
+    @EnableFeatures(ChromeFeatureList.OMNIBOX_SEARCH_ENGINE_LOGO)
+    @Feature({"OmniboxSearchEngineLogo"})
     public void testOmniboxSearchEngineLogo_unfocusedOnNTP() {
         final LocationBarLayout locationBar = getLocationBar();
         final View iconView = locationBar.getSecurityIconView();
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandlerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandlerTest.java
index 210ee20..dd72c01 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandlerTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/DelayedScreenLockIntentHandlerTest.java
@@ -9,6 +9,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
+import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -29,15 +30,18 @@
 @RunWith(LocalRobolectricTestRunner.class)
 @Config(manifest = Config.NONE)
 public class DelayedScreenLockIntentHandlerTest {
-    @Mock private Context mContextMock;
+    @Mock
+    private Context mApplicationContextMock;
+    @Mock
+    private Activity mActivityMock;
 
     private DelayedScreenLockIntentHandler mIntentHandler;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mIntentHandler = new DelayedScreenLockIntentHandler();
-        ContextUtils.initApplicationContextForTests(mContextMock);
+        mIntentHandler = new DelayedScreenLockIntentHandler(mActivityMock);
+        ContextUtils.initApplicationContextForTests(mApplicationContextMock);
     }
 
     @Test
@@ -46,11 +50,12 @@
         final Intent intent = new Intent(Intent.ACTION_USER_PRESENT);
 
         mIntentHandler.updateDeferredIntent(deferredIntent);
-        mIntentHandler.onReceive(mContextMock, intent);
+        mIntentHandler.onReceive(null, intent);
 
-        verify(mContextMock).registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
-        verify(mContextMock).startActivity(deferredIntent);
-        verify(mContextMock).unregisterReceiver(mIntentHandler);
+        verify(mApplicationContextMock)
+                .registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
+        verify(mActivityMock).startActivity(deferredIntent);
+        verify(mApplicationContextMock).unregisterReceiver(mIntentHandler);
     }
 
     @Test
@@ -59,12 +64,13 @@
         final Intent intent = new Intent(Intent.ACTION_USER_PRESENT);
 
         mIntentHandler.updateDeferredIntent(deferredIntent);
-        mIntentHandler.onReceive(mContextMock, intent);
-        mIntentHandler.onReceive(mContextMock, intent);
+        mIntentHandler.onReceive(null, intent);
+        mIntentHandler.onReceive(null, intent);
 
-        verify(mContextMock).registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
-        verify(mContextMock).startActivity(deferredIntent);
-        verify(mContextMock).unregisterReceiver(mIntentHandler);
+        verify(mApplicationContextMock)
+                .registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
+        verify(mActivityMock).startActivity(deferredIntent);
+        verify(mApplicationContextMock).unregisterReceiver(mIntentHandler);
     }
 
     @Test
@@ -75,24 +81,26 @@
 
         mIntentHandler.updateDeferredIntent(deferredIntent1);
         mIntentHandler.updateDeferredIntent(deferredIntent2);
-        mIntentHandler.onReceive(mContextMock, intent);
+        mIntentHandler.onReceive(null, intent);
 
-        verify(mContextMock).registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
-        verify(mContextMock).startActivity(deferredIntent2);
-        verify(mContextMock).unregisterReceiver(mIntentHandler);
+        verify(mApplicationContextMock)
+                .registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
+        verify(mActivityMock).startActivity(deferredIntent2);
+        verify(mApplicationContextMock).unregisterReceiver(mIntentHandler);
     }
 
     @Test
     public void testNonExpectedIntentAction() {
         mIntentHandler.updateDeferredIntent(new Intent());
         try {
-            mIntentHandler.onReceive(mContextMock, new Intent());
+            mIntentHandler.onReceive(null, new Intent());
         } catch (AssertionError assertError) {
             // Ignore AssertErrors
         }
 
-        verify(mContextMock).registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
-        verify(mContextMock, never()).startActivity(any(Intent.class));
-        verify(mContextMock, never()).unregisterReceiver(mIntentHandler);
+        verify(mApplicationContextMock)
+                .registerReceiver(eq(mIntentHandler), any(IntentFilter.class));
+        verify(mActivityMock, never()).startActivity(any(Intent.class));
+        verify(mApplicationContextMock, never()).unregisterReceiver(mIntentHandler);
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java
index a469e6d2..492d251 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/browserservices/TrustedWebActivityClientTest.java
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.browserservices;
 
+import static org.junit.Assert.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -13,10 +14,15 @@
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
+import android.content.pm.ResolveInfo;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.RemoteException;
 
+import androidx.browser.trusted.Token;
+import androidx.browser.trusted.TrustedWebActivityServiceConnection;
+import androidx.browser.trusted.TrustedWebActivityServiceConnectionPool;
+
 import com.google.common.util.concurrent.ListenableFuture;
 
 import org.junit.Before;
@@ -26,6 +32,7 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.stubbing.Answer;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.test.BaseRobolectricTestRunner;
@@ -34,14 +41,11 @@
 import org.chromium.chrome.browser.notifications.NotificationBuilderBase;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
-import androidx.browser.trusted.Token;
-import androidx.browser.trusted.TrustedWebActivityServiceConnection;
-import androidx.browser.trusted.TrustedWebActivityServiceConnectionPool;
-
 /**
  * Unit tests for {@link TrustedWebActivityClient}.
  */
@@ -152,4 +156,10 @@
         mClient.notifyNotification(uri, "tag", 1, mNotificationBuilder,
                 mNotificationUmaTracker);
     }
+
+    @Test
+    public void createLaunchIntentForTwaNonHttpScheme() {
+        assertNull(TrustedWebActivityClient.createLaunchIntentForTwa(RuntimeEnvironment.application,
+                "mailto:miranda@example.com", new ArrayList<ResolveInfo>()));
+    }
 }
diff --git a/chrome/app/os_settings_strings.grdp b/chrome/app/os_settings_strings.grdp
index 91fe605a..e90d6f1 100644
--- a/chrome/app/os_settings_strings.grdp
+++ b/chrome/app/os_settings_strings.grdp
@@ -2254,5 +2254,49 @@
   <message name="IDS_SETTINGS_ADD_FINGERPRINT_DIALOG_FINGER_IMMOBILE" desc="Warning text in the add fingerprint dialog to tell users they should move their finger slightly to capture different parts of the fingerprint.">
     Move slightly to capture a different part of the fingerprint.
   </message>
+
+  <!-- Date/Time (OS settings) -->
+  <message name="IDS_SETTINGS_DATE_TIME" desc="Name of the settings page which displays date and time preferences.">
+    Date and time
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_BUTTON" desc="Label for the button that opens time zone settings page.">
+    Time zone
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_SUBPAGE_TITLE" desc="The title of the dialog that allows user to control different time zone settings.">
+    Time zone
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_SET_AUTOMATICALLY" desc="The name for the radio button that turns automatic time zone detection on.">
+    Set automatically
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_CHOOSE_FROM_LIST" desc="The name for the radio button that turns automatic time zone detection off, and allows user to select time zone from the list of all supported time zones.">
+    Choose from list
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_DISABLED" desc="Display name for the automatic time zone detection mode which disables automatic detection.">
+    Automatic time zone detection is disabled
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_IP_ONLY_DEFAULT" desc="Display name for the automatic time zone detection mode which enables time zone detection sending unprecise location to Google servers. Default means that this is the default time zone detection mode.">
+    Use your IP address to determine location (default)
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_SEND_WIFI_AP" desc="Display name for the automatic time zone detection mode which enables time zone detection sending list of visible WiFi Access Point names to Google servers.">
+    Use only Wi-Fi to determine location
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_SEND_ALL_INFO" desc="Display name for the automatic time zone detection mode which enables time zone detection sending list of visible WiFi and Cellular Access Point names to Google servers.">
+    Use Wi-Fi or mobile networks to determine location
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE" desc="Label for the picker which allows users to choose their time zone.">
+    Time zone
+  </message>
+  <message name="IDS_SETTINGS_TIME_ZONE_GEOLOCATION" desc="Label for the checkbox which enables setting the time zone automatically with the detected location of the device.">
+    Set time zone automatically using your location
+  </message>
+  <message name="IDS_SETTINGS_SELECT_TIME_ZONE_RESOLVE_METHOD" desc="Label for the drop-down menu that allows user to select specific method to automatically resolve device time zone.">
+    Time zone detection method
+  </message>
+  <message name="IDS_SETTINGS_USE_24_HOUR_CLOCK" desc="Label for the checkbox which enables a 24-hour clock (as opposed to a 12-hour clock).">
+    Use 24-hour clock
+  </message>
+  <message name="IDS_SETTINGS_SET_DATE_TIME" desc="Label for the button that shows the dialog for setting the system date and time.">
+    Set date and time
+  </message>
 </if>
 </grit-part>
\ No newline at end of file
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index af5a249..19ee0b4 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -799,52 +799,6 @@
     </message>
   </if>
 
-  <!-- Date/Time Page -->
-  <if expr="chromeos">
-    <message name="IDS_SETTINGS_DATE_TIME" desc="Name of the settings page which displays date and time preferences.">
-      Date and time
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_BUTTON" desc="Label for the button that opens time zone settings page.">
-      Time zone
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_SUBPAGE_TITLE" desc="The title of the dialog that allows user to control different time zone settings.">
-      Time zone
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_SET_AUTOMATICALLY" desc="The name for the radio button that turns automatic time zone detection on.">
-      Set automatically
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_CHOOSE_FROM_LIST" desc="The name for the radio button that turns automatic time zone detection off, and allows user to select time zone from the list of all supported time zones.">
-      Choose from list
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_DISABLED" desc="Display name for the automatic time zone detection mode which disables automatic detection.">
-      Automatic time zone detection is disabled
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_IP_ONLY_DEFAULT" desc="Display name for the automatic time zone detection mode which enables time zone detection sending unprecise location to Google servers. Default means that this is the default time zone detection mode.">
-      Use your IP address to determine location (default)
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_SEND_WIFI_AP" desc="Display name for the automatic time zone detection mode which enables time zone detection sending list of visible WiFi Access Point names to Google servers.">
-      Use only Wi-Fi to determine location
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_DETECTION_MODE_SEND_ALL_INFO" desc="Display name for the automatic time zone detection mode which enables time zone detection sending list of visible WiFi and Cellular Access Point names to Google servers.">
-      Use Wi-Fi or mobile networks to determine location
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE" desc="Label for the picker which allows users to choose their time zone.">
-      Time zone
-    </message>
-    <message name="IDS_SETTINGS_TIME_ZONE_GEOLOCATION" desc="Label for the checkbox which enables setting the time zone automatically with the detected location of the device.">
-      Set time zone automatically using your location
-    </message>
-    <message name="IDS_SETTINGS_SELECT_TIME_ZONE_RESOLVE_METHOD" desc="Label for the drop-down menu that allows user to select specific method to automatically resolve device time zone.">
-      Time zone detection method
-    </message>
-    <message name="IDS_SETTINGS_USE_24_HOUR_CLOCK" desc="Label for the checkbox which enables a 24-hour clock (as opposed to a 12-hour clock).">
-      Use 24-hour clock
-    </message>
-    <message name="IDS_SETTINGS_SET_DATE_TIME" desc="Label for the button that shows the dialog for setting the system date and time.">
-      Set date and time
-    </message>
-  </if>
-
   <!-- Easy Unlock Page -->
   <if expr="chromeos">
     <message name="IDS_SETTINGS_EASY_UNLOCK_SETUP" desc="The label of the button to set up Easy Unlock section on the settings page.">
@@ -1959,6 +1913,9 @@
   <message name="IDS_SETTINGS_COOKIES_PAGE" desc="Title of the cookies settings page">
     Cookies
   </message>
+  <message name="IDS_SETTINGS_COOKIES_SITE_SPECIFIC_EXCEPTIONS" desc="Title of cookies page section for managing site specific cookie settings">
+    Manage site specific exceptions
+  </message>
   <message name="IDS_SETTINGS_SITE_SETTINGS_COOKIES" desc="Label for the cookies and site data site settings.">
     Cookies and site data
   </message>
diff --git a/chrome/browser/apps/app_service/app_launch_params.cc b/chrome/browser/apps/app_service/app_launch_params.cc
index 2bb4906..548409e 100644
--- a/chrome/browser/apps/app_service/app_launch_params.cc
+++ b/chrome/browser/apps/app_service/app_launch_params.cc
@@ -22,4 +22,31 @@
 
 AppLaunchParams::~AppLaunchParams() = default;
 
+AppLaunchParams CreateAppIdLaunchParamsWithEventFlags(
+    const std::string& app_id,
+    int event_flags,
+    apps::mojom::AppLaunchSource source,
+    int64_t display_id,
+    apps::mojom::LaunchContainer fallback_container) {
+  WindowOpenDisposition raw_disposition =
+      ui::DispositionFromEventFlags(event_flags);
+
+  apps::mojom::LaunchContainer container;
+  WindowOpenDisposition disposition;
+  if (raw_disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
+      raw_disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) {
+    container = apps::mojom::LaunchContainer::kLaunchContainerTab;
+    disposition = raw_disposition;
+  } else if (raw_disposition == WindowOpenDisposition::NEW_WINDOW) {
+    container = apps::mojom::LaunchContainer::kLaunchContainerWindow;
+    disposition = raw_disposition;
+  } else {
+    // Look at preference to find the right launch container.  If no preference
+    // is set, launch as a regular tab.
+    container = fallback_container;
+    disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
+  }
+  return AppLaunchParams(app_id, container, disposition, source, display_id);
+}
+
 }  // namespace apps
diff --git a/chrome/browser/apps/app_service/app_launch_params.h b/chrome/browser/apps/app_service/app_launch_params.h
index db658e5..3a7a3e9 100644
--- a/chrome/browser/apps/app_service/app_launch_params.h
+++ b/chrome/browser/apps/app_service/app_launch_params.h
@@ -74,6 +74,16 @@
   std::vector<base::FilePath> launch_files;
 };
 
+// Helper to create AppLaunchParams using event flags that allows user to
+// override the user-configured container using modifier keys. |display_id| is
+// the id of the display from which the app is launched.
+AppLaunchParams CreateAppIdLaunchParamsWithEventFlags(
+    const std::string& app_id,
+    int event_flags,
+    apps::mojom::AppLaunchSource source,
+    int64_t display_id,
+    apps::mojom::LaunchContainer fallback_container);
+
 }  // namespace apps
 
 #endif  // CHROME_BROWSER_APPS_APP_SERVICE_APP_LAUNCH_PARAMS_H_
diff --git a/chrome/browser/apps/app_service/web_apps.cc b/chrome/browser/apps/app_service/web_apps.cc
index 7608590..448a554 100644
--- a/chrome/browser/apps/app_service/web_apps.cc
+++ b/chrome/browser/apps/app_service/web_apps.cc
@@ -18,6 +18,7 @@
 #include "base/strings/string16.h"
 #include "base/strings/stringprintf.h"
 #include "chrome/browser/apps/app_service/app_icon_factory.h"
+#include "chrome/browser/apps/app_service/app_launch_params.h"
 #include "chrome/browser/apps/app_service/menu_util.h"
 #include "chrome/browser/apps/launch_service/launch_service.h"
 #include "chrome/browser/chromeos/arc/arc_util.h"
@@ -30,7 +31,6 @@
 #include "chrome/browser/ui/browser_finder.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/chrome_pages.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
 #include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
 #include "chrome/browser/ui/web_applications/web_app_ui_manager_impl.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
@@ -229,7 +229,7 @@
   web_app::DisplayMode display_mode =
       GetRegistrar().GetAppEffectiveDisplayMode(app_id);
 
-  AppLaunchParams params = CreateAppIdLaunchParamsWithEventFlags(
+  AppLaunchParams params = apps::CreateAppIdLaunchParamsWithEventFlags(
       web_app->app_id(), event_flags,
       apps::mojom::AppLaunchSource::kSourceAppLauncher, display_id,
       /*fallback_container=*/
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 85eb3a2..0d3a4c5 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -4291,11 +4291,14 @@
   std::string client_data_header;
   if (frame_tree_node_id != content::RenderFrameHost::kNoFrameTreeNodeId) {
     auto* web_contents = WebContents::FromFrameTreeNodeId(frame_tree_node_id);
-    auto* client_data_header_observer =
-        customtabs::ClientDataHeaderWebContentsObserver::FromWebContents(
-            web_contents);
-    if (client_data_header_observer)
-      client_data_header = client_data_header_observer->header();
+    // Could be null if the FrameTreeNode's RenderFrameHost is shutting down.
+    if (web_contents) {
+      auto* client_data_header_observer =
+          customtabs::ClientDataHeaderWebContentsObserver::FromWebContents(
+              web_contents);
+      if (client_data_header_observer)
+        client_data_header = client_data_header_observer->header();
+    }
   }
 #endif
 
diff --git a/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.cc b/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.cc
index f776ac2c..db3276f 100644
--- a/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.cc
+++ b/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.cc
@@ -5,7 +5,11 @@
 #include "chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.h"
 
 #include "chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h"
+#include "chrome/browser/chromeos/arc/accessibility/geometry_util.h"
 #include "components/exo/wm_helper.h"
+#include "ui/gfx/geometry/rect_f.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
 
 namespace arc {
 
@@ -16,33 +20,47 @@
 void AccessibilityInfoDataWrapper::Serialize(ui::AXNodeData* out_data) const {
   out_data->id = GetId();
   PopulateAXRole(out_data);
+  PopulateBounds(out_data);
+}
 
-  exo::WMHelper* wm_helper =
-      exo::WMHelper::HasInstance() ? exo::WMHelper::GetInstance() : nullptr;
+void AccessibilityInfoDataWrapper::PopulateBounds(
+    ui::AXNodeData* out_data) const {
+  AccessibilityInfoDataWrapper* root = tree_source_->GetRoot();
+  gfx::Rect info_data_bounds = GetBounds();
+  gfx::RectF& out_bounds = out_data->relative_bounds.bounds;
 
-  if (tree_source_->GetRoot() && wm_helper) {
-    // This is the computed bounds which relies upon the existence of an
-    // associated focused window and a root node.
-    aura::Window* active_window = (tree_source_->is_notification() ||
-                                   tree_source_->is_input_method_window())
-                                      ? nullptr
-                                      : wm_helper->GetActiveWindow();
-    const gfx::Rect& local_bounds = tree_source_->GetBounds(
-        tree_source_->GetFromId(GetId()), active_window);
-    out_data->relative_bounds.bounds.SetRect(local_bounds.x(), local_bounds.y(),
-                                             local_bounds.width(),
-                                             local_bounds.height());
+  if (root && exo::WMHelper::HasInstance()) {
+    if (tree_source_->is_notification() ||
+        tree_source_->is_input_method_window() || root->GetId() != GetId()) {
+      // By default, populate the bounds relative to the tree root.
+      const gfx::Rect& root_bounds = root->GetBounds();
+
+      info_data_bounds.Offset(-1 * root_bounds.x(), -1 * root_bounds.y());
+      out_bounds = ToChromeScale(info_data_bounds);
+      out_data->relative_bounds.offset_container_id = root->GetId();
+    } else {
+      // For the root node of application tree, populate the bounds to be
+      // relative to its container View.
+      views::Widget* widget = views::Widget::GetWidgetForNativeView(
+          exo::WMHelper::GetInstance()->GetActiveWindow());
+      DCHECK(widget);
+      DCHECK(widget->widget_delegate());
+      DCHECK(widget->widget_delegate()->GetContentsView());
+      const gfx::Rect& root_bounds =
+          widget->widget_delegate()->GetContentsView()->GetBoundsInScreen();
+
+      out_bounds = ToChromeBounds(info_data_bounds, widget);
+      out_bounds.Offset(-1 * root_bounds.x(), -1 * root_bounds.y());
+    }
+
+    // |out_bounds| is in Chrome DPI here. As ARC is considered the same as web
+    // in Chrome automation, scale the bounds by device scale factor.
+    out_bounds.Scale(tree_source_->device_scale_factor());
   } else {
     // We cannot compute global bounds, so use the raw bounds.
-    const auto& bounds = GetBounds();
-    out_data->relative_bounds.bounds.SetRect(bounds.x(), bounds.y(),
-                                             bounds.width(), bounds.height());
+    out_bounds.SetRect(info_data_bounds.x(), info_data_bounds.y(),
+                       info_data_bounds.width(), info_data_bounds.height());
   }
-
-  // TODO(hirokisato): Try using offset_container_id to make bounds calculations
-  // more efficient. If this is the child of the root, set the
-  // offset_container_id to be the root. Otherwise, set it to the first node
-  // child of the root.
 }
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.h b/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.h
index 4b40b7e..24cd7d4 100644
--- a/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.h
+++ b/chrome/browser/chromeos/arc/accessibility/accessibility_info_data_wrapper.h
@@ -43,6 +43,14 @@
 
  protected:
   AXTreeSourceArc* tree_source_;
+
+ private:
+  // Populate bounds of a node which can be passed to AXNodeData.location.
+  // Bounds are returned in the following coordinates depending on whether it's
+  // root or not.
+  // - Root node is relative to its container, i.e. focused window.
+  // - Non-root node is relative to the root node of this tree.
+  void PopulateBounds(ui::AXNodeData* out_data) const;
 };
 
 }  // namespace arc
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc
index 8562c5e7..fa1a11a 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.cc
@@ -46,6 +46,12 @@
 
 namespace {
 
+float DeviceScaleFactorFromWindow(aura::Window* window) {
+  if (!window || !window->GetToplevelWindow())
+    return 1.0;
+  return window->GetToplevelWindow()->layer()->device_scale_factor();
+}
+
 void DispatchFocusChange(arc::mojom::AccessibilityNodeInfoData* node_data,
                          Profile* profile) {
   chromeos::AccessibilityManager* accessibility_manager =
@@ -388,16 +394,25 @@
   auto key = KeyForNotification(notification_key);
   switch (state) {
     case arc::mojom::AccessibilityNotificationStateType::SURFACE_CREATED: {
-      if (GetFromKey(key))
-        return;
+      aura::Window* window = nullptr;
+      auto* surface_manager = ArcNotificationSurfaceManager::Get();
+      if (surface_manager) {
+        ArcNotificationSurface* surface =
+            surface_manager->GetArcSurface(notification_key);
+        if (surface)
+          window = surface->GetWindow();
+      }
 
-      AXTreeSourceArc* tree_source = CreateFromKey(std::move(key));
-      ui::AXTreeData tree_data;
-      if (!tree_source->GetTreeData(&tree_data)) {
-        NOTREACHED();
+      AXTreeSourceArc* tree_source = GetFromKey(key);
+      if (tree_source) {
+        tree_source->set_device_scale_factor(
+            DeviceScaleFactorFromWindow(window));
         return;
       }
-      UpdateTreeIdOfNotificationSurface(notification_key, tree_data.tree_id);
+
+      tree_source = CreateFromKey(std::move(key), window);
+      UpdateTreeIdOfNotificationSurface(notification_key,
+                                        tree_source->ax_tree_id());
       break;
     }
     case arc::mojom::AccessibilityNotificationStateType::SURFACE_REMOVED:
@@ -499,11 +514,9 @@
   if (!tree)
     return;
 
-  ui::AXTreeData tree_data;
-  if (!tree->GetTreeData(&tree_data))
-    return;
-
-  surface->SetAXTreeId(tree_data.tree_id);
+  surface->SetAXTreeId(tree->ax_tree_id());
+  tree->set_device_scale_factor(
+      DeviceScaleFactorFromWindow(surface->GetWindow()));
 
   // Dispatch ax::mojom::Event::kChildrenChanged to force AXNodeData of the
   // notification updated. As order of OnNotificationSurfaceAdded call is not
@@ -624,7 +637,7 @@
     return base::nullopt;
 
   gfx::RectF rect_f = arc::ToChromeScale(*result_rect);
-  arc::ScaleDeviceFactor(rect_f, active_window->GetToplevelWindow());
+  rect_f.Scale(DeviceScaleFactorFromWindow(active_window));
   return gfx::ToEnclosingRect(rect_f);
 }
 
@@ -716,7 +729,7 @@
     TreeKey key = KeyForTaskId(task_id);
     AXTreeSourceArc* tree = GetFromKey(key);
     if (!tree)
-      tree = CreateFromKey(std::move(key));
+      tree = CreateFromKey(std::move(key), window);
 
     // Just after the creation of window, widget has not been set yet and this
     // is not dispatched to ShellSurfaceBase. Thus, call this every time.
@@ -804,10 +817,9 @@
       return;
 
     if (!trees_.count(KeyForInputMethod())) {
-      auto* tree = CreateFromKey(KeyForInputMethod());
-      ui::AXTreeData tree_data;
-      tree->GetTreeData(&tree_data);
-      input_method_surface->SetChildAxTreeId(tree_data.tree_id);
+      auto* tree = CreateFromKey(KeyForInputMethod(),
+                                 input_method_surface->host_window());
+      input_method_surface->SetChildAxTreeId(tree->ax_tree_id());
     }
 
     tree_source = GetFromKey(KeyForInputMethod());
@@ -832,8 +844,11 @@
     tree_source = GetFromKey(key);
 
     if (!tree_source) {
-      tree_source = CreateFromKey(key);
+      tree_source = CreateFromKey(key, active_window);
       SetChildAxTreeIDForWindow(active_window, tree_source->ax_tree_id());
+    } else {
+      tree_source->set_device_scale_factor(
+          DeviceScaleFactorFromWindow(active_window));
     }
   }
 
@@ -884,8 +899,11 @@
   GetEventRouter()->BroadcastEvent(std::move(event));
 }
 
-AXTreeSourceArc* ArcAccessibilityHelperBridge::CreateFromKey(TreeKey key) {
-  auto tree = std::make_unique<AXTreeSourceArc>(this);
+AXTreeSourceArc* ArcAccessibilityHelperBridge::CreateFromKey(
+    TreeKey key,
+    aura::Window* window) {
+  auto tree = std::make_unique<AXTreeSourceArc>(
+      this, DeviceScaleFactorFromWindow(window));
   auto* tree_ptr = tree.get();
   trees_.insert(std::make_pair(std::move(key), std::move(tree)));
   return tree_ptr;
diff --git a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h
index 14759431..f76a79f5 100644
--- a/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h
+++ b/chrome/browser/chromeos/arc/accessibility/arc_accessibility_helper_bridge.h
@@ -158,7 +158,7 @@
 
   void DispatchCustomSpokenFeedbackToggled(bool enabled) const;
 
-  AXTreeSourceArc* CreateFromKey(TreeKey);
+  AXTreeSourceArc* CreateFromKey(TreeKey, aura::Window* window);
   AXTreeSourceArc* GetFromKey(const TreeKey&);
   AXTreeSourceArc* GetFromTreeId(ui::AXTreeID tree_id) const;
 
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
index 7d96ea2..00cff8f2 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.cc
@@ -18,13 +18,7 @@
 #include "extensions/browser/api/automation_internal/automation_event_router.h"
 #include "extensions/common/extension_messages.h"
 #include "ui/accessibility/ax_enums.mojom.h"
-#include "ui/accessibility/platform/ax_android_constants.h"
-#include "ui/aura/window.h"
-#include "ui/gfx/geometry/rect_conversions.h"
-#include "ui/gfx/geometry/rect_f.h"
-#include "ui/views/view.h"
-#include "ui/views/widget/widget.h"
-#include "ui/views/widget/widget_delegate.h"
+#include "ui/gfx/geometry/rect.h"
 
 namespace arc {
 
@@ -53,8 +47,9 @@
 }
 }  // namespace
 
-AXTreeSourceArc::AXTreeSourceArc(Delegate* delegate)
-    : current_tree_serializer_(new AXTreeArcSerializer(this)),
+AXTreeSourceArc::AXTreeSourceArc(Delegate* delegate, float device_scale_factor)
+    : device_scale_factor_(device_scale_factor),
+      current_tree_serializer_(new AXTreeArcSerializer(this)),
       is_notification_(false),
       is_input_method_window_(false),
       delegate_(delegate) {}
@@ -221,12 +216,17 @@
 
   event_bundle.updates.emplace_back();
 
-  // Force the tree, starting at the target of the event, to update, so
-  // unignored fields get updated.
-  event_bundle.updates[0].node_id_to_clear = event_data->source_id;
-  current_tree_serializer_->InvalidateSubtree(GetFromId(event_data->source_id));
+  // Force the tree, to update, so unignored fields get updated.
+  // On event type of WINDOW_STATE_CHANGED, update the entire tree so that
+  // window location is correctly calculated.
+  int32_t node_id_to_clear =
+      (event_data->event_type == AXEventType::WINDOW_STATE_CHANGED)
+          ? *root_id_
+          : event_data->source_id;
+  event_bundle.updates[0].node_id_to_clear = node_id_to_clear;
+  current_tree_serializer_->InvalidateSubtree(GetFromId(node_id_to_clear));
 
-  current_tree_serializer_->SerializeChanges(GetFromId(event_data->source_id),
+  current_tree_serializer_->SerializeChanges(GetFromId(node_id_to_clear),
                                              &event_bundle.updates.back());
 
   GetAutomationEventRouter()->DispatchAccessibilityEvents(event_bundle);
@@ -251,48 +251,6 @@
   chrome_focused_bounds_ = node->GetBounds();
 }
 
-const gfx::Rect AXTreeSourceArc::GetBounds(
-    AccessibilityInfoDataWrapper* info_data,
-    aura::Window* active_window) const {
-  DCHECK(root_id_.has_value());
-
-  gfx::Rect info_data_bounds = info_data->GetBounds();
-
-  if (!active_window) {
-    const gfx::Rect root_bounds = GetRoot()->GetBounds();
-    info_data_bounds.Offset(-1 * root_bounds.x(), -1 * root_bounds.y());
-    return info_data_bounds;
-  }
-
-  // By default, the node bounds is relative to the tree root.
-  if (info_data->GetId() != root_id_) {
-    const gfx::Rect root_bounds = GetRoot()->GetBounds();
-    info_data_bounds.Offset(-1 * root_bounds.x(), -1 * root_bounds.y());
-
-    gfx::RectF info_data_bounds_f = ToChromeScale(info_data_bounds);
-    arc::ScaleDeviceFactor(info_data_bounds_f,
-                           active_window->GetToplevelWindow());
-    return gfx::ToEnclosingRect(info_data_bounds_f);
-  }
-
-  // For the root node, the node bounds is relative to its container View.
-  views::Widget* widget = views::Widget::GetWidgetForNativeView(active_window);
-  DCHECK(widget);
-
-  gfx::RectF info_data_bounds_f = arc::ToChromeBounds(info_data_bounds, widget);
-
-  DCHECK(widget->widget_delegate());
-  DCHECK(widget->widget_delegate()->GetContentsView());
-  const gfx::Rect root_bounds =
-      widget->widget_delegate()->GetContentsView()->GetBoundsInScreen();
-
-  info_data_bounds_f.Offset(-1 * root_bounds.x(), -1 * root_bounds.y());
-
-  arc::ScaleDeviceFactor(info_data_bounds_f,
-                         active_window->GetToplevelWindow());
-  return gfx::ToEnclosingRect(info_data_bounds_f);
-}
-
 void AXTreeSourceArc::InvalidateTree() {
   current_tree_serializer_->Reset();
 }
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h
index 1f6166b..7c9a304 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc.h
@@ -21,10 +21,6 @@
 #include "ui/accessibility/ax_tree_source.h"
 #include "ui/views/view.h"
 
-namespace aura {
-class Window;
-}
-
 namespace ui {
 struct AXEvent;
 }
@@ -47,7 +43,7 @@
     virtual void OnAction(const ui::AXActionData& data) const = 0;
   };
 
-  explicit AXTreeSourceArc(Delegate* delegate);
+  AXTreeSourceArc(Delegate* delegate, float device_scale_factor);
   ~AXTreeSourceArc() override;
 
   // Notify automation of an accessibility event.
@@ -63,16 +59,6 @@
   // Update Chrome's accessibility focused node by id.
   void UpdateAccessibilityFocusLocation(int32_t id);
 
-  // Returns bounds of a node which can be passed to AXNodeData.location. Bounds
-  // are returned in the following coordinates depending on whether it's root or
-  // not.
-  // - Root node is relative to its container, i.e. focused window.
-  // - Non-root node is relative to the root node of this tree.
-  //
-  // focused_window is nullptr for notification.
-  const gfx::Rect GetBounds(AccessibilityInfoDataWrapper* info_data,
-                            aura::Window* focused_window) const;
-
   // Invalidates the tree serializer.
   void InvalidateTree();
 
@@ -89,6 +75,9 @@
   void SerializeNode(AccessibilityInfoDataWrapper* info_data,
                      ui::AXNodeData* out_data) const override;
 
+  float device_scale_factor() const { return device_scale_factor_; }
+  void set_device_scale_factor(float dsf) { device_scale_factor_ = dsf; }
+
   bool is_notification() { return is_notification_; }
 
   bool is_input_method_window() { return is_input_method_window_; }
@@ -166,6 +155,9 @@
   // Maps an AccessibilityInfoDataWrapper ID to its tree data.
   std::map<int32_t, std::unique_ptr<AccessibilityInfoDataWrapper>> tree_map_;
 
+  // The device scale factor of the display which the window is on.
+  float device_scale_factor_;
+
   // Maps an AccessibilityInfoDataWrapper ID to its parent.
   std::map<int32_t, int32_t> parent_map_;
 
diff --git a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc
index 83ac4204..dd2520aef 100644
--- a/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc
+++ b/chrome/browser/chromeos/arc/accessibility/ax_tree_source_arc_unittest.cc
@@ -142,7 +142,7 @@
    public:
     TestAXTreeSourceArc(AXTreeSourceArc::Delegate* delegate,
                         MockAutomationEventRouter* router)
-        : AXTreeSourceArc(delegate), router_(router) {}
+        : AXTreeSourceArc(delegate, 1.0), router_(router) {}
 
    private:
     extensions::AutomationEventRouterInterface* GetAutomationEventRouter()
diff --git a/chrome/browser/chromeos/arc/accessibility/geometry_util.cc b/chrome/browser/chromeos/arc/accessibility/geometry_util.cc
index 2df0fd3..28bcd99 100644
--- a/chrome/browser/chromeos/arc/accessibility/geometry_util.cc
+++ b/chrome/browser/chromeos/arc/accessibility/geometry_util.cc
@@ -35,11 +35,6 @@
 
   return chrome_bounds;
 }
-
-void ScaleDeviceFactor(gfx::RectF& bounds, aura::Window* toplevel_window) {
-  DCHECK(toplevel_window);
-  bounds.Scale(toplevel_window->layer()->device_scale_factor());
-}
 }  // namespace arc
 
 #endif  // CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_GEOMETRY_UTIL_H_
diff --git a/chrome/browser/chromeos/arc/accessibility/geometry_util.h b/chrome/browser/chromeos/arc/accessibility/geometry_util.h
index e3c332a..a74a456 100644
--- a/chrome/browser/chromeos/arc/accessibility/geometry_util.h
+++ b/chrome/browser/chromeos/arc/accessibility/geometry_util.h
@@ -7,10 +7,6 @@
 
 // TODO(hirokisato) support multiple display.
 
-namespace aura {
-class Window;
-}
-
 namespace gfx {
 class RectF;
 }
@@ -27,9 +23,6 @@
 // Given ARC pixels in screen coordinate, returns DIPs in Chrome OS main
 // display. This function adjusts differences between ARC and Chrome.
 gfx::RectF ToChromeBounds(const gfx::Rect& rect, views::Widget* widget);
-
-// Given DIPs in Chrome OS main display, scales it into pixels.
-void ScaleDeviceFactor(gfx::RectF& rect, aura::Window* toplevel_window);
 }  // namespace arc
 
 #endif  // CHROME_BROWSER_CHROMEOS_ARC_ACCESSIBILITY_GEOMETRY_UTIL_H_
diff --git a/chrome/browser/chromeos/crostini/crostini_upgrader.cc b/chrome/browser/chromeos/crostini/crostini_upgrader.cc
index f1c97bf..e389437 100644
--- a/chrome/browser/chromeos/crostini/crostini_upgrader.cc
+++ b/chrome/browser/chromeos/crostini/crostini_upgrader.cc
@@ -181,7 +181,7 @@
   }
   backup_path_ = backup_path;
   for (auto& observer : upgrader_observers_) {
-    observer.OnBackupSucceeded();
+    observer.OnBackupSucceeded(!backup_path.has_value());
   }
 }
 
diff --git a/chrome/browser/chromeos/crostini/crostini_upgrader_ui_delegate.h b/chrome/browser/chromeos/crostini/crostini_upgrader_ui_delegate.h
index 7c7f23b8..6f47055 100644
--- a/chrome/browser/chromeos/crostini/crostini_upgrader_ui_delegate.h
+++ b/chrome/browser/chromeos/crostini/crostini_upgrader_ui_delegate.h
@@ -20,7 +20,7 @@
 class CrostiniUpgraderUIObserver {
  public:
   virtual void OnBackupProgress(int percent) = 0;
-  virtual void OnBackupSucceeded() = 0;
+  virtual void OnBackupSucceeded(bool was_cancelled) = 0;
   virtual void OnBackupFailed() = 0;
   virtual void PrecheckStatus(
       chromeos::crostini_upgrader::mojom::UpgradePrecheckStatus status) = 0;
diff --git a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
index fcf72f8..96aced2 100644
--- a/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
+++ b/chrome/browser/chromeos/login/oobe_interactive_ui_test.cc
@@ -762,23 +762,16 @@
   WaitForLoginDisplayHostShutdown();
 }
 
-// Disabled on *San bots since they time out.
-#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || \
-    defined(LEAK_SANITIZER)
-#define MAYBE_SimpleEndToEnd DISABLED_SimpleEndToEnd
-#else
-#define MAYBE_SimpleEndToEnd SimpleEndToEnd
-#endif
-
-// Note that this probably the largest test that is run on ChromeOS, and it
-// might be running close to time limits especially on instrumented builds.
-// As such it might sometimes cause flakiness.
-// Please do not disable it for whole ChromeOS, only for specific instrumented
-// bots. Another alternative is to increase respective multiplier in
-// base/test/test_timeouts.h.
-IN_PROC_BROWSER_TEST_P(OobeInteractiveUITest, MAYBE_SimpleEndToEnd) {
+// crbug.com/1054935: SimpleEndToEnd is flaky on ChromeOS.
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_P(OobeInteractiveUITest, DISABLED_SimpleEndToEnd) {
   SimpleEndToEnd();
 }
+#else
+IN_PROC_BROWSER_TEST_P(OobeInteractiveUITest, SimpleEndToEnd) {
+  SimpleEndToEnd();
+}
+#endif
 
 INSTANTIATE_TEST_SUITE_P(
     OobeInteractiveUITestImpl,
@@ -835,21 +828,16 @@
   WaitForLoginDisplayHostShutdown();
 }
 
-// crbug.com/997987. Disabled on MSAN since they time out.
+// crbug.com/997987. Disabled on MSAN since they time out. crbug.com/1004327
+// crbug.com/1054935: EndToEnd is flaky on ChromeOS.
 // crbug.com/1055853: EndToEnd is flaky on Linux Chromium OS ASan LSan
-#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER) || \
-    defined(LEAK_SANITIZER)
+#if defined(MEMORY_SANITIZER) || defined(OS_CHROMEOS) || \
+    defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER)
 #define MAYBE_EndToEnd DISABLED_EndToEnd
 #else
 #define MAYBE_EndToEnd EndToEnd
 #endif
 
-// Note that this probably the largest test that is run on ChromeOS, and it
-// might be running close to time limits especially on instrumented builds.
-// As such it might sometimes cause flakiness.
-// Please do not disable it for whole ChromeOS, only for specific instrumented
-// bots. Another alternative is to increase respective multiplier in
-// base/test/test_timeouts.h.
 IN_PROC_BROWSER_TEST_P(OobeZeroTouchInteractiveUITest, MAYBE_EndToEnd) {
   ZeroTouchEndToEnd();
 }
diff --git a/chrome/browser/chromeos/login/screens/gesture_navigation_screen.cc b/chrome/browser/chromeos/login/screens/gesture_navigation_screen.cc
index 770c481..e880238 100644
--- a/chrome/browser/chromeos/login/screens/gesture_navigation_screen.cc
+++ b/chrome/browser/chromeos/login/screens/gesture_navigation_screen.cc
@@ -5,9 +5,12 @@
 #include "chrome/browser/chromeos/login/screens/gesture_navigation_screen.h"
 
 #include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/tablet_mode.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
 #include "chrome/browser/chromeos/login/users/chrome_user_manager_util.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "components/prefs/pref_service.h"
 
 namespace chromeos {
 
@@ -17,20 +20,6 @@
 
 }  // namespace
 
-// static
-bool GestureNavigationScreen::ShouldSkipGestureNavigationScreen() {
-  // TODO(mmourgos): If clamshell mode is enabled and device is detachable, then
-  // show the gesture navigation flow.
-
-  AccessibilityManager* accessibility_manager = AccessibilityManager::Get();
-  return (chrome_user_manager_util::IsPublicSessionOrEphemeralLogin() ||
-          !ash::features::IsHideShelfControlsInTabletModeEnabled() ||
-          !ash::TabletMode::Get()->InTabletMode() ||
-          accessibility_manager->IsSpokenFeedbackEnabled() ||
-          accessibility_manager->IsAutoclickEnabled() ||
-          accessibility_manager->IsSwitchAccessEnabled());
-}
-
 GestureNavigationScreen::GestureNavigationScreen(
     GestureNavigationScreenView* view,
     const base::RepeatingClosure& exit_callback)
@@ -47,7 +36,16 @@
 }
 
 void GestureNavigationScreen::ShowImpl() {
-  if (ShouldSkipGestureNavigationScreen()) {
+  // TODO(mmourgos): If clamshell mode is enabled and device is detachable, then
+  // show the gesture navigation flow.
+
+  AccessibilityManager* accessibility_manager = AccessibilityManager::Get();
+  if (chrome_user_manager_util::IsPublicSessionOrEphemeralLogin() ||
+      !ash::features::IsHideShelfControlsInTabletModeEnabled() ||
+      !ash::TabletMode::Get()->InTabletMode() ||
+      accessibility_manager->IsSpokenFeedbackEnabled() ||
+      accessibility_manager->IsAutoclickEnabled() ||
+      accessibility_manager->IsSwitchAccessEnabled()) {
     exit_callback_.Run();
     return;
   }
@@ -60,6 +58,10 @@
 
 void GestureNavigationScreen::OnUserAction(const std::string& action_id) {
   if (action_id == kUserActionExitPressed) {
+    // Make sure the user does not see a notification about the new gestures
+    // since they have already gone through this gesture education screen.
+    ProfileManager::GetActiveUserProfile()->GetPrefs()->SetBoolean(
+        ash::prefs::kGestureEducationNotificationShown, true);
     exit_callback_.Run();
   } else {
     BaseScreen::OnUserAction(action_id);
diff --git a/chrome/browser/chromeos/login/screens/gesture_navigation_screen.h b/chrome/browser/chromeos/login/screens/gesture_navigation_screen.h
index 85e8b23..76a39e1 100644
--- a/chrome/browser/chromeos/login/screens/gesture_navigation_screen.h
+++ b/chrome/browser/chromeos/login/screens/gesture_navigation_screen.h
@@ -23,9 +23,6 @@
   GestureNavigationScreen(const GestureNavigationScreen&) = delete;
   GestureNavigationScreen operator=(const GestureNavigationScreen&) = delete;
 
-  // Returns whether the gesture navigation screen should be shown.
-  static bool ShouldSkipGestureNavigationScreen();
-
   void set_exit_callback_for_testing(
       const base::RepeatingClosure& exit_callback) {
     exit_callback_ = exit_callback;
diff --git a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc
index d6280cd..f7361948 100644
--- a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc
+++ b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.cc
@@ -4,7 +4,11 @@
 
 #include "chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h"
 
+#include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/ash_pref_names.h"
 #include "ash/public/cpp/login_screen.h"
+#include "ash/public/cpp/tablet_mode.h"
+#include "base/bind.h"
 #include "base/command_line.h"
 #include "base/logging.h"
 #include "chrome/browser/chromeos/login/screens/gesture_navigation_screen.h"
@@ -64,17 +68,28 @@
 
 void MarketingOptInScreen::ShowImpl() {
   PrefService* prefs = ProfileManager::GetActiveUserProfile()->GetPrefs();
+
+  const bool did_skip_gesture_navigation_screen =
+      !prefs->GetBoolean(ash::prefs::kGestureEducationNotificationShown);
+
+  // Always skip the screen if it is a public session or non-regular ephemeral
+  // user login. Also skip the screen if clamshell mode is active.
+  // TODO(mmourgos): Enable this screen for clamshell mode.
+  if (chrome_user_manager_util::IsPublicSessionOrEphemeralLogin() ||
+      !ash::TabletMode::Get()->InTabletMode()) {
+    exit_callback_.Run();
+    return;
+  }
+
   // Skip the screen if:
   //   1) the feature is disabled, or
-  //   2) the screen has been shown for this user, or
-  //   3) it is public session or non-regular ephemeral user login.
+  //   2) the screen has been shown for this user
   //    AND
-  //   4) the gesture navigation screen was skipped.
+  //   3) the hide shelf controls in tablet mode feature is disabled.
   if ((!base::CommandLine::ForCurrentProcess()->HasSwitch(
            chromeos::switches::kEnableMarketingOptInScreen) ||
-       prefs->GetBoolean(prefs::kOobeMarketingOptInScreenFinished) ||
-       chrome_user_manager_util::IsPublicSessionOrEphemeralLogin()) &&
-      GestureNavigationScreen::ShouldSkipGestureNavigationScreen()) {
+       prefs->GetBoolean(prefs::kOobeMarketingOptInScreenFinished)) &&
+      !ash::features::IsHideShelfControlsInTabletModeEnabled()) {
     exit_callback_.Run();
     return;
   }
@@ -89,6 +104,25 @@
 
   // Make sure the screen next button visibility is properly initialized.
   view_->UpdateAllSetButtonVisibility(!handling_shelf_gestures_ /*visible*/);
+
+  // Only show the link for accessibility settings if the gesture navigation
+  // screen was shown. This button gets shown when the login shelf gesture
+  // gets enabled.
+  view_->UpdateA11ySettingsButtonVisibility(
+      !did_skip_gesture_navigation_screen || handling_shelf_gestures_);
+
+  view_->UpdateA11yShelfNavigationButtonToggle(prefs->GetBoolean(
+      ash::prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled));
+
+  // Observe the a11y shelf navigation buttons pref so the setting toggle in the
+  // screen can be updated if the pref value changes.
+  active_user_pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
+  active_user_pref_change_registrar_->Init(prefs);
+  active_user_pref_change_registrar_->Add(
+      ash::prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled,
+      base::BindRepeating(
+          &MarketingOptInScreen::OnA11yShelfNavigationButtonPrefChanged,
+          base::Unretained(this)));
 }
 
 void MarketingOptInScreen::HideImpl() {
@@ -102,6 +136,8 @@
   view_->Hide();
 
   ClearLoginShelfGestureHandler();
+
+  active_user_pref_change_registrar_.reset();
 }
 
 void MarketingOptInScreen::OnAllSet(bool play_communications_opt_in,
@@ -143,4 +179,10 @@
   ash::LoginScreen::Get()->ClearLoginShelfGestureHandler();
 }
 
+void MarketingOptInScreen::OnA11yShelfNavigationButtonPrefChanged() {
+  view_->UpdateA11yShelfNavigationButtonToggle(
+      ProfileManager::GetActiveUserProfile()->GetPrefs()->GetBoolean(
+          ash::prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled));
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h
index 60024ec..bfed287 100644
--- a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h
+++ b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h
@@ -11,6 +11,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/chromeos/login/screens/base_screen.h"
+#include "components/prefs/pref_change_registrar.h"
 
 namespace chromeos {
 
@@ -61,6 +62,8 @@
   // gestures.
   void ClearLoginShelfGestureHandler();
 
+  void OnA11yShelfNavigationButtonPrefChanged();
+
   MarketingOptInScreenView* const view_;
 
   // Whether the screen is shown and exit callback has not been run.
@@ -74,6 +77,8 @@
   ScopedObserver<ash::ShelfConfig, ash::ShelfConfig::Observer>
       shelf_config_observer_{this};
 
+  std::unique_ptr<PrefChangeRegistrar> active_user_pref_change_registrar_;
+
   base::WeakPtrFactory<MarketingOptInScreen> weak_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(MarketingOptInScreen);
diff --git a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc
index 2fe4902..0a7d689 100644
--- a/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc
+++ b/chrome/browser/chromeos/login/screens/marketing_opt_in_screen_browsertest.cc
@@ -114,19 +114,25 @@
   test::OobeJS().ExpectHiddenPath(
       {"marketing-opt-in", "marketing-opt-in-subtitle"});
   test::OobeJS().ExpectHiddenPath(
-      {"marketing-opt-in", "marketing-opt-in-toggles"});
+      {"marketing-opt-in", "marketing-opt-in-toggle-1"});
+  test::OobeJS().ExpectHiddenPath(
+      {"marketing-opt-in", "marketing-opt-in-toggle-2"});
 
   ash::ShellTestApi().SetTabletModeEnabledForTest(false);
   test::OobeJS().ExpectHiddenPath(
       {"marketing-opt-in", "marketing-opt-in-subtitle"});
   test::OobeJS().ExpectHiddenPath(
-      {"marketing-opt-in", "marketing-opt-in-toggles"});
+      {"marketing-opt-in", "marketing-opt-in-toggle-1"});
+  test::OobeJS().ExpectHiddenPath(
+      {"marketing-opt-in", "marketing-opt-in-toggle-2"});
 
   ash::ShellTestApi().SetTabletModeEnabledForTest(true);
   test::OobeJS().ExpectHiddenPath(
       {"marketing-opt-in", "marketing-opt-in-subtitle"});
   test::OobeJS().ExpectHiddenPath(
-      {"marketing-opt-in", "marketing-opt-in-toggles"});
+      {"marketing-opt-in", "marketing-opt-in-toggle-1"});
+  test::OobeJS().ExpectHiddenPath(
+      {"marketing-opt-in", "marketing-opt-in-toggle-2"});
 }
 
 // Tests that fling from shelf exits the screen in tablet mode.
diff --git a/chrome/browser/chromeos/release_notes/release_notes_storage.cc b/chrome/browser/chromeos/release_notes/release_notes_storage.cc
index cd0a3f44..ba8e1c86 100644
--- a/chrome/browser/chromeos/release_notes/release_notes_storage.cc
+++ b/chrome/browser/chromeos/release_notes/release_notes_storage.cc
@@ -50,17 +50,6 @@
           chromeos::features::kReleaseNotesNotification))
     return false;
 
-  // TODO: remove after fixing http://crbug.com/991767.
-  const base::CommandLine* current_command_line =
-      base::CommandLine::ForCurrentProcess();
-  const bool is_running_test =
-      current_command_line->HasSwitch(::switches::kTestName) ||
-      current_command_line->HasSwitch(::switches::kTestType);
-  if (is_running_test) {
-    DLOG(WARNING) << "Ignoring Release Notes Notification for test.";
-    return false;
-  }
-
   std::string user_email = profile_->GetProfileUserName();
   if (gaia::IsGoogleInternalAccountEmail(user_email) ||
       (ProfileHelper::Get()->GetUserByProfile(profile_)->HasGaiaAccount() &&
diff --git a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
index 09143fa..7e6f3e4f 100644
--- a/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/help_app_integration_browsertest.cc
@@ -25,16 +25,22 @@
 
 // Test that the Help App installs and launches correctly. Runs some spot
 // checks on the manifest.
-IN_PROC_BROWSER_TEST_F(HelpAppIntegrationTest, HelpAppV2) {
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2) {
   const GURL url(chromeos::kChromeUIHelpAppURL);
   EXPECT_NO_FATAL_FAILURE(
       ExpectSystemWebAppValid(web_app::SystemAppType::HELP, url, "Discover"));
 }
 
 // Test that the Help App is searchable by additional strings.
-IN_PROC_BROWSER_TEST_F(HelpAppIntegrationTest, HelpAppV2SearchInLauncher) {
+IN_PROC_BROWSER_TEST_P(HelpAppIntegrationTest, HelpAppV2SearchInLauncher) {
   WaitForSystemAppInstallAndLaunch(web_app::SystemAppType::HELP);
   EXPECT_EQ(
       std::vector<std::string>({"Get Help", "Perks", "Offers"}),
       GetManager().GetAdditionalSearchTerms(web_app::SystemAppType::HELP));
 }
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         HelpAppIntegrationTest,
+                         ::testing::Values(web_app::ProviderType::kBookmarkApps,
+                                           web_app::ProviderType::kWebApps),
+                         web_app::ProviderTypeParamToString);
diff --git a/chrome/browser/chromeos/web_applications/media_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/media_app_integration_browsertest.cc
index 75ac9330..12789c3 100644
--- a/chrome/browser/chromeos/web_applications/media_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/media_app_integration_browsertest.cc
@@ -161,7 +161,7 @@
 
 // Test that the Media App installs and launches correctly. Runs some spot
 // checks on the manifest.
-IN_PROC_BROWSER_TEST_F(MediaAppIntegrationTest, MediaApp) {
+IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, MediaApp) {
   const GURL url(chromeos::kChromeUIMediaAppURL);
   EXPECT_NO_FATAL_FAILURE(
       ExpectSystemWebAppValid(web_app::SystemAppType::MEDIA, url, "Media App"));
@@ -169,7 +169,7 @@
 
 // Test that the MediaApp successfully loads a file passed in on its launch
 // params.
-IN_PROC_BROWSER_TEST_F(MediaAppIntegrationTest, MediaAppLaunchWithFile) {
+IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, MediaAppLaunchWithFile) {
   WaitForTestSystemAppInstall();
   auto params = LaunchParamsForApp(web_app::SystemAppType::MEDIA);
 
@@ -193,7 +193,7 @@
 
 // Ensures that chrome://media-app is available as a file task for the ChromeOS
 // file manager and eligible for opening appropriate files / mime types.
-IN_PROC_BROWSER_TEST_F(MediaAppIntegrationTest, MediaAppEligibleOpenTask) {
+IN_PROC_BROWSER_TEST_P(MediaAppIntegrationTest, MediaAppEligibleOpenTask) {
   constexpr bool kIsDirectory = false;
   const extensions::EntryInfo image_entry(TestFile(kFilePng800x600),
                                           "image/png", kIsDirectory);
@@ -226,7 +226,7 @@
 // End-to-end test to ensure that the MediaApp successfully registers as a file
 // handler with the ChromeOS file manager on startup and acts as the default
 // handler for a given file.
-IN_PROC_BROWSER_TEST_F(MediaAppIntegrationWithFilesAppTest,
+IN_PROC_BROWSER_TEST_P(MediaAppIntegrationWithFilesAppTest,
                        FileOpenUsesMediaApp) {
   WaitForTestSystemAppInstall();
   Browser* test_browser = chrome::FindBrowserWithActiveWindow();
@@ -255,7 +255,7 @@
 
 // Test that the MediaApp can navigate other files in the directory of a file
 // that was opened.
-IN_PROC_BROWSER_TEST_F(MediaAppIntegrationWithFilesAppTest,
+IN_PROC_BROWSER_TEST_P(MediaAppIntegrationWithFilesAppTest,
                        FileOpenCanTraverseDirectory) {
   WaitForTestSystemAppInstall();
 
@@ -310,3 +310,15 @@
   EXPECT_EQ(true, ExecuteScript(web_ui, "advance(1)"));
   EXPECT_EQ("800x600", WaitForOpenedImage(web_ui));
 }
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         MediaAppIntegrationTest,
+                         ::testing::Values(web_app::ProviderType::kBookmarkApps,
+                                           web_app::ProviderType::kWebApps),
+                         web_app::ProviderTypeParamToString);
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         MediaAppIntegrationWithFilesAppTest,
+                         ::testing::Values(web_app::ProviderType::kBookmarkApps,
+                                           web_app::ProviderType::kWebApps),
+                         web_app::ProviderTypeParamToString);
diff --git a/chrome/browser/chromeos/web_applications/sample_system_web_app_integration_browsertest.cc b/chrome/browser/chromeos/web_applications/sample_system_web_app_integration_browsertest.cc
index c1f38ec..d22b825ba 100644
--- a/chrome/browser/chromeos/web_applications/sample_system_web_app_integration_browsertest.cc
+++ b/chrome/browser/chromeos/web_applications/sample_system_web_app_integration_browsertest.cc
@@ -11,8 +11,14 @@
 
 // Test that the Sample System Web App installs and launches correctly. Runs
 // some spot checks on the manifest.
-IN_PROC_BROWSER_TEST_F(SampleSystemWebAppIntegrationTest, SampleSystemWebApp) {
+IN_PROC_BROWSER_TEST_P(SampleSystemWebAppIntegrationTest, SampleSystemWebApp) {
   const GURL url(chromeos::kChromeUISampleSystemWebAppURL);
   EXPECT_NO_FATAL_FAILURE(ExpectSystemWebAppValid(
       web_app::SystemAppType::SAMPLE, url, "Sample System Web App"));
 }
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SampleSystemWebAppIntegrationTest,
+                         ::testing::Values(web_app::ProviderType::kBookmarkApps,
+                                           web_app::ProviderType::kWebApps),
+                         web_app::ProviderTypeParamToString);
diff --git a/chrome/browser/crash_recovery_browsertest.cc b/chrome/browser/crash_recovery_browsertest.cc
index 7cab695..6cb18b52 100644
--- a/chrome/browser/crash_recovery_browsertest.cc
+++ b/chrome/browser/crash_recovery_browsertest.cc
@@ -126,7 +126,14 @@
 }
 
 // Test that reload after a crash forces a cache revalidation.
-IN_PROC_BROWSER_TEST_F(CrashRecoveryBrowserTest, ReloadCacheRevalidate) {
+//
+// Flaky timeouts on Win7 Tests (dbg)(1); see https://crbug.com/985255.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_ReloadCacheRevalidate DISABLED_ReloadCacheRevalidate
+#else
+#define MAYBE_ReloadCacheRevalidate ReloadCacheRevalidate
+#endif
+IN_PROC_BROWSER_TEST_F(CrashRecoveryBrowserTest, MAYBE_ReloadCacheRevalidate) {
   const char kTestPath[] = "/test";
 
   // Use the test server so as not to bypass cache behavior. The title of the
@@ -188,7 +195,14 @@
 
 // Tests that reloads of navigation errors behave correctly after a crash.
 // Regression test for http://crbug.com/348918
-IN_PROC_BROWSER_TEST_F(CrashRecoveryBrowserTest, DoubleReloadWithError) {
+//
+// Flaky timeouts on Win7 Tests (dbg)(1); see https://crbug.com/985255.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_DoubleReloadWithError DISABLED_DoubleReloadWithError
+#else
+#define MAYBE_DoubleReloadWithError DoubleReloadWithError
+#endif
+IN_PROC_BROWSER_TEST_F(CrashRecoveryBrowserTest, MAYBE_DoubleReloadWithError) {
   GURL url(content::GetWebUIURL("bogus"));
   ui_test_utils::NavigateToURL(browser(), url);
   ASSERT_EQ(url, GetActiveWebContents()->GetVisibleURL());
@@ -206,7 +220,14 @@
 
 // Tests that a beforeunload handler doesn't run if user navigates to
 // chrome::crash.
-IN_PROC_BROWSER_TEST_F(CrashRecoveryBrowserTest, BeforeUnloadNotRun) {
+//
+// Flaky timeouts on Win7 Tests (dbg)(1); see https://crbug.com/985255.
+#if defined(OS_WIN) && !defined(NDEBUG)
+#define MAYBE_BeforeUnloadNotRun DISABLED_BeforeUnloadNotRun
+#else
+#define MAYBE_BeforeUnloadNotRun BeforeUnloadNotRun
+#endif
+IN_PROC_BROWSER_TEST_F(CrashRecoveryBrowserTest, MAYBE_BeforeUnloadNotRun) {
   const char* kBeforeUnloadHTML =
     "<html><body>"
     "<script>window.onbeforeunload=function(e){return 'foo'}</script>"
diff --git a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc
index 7606bc9..9ad9aefa 100644
--- a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc
+++ b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.cc
@@ -86,7 +86,8 @@
 // http://crbug.com/710371
 content::RenderProcessHost* WebrtcLoggingPrivateFunction::RphFromRequest(
     const api::webrtc_logging_private::RequestInfo& request,
-    const std::string& security_origin) {
+    const std::string& security_origin,
+    std::string* error) {
   // There are 2 ways these API functions can get called.
   //
   //  1. From a whitelisted component extension on behalf of a page with the
@@ -118,11 +119,11 @@
             GetSenderWebContents(),
             base::BindRepeating(get_guest, &guests_found, &target_host));
     if (!target_host) {
-      SetError("No webview render process found");
+      *error = "No webview render process found";
       return nullptr;
     }
     if (guests_found > 1) {
-      SetError("Multiple webviews found");
+      *error = "Multiple webviews found";
       return nullptr;
     }
     return target_host;
@@ -134,9 +135,9 @@
     content::RenderProcessHost* rph =
         content::RenderProcessHost::FromID(*request.guest_process_id);
     if (!rph) {
-      SetError(
+      *error =
           base::StringPrintf("Failed to get RPH fro guest proccess ID (%d).",
-                             *request.guest_process_id));
+                             *request.guest_process_id);
     }
     return rph;
   }
@@ -144,33 +145,34 @@
   // Otherwise, use the |tab_id|. If there's no |target_viewview|, no |tab_id|,
   // and no |guest_process_id|, we can't look up the RenderProcessHost.
   if (!request.tab_id.get()) {
-    SetError("No webview, tab ID, or guest process ID specified.");
+    *error = "No webview, tab ID, or guest process ID specified.";
     return nullptr;
   }
 
   int tab_id = *request.tab_id;
   content::WebContents* contents = nullptr;
-  if (!ExtensionTabUtil::GetTabById(tab_id, GetProfile(), true, &contents)) {
-    SetError(extensions::ErrorUtils::FormatErrorMessage(
+  if (!ExtensionTabUtil::GetTabById(tab_id, browser_context(), true,
+                                    &contents)) {
+    *error = extensions::ErrorUtils::FormatErrorMessage(
         extensions::tabs_constants::kTabNotFoundError,
-        base::NumberToString(tab_id)));
+        base::NumberToString(tab_id));
     return nullptr;
   }
   if (!contents) {
-    SetError("Web contents for tab not found.");
+    *error = "Web contents for tab not found.";
     return nullptr;
   }
   GURL expected_origin = contents->GetLastCommittedURL().GetOrigin();
   if (expected_origin.spec() != security_origin) {
-    SetError(base::StringPrintf(
+    *error = base::StringPrintf(
         "Invalid security origin. Expected=%s, actual=%s",
-        expected_origin.spec().c_str(), security_origin.c_str()));
+        expected_origin.spec().c_str(), security_origin.c_str());
     return nullptr;
   }
 
   content::RenderProcessHost* rph = contents->GetMainFrame()->GetProcess();
   if (!rph) {
-    SetError("Failed to get RPH.");
+    *error = "Failed to get RPH.";
   }
   return rph;
 }
@@ -178,10 +180,12 @@
 WebRtcLoggingController*
 WebrtcLoggingPrivateFunction::LoggingControllerFromRequest(
     const api::webrtc_logging_private::RequestInfo& request,
-    const std::string& security_origin) {
-  content::RenderProcessHost* host = RphFromRequest(request, security_origin);
+    const std::string& security_origin,
+    std::string* error) {
+  content::RenderProcessHost* host =
+      RphFromRequest(request, security_origin, error);
   if (!host) {
-    // SetError() will have been called by RphFromRequest().
+    DCHECK(!error->empty()) << "|error| must be set by RphFromRequest()";
     return nullptr;
   }
   return WebRtcLoggingController::FromRenderProcessHost(host);
@@ -191,18 +195,21 @@
 WebrtcLoggingPrivateFunctionWithGenericCallback::PrepareTask(
     const api::webrtc_logging_private::RequestInfo& request,
     const std::string& security_origin,
-    WebRtcLoggingController::GenericDoneCallback* callback) {
+    WebRtcLoggingController::GenericDoneCallback* callback,
+    std::string* error) {
   *callback = base::Bind(
       &WebrtcLoggingPrivateFunctionWithGenericCallback::FireCallback, this);
-  return LoggingControllerFromRequest(request, security_origin);
+  return LoggingControllerFromRequest(request, security_origin, error);
 }
 
 void WebrtcLoggingPrivateFunctionWithGenericCallback::FireCallback(
     bool success, const std::string& error_message) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  if (!success)
-    SetError(error_message);
-  SendResponse(success);
+  if (success) {
+    Respond(NoArguments());
+  } else {
+    Respond(Error(error_message));
+  }
 }
 
 void WebrtcLoggingPrivateFunctionWithUploadCallback::FireCallback(
@@ -212,18 +219,16 @@
   if (success) {
     api::webrtc_logging_private::UploadResult result;
     result.report_id = report_id;
-    SetResult(result.ToValue());
+    Respond(OneArgument(result.ToValue()));
   } else {
-    SetError(error_message);
+    Respond(Error(error_message));
   }
-  SendResponse(success);
 }
 
 void WebrtcLoggingPrivateFunctionWithRecordingDoneCallback::FireErrorCallback(
     const std::string& error_message) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  SetError(error_message);
-  SendResponse(false);
+  Respond(Error(error_message));
 }
 
 void WebrtcLoggingPrivateFunctionWithRecordingDoneCallback::FireCallback(
@@ -235,107 +240,108 @@
   result.prefix_path = prefix_path;
   result.did_stop = did_stop;
   result.did_manual_stop = did_manual_stop;
-  SetResult(result.ToValue());
-  SendResponse(true);
+  Respond(OneArgument(result.ToValue()));
 }
 
-bool WebrtcLoggingPrivateSetMetaDataFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateSetMetaDataFunction::Run() {
   std::unique_ptr<SetMetaData::Params> params(
       SetMetaData::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   WebRtcLoggingController::GenericDoneCallback callback;
+  std::string error;
   WebRtcLoggingController* webrtc_logging_controller =
-      PrepareTask(params->request, params->security_origin, &callback);
+      PrepareTask(params->request, params->security_origin, &callback, &error);
   if (!webrtc_logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   std::unique_ptr<WebRtcLogMetaDataMap> meta_data(new WebRtcLogMetaDataMap());
   for (const MetaDataEntry& entry : params->meta_data)
     (*meta_data)[entry.key] = entry.value;
 
   webrtc_logging_controller->SetMetaData(std::move(meta_data), callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStartFunction::RunAsync() {
+ExtensionFunction::ResponseAction WebrtcLoggingPrivateStartFunction::Run() {
   std::unique_ptr<Start::Params> params(Start::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   WebRtcLoggingController::GenericDoneCallback callback;
+  std::string error;
   WebRtcLoggingController* webrtc_logging_controller =
-      PrepareTask(params->request, params->security_origin, &callback);
+      PrepareTask(params->request, params->security_origin, &callback, &error);
   if (!webrtc_logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   webrtc_logging_controller->StartLogging(callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateSetUploadOnRenderCloseFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateSetUploadOnRenderCloseFunction::Run() {
   std::unique_ptr<SetUploadOnRenderClose::Params> params(
       SetUploadOnRenderClose::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
+  std::string error;
   WebRtcLoggingController* webrtc_logging_controller(
-      LoggingControllerFromRequest(params->request, params->security_origin));
+      LoggingControllerFromRequest(params->request, params->security_origin,
+                                   &error));
   if (!webrtc_logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   webrtc_logging_controller->set_upload_log_on_render_close(
       params->should_upload);
 
-  // Post a task since this is an asynchronous extension function.
-  // TODO(devlin): This is unnecessary; this should just be a
-  // ExtensionFunction. Fix this.
-  base::PostTask(
-      FROM_HERE, {BrowserThread::UI},
-      base::BindOnce(
-          &WebrtcLoggingPrivateSetUploadOnRenderCloseFunction::SendResponse,
-          this, true));
-  return true;
+  return RespondNow(NoArguments());
 }
 
-bool WebrtcLoggingPrivateStopFunction::RunAsync() {
+ExtensionFunction::ResponseAction WebrtcLoggingPrivateStopFunction::Run() {
   std::unique_ptr<Stop::Params> params(Stop::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   WebRtcLoggingController::GenericDoneCallback callback;
+  std::string error;
   WebRtcLoggingController* webrtc_logging_controller =
-      PrepareTask(params->request, params->security_origin, &callback);
+      PrepareTask(params->request, params->security_origin, &callback, &error);
   if (!webrtc_logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   webrtc_logging_controller->StopLogging(callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStoreFunction::RunAsync() {
+ExtensionFunction::ResponseAction WebrtcLoggingPrivateStoreFunction::Run() {
   std::unique_ptr<Store::Params> params(Store::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   WebRtcLoggingController::GenericDoneCallback callback;
+  std::string error;
   WebRtcLoggingController* webrtc_logging_controller =
-      PrepareTask(params->request, params->security_origin, &callback);
+      PrepareTask(params->request, params->security_origin, &callback, &error);
   if (!webrtc_logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   const std::string local_log_id(HashIdWithOrigin(params->security_origin,
                                                   params->log_id));
 
   webrtc_logging_controller->StoreLog(local_log_id, callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateUploadStoredFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateUploadStoredFunction::Run() {
   std::unique_ptr<UploadStored::Params> params(
       UploadStored::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  WebRtcLoggingController* logging_controller =
-      LoggingControllerFromRequest(params->request, params->security_origin);
+  std::string error;
+  WebRtcLoggingController* logging_controller = LoggingControllerFromRequest(
+      params->request, params->security_origin, &error);
   if (!logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   WebRtcLoggingController::UploadDoneCallback callback =
       base::Bind(&WebrtcLoggingPrivateUploadStoredFunction::FireCallback, this);
@@ -344,47 +350,50 @@
                                                   params->log_id));
 
   logging_controller->UploadStoredLog(local_log_id, callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateUploadFunction::RunAsync() {
+ExtensionFunction::ResponseAction WebrtcLoggingPrivateUploadFunction::Run() {
   std::unique_ptr<Upload::Params> params(Upload::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
-  WebRtcLoggingController* logging_controller =
-      LoggingControllerFromRequest(params->request, params->security_origin);
+  std::string error;
+  WebRtcLoggingController* logging_controller = LoggingControllerFromRequest(
+      params->request, params->security_origin, &error);
   if (!logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   WebRtcLoggingController::UploadDoneCallback callback =
       base::Bind(&WebrtcLoggingPrivateUploadFunction::FireCallback, this);
 
   logging_controller->UploadLog(callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateDiscardFunction::RunAsync() {
+ExtensionFunction::ResponseAction WebrtcLoggingPrivateDiscardFunction::Run() {
   std::unique_ptr<Discard::Params> params(Discard::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   WebRtcLoggingController::GenericDoneCallback callback;
+  std::string error;
   WebRtcLoggingController* webrtc_logging_controller =
-      PrepareTask(params->request, params->security_origin, &callback);
+      PrepareTask(params->request, params->security_origin, &callback, &error);
   if (!webrtc_logging_controller)
-    return false;
+    return RespondNow(Error(error));
 
   webrtc_logging_controller->DiscardLog(callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStartRtpDumpFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateStartRtpDumpFunction::Run() {
   std::unique_ptr<StartRtpDump::Params> params(
       StartRtpDump::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   if (!params->incoming && !params->outgoing) {
     FireCallback(false, "Either incoming or outgoing must be true.");
-    return true;
+    return AlreadyResponded();
   }
 
   RtpDumpType type =
@@ -392,11 +401,11 @@
           ? RTP_DUMP_BOTH
           : (params->incoming ? RTP_DUMP_INCOMING : RTP_DUMP_OUTGOING);
 
+  std::string error;
   content::RenderProcessHost* host =
-      RphFromRequest(params->request, params->security_origin);
+      RphFromRequest(params->request, params->security_origin, &error);
   if (!host) {
-    // SetError() will have been called by RphFromRequest().
-    return false;
+    return RespondNow(Error(error));
   }
 
   WebRtcLoggingController* webrtc_logging_controller =
@@ -406,17 +415,18 @@
       base::Bind(&WebrtcLoggingPrivateStartRtpDumpFunction::FireCallback, this);
 
   webrtc_logging_controller->StartRtpDump(type, callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStopRtpDumpFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateStopRtpDumpFunction::Run() {
   std::unique_ptr<StopRtpDump::Params> params(
       StopRtpDump::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
   if (!params->incoming && !params->outgoing) {
     FireCallback(false, "Either incoming or outgoing must be true.");
-    return true;
+    return AlreadyResponded();
   }
 
   RtpDumpType type =
@@ -424,11 +434,11 @@
           ? RTP_DUMP_BOTH
           : (params->incoming ? RTP_DUMP_INCOMING : RTP_DUMP_OUTGOING);
 
+  std::string error;
   content::RenderProcessHost* host =
-      RphFromRequest(params->request, params->security_origin);
+      RphFromRequest(params->request, params->security_origin, &error);
   if (!host) {
-    // SetError() will have been called by RphFromRequest().
-    return false;
+    return RespondNow(Error(error));
   }
 
   WebRtcLoggingController* webrtc_logging_controller =
@@ -438,12 +448,13 @@
       base::Bind(&WebrtcLoggingPrivateStopRtpDumpFunction::FireCallback, this);
 
   webrtc_logging_controller->StopRtpDump(type, callback);
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStartAudioDebugRecordingsFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateStartAudioDebugRecordingsFunction::Run() {
   if (!CanEnableAudioDebugRecordingsFromExtension(extension())) {
-    return false;
+    return RespondNow(Error(""));
   }
 
   std::unique_ptr<StartAudioDebugRecordings::Params> params(
@@ -452,14 +463,14 @@
 
   if (params->seconds < 0) {
     FireErrorCallback("seconds must be greater than or equal to 0");
-    return true;
+    return AlreadyResponded();
   }
 
+  std::string error;
   content::RenderProcessHost* host =
-      RphFromRequest(params->request, params->security_origin);
+      RphFromRequest(params->request, params->security_origin, &error);
   if (!host) {
-    // SetError() will have been called by RphFromRequest().
-    return false;
+    return RespondNow(Error(error));
   }
 
   scoped_refptr<AudioDebugRecordingsHandler> audio_debug_recordings_handler(
@@ -474,23 +485,24 @@
       base::Bind(&WebrtcLoggingPrivateStartAudioDebugRecordingsFunction::
                      FireErrorCallback,
                  this));
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStopAudioDebugRecordingsFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateStopAudioDebugRecordingsFunction::Run() {
   if (!CanEnableAudioDebugRecordingsFromExtension(extension())) {
-    return false;
+    return RespondNow(Error(""));
   }
 
   std::unique_ptr<StopAudioDebugRecordings::Params> params(
       StopAudioDebugRecordings::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
+  std::string error;
   content::RenderProcessHost* host =
-      RphFromRequest(params->request, params->security_origin);
+      RphFromRequest(params->request, params->security_origin, &error);
   if (!host) {
-    // SetError() will have been called by RphFromRequest().
-    return false;
+    return RespondNow(Error(error));
   }
 
   scoped_refptr<AudioDebugRecordingsHandler> audio_debug_recordings_handler(
@@ -505,26 +517,26 @@
       base::Bind(&WebrtcLoggingPrivateStopAudioDebugRecordingsFunction::
                      FireErrorCallback,
                  this));
-  return true;
+  return RespondLater();
 }
 
-bool WebrtcLoggingPrivateStartEventLoggingFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateStartEventLoggingFunction::Run() {
   std::unique_ptr<StartEventLogging::Params> params(
       StartEventLogging::Params::Create(*args_));
   EXTENSION_FUNCTION_VALIDATE(params.get());
 
+  std::string error;
   content::RenderProcessHost* host =
-      RphFromRequest(params->request, params->security_origin);
+      RphFromRequest(params->request, params->security_origin, &error);
   if (!host) {
-    // SetError() will have been called by RphFromRequest().
-    return false;
+    return RespondNow(Error(error));
   }
 
   WebRtcLoggingController* webrtc_logging_controller =
       WebRtcLoggingController::FromRenderProcessHost(host);
   if (!webrtc_logging_controller) {
-    SetError("WebRTC logging controller not found.");
-    return false;
+    return RespondNow(Error("WebRTC logging controller not found."));
   }
 
   WebRtcLoggingController::StartEventLoggingCallback callback =
@@ -534,7 +546,7 @@
   webrtc_logging_controller->StartEventLogging(
       params->session_id, params->max_log_size_bytes, params->output_period_ms,
       params->web_app_id, callback);
-  return true;
+  return RespondLater();
 }
 
 void WebrtcLoggingPrivateStartEventLoggingFunction::FireCallback(
@@ -547,16 +559,16 @@
     DCHECK(error_message.empty());
     api::webrtc_logging_private::StartEventLoggingResult result;
     result.log_id = log_id;
-    SetResult(result.ToValue());
+    Respond(OneArgument(result.ToValue()));
   } else {
     DCHECK(log_id.empty());
     DCHECK(!error_message.empty());
-    SetError(error_message);
+    Respond(Error(error_message));
   }
-  SendResponse(success);
 }
 
-bool WebrtcLoggingPrivateGetLogsDirectoryFunction::RunAsync() {
+ExtensionFunction::ResponseAction
+WebrtcLoggingPrivateGetLogsDirectoryFunction::Run() {
 #if defined(OS_LINUX) || defined(OS_CHROMEOS)
   // Unlike other WebrtcLoggingPrivate functions that take a RequestInfo object,
   // this function shouldn't be called by a component extension on behalf of
@@ -570,7 +582,7 @@
       WebRtcLoggingController::FromRenderProcessHost(host);
   if (!webrtc_logging_controller) {
     FireErrorCallback("WebRTC logging controller not found.");
-    return true;
+    return AlreadyResponded();
   }
 
   webrtc_logging_controller->GetLogsDirectory(
@@ -579,11 +591,9 @@
       base::Bind(
           &WebrtcLoggingPrivateGetLogsDirectoryFunction::FireErrorCallback,
           this));
-  return true;
+  return RespondLater();
 #else   // defined(OS_LINUX) || defined(OS_CHROMEOS)
-  SetError("Not supported on the current OS");
-  SendResponse(false);
-  return false;
+  return RespondNow(Error("Not supported on the current OS"));
 #endif  // defined(OS_LINUX) || defined(OS_CHROMEOS)
 }
 
@@ -600,8 +610,7 @@
 void WebrtcLoggingPrivateGetLogsDirectoryFunction::FireErrorCallback(
     const std::string& error_message) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  SetError(error_message);
-  SendResponse(false);
+  Respond(Error(error_message));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h
index 3ded513..e371b98 100644
--- a/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h
+++ b/chrome/browser/extensions/api/webrtc_logging_private/webrtc_logging_private_api.h
@@ -7,10 +7,10 @@
 
 #include <string>
 
-#include "chrome/browser/extensions/chrome_extension_function.h"
 #include "chrome/browser/media/webrtc/audio_debug_recordings_handler.h"
 #include "chrome/browser/media/webrtc/webrtc_logging_controller.h"
 #include "chrome/common/extensions/api/webrtc_logging_private.h"
+#include "extensions/browser/extension_function.h"
 #include "media/media_buildflags.h"
 
 namespace content {
@@ -21,20 +21,22 @@
 
 namespace extensions {
 
-class WebrtcLoggingPrivateFunction : public ChromeAsyncExtensionFunction {
+class WebrtcLoggingPrivateFunction : public ExtensionFunction {
  protected:
   ~WebrtcLoggingPrivateFunction() override {}
 
   // Returns the RenderProcessHost associated with the given |request|
-  // authorized by the |security_origin|. Returns null if unauthorized or
-  // the RPH does not exist.
+  // authorized by the |security_origin|. Returns null and sets |*error| to an
+  // appropriate error if unauthorized or the RPH does not exist.
   content::RenderProcessHost* RphFromRequest(
       const api::webrtc_logging_private::RequestInfo& request,
-      const std::string& security_origin);
+      const std::string& security_origin,
+      std::string* error);
 
   WebRtcLoggingController* LoggingControllerFromRequest(
       const api::webrtc_logging_private::RequestInfo& request,
-      const std::string& security_origin);
+      const std::string& security_origin,
+      std::string* error);
 };
 
 class WebrtcLoggingPrivateFunctionWithGenericCallback
@@ -45,11 +47,13 @@
   // Finds the appropriate logging controller for performing the task and
   // prepares a generic callback object for when the task is completed.  If the
   // logging controller can't be found for the given request+origin, the
-  // returned ptr will be null.
+  // returned ptr will be null and |*error| will be set to an appropriate error
+  // message.
   WebRtcLoggingController* PrepareTask(
       const api::webrtc_logging_private::RequestInfo& request,
       const std::string& security_origin,
-      WebRtcLoggingController::GenericDoneCallback* callback);
+      WebRtcLoggingController::GenericDoneCallback* callback,
+      std::string* error);
 
   // Must be called on UI thread.
   void FireCallback(bool success, const std::string& error_message);
@@ -88,7 +92,7 @@
   ~WebrtcLoggingPrivateSetMetaDataFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStartFunction
@@ -102,7 +106,7 @@
   ~WebrtcLoggingPrivateStartFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateSetUploadOnRenderCloseFunction
@@ -116,7 +120,7 @@
   ~WebrtcLoggingPrivateSetUploadOnRenderCloseFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStopFunction
@@ -130,7 +134,7 @@
   ~WebrtcLoggingPrivateStopFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStoreFunction
@@ -144,7 +148,7 @@
   ~WebrtcLoggingPrivateStoreFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateUploadStoredFunction
@@ -158,7 +162,7 @@
   ~WebrtcLoggingPrivateUploadStoredFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateUploadFunction
@@ -172,7 +176,7 @@
   ~WebrtcLoggingPrivateUploadFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateDiscardFunction
@@ -186,7 +190,7 @@
   ~WebrtcLoggingPrivateDiscardFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStartRtpDumpFunction
@@ -200,7 +204,7 @@
   ~WebrtcLoggingPrivateStartRtpDumpFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStopRtpDumpFunction
@@ -214,7 +218,7 @@
   ~WebrtcLoggingPrivateStopRtpDumpFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStartAudioDebugRecordingsFunction
@@ -228,7 +232,7 @@
   ~WebrtcLoggingPrivateStartAudioDebugRecordingsFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStopAudioDebugRecordingsFunction
@@ -242,7 +246,7 @@
   ~WebrtcLoggingPrivateStopAudioDebugRecordingsFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 };
 
 class WebrtcLoggingPrivateStartEventLoggingFunction
@@ -256,7 +260,7 @@
   ~WebrtcLoggingPrivateStartEventLoggingFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 
   // If |success|, |log_id| must hold the ID. Otherwise, |error_message| must
   // hold a non-empty error message.
@@ -277,7 +281,7 @@
   ~WebrtcLoggingPrivateGetLogsDirectoryFunction() override {}
 
   // ExtensionFunction overrides.
-  bool RunAsync() override;
+  ResponseAction Run() override;
 
   // Must be called on UI thread.
   void FireErrorCallback(const std::string& error_message);
diff --git a/chrome/browser/policy/configuration_policy_handler_list_factory.cc b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
index e7af1d0..d916f8d4 100644
--- a/chrome/browser/policy/configuration_policy_handler_list_factory.cc
+++ b/chrome/browser/policy/configuration_policy_handler_list_factory.cc
@@ -596,8 +596,8 @@
   { key::kDNSInterceptionChecksEnabled,
     prefs::kDNSInterceptionChecksEnabled,
     base::Value::Type::BOOLEAN },
-  { key::kAdvancedProtectionDeepScanningEnabled,
-    prefs::kAdvancedProtectionDeepScanningEnabled,
+  { key::kAdvancedProtectionExtraSecurityAllowed,
+    prefs::kAdvancedProtectionExtraSecurityAllowed,
     base::Value::Type::BOOLEAN },
 
 #if defined(OS_ANDROID)
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
index 7349280bb..1e5533d 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background.js
@@ -357,7 +357,7 @@
    * Open the options page in a new tab.
    */
   showOptionsPage() {
-    const optionsPage = {url: 'options/options.html'};
+    const optionsPage = {url: '/chromevox/options/options.html'};
     chrome.tabs.create(optionsPage);
   }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.html b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.html
index 8fcef085..64c89bd1b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.html
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/panel/panel.html
@@ -21,8 +21,8 @@
 <div id="main">
   <div hidden id="menus_title" class="i18n" msgid="menus_title"></div>
   <button id="menus_button" aria-labelledby="menus_title">
-    <img id="chromevox" src="/images/chromevox-19.png">
-    <img id="triangle" src="/images/triangle-6.png">
+    <img id="chromevox" src="/chromevox/images/chromevox-19.png">
+    <img id="triangle" src="/chromevox/images/triangle-6.png">
   </button>
   <div id="caption">
     <div id="speech-container">
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js b/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js
index 2c750cf..ab89c4e 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/menu_manager.js
@@ -519,15 +519,6 @@
       case SAConstants.MenuAction.MOVE_UP_ONE_LINE_OF_TEXT:
         TextNavigationManager.moveUpOneLine();
         return;
-      case SAConstants.MenuAction.CUT:
-        EventHelper.simulateKeyPress(EventHelper.KeyCode.X, {ctrl: true});
-        return;
-      case SAConstants.MenuAction.COPY:
-        EventHelper.simulateKeyPress(EventHelper.KeyCode.C, {ctrl: true});
-        return;
-      case SAConstants.MenuAction.PASTE:
-        EventHelper.simulateKeyPress(EventHelper.KeyCode.V, {ctrl: true});
-        return;
       case SAConstants.MenuAction.SELECT_START:
         TextNavigationManager.saveSelectStart();
         if (this.menuOriginNode_) {
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/editable_text_node.js b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/editable_text_node.js
index 7db9bf8..01fb5b59 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/editable_text_node.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/nodes/editable_text_node.js
@@ -60,6 +60,15 @@
       case SAConstants.MenuAction.DICTATION:
         chrome.accessibilityPrivate.toggleDictation();
         return SAConstants.ActionResponse.CLOSE_MENU;
+      case SAConstants.MenuAction.CUT:
+        EventHelper.simulateKeyPress(EventHelper.KeyCode.X, {ctrl: true});
+        return SAConstants.ActionResponse.REMAIN_OPEN;
+      case SAConstants.MenuAction.COPY:
+        EventHelper.simulateKeyPress(EventHelper.KeyCode.C, {ctrl: true});
+        return SAConstants.ActionResponse.REMAIN_OPEN;
+      case SAConstants.MenuAction.PASTE:
+        EventHelper.simulateKeyPress(EventHelper.KeyCode.V, {ctrl: true});
+        return SAConstants.ActionResponse.REMAIN_OPEN;
     }
     return super.performAction(action);
   }
diff --git a/chrome/browser/resources/chromeos/crostini_upgrader/app.js b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
index 5a245e6b..c3a4445 100644
--- a/chrome/browser/resources/chromeos/crostini_upgrader/app.js
+++ b/chrome/browser/resources/chromeos/crostini_upgrader/app.js
@@ -109,13 +109,13 @@
         assert(this.state_ === State.BACKUP);
         this.backupProgress_ = percent;
       }),
-      callbackRouter.onBackupSucceeded.addListener(() => {
+      callbackRouter.onBackupSucceeded.addListener((wasCancelled) => {
         assert(this.state_ === State.BACKUP);
         this.state_ = State.BACKUP_SUCCEEDED;
         // We do a short (2 second) interstitial display of the backup success
         // message before continuing the upgrade.
         var timeout = new Promise((resolve, reject) => {
-          setTimeout(resolve, 2000);
+          setTimeout(resolve, wasCancelled ? 0 : 2000);
         });
         // We also want to wait for the prechecks to finish.
         var callback = new Promise((resolve, reject) => {
diff --git a/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json b/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
index 16a9f3b3..a46f78e5 100644
--- a/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
+++ b/chrome/browser/resources/chromeos/input_method/google_xkb_manifest.json
@@ -11,6 +11,7 @@
     "app.window.alwaysOnTop",
     "app.window.ime",
     "audioCapture",
+    "crashReportPrivate",
     "https://clients4.google.com/",
     "https://dl.google.com/",
     "https://handwriting.googleapis.com/",
diff --git a/chrome/browser/resources/chromeos/login/marketing_opt_in.css b/chrome/browser/resources/chromeos/login/marketing_opt_in.css
index 834d90d1..82fc873 100644
--- a/chrome/browser/resources/chromeos/login/marketing_opt_in.css
+++ b/chrome/browser/resources/chromeos/login/marketing_opt_in.css
@@ -4,6 +4,7 @@
 
 :host {
   --marketing-opt-in-dialog-list-item-border: 1px solid var(--google-grey-200);
+  --oobe-a11y-dialog-list-item-border: 1px solid var(--google-grey-200);
 }
 
 .marketing-option {
@@ -23,3 +24,22 @@
   --iron-icon-fill-color: var(--google-blue-600);
   padding-inline-end: 16px;
 }
+
+#finalAccessibilityLink {
+  align-self: flex-end;
+}
+
+#finalAccessibilityLinkContainer {
+  display: flex;
+  height: 100%;
+  justify-content: center;
+}
+
+#finalAccessibilityPage oobe-a11y-option {
+  border-top: var(--oobe-a11y-dialog-list-item-border);
+  min-height: 64px;
+}
+
+#finalAccessibilityPage oobe-a11y-option:last-of-type {
+  border-bottom: var(--oobe-a11y-dialog-list-item-border);
+}
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/login/marketing_opt_in.html b/chrome/browser/resources/chromeos/login/marketing_opt_in.html
index be6037a16..70f30fa3 100644
--- a/chrome/browser/resources/chromeos/login/marketing_opt_in.html
+++ b/chrome/browser/resources/chromeos/login/marketing_opt_in.html
@@ -3,6 +3,27 @@
      found in the LICENSE file. -->
 
 <link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
+<link rel="import" href="chrome://resources/polymer/v1_0/iron-iconset-svg/iron-iconset-svg.html">
+
+<iron-iconset-svg name="marketing-opt-in-32" size="32">
+    <svg>
+        <defs>
+          <g id="accessibility" fill-rule="evenodd">
+            <path d="M16 2.667c1.467 0 2.667 1.184 2.667 2.633 0 1.448-1.2 2.632-2.667 2.632-1.467 0-2.667-1.184-2.667-2.632 0-1.45 1.2-2.633 2.667-2.633zm4 9.215v16.785h-3v-7.9h-2v7.9h-3V11.882H4V8.92h24v2.962h-8z"></path>
+          </g>
+        </defs>
+      </svg>
+</iron-iconset-svg>
+
+<iron-iconset-svg name="marketing-opt-in-64" size="64">
+    <svg>
+        <defs>
+          <g id="accessibility" fill-rule="evenodd">
+            <path d="M32 5.333c2.933 0 5.333 2.415 5.333 5.367 0 2.95-2.4 5.366-5.333 5.366-2.933 0-5.333-2.415-5.333-5.366 0-2.952 2.4-5.367 5.333-5.367zM40 24v35h-5V43h-6v16.006h-5V24H8v-4.915h48V24H40z"></path>
+          </g>
+        </defs>
+      </svg>
+</iron-iconset-svg>
 
 <dom-module id="marketing-opt-in">
   <template>
@@ -10,6 +31,7 @@
     <link rel="stylesheet" href="marketing_opt_in.css">
     <link rel="stylesheet" href="oobe_flex_layout.css">
     <oobe-dialog id="marketingOptInOverviewDialog" role="dialog" has-buttons
+        hidden="[[isAccessibilitySettingsShown_]]"
         title-key="marketingOptInScreenTitle"
         aria-label$="[[i18nDynamic(locale, 'marketingOptInScreenTitle')]]">
       <hd-iron-icon slot="oobe-icon" icon1x="oobe-32:checkmark"
@@ -19,9 +41,10 @@
           hidden="[[!marketingOptInEnabled_]]">
         [[i18nRecursive(locale, 'marketingOptInScreenSubtitle', 'productName')]]
       </div>
-      <div slot="footer" class="layout vertical" id="marketing-opt-in-toggles"
-          hidden="[[!marketingOptInEnabled_]]">
-        <div class="marketing-option layout horizontal center">
+      <div slot="footer" class="layout vertical flex">
+        <div class="marketing-option layout horizontal center"
+            hidden="[[!marketingOptInEnabled_]]"
+            id="marketing-opt-in-toggle-1">
           <hd-iron-icon icon1x="oobe-32:checkmark" icon2x="oobe-64:checkmark">
           </hd-iron-icon>
           <div id="playUpdatesOptionLabel" class="flex">
@@ -31,7 +54,9 @@
               aria-labelledby="usageStatsLabel">
           </cr-toggle>
         </div>
-        <div class="marketing-option layout horizontal center">
+        <div class="marketing-option layout horizontal center"
+            hidden="[[!marketingOptInEnabled_]]"
+            id="marketing-opt-in-toggle-2">
           <hd-iron-icon icon1x="oobe-32:checkmark" icon2x="oobe-64:checkmark">
           </hd-iron-icon>
           <div id="chromebookUpdatesOptionLabel" class="flex">
@@ -42,6 +67,13 @@
               aria-labelledby="chromebookUpdatesOption">
           </cr-toggle>
         </div>
+        <div hidden="[[!isA11ySettingsButtonVisible_]]"
+            id="finalAccessibilityLinkContainer">
+          <a class="oobe-local-link"
+              id="finalAccessibilityLink" on-tap="onToggleAccessibilityPage_">
+           [[i18nDynamic(locale, 'marketingOptInA11yLinkLabel')]]
+          </a>
+        </div>
       </div>
       <div slot="bottom-buttons" class="layout horizontal end-justified">
         <oobe-text-button on-tap="onAllSet_" class="focus-on-show" inverse
@@ -51,5 +83,36 @@
         </oobe-text-button>
       </div>
     </oobe-dialog>
+
+    <oobe-dialog id="finalAccessibilityPage" role="dialog" has-buttons
+        hidden="[[!isAccessibilitySettingsShown_]]"
+        title-key="finalA11yPageTitle"
+        aria-label$="[[i18nDynamic(locale, 'finalA11yPageTitle')]]">>
+      <hd-iron-icon slot="oobe-icon"
+          icon1x="marketing-opt-in-32:accessibility"
+          icon2x="marketing-opt-in-64:accessibility">
+      </hd-iron-icon>
+      <div slot="footer" class="layout vertical">
+        <oobe-a11y-option id="a11yNavButtonToggle"
+            on-change="onA11yNavButtonsSettingChanged_">
+          <span slot="title">
+            [[i18nDynamic(locale, 'finalA11yPageNavButtonSettingTitle')]]
+          </span>
+          <span slot="checked-value">
+            [[i18nDynamic(locale, 'finalA11yPageNavButtonSettingDescription')]]
+          </span>
+          <span slot="unchecked-value">
+            [[i18nDynamic(locale, 'finalA11yPageNavButtonSettingDescription')]]
+          </span>
+        </oobe-a11y-option>
+      </div>
+      <div slot="bottom-buttons" class="layout horizontal justified">
+        <oobe-back-button on-tap="onToggleAccessibilityPage_"
+            id="final-accessibility-back-button"></oobe-back-button>
+        <oobe-text-button on-tap="onAllSet_" class="focus-on-show" inverse
+            text-key="finalA11yPageDoneButtonTitle">
+        </oobe-text-button>
+      </div>
+    </oobe-dialog>
   </template>
 </dom-module>
diff --git a/chrome/browser/resources/chromeos/login/marketing_opt_in.js b/chrome/browser/resources/chromeos/login/marketing_opt_in.js
index 07ecf3d..90ac9e5c 100644
--- a/chrome/browser/resources/chromeos/login/marketing_opt_in.js
+++ b/chrome/browser/resources/chromeos/login/marketing_opt_in.js
@@ -16,6 +16,16 @@
       value: true,
     },
 
+    isAccessibilitySettingsShown_: {
+      type: Boolean,
+      value: false,
+    },
+
+    isA11ySettingsButtonVisible_: {
+      type: Boolean,
+      value: false,
+    },
+
     /**
      * Whether the marketing opt in toggles should be shown, which will be the
      * case only if marketing opt in feature is enabled.
@@ -36,6 +46,8 @@
   /** Overridden from LoginScreenBehavior. */
   EXTERNAL_API: [
     'updateAllSetButtonVisibility',
+    'updateA11ySettingsButtonVisibility',
+    'updateA11yNavigationButtonToggle',
   ],
 
   /** @override */
@@ -57,7 +69,46 @@
    * @param {boolean} visible Whether the all set button should be shown.
    */
   updateAllSetButtonVisibility(visible) {
-    // TODO(mmourgos): Update |this.allSetButtonVisible_| once the accessibility
-    // setting to show shelf buttons is added to screen.
+    this.allSetButtonVisible_ = visible;
+
+    // When showing the all set button, give the user a way to disable shelf
+    // gestures, and enabled "all set" button.
+    if(visible)
+      this.isA11ySettingsButtonVisible_ = true;
   },
+
+  /**
+   * @param {boolean} shown Whether the A11y Settings button should be shown.
+   */
+  updateA11ySettingsButtonVisibility(shown) {
+    this.isA11ySettingsButtonVisible_ = shown;
+  },
+
+  /**
+   * @param {boolean} enabled Whether the a11y setting for shownig shelf
+   * navigation buttons is enabled.
+   */
+  updateA11yNavigationButtonToggle(enabled) {
+    this.$.a11yNavButtonToggle.checked = enabled;
+  },
+
+  /**
+   * This is the 'on-tap' event handler for the accessibility settings link and
+   * for the back button on the accessibility page.
+   * @private
+   */
+  onToggleAccessibilityPage_() {
+    this.isAccessibilitySettingsShown_ = !this.isAccessibilitySettingsShown_;
+  },
+
+  /**
+   * The 'on-change' event handler for when the a11y navigation button setting
+   * is toggled on or off.
+   * @private
+   */
+  onA11yNavButtonsSettingChanged_() {
+    chrome.send('login.MarketingOptInScreen.setA11yNavigationButtonsEnabled', [
+      this.$.a11yNavButtonToggle.checked
+    ]);
+  }
 });
diff --git a/chrome/browser/resources/new_tab_page/customize_backgrounds.html b/chrome/browser/resources/new_tab_page/customize_backgrounds.html
index c7334ad9..8b36ccd 100644
--- a/chrome/browser/resources/new_tab_page/customize_backgrounds.html
+++ b/chrome/browser/resources/new_tab_page/customize_backgrounds.html
@@ -1,9 +1,12 @@
 <style>
-  #collections {
-    --ntp-grid-gap: 8px;
+  #container {
     padding: 4px;
   }
 
+  ntp-grid {
+    --ntp-grid-gap: 8px;
+  }
+
   .tile {
     cursor: pointer;
     outline-width: 0;
@@ -36,13 +39,24 @@
     min-height: 30px;
   }
 </style>
-<ntp-grid id="collections" columns="3">
-  <dom-repeat items="[[collections_]]">
+<ntp-grid id="collections" columns="3" hidden="[[selectedCollection]]">
+  <dom-repeat id="collectionsRepeat" items="[[collections_]]">
+    <template>
+      <div class="tile" tabindex="0" title="[[item.label]]" role="button"
+        on-click="onCollectionClick_">
+        <!-- TODO(crbug.com/1032328): Show image in an iframe. -->
+        <div class="image">[[item.previewImageUrl.url]]</div>
+        <div class="label">[[item.label]]</div>
+      </div>
+    </template>
+  </dom-repeat>
+</ntp-grid>
+<ntp-grid id="images" columns="3" hidden="[[!selectedCollection]]">
+  <dom-repeat items="[[images_]]">
     <template>
       <div class="tile" tabindex="0" title="[[item.label]]" role="button">
         <!-- TODO(crbug.com/1032328): Show image in an iframe. -->
         <div class="image">[[item.previewImageUrl.url]]</div>
-        <div class="label">[[item.label]]</div>
       </div>
     </template>
   </dom-repeat>
diff --git a/chrome/browser/resources/new_tab_page/customize_backgrounds.js b/chrome/browser/resources/new_tab_page/customize_backgrounds.js
index 60a3edc5..de5fc8c8 100644
--- a/chrome/browser/resources/new_tab_page/customize_backgrounds.js
+++ b/chrome/browser/resources/new_tab_page/customize_backgrounds.js
@@ -19,8 +19,19 @@
 
   static get properties() {
     return {
+      /** @private {newTabPage.mojom.BackgroundCollection} */
+      selectedCollection: {
+        notify: true,
+        observer: 'onSelectedCollectionChange_',
+        type: Object,
+        value: null,
+      },
+
       /** @private {!Array<!newTabPage.mojom.BackgroundCollection>} */
       collections_: Array,
+
+      /** @private {!Array<!newTabPage.mojom.BackgroundImage>} */
+      images_: Array,
     };
   }
 
@@ -31,6 +42,33 @@
           this.collections_ = collections;
         });
   }
+
+  /**
+   * @param {!Event} e
+   * @private
+   */
+  onCollectionClick_(e) {
+    this.selectedCollection = this.$.collectionsRepeat.itemForElement(e.target);
+  }
+
+  /** @private */
+  async onSelectedCollectionChange_() {
+    this.images_ = [];
+    if (!this.selectedCollection) {
+      return;
+    }
+    const collectionId = this.selectedCollection.id;
+    const {images} =
+        await BrowserProxy.getInstance().handler.getBackgroundImages(
+            collectionId);
+    // We check the IDs match since the user may have already moved to a
+    // different collection before the results come back.
+    if (!this.selectedCollection ||
+        this.selectedCollection.id !== collectionId) {
+      return;
+    }
+    this.images_ = images;
+  }
 }
 
 customElements.define(
diff --git a/chrome/browser/resources/new_tab_page/customize_dialog.html b/chrome/browser/resources/new_tab_page/customize_dialog.html
index 07785024..218fbacf 100644
--- a/chrome/browser/resources/new_tab_page/customize_dialog.html
+++ b/chrome/browser/resources/new_tab_page/customize_dialog.html
@@ -1,4 +1,4 @@
-<style>
+<style include="cr-icons">
   ::part(dialog) {
     min-width: 800px;
   }
@@ -9,6 +9,7 @@
 
   div[slot=title] {
     align-items: center;
+    color: var(--ntp-primary-text-color);
     display: flex;
     flex-direction: row;
     height: 80px;
@@ -120,15 +121,29 @@
   #themesIcon {
     -webkit-mask-image: url(icons/colors.svg);
   }
+
+  #backButton {
+    --cr-icon-button-fill-color: var(--ntp-primary-text-color);
+    margin-inline-end: 4px;
+    /* So that the arrow aligns with the grid. */
+    margin-inline-start: -12px;
+  }
 </style>
 <cr-dialog id="dialog" show-on-attach>
   <div slot="title">
     <div id="leftTitleSpacer"></div>
-    <div id="title">$i18n{customizeThisPage}</div>
+    <div id="title">
+      <div id="titleText" hidden="[[showTitleNavigation_]]">
+        $i18n{customizeThisPage}
+      </div>
+      <div id="titleNavigation" hidden="[[!showTitleNavigation_]]">
+        <cr-icon-button id="backButton" class="icon-arrow-back"
+            on-click="onBackClick_" title="$i18n{backButton}">
+        </cr-icon-button>
+        [[selectedCollection_.label]]
+      </div>
+    </div>
   </div>
-  <!-- TODO(crbug.com/1040256): Currently, the sidebar scrolls in sync with the
-       page content area. Fix, so that the page content can scroll
-       separately. -->
   <div slot="body">
     <div id="menuContainer">
       <div id="menu">
@@ -153,7 +168,8 @@
     <div id="pagesContainer">
       <div id="pages">
         <iron-pages selected="[[selectedPage_]]" attr-for-selected="page-name">
-          <ntp-customize-backgrounds page-name="backgrounds">
+          <ntp-customize-backgrounds id="backgrounds" page-name="backgrounds"
+              selected-collection="{{selectedCollection_}}">
           </ntp-customize-backgrounds>
           <ntp-customize-shortcuts page-name="shortcuts">
           </ntp-customize-shortcuts>
diff --git a/chrome/browser/resources/new_tab_page/customize_dialog.js b/chrome/browser/resources/new_tab_page/customize_dialog.js
index fae7709..64d1dc6 100644
--- a/chrome/browser/resources/new_tab_page/customize_dialog.js
+++ b/chrome/browser/resources/new_tab_page/customize_dialog.js
@@ -39,6 +39,17 @@
         value: 'backgrounds',
         observer: 'onSelectedPageChange_',
       },
+
+      /** @private {newTabPage.mojom.BackgroundCollection} */
+      selectedCollection_: Object,
+
+      /** @private */
+      showTitleNavigation_: {
+        type: Boolean,
+        computed:
+            'computeShowTitleNavigation_(selectedPage_, selectedCollection_)',
+        value: false,
+      },
     };
   }
 
@@ -98,6 +109,16 @@
   onSelectedPageChange_() {
     this.$.pages.scrollTop = 0;
   }
+
+  /** @private */
+  computeShowTitleNavigation_() {
+    return this.selectedPage_ === 'backgrounds' && this.selectedCollection_;
+  }
+
+  /** @private */
+  onBackClick_() {
+    this.selectedCollection_ = null;
+  }
 }
 
 customElements.define(CustomizeDialogElement.is, CustomizeDialogElement);
diff --git a/chrome/browser/resources/new_tab_page/grid.html b/chrome/browser/resources/new_tab_page/grid.html
index 197602f..92119115 100644
--- a/chrome/browser/resources/new_tab_page/grid.html
+++ b/chrome/browser/resources/new_tab_page/grid.html
@@ -1,7 +1,6 @@
 <style>
   :host {
     --ntp-grid-gap: 0px;
-    display: block;
   }
 
   #grid {
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html
index 3a0dbc6..a23a437 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.html
@@ -99,20 +99,22 @@
               <cr-radio-button name="pin+password" class="list-item-start"
                   label=$i18n{lockScreenPinOrPassword}>
               </cr-radio-button>
-              <div class="list-item-end"
-                  hidden="[[!showConfigurePinButton_(selectedUnlockType)]]">
-                <div class="separator"></div>
-                <div id="pinPasswordSecondaryActionDiv"
-                    class="secondary-action">
-                  <!-- Use stop-keyboard-event-propagation to prevent
-                       triggering this when focused after closing the
-                       dialog. -->
-                  <cr-button id="setupPinButton" on-click="onConfigurePin_"
-                      stop-keyboard-event-propagation>
-                    [[getSetupPinText_(hasPin)]]
-                  </cr-button>
+              <template is="dom-if"
+                  if="[[showConfigurePinButton_(selectedUnlockType)]]">
+                <div class="list-item-end">
+                  <div class="separator"></div>
+                  <div id="pinPasswordSecondaryActionDiv"
+                      class="secondary-action">
+                    <!-- Use stop-keyboard-event-propagation to prevent
+                         triggering this when focused after closing the
+                         dialog. -->
+                    <cr-button id="setupPinButton" on-click="onConfigurePin_"
+                        stop-keyboard-event-propagation>
+                      [[getSetupPinText_(hasPin)]]
+                    </cr-button>
+                  </div>
                 </div>
-              </div>
+              </template>
             </cr-radio-group>
           </div>
         </div>
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
index 27d41b3..bba5a6d 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_screen.js
@@ -167,7 +167,7 @@
    */
   currentRouteChanged(newRoute, oldRoute) {
     if (newRoute == settings.routes.LOCK_SCREEN) {
-      this.updateUnlockType();
+      this.updateUnlockType(/*activeModesChanged=*/ false);
       this.updateNumFingerprints_();
     }
 
@@ -201,11 +201,19 @@
     }
 
     if (selected != LockScreenUnlockType.PIN_PASSWORD && this.setModes_) {
+      // If the user selects PASSWORD only (which sends an asynchronous
+      // setModes_.call() to clear the quick unlock capability), indicate to the
+      // user immediately that the quick unlock capability is cleared by setting
+      // |hasPin| to false. If there is an error clearing quick unlock, revert
+      // |hasPin| to true. This prevents setupPinButton UI delays, except in the
+      // small chance that CrOS fails to remove the quick unlock capability. See
+      // https://crbug.com/1054327 for details.
+      this.hasPin = false;
       this.setModes_.call(null, [], [], function(result) {
         assert(result, 'Failed to clear quick unlock modes');
-        if (!result) {
-          console.error('Failed to clear quick unlock modes');
-        }
+        // Revert |hasPin| to true in the event setModes fails to set lock state
+        // to PASSWORD only.
+        this.hasPin = true;
       });
     }
   },
diff --git a/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js b/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
index fa58641..4e17698 100644
--- a/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
+++ b/chrome/browser/resources/settings/chromeos/os_people_page/lock_state_behavior.js
@@ -63,7 +63,8 @@
 
   /** @override */
   attached() {
-    this.boundOnActiveModesChanged_ = this.updateUnlockType.bind(this);
+    this.boundOnActiveModesChanged_ =
+        this.updateUnlockType.bind(this, /*activeModesChanged=*/ true);
     this.quickUnlockPrivate.onActiveModesChanged.addListener(
         this.boundOnActiveModesChanged_);
 
@@ -77,7 +78,7 @@
       this.hasPinLogin = cachedHasPinLogin;
     }
 
-    this.updateUnlockType();
+    this.updateUnlockType(/*activeModesChanged=*/ false);
   },
 
   /** @override */
@@ -90,13 +91,32 @@
    * Updates the selected unlock type radio group. This function will get called
    * after preferences are initialized, after the quick unlock mode has been
    * changed, and after the lockscreen preference has changed.
+   *
+   * @param {boolean} activeModesChanged If the function is called because
+   *     active modes have changed.
    */
-  updateUnlockType() {
+  updateUnlockType(activeModesChanged) {
     this.quickUnlockPrivate.getActiveModes(modes => {
       if (modes.includes(chrome.quickUnlockPrivate.QuickUnlockMode.PIN)) {
         this.hasPin = true;
         this.selectedUnlockType = LockScreenUnlockType.PIN_PASSWORD;
       } else {
+        // A race condition can occur:
+        // (1) User selects PIN_PASSSWORD, and successfully sets a pin, adding
+        //     QuickUnlockMode.PIN to active modes.
+        // (2) User selects PASSWORD, QuickUnlockMode.PIN capability is cleared
+        //     from the active modes, notifying LockStateBehavior to call
+        //     updateUnlockType to fetch the active modes asynchronously.
+        // (3) User selects PIN_PASSWORD, but the process from step 2 has
+        //     not yet completed.
+        // In this case, do not forcibly select the PASSWORD radio button even
+        // though the unlock type is still PASSWORD (|hasPin| is false). If the
+        // user wishes to set a pin, they will have to click the set pin button.
+        // See https://crbug.com/1054327 for details.
+        if (activeModesChanged && !this.hasPin &&
+            this.selectedUnlockType == LockScreenUnlockType.PIN_PASSWORD) {
+          return;
+        }
         this.hasPin = false;
         this.selectedUnlockType = LockScreenUnlockType.PASSWORD;
       }
diff --git a/chrome/browser/resources/settings/privacy_page/BUILD.gn b/chrome/browser/resources/settings/privacy_page/BUILD.gn
index 6307c97..d74bc10 100644
--- a/chrome/browser/resources/settings/privacy_page/BUILD.gn
+++ b/chrome/browser/resources/settings/privacy_page/BUILD.gn
@@ -23,7 +23,10 @@
 }
 
 js_library("cookies_page") {
-  deps = [ "//ui/webui/resources/js:load_time_data" ]
+  deps = [
+    "../site_settings:constants",
+    "//ui/webui/resources/js:load_time_data",
+  ]
 }
 
 js_library("personalization_options") {
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.html b/chrome/browser/resources/settings/privacy_page/cookies_page.html
index e5f3eb1..e426a4ce 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.html
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.html
@@ -1,13 +1,24 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="../settings_shared_css.html">
+<link rel="import" href="../site_settings/category_setting_exceptions.html">
+<link rel="import" href="../site_settings/constants.html">
+
 <dom-module id="settings-cookies-page">
   <template>
-    <style>
+    <style include="settings-shared">
       img {
         width: 100%;
       }
     </style>
     <img src="../images/cookies_banner.svg">
+    <div class="settings-box first">
+      <h2>$i18n{cookiesManageSiteSpecificExceptions}</h2>
+    </div>
+    <category-setting-exceptions
+        category="[[ContentSettingsTypes.COOKIES]]"
+        block-header="$i18n{siteSettingsBlock}">
+    </category-setting-exceptions>
   </template>
   <script src="cookies_page.js"></script>
 </dom-module>
diff --git a/chrome/browser/resources/settings/privacy_page/cookies_page.js b/chrome/browser/resources/settings/privacy_page/cookies_page.js
index f1ae6f9..1814609 100644
--- a/chrome/browser/resources/settings/privacy_page/cookies_page.js
+++ b/chrome/browser/resources/settings/privacy_page/cookies_page.js
@@ -1,4 +1,4 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
+// Copyright 2020 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.
 
@@ -7,9 +7,14 @@
  * 'settings-cookies-page' is the settings page containing cookies
  * settings.
  */
-(function() {
 
 Polymer({
   is: 'settings-cookies-page',
+
+  properties: {
+    /**
+     * @private {!settings.ContentSettingsTypes}
+     */
+    ContentSettingsTypes: {type: Object, value: settings.ContentSettingsTypes},
+  },
 });
-})();
diff --git a/chrome/browser/resources/settings/privacy_page/secure_dns_input.html b/chrome/browser/resources/settings/privacy_page/secure_dns_input.html
index d987901..2f3189b7 100644
--- a/chrome/browser/resources/settings/privacy_page/secure_dns_input.html
+++ b/chrome/browser/resources/settings/privacy_page/secure_dns_input.html
@@ -22,7 +22,7 @@
     <cr-input id="input" value="{{value}}"
         placeholder="$i18n{secureDnsCustomPlaceholder}" invalid="[[showError_]]"
         error-message="[[errorText_]]" maxlength="102400" spellcheck="false"
-        on-input="onInput_" on-blur="onBlur_">
+        on-input="onInput_" on-blur="validate">
     </cr-input>
   </template>
   <script src="secure_dns_input.js"></script>
diff --git a/chrome/browser/resources/settings/privacy_page/secure_dns_input.js b/chrome/browser/resources/settings/privacy_page/secure_dns_input.js
index 84ad4d5..40f1071a 100644
--- a/chrome/browser/resources/settings/privacy_page/secure_dns_input.js
+++ b/chrome/browser/resources/settings/privacy_page/secure_dns_input.js
@@ -51,9 +51,8 @@
    * When the custom input field loses focus, validate the current value and
    * trigger an event with the result. Show an error message if the validated
    * value is still the most recent value, is invalid, and is non-empty.
-   * @private
    */
-  onBlur_: function() {
+  validate: function() {
     const valueToValidate = this.value;
     this.browserProxy_.validateCustomDnsEntry(valueToValidate).then(valid => {
       this.showError_ =
diff --git a/chrome/browser/safe_browsing/advanced_protection_status_manager.cc b/chrome/browser/safe_browsing/advanced_protection_status_manager.cc
index f1adcaa..e58d3f0 100644
--- a/chrome/browser/safe_browsing/advanced_protection_status_manager.cc
+++ b/chrome/browser/safe_browsing/advanced_protection_status_manager.cc
@@ -227,9 +227,15 @@
 }
 
 bool AdvancedProtectionStatusManager::IsUnderAdvancedProtection() const {
-  return base::CommandLine::ForCurrentProcess()->HasSwitch(
-             kForceTreatUserAsAdvancedProtection) ||
-         is_under_advanced_protection_;
+  if (!pref_service_->GetBoolean(
+          prefs::kAdvancedProtectionExtraSecurityAllowed))
+    return false;
+
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+          kForceTreatUserAsAdvancedProtection))
+    return true;
+
+  return is_under_advanced_protection_;
 }
 
 bool AdvancedProtectionStatusManager::IsUnconsentedPrimaryAccount(
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service.cc b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
index 2b441fb..92a3cd2 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service.cc
@@ -1410,12 +1410,10 @@
     return false;
   }
   bool extended_reporting_enabled = IsExtendedReporting();
-  bool enhanced_protection_enabled = IsEnhancedProtection();
   if (trigger_type == LoginReputationClientRequest::PASSWORD_REUSE_EVENT) {
     if (password_type.account_type() ==
         ReusedPasswordAccountType::SAVED_PASSWORD) {
       bool enabled = extended_reporting_enabled ||
-                     enhanced_protection_enabled ||
                      base::FeatureList::IsEnabled(
                          safe_browsing::kPasswordProtectionForSavedPasswords);
       if (!enabled)
@@ -1433,13 +1431,13 @@
     // If the account type is UNKNOWN (i.e. AccountInfo fields could not be
     // retrieved from server), pings should be gated by SBER.
     if (password_type.account_type() == ReusedPasswordAccountType::UNKNOWN) {
-      return extended_reporting_enabled || enhanced_protection_enabled;
+      return extended_reporting_enabled;
     }
 
 // Only saved password reuse warnings are shown on Android, so other types of
 // password reuse events should be gated by extended reporting.
 #if defined(OS_ANDROID)
-    return extended_reporting_enabled || enhanced_protection_enabled;
+    return extended_reporting_enabled;
 #else
     return true;
 #endif
@@ -1451,7 +1449,7 @@
     *reason = RequestOutcome::DISABLED_DUE_TO_INCOGNITO;
     return false;
   }
-  if (!extended_reporting_enabled && !enhanced_protection_enabled) {
+  if (!extended_reporting_enabled) {
     *reason = RequestOutcome::DISABLED_DUE_TO_USER_POPULATION;
     return false;
   }
@@ -1685,7 +1683,7 @@
 
 bool ChromePasswordProtectionService::CanSendSamplePing() {
   // Send a sample ping only 1% of the time.
-  return (IsExtendedReporting() || IsEnhancedProtection()) && !IsIncognito() &&
+  return IsExtendedReporting() && !IsIncognito() &&
          (bypass_probability_for_tests_ ||
           base::RandDouble() <= kProbabilityForSendingReportsFromSafeURLs);
 }
diff --git a/chrome/browser/safe_browsing/chrome_password_protection_service_unittest.cc b/chrome/browser/safe_browsing/chrome_password_protection_service_unittest.cc
index d4ee83df..f927e50 100644
--- a/chrome/browser/safe_browsing/chrome_password_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/chrome_password_protection_service_unittest.cc
@@ -155,10 +155,8 @@
         is_extended_reporting_(false),
         is_syncing_(false),
         is_no_hosted_domain_found_(false),
-        is_account_signed_in_(false),
-        is_enhanced_protection_(false) {}
+        is_account_signed_in_(false) {}
   bool IsExtendedReporting() override { return is_extended_reporting_; }
-  bool IsEnhancedProtection() override { return is_enhanced_protection_; }
   bool IsIncognito() override { return is_incognito_; }
   bool IsPrimaryAccountSyncing() const override { return is_syncing_; }
   bool IsPrimaryAccountSignedIn() const override {
@@ -186,9 +184,6 @@
   void SetIsAccountSignedIn(bool is_account_signed_in) {
     is_account_signed_in_ = is_account_signed_in;
   }
-  void SetIsEnhancedProtection(bool is_enhanced_protection) {
-    is_enhanced_protection_ = is_enhanced_protection;
-  }
   void SetAccountInfo(const std::string& username) {
     AccountInfo account_info;
     account_info.account_id = CoreAccountId("account_id");
@@ -208,7 +203,6 @@
   bool is_syncing_;
   bool is_no_hosted_domain_found_;
   bool is_account_signed_in_;
-  bool is_enhanced_protection_;
   AccountInfo account_info_;
   std::string mocked_sync_password_hash_;
 };
@@ -413,12 +407,6 @@
       &reason));
   EXPECT_EQ(RequestOutcome::DISABLED_DUE_TO_USER_POPULATION, reason);
 
-  service_->SetIsEnhancedProtection(
-      /*is_enhanced_protection=*/true);
-  EXPECT_TRUE(service_->IsPingingEnabled(
-      LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, reused_password_type,
-      &reason));
-
   service_->ConfigService(false /*incognito*/, true /*SBER*/);
   EXPECT_TRUE(service_->IsPingingEnabled(
       LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, reused_password_type,
@@ -621,17 +609,6 @@
   service_->PersistPhishedSavedPasswordCredential("username", domains);
 }
 
-TEST_F(ChromePasswordProtectionServiceTest,
-       VerifyPersistPhishedSavedPasswordCredentialForEnhancedProtection) {
-  service_->SetIsEnhancedProtection(
-      /*is_enhanced_protection=*/true);
-
-  std::vector<std::string> domains{"http://example.com",
-                                   "https://2.example.com"};
-  EXPECT_CALL(*password_store_, AddCompromisedCredentialsImpl(_)).Times(2);
-  service_->PersistPhishedSavedPasswordCredential("username", domains);
-}
-
 TEST_F(ChromePasswordProtectionServiceTest, VerifyCanSendSamplePing) {
   // Experiment is on by default.
   service_->ConfigService(/*is_incognito=*/false,
@@ -655,9 +632,6 @@
 
   service_->ConfigService(/*is_incognito=*/false,
                           /*is_extended_reporting=*/false);
-  service_->SetIsEnhancedProtection(
-      /*is_enhanced_protection=*/true);
-  EXPECT_TRUE(service_->CanSendSamplePing());
 }
 
 TEST_F(ChromePasswordProtectionServiceTest, VerifyGetOrganizationTypeGmail) {
diff --git a/chrome/browser/safe_browsing/client_side_detection_host.cc b/chrome/browser/safe_browsing/client_side_detection_host.cc
index 432d5016..836074d 100644
--- a/chrome/browser/safe_browsing/client_side_detection_host.cc
+++ b/chrome/browser/safe_browsing/client_side_detection_host.cc
@@ -444,8 +444,7 @@
     UMA_HISTOGRAM_ENUMERATION(
         "SBClientPhishing.ClassifierNotReadyReason",
         csd_service_->GetLastModelStatus(
-            IsExtendedReportingEnabled(*profile->GetPrefs()) ||
-            IsEnhancedProtectionEnabled(*profile->GetPrefs())));
+            IsExtendedReportingEnabled(*profile->GetPrefs())));
   }
   if (result != mojom::PhishingDetectorResult::SUCCESS)
     return;
diff --git a/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc b/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc
index d15e9120..3c381e7 100644
--- a/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc
+++ b/chrome/browser/safe_browsing/download_protection/check_client_download_request.cc
@@ -300,8 +300,6 @@
 #else
   Profile* profile = Profile::FromBrowserContext(GetBrowserContext());
   return base::FeatureList::IsEnabled(kPromptAppForDeepScanning) &&
-         profile->GetPrefs()->GetBoolean(
-             prefs::kAdvancedProtectionDeepScanningEnabled) &&
          AdvancedProtectionStatusManagerFactory::GetForProfile(profile)
              ->IsUnderAdvancedProtection();
 #endif
diff --git a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
index be66504d..dfebaae6 100644
--- a/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
+++ b/chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
@@ -220,8 +220,7 @@
 bool CheckClientDownloadRequestBase::ShouldSampleWhitelistedDownload() {
   // We currently sample 1% whitelisted downloads from users who opted
   // in extended reporting and are not in incognito mode.
-  return service_ && (is_extended_reporting_ || is_enhanced_protection_) &&
-         !is_incognito_ &&
+  return service_ && is_extended_reporting_ && !is_incognito_ &&
          base::RandDouble() < service_->whitelist_sample_rate();
 }
 
@@ -232,8 +231,7 @@
   // we'll send a "light ping" with private info removed, and we won't
   // use the verdict.
   const FileTypePolicies* policies = FileTypePolicies::GetInstance();
-  return service_ && (is_extended_reporting_ || is_enhanced_protection_) &&
-         !is_incognito_ &&
+  return service_ && is_extended_reporting_ && !is_incognito_ &&
          base::RandDouble() < policies->SampledPingProbability() &&
          policies->PingSettingForFile(filename) ==
              DownloadFileType::SAMPLED_PING;
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
index a934602..85769214 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service.cc
@@ -102,8 +102,7 @@
   const PrefService* prefs = profile->GetPrefs();
   if (!prefs)
     return kDownloadAttributionUserGestureLimit;
-  if (!IsExtendedReportingEnabled(*prefs) &&
-      !IsEnhancedProtectionEnabled(*prefs))
+  if (!IsExtendedReportingEnabled(*prefs))
     return kDownloadAttributionUserGestureLimit;
   return kDownloadAttributionUserGestureLimitForExtendedReporting;
 }
@@ -424,9 +423,7 @@
   OnDangerousDownloadOpened(item, profile);
   if (sb_service_ &&
       !token.empty() &&  // Only dangerous downloads have token stored.
-      profile &&
-      (IsExtendedReportingEnabled(*profile->GetPrefs()) ||
-       IsEnhancedProtectionEnabled(*profile->GetPrefs()))) {
+      profile && (IsExtendedReportingEnabled(*profile->GetPrefs()))) {
     safe_browsing::ClientSafeBrowsingReportRequest report;
     report.set_url(item->GetURL().spec());
     report.set_type(safe_browsing::ClientSafeBrowsingReportRequest::
@@ -592,8 +589,7 @@
   PrefService* prefs = profile->GetPrefs();
   bool is_extended_reporting =
       ExtendedReportingPrefExists(*prefs) && IsExtendedReportingEnabled(*prefs);
-  if (!profile->IsOffTheRecord() &&
-      (is_extended_reporting || IsEnhancedProtectionEnabled(*prefs))) {
+  if (!profile->IsOffTheRecord() && is_extended_reporting) {
     feedback_service_->BeginFeedbackForDownload(download, download_command);
     return true;
   }
diff --git a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
index a20f15b..5d5879d 100644
--- a/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_protection_service_unittest.cc
@@ -635,10 +635,6 @@
     SetExtendedReportingPref(profile()->GetPrefs(), is_extended_reporting);
   }
 
-  void SetEnhancedProtectionPreference(bool is_enhanced_protection) {
-    SetEnhancedProtectionPref(profile()->GetPrefs(), is_enhanced_protection);
-  }
-
   // Verify that corrupted ZIP/DMGs do send a ping.
   void CheckClientDownloadReportCorruptArchive(ArchiveType type);
 
@@ -895,13 +891,12 @@
                            "http://www.google.com/",     // referrer
                            FILE_PATH_LITERAL("a.tmp"),   // tmp_path
                            FILE_PATH_LITERAL("a.exe"));  // final_path
-  // Case 4 and 5 sends a request.
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
-      .Times(2);
+      .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
-      .Times(4);
+      .Times(3);
   // Assume http://www.whitelist.com/a.exe is on the whitelist.
   EXPECT_CALL(*sb_service_->mock_database_manager(),
               MatchDownloadWhitelistUrl(_))
@@ -975,25 +970,6 @@
     EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
     ClearClientDownloadRequest();
   }
-  {
-    // Case (5): is_enhanced_protection && !is_extended_reporting &&
-    // !is_incognito &&
-    //           Download matches URL whitelist.
-    //           ClientDownloadRequest should be sent.
-    SetEnhancedProtectionPreference(true);
-    content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
-    RunLoop run_loop;
-    download_service_->CheckClientDownload(
-        &item,
-        base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-    EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
-    ASSERT_TRUE(HasClientDownloadRequest());
-    EXPECT_TRUE(GetClientDownloadRequest()->skipped_url_whitelist());
-    EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
-    ClearClientDownloadRequest();
-  }
 
   // Setup trusted and whitelisted certificates for test cases (5) and (6).
   scoped_refptr<net::X509Certificate> test_cert(
@@ -1066,13 +1042,12 @@
       "http://referrer.com/3/4",    // Referrer
       FILE_PATH_LITERAL("a.tmp"),   // tmp_path
       FILE_PATH_LITERAL("a.txt"));  // final_path, txt is set to SAMPLED_PING
-  // Case 1 and 5 both send requests.
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
-      .Times(2);
+      .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
-      .Times(2);
+      .Times(1);
 
   // Set ping sample rate to 1.00 so download_service_ will always send a
   // "light" ping for unknown types if allowed.
@@ -1146,33 +1121,6 @@
     EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
     EXPECT_FALSE(HasClientDownloadRequest());
   }
-  {
-    // Case (5): is_enhanced_protection && !is_extended_reporting &&
-    //           !is_incognito.
-    //           A "light" ClientDownloadRequest should be sent.
-    SetEnhancedProtectionPreference(true);
-    content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
-    RunLoop run_loop;
-    download_service_->CheckClientDownload(
-        &item,
-        base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-    EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
-    ASSERT_TRUE(HasClientDownloadRequest());
-
-    // Verify it's a "light" ping, check that URLs don't have paths.
-    auto* req = GetClientDownloadRequest();
-    EXPECT_EQ(ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE,
-              req->download_type());
-    EXPECT_EQ(GURL(req->url()).GetOrigin().spec(), req->url());
-    for (auto resource : req->resources()) {
-      EXPECT_EQ(GURL(resource.url()).GetOrigin().spec(), resource.url());
-      EXPECT_EQ(GURL(resource.referrer()).GetOrigin().spec(),
-                resource.referrer());
-    }
-    ClearClientDownloadRequest();
-  }
 }
 
 TEST_F(DownloadProtectionServiceTest, CheckClientDownloadFetchFailed) {
@@ -2520,23 +2468,6 @@
     ASSERT_EQ(ChromeUserPopulation::EXTENDED_REPORTING,
               GetClientDownloadRequest()->population().user_population());
   }
-
-  for (const auto& test_case : kExpectedResults) {
-    sb_service_->test_url_loader_factory()->ClearResponses();
-    PrepareResponse(test_case.verdict, net::HTTP_OK, net::OK);
-    SetExtendedReportingPreference(false);
-    SetEnhancedProtectionPreference(true);
-    RunLoop run_loop;
-    download_service_->CheckPPAPIDownloadRequest(
-        GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
-        alternate_extensions, profile(),
-        base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-    ASSERT_TRUE(IsResult(test_case.expected_result));
-    ASSERT_EQ(ChromeUserPopulation::ENHANCED_PROTECTION,
-              GetClientDownloadRequest()->population().user_population());
-  }
 }
 
 TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_SupportedAlternate) {
@@ -2715,31 +2646,19 @@
   download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
   EXPECT_EQ(0, sb_service_->download_report_count());
 
-  // No report sent if user is not in extended reporting and enhanced protection
-  // group.
+  // No report sent if user is not in extended reporting  group.
   content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
   SetExtendedReportingPreference(false);
-  SetEnhancedProtectionPreference(false);
   download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
   EXPECT_EQ(0, sb_service_->download_report_count());
 
   // Report successfully sent if user opted-in extended reporting, not in
   // incognito, and download item has a token stored.
   SetExtendedReportingPreference(true);
-  SetEnhancedProtectionPreference(false);
   download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
   EXPECT_EQ(1, sb_service_->download_report_count());
   download_service_->MaybeSendDangerousDownloadOpenedReport(&item, true);
   EXPECT_EQ(2, sb_service_->download_report_count());
-
-  // Report successfully sent if user opted-in enhanced protection, not in
-  // incognito, and download item has a token stored.
-  SetExtendedReportingPreference(false);
-  SetEnhancedProtectionPreference(true);
-  download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
-  EXPECT_EQ(3, sb_service_->download_report_count());
-  download_service_->MaybeSendDangerousDownloadOpenedReport(&item, true);
-  EXPECT_EQ(4, sb_service_->download_report_count());
 }
 
 TEST_F(DownloadProtectionServiceTest, VerifyDangerousDownloadOpenedAPICall) {
@@ -2812,17 +2731,6 @@
       profile()->GetOffTheRecordProfile(), &item, DownloadCommands::KEEP));
 }
 
-TEST_F(
-    DownloadProtectionServiceTest,
-    CheckNotExtendedReportedAndEnhancedProtectionDisabledDoesNotSendFeedback) {
-  SetExtendedReportingPreference(false);
-  SetEnhancedProtectionPreference(false);
-
-  NiceMockDownloadItem item;
-  EXPECT_FALSE(download_service_->MaybeBeginFeedbackForDownload(
-      profile(), &item, DownloadCommands::KEEP));
-}
-
 // ------------ class DownloadProtectionServiceFlagTest ----------------
 class DownloadProtectionServiceFlagTest : public DownloadProtectionServiceTest {
  protected:
@@ -2961,16 +2869,6 @@
   referrer_chain_data = download_service_->IdentifyReferrerChain(item);
   // 3 entries means 2 interactions between entries.
   EXPECT_EQ(referrer_chain_data->referrer_chain_length(), 3u);
-
-  SetEnhancedProtectionPref(profile()->GetPrefs(), true);
-  referrer_chain_data = download_service_->IdentifyReferrerChain(item);
-  // 6 entries means 5 interactions between entries.
-  EXPECT_EQ(referrer_chain_data->referrer_chain_length(), 6u);
-
-  SetEnhancedProtectionPref(profile()->GetPrefs(), false);
-  referrer_chain_data = download_service_->IdentifyReferrerChain(item);
-  // 3 entries means 2 interactions between entries.
-  EXPECT_EQ(referrer_chain_data->referrer_chain_length(), 3u);
 }
 
 TEST_F(DownloadProtectionServiceTest, DoesNotSendPingForCancelledDownloads) {
@@ -3150,13 +3048,12 @@
       /*tmp_path=*/FILE_PATH_LITERAL("a.txt.crswap"),
       /*final_path=*/FILE_PATH_LITERAL("a.txt"));
 
-  // Case 2 and 5 send requests.
   EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
-      .Times(2);
+      .Times(1);
   EXPECT_CALL(*binary_feature_extractor_.get(),
               ExtractImageFeatures(
                   tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
-      .Times(2);
+      .Times(1);
 
   // Set ping sample rate to 1.00 so download_service_ will always send a
   // "light" ping for unknown types if allowed.
@@ -3237,36 +3134,6 @@
     EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
     EXPECT_FALSE(HasClientDownloadRequest());
   }
-  {
-    // Case (5): is_enhanced_protection && !is_extended_reporting &&
-    //           !is_incognito.
-    //           A "light" ClientDownloadRequest should be sent.
-    SetExtendedReportingPreference(false);
-    SetEnhancedProtectionPreference(true);
-    item = PrepareBasicNativeFileSystemWriteItem(
-        /*tmp_path=*/FILE_PATH_LITERAL("a.txt.crswap"),
-        /*final_path=*/FILE_PATH_LITERAL("a.txt"));
-    RunLoop run_loop;
-    download_service_->CheckNativeFileSystemWrite(
-        std::move(item),
-        base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
-                            base::Unretained(this), run_loop.QuitClosure()));
-    run_loop.Run();
-    EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
-    ASSERT_TRUE(HasClientDownloadRequest());
-
-    // Verify it's a "light" ping, check that URLs don't have paths.
-    auto* req = GetClientDownloadRequest();
-    EXPECT_EQ(ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE,
-              req->download_type());
-    EXPECT_EQ(GURL(req->url()).GetOrigin().spec(), req->url());
-    for (auto resource : req->resources()) {
-      EXPECT_EQ(GURL(resource.url()).GetOrigin().spec(), resource.url());
-      EXPECT_EQ(GURL(resource.referrer()).GetOrigin().spec(),
-                resource.referrer());
-    }
-    ClearClientDownloadRequest();
-  }
 }
 
 TEST_F(DownloadProtectionServiceTest,
diff --git a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
index 8949d86..be8e5fc6 100644
--- a/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
+++ b/chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.cc
@@ -306,8 +306,7 @@
     return false;
   if (!profile->GetPrefs()->GetBoolean(prefs::kSafeBrowsingEnabled))
     return false;
-  return IsExtendedReportingEnabled(*profile->GetPrefs()) ||
-         IsEnhancedProtectionEnabled(*profile->GetPrefs());
+  return IsExtendedReportingEnabled(*profile->GetPrefs());
 }
 
 IncidentReportingService::IncidentReportingService(
diff --git a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
index cd7bf4bc..9384340f 100644
--- a/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
+++ b/chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.cc
@@ -514,8 +514,7 @@
 size_t SafeBrowsingNavigationObserverManager::CountOfRecentNavigationsToAppend(
     const Profile& profile,
     AttributionResult result) {
-  if ((!IsExtendedReportingEnabled(*profile.GetPrefs()) &&
-       !IsEnhancedProtectionEnabled(*profile.GetPrefs())) ||
+  if (!IsExtendedReportingEnabled(*profile.GetPrefs()) ||
       profile.IsOffTheRecord() || result == SUCCESS_LANDING_REFERRER) {
     return 0u;
   }
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
index dbdb39cf..fad93d6 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service.cc
@@ -154,8 +154,7 @@
     return false;
   }
 
-  bool no_ping_allowed = !IsExtendedReportingEnabled(*GetPrefs()) &&
-                         !IsEnhancedProtectionEnabled(*GetPrefs());
+  bool no_ping_allowed = !IsExtendedReportingEnabled(*GetPrefs());
 
   if (no_ping_allowed) {
     RecordApkDownloadTelemetryOutcome(
diff --git a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
index 4e4113c..d92c765 100644
--- a/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
+++ b/chrome/browser/safe_browsing/telemetry/android/android_telemetry_service_unittest.cc
@@ -69,6 +69,9 @@
 
     telemetry_service_ =
         std::make_unique<AndroidTelemetryService>(sb_service_.get(), profile());
+
+    scoped_feature_list_.InitAndEnableFeature(
+        safe_browsing::kEnhancedProtection);
   }
 
   void TearDown() override {
diff --git a/chrome/browser/themes/browser_theme_pack.h b/chrome/browser/themes/browser_theme_pack.h
index 6dfc2bf..c09272d 100644
--- a/chrome/browser/themes/browser_theme_pack.h
+++ b/chrome/browser/themes/browser_theme_pack.h
@@ -281,6 +281,8 @@
   void GenerateRawImageForAllSupportedScales(int prs_id);
 
   // Data pack, if we have one.
+  // TODO(crbug.com/1057889): Destroy this on in the thread pool, make the rest
+  // of CustomThemeSupplier destruction synchronous.
   std::unique_ptr<ui::DataPack> data_pack_;
 
   // All structs written to disk need to be packed; no alignment tricks here,
diff --git a/chrome/browser/themes/theme_helper.cc b/chrome/browser/themes/theme_helper.cc
index 2651f23e..902d213 100644
--- a/chrome/browser/themes/theme_helper.cc
+++ b/chrome/browser/themes/theme_helper.cc
@@ -89,12 +89,20 @@
 bool ThemeHelper::IsDefaultTheme(const CustomThemeSupplier* theme_supplier) {
   if (!theme_supplier)
     return true;
-  if (theme_supplier->get_theme_type() !=
-      CustomThemeSupplier::ThemeType::EXTENSION) {
-    return false;
+
+  using Type = CustomThemeSupplier::ThemeType;
+
+  switch (theme_supplier->get_theme_type()) {
+    case Type::INCREASED_CONTRAST:
+      return true;
+    case Type::EXTENSION: {
+      const std::string& id = theme_supplier->extension_id();
+      return id == kDefaultThemeID || id == kDefaultThemeGalleryID;
+    }
+    case Type::NATIVE_X11:
+    case Type::AUTOGENERATED:
+      return false;
   }
-  const std::string& id = theme_supplier->extension_id();
-  return id == kDefaultThemeID || id == kDefaultThemeGalleryID;
 }
 
 // static
diff --git a/chrome/browser/themes/theme_service.h b/chrome/browser/themes/theme_service.h
index 9f7b368..94ce922 100644
--- a/chrome/browser/themes/theme_service.h
+++ b/chrome/browser/themes/theme_service.h
@@ -154,6 +154,8 @@
   std::unique_ptr<ThemeService::ThemeReinstaller>
   BuildReinstallerForCurrentTheme();
 
+  const ThemeHelper& theme_helper_for_testing() const { return theme_helper_; }
+
  protected:
   // Set a custom default theme instead of the normal default theme.
   virtual void SetCustomDefaultTheme(
diff --git a/chrome/browser/themes/theme_service_unittest.cc b/chrome/browser/themes/theme_service_unittest.cc
index 9fa21be..4dffd62 100644
--- a/chrome/browser/themes/theme_service_unittest.cc
+++ b/chrome/browser/themes/theme_service_unittest.cc
@@ -585,4 +585,25 @@
   }
 }
 
+TEST_F(ThemeServiceTest, NativeIncreasedContrastChanged) {
+  theme_service_->UseDefaultTheme();
+
+  native_theme_.SetUsesHighContrastColors(true);
+  theme_service_->OnNativeThemeUpdated(&native_theme_);
+  EXPECT_TRUE(theme_service_->UsingDefaultTheme());
+  bool using_increased_contrast =
+      theme_service_->GetThemeSupplier() &&
+      theme_service_->GetThemeSupplier()->get_theme_type() ==
+          CustomThemeSupplier::ThemeType::INCREASED_CONTRAST;
+  bool expecting_increased_contrast =
+      theme_service_->theme_helper_for_testing()
+          .ShouldUseIncreasedContrastThemeSupplier(&native_theme_);
+  EXPECT_EQ(using_increased_contrast, expecting_increased_contrast);
+
+  native_theme_.SetUsesHighContrastColors(false);
+  theme_service_->OnNativeThemeUpdated(&native_theme_);
+  EXPECT_TRUE(theme_service_->UsingDefaultTheme());
+  EXPECT_EQ(theme_service_->GetThemeSupplier(), nullptr);
+}
+
 }  // namespace theme_service_internal
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc
index 3bac0b6..1f123d43 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_app_window_browsertest.cc
@@ -8,6 +8,7 @@
 #include "ash/public/cpp/shelf_model.h"
 #include "ash/shell.h"
 #include "base/run_loop.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 #include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
@@ -17,6 +18,7 @@
 #include "chrome/browser/ui/ash/launcher/chrome_launcher_controller_test_util.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/services/app_service/public/cpp/instance.h"
 #include "chrome/services/app_service/public/cpp/instance_registry.h"
@@ -338,6 +340,9 @@
   EXPECT_EQ(0u, windows.size());
 }
 
+// TODO(crbug.com/2078377): Parameterize this test, retire
+// AppServiceWebAndBookmarkAppBrowserTest.
+
 class AppServiceAppWindowWebAppBrowserTest
     : public AppServiceAppWindowBrowserTest {
  protected:
@@ -427,6 +432,32 @@
   EXPECT_EQ(0u, windows.size());
 }
 
+class AppServiceWebAndBookmarkAppBrowserTest
+    : public AppServiceAppWindowWebAppBrowserTest,
+      public ::testing::WithParamInterface<web_app::ProviderType> {
+ protected:
+  AppServiceWebAndBookmarkAppBrowserTest() {
+    if (GetParam() == web_app::ProviderType::kWebApps) {
+      scoped_feature_list_.InitAndEnableFeature(
+          features::kDesktopPWAsWithoutExtensions);
+    } else if (GetParam() == web_app::ProviderType::kBookmarkApps) {
+      scoped_feature_list_.InitAndDisableFeature(
+          features::kDesktopPWAsWithoutExtensions);
+    }
+  }
+  ~AppServiceWebAndBookmarkAppBrowserTest() override = default;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(AppServiceWebAndBookmarkAppBrowserTest, GetWindows) {
+  const std::string app_id = CreateWebApp();
+
+  auto windows = app_service_proxy_->InstanceRegistry().GetWindows(app_id);
+  EXPECT_EQ(1u, windows.size());
+}
+
 class AppServiceAppWindowArcAppBrowserTest
     : public AppServiceAppWindowBrowserTest {
  protected:
@@ -610,3 +641,9 @@
 
   StopInstance();
 }
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         AppServiceWebAndBookmarkAppBrowserTest,
+                         ::testing::Values(web_app::ProviderType::kBookmarkApps,
+                                           web_app::ProviderType::kWebApps),
+                         web_app::ProviderTypeParamToString);
diff --git a/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc b/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
index fe96498..e6df5b9 100644
--- a/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
+++ b/chrome/browser/ui/ash/launcher/app_service/app_service_shelf_context_menu.cc
@@ -191,24 +191,17 @@
 bool AppServiceShelfContextMenu::IsCommandIdChecked(int command_id) const {
   switch (app_type_) {
     case apps::mojom::AppType::kWeb:
-      if (base::FeatureList::IsEnabled(
-              features::kDesktopPWAsWithoutExtensions)) {
-        if (command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
-            command_id <= ash::LAUNCH_TYPE_WINDOW) {
-          auto* provider =
-              web_app::WebAppProvider::Get(controller()->profile());
-          DCHECK(provider);
-          web_app::DisplayMode effective_display_mode =
-              provider->registrar().GetAppEffectiveDisplayMode(
-                  item().id.app_id);
-          return effective_display_mode != web_app::DisplayMode::kUndefined &&
-                 effective_display_mode ==
-                     ConvertLaunchTypeCommandToDisplayMode(command_id);
-        }
-        return ShelfContextMenu::IsCommandIdChecked(command_id);
+      if (command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
+          command_id <= ash::LAUNCH_TYPE_WINDOW) {
+        auto* provider = web_app::WebAppProvider::Get(controller()->profile());
+        DCHECK(provider);
+        web_app::DisplayMode effective_display_mode =
+            provider->registrar().GetAppEffectiveDisplayMode(item().id.app_id);
+        return effective_display_mode != web_app::DisplayMode::kUndefined &&
+               effective_display_mode ==
+                   ConvertLaunchTypeCommandToDisplayMode(command_id);
       }
-      // Otherwise deliberately fall through to fallback on Bookmark Apps.
-      FALLTHROUGH;
+      return ShelfContextMenu::IsCommandIdChecked(command_id);
     case apps::mojom::AppType::kExtension:
       if (command_id >= ash::LAUNCH_TYPE_PINNED_TAB &&
           command_id <= ash::LAUNCH_TYPE_WINDOW) {
@@ -382,23 +375,18 @@
 
 void AppServiceShelfContextMenu::SetLaunchType(int command_id) {
   switch (app_type_) {
-    case apps::mojom::AppType::kWeb:
-      if (base::FeatureList::IsEnabled(
-              features::kDesktopPWAsWithoutExtensions)) {
-        // Web apps can only toggle between kStandalone and kBrowser.
-        web_app::DisplayMode user_display_mode =
-            ConvertLaunchTypeCommandToDisplayMode(command_id);
-        if (user_display_mode != web_app::DisplayMode::kUndefined) {
-          auto* provider =
-              web_app::WebAppProvider::Get(controller()->profile());
-          DCHECK(provider);
-          provider->registry_controller().SetAppUserDisplayMode(
-              item().id.app_id, user_display_mode);
-        }
-        return;
+    case apps::mojom::AppType::kWeb: {
+      // Web apps can only toggle between kStandalone and kBrowser.
+      web_app::DisplayMode user_display_mode =
+          ConvertLaunchTypeCommandToDisplayMode(command_id);
+      if (user_display_mode != web_app::DisplayMode::kUndefined) {
+        auto* provider = web_app::WebAppProvider::Get(controller()->profile());
+        DCHECK(provider);
+        provider->registry_controller().SetAppUserDisplayMode(
+            item().id.app_id, user_display_mode);
       }
-      // Otherwise deliberately fall through to fallback on Bookmark Apps.
-      FALLTHROUGH;
+      return;
+    }
     case apps::mojom::AppType::kExtension:
       SetExtensionLaunchType(command_id);
       return;
diff --git a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
index 4d38088..26c0c4b 100644
--- a/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
+++ b/chrome/browser/ui/ash/launcher/launcher_controller_helper.cc
@@ -28,7 +28,10 @@
 #include "chrome/browser/ui/app_list/internal_app/internal_app_metadata.h"
 #include "chrome/browser/ui/ash/launcher/arc_app_shelf_id.h"
 #include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/web_applications/components/app_registrar.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
+#include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/browser/web_applications/components/web_app_provider_base.h"
 #include "chrome/browser/web_applications/extensions/bookmark_app_util.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
@@ -83,6 +86,36 @@
   return nullptr;
 }
 
+base::Optional<std::string> GetAppIdForTab(Profile* profile,
+                                           content::WebContents* tab) {
+  if (base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions)) {
+    if (web_app::WebAppProviderBase* provider =
+            web_app::WebAppProviderBase::GetProviderBase(profile)) {
+      // Use the Browser's app name to determine the web app for app windows and
+      // use the tab's url for app tabs.
+
+      // Note: It is possible to come here after a tab got removed from the
+      // browser before it gets destroyed, in which case there is no browser.
+      if (Browser* browser = chrome::FindBrowserWithWebContents(tab)) {
+        if (browser->app_controller() && browser->app_controller()->HasAppId())
+          return browser->app_controller()->GetAppId();
+      }
+
+      base::Optional<web_app::AppId> app_id =
+          provider->registrar().FindAppWithUrlInScope(tab->GetURL());
+      if (app_id)
+        return app_id;
+    }
+  }
+
+  const extensions::Extension* extension = GetExtensionForTab(profile, tab);
+  if (extension &&
+      (!extension->from_bookmark() ||
+       !base::FeatureList::IsEnabled(features::kDesktopPWAsWithoutExtensions)))
+    return extension->id();
+  return base::nullopt;
+}
+
 }  // namespace
 
 LauncherControllerHelper::LauncherControllerHelper(Profile* profile)
@@ -126,16 +159,17 @@
         profile_manager->GetLoadedProfiles();
     if (!profile_list.empty()) {
       for (auto* i : profile_list) {
-        const extensions::Extension* extension = GetExtensionForTab(i, tab);
-        if (extension)
-          return extension->id();
+        base::Optional<std::string> app_id = GetAppIdForTab(i, tab);
+        if (app_id.has_value())
+          return *app_id;
       }
       return std::string();
     }
   }
+
   // If there is no profile manager we only use the known profile.
-  const extensions::Extension* extension = GetExtensionForTab(profile_, tab);
-  return extension ? extension->id() : std::string();
+  base::Optional<std::string> app_id = GetAppIdForTab(profile_, tab);
+  return app_id.has_value() ? *app_id : std::string();
 }
 
 bool LauncherControllerHelper::IsValidIDForCurrentUser(
diff --git a/chrome/browser/ui/extensions/app_launch_params.cc b/chrome/browser/ui/extensions/app_launch_params.cc
index 61cb0c7..05f76b3 100644
--- a/chrome/browser/ui/extensions/app_launch_params.cc
+++ b/chrome/browser/ui/extensions/app_launch_params.cc
@@ -29,34 +29,6 @@
   return apps::AppLaunchParams(extension->id(), container, disposition, source);
 }
 
-apps::AppLaunchParams CreateAppIdLaunchParamsWithEventFlags(
-    const std::string& app_id,
-    int event_flags,
-    apps::mojom::AppLaunchSource source,
-    int64_t display_id,
-    apps::mojom::LaunchContainer fallback_container) {
-  WindowOpenDisposition raw_disposition =
-      ui::DispositionFromEventFlags(event_flags);
-
-  apps::mojom::LaunchContainer container;
-  WindowOpenDisposition disposition;
-  if (raw_disposition == WindowOpenDisposition::NEW_FOREGROUND_TAB ||
-      raw_disposition == WindowOpenDisposition::NEW_BACKGROUND_TAB) {
-    container = apps::mojom::LaunchContainer::kLaunchContainerTab;
-    disposition = raw_disposition;
-  } else if (raw_disposition == WindowOpenDisposition::NEW_WINDOW) {
-    container = apps::mojom::LaunchContainer::kLaunchContainerWindow;
-    disposition = raw_disposition;
-  } else {
-    // Look at preference to find the right launch container.  If no preference
-    // is set, launch as a regular tab.
-    container = fallback_container;
-    disposition = WindowOpenDisposition::NEW_FOREGROUND_TAB;
-  }
-  return apps::AppLaunchParams(app_id, container, disposition, source,
-                               display_id);
-}
-
 apps::AppLaunchParams CreateAppLaunchParamsWithEventFlags(
     Profile* profile,
     const extensions::Extension* extension,
@@ -65,6 +37,6 @@
     int64_t display_id) {
   apps::mojom::LaunchContainer fallback_container =
       extensions::GetLaunchContainer(ExtensionPrefs::Get(profile), extension);
-  return CreateAppIdLaunchParamsWithEventFlags(
+  return apps::CreateAppIdLaunchParamsWithEventFlags(
       extension->id(), event_flags, source, display_id, fallback_container);
 }
diff --git a/chrome/browser/ui/extensions/app_launch_params.h b/chrome/browser/ui/extensions/app_launch_params.h
index 79f8df97..631e330 100644
--- a/chrome/browser/ui/extensions/app_launch_params.h
+++ b/chrome/browser/ui/extensions/app_launch_params.h
@@ -23,16 +23,6 @@
     WindowOpenDisposition disposition,
     apps::mojom::AppLaunchSource source);
 
-// Helper to create AppLaunchParams using event flags that allows user to
-// override the user-configured container using modifier keys. |display_id| is
-// the id of the display from which the app is launched.
-apps::AppLaunchParams CreateAppIdLaunchParamsWithEventFlags(
-    const std::string& app_id,
-    int event_flags,
-    apps::mojom::AppLaunchSource source,
-    int64_t display_id,
-    apps::mojom::LaunchContainer fallback_container);
-
 // Helper to create AppLaunchParams, falling back to
 // extensions::GetLaunchContainer() with no modifiers.
 apps::AppLaunchParams CreateAppLaunchParamsWithEventFlags(
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
index 64a86b4..1263192 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_browsertest.cc
@@ -158,7 +158,7 @@
     web_app::SystemWebAppManagerBrowserTest;
 
 // System Web Apps don't get the hosted app buttons.
-IN_PROC_BROWSER_TEST_F(SystemWebAppNonClientFrameViewBrowserTest,
+IN_PROC_BROWSER_TEST_P(SystemWebAppNonClientFrameViewBrowserTest,
                        HideHostedAppButtonContainer) {
   Browser* app_browser =
       WaitForSystemAppInstallAndLaunch(web_app::SystemAppType::SETTINGS);
@@ -267,3 +267,9 @@
   EXPECT_TRUE(app_frame_view_->Contains(icon));
   EXPECT_TRUE(icon->GetVisible());
 }
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SystemWebAppNonClientFrameViewBrowserTest,
+                         ::testing::Values(web_app::ProviderType::kBookmarkApps,
+                                           web_app::ProviderType::kWebApps),
+                         web_app::ProviderTypeParamToString);
diff --git a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
index 2da341a..e263dd7 100644
--- a/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
+++ b/chrome/browser/ui/web_applications/system_web_app_ui_utils.cc
@@ -20,7 +20,6 @@
 #include "chrome/browser/ui/browser_navigator.h"
 #include "chrome/browser/ui/browser_navigator_params.h"
 #include "chrome/browser/ui/browser_window.h"
-#include "chrome/browser/ui/extensions/app_launch_params.h"
 #include "chrome/browser/ui/extensions/application_launch.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "chrome/browser/ui/web_applications/web_app_launch_manager.h"
@@ -68,7 +67,7 @@
       provider->registrar().GetAppEffectiveDisplayMode(app_id.value());
 
   // TODO(calamity): Plumb through better launch sources from callsites.
-  apps::AppLaunchParams params = CreateAppIdLaunchParamsWithEventFlags(
+  apps::AppLaunchParams params = apps::CreateAppIdLaunchParamsWithEventFlags(
       app_id.value(), /*event_flags=*/0,
       apps::mojom::AppLaunchSource::kSourceChromeInternal,
       display::kInvalidDisplayId, /*fallback_container=*/
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom
index fed134a..7f01682 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader.mojom
@@ -57,8 +57,10 @@
   // Callback to receive the container backup progress once the export has
   // started.
   OnBackupProgress(int32 percent);
-  // This is called when the backup succeeded.
-  OnBackupSucceeded();
+  // This is called when the backup succeeded. If the user cancels the backup,
+  // we treat this as a success and proceed with upgrading (though the backup
+  // will not be used if the upgrade fails).
+  OnBackupSucceeded(bool was_cancelled);
   // This is called when the backup failed.
   OnBackupFailed();
   // Handle the result of the prechecks.
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc
index 9dfd52e4..51a80a4 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.cc
@@ -107,9 +107,9 @@
   page_->OnBackupProgress(percent);
 }
 
-void CrostiniUpgraderPageHandler::OnBackupSucceeded() {
+void CrostiniUpgraderPageHandler::OnBackupSucceeded(bool was_cancelled) {
   Redisplay();
-  page_->OnBackupSucceeded();
+  page_->OnBackupSucceeded(was_cancelled);
 }
 
 void CrostiniUpgraderPageHandler::OnBackupFailed() {
diff --git a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.h b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.h
index fd3a65a..9eb0248b 100644
--- a/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.h
+++ b/chrome/browser/ui/webui/chromeos/crostini_upgrader/crostini_upgrader_page_handler.h
@@ -48,7 +48,7 @@
 
   // CrostiniUpgraderUIObserver
   void OnBackupProgress(int percent) override;
-  void OnBackupSucceeded() override;
+  void OnBackupSucceeded(bool was_cancelled) override;
   void OnBackupFailed() override;
   void PrecheckStatus(chromeos::crostini_upgrader::mojom::UpgradePrecheckStatus
                           status) override;
diff --git a/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.cc
index 9100315..bbf1627 100644
--- a/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.cc
@@ -4,11 +4,14 @@
 
 #include "chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h"
 
+#include "ash/public/cpp/ash_pref_names.h"
 #include "base/command_line.h"
 #include "chrome/browser/chromeos/login/screens/marketing_opt_in_screen.h"
+#include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/grit/generated_resources.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "components/login/localized_values_builder.h"
+#include "components/prefs/pref_service.h"
 
 namespace chromeos {
 
@@ -33,6 +36,16 @@
                IDS_LOGIN_MARKETING_OPT_IN_SCREEN_GET_CHROMEBOOK_UPDATES);
   builder->Add("marketingOptInScreenAllSet",
                IDS_LOGIN_MARKETING_OPT_IN_SCREEN_ALL_SET);
+  builder->Add("marketingOptInA11yLinkLabel",
+               IDS_MARKETING_OPT_IN_ACCESSIBILITY_SETTINGS_LINK);
+  builder->Add("finalA11yPageTitle", IDS_MARKETING_OPT_IN_ACCESSIBILITY_TITLE);
+  builder->Add("finalA11yPageNavButtonSettingTitle",
+               IDS_MARKETING_OPT_IN_ACCESSIBILITY_NAV_BUTTON_SETTING_TITLE);
+  builder->Add(
+      "finalA11yPageNavButtonSettingDescription",
+      IDS_MARKETING_OPT_IN_ACCESSIBILITY_NAV_BUTTON_SETTING_DESCRIPTION);
+  builder->Add("finalA11yPageDoneButtonTitle",
+               IDS_MARKETING_OPT_IN_ACCESSIBILITY_DONE_BUTTON);
 }
 
 void MarketingOptInScreenHandler::Bind(MarketingOptInScreen* screen) {
@@ -50,11 +63,26 @@
   CallJS("login.MarketingOptInScreen.updateAllSetButtonVisibility", visible);
 }
 
+void MarketingOptInScreenHandler::UpdateA11ySettingsButtonVisibility(
+    bool shown) {
+  CallJS("login.MarketingOptInScreen.updateA11ySettingsButtonVisibility",
+         shown);
+}
+
+void MarketingOptInScreenHandler::UpdateA11yShelfNavigationButtonToggle(
+    bool enabled) {
+  CallJS("login.MarketingOptInScreen.updateA11yNavigationButtonToggle",
+         enabled);
+}
+
 void MarketingOptInScreenHandler::Initialize() {}
 
 void MarketingOptInScreenHandler::RegisterMessages() {
   AddCallback("login.MarketingOptInScreen.allSet",
               &MarketingOptInScreenHandler::HandleAllSet);
+  AddCallback(
+      "login.MarketingOptInScreen.setA11yNavigationButtonsEnabled",
+      &MarketingOptInScreenHandler::HandleSetA11yNavigationButtonsEnabled);
 }
 
 void MarketingOptInScreenHandler::GetAdditionalParameters(
@@ -71,4 +99,11 @@
   screen_->OnAllSet(play_communications_opt_in, tips_communications_opt_in);
 }
 
+void MarketingOptInScreenHandler::HandleSetA11yNavigationButtonsEnabled(
+    bool enabled) {
+  ProfileManager::GetActiveUserProfile()->GetPrefs()->SetBoolean(
+      ash::prefs::kAccessibilityTabletModeShelfNavigationButtonsEnabled,
+      enabled);
+}
+
 }  // namespace chromeos
diff --git a/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h b/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h
index 50ae413..9d20c08 100644
--- a/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h
+++ b/chrome/browser/ui/webui/chromeos/login/marketing_opt_in_screen_handler.h
@@ -31,6 +31,13 @@
 
   // Shows or hides the screen's all set (next) button visibility.
   virtual void UpdateAllSetButtonVisibility(bool visible) = 0;
+
+  // Sets whether the a11y Settings button is visible.
+  virtual void UpdateA11ySettingsButtonVisibility(bool shown) = 0;
+
+  // Sets whether the a11y setting for showing shelf navigation buttons is
+  // toggled on or off.
+  virtual void UpdateA11yShelfNavigationButtonToggle(bool enabled) = 0;
 };
 
 // The sole implementation of the MarketingOptInScreenView, using WebUI.
@@ -51,6 +58,8 @@
   void Show() override;
   void Hide() override;
   void UpdateAllSetButtonVisibility(bool visible) override;
+  void UpdateA11ySettingsButtonVisibility(bool shown) override;
+  void UpdateA11yShelfNavigationButtonToggle(bool enabled) override;
 
  private:
   // BaseScreenHandler:
@@ -61,6 +70,7 @@
   // WebUI event handler.
   void HandleAllSet(bool play_communications_opt_in,
                     bool tips_communications_opt_in);
+  void HandleSetA11yNavigationButtonsEnabled(bool enabled);
 
   MarketingOptInScreen* screen_ = nullptr;
 
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom b/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom
index 2756fead..e4a80fe 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom
@@ -29,12 +29,22 @@
 
 // A collection of background images.
 struct BackgroundCollection {
+  // Collection identifier.
+  string id;
   // Localized string of the collection name.
   string label;
   // URL to a preview image for the collection. Can point to untrusted content.
   url.mojom.Url preview_image_url;
 };
 
+// A background image in a collection.
+struct BackgroundImage {
+  // Localized string of extra image info.
+  string label;
+  // URL to a preview of the image. Can point to untrusted content.
+  url.mojom.Url preview_image_url;
+};
+
 // A predefined theme provided by Chrome. Created from data embedded in the
 // Chrome binary.
 struct ChromeTheme {
@@ -127,6 +137,8 @@
   RevertThemeChanges();
   // Returns the collections of background images.
   GetBackgroundCollections() => (array<BackgroundCollection> collections);
+  // Returns the images of a collection identified by |collection_id|.
+  GetBackgroundImages(string collection_id) => (array<BackgroundImage> images);
 };
 
 // WebUI-side handler for requests from the browser.
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
index 87db736..398b679 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
@@ -204,7 +204,7 @@
 
 void NewTabPageHandler::GetBackgroundCollections(
     GetBackgroundCollectionsCallback callback) {
-  if (!ntp_background_service_) {
+  if (!ntp_background_service_ || background_collections_callback_) {
     std::move(callback).Run(
         std::vector<new_tab_page::mojom::BackgroundCollectionPtr>());
     return;
@@ -213,6 +213,23 @@
   ntp_background_service_->FetchCollectionInfo();
 }
 
+void NewTabPageHandler::GetBackgroundImages(
+    const std::string& collection_id,
+    GetBackgroundImagesCallback callback) {
+  if (background_images_callback_) {
+    std::move(background_images_callback_)
+        .Run(std::vector<new_tab_page::mojom::BackgroundImagePtr>());
+  }
+  if (!ntp_background_service_) {
+    std::move(callback).Run(
+        std::vector<new_tab_page::mojom::BackgroundImagePtr>());
+    return;
+  }
+  images_request_collection_id_ = collection_id;
+  background_images_callback_ = std::move(callback);
+  ntp_background_service_->FetchCollectionImageInfo(collection_id);
+}
+
 void NewTabPageHandler::NtpThemeChanged(const NtpTheme& ntp_theme) {
   page_->SetTheme(MakeTheme(ntp_theme));
 }
@@ -248,6 +265,7 @@
   std::vector<new_tab_page::mojom::BackgroundCollectionPtr> collections;
   for (const auto& info : ntp_background_service_->collection_info()) {
     auto collection = new_tab_page::mojom::BackgroundCollection::New();
+    collection->id = info.collection_id;
     collection->label = info.collection_name;
     collection->preview_image_url = GURL(info.preview_image_url);
     collections.push_back(std::move(collection));
@@ -255,7 +273,25 @@
   std::move(background_collections_callback_).Run(std::move(collections));
 }
 
-void NewTabPageHandler::OnCollectionImagesAvailable() {}
+void NewTabPageHandler::OnCollectionImagesAvailable() {
+  if (!background_images_callback_) {
+    return;
+  }
+  std::vector<new_tab_page::mojom::BackgroundImagePtr> images;
+  if (ntp_background_service_->collection_images().empty()) {
+    std::move(background_images_callback_).Run(std::move(images));
+  }
+  auto collection_id =
+      ntp_background_service_->collection_images()[0].collection_id;
+  for (const auto& info : ntp_background_service_->collection_images()) {
+    DCHECK(info.collection_id == collection_id);
+    auto image = new_tab_page::mojom::BackgroundImage::New();
+    image->preview_image_url = GURL(info.thumbnail_image_url);
+    image->label = !info.attribution.empty() ? info.attribution[0] : "";
+    images.push_back(std::move(image));
+  }
+  std::move(background_images_callback_).Run(std::move(images));
+}
 
 void NewTabPageHandler::OnNextCollectionImageAvailable() {}
 
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h
index 029ba35..e6772963 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h
@@ -59,6 +59,8 @@
   void RevertThemeChanges() override;
   void GetBackgroundCollections(
       GetBackgroundCollectionsCallback callback) override;
+  void GetBackgroundImages(const std::string& collection_id,
+                           GetBackgroundImagesCallback callback) override;
 
  private:
   // InstantServiceObserver:
@@ -76,6 +78,8 @@
   NtpBackgroundService* ntp_background_service_;
   GURL last_blacklisted_;
   GetBackgroundCollectionsCallback background_collections_callback_;
+  std::string images_request_collection_id_;
+  GetBackgroundImagesCallback background_images_callback_;
   mojo::Remote<new_tab_page::mojom::Page> page_;
   mojo::Receiver<new_tab_page::mojom::PageHandler> receiver_;
 
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index 0a3519d..8b72132 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -73,6 +73,7 @@
       {"urlField", IDS_NTP_CUSTOM_LINKS_URL},
 
       // Customize button and dialog.
+      {"backButton", IDS_ACCNAME_BACK},
       {"backgroundsMenuItem", IDS_NTP_CUSTOMIZE_MENU_BACKGROUND_LABEL},
       {"cancelButton", IDS_CANCEL},
       {"colorPickerLabel", IDS_NTP_CUSTOMIZE_COLOR_PICKER_LABEL},
@@ -80,17 +81,17 @@
       {"customizeThisPage", IDS_NTP_CUSTOM_BG_CUSTOMIZE_NTP_LABEL},
       {"defaultThemeLabel", IDS_NTP_CUSTOMIZE_DEFAULT_LABEL},
       {"doneButton", IDS_DONE},
-      {"shortcutsMenuItem", IDS_NTP_CUSTOMIZE_MENU_SHORTCUTS_LABEL},
-      {"themesMenuItem", IDS_NTP_CUSTOMIZE_MENU_COLOR_LABEL},
-      {"thirdPartyThemeDescription", IDS_NTP_CUSTOMIZE_3PT_THEME_DESC},
-      {"uninstallThirdPartyThemeButton", IDS_NTP_CUSTOMIZE_3PT_THEME_UNINSTALL},
       {"hideShortcuts", IDS_NTP_CUSTOMIZE_HIDE_SHORTCUTS_LABEL},
       {"hideShortcutsDesc", IDS_NTP_CUSTOMIZE_HIDE_SHORTCUTS_DESC},
       {"mostVisited", IDS_NTP_CUSTOMIZE_MOST_VISITED_LABEL},
       {"myShortcuts", IDS_NTP_CUSTOMIZE_MY_SHORTCUTS_LABEL},
       {"shortcutsCurated", IDS_NTP_CUSTOMIZE_MY_SHORTCUTS_DESC},
+      {"shortcutsMenuItem", IDS_NTP_CUSTOMIZE_MENU_SHORTCUTS_LABEL},
       {"shortcutsOption", IDS_NTP_CUSTOMIZE_MENU_SHORTCUTS_LABEL},
       {"shortcutsSuggested", IDS_NTP_CUSTOMIZE_MOST_VISITED_DESC},
+      {"themesMenuItem", IDS_NTP_CUSTOMIZE_MENU_COLOR_LABEL},
+      {"thirdPartyThemeDescription", IDS_NTP_CUSTOMIZE_3PT_THEME_DESC},
+      {"uninstallThirdPartyThemeButton", IDS_NTP_CUSTOMIZE_3PT_THEME_UNINSTALL},
 
       // Voice search.
       {"audioError", IDS_NEW_TAB_VOICE_AUDIO_ERROR},
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 5aee8cc..2f2a4490 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -1328,6 +1328,8 @@
     {"siteSettingsCategoryCamera", IDS_SETTINGS_SITE_SETTINGS_CAMERA},
     {"siteSettingsCameraLabel", IDS_SETTINGS_SITE_SETTINGS_CAMERA_LABEL},
     {"cookiePageTitle", IDS_SETTINGS_COOKIES_PAGE},
+    {"cookiesManageSiteSpecificExceptions",
+     IDS_SETTINGS_COOKIES_SITE_SPECIFIC_EXCEPTIONS},
     {"siteSettingsCategoryCookies", IDS_SETTINGS_SITE_SETTINGS_COOKIES},
     {"siteSettingsCategoryHandlers", IDS_SETTINGS_SITE_SETTINGS_HANDLERS},
     {"siteSettingsCategoryImages", IDS_SETTINGS_SITE_SETTINGS_IMAGES},
diff --git a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
index 08f0c95..a73bfca6 100644
--- a/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
+++ b/chrome/browser/ui/webui/signin/user_manager_screen_handler.cc
@@ -541,11 +541,6 @@
     stat->SetKey("count", base::Value(item.count));
     return_value.SetWithoutPathExpansion(item.category, std::move(stat));
   }
-  if (result.size() == profiles::kProfileStatisticsCategories.size()) {
-    // All categories are finished.
-    UMA_HISTOGRAM_TIMES("Profile.RemoveUserWarningStatsTime",
-                        base::Time::Now() - start_time);
-  }
   web_ui()->CallJavascriptFunctionUnsafe("updateRemoveWarningDialog",
                                          base::Value(profile_path.value()),
                                          return_value);
diff --git a/chrome/browser/web_applications/components/web_app_icon_downloader.cc b/chrome/browser/web_applications/components/web_app_icon_downloader.cc
index fb3d253..8740163 100644
--- a/chrome/browser/web_applications/components/web_app_icon_downloader.cc
+++ b/chrome/browser/web_applications/components/web_app_icon_downloader.cc
@@ -23,6 +23,7 @@
     WebAppIconDownloaderCallback callback)
     : content::WebContentsObserver(web_contents),
       need_favicon_urls_(true),
+      fail_all_if_any_fail_(false),
       extra_favicon_urls_(extra_favicon_urls),
       callback_(std::move(callback)),
       histogram_(histogram) {}
@@ -33,6 +34,10 @@
   need_favicon_urls_ = false;
 }
 
+void WebAppIconDownloader::FailAllIfAnyFail() {
+  fail_all_if_any_fail_ = true;
+}
+
 void WebAppIconDownloader::Start() {
   // Favicons are not supported in extension WebContents.
   if (IsValidExtensionUrl(web_contents()->GetLastCommittedURL()))
@@ -124,11 +129,16 @@
     base::UmaHistogramExactLinear(histogram_name, http_status_code / 100, 5);
   }
 
+  if (fail_all_if_any_fail_ && bitmaps.empty()) {
+    CancelDownloads();
+    return;
+  }
+
   icons_map_[image_url] = bitmaps;
 
   // Once all requests have been resolved, perform post-download tasks.
   if (in_progress_requests_.empty() && !need_favicon_urls_) {
-    std::move(callback_).Run(true, std::move(icons_map_));
+    std::move(callback_).Run(/*success=*/true, std::move(icons_map_));
   }
 }
 
@@ -139,10 +149,7 @@
       !navigation_handle->HasCommitted() || navigation_handle->IsSameDocument())
     return;
 
-  // Clear all pending requests.
-  in_progress_requests_.clear();
-  icons_map_.clear();
-  std::move(callback_).Run(false, icons_map_);
+  CancelDownloads();
 }
 
 void WebAppIconDownloader::DidUpdateFaviconURL(
@@ -156,4 +163,10 @@
   FetchIcons(candidates);
 }
 
+void WebAppIconDownloader::CancelDownloads() {
+  in_progress_requests_.clear();
+  icons_map_.clear();
+  std::move(callback_).Run(/*success=*/false, icons_map_);
+}
+
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/components/web_app_icon_downloader.h b/chrome/browser/web_applications/components/web_app_icon_downloader.h
index 842b04d..f50077e 100644
--- a/chrome/browser/web_applications/components/web_app_icon_downloader.h
+++ b/chrome/browser/web_applications/components/web_app_icon_downloader.h
@@ -53,6 +53,8 @@
   // |extra_favicon_urls| argument).
   void SkipPageFavicons();
 
+  void FailAllIfAnyFail();
+
   void Start();
 
  private:
@@ -85,9 +87,15 @@
   void DidUpdateFaviconURL(
       const std::vector<blink::mojom::FaviconURLPtr>& candidates) override;
 
+  void CancelDownloads();
+
   // Whether we need to fetch favicons from the renderer.
   bool need_favicon_urls_;
 
+  // Whether we consider all requests to have failed if any individual URL fails
+  // to load.
+  bool fail_all_if_any_fail_;
+
   // URLs that aren't given by WebContentsObserver::DidUpdateFaviconURL() that
   // should be used for this favicon. This is necessary in order to get touch
   // icons on non-android environments.
diff --git a/chrome/browser/web_applications/components/web_app_shortcut.cc b/chrome/browser/web_applications/components/web_app_shortcut.cc
index 9341eccb..6498cca 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut.cc
+++ b/chrome/browser/web_applications/components/web_app_shortcut.cc
@@ -92,15 +92,13 @@
 }
 
 base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
-                                      const std::string& extension_id,
+                                      const std::string& app_id,
                                       const GURL& url) {
   DCHECK(!profile_path.empty());
   base::FilePath app_data_dir(profile_path.Append(chrome::kWebAppDirname));
 
-  if (!extension_id.empty()) {
-    return app_data_dir.AppendASCII(
-        GenerateApplicationNameFromAppId(extension_id));
-  }
+  if (!app_id.empty())
+    return app_data_dir.AppendASCII(GenerateApplicationNameFromAppId(app_id));
 
   std::string host(url.host());
   std::string scheme(url.has_scheme() ? url.scheme() : "http");
diff --git a/chrome/browser/web_applications/components/web_app_shortcut.h b/chrome/browser/web_applications/components/web_app_shortcut.h
index e35a961f..87849300 100644
--- a/chrome/browser/web_applications/components/web_app_shortcut.h
+++ b/chrome/browser/web_applications/components/web_app_shortcut.h
@@ -99,11 +99,16 @@
 // Compute a deterministic name based on data in the shortcut_info.
 std::string GenerateApplicationNameFromInfo(const ShortcutInfo& shortcut_info);
 
-// Gets the user data directory for given web app. The path for the directory is
-// based on |extension_id|. If |extension_id| is empty then |url| is used
-// to construct a unique ID.
+// Returns a per-app directory for OS-specific web app data to handle OS
+// registration and unregistration. To store manifest resources, use
+// GetManifestResourcesDirectoryForApp() declared in web_app_utils.h.
+//
+// The path for the directory is based on |app_id|. If |app_id| is empty then
+// |url| is used to construct a unique ID.
+// TODO(crbug.com/877898): Rename this function to
+// GetOsIntegrationDataDirectoryForApp().
 base::FilePath GetWebAppDataDirectory(const base::FilePath& profile_path,
-                                      const std::string& extension_id,
+                                      const std::string& app_id,
                                       const GURL& url);
 
 // Callback made when CreateShortcuts has finished trying to create the
diff --git a/chrome/browser/web_applications/components/web_app_utils.cc b/chrome/browser/web_applications/components/web_app_utils.cc
index 93e57f5..d3016233 100644
--- a/chrome/browser/web_applications/components/web_app_utils.cc
+++ b/chrome/browser/web_applications/components/web_app_utils.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/file_path.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_constants.h"
 
 #if defined(OS_CHROMEOS)
 #include "chrome/browser/chromeos/profiles/profile_helper.h"
@@ -14,8 +15,11 @@
 
 namespace web_app {
 
-constexpr base::FilePath::CharType kWebAppsDirectoryName[] =
-    FILE_PATH_LITERAL("WebApps");
+constexpr base::FilePath::CharType kManifestResourcesDirectoryName[] =
+    FILE_PATH_LITERAL("Manifest Resources");
+
+constexpr base::FilePath::CharType kTempDirectoryName[] =
+    FILE_PATH_LITERAL("Temp");
 
 bool AreWebAppsEnabled(Profile* profile) {
   if (!profile)
@@ -67,8 +71,29 @@
   return is_web_app_metrics_enabled ? original_profile : nullptr;
 }
 
-base::FilePath GetWebAppsDirectory(Profile* profile) {
-  return profile->GetPath().Append(base::FilePath(kWebAppsDirectoryName));
+base::FilePath GetWebAppsRootDirectory(Profile* profile) {
+  return profile->GetPath().Append(chrome::kWebAppDirname);
+}
+
+base::FilePath GetManifestResourcesDirectory(
+    const base::FilePath& web_apps_root_directory) {
+  return web_apps_root_directory.Append(kManifestResourcesDirectoryName);
+}
+
+base::FilePath GetManifestResourcesDirectory(Profile* profile) {
+  return GetManifestResourcesDirectory(GetWebAppsRootDirectory(profile));
+}
+
+base::FilePath GetManifestResourcesDirectoryForApp(
+    const base::FilePath& web_apps_root_directory,
+    const AppId& app_id) {
+  return GetManifestResourcesDirectory(web_apps_root_directory)
+      .AppendASCII(app_id);
+}
+
+base::FilePath GetWebAppsTempDirectory(
+    const base::FilePath& web_apps_root_directory) {
+  return web_apps_root_directory.Append(kTempDirectoryName);
 }
 
 std::string GetProfileCategoryForLogging(Profile* profile) {
diff --git a/chrome/browser/web_applications/components/web_app_utils.h b/chrome/browser/web_applications/components/web_app_utils.h
index 36abf4a..9bb62799 100644
--- a/chrome/browser/web_applications/components/web_app_utils.h
+++ b/chrome/browser/web_applications/components/web_app_utils.h
@@ -7,6 +7,8 @@
 
 #include <string>
 
+#include "chrome/browser/web_applications/components/web_app_id.h"
+
 class Profile;
 
 namespace base {
@@ -35,7 +37,29 @@
 content::BrowserContext* GetBrowserContextForWebAppMetrics(
     content::BrowserContext* context);
 
-base::FilePath GetWebAppsDirectory(Profile* profile);
+// Returns a root directory for all Web Apps themed data.
+//
+// All the related directory getters always require |web_apps_root_directory| as
+// a first argument to avoid directory confusions.
+base::FilePath GetWebAppsRootDirectory(Profile* profile);
+
+// Returns a directory to store local cached manifest resources in
+// OS-independent manner. Use GetManifestResourcesDirectoryForApp function to
+// get per-app manifest resources directory.
+//
+// To store OS-specific integration data, use GetWebAppDataDirectory declared in
+// web_app_shortcut.h.
+base::FilePath GetManifestResourcesDirectory(
+    const base::FilePath& web_apps_root_directory);
+base::FilePath GetManifestResourcesDirectory(Profile* profile);
+
+// Returns per-app directory name to store manifest resources.
+base::FilePath GetManifestResourcesDirectoryForApp(
+    const base::FilePath& web_apps_root_directory,
+    const AppId& app_id);
+
+base::FilePath GetWebAppsTempDirectory(
+    const base::FilePath& web_apps_root_directory);
 
 // The return value (profile categories) are used to report metrics. They are
 // persisted to logs and should not be renamed. If new names are added, update
diff --git a/chrome/browser/web_applications/manifest_update_manager.cc b/chrome/browser/web_applications/manifest_update_manager.cc
index ec07fe1..2340501 100644
--- a/chrome/browser/web_applications/manifest_update_manager.cc
+++ b/chrome/browser/web_applications/manifest_update_manager.cc
@@ -74,10 +74,12 @@
 
 void ManifestUpdateManager::SetSubsystems(
     AppRegistrar* registrar,
+    AppIconManager* icon_manager,
     WebAppUiManager* ui_manager,
     InstallManager* install_manager,
     SystemWebAppManager* system_web_app_manager) {
   registrar_ = registrar;
+  icon_manager_ = icon_manager;
   ui_manager_ = ui_manager;
   install_manager_ = install_manager;
   system_web_app_manager_ = system_web_app_manager;
@@ -125,8 +127,8 @@
                   url, app_id, web_contents,
                   base::Bind(&ManifestUpdateManager::OnUpdateStopped,
                              base::Unretained(this)),
-                  hang_update_checks_for_testing_, *registrar_, ui_manager_,
-                  install_manager_));
+                  hang_update_checks_for_testing_, *registrar_, *icon_manager_,
+                  ui_manager_, install_manager_));
 }
 
 // AppRegistrarObserver:
diff --git a/chrome/browser/web_applications/manifest_update_manager.h b/chrome/browser/web_applications/manifest_update_manager.h
index 5cd0469..00b281fe 100644
--- a/chrome/browser/web_applications/manifest_update_manager.h
+++ b/chrome/browser/web_applications/manifest_update_manager.h
@@ -46,6 +46,7 @@
   ~ManifestUpdateManager() override;
 
   void SetSubsystems(AppRegistrar* registrar,
+                     AppIconManager* icon_manager,
                      WebAppUiManager* ui_manager,
                      InstallManager* install_manager,
                      SystemWebAppManager* system_web_app_manager);
@@ -84,6 +85,7 @@
 
   Profile* const profile_ = nullptr;
   AppRegistrar* registrar_ = nullptr;
+  AppIconManager* icon_manager_ = nullptr;
   WebAppUiManager* ui_manager_ = nullptr;
   InstallManager* install_manager_ = nullptr;
   SystemWebAppManager* system_web_app_manager_ = nullptr;
diff --git a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
index eca394dc..a736cd6 100644
--- a/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
+++ b/chrome/browser/web_applications/manifest_update_manager_browsertest.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/installable/installable_metrics.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/app_registry_controller.h"
 #include "chrome/browser/web_applications/components/install_finalizer.h"
 #include "chrome/browser/web_applications/components/install_manager.h"
@@ -27,6 +28,7 @@
 #include "chrome/common/chrome_features.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "content/public/test/url_loader_interceptor.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -683,6 +685,121 @@
             DisplayMode::kStandalone);
 }
 
+IN_PROC_BROWSER_TEST_P(ManifestUpdateManagerBrowserTest,
+                       CheckFindsIconContentChange) {
+  constexpr char kManifest[] = R"(
+    {
+      "name": "Test app name",
+      "start_url": ".",
+      "scope": "/",
+      "display": "standalone",
+      "icons": [
+        {
+          "src": "/web_apps/basic-192.png",
+          "sizes": "192x192",
+          "type": "image/png"
+        }
+      ]
+    }
+  )";
+  OverrideManifest(kManifest, {});
+  AppId app_id = InstallWebApp();
+
+  // Replace the contents of basic-192.png with blue-192.png without changing
+  // the URL.
+  content::URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
+      [this](content::URLLoaderInterceptor::RequestParams* params)
+          -> bool /*intercepted*/ {
+        if (params->url_request.url ==
+            http_server_.GetURL("/web_apps/basic-192.png")) {
+          content::URLLoaderInterceptor::WriteResponse(
+              "chrome/test/data/web_apps/blue-192.png", params->client.get());
+          return true;
+        }
+        return false;
+      }));
+
+  EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
+            ManifestUpdateResult::kAppUpdated);
+  histogram_tester_.ExpectBucketCount(kUpdateHistogramName,
+                                      ManifestUpdateResult::kAppUpdated, 1);
+
+  // Check that the installed icon is now blue.
+  base::RunLoop run_loop;
+  GetProvider().icon_manager().ReadIcons(
+      app_id, {192},
+      base::BindLambdaForTesting(
+          [&run_loop](std::map<SquareSizePx, SkBitmap> icon_bitmaps) {
+            run_loop.Quit();
+            EXPECT_EQ(icon_bitmaps.at(192).getColor(0, 0), SK_ColorBLUE);
+          }));
+  run_loop.Run();
+}
+
+IN_PROC_BROWSER_TEST_P(ManifestUpdateManagerBrowserTest,
+                       CheckIgnoresIconDownloadFail) {
+  constexpr char kManifest[] = R"(
+    {
+      "name": "Test app name",
+      "start_url": ".",
+      "scope": "/",
+      "display": "standalone",
+      "icons": [
+        {
+          "src": "/web_apps/basic-48.png",
+          "sizes": "48x48",
+          "type": "image/png"
+        },
+        {
+          "src": "/web_apps/basic-192.png",
+          "sizes": "192x192",
+          "type": "image/png"
+        }
+      ]
+    }
+  )";
+  OverrideManifest(kManifest, {});
+  AppId app_id = InstallWebApp();
+
+  // Make basic-48.png fail to download.
+  // Replace the contents of basic-192.png with blue-192.png without changing
+  // the URL.
+  content::URLLoaderInterceptor url_interceptor(base::BindLambdaForTesting(
+      [this](content::URLLoaderInterceptor::RequestParams* params)
+          -> bool /*intercepted*/ {
+        if (params->url_request.url ==
+            http_server_.GetURL("/web_apps/basic-48.png")) {
+          content::URLLoaderInterceptor::WriteResponse("malformed response", "",
+                                                       params->client.get());
+          return true;
+        }
+        if (params->url_request.url ==
+            http_server_.GetURL("/web_apps/basic-192.png")) {
+          content::URLLoaderInterceptor::WriteResponse(
+              "chrome/test/data/web_apps/blue-192.png", params->client.get());
+          return true;
+        }
+        return false;
+      }));
+
+  EXPECT_EQ(GetResultAfterPageLoad(GetAppURL(), &app_id),
+            ManifestUpdateResult::kIconDownloadFailed);
+  histogram_tester_.ExpectBucketCount(
+      kUpdateHistogramName, ManifestUpdateResult::kIconDownloadFailed, 1);
+
+  // Check that the installed icon is still black.
+  base::RunLoop run_loop;
+  GetProvider().icon_manager().ReadIcons(
+      app_id, {48, 192},
+      base::BindLambdaForTesting(
+          [&run_loop](std::map<SquareSizePx, SkBitmap> icon_bitmaps) {
+            run_loop.Quit();
+            EXPECT_EQ(icon_bitmaps.at(48).getColor(0, 0), SK_ColorBLACK);
+            EXPECT_EQ(icon_bitmaps.at(192).getColor(0, 0), SK_ColorBLACK);
+          }));
+  run_loop.Run();
+}
+
 class ManifestUpdateManagerSystemAppBrowserTest
     : public ManifestUpdateManagerBrowserTest {
  public:
diff --git a/chrome/browser/web_applications/manifest_update_task.cc b/chrome/browser/web_applications/manifest_update_task.cc
index ad694c9d5f..0b428964 100644
--- a/chrome/browser/web_applications/manifest_update_task.cc
+++ b/chrome/browser/web_applications/manifest_update_task.cc
@@ -6,6 +6,7 @@
 
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/browser/installable/installable_manager.h"
+#include "chrome/browser/web_applications/components/app_icon_manager.h"
 #include "chrome/browser/web_applications/components/app_registrar.h"
 #include "chrome/browser/web_applications/components/install_manager.h"
 #include "chrome/browser/web_applications/components/web_app_constants.h"
@@ -13,6 +14,7 @@
 #include "chrome/browser/web_applications/components/web_app_install_utils.h"
 #include "chrome/browser/web_applications/components/web_app_ui_manager.h"
 #include "chrome/common/web_application_info.h"
+#include "ui/gfx/skia_util.h"
 
 namespace web_app {
 
@@ -22,10 +24,12 @@
                                        StoppedCallback stopped_callback,
                                        bool hang_for_testing,
                                        const AppRegistrar& registrar,
+                                       const AppIconManager& icon_manager,
                                        WebAppUiManager* ui_manager,
                                        InstallManager* install_manager)
     : content::WebContentsObserver(web_contents),
       registrar_(registrar),
+      icon_manager_(icon_manager),
       ui_manager_(*ui_manager),
       install_manager_(*install_manager),
       url_(url),
@@ -69,10 +73,13 @@
   switch (stage_) {
     case Stage::kPendingPageLoad:
     case Stage::kPendingInstallableData:
+    case Stage::kPendingIconDownload:
       DestroySelf(ManifestUpdateResult::kWebContentsDestroyed);
       return;
+    case Stage::kPendingIconReadFromDisk:
     case Stage::kPendingWindowsClosed:
     case Stage::kPendingInstallation:
+      // These stages should have stopped listening to the web contents.
       NOTREACHED();
       Observe(nullptr);
       break;
@@ -88,59 +95,149 @@
   }
 
   DCHECK(data.manifest);
-  std::unique_ptr<WebApplicationInfo> web_application_info =
-      std::make_unique<WebApplicationInfo>();
-  UpdateWebAppInfoFromManifest(*data.manifest, web_application_info.get());
-  if (!IsUpdateNeeded(*web_application_info)) {
-    DestroySelf(ManifestUpdateResult::kAppUpToDate);
+  web_application_info_.emplace();
+  UpdateWebAppInfoFromManifest(*data.manifest, &web_application_info_.value());
+
+  if (IsUpdateNeededForManifest()) {
+    UpdateAfterWindowsClose();
     return;
   }
 
-  stage_ = Stage::kPendingWindowsClosed;
-  Observe(nullptr);
-  ui_manager_.NotifyOnAllAppWindowsClosed(
-      app_id_, base::BindOnce(&ManifestUpdateTask::OnAllAppWindowsClosed,
-                              AsWeakPtr(), std::move(web_application_info)));
+  LoadAndCheckIconContents();
 }
 
-bool ManifestUpdateTask::IsUpdateNeeded(
-    const WebApplicationInfo& web_application_info) const {
-  if (app_id_ != GenerateAppIdFromURL(web_application_info.app_url))
+bool ManifestUpdateTask::IsUpdateNeededForManifest() const {
+  DCHECK(web_application_info_.has_value());
+
+  if (app_id_ != GenerateAppIdFromURL(web_application_info_->app_url))
     return false;
 
-  if (web_application_info.theme_color != registrar_.GetAppThemeColor(app_id_))
+  if (web_application_info_->theme_color !=
+      registrar_.GetAppThemeColor(app_id_))
     return true;
 
-  if (web_application_info.scope != registrar_.GetAppScopeInternal(app_id_))
+  if (web_application_info_->scope != registrar_.GetAppScopeInternal(app_id_))
     return true;
 
-  if (web_application_info.display_mode !=
+  if (web_application_info_->display_mode !=
       registrar_.GetAppDisplayMode(app_id_)) {
     return true;
   }
 
-  if (web_application_info.icon_infos != registrar_.GetAppIconInfos(app_id_))
+  if (web_application_info_->icon_infos != registrar_.GetAppIconInfos(app_id_))
     return true;
 
   // TODO(crbug.com/926083): Check more manifest fields.
   return false;
 }
 
-void ManifestUpdateTask::OnAllAppWindowsClosed(
-    std::unique_ptr<WebApplicationInfo> web_application_info) {
+void ManifestUpdateTask::UpdateAfterWindowsClose() {
+  DCHECK(stage_ == Stage::kPendingInstallableData ||
+         stage_ == Stage::kPendingIconReadFromDisk);
+  stage_ = Stage::kPendingWindowsClosed;
+  Observe(nullptr);
+
+  ui_manager_.NotifyOnAllAppWindowsClosed(
+      app_id_,
+      base::BindOnce(&ManifestUpdateTask::OnAllAppWindowsClosed, AsWeakPtr()));
+}
+
+void ManifestUpdateTask::LoadAndCheckIconContents() {
+  DCHECK(stage_ == Stage::kPendingInstallableData);
+  stage_ = Stage::kPendingIconDownload;
+
+  DCHECK(web_application_info_.has_value());
+  std::vector<GURL> icon_urls =
+      GetValidIconUrlsToDownload(*web_application_info_);
+  icon_downloader_.emplace(
+      web_contents(), std::move(icon_urls),
+      WebAppIconDownloader::Histogram::kForUpdate,
+      base::BindOnce(&ManifestUpdateTask::OnIconsDownloaded, AsWeakPtr()));
+  icon_downloader_->SkipPageFavicons();
+  icon_downloader_->FailAllIfAnyFail();
+  icon_downloader_->Start();
+}
+
+void ManifestUpdateTask::OnIconsDownloaded(bool success, IconsMap icons_map) {
+  DCHECK(stage_ == Stage::kPendingIconDownload);
+
+  if (!success) {
+    DestroySelf(ManifestUpdateResult::kIconDownloadFailed);
+    return;
+  }
+
+  stage_ = Stage::kPendingIconReadFromDisk;
+  Observe(nullptr);
+  icon_manager_.ReadAllIcons(
+      app_id_, base::BindOnce(&ManifestUpdateTask::OnAllIconsRead, AsWeakPtr(),
+                              std::move(icons_map)));
+}
+
+void ManifestUpdateTask::OnAllIconsRead(
+    IconsMap downloaded_icons_map,
+    std::map<SquareSizePx, SkBitmap> disk_icon_bitmaps) {
+  DCHECK(stage_ == Stage::kPendingIconReadFromDisk);
+
+  if (disk_icon_bitmaps.empty()) {
+    DestroySelf(ManifestUpdateResult::kIconReadFromDiskFailed);
+    return;
+  }
+
+  DCHECK(web_application_info_.has_value());
+  FilterAndResizeIconsGenerateMissing(&web_application_info_.value(),
+                                      &downloaded_icons_map,
+                                      /*is_for_sync=*/false);
+
+  // TODO: compare in a BEST_EFFORT blocking PostTaskAndReply.
+  if (IsUpdateNeededForIconContents(disk_icon_bitmaps)) {
+    UpdateAfterWindowsClose();
+    return;
+  }
+
+  DestroySelf(ManifestUpdateResult::kAppUpToDate);
+}
+
+bool ManifestUpdateTask::IsUpdateNeededForIconContents(
+    const std::map<SquareSizePx, SkBitmap>& disk_icon_bitmaps) const {
+  DCHECK(web_application_info_.has_value());
+  const std::map<SquareSizePx, SkBitmap>& downloaded_icon_bitmaps =
+      web_application_info_->icon_bitmaps;
+  if (disk_icon_bitmaps.size() != disk_icon_bitmaps.size())
+    return true;
+
+  for (const std::pair<const SquareSizePx, SkBitmap>& entry :
+       downloaded_icon_bitmaps) {
+    SquareSizePx size = entry.first;
+    const SkBitmap& downloaded_bitmap = entry.second;
+
+    auto it = disk_icon_bitmaps.find(size);
+    if (it == disk_icon_bitmaps.end())
+      return true;
+
+    const SkBitmap& disk_bitmap = it->second;
+    if (!gfx::BitmapsAreEqual(downloaded_bitmap, disk_bitmap))
+      return true;
+  }
+
+  return false;
+}
+
+void ManifestUpdateTask::OnAllAppWindowsClosed() {
   DCHECK_EQ(stage_, Stage::kPendingWindowsClosed);
 
+  DCHECK(web_application_info_.has_value());
+
   // The app's name must not change due to an automatic update.
-  web_application_info->title =
+  web_application_info_->title =
       base::UTF8ToUTF16(registrar_.GetAppShortName(app_id_));
 
   // Preserve the user's choice of opening in browser tab or standalone window.
   switch (registrar_.GetAppUserDisplayMode(app_id_)) {
     case DisplayMode::kBrowser:
-      web_application_info->open_as_window = false;
+      web_application_info_->open_as_window = false;
       break;
     case DisplayMode::kStandalone:
-      web_application_info->open_as_window = true;
+      web_application_info_->open_as_window = true;
       break;
     case DisplayMode::kUndefined:
     case DisplayMode::kMinimalUi:
@@ -149,21 +246,13 @@
       break;
   }
 
-  std::unique_ptr<WebApplicationInfo> web_application_info_for_dchecking;
-#if DCHECK_IS_ON()
-  web_application_info_for_dchecking =
-      std::make_unique<WebApplicationInfo>(*web_application_info);
-#endif
-
   stage_ = Stage::kPendingInstallation;
   install_manager_.UpdateWebAppFromInfo(
-      app_id_, std::move(web_application_info),
-      base::BindOnce(&ManifestUpdateTask::OnInstallationComplete, AsWeakPtr(),
-                     std::move(web_application_info_for_dchecking)));
+      app_id_, std::make_unique<WebApplicationInfo>(*web_application_info_),
+      base::BindOnce(&ManifestUpdateTask::OnInstallationComplete, AsWeakPtr()));
 }
 
 void ManifestUpdateTask::OnInstallationComplete(
-    std::unique_ptr<WebApplicationInfo> opt_web_application_info,
     const AppId& app_id,
     InstallResultCode code) {
   DCHECK_EQ(stage_, Stage::kPendingInstallation);
@@ -174,13 +263,14 @@
   }
 
   DCHECK_EQ(app_id_, app_id);
-  DCHECK(!IsUpdateNeeded(*opt_web_application_info));
+  DCHECK(!IsUpdateNeededForManifest());
   DCHECK_EQ(code, InstallResultCode::kSuccessAlreadyInstalled);
 
   DestroySelf(ManifestUpdateResult::kAppUpdated);
 }
 
 void ManifestUpdateTask::DestroySelf(ManifestUpdateResult result) {
+  // Asserts that calling the callback results in |this| getting deleted.
 #if DCHECK_IS_ON()
   bool destructor_called = false;
   destructor_called_ptr_ = &destructor_called;
diff --git a/chrome/browser/web_applications/manifest_update_task.h b/chrome/browser/web_applications/manifest_update_task.h
index 5d69e33..d05ecc2 100644
--- a/chrome/browser/web_applications/manifest_update_task.h
+++ b/chrome/browser/web_applications/manifest_update_task.h
@@ -7,7 +7,10 @@
 
 #include "base/logging.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "chrome/browser/web_applications/components/web_app_icon_downloader.h"
 #include "chrome/browser/web_applications/components/web_app_id.h"
+#include "chrome/common/web_application_info.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "third_party/blink/public/common/manifest/manifest.h"
 
@@ -16,6 +19,7 @@
 
 namespace web_app {
 
+class AppIconManager;
 class AppRegistrar;
 class WebAppUiManager;
 class InstallManager;
@@ -33,7 +37,9 @@
   kAppUpdateFailed = 7,
   kAppUpdated = 8,
   kAppIsSystemWebApp = 9,
-  kMaxValue = kAppIsSystemWebApp,
+  kIconDownloadFailed = 10,
+  kIconReadFromDiskFailed = 11,
+  kMaxValue = kIconReadFromDiskFailed,
 };
 
 // Checks whether the installed web app associated with a given WebContents has
@@ -64,6 +70,7 @@
                      StoppedCallback stopped_callback,
                      bool hang_for_testing,
                      const AppRegistrar& registrar,
+                     const AppIconManager& icon_manager,
                      WebAppUiManager* ui_manager,
                      InstallManager* install_manager);
 
@@ -81,29 +88,41 @@
   enum class Stage {
     kPendingPageLoad,
     kPendingInstallableData,
+    kPendingIconDownload,
+    kPendingIconReadFromDisk,
     kPendingWindowsClosed,
     kPendingInstallation,
   };
 
   void OnDidGetInstallableData(const InstallableData& data);
-  bool IsUpdateNeeded(const WebApplicationInfo& web_application_info) const;
-  void OnAllAppWindowsClosed(
-      std::unique_ptr<WebApplicationInfo> web_application_info);
+  bool IsUpdateNeededForManifest() const;
+  void LoadAndCheckIconContents();
+  void OnIconsDownloaded(bool success, IconsMap icons_map);
+  void OnAllIconsRead(IconsMap downloaded_icons_map,
+                      std::map<SquareSizePx, SkBitmap> disk_icon_bitmaps);
+  bool IsUpdateNeededForIconContents(
+      const std::map<SquareSizePx, SkBitmap>& disk_icon_bitmaps) const;
+  void UpdateAfterWindowsClose();
+  void OnAllAppWindowsClosed();
   void OnInstallationComplete(
-      std::unique_ptr<WebApplicationInfo> opt_web_application_info,
       const AppId& app_id,
       InstallResultCode code);
   void DestroySelf(ManifestUpdateResult result);
 
   const AppRegistrar& registrar_;
+  const AppIconManager& icon_manager_;
   WebAppUiManager& ui_manager_;
   InstallManager& install_manager_;
 
   Stage stage_;
+  base::Optional<WebApplicationInfo> web_application_info_;
+  base::Optional<WebAppIconDownloader> icon_downloader_;
+
   const GURL url_;
   const AppId app_id_;
   StoppedCallback stopped_callback_;
   bool hang_for_testing_ = false;
+
 #if DCHECK_IS_ON()
   bool* destructor_called_ptr_ = nullptr;
 #endif
diff --git a/chrome/browser/web_applications/system_web_app_manager_browsertest.cc b/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
index 6c4bf38..5bdfd5e8 100644
--- a/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
+++ b/chrome/browser/web_applications/system_web_app_manager_browsertest.cc
@@ -41,7 +41,7 @@
 
 namespace web_app {
 
-SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest(
+SystemWebAppManagerBrowserTestBase::SystemWebAppManagerBrowserTestBase(
     bool install_mock) {
   scoped_feature_list_.InitWithFeatures(
       {features::kSystemWebApps, blink::features::kNativeFileSystemAPI,
@@ -53,18 +53,19 @@
   }
 }
 
-SystemWebAppManagerBrowserTest::~SystemWebAppManagerBrowserTest() = default;
+SystemWebAppManagerBrowserTestBase::~SystemWebAppManagerBrowserTestBase() =
+    default;
 
-SystemWebAppManager& SystemWebAppManagerBrowserTest::GetManager() {
+SystemWebAppManager& SystemWebAppManagerBrowserTestBase::GetManager() {
   return WebAppProvider::Get(browser()->profile())->system_web_app_manager();
 }
 
-SystemAppType SystemWebAppManagerBrowserTest::GetMockAppType() {
+SystemAppType SystemWebAppManagerBrowserTestBase::GetMockAppType() {
   DCHECK(maybe_installation_);
   return maybe_installation_->GetType();
 }
 
-void SystemWebAppManagerBrowserTest::WaitForTestSystemAppInstall() {
+void SystemWebAppManagerBrowserTestBase::WaitForTestSystemAppInstall() {
   // Wait for the System Web Apps to install.
   if (maybe_installation_) {
     maybe_installation_->WaitForAppInstall();
@@ -73,7 +74,7 @@
   }
 }
 
-Browser* SystemWebAppManagerBrowserTest::WaitForSystemAppInstallAndLaunch(
+Browser* SystemWebAppManagerBrowserTestBase::WaitForSystemAppInstallAndLaunch(
     SystemAppType system_app_type) {
   WaitForTestSystemAppInstall();
   apps::AppLaunchParams params = LaunchParamsForApp(system_app_type);
@@ -84,7 +85,7 @@
   return browser;
 }
 
-apps::AppLaunchParams SystemWebAppManagerBrowserTest::LaunchParamsForApp(
+apps::AppLaunchParams SystemWebAppManagerBrowserTestBase::LaunchParamsForApp(
     SystemAppType system_app_type) {
   base::Optional<AppId> app_id =
       GetManager().GetAppIdForSystemApp(system_app_type);
@@ -95,7 +96,7 @@
       apps::mojom::AppLaunchSource::kSourceTest);
 }
 
-content::WebContents* SystemWebAppManagerBrowserTest::LaunchApp(
+content::WebContents* SystemWebAppManagerBrowserTestBase::LaunchApp(
     const apps::AppLaunchParams& params) {
   // Use apps::LaunchService::OpenApplication() to get the most coverage. E.g.,
   // this is what is invoked by file_manager::file_tasks::ExecuteWebTask() on
@@ -104,24 +105,20 @@
       ->OpenApplication(params);
 }
 
-content::EvalJsResult EvalJs(content::WebContents* web_contents,
-                             const std::string& script) {
-  // Set world_id = 1 to bypass Content Security Policy restriction.
-  return content::EvalJs(web_contents, script,
-                         content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
-                         1 /*world_id*/);
-}
-
-::testing::AssertionResult ExecJs(content::WebContents* web_contents,
-                                  const std::string& script) {
-  // Set world_id = 1 to bypass Content Security Policy restriction.
-  return content::ExecJs(web_contents, script,
-                         content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
-                         1 /*world_id*/);
+SystemWebAppManagerBrowserTest::SystemWebAppManagerBrowserTest(
+    bool install_mock)
+    : SystemWebAppManagerBrowserTestBase(install_mock) {
+  if (GetParam() == ProviderType::kWebApps) {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kDesktopPWAsWithoutExtensions);
+  } else if (GetParam() == ProviderType::kBookmarkApps) {
+    scoped_feature_list_.InitAndDisableFeature(
+        features::kDesktopPWAsWithoutExtensions);
+  }
 }
 
 // Test that System Apps install correctly with a manifest.
-IN_PROC_BROWSER_TEST_F(SystemWebAppManagerBrowserTest, Install) {
+IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest, Install) {
   Browser* app_browser = WaitForSystemAppInstallAndLaunch(GetMockAppType());
 
   AppId app_id = app_browser->app_controller()->GetAppId();
@@ -158,7 +155,7 @@
 
 // Check the toolbar is not shown for system web apps for pages on the chrome://
 // scheme but is shown off the chrome:// scheme.
-IN_PROC_BROWSER_TEST_F(SystemWebAppManagerBrowserTest,
+IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest,
                        ToolbarVisibilityForSystemWebApp) {
   Browser* app_browser = WaitForSystemAppInstallAndLaunch(GetMockAppType());
   // In scope, the toolbar should not be visible.
@@ -187,7 +184,7 @@
 // Check launch files are passed to application.
 // Note: This test uses ExecuteScriptXXX instead of ExecJs and EvalJs because of
 // some quirks surrounding origin trials and content security policies.
-IN_PROC_BROWSER_TEST_F(SystemWebAppManagerBrowserTest,
+IN_PROC_BROWSER_TEST_P(SystemWebAppManagerBrowserTest,
                        LaunchFilesForSystemWebApp) {
   WaitForTestSystemAppInstall();
   apps::AppLaunchParams params = LaunchParamsForApp(GetMockAppType());
@@ -263,11 +260,11 @@
 }
 
 class SystemWebAppManagerLaunchFilesBrowserTest
-    : public SystemWebAppManagerBrowserTest,
+    : public SystemWebAppManagerBrowserTestBase,
       public testing::WithParamInterface<std::vector<base::Feature>> {
  public:
   SystemWebAppManagerLaunchFilesBrowserTest()
-      : SystemWebAppManagerBrowserTest(/*install_mock=*/false) {
+      : SystemWebAppManagerBrowserTestBase(/*install_mock=*/false) {
     scoped_feature_list_.InitWithFeatures(GetParam(), {});
     maybe_installation_ =
         TestSystemWebAppInstallation::SetUpAppThatReceivesLaunchDirectory();
@@ -278,7 +275,7 @@
 };
 
 // Launching behavior for apps that do not want to received launch directory are
-// tested in |SystemWebAppManagerBrowserTest.LaunchFilesForSystemWebApp|.
+// tested in |SystemWebAppManagerBrowserTestBase.LaunchFilesForSystemWebApp|.
 // Note: This test uses ExecuteScriptXXX instead of ExecJs and EvalJs because of
 // some quirks surrounding origin trials and content security policies.
 IN_PROC_BROWSER_TEST_P(SystemWebAppManagerLaunchFilesBrowserTest,
@@ -457,16 +454,23 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(SystemWebAppManagerNotShownInLauncherTest,
+IN_PROC_BROWSER_TEST_P(SystemWebAppManagerNotShownInLauncherTest,
                        NotShownInLauncher) {
+  // TODO(crbug.com/1054195): Make the expectation unconditional.
+  const web_app::ProviderType provider = provider_type();
+
   WaitForSystemAppInstallAndLaunch(GetMockAppType());
   AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value();
 
   apps::AppServiceProxy* proxy =
       apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
   proxy->AppRegistryCache().ForOneApp(
-      app_id, [](const apps::AppUpdate& update) {
-        EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInLauncher());
+      app_id, [provider](const apps::AppUpdate& update) {
+        if (provider == ProviderType::kWebApps) {
+          EXPECT_EQ(apps::mojom::OptionalBool::kTrue, update.ShowInLauncher());
+        } else {
+          EXPECT_EQ(apps::mojom::OptionalBool::kFalse, update.ShowInLauncher());
+        }
       });
 }
 
@@ -480,17 +484,26 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(SystemWebAppManagerAdditionalSearchTermsTest,
+IN_PROC_BROWSER_TEST_P(SystemWebAppManagerAdditionalSearchTermsTest,
                        AdditionalSearchTerms) {
+  // TODO(crbug.com/1054195): Make the expectation unconditional.
+  const web_app::ProviderType provider = provider_type();
+
   WaitForSystemAppInstallAndLaunch(GetMockAppType());
   AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value();
 
   apps::AppServiceProxy* proxy =
       apps::AppServiceProxyFactory::GetForProfile(browser()->profile());
   proxy->AppRegistryCache().ForOneApp(
-      app_id, [](const apps::AppUpdate& update) {
-        EXPECT_EQ(std::vector<std::string>({"Security"}),
-                  update.AdditionalSearchTerms());
+      app_id, [provider](const apps::AppUpdate& update) {
+        // TODO(crbug.com/1054195): Unconditionally expect "Security".
+        if (provider == ProviderType::kBookmarkApps) {
+          EXPECT_EQ(std::vector<std::string>({"Security"}),
+                    update.AdditionalSearchTerms());
+        } else {
+          EXPECT_EQ(std::vector<std::string>({}),
+                    update.AdditionalSearchTerms());
+        }
       });
 }
 
@@ -504,7 +517,7 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(SystemWebAppManagerChromeUntrustedTest, Install) {
+IN_PROC_BROWSER_TEST_P(SystemWebAppManagerChromeUntrustedTest, Install) {
   Browser* app_browser = WaitForSystemAppInstallAndLaunch(GetMockAppType());
   AppId app_id = GetManager().GetAppIdForSystemApp(GetMockAppType()).value();
   EXPECT_EQ(app_id, app_browser->app_controller()->GetAppId());
@@ -523,12 +536,45 @@
             app_id);
 }
 
+// We test with and without enabling kDesktopPWAsWithoutExtensions.
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SystemWebAppManagerBrowserTest,
+                         ::testing::Values(ProviderType::kBookmarkApps,
+                                           ProviderType::kWebApps),
+                         ProviderTypeParamToString);
+
 INSTANTIATE_TEST_SUITE_P(
     PermissionContext,
     SystemWebAppManagerLaunchFilesBrowserTest,
     testing::Values(
         /*default_enabled_permission_context*/ std::vector<base::Feature>(),
-        /*origin_scoped_permission_context*/ std::vector<base::Feature>(
-            {features::kNativeFileSystemOriginScopedPermissions})));
+        /*origin_scoped_permission_context*/
+        std::vector<base::Feature>(
+            {features::kNativeFileSystemOriginScopedPermissions}),
+        /*default_enabled_permission_context*/
+        std::vector<base::Feature>({features::kDesktopPWAsWithoutExtensions}),
+        /*origin_scoped_permission_context*/
+        std::vector<base::Feature>(
+            {features::kNativeFileSystemOriginScopedPermissions,
+             features::kDesktopPWAsWithoutExtensions})));
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SystemWebAppManagerNotShownInLauncherTest,
+                         ::testing::Values(ProviderType::kBookmarkApps,
+                                           ProviderType::kWebApps),
+                         ProviderTypeParamToString);
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SystemWebAppManagerAdditionalSearchTermsTest,
+                         ::testing::Values(ProviderType::kBookmarkApps,
+                                           ProviderType::kWebApps),
+                         ProviderTypeParamToString);
+
+INSTANTIATE_TEST_SUITE_P(All,
+                         SystemWebAppManagerChromeUntrustedTest,
+                         ::testing::Values(ProviderType::kBookmarkApps,
+                                           ProviderType::kWebApps),
+                         ProviderTypeParamToString);
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/system_web_app_manager_browsertest.h b/chrome/browser/web_applications/system_web_app_manager_browsertest.h
index 5005284..1c904aea 100644
--- a/chrome/browser/web_applications/system_web_app_manager_browsertest.h
+++ b/chrome/browser/web_applications/system_web_app_manager_browsertest.h
@@ -11,6 +11,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "chrome/browser/web_applications/test/test_system_web_app_installation.h"
 #include "chrome/browser/web_applications/test/test_web_app_provider.h"
+#include "chrome/browser/web_applications/test/web_app_test.h"
 #include "chrome/test/base/in_process_browser_test.h"
 
 class Browser;
@@ -28,15 +29,17 @@
 
 enum class SystemAppType;
 
-class SystemWebAppManagerBrowserTest : public InProcessBrowserTest {
+// Clients should use SystemWebAppManagerBrowserTest, so test can be run with
+// both the new web apps provider and the legacy bookmark apps provider.
+class SystemWebAppManagerBrowserTestBase : public InProcessBrowserTest {
  public:
   // Performs common initialization for testing SystemWebAppManager features.
   // If true, |install_mock| installs a WebUIController that serves a mock
   // System PWA, and ensures the WebAppProvider associated with the startup
   // profile is a TestWebAppProviderCreator.
-  explicit SystemWebAppManagerBrowserTest(bool install_mock = true);
+  explicit SystemWebAppManagerBrowserTestBase(bool install_mock = true);
 
-  ~SystemWebAppManagerBrowserTest() override;
+  ~SystemWebAppManagerBrowserTestBase() override;
 
   // Returns the SystemWebAppManager for browser()->profile(). This will be a
   // TestSystemWebAppManager if initialized with |install_mock| true.
@@ -66,7 +69,20 @@
 
   base::test::ScopedFeatureList scoped_feature_list_;
 
-  DISALLOW_COPY_AND_ASSIGN(SystemWebAppManagerBrowserTest);
+  DISALLOW_COPY_AND_ASSIGN(SystemWebAppManagerBrowserTestBase);
+};
+
+class SystemWebAppManagerBrowserTest
+    : public SystemWebAppManagerBrowserTestBase,
+      public ::testing::WithParamInterface<web_app::ProviderType> {
+ public:
+  explicit SystemWebAppManagerBrowserTest(bool install_mock = true);
+  ~SystemWebAppManagerBrowserTest() override = default;
+
+  web_app::ProviderType provider_type() const { return GetParam(); }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 }  // namespace web_app
diff --git a/chrome/browser/web_applications/test/web_app_icon_test_utils.cc b/chrome/browser/web_applications/test/web_app_icon_test_utils.cc
index 0eb6c650..80f4b9ba 100644
--- a/chrome/browser/web_applications/test/web_app_icon_test_utils.cc
+++ b/chrome/browser/web_applications/test/web_app_icon_test_utils.cc
@@ -59,8 +59,9 @@
 }
 
 base::FilePath GetAppIconsDir(Profile* profile, const AppId& app_id) {
-  base::FilePath web_apps_dir = GetWebAppsDirectory(profile);
-  base::FilePath app_dir = web_apps_dir.AppendASCII(app_id);
+  base::FilePath web_apps_root_directory = GetWebAppsRootDirectory(profile);
+  base::FilePath app_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_root_directory, app_id);
   base::FilePath icons_dir = app_dir.AppendASCII("Icons");
   return icons_dir;
 }
diff --git a/chrome/browser/web_applications/web_app_database_factory.cc b/chrome/browser/web_applications/web_app_database_factory.cc
index e9601e4..b226993f 100644
--- a/chrome/browser/web_applications/web_app_database_factory.cc
+++ b/chrome/browser/web_applications/web_app_database_factory.cc
@@ -17,7 +17,7 @@
   if (!base::FeatureList::IsEnabled(features::kDesktopPWAsSharedStoreService)) {
     model_type_store_service_ =
         std::make_unique<syncer::ModelTypeStoreServiceImpl>(
-            GetWebAppsDirectory(profile));
+            GetWebAppsRootDirectory(profile));
   }
 }
 
diff --git a/chrome/browser/web_applications/web_app_icon_manager.cc b/chrome/browser/web_applications/web_app_icon_manager.cc
index 42fbfb3..37e77817 100644
--- a/chrome/browser/web_applications/web_app_icon_manager.cc
+++ b/chrome/browser/web_applications/web_app_icon_manager.cc
@@ -28,42 +28,37 @@
 
 namespace {
 
-constexpr base::FilePath::CharType kTempDirectoryName[] =
-    FILE_PATH_LITERAL("Temp");
-
-constexpr base::FilePath::CharType kIconsDirectoryName[] =
-    FILE_PATH_LITERAL("Icons");
-
-base::FilePath GetAppDirectory(const base::FilePath& web_apps_directory,
-                               const AppId& app_id) {
-  return web_apps_directory.AppendASCII(app_id);
-}
-
-base::FilePath GetTempDir(FileUtilsWrapper* utils,
-                          const base::FilePath& web_apps_dir) {
-  // Create the temp directory as a sub-directory of the WebApps directory.
-  // This guarantees it is on the same file system as the WebApp's eventual
-  // install target.
-  base::FilePath temp_path = web_apps_dir.Append(kTempDirectoryName);
-  if (utils->PathExists(temp_path)) {
-    if (!utils->DirectoryExists(temp_path)) {
-      LOG(ERROR) << "Not a directory: " << temp_path.value();
-      return base::FilePath();
+// Returns false if directory doesn't exist or it is not writable.
+bool CreateDirectoryIfNotExists(FileUtilsWrapper* utils,
+                                const base::FilePath& path) {
+  if (utils->PathExists(path)) {
+    if (!utils->DirectoryExists(path)) {
+      LOG(ERROR) << "Not a directory: " << path.value();
+      return false;
     }
-    if (!utils->PathIsWritable(temp_path)) {
-      LOG(ERROR) << "Can't write to path: " << temp_path.value();
-      return base::FilePath();
+    if (!utils->PathIsWritable(path)) {
+      LOG(ERROR) << "Can't write to path: " << path.value();
+      return false;
     }
     // This is a directory we can write to.
-    return temp_path;
+    return true;
   }
 
   // Directory doesn't exist, so create it.
-  if (!utils->CreateDirectory(temp_path)) {
-    LOG(ERROR) << "Could not create directory: " << temp_path.value();
-    return base::FilePath();
+  if (!utils->CreateDirectory(path)) {
+    LOG(ERROR) << "Could not create directory: " << path.value();
+    return false;
   }
-  return temp_path;
+  return true;
+}
+
+// This is a private implementation detail of WebAppIconManager, where and how
+// to store icon files.
+base::FilePath GetAppIconsDirectory(
+    const base::FilePath& app_manifest_resources_directory) {
+  constexpr base::FilePath::CharType kIconsDirectoryName[] =
+      FILE_PATH_LITERAL("Icons");
+  return app_manifest_resources_directory.Append(kIconsDirectoryName);
 }
 
 bool WriteIcon(FileUtilsWrapper* utils,
@@ -95,7 +90,7 @@
 bool WriteIcons(FileUtilsWrapper* utils,
                 const base::FilePath& app_dir,
                 const std::map<SquareSizePx, SkBitmap>& icon_bitmaps) {
-  const base::FilePath icons_dir = app_dir.Append(kIconsDirectoryName);
+  const base::FilePath icons_dir = GetAppIconsDirectory(app_dir);
   if (!utils->CreateDirectory(icons_dir)) {
     LOG(ERROR) << "Could not create icons directory.";
     return false;
@@ -116,10 +111,13 @@
                        const base::FilePath& web_apps_directory,
                        const AppId& app_id,
                        const std::map<SquareSizePx, SkBitmap>& icons) {
-  const base::FilePath temp_dir = GetTempDir(utils.get(), web_apps_directory);
-  if (temp_dir.empty()) {
-    LOG(ERROR)
-        << "Could not get path to WebApps temporary directory in profile.";
+  // Create the temp directory under the web apps root.
+  // This guarantees it is on the same file system as the WebApp's eventual
+  // install target.
+  base::FilePath temp_dir = GetWebAppsTempDirectory(web_apps_directory);
+  if (!CreateDirectoryIfNotExists(utils.get(), temp_dir)) {
+    LOG(ERROR) << "Could not create or write to WebApps temporary directory in "
+                  "profile.";
     return false;
   }
 
@@ -132,12 +130,20 @@
   if (!WriteIcons(utils.get(), app_temp_dir.GetPath(), icons))
     return false;
 
-  // Commit: move whole app data dir to final destination in one mv operation.
-  const base::FilePath app_dir = GetAppDirectory(web_apps_directory, app_id);
+  base::FilePath manifest_resources_directory =
+      GetManifestResourcesDirectory(web_apps_directory);
+  if (!CreateDirectoryIfNotExists(utils.get(), manifest_resources_directory)) {
+    LOG(ERROR) << "Could not create Manifest Resources directory.";
+    return false;
+  }
 
-  // Try to delete the destination. Needed for update.
+  base::FilePath app_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
+
+  // Try to delete the destination. Needed for update. Ignore the result.
   utils->DeleteFileRecursively(app_dir);
 
+  // Commit: move whole app data dir to final destination in one mv operation.
   if (!utils->Move(app_temp_dir.GetPath(), app_dir)) {
     LOG(ERROR) << "Could not move temp WebApp directory to final destination.";
     return false;
@@ -152,15 +158,18 @@
 bool DeleteDataBlocking(const std::unique_ptr<FileUtilsWrapper>& utils,
                         const base::FilePath& web_apps_directory,
                         const AppId& app_id) {
-  const base::FilePath app_dir = GetAppDirectory(web_apps_directory, app_id);
+  base::FilePath app_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
+
   return utils->DeleteFileRecursively(app_dir);
 }
 
 base::FilePath GetIconFileName(const base::FilePath& web_apps_directory,
                                const AppId& app_id,
                                int icon_size_px) {
-  const base::FilePath app_dir = GetAppDirectory(web_apps_directory, app_id);
-  const base::FilePath icons_dir = app_dir.Append(kIconsDirectoryName);
+  base::FilePath app_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_directory, app_id);
+  base::FilePath icons_dir = GetAppIconsDirectory(app_dir);
 
   return icons_dir.AppendASCII(base::StringPrintf("%i.png", icon_size_px));
 }
@@ -269,7 +278,7 @@
                                      WebAppRegistrar& registrar,
                                      std::unique_ptr<FileUtilsWrapper> utils)
     : registrar_(registrar), utils_(std::move(utils)) {
-  web_apps_directory_ = GetWebAppsDirectory(profile);
+  web_apps_directory_ = GetWebAppsRootDirectory(profile);
 }
 
 WebAppIconManager::~WebAppIconManager() = default;
diff --git a/chrome/browser/web_applications/web_app_icon_manager_unittest.cc b/chrome/browser/web_applications/web_app_icon_manager_unittest.cc
index adc94e7b..6a418e2 100644
--- a/chrome/browser/web_applications/web_app_icon_manager_unittest.cc
+++ b/chrome/browser/web_applications/web_app_icon_manager_unittest.cc
@@ -389,9 +389,12 @@
   WriteIcons(app1_id, sizes_px, colors);
   WriteIcons(app2_id, sizes_px, colors);
 
-  const base::FilePath web_apps_directory = GetWebAppsDirectory(profile());
-  const base::FilePath app1_dir = web_apps_directory.AppendASCII(app1_id);
-  const base::FilePath app2_dir = web_apps_directory.AppendASCII(app2_id);
+  const base::FilePath web_apps_root_directory =
+      GetWebAppsRootDirectory(profile());
+  const base::FilePath app1_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_root_directory, app1_id);
+  const base::FilePath app2_dir =
+      GetManifestResourcesDirectoryForApp(web_apps_root_directory, app2_id);
 
   EXPECT_TRUE(file_utils().DirectoryExists(app1_dir));
   EXPECT_FALSE(file_utils().IsDirectoryEmpty(app1_dir));
@@ -407,7 +410,9 @@
                             }));
   run_loop.Run();
 
-  EXPECT_TRUE(file_utils().DirectoryExists(web_apps_directory));
+  base::FilePath manifest_resources_directory =
+      GetManifestResourcesDirectory(web_apps_root_directory);
+  EXPECT_TRUE(file_utils().DirectoryExists(manifest_resources_directory));
 
   EXPECT_TRUE(file_utils().DirectoryExists(app1_dir));
   EXPECT_FALSE(file_utils().IsDirectoryEmpty(app1_dir));
diff --git a/chrome/browser/web_applications/web_app_install_task_unittest.cc b/chrome/browser/web_applications/web_app_install_task_unittest.cc
index 4f2c056..4fdeb2ea 100644
--- a/chrome/browser/web_applications/web_app_install_task_unittest.cc
+++ b/chrome/browser/web_applications/web_app_install_task_unittest.cc
@@ -643,9 +643,10 @@
 
   // TestingProfile creates temp directory if TestingProfile::path_ is empty
   // (i.e. if TestingProfile::Builder::SetPath was not called by a test fixture)
-  const base::FilePath profile_dir = profile()->GetPath();
-  const base::FilePath web_apps_dir = profile_dir.AppendASCII("WebApps");
-  EXPECT_FALSE(file_utils_->DirectoryExists(web_apps_dir));
+  const base::FilePath web_apps_dir = GetWebAppsRootDirectory(profile());
+  const base::FilePath manifest_resources_directory =
+      GetManifestResourcesDirectory(web_apps_dir);
+  EXPECT_FALSE(file_utils_->DirectoryExists(manifest_resources_directory));
 
   const SkColor color = SK_ColorGREEN;
   const int original_icon_size_px = icon_size::k512;
@@ -656,13 +657,14 @@
 
   const AppId app_id = InstallWebAppFromManifestWithFallback();
 
-  EXPECT_TRUE(file_utils_->DirectoryExists(web_apps_dir));
+  EXPECT_TRUE(file_utils_->DirectoryExists(manifest_resources_directory));
 
   const base::FilePath temp_dir = web_apps_dir.AppendASCII("Temp");
   EXPECT_TRUE(file_utils_->DirectoryExists(temp_dir));
   EXPECT_TRUE(file_utils_->IsDirectoryEmpty(temp_dir));
 
-  const base::FilePath app_dir = web_apps_dir.AppendASCII(app_id);
+  const base::FilePath app_dir =
+      manifest_resources_directory.AppendASCII(app_id);
   EXPECT_TRUE(file_utils_->DirectoryExists(app_dir));
 
   const base::FilePath icons_dir = app_dir.AppendASCII("Icons");
@@ -712,10 +714,11 @@
                     SK_ColorBLUE, &icons_map);
   SetIconsMapToRetrieve(std::move(icons_map));
 
-  const base::FilePath profile_dir = profile()->GetPath();
-  const base::FilePath web_apps_dir = profile_dir.AppendASCII("WebApps");
+  const base::FilePath web_apps_dir = GetWebAppsRootDirectory(profile());
+  const base::FilePath manifest_resources_directory =
+      GetManifestResourcesDirectory(web_apps_dir);
 
-  EXPECT_TRUE(file_utils_->CreateDirectory(web_apps_dir));
+  EXPECT_TRUE(file_utils_->CreateDirectory(manifest_resources_directory));
 
   // Induce an error: Simulate "Disk Full" for writing icon files.
   file_utils_->SetRemainingDiskSpaceSize(1024);
@@ -742,7 +745,8 @@
   EXPECT_TRUE(file_utils_->IsDirectoryEmpty(temp_dir));
 
   const AppId app_id = GenerateAppIdFromURL(app_url);
-  const base::FilePath app_dir = web_apps_dir.AppendASCII(app_id);
+  const base::FilePath app_dir =
+      manifest_resources_directory.AppendASCII(app_id);
   EXPECT_FALSE(file_utils_->DirectoryExists(app_dir));
 }
 
diff --git a/chrome/browser/web_applications/web_app_provider.cc b/chrome/browser/web_applications/web_app_provider.cc
index e6e79d0..e264d12 100644
--- a/chrome/browser/web_applications/web_app_provider.cc
+++ b/chrome/browser/web_applications/web_app_provider.cc
@@ -234,9 +234,9 @@
   install_manager_->SetSubsystems(registrar_.get(), shortcut_manager_.get(),
                                   file_handler_manager_.get(),
                                   install_finalizer_.get());
-  manifest_update_manager_->SetSubsystems(registrar_.get(), ui_manager_.get(),
-                                          install_manager_.get(),
-                                          system_web_app_manager_.get());
+  manifest_update_manager_->SetSubsystems(
+      registrar_.get(), icon_manager_.get(), ui_manager_.get(),
+      install_manager_.get(), system_web_app_manager_.get());
   pending_app_manager_->SetSubsystems(
       registrar_.get(), shortcut_manager_.get(), file_handler_manager_.get(),
       ui_manager_.get(), install_finalizer_.get());
diff --git a/chrome/credential_provider/gaiacp/gaia_resources.grd b/chrome/credential_provider/gaiacp/gaia_resources.grd
index 562d4a6..429f2bd 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources.grd
+++ b/chrome/credential_provider/gaiacp/gaia_resources.grd
@@ -131,7 +131,7 @@
         Used to run Google Credential Provider sign in page.
       </message>
       <message name="IDS_AUTH_FID_DESCRIPTION" desc="">
-        Sign in using your work account
+        Sign in with your work account
       </message>
       <message name="IDS_REAUTH_FID_DESCRIPTION" desc="">
         Your session has expired. Sign in with your work account.
@@ -140,19 +140,19 @@
         This device isn’t yet enrolled with your organization’s device management. Sign in with your work account.
       </message>
       <message name="IDS_REAUTH_MISSING_PASSWORD_RECOVERY_INFO_FID_DESCRIPTION" desc="">
-        Sign in with your work account.
+        Sign in with your work account
       </message>
       <message name="IDS_REAUTH_AD_NO_USER_FID_DESCRIPTION" desc="">
-        Sign in with your work account.
+        Sign in with your work account
       </message>
       <message name="IDS_REAUTH_FAILED_UPLOAD_DEVICE_DETAILS_DESCRIPTION" desc="">
-        Sign in with your work account.
+        Sign in with your work account
       </message>
       <message name="IDS_AUTH_FID_PROVIDER_LABEL" desc="">
         Add work account
       </message>
       <message name="IDS_EXISTING_AUTH_FID_PROVIDER_LABEL" desc="">
-        Sign in using your work account
+        Sign in with your work account
       </message>
       <message name="IDS_USER_ACCOUNT_COMMENT" desc="">
         User account created by Google Credential Provider for Windows
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_DESCRIPTION.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_DESCRIPTION.png.sha1
index e0fd529..d87c5e6f 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_DESCRIPTION.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_AUTH_FID_DESCRIPTION.png.sha1
@@ -1 +1 @@
-0290a1c7d0e931d0cc66411b35ccafdddb2a028e
\ No newline at end of file
+97dded26b665584a77ed175e00b66e24ed32f5bd
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1
index 9e9df61f..d87c5e6f 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_EXISTING_AUTH_FID_PROVIDER_LABEL.png.sha1
@@ -1 +1 @@
-2918e662e203c8dd44e70bd311f5677500c0ccf2
\ No newline at end of file
+97dded26b665584a77ed175e00b66e24ed32f5bd
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_AD_NO_USER_FID_DESCRIPTION.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_AD_NO_USER_FID_DESCRIPTION.png.sha1
index 3eaccb27..d87c5e6f 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_AD_NO_USER_FID_DESCRIPTION.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_AD_NO_USER_FID_DESCRIPTION.png.sha1
@@ -1 +1 @@
-b6f1778855335eae46d234f5332d7f59021e038e
\ No newline at end of file
+97dded26b665584a77ed175e00b66e24ed32f5bd
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FAILED_UPLOAD_DEVICE_DETAILS_DESCRIPTION.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FAILED_UPLOAD_DEVICE_DETAILS_DESCRIPTION.png.sha1
index 3eaccb27..d87c5e6f 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FAILED_UPLOAD_DEVICE_DETAILS_DESCRIPTION.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_FAILED_UPLOAD_DEVICE_DETAILS_DESCRIPTION.png.sha1
@@ -1 +1 @@
-b6f1778855335eae46d234f5332d7f59021e038e
\ No newline at end of file
+97dded26b665584a77ed175e00b66e24ed32f5bd
\ No newline at end of file
diff --git a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_MISSING_PASSWORD_RECOVERY_INFO_FID_DESCRIPTION.png.sha1 b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_MISSING_PASSWORD_RECOVERY_INFO_FID_DESCRIPTION.png.sha1
index 3eaccb27..d87c5e6f 100644
--- a/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_MISSING_PASSWORD_RECOVERY_INFO_FID_DESCRIPTION.png.sha1
+++ b/chrome/credential_provider/gaiacp/gaia_resources_grd/IDS_REAUTH_MISSING_PASSWORD_RECOVERY_INFO_FID_DESCRIPTION.png.sha1
@@ -1 +1 @@
-b6f1778855335eae46d234f5332d7f59021e038e
\ No newline at end of file
+97dded26b665584a77ed175e00b66e24ed32f5bd
\ No newline at end of file
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 52ca8f4..4bdcb96 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -6880,12 +6880,15 @@
     ]
   },
   "AdvancedProtectionDeepScanningEnabled": {
-    "os": ["win", "linux", "mac"],
+    "note": "This policy was replaced with AdvancedProtectionExtraSecurityAllowed in M82"
+  },
+  "AdvancedProtectionExtraSecurityAllowed": {
+    "os": ["win", "linux", "mac", "chromeos"],
     "policy_pref_mapping_test": [
       {
-        "policies": { "AdvancedProtectionDeepScanningEnabled": true },
+        "policies": { "AdvancedProtectionExtraSecurityAllowed": true },
         "prefs": {
-          "safebrowsing.advanced_protection_deep_scanning_enabled": {}
+          "safebrowsing.advanced_protection_extra_security_allowed": {}
         }
       }
     ]
diff --git a/chrome/test/data/web_apps/blue-192.png b/chrome/test/data/web_apps/blue-192.png
new file mode 100644
index 0000000..5bb998d
--- /dev/null
+++ b/chrome/test/data/web_apps/blue-192.png
Binary files differ
diff --git a/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.js b/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.js
index 30c85af8..7b4361e 100644
--- a/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.js
+++ b/chrome/test/data/webui/new_tab_page/customize_backgrounds_test.js
@@ -5,55 +5,136 @@
 import 'chrome://new-tab-page/customize_backgrounds.js';
 
 import {BrowserProxy} from 'chrome://new-tab-page/browser_proxy.js';
-import {createTestProxy} from 'chrome://test/new_tab_page/test_support.js';
-import {flushTasks} from 'chrome://test/test_util.m.js';
+import {assertNotStyle, assertStyle, createTestProxy} from 'chrome://test/new_tab_page/test_support.js';
+import {flushTasks, isVisible} from 'chrome://test/test_util.m.js';
+
+function createCollection(id = 0, label = '', url = '') {
+  return {id: id, label: label, previewImageUrl: {url: url}};
+}
 
 suite('NewTabPageCustomizeBackgroundsTest', () => {
-  /** @type {TestProxy} */
-  let testProxy;
+  /** @type {newTabPage.mojom.PageHandlerRemote} */
+  let handler;
+
+  async function createCustomizeBackgrounds() {
+    const customizeBackgrounds =
+        document.createElement('ntp-customize-backgrounds');
+    document.body.appendChild(customizeBackgrounds);
+    await handler.whenCalled('getBackgroundCollections');
+    await flushTasks();
+    return customizeBackgrounds;
+  }
 
   setup(() => {
     PolymerTest.clearBody();
 
-    testProxy = createTestProxy();
+    const testProxy = createTestProxy();
+    handler = testProxy.handler;
+    handler.setResultFor('getBackgroundCollections', Promise.resolve({
+      collections: [],
+    }));
+    handler.setResultFor('getBackgroundImages', Promise.resolve({
+      images: [],
+    }));
     BrowserProxy.instance_ = testProxy;
   });
 
   test('creating element shows background collection tiles', async () => {
     // Arrange.
-    const collections = [
-      {
-        label: 'collection_0',
-        previewImageUrl: {url: 'https://example.com/image_0.jpg'},
-      },
-      {
-        label: 'collection_1',
-        previewImageUrl: {url: 'https://example.com/image_1.jpg'},
-      },
-    ];
-    const getBackgroundCollectionsCalled =
-        testProxy.handler.whenCalled('getBackgroundCollections');
-    testProxy.handler.setResultFor('getBackgroundCollections', Promise.resolve({
-      collections: collections,
+    const collection = createCollection(0, 'col_0', 'https://col_0.jpg');
+    handler.setResultFor('getBackgroundCollections', Promise.resolve({
+      collections: [collection],
     }));
 
     // Act.
-    const customizeBackgrounds =
-        document.createElement('ntp-customize-backgrounds');
-    document.body.appendChild(customizeBackgrounds);
-    await getBackgroundCollectionsCalled;
+    const customizeBackgrounds = await createCustomizeBackgrounds();
+
+    // Assert.
+    assertTrue(isVisible(customizeBackgrounds.$.collections));
+    assertStyle(customizeBackgrounds.$.images, 'display', 'none');
+    const tiles =
+        customizeBackgrounds.shadowRoot.querySelectorAll('#collections .tile');
+    assertEquals(tiles.length, 1);
+    assertEquals(tiles[0].getAttribute('title'), 'col_0');
+    assertEquals(
+        tiles[0].querySelector('.image').textContent.trim(),
+        'https://col_0.jpg');
+  });
+
+  test('clicking collection selects collection', async function() {
+    // Arrange.
+    const collection = createCollection();
+    handler.setResultFor('getBackgroundCollections', Promise.resolve({
+      collections: [collection],
+    }));
+    const customizeBackgrounds = await createCustomizeBackgrounds();
+
+    // Act.
+    customizeBackgrounds.shadowRoot.querySelector('#collections .tile').click();
+
+    // Assert.
+    assertDeepEquals(customizeBackgrounds.selectedCollection, collection);
+  });
+
+  test('setting collection requests images', async function() {
+    // Arrange.
+    const customizeBackgrounds = await createCustomizeBackgrounds();
+
+    // Act.
+    customizeBackgrounds.selectedCollection = createCollection();
+
+    // Assert.
+    assertFalse(isVisible(customizeBackgrounds.$.collections));
+    await handler.whenCalled('getBackgroundImages');
+  });
+
+  test('Loading images shows image tiles', async function() {
+    // Arrange.
+    const image = {
+      label: 'image_0',
+      previewImageUrl: {url: 'https://example.com/image.png'},
+    };
+    handler.setResultFor('getBackgroundImages', Promise.resolve({
+      images: [image],
+    }));
+    const customizeBackgrounds = await createCustomizeBackgrounds();
+    customizeBackgrounds.selectedCollection = createCollection(0);
+
+    // Act.
+    const id = await handler.whenCalled('getBackgroundImages');
     await flushTasks();
 
     // Assert.
-    const tiles = customizeBackgrounds.shadowRoot.querySelectorAll('.tile');
-    assertEquals(tiles.length, 2);
-    assertEquals(tiles[0].getAttribute('title'), 'collection_0');
-    assertEquals(tiles[1].getAttribute('title'), 'collection_1');
+    assertEquals(id, 0);
+    assertFalse(isVisible(customizeBackgrounds.$.collections));
+    assertTrue(isVisible(customizeBackgrounds.$.images));
+    const tiles =
+        customizeBackgrounds.shadowRoot.querySelectorAll('#images .tile');
+    assertEquals(tiles.length, 1);
     assertEquals(
         tiles[0].querySelector('.image').textContent.trim(),
-        'https://example.com/image_0.jpg');
-    assertEquals(
-        tiles[1].querySelector('.image').textContent.trim(),
-        'https://example.com/image_1.jpg');
+        'https://example.com/image.png');
+  });
+
+  test('Going back shows collections', async function() {
+    // Arrange.
+    const image = {
+      label: 'image_0',
+      previewImageUrl: {url: 'https://example.com/image.png'},
+    };
+    const customizeBackgrounds = await createCustomizeBackgrounds();
+    handler.setResultFor('getBackgroundImages', Promise.resolve({
+      images: [image],
+    }));
+    customizeBackgrounds.selectedCollection = createCollection();
+    await flushTasks();
+
+    // Act.
+    customizeBackgrounds.selectedCollection = null;
+    await flushTasks();
+
+    // Assert.
+    assertNotStyle(customizeBackgrounds.$.collections, 'display', 'none');
+    assertStyle(customizeBackgrounds.$.images, 'display', 'none');
   });
 });
diff --git a/chrome/test/data/webui/new_tab_page/customize_dialog_test.js b/chrome/test/data/webui/new_tab_page/customize_dialog_test.js
index 6d7aecaa..474abbe 100644
--- a/chrome/test/data/webui/new_tab_page/customize_dialog_test.js
+++ b/chrome/test/data/webui/new_tab_page/customize_dialog_test.js
@@ -6,7 +6,7 @@
 
 import {BrowserProxy} from 'chrome://new-tab-page/browser_proxy.js';
 import {createTestProxy} from 'chrome://test/new_tab_page/test_support.js';
-import {flushTasks, waitAfterNextRender} from 'chrome://test/test_util.m.js';
+import {flushTasks, isVisible, waitAfterNextRender} from 'chrome://test/test_util.m.js';
 
 suite('NewTabPageCustomizeDialogTest', () => {
   /** @type {!CustomizeDialogElement} */
@@ -53,4 +53,46 @@
     assertEquals(shownPages.length, 1);
     assertEquals(shownPages[0].getAttribute('page-name'), 'themes');
   });
+
+  suite('scroll borders', () => {
+    /**
+     * @param {!HTMLElement} container
+     * @private
+     */
+    async function testScrollBorders(container) {
+      const assertHidden = el => {
+        assertTrue(el.matches('[scroll-border]:not([show])'));
+      };
+      const assertShown = el => {
+        assertTrue(el.matches('[scroll-border][show]'));
+      };
+      const {firstElementChild: top, lastElementChild: bottom} = container;
+      const scrollableElement = top.nextSibling;
+      const dialogBody =
+          customizeDialog.shadowRoot.querySelector('div[slot=body]');
+      const heightWithBorders = `${scrollableElement.scrollHeight + 2}px`;
+      dialogBody.style.height = heightWithBorders;
+      assertHidden(top);
+      assertHidden(bottom);
+      dialogBody.style.height = '50px';
+      await waitAfterNextRender();
+      assertHidden(top);
+      assertShown(bottom);
+      scrollableElement.scrollTop = 1;
+      await waitAfterNextRender();
+      assertShown(top);
+      assertShown(bottom);
+      scrollableElement.scrollTop = scrollableElement.scrollHeight;
+      await waitAfterNextRender();
+      assertShown(top);
+      assertHidden(bottom);
+      dialogBody.style.height = heightWithBorders;
+      await waitAfterNextRender();
+      assertHidden(top);
+      assertHidden(bottom);
+    }
+
+    test('menu', () => testScrollBorders(customizeDialog.$.menuContainer));
+    test('pages', () => testScrollBorders(customizeDialog.$.pagesContainer));
+  });
 });
diff --git a/chrome/test/data/webui/new_tab_page/test_support.js b/chrome/test/data/webui/new_tab_page/test_support.js
index 9d408f4..6853289 100644
--- a/chrome/test/data/webui/new_tab_page/test_support.js
+++ b/chrome/test/data/webui/new_tab_page/test_support.js
@@ -33,6 +33,17 @@
 }
 
 /**
+ * Asserts the computed style for an element is not value.
+ * @param {!HTMLElement} element The element.
+ * @param {string} name The name of the style to assert.
+ * @param {string} not The value the style should not be.
+ */
+export function assertNotStyle(element, name, not) {
+  const actual = window.getComputedStyle(element).getPropertyValue(name).trim();
+  assertNotEquals(not, actual);
+}
+
+/**
  * Asserts that an element is focused.
  * @param {!HTMLElement} element The element to test.
  */
diff --git a/chrome/test/data/webui/settings/chromeos/quick_unlock_authenticate_browsertest_chromeos.js b/chrome/test/data/webui/settings/chromeos/quick_unlock_authenticate_browsertest_chromeos.js
index f1a4229..61773a16 100644
--- a/chrome/test/data/webui/settings/chromeos/quick_unlock_authenticate_browsertest_chromeos.js
+++ b/chrome/test/data/webui/settings/chromeos/quick_unlock_authenticate_browsertest_chromeos.js
@@ -389,6 +389,39 @@
         assertDeepEquals([], quickUnlockPrivateApi.activeModes);
       });
 
+      // Tests correct UI conflict resolution in the event of a race condition
+      // that may occur when:
+      // (1) User selects PIN_PASSSWORD, and successfully sets a pin, adding
+      //     QuickUnlockMode.PIN to active modes.
+      // (2) User selects PASSWORD, QuickUnlockMode.PIN capability is cleared
+      //     from the active modes, notifying LockStateBehavior to call
+      //     updateUnlockType to fetch the active modes asynchronously.
+      // (3) User selects PIN_PASSWORD, but the process from step 2 has
+      //     not yet completed.
+      // See https://crbug.com/1054327 for details.
+      test('UserSelectsPinBeforePasswordOnlyStateSet', function() {
+        setActiveModes([QuickUnlockMode.PIN]);
+        assertRadioButtonChecked(pinPasswordRadioButton);
+        assertTrue(isSetupPinButtonVisible());
+        Polymer.dom.flush();
+        assertEquals(testElement.$$('#setupPinButton').innerText, 'Change PIN');
+
+        // Clicking will trigger an async call which setActiveModes([]) fakes.
+        passwordRadioButton.click();
+        assertFalse(isSetupPinButtonVisible());
+
+        pinPasswordRadioButton.click();
+        assertTrue(isSetupPinButtonVisible());
+
+        // Simulate the state change to PASSWORD after selecting PIN radio.
+        setActiveModes([]);
+
+        Polymer.dom.flush();
+        assertRadioButtonChecked(pinPasswordRadioButton);
+        assertTrue(isSetupPinButtonVisible());
+        assertEquals(testElement.$$('#setupPinButton').innerText, 'Set up PIN');
+      });
+
       // Tapping the PIN configure button opens up the setup PIN dialog, and
       // records a chose pin or password uma.
       test('TappingConfigureOpensSetupPin', function() {
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 4e2d3c6..6c285d0 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -1025,8 +1025,7 @@
   ]),
 };
 
-// Disabled due to flakiness: https://crbug.com/1057504.
-TEST_F('CrSettingsSecureDnsTest', 'DISABLED_All', function() {
+TEST_F('CrSettingsSecureDnsTest', 'All', function() {
   mocha.run();
 });
 
diff --git a/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js b/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js
index fcf7639..d1f32be2 100644
--- a/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js
+++ b/chrome/test/data/webui/settings/cr_settings_interactive_ui_tests.js
@@ -72,3 +72,29 @@
 TEST_F('CrSettingsAnimatedPagesTest', 'All', function() {
   mocha.run();
 });
+
+
+/**
+ * @constructor
+ * @extends {CrSettingsBrowserTest}
+ */
+function CrSettingsSecureDnsTest() {}
+
+CrSettingsSecureDnsTest.prototype = {
+  __proto__: CrSettingsInteractiveUITest.prototype,
+
+  /** @override */
+  browsePreload: 'chrome://settings/privacy_page/secure_dns.html',
+
+  /** @override */
+  extraLibraries: CrSettingsInteractiveUITest.prototype.extraLibraries.concat([
+    '../test_util.js',
+    '../test_browser_proxy.js',
+    'test_privacy_page_browser_proxy.js',
+    'secure_dns_interactive_test.js',
+  ]),
+};
+
+TEST_F('CrSettingsSecureDnsTest', 'All', function() {
+  mocha.run();
+});
diff --git a/chrome/test/data/webui/settings/secure_dns_interactive_test.js b/chrome/test/data/webui/settings/secure_dns_interactive_test.js
new file mode 100644
index 0000000..43bcd85
--- /dev/null
+++ b/chrome/test/data/webui/settings/secure_dns_interactive_test.js
@@ -0,0 +1,406 @@
+// Copyright 2020 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.
+
+/**
+ * @fileoverview Suite of tests for settings-secure-dns and
+ * secure-dns-input interactively.
+ */
+
+suite('SettingsSecureDnsInputInteractive', function() {
+  /** @type {SecureDnsInputElement} */
+  let testElement;
+
+  setup(function() {
+    PolymerTest.clearBody();
+    testElement = document.createElement('secure-dns-input');
+    document.body.appendChild(testElement);
+    Polymer.dom.flush();
+  });
+
+  teardown(function() {
+    testElement.remove();
+  });
+
+  test('SecureDnsInputFocus', function() {
+    const crInput = testElement.$$('#input');
+    assertFalse(crInput.hasAttribute('focused_'));
+    testElement.focus();
+    assertTrue(crInput.hasAttribute('focused_'));
+    testElement.blur();
+    assertFalse(crInput.hasAttribute('focused_'));
+  });
+});
+
+suite('SettingsSecureDnsInteractive', function() {
+  /** @type {settings.TestPrivacyPageBrowserProxy} */
+  let testBrowserProxy;
+
+  /** @type {SettingsSecureDnsElement} */
+  let testElement;
+
+  /** @type {SettingsToggleButtonElement} */
+  let secureDnsToggle;
+
+  /** @type {CrRadioGroupElement} */
+  let secureDnsRadioGroup;
+
+  /** @type {!Array<!settings.ResolverOption>} */
+  const resolverList = [
+    {name: 'Custom', value: 'custom', policy: ''},
+    {
+      name: 'resolver1',
+      value: 'resolver1_template',
+      policy: 'https://resolver1_policy.com/'
+    },
+    {
+      name: 'resolver2',
+      value: 'resolver2_template',
+      policy: 'https://resolver2_policy.com/'
+    },
+    {
+      name: 'resolver3',
+      value: 'resolver3_template',
+      policy: 'https://resolver3_policy.com/'
+    },
+  ];
+
+  suiteSetup(function() {
+    loadTimeData.overrideValues({showSecureDnsSetting: true});
+  });
+
+  setup(async function() {
+    testBrowserProxy = new TestPrivacyPageBrowserProxy();
+    testBrowserProxy.setResolverList(resolverList);
+    settings.PrivacyPageBrowserProxyImpl.instance_ = testBrowserProxy;
+    PolymerTest.clearBody();
+    testElement = document.createElement('settings-secure-dns');
+    testElement.prefs = {
+      dns_over_https: {
+        mode: {value: settings.SecureDnsMode.AUTOMATIC},
+        templates: {value: ''}
+      },
+    };
+    document.body.appendChild(testElement);
+
+    await testBrowserProxy.whenCalled('getSecureDnsSetting');
+    await test_util.flushTasks();
+    secureDnsToggle = testElement.$$('#secureDnsToggle');
+    secureDnsRadioGroup = testElement.$$('#secureDnsRadioGroup');
+  });
+
+  teardown(function() {
+    testElement.remove();
+  });
+
+  test('SecureDnsModeChange', async function() {
+    // Start in automatic mode.
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.AUTOMATIC,
+      templates: [],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+
+    // Click on the secure dns toggle to disable secure dns.
+    secureDnsToggle.click();
+    assertEquals(
+        settings.SecureDnsMode.OFF,
+        testElement.prefs.dns_over_https.mode.value);
+
+    // Click on the secure dns toggle to go back to automatic mode.
+    secureDnsToggle.click();
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC,
+        testElement.prefs.dns_over_https.mode.value);
+
+    // Change the radio button to secure mode. The focus should be on the
+    // custom text field and the mode pref should still be 'automatic'.
+    secureDnsRadioGroup.querySelectorAll('cr-radio-button')[1].click();
+    const secureDnsInput = testElement.$$('#secureDnsInput');
+    assertTrue(secureDnsInput.matches(':focus-within'));
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC,
+        testElement.prefs.dns_over_https.mode.value);
+
+    // Enter a correctly formatted template in the custom text field and
+    // click outside the text field. The mode pref should be updated to
+    // 'secure'.
+    secureDnsInput.focus();
+    secureDnsInput.value = 'https://example.doh.server/dns-query';
+    testBrowserProxy.setIsEntryValid(true);
+    secureDnsInput.blur();
+    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
+    assertEquals(
+        settings.SecureDnsMode.SECURE,
+        testElement.prefs.dns_over_https.mode.value);
+
+    // Click on the secure dns toggle to disable secure dns.
+    secureDnsToggle.click();
+    assertEquals(
+        settings.SecureDnsMode.OFF,
+        testElement.prefs.dns_over_https.mode.value);
+
+    // Click on the secure dns toggle. Focus should be on the custom text field
+    // and the mode pref should remain 'off' until the text field is blurred.
+    secureDnsToggle.click();
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+    assertTrue(secureDnsInput.matches(':focus-within'));
+    assertEquals('https://example.doh.server/dns-query', secureDnsInput.value);
+    assertEquals(
+        settings.SecureDnsMode.OFF,
+        testElement.prefs.dns_over_https.mode.value);
+    secureDnsInput.blur();
+    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
+    assertEquals(
+        settings.SecureDnsMode.SECURE,
+        testElement.prefs.dns_over_https.mode.value);
+  });
+
+  test('SecureDnsDropdown', function() {
+    const options =
+        testElement.$$('#secureResolverSelect').querySelectorAll('option');
+    assertEquals(4, options.length);
+
+    for (let i = 0; i < options.length; i++) {
+      assertEquals(resolverList[i].name, options[i].text);
+      assertEquals(resolverList[i].value, options[i].value);
+    }
+  });
+
+  test('SecureDnsDropdownCustom', function() {
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.SECURE,
+      templates: ['custom'],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+    assertEquals(0, testElement.$$('#secureResolverSelect').selectedIndex);
+    assertTrue(testElement.$$('#privacyPolicy').hasAttribute('hidden'));
+    assertFalse(testElement.$$('#secureDnsInput').hasAttribute('hidden'));
+    assertFalse(testElement.$$('#secureDnsInput').matches(':focus-within'));
+    assertEquals('custom', testElement.$$('#secureDnsInput').value);
+  });
+
+  test('SecureDnsDropdownChangeInSecureMode', function() {
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.SECURE,
+      templates: [resolverList[1].value],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+
+    const dropdownMenu = testElement.$$('#secureResolverSelect');
+    const privacyPolicyLine = testElement.$$('#privacyPolicy');
+    const secureDnsInput = testElement.$$('#secureDnsInput');
+
+    assertEquals(1, dropdownMenu.selectedIndex);
+    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
+    assertEquals(
+        resolverList[1].policy, privacyPolicyLine.querySelector('a').href);
+
+    // Change to resolver2
+    dropdownMenu.value = resolverList[2].value;
+    dropdownMenu.dispatchEvent(new Event('change'));
+    assertEquals(2, dropdownMenu.selectedIndex);
+    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
+    assertEquals(
+        resolverList[2].policy, privacyPolicyLine.querySelector('a').href);
+    assertEquals(
+        resolverList[2].value,
+        testElement.prefs.dns_over_https.templates.value);
+
+    // Change to custom
+    dropdownMenu.value = 'custom';
+    dropdownMenu.dispatchEvent(new Event('change'));
+    assertEquals(0, dropdownMenu.selectedIndex);
+    assertTrue(privacyPolicyLine.hasAttribute('hidden'));
+    assertTrue(secureDnsInput.matches(':focus-within'));
+    assertFalse(secureDnsInput.isInvalid());
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+    assertEquals(
+        settings.SecureDnsMode.SECURE,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals(
+        resolverList[2].value,
+        testElement.prefs.dns_over_https.templates.value);
+
+    // Input a custom template and make sure it is still there after
+    // manipulating the dropdown.
+    secureDnsInput.value = 'some_input';
+    dropdownMenu.value = resolverList[1].value;
+    dropdownMenu.dispatchEvent(new Event('change'));
+    assertEquals(
+        settings.SecureDnsMode.SECURE,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals(
+        resolverList[1].value,
+        testElement.prefs.dns_over_https.templates.value);
+    dropdownMenu.value = 'custom';
+    dropdownMenu.dispatchEvent(new Event('change'));
+    assertEquals('some_input', secureDnsInput.value);
+  });
+
+  test('SecureDnsDropdownChangeInAutomaticMode', function() {
+    testElement.prefs.dns_over_https.templates.value = 'resolver1_template';
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.AUTOMATIC,
+      templates: [resolverList[1].value],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
+
+    const dropdownMenu = testElement.$$('#secureResolverSelect');
+    const privacyPolicyLine = testElement.$$('#privacyPolicy');
+
+    // Select resolver3. This change should not be reflected in prefs.
+    assertNotEquals(3, dropdownMenu.selectedIndex);
+    dropdownMenu.value = resolverList[3].value;
+    dropdownMenu.dispatchEvent(new Event('change'));
+    assertEquals(3, dropdownMenu.selectedIndex);
+    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
+    assertEquals(
+        resolverList[3].policy, privacyPolicyLine.querySelector('a').href);
+    assertEquals(
+        'resolver1_template', testElement.prefs.dns_over_https.templates.value);
+
+    // Click on the secure dns toggle to disable secure dns.
+    secureDnsToggle.click();
+    assertTrue(secureDnsRadioGroup.hidden);
+    assertEquals(
+        settings.SecureDnsMode.OFF,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals('', testElement.prefs.dns_over_https.templates.value);
+
+    // Get another event enabling automatic mode.
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.AUTOMATIC,
+      templates: [resolverList[1].value],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    assertFalse(secureDnsRadioGroup.hidden);
+    assertEquals(3, dropdownMenu.selectedIndex);
+    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
+    assertEquals(
+        resolverList[3].policy, privacyPolicyLine.querySelector('a').href);
+
+    // Click on secure mode radio button.
+    secureDnsRadioGroup.querySelectorAll('cr-radio-button')[1].click();
+    assertFalse(secureDnsRadioGroup.hidden);
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+    assertEquals(3, dropdownMenu.selectedIndex);
+    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
+    assertEquals(
+        resolverList[3].policy, privacyPolicyLine.querySelector('a').href);
+    assertEquals(
+        settings.SecureDnsMode.SECURE,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals(
+        'resolver3_template', testElement.prefs.dns_over_https.templates.value);
+  });
+
+  test('SecureDnsInputChange', async function() {
+    // Start in secure mode with a custom valid template
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.SECURE,
+      templates: ['https://dns.example/dns-query'],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    const secureDnsRadioGroup = testElement.$$('#secureDnsRadioGroup');
+    const secureDnsInput = testElement.$$('#secureDnsInput');
+    assertFalse(secureDnsInput.hasAttribute('hidden'));
+    assertFalse(secureDnsInput.matches(':focus-within'));
+    assertFalse(secureDnsInput.isInvalid());
+    assertEquals('https://dns.example/dns-query', secureDnsInput.value);
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+
+    // Make the template invalid and check that the mode pref changes to
+    // 'automatic'.
+    secureDnsInput.focus();
+    secureDnsInput.value = 'invalid_template';
+    testBrowserProxy.setIsEntryValid(false);
+    secureDnsInput.blur();
+    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
+    assertFalse(secureDnsInput.matches(':focus-within'));
+    assertTrue(secureDnsInput.isInvalid());
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals('', testElement.prefs.dns_over_https.templates.value);
+
+    // Receive a pref update and make sure the custom input field is not
+    // cleared.
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.AUTOMATIC,
+      templates: [],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    assertFalse(secureDnsInput.hasAttribute('hidden'));
+    assertFalse(secureDnsInput.matches(':focus-within'));
+    assertTrue(secureDnsInput.isInvalid());
+    assertEquals('invalid_template', secureDnsInput.value);
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
+
+    // Make the template valid but don't change the radio button yet.
+    secureDnsInput.focus();
+    secureDnsInput.value =
+        'https://dns.ex/dns-query  invalid  https://dns.ex.another/dns-query';
+    testBrowserProxy.setIsEntryValid(true);
+    secureDnsInput.blur();
+    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
+    assertFalse(secureDnsInput.matches(':focus-within'));
+    assertFalse(secureDnsInput.isInvalid());
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
+
+    // Select the secure radio button and blur the input field.
+    secureDnsRadioGroup.querySelectorAll('cr-radio-button')[1].click();
+    assertTrue(secureDnsInput.matches(':focus-within'));
+    assertFalse(secureDnsInput.isInvalid());
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+    assertEquals(
+        settings.SecureDnsMode.AUTOMATIC,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals('', testElement.prefs.dns_over_https.templates.value);
+    secureDnsInput.blur();
+    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
+    assertFalse(secureDnsInput.matches(':focus-within'));
+    assertFalse(secureDnsInput.isInvalid());
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+    assertEquals(
+        settings.SecureDnsMode.SECURE,
+        testElement.prefs.dns_over_https.mode.value);
+    assertEquals(
+        'https://dns.ex/dns-query  invalid  https://dns.ex.another/dns-query',
+        testElement.prefs.dns_over_https.templates.value);
+
+    // Make sure the input field updates with a change in the underlying
+    // templates pref in secure mode.
+    cr.webUIListenerCallback('secure-dns-setting-changed', {
+      mode: settings.SecureDnsMode.SECURE,
+      templates: [
+        'https://manage.ex/dns-query',
+        'https://manage.ex.another/dns-query{?dns}'
+      ],
+      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
+    });
+    Polymer.dom.flush();
+    assertFalse(secureDnsInput.hasAttribute('hidden'));
+    assertFalse(secureDnsInput.matches(':focus-within'));
+    assertFalse(secureDnsInput.isInvalid());
+    assertEquals(
+        'https://manage.ex/dns-query https://manage.ex.another/dns-query{?dns}',
+        secureDnsInput.value);
+    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
+  });
+});
diff --git a/chrome/test/data/webui/settings/secure_dns_test.js b/chrome/test/data/webui/settings/secure_dns_test.js
index bc8c62c..b54ae6a 100644
--- a/chrome/test/data/webui/settings/secure_dns_test.js
+++ b/chrome/test/data/webui/settings/secure_dns_test.js
@@ -43,8 +43,7 @@
 
     // Trigger validation on an empty input.
     testBrowserProxy.setIsEntryValid(false);
-    testElement.focus();
-    testElement.blur();
+    testElement.validate();
     await testBrowserProxy.whenCalled('validateCustomDnsEntry');
     assertFalse(crInput.invalid);
     assertFalse(testElement.isInvalid());
@@ -52,8 +51,7 @@
     // Enter a valid input and trigger validation.
     testElement.value = 'https://example.server/dns-query';
     testBrowserProxy.setIsEntryValid(true);
-    testElement.focus();
-    testElement.blur();
+    testElement.validate();
     await testBrowserProxy.whenCalled('validateCustomDnsEntry');
     assertFalse(crInput.invalid);
     assertFalse(testElement.isInvalid());
@@ -61,8 +59,7 @@
     // Enter an invalid input and trigger validation.
     testElement.value = 'invalid_template';
     testBrowserProxy.setIsEntryValid(false);
-    testElement.focus();
-    testElement.blur();
+    testElement.validate();
     await testBrowserProxy.whenCalled('validateCustomDnsEntry');
     assertTrue(crInput.invalid);
     assertTrue(testElement.isInvalid());
@@ -74,15 +71,6 @@
     assertFalse(testElement.isInvalid());
     assertEquals('invalid_template', testElement.value);
   });
-
-  test('SecureDnsInputFocus', function() {
-    const crInput = testElement.$$('#input');
-    assertFalse(crInput.hasAttribute('focused_'));
-    testElement.focus();
-    assertTrue(crInput.hasAttribute('focused_'));
-    testElement.blur();
-    assertFalse(crInput.hasAttribute('focused_'));
-  });
 });
 
 suite('SettingsSecureDns', function() {
@@ -101,21 +89,6 @@
   /** @type {!Array<!settings.ResolverOption>} */
   const resolverList = [
     {name: 'Custom', value: 'custom', policy: ''},
-    {
-      name: 'resolver1',
-      value: 'resolver1_template',
-      policy: 'https://resolver1_policy.com/'
-    },
-    {
-      name: 'resolver2',
-      value: 'resolver2_template',
-      policy: 'https://resolver2_policy.com/'
-    },
-    {
-      name: 'resolver3',
-      value: 'resolver3_template',
-      policy: 'https://resolver3_policy.com/'
-    },
   ];
 
   // Possible subtitle overrides.
@@ -268,316 +241,4 @@
                     .$$('cr-tooltip-icon')
                     .hidden);
   });
-
-  test('SecureDnsModeChange', async function() {
-    // Start in automatic mode.
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.AUTOMATIC,
-      templates: [],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-
-    // Click on the secure dns toggle to disable secure dns.
-    secureDnsToggle.click();
-    assertEquals(
-        settings.SecureDnsMode.OFF,
-        testElement.prefs.dns_over_https.mode.value);
-
-    // Click on the secure dns toggle to go back to automatic mode.
-    secureDnsToggle.click();
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC,
-        testElement.prefs.dns_over_https.mode.value);
-
-    // Change the radio button to secure mode. The focus should be on the
-    // custom text field and the mode pref should still be 'automatic'.
-    secureDnsRadioGroup.querySelectorAll('cr-radio-button')[1].click();
-    const secureDnsInput = testElement.$$('#secureDnsInput');
-    assertTrue(secureDnsInput.matches(':focus-within'));
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC,
-        testElement.prefs.dns_over_https.mode.value);
-
-    // Enter a correctly formatted template in the custom text field and
-    // click outside the text field. The mode pref should be updated to
-    // 'secure'.
-    secureDnsInput.value = 'https://example.doh.server/dns-query';
-    testBrowserProxy.setIsEntryValid(true);
-    secureDnsInput.blur();
-    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
-    assertEquals(
-        settings.SecureDnsMode.SECURE,
-        testElement.prefs.dns_over_https.mode.value);
-
-    // Click on the secure dns toggle to disable secure dns.
-    secureDnsToggle.click();
-    assertEquals(
-        settings.SecureDnsMode.OFF,
-        testElement.prefs.dns_over_https.mode.value);
-
-    // Click on the secure dns toggle. Focus should be on the custom text field
-    // and the mode pref should remain 'off' until the text field is blurred.
-    secureDnsToggle.click();
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-    assertTrue(secureDnsInput.matches(':focus-within'));
-    assertEquals('https://example.doh.server/dns-query', secureDnsInput.value);
-    assertEquals(
-        settings.SecureDnsMode.OFF,
-        testElement.prefs.dns_over_https.mode.value);
-    secureDnsInput.blur();
-    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
-    assertEquals(
-        settings.SecureDnsMode.SECURE,
-        testElement.prefs.dns_over_https.mode.value);
-  });
-
-  test('SecureDnsDropdown', function() {
-    const options =
-        testElement.$$('#secureResolverSelect').querySelectorAll('option');
-    assertEquals(4, options.length);
-
-    for (let i = 0; i < options.length; i++) {
-      assertEquals(resolverList[i].name, options[i].text);
-      assertEquals(resolverList[i].value, options[i].value);
-    }
-  });
-
-  test('SecureDnsDropdownCustom', function() {
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.SECURE,
-      templates: ['custom'],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    assertRadioButtonsShown();
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-    assertEquals(0, testElement.$$('#secureResolverSelect').selectedIndex);
-    assertTrue(testElement.$$('#privacyPolicy').hasAttribute('hidden'));
-    assertFalse(testElement.$$('#secureDnsInput').hasAttribute('hidden'));
-    assertFalse(testElement.$$('#secureDnsInput').matches(':focus-within'));
-    assertEquals('custom', testElement.$$('#secureDnsInput').value);
-  });
-
-  test('SecureDnsDropdownChangeInSecureMode', function() {
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.SECURE,
-      templates: [resolverList[1].value],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    assertRadioButtonsShown();
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-
-    const dropdownMenu = testElement.$$('#secureResolverSelect');
-    const privacyPolicyLine = testElement.$$('#privacyPolicy');
-    const secureDnsInput = testElement.$$('#secureDnsInput');
-
-    assertEquals(1, dropdownMenu.selectedIndex);
-    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
-    assertEquals(
-        resolverList[1].policy, privacyPolicyLine.querySelector('a').href);
-
-    // Change to resolver2
-    dropdownMenu.value = resolverList[2].value;
-    dropdownMenu.dispatchEvent(new Event('change'));
-    assertEquals(2, dropdownMenu.selectedIndex);
-    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
-    assertEquals(
-        resolverList[2].policy, privacyPolicyLine.querySelector('a').href);
-    assertEquals(
-        resolverList[2].value,
-        testElement.prefs.dns_over_https.templates.value);
-
-    // Change to custom
-    dropdownMenu.value = 'custom';
-    dropdownMenu.dispatchEvent(new Event('change'));
-    assertEquals(0, dropdownMenu.selectedIndex);
-    assertTrue(privacyPolicyLine.hasAttribute('hidden'));
-    assertTrue(secureDnsInput.matches(':focus-within'));
-    assertFalse(secureDnsInput.isInvalid());
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-    assertEquals(
-        settings.SecureDnsMode.SECURE,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals(
-        resolverList[2].value,
-        testElement.prefs.dns_over_https.templates.value);
-
-    // Input a custom template and make sure it is still there after
-    // manipulating the dropdown.
-    secureDnsInput.value = 'some_input';
-    dropdownMenu.value = resolverList[1].value;
-    dropdownMenu.dispatchEvent(new Event('change'));
-    assertEquals(
-        settings.SecureDnsMode.SECURE,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals(
-        resolverList[1].value,
-        testElement.prefs.dns_over_https.templates.value);
-    dropdownMenu.value = 'custom';
-    dropdownMenu.dispatchEvent(new Event('change'));
-    assertEquals('some_input', secureDnsInput.value);
-  });
-
-  test('SecureDnsDropdownChangeInAutomaticMode', function() {
-    testElement.prefs.dns_over_https.templates.value = 'resolver1_template';
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.AUTOMATIC,
-      templates: [resolverList[1].value],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    assertRadioButtonsShown();
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
-
-    const dropdownMenu = testElement.$$('#secureResolverSelect');
-    const privacyPolicyLine = testElement.$$('#privacyPolicy');
-
-    // Select resolver3. This change should not be reflected in prefs.
-    assertNotEquals(3, dropdownMenu.selectedIndex);
-    dropdownMenu.value = resolverList[3].value;
-    dropdownMenu.dispatchEvent(new Event('change'));
-    assertEquals(3, dropdownMenu.selectedIndex);
-    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
-    assertEquals(
-        resolverList[3].policy, privacyPolicyLine.querySelector('a').href);
-    assertEquals(
-        'resolver1_template', testElement.prefs.dns_over_https.templates.value);
-
-    // Click on the secure dns toggle to disable secure dns.
-    secureDnsToggle.click();
-    assertTrue(secureDnsRadioGroup.hidden);
-    assertEquals(
-        settings.SecureDnsMode.OFF,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals('', testElement.prefs.dns_over_https.templates.value);
-
-    // Get another event enabling automatic mode.
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.AUTOMATIC,
-      templates: [resolverList[1].value],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    assertFalse(secureDnsRadioGroup.hidden);
-    assertEquals(3, dropdownMenu.selectedIndex);
-    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
-    assertEquals(
-        resolverList[3].policy, privacyPolicyLine.querySelector('a').href);
-
-    // Click on secure mode radio button.
-    secureDnsRadioGroup.querySelectorAll('cr-radio-button')[1].click();
-    assertFalse(secureDnsRadioGroup.hidden);
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-    assertEquals(3, dropdownMenu.selectedIndex);
-    assertFalse(privacyPolicyLine.hasAttribute('hidden'));
-    assertEquals(
-        resolverList[3].policy, privacyPolicyLine.querySelector('a').href);
-    assertEquals(
-        settings.SecureDnsMode.SECURE,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals(
-        'resolver3_template', testElement.prefs.dns_over_https.templates.value);
-  });
-
-  test('SecureDnsInputChange', async function() {
-    // Start in secure mode with a custom valid template
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.SECURE,
-      templates: ['https://dns.example/dns-query'],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    const secureDnsInput = testElement.$$('#secureDnsInput');
-    assertFalse(secureDnsInput.hasAttribute('hidden'));
-    assertFalse(secureDnsInput.matches(':focus-within'));
-    assertFalse(secureDnsInput.isInvalid());
-    assertEquals('https://dns.example/dns-query', secureDnsInput.value);
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-
-    // Make the template invalid and check that the mode pref changes to
-    // 'automatic'.
-    secureDnsInput.focus();
-    secureDnsInput.value = 'invalid_template';
-    testBrowserProxy.setIsEntryValid(false);
-    secureDnsInput.blur();
-    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
-    assertFalse(secureDnsInput.matches(':focus-within'));
-    assertTrue(secureDnsInput.isInvalid());
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals('', testElement.prefs.dns_over_https.templates.value);
-
-    // Receive a pref update and make sure the custom input field is not
-    // cleared.
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.AUTOMATIC,
-      templates: [],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    assertFalse(secureDnsInput.hasAttribute('hidden'));
-    assertFalse(secureDnsInput.matches(':focus-within'));
-    assertTrue(secureDnsInput.isInvalid());
-    assertEquals('invalid_template', secureDnsInput.value);
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
-
-    // Make the template valid but don't change the radio button yet.
-    secureDnsInput.focus();
-    secureDnsInput.value =
-        'https://dns.ex/dns-query  invalid  https://dns.ex.another/dns-query';
-    testBrowserProxy.setIsEntryValid(true);
-    secureDnsInput.blur();
-    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
-    assertFalse(secureDnsInput.matches(':focus-within'));
-    assertFalse(secureDnsInput.isInvalid());
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC, secureDnsRadioGroup.selected);
-
-    // Select the secure radio button and blur the input field.
-    secureDnsRadioGroup.querySelectorAll('cr-radio-button')[1].click();
-    assertTrue(secureDnsInput.matches(':focus-within'));
-    assertFalse(secureDnsInput.isInvalid());
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-    assertEquals(
-        settings.SecureDnsMode.AUTOMATIC,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals('', testElement.prefs.dns_over_https.templates.value);
-    secureDnsInput.blur();
-    await testBrowserProxy.whenCalled('validateCustomDnsEntry');
-    assertFalse(secureDnsInput.matches(':focus-within'));
-    assertFalse(secureDnsInput.isInvalid());
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-    assertEquals(
-        settings.SecureDnsMode.SECURE,
-        testElement.prefs.dns_over_https.mode.value);
-    assertEquals(
-        'https://dns.ex/dns-query  invalid  https://dns.ex.another/dns-query',
-        testElement.prefs.dns_over_https.templates.value);
-
-    // Make sure the input field updates with a change in the underlying
-    // templates pref in secure mode.
-    cr.webUIListenerCallback('secure-dns-setting-changed', {
-      mode: settings.SecureDnsMode.SECURE,
-      templates: [
-        'https://manage.ex/dns-query',
-        'https://manage.ex.another/dns-query{?dns}'
-      ],
-      managementMode: settings.SecureDnsUiManagementMode.NO_OVERRIDE,
-    });
-    Polymer.dom.flush();
-    assertFalse(secureDnsInput.hasAttribute('hidden'));
-    assertFalse(secureDnsInput.matches(':focus-within'));
-    assertFalse(secureDnsInput.isInvalid());
-    assertEquals(
-        'https://manage.ex/dns-query https://manage.ex.another/dns-query{?dns}',
-        secureDnsInput.value);
-    assertEquals(settings.SecureDnsMode.SECURE, secureDnsRadioGroup.selected);
-  });
 });
diff --git a/chromeos/components/media_app_ui/resources/js/message_pipe.js b/chromeos/components/media_app_ui/resources/js/message_pipe.js
index 1e326e3..15b92fbb 100644
--- a/chromeos/components/media_app_ui/resources/js/message_pipe.js
+++ b/chromeos/components/media_app_ui/resources/js/message_pipe.js
@@ -61,6 +61,26 @@
 }
 
 /**
+ * A simplified "assert" that casts away null types. Assumes preconditions that
+ * satisfy the assert have already been checked. Inspired by
+ * webui/resources/js/assert/assert.js. However, this file is used (and tested)
+ * verbatim in multiple repositories with different dependency management, so
+ * that's not used directly. TODO(b/150650426): consolidate this better.
+ *
+ * @template T
+ * @param {?T|undefined} condition
+ * @return {T} A non-null |condition|.
+ * @closurePrimitive {asserts.truthy}
+ * @suppress {reportUnknownTypes} because T is not sufficiently constrained.
+ */
+function assertCast(condition) {
+  if (!condition) {
+    throw new Error('Failed assertion');
+  }
+  return condition;
+}
+
+/**
  * Enum for reserved message types used in generated messages.
  *
  * @enum {string}
@@ -124,11 +144,11 @@
       if (!frame || !frame.contentWindow) {
         throw new Error('Unable to locate target content window.');
       }
-      target = /** @type{!Window} */ (frame.contentWindow);
+      target = assertCast(frame.contentWindow);
     }
 
     /** @private @const {!Window} */
-    this.target_ = /** @type {!Window} */ (target);
+    this.target_ = target;
 
     /** @private @const {string} */
     this.targetOrigin_ = targetOrigin;
@@ -175,7 +195,7 @@
     this.messageListener_ = (m) => this.receiveMessage_(m);
 
     // Make sure we aren't trying to send messages to ourselves.
-    console.assert(this.target_ !== window);
+    console.assert(this.target_ !== window, 'target !== window');
 
     window.addEventListener('message', this.messageListener_);
   }
@@ -270,18 +290,21 @@
     let response;
     /** @type {?Error} */
     let error = null;
+    /** @type {boolean} */
+    let sawError = false;
 
     try {
       response = await this.messageHandlers_.get(messageType)(message);
     } catch (/** @type {!Error} */ err) {
       error = err;
+      sawError = true;
       // If an error happened capture the error and send it back.
       response = {message: error.message || ''};
     }
 
     this.postToTarget_(error ? ERROR_TYPE : RESPONSE_TYPE, response, messageId);
 
-    if (error && this.rethrowErrors) {
+    if (sawError && this.rethrowErrors) {
       // Rethrow the error so the current frame has visibility on its handler
       // failures.
       throw error;
@@ -336,12 +359,11 @@
    * @param {number} messageId
    */
   postToTarget_(messageType, message, messageId) {
-    // Message must be an object.
-    if (!message) {
-      message = {};
-    }
-
-    const messageWrapper = {messageId, type: messageType, message};
+    const messageWrapper = {
+      messageId,
+      type: messageType,
+      message: message || {}
+    };
     // The next line should probably be passing a transfer argument, but that
     // causes Chrome to send a "null" message. The transfer seems to work
     // without the third argument (but inefficiently, perhaps).
diff --git a/chromeos/components/media_app_ui/resources/js/web_app_file_handling.externs.js b/chromeos/components/media_app_ui/resources/js/web_app_file_handling.externs.js
index 6964cd122..e929b7e 100644
--- a/chromeos/components/media_app_ui/resources/js/web_app_file_handling.externs.js
+++ b/chromeos/components/media_app_ui/resources/js/web_app_file_handling.externs.js
@@ -133,5 +133,30 @@
   setConsumer(consumer) {}
 }
 
+/**
+ * @typedef {{
+ *    description: (string|undefined),
+ *    mimeTypes: (Array<string>|undefined),
+ *    extensions: (Array<string>|undefined)
+ * }}
+ */
+var ChooseFileSystemEntriesOptionsAccepts;
+
+/**
+ * @typedef {{
+ *    type: (string|undefined),
+ *    multiple: (boolean|undefined),
+ *    accepts: (Array<ChooseFileSystemEntriesOptionsAccepts>|undefined),
+ *    excludeAcceptAllOption: (boolean|undefined)
+ * }}
+ */
+var chooseFileSystemEntriesOptions;
+
+/**
+ * @param {(chooseFileSystemEntriesOptions|undefined)} options
+ * @return {Promise<(FileSystemHandle|Array<FileSystemHandle>)>}
+ */
+window.chooseFileSystemEntries = function(options) {};
+
 /** @type {LaunchQueue} */
 window.launchQueue;
diff --git a/chromeos/constants/chromeos_features.cc b/chromeos/constants/chromeos_features.cc
index 06cfd64..4397488 100644
--- a/chromeos/constants/chromeos_features.cc
+++ b/chromeos/constants/chromeos_features.cc
@@ -241,8 +241,8 @@
                                   base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables or disables Release Notes notifications on Chrome OS.
-const base::Feature kReleaseNotesNotification{
-    "ReleaseNotesNotification", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kReleaseNotesNotification{"ReleaseNotesNotification",
+                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables or disables long kill timeout for session manager daemon. When
 // enabled, session manager daemon waits for a longer time (e.g. 12s) for chrome
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index c7c553e..4197364 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -881,7 +881,13 @@
   if (!is_display_move_pending) {
     ash::ClientControlledState::AdjustBoundsForMinimumWindowVisibility(
         target_display.work_area(), &adjusted_bounds);
-    if (GetWindowState()->IsPip() && !GetWindowState()->is_dragged()) {
+    // Collision detection to the bounds set by Android should be applied only
+    // to initial bounds. Do not adjust new bounds as it can be obsolete or in
+    // transit during animation, which results in incorrect resting postiion.
+    // The resting position should be fully controlled by chrome afterwards
+    // because Android isn't aware of Chrome OS System UI.
+    if (GetWindowState()->IsPip() &&
+        !ash::PipPositioner::HasSnapFraction(GetWindowState())) {
       adjusted_bounds = ash::CollisionDetectionUtils::GetRestingPosition(
           target_display, adjusted_bounds,
           ash::CollisionDetectionUtils::RelativePriority::kPictureInPicture);
diff --git a/components/feed/core/proto/v2/store.proto b/components/feed/core/proto/v2/store.proto
index 4fcc9e9..95c6ccb9 100644
--- a/components/feed/core/proto/v2/store.proto
+++ b/components/feed/core/proto/v2/store.proto
@@ -6,15 +6,11 @@
 
 package feedstore;
 
-option optimize_for = LITE_RUNTIME;
-
-import "components/feed/core/proto/ui/piet/piet.proto";
 import "components/feed/core/proto/ui/action/ui_feed_action.proto";
 import "components/feed/core/proto/ui/stream/stream_structure.proto";
-import "components/feed/core/proto/wire/action_payload.proto";
 import "components/feed/core/proto/wire/content_id.proto";
-import "components/feed/core/proto/wire/piet_shared_state_item.proto";
-import "components/feed/core/proto/wire/semantic_properties.proto";
+
+option optimize_for = LITE_RUNTIME;
 
 // Actual data stored by the client.
 // This data is sourced from the wire protocol, which is converted upon receipt.
@@ -22,18 +18,19 @@
 //
 // This is the 'value' in the key/value store.
 // Keys are defined as:
-// 'root'    -> stream_data
-// 'c:<id>'  -> cluster
-// 'la:<id>' -> local_action
-// 'ua:<id>' -> uploadable_action
-// 'ss:<id>' -> shared_state
+// 'S/<stream-id>' -> stream_data
+// 'c/<content-id>' -> content
+// 'a/<id>' -> action
+// 's/<content-id>' -> shared_state
+// 'N' -> next_stream_state
 message Record {
   oneof data {
-    Cluster cluster = 1;
-    StreamData stream_data = 2;
-    StreamLocalAction local_action = 3;
-    StreamUploadableAction uploadable_action = 4;
-    StreamSharedState shared_state = 5;
+    StreamData stream_data = 1;
+    Content content = 2;
+    StoredAction local_action = 3;
+    StreamSharedState shared_state = 4;
+    // The result of a background refresh, to be processed later.
+    StreamAndContentState next_stream_state = 5;
   }
 }
 
@@ -47,26 +44,58 @@
   bytes consistency_token = 3;
   // The unix timestamp in milliseconds that the most recent content was added.
   int64 last_added_time_millis = 4;
-  // List of all clusters, in order they would be shown in the stream.
-  repeated feedwire.ContentId cluster_ids = 5;
-  // List of all stale clusters. Stale clusters can be removed when no session
-  // references them.
-  repeated feedwire.ContentId stale_cluster_ids = 6;
+  // List of operations to perform to create the stream.
+  repeated StreamStructure structures = 5;
+  // Next sequential ID to be used for StoredAction.id.
+  int32 next_action_id = 6;
 }
 
-message Cluster {
-  feedwire.ContentId content_id = 1;
-  feedwire.SemanticProperties semantic_properties = 2;
-  repeated Card cards = 3;
+// This is the structure of the stream.  It is defined through the parent/child
+// relationship and an operation.  This message will be journaled.  Reading
+// the journal from start to end will fully define the structure of the stream.
+message StreamStructure {
+  // The defined set of DataOperations
+  // These operations align with the Operation enum defined in
+  // data_operation.proto.
+  enum Operation {
+    UNKNOWN = 0;
+    // Clear all the content in the session, creating a new session
+    CLEAR_ALL = 1;
+    // Append if not present or update
+    UPDATE_OR_APPEND = 2;
+    // Remove the node from its parent
+    REMOVE = 3;
+  }
+  Operation operation = 1;
+  // The ContentId of the content.
+  feedwire.ContentId content_id = 2;
+  // The parent ContentId, or unset if this is the root.
+  feedwire.ContentId parent_id = 3;
+
+  // Type of node as denoted by the server. This type has no meaning for the
+  // client.
+  enum Type {
+    // Default type for operations that don't affect the stream (e.g. operations
+    // on shared states).
+    UNKNOWN_TYPE = 0;
+    // The root of the stream.
+    STREAM = 1;
+    // An internal tree node, which may have children.
+    CARD = 2;
+    // A leaf node, which provides content.
+    CONTENT = 3;
+    // An internal tree node, which may have children.
+    CLUSTER = 4;
+  }
+  Type type = 4;
+  // Set iff type=CONTENT
+  ContentInfo content_info = 5;
 }
 
-message Card {
-  feedwire.ContentId card_id = 1;
-  feedwire.SemanticProperties semantic_properties = 2;
-  // This may not be present if Card is part of a DataOperation.
-  Content content = 3;
-  // All actions bound to this card or content.
-  repeated components.feed.core.proto.ui.action.FeedActionMetadata actions = 4;
+message DataOperation {
+  StreamStructure structure = 1;
+  // Provided if structure adds content.
+  Content content = 2;
 }
 
 message RepresentationData {
@@ -76,63 +105,44 @@
   int64 published_time_seconds = 2;
 }
 
-message Content {
-  // The parent Card's ContentId.
-  feedwire.ContentId content_id = 1;
-  feedwire.SemanticProperties semantic_properties = 2;
-
-  enum ContentType {
-    UNKNOWN_CONTENT = 0;
-    PIET = 1;
-  }
-
-  ContentType content_type = 3;
-  PietContent piet_content = 4;
+message ContentInfo {
   // Score given by server.
-  float score = 5;
+  float score = 1;
   // Unix timestamp (seconds) that content was received by Chrome.
-  int64 availability_time_seconds = 6;
-  RepresentationData representation_data = 7;
-  components.feed.core.proto.ui.stream.OfflineMetadata offline_metadata = 8;
-  components.feed.core.proto.ui.action.FeedActionMetadata swipe_action = 9;
+  int64 availability_time_seconds = 2;
+  RepresentationData representation_data = 3;
+  components.feed.core.proto.ui.stream.OfflineMetadata offline_metadata = 4;
 }
 
-// Content which is able to show a Piet frame. This includes any data which may
-// be needed to show a Piet frame.
-message PietContent {
-  // Content Ids of Piet Shared States which should be provided to Piet in order
-  // to show its content.
-  repeated feedwire.ContentId piet_shared_states = 1;
-
-  // The Piet frame to render. This is the same frame as sent over the wire,
-  // except the action extensions have been replaced by ui.Action.
-  components.feed.core.proto.ui.piet.Frame frame = 2;
+message Content {
+  feedwire.ContentId content_id = 1;
+  // Opaque content. The UI layer knows how to parse and render this as a slice.
+  bytes frame = 2;
 }
 
 // This represents a shared state item.
 message StreamSharedState {
   feedwire.ContentId content_id = 1;
-  feedwire.PietSharedStateItem piet_shared_state_item = 2;
+  // Opaque data required to render content.
+  bytes shared_state_data = 2;
 }
 
-// An action that should be eventually uploaded, unless it is undone.
-message StreamLocalAction {
-  enum Type {
-    UNKNOWN = 0;
-    DISMISS = 1;
-  }
-  Type action = 1;
-  feedwire.ContentId feature_content_id = 2;
-  // When the action was recorded
-  int64 timestamp_seconds = 3;
+// A stored action awaiting upload.
+message StoredAction {
+  // Unique ID for this stored action, provided by the client.
+  // This is a sequential number, so that the action with the lowest id value
+  // was recorded chronologically first.
+  int32 id = 1;
+  // How many times we have tried to upload the action.
+  int32 upload_attempt_count = 2;
+  // The action to upload.
+  components.feed.core.proto.ui.action.FeedAction action = 3;
 }
 
-// An action ready to be uploaded.
-message StreamUploadableAction {
-  feedwire.ContentId feature_content_id = 1;
-  // The number of time this action was attempted to be recorded
-  int32 upload_attempts = 2;
-  // Unix timestamp (seconds) when the action was recorded
-  int64 timestamp_seconds = 3;
-  feedwire.ActionPayload payload = 4;
+// The internal version of the server response. Includes feature tree and
+// content.
+message StreamAndContentState {
+  StreamData stream_data = 1;
+  repeated Content content = 2;
+  repeated StreamSharedState shared_state = 3;
 }
diff --git a/components/feed/core/proto/v2/ui.proto b/components/feed/core/proto/v2/ui.proto
index c617b99..ff2a85a 100644
--- a/components/feed/core/proto/v2/ui.proto
+++ b/components/feed/core/proto/v2/ui.proto
@@ -6,118 +6,48 @@
 
 package feedui;
 
-option optimize_for = LITE_RUNTIME;
-
 import "components/feed/core/proto/ui/piet/piet.proto";
 
+option optimize_for = LITE_RUNTIME;
+
 // This is a simplified and complete set of protos that define UI.
 // It includes everything from search.now.ui needed in the UI, and excludes
 // other data to reduce complexity. These proto messages should be constructible
 // from the store protos.
 
-// A stream is a list of cards in order.
-// Each StreamUpdate contains the full list of cards,
+// A stream is a list of chunks in order.
+// Each StreamUpdate contains the full list of chunks,
 // but subsequent StreamUpdates after the first may refer to
-// cards previously received by card_id.
+// chunks previously received by chunk_id.
 message StreamUpdate {
-  // Either a reference to an existing card, or a new card.
-  message CardUpdate {
+  // Either a reference to an existing slice, or a new slice.
+  message SliceUpdate {
     oneof update {
-      Card card = 1;
-      string card_id = 2;
+      Slice slice = 1;
+      string slice_id = 2;
     }
   }
-  // One entry for each card in the stream, in the order they should be
-  // presented. Existing cards not present in updated_cards should be dropped.
-  repeated CardUpdate updated_cards = 1;
+  // One entry for each slice in the stream, in the order they should be
+  // presented. Existing slices not present in updated_slices should be dropped.
+  repeated SliceUpdate updated_slices = 1;
+  // Additional shared states to be used. Usually just one, and sent only on the
+  // first update.
   repeated SharedState new_shared_states = 2;
-  // True if all cards are already shown. The 'more' button will be
-  // hidden if this is true.
-  bool no_more_cards = 3;
 }
 
-// A card rendered with Piet.
-message Card {
-  // An opaque unique ID.
-  string card_id = 1;
-  repeated string piet_shared_state_ids = 2;
-  // The piet frame. The frame contains Action messages through an
-  // extension of search.now.ui.piet.Action.
-  components.feed.core.proto.ui.piet.Frame frame = 3;
-  bool can_swipe = 4;
-  bool can_tap = 5;
-  // And other action availability...
-
-  // True if the card is stale. Stale cards shouldn't be shown unless the user
-  // has already seen it on the same surface.
-  bool is_stale = 6;
+// A horizontal slice of UI to be presented in the vertical-scrolling feed.
+message Slice {
+  oneof SliceData { XSurfaceSlice xsurface_slice = 1; }
 }
 
-// Wraps a piet shared state with a unique ID.
+message XSurfaceSlice {
+  bytes xsurface_frame = 1;
+}
+
+// Wraps an XSurface shared state with a unique ID.
 message SharedState {
   string id = 1;
-  components.feed.core.proto.ui.piet.PietSharedState piet_shared_state = 2;
-}
-
-// Piet attaches actions to many things: entities, frames, text, etc...,
-// and uses one Action for each user-action: tap, swipe, long-press, etc...
-// We can swap out PietFeedActionPayload for the 'Action' message below
-// before sending the protos over to java.
-// See ui_extension.proto. This message extends ui.piet.Action.
-message Action {
-  // Always sent to C++ when action is triggered.
-  string action_id = 1;
-
-  // The following fields are only necessary for a subset of action types that
-  // have an effect on the feed UI.
-  oneof extra_data {
-    // Present if this is a context menu action.
-    ContextMenu context_menu = 2;
-    // Present if this is a tooltip action.
-    TooltipData tooltip = 3;
-  }
-  // Present if the action can be undone.
-  UndoAction undo = 4;
-  // If not empty, this is the card dismissed by this action.
-  // Used when undo is present, so that the UI can hide the dismissed card
-  // before the dismiss is committed.
-  string dismiss_card_id = 5;
-}
-
-message ContextMenuEntry {
-  string label = 1;
-  int32 id = 2;
-}
-
-message ContextMenu {
-  repeated ContextMenuEntry entries = 1;
-}
-
-// These messages didn't change from now.ui.action.
-
-message UndoAction {
-  // The string shown to the user that confirms the action was just taken.
-  string confirmation_label = 1;
-  // The string that labels that option to reverse the action. Defaults to
-  // "Undo" if not set.
-  string undo_label = 2;
-}
-
-message TooltipData {
-  message Insets {
-    int32 top = 1;
-    int32 bottom = 2;
-  }
-  enum FeatureName {
-    // No tooltip will render if the FeatureName is UNKNOWN.
-    UNKNOWN = 0;
-    CARD_MENU = 1;
-  }
-  string label = 1;
-  string accessibility_label = 2;
-  FeatureName feature_name = 3;
-  // The information for where to offset the arrow from the referenced view.
-  Insets insets = 4;
+  bytes xsurface_shared_state = 2;
 }
 
 // An event on the UI.
@@ -149,7 +79,7 @@
     INFINITE_FEED = 5;
   }
   // For CARD_* type events.
-  string card_id = 1;
+  string chunk_id = 1;
   // For MORE_BUTTON_* type events.
   int32 more_button_position = 2;
   // For SPINNER_* type events.
diff --git a/components/feed/core/v2/BUILD.gn b/components/feed/core/v2/BUILD.gn
index 09eee44..79e2872f 100644
--- a/components/feed/core/v2/BUILD.gn
+++ b/components/feed/core/v2/BUILD.gn
@@ -19,6 +19,14 @@
     "feed_stream_api.h",
     "feed_stream_background.cc",
     "feed_stream_background.h",
+    "proto_util.cc",
+    "proto_util.h",
+    "stream_model.cc",
+    "stream_model.h",
+    "stream_model/ephemeral_change.cc",
+    "stream_model/ephemeral_change.h",
+    "stream_model/feature_tree.cc",
+    "stream_model/feature_tree.h",
   ]
   deps = [
     "//components/feed/core:feed_core",
@@ -46,6 +54,7 @@
   sources = [
     "feed_network_impl_unittest.cc",
     "feed_stream_unittest.cc",
+    "stream_model_unittest.cc",
   ]
 
   deps = [
diff --git a/components/feed/core/v2/proto_util.cc b/components/feed/core/v2/proto_util.cc
new file mode 100644
index 0000000..56b2917
--- /dev/null
+++ b/components/feed/core/v2/proto_util.cc
@@ -0,0 +1,23 @@
+// Copyright 2020 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/feed/core/v2/proto_util.h"
+#include <tuple>
+
+namespace feed {
+
+bool Equal(const feedwire::ContentId& a, const feedwire::ContentId& b) {
+  return a.content_domain() == b.content_domain() && a.id() == b.id() &&
+         a.table() == b.table();
+}
+
+bool CompareContentId(const feedwire::ContentId& a,
+                      const feedwire::ContentId& b) {
+  const int a_id = a.id();  // tie() needs l-values
+  const int b_id = b.id();
+  return std::tie(a.content_domain(), a_id, a.table()) <
+         std::tie(b.content_domain(), b_id, b.table());
+}
+
+}  // namespace feed
diff --git a/components/feed/core/v2/proto_util.h b/components/feed/core/v2/proto_util.h
new file mode 100644
index 0000000..14b239f
--- /dev/null
+++ b/components/feed/core/v2/proto_util.h
@@ -0,0 +1,28 @@
+// Copyright 2020 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_FEED_CORE_V2_PROTO_UTIL_H_
+#define COMPONENTS_FEED_CORE_V2_PROTO_UTIL_H_
+
+#include "components/feed/core/proto/wire/content_id.pb.h"
+
+// Helper functions/classes for dealing with feed proto messages.
+
+namespace feed {
+
+bool Equal(const feedwire::ContentId& a, const feedwire::ContentId& b);
+bool CompareContentId(const feedwire::ContentId& a,
+                      const feedwire::ContentId& b);
+
+class ContentIdCompareFunctor {
+ public:
+  bool operator()(const feedwire::ContentId& a,
+                  const feedwire::ContentId& b) const {
+    return CompareContentId(a, b);
+  }
+};
+
+}  // namespace feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_PROTO_UTIL_H_
diff --git a/components/feed/core/v2/stream_model.cc b/components/feed/core/v2/stream_model.cc
new file mode 100644
index 0000000..357c3189
--- /dev/null
+++ b/components/feed/core/v2/stream_model.cc
@@ -0,0 +1,101 @@
+// Copyright 2020 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/feed/core/v2/stream_model.h"
+
+#include <algorithm>
+#include <utility>
+#include "base/logging.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/proto/wire/content_id.pb.h"
+
+namespace feed {
+
+StreamModel::UiUpdate::UiUpdate() = default;
+StreamModel::UiUpdate::~UiUpdate() = default;
+StreamModel::UiUpdate::UiUpdate(const UiUpdate&) = default;
+StreamModel::UiUpdate& StreamModel::UiUpdate::operator=(const UiUpdate&) =
+    default;
+
+StreamModel::StreamModel(Observer* observer) : observer_(observer) {}
+StreamModel::~StreamModel() = default;
+
+const feedstore::Content* StreamModel::FindContent(
+    ContentRevision revision) const {
+  return GetFinalFeatureTree()->FindContent(revision);
+}
+
+StreamModel::EphemeralChangeId StreamModel::CreateEphemeralChange(
+    std::vector<feedstore::DataOperation> operations) {
+  const EphemeralChangeId id =
+      ephemeral_changes_.AddEphemeralChange(std::move(operations))->id();
+
+  UpdateFlattenedTree();
+
+  return id;
+}
+
+void StreamModel::ExecuteOperations(
+    std::vector<feedstore::DataOperation> operations) {
+  for (feedstore::DataOperation& operation : operations) {
+    if (operation.has_structure()) {
+      base_feature_tree_.ApplyStreamStructure(operation.structure());
+    }
+    if (operation.has_content()) {
+      base_feature_tree_.AddContent(std::move(*operation.mutable_content()));
+    }
+  }
+  UpdateFlattenedTree();
+}
+
+bool StreamModel::CommitEphemeralChange(EphemeralChangeId id) {
+  std::unique_ptr<stream_model::EphemeralChange> change =
+      ephemeral_changes_.Remove(id);
+  if (!change)
+    return false;
+
+  // Note: it's possible that the does change even upon commit because it
+  // may change the order that operations are applied. ExecuteOperations
+  // will ensure observers are updated.
+  ExecuteOperations(change->GetOperations());
+  return true;
+}
+
+bool StreamModel::RejectEphemeralChange(EphemeralChangeId id) {
+  if (ephemeral_changes_.Remove(id)) {
+    UpdateFlattenedTree();
+    return true;
+  }
+  return false;
+}
+
+void StreamModel::UpdateFlattenedTree() {
+  if (ephemeral_changes_.GetChangeList().empty()) {
+    feature_tree_after_changes_.reset();
+  } else {
+    feature_tree_after_changes_ =
+        ApplyEphemeralChanges(base_feature_tree_, ephemeral_changes_);
+  }
+  std::vector<ContentRevision> new_state =
+      GetFinalFeatureTree()->GetVisibleContent();
+
+  UiUpdate update;
+  update.content_list_changed = content_list_ != new_state;
+
+  content_list_ = std::move(new_state);
+
+  observer_->OnUiUpdate(update);
+}
+
+stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() {
+  return feature_tree_after_changes_ ? feature_tree_after_changes_.get()
+                                     : &base_feature_tree_;
+}
+const stream_model::FeatureTree* StreamModel::GetFinalFeatureTree() const {
+  return const_cast<StreamModel*>(this)->GetFinalFeatureTree();
+}
+
+}  // namespace feed
diff --git a/components/feed/core/v2/stream_model.h b/components/feed/core/v2/stream_model.h
new file mode 100644
index 0000000..fc509b2
--- /dev/null
+++ b/components/feed/core/v2/stream_model.h
@@ -0,0 +1,104 @@
+// Copyright 2020 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_FEED_CORE_V2_STREAM_MODEL_H_
+#define COMPONENTS_FEED_CORE_V2_STREAM_MODEL_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/proto/wire/content_id.pb.h"
+#include "components/feed/core/v2/proto_util.h"
+#include "components/feed/core/v2/stream_model/ephemeral_change.h"
+#include "components/feed/core/v2/stream_model/feature_tree.h"
+
+namespace feedwire {
+class DataOperation;
+}  // namespace feedwire
+
+namespace feed {
+
+// An in-memory stream model.
+class StreamModel {
+ public:
+  using ContentId = feedwire::ContentId;
+  using ContentRevision = stream_model::ContentRevision;
+  using EphemeralChangeId = stream_model::EphemeralChangeId;
+
+  // Information about an update to the model.
+  struct UiUpdate {
+    struct SharedStateInfo {
+      // The shared state's unique ID.
+      std::string shared_state_id;
+      // Whether the shared state was just modified or added.
+      bool updated = false;
+    };
+    UiUpdate();
+    ~UiUpdate();
+    UiUpdate(const UiUpdate&);
+    UiUpdate& operator=(const UiUpdate&);
+    // Whether the list of content has changed. Use
+    // |StreamModel::GetContentList()| to get the updated list of content.
+    bool content_list_changed = false;
+    // The list of shared states in the model.
+    std::vector<SharedStateInfo> shared_states;
+  };
+
+  class Observer {
+   public:
+    // Called when the UI model changes.
+    virtual void OnUiUpdate(const UiUpdate&) = 0;
+  };
+
+  explicit StreamModel(Observer* observer);
+  ~StreamModel();
+
+  StreamModel(const StreamModel& src) = delete;
+  StreamModel& operator=(const StreamModel&) = delete;
+
+  // Data access.
+
+  // Returns the full list of content in the order it should be presented.
+  const std::vector<ContentRevision>& GetContentList() const {
+    return content_list_;
+  }
+  // Returns the content identified by |ContentRevision|.
+  const feedstore::Content* FindContent(ContentRevision revision) const;
+  // Apply |operations| to the model.
+  void ExecuteOperations(std::vector<feedstore::DataOperation> operations);
+
+  // Create a temporary change that may be undone or committed later.
+  EphemeralChangeId CreateEphemeralChange(
+      std::vector<feedstore::DataOperation> operations);
+  // Commits a change. Returns false if the change does not exist.
+  bool CommitEphemeralChange(EphemeralChangeId id);
+  // Rejects a change. Returns false if the change does not exist.
+  bool RejectEphemeralChange(EphemeralChangeId id);
+
+ private:
+  // The final feature tree after applying any ephemeral changes.
+  // May link directly to |base_feature_tree_|.
+  stream_model::FeatureTree* GetFinalFeatureTree();
+  const stream_model::FeatureTree* GetFinalFeatureTree() const;
+
+  void UpdateFlattenedTree();
+
+  Observer* observer_;  // Unowned.
+
+  stream_model::ContentIdMap id_map_;
+  stream_model::FeatureTree base_feature_tree_{&id_map_};
+  // |base_feature_tree_| with |ephemeral_changes_| applied.
+  // Null if there are no ephemeral changes.
+  std::unique_ptr<stream_model::FeatureTree> feature_tree_after_changes_;
+  stream_model::EphemeralChangeList ephemeral_changes_;
+
+  // Current state of the flattened tree.
+  // Updated after each tree change.
+  std::vector<ContentRevision> content_list_;
+};
+
+}  // namespace feed
+#endif  // COMPONENTS_FEED_CORE_V2_STREAM_MODEL_H_
diff --git a/components/feed/core/v2/stream_model/README.md b/components/feed/core/v2/stream_model/README.md
new file mode 100644
index 0000000..4f3f75b
--- /dev/null
+++ b/components/feed/core/v2/stream_model/README.md
@@ -0,0 +1 @@
+This directory contains implementation details for StreamModel.
diff --git a/components/feed/core/v2/stream_model/ephemeral_change.cc b/components/feed/core/v2/stream_model/ephemeral_change.cc
new file mode 100644
index 0000000..96e8a65
--- /dev/null
+++ b/components/feed/core/v2/stream_model/ephemeral_change.cc
@@ -0,0 +1,64 @@
+// Copyright 2020 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/feed/core/v2/stream_model/ephemeral_change.h"
+
+namespace feed {
+namespace stream_model {
+
+EphemeralChange::EphemeralChange(
+    EphemeralChangeId id,
+    std::vector<feedstore::DataOperation> operations)
+    : id_(id), operations_(std::move(operations)) {}
+EphemeralChange::~EphemeralChange() = default;
+
+EphemeralChangeList::EphemeralChangeList() = default;
+EphemeralChangeList::~EphemeralChangeList() = default;
+EphemeralChange* EphemeralChangeList::AddEphemeralChange(
+    std::vector<feedstore::DataOperation> operations) {
+  change_list_.push_back(std::make_unique<EphemeralChange>(
+      id_generator_.GenerateNextId(), operations));
+  return change_list_.back().get();
+}
+EphemeralChange* EphemeralChangeList::Find(EphemeralChangeId id) {
+  for (std::unique_ptr<EphemeralChange>& change : change_list_) {
+    if (change->id() == id)
+      return change.get();
+  }
+  return nullptr;
+}
+
+std::unique_ptr<FeatureTree> ApplyEphemeralChanges(
+    const FeatureTree& tree,
+    const EphemeralChangeList& changes) {
+  auto tree_with_changes = std::make_unique<FeatureTree>(&tree);
+
+  for (const std::unique_ptr<EphemeralChange>& change :
+       changes.GetChangeList()) {
+    for (const feedstore::DataOperation& operation : change->GetOperations()) {
+      if (operation.has_structure()) {
+        tree_with_changes->ApplyStreamStructure(operation.structure());
+      }
+      if (operation.has_content()) {
+        tree_with_changes->AddContent(operation.content());
+      }
+    }
+  }
+  return tree_with_changes;
+}
+
+std::unique_ptr<EphemeralChange> EphemeralChangeList::Remove(
+    EphemeralChangeId id) {
+  for (size_t i = 0; i < change_list_.size(); ++i) {
+    if (change_list_[i]->id() == id) {
+      std::unique_ptr<EphemeralChange> result = std::move(change_list_[i]);
+      change_list_.erase(change_list_.begin() + i);
+      return result;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace stream_model
+}  // namespace feed
diff --git a/components/feed/core/v2/stream_model/ephemeral_change.h b/components/feed/core/v2/stream_model/ephemeral_change.h
new file mode 100644
index 0000000..90711d1
--- /dev/null
+++ b/components/feed/core/v2/stream_model/ephemeral_change.h
@@ -0,0 +1,67 @@
+// Copyright 2020 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_FEED_CORE_V2_STREAM_MODEL_EPHEMERAL_CHANGE_H_
+#define COMPONENTS_FEED_CORE_V2_STREAM_MODEL_EPHEMERAL_CHANGE_H_
+
+#include <memory>
+#include <vector>
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/v2/stream_model/feature_tree.h"
+
+namespace feed {
+namespace stream_model {
+
+using EphemeralChangeId = util::IdTypeU32<class EphemeralChangeIdClass>;
+
+// A sequence of data operations that may be reverted.
+class EphemeralChange {
+ public:
+  EphemeralChange(EphemeralChangeId id,
+                  std::vector<feedstore::DataOperation> operations);
+  ~EphemeralChange();
+  EphemeralChange(const EphemeralChange&) = delete;
+  EphemeralChange& operator=(const EphemeralChange&) = delete;
+
+  EphemeralChangeId id() const { return id_; }
+  const std::vector<feedstore::DataOperation>& GetOperations() const {
+    return operations_;
+  }
+  std::vector<feedstore::DataOperation>& GetOperations() { return operations_; }
+
+ private:
+  EphemeralChangeId id_;
+  std::vector<feedstore::DataOperation> operations_;
+};
+
+// A list of |EphemeralChange| objects.
+class EphemeralChangeList {
+ public:
+  EphemeralChangeList();
+  ~EphemeralChangeList();
+  EphemeralChangeList(const EphemeralChangeList&) = delete;
+  EphemeralChangeList& operator=(const EphemeralChangeList&) = delete;
+
+  const std::vector<std::unique_ptr<EphemeralChange>>& GetChangeList() const {
+    return change_list_;
+  }
+  EphemeralChange* Find(EphemeralChangeId id);
+  EphemeralChange* AddEphemeralChange(
+      std::vector<feedstore::DataOperation> operations);
+  std::unique_ptr<EphemeralChange> Remove(EphemeralChangeId id);
+
+ private:
+  EphemeralChangeId::Generator id_generator_;
+  std::vector<std::unique_ptr<EphemeralChange>> change_list_;
+};
+
+// Return a new |FeatureTree| by applying |changes| to |tree|.
+std::unique_ptr<FeatureTree> ApplyEphemeralChanges(
+    const FeatureTree& tree,
+    const EphemeralChangeList& changes);
+
+}  // namespace stream_model
+}  // namespace feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_STREAM_MODEL_EPHEMERAL_CHANGE_H_
diff --git a/components/feed/core/v2/stream_model/feature_tree.cc b/components/feed/core/v2/stream_model/feature_tree.cc
new file mode 100644
index 0000000..ad1701c1
--- /dev/null
+++ b/components/feed/core/v2/stream_model/feature_tree.cc
@@ -0,0 +1,188 @@
+// Copyright 2020 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/feed/core/v2/stream_model/feature_tree.h"
+#include "base/logging.h"
+
+namespace feed {
+namespace stream_model {
+
+ContentIdMap::ContentIdMap() = default;
+ContentIdMap::~ContentIdMap() = default;
+
+ContentTag ContentIdMap::GetContentTag(const feedwire::ContentId& id) {
+  auto iter = mapping_.find(id);
+  if (iter != mapping_.end())
+    return iter->second;
+  ContentTag tag = tag_generator_.GenerateNextId();
+  mapping_[id] = tag;
+  return tag;
+}
+
+ContentRevision ContentIdMap::NextContentRevision() {
+  return revision_generator_.GenerateNextId();
+}
+
+StreamNode::StreamNode() = default;
+StreamNode::~StreamNode() = default;
+StreamNode::StreamNode(const StreamNode&) = default;
+StreamNode& StreamNode::operator=(const StreamNode&) = default;
+
+FeatureTree::FeatureTree(ContentIdMap* id_map) : id_map_(id_map) {}
+
+FeatureTree::FeatureTree(const FeatureTree* base)
+    : base_(base),
+      id_map_(base->id_map_),
+      computed_root_(base->computed_root_),
+      root_tag_(base->root_tag_),
+      nodes_(base->nodes_) {}
+FeatureTree::~FeatureTree() = default;
+
+StreamNode* FeatureTree::GetOrMakeNode(ContentTag id) {
+  ResizeNodesIfNeeded(id);
+  return &nodes_[id.value()];
+}
+
+const StreamNode* FeatureTree::FindNode(ContentTag id) const {
+  return const_cast<FeatureTree*>(this)->FindNode(id);
+}
+
+StreamNode* FeatureTree::FindNode(ContentTag id) {
+  if (!id.is_null() && nodes_.size() > id.value())
+    return &nodes_[id.value()];
+  return nullptr;
+}
+
+const feedstore::Content* FeatureTree::FindContent(ContentRevision id) const {
+  auto iter = content_.find(id);
+  if (iter != content_.end())
+    return &iter->second;
+  return base_ ? base_->FindContent(id) : nullptr;
+}
+
+void FeatureTree::ApplyStreamStructure(
+    const feedstore::StreamStructure& structure) {
+  switch (structure.operation()) {
+    case feedstore::StreamStructure::CLEAR_ALL:
+      nodes_.clear();
+      content_.clear();
+      computed_root_ = false;
+      break;
+    case feedstore::StreamStructure::UPDATE_OR_APPEND: {
+      const ContentTag child_id = GetContentTag(structure.content_id());
+      const bool is_stream =
+          structure.type() == feedstore::StreamStructure::STREAM;
+      ContentTag parent_id;
+      if (structure.has_parent_id()) {
+        parent_id = GetContentTag(structure.parent_id());
+      }
+      ResizeNodesIfNeeded(std::max(child_id, parent_id));
+      StreamNode& child = nodes_[child_id.value()];
+      StreamNode* parent = FindNode(parent_id);
+
+      // If a node already has a parent, treat this as an update, not an append
+      // operation.
+      child.is_stream = is_stream;
+      child.tombstoned = false;
+      if (root_tag_ == child_id) {
+        computed_root_ = false;
+      }
+
+      if (parent && !child.has_parent) {
+        // The child doesn't yet have a parent, but it should. Link to the
+        // parent now. If the child already has a parent, we will never change
+        // the parent even if requested by UPDATE_OR_APPEND.
+        child.has_parent = true;
+        child.previous_sibling = parent->last_child;
+        parent->last_child = child_id;
+      } else if (!parent && is_stream) {
+        // The node meets the criteria for root.
+        computed_root_ = true;
+        root_tag_ = child_id;
+      }
+    } break;
+    case feedstore::StreamStructure::REMOVE: {
+      // Removal is just unlinking the node from the tree.
+      // If it's added back again later, it retains its old children.
+      ContentTag tag = GetContentTag(structure.content_id());
+      if (root_tag_ == tag) {
+        computed_root_ = false;
+      }
+      GetOrMakeNode(tag)->tombstoned = true;
+    } break;
+    default:
+      break;
+  }
+}  // namespace stream_model
+
+void FeatureTree::ResizeNodesIfNeeded(ContentTag id) {
+  if (nodes_.size() <= id.value())
+    nodes_.resize(id.value() + 1);
+}
+
+void FeatureTree::AddContent(feedstore::Content content) {
+  AddContent(id_map_->NextContentRevision(), std::move(content));
+}
+
+void FeatureTree::AddContent(ContentRevision revision_id,
+                             feedstore::Content content) {
+  // TODO(harringtond): Consider de-duping content.
+  // Currently, we copy content for ephemeral changes. Both when the ephemeral
+  // change is created, and when it is committed. We should consider eliminating
+  // these copies.
+  const ContentTag tag = GetContentTag(content.content_id());
+  DCHECK(!content_.count(revision_id));
+
+  GetOrMakeNode(tag)->content_revision = revision_id;
+  content_[revision_id] = std::move(content);
+}
+
+void FeatureTree::ResolveRoot() {
+  if (computed_root_) {
+    DCHECK(!FindNode(root_tag_) || FindNode(root_tag_)->is_stream) << root_tag_;
+    DCHECK(!FindNode(root_tag_) || !FindNode(root_tag_)->tombstoned);
+    DCHECK(!FindNode(root_tag_) || !FindNode(root_tag_)->has_parent);
+    return;
+  }
+  root_tag_ = ContentTag();
+  for (size_t i = 0; i < nodes_.size(); ++i) {
+    const StreamNode& node = nodes_[i];
+    if (node.is_stream && !node.tombstoned && !node.has_parent) {
+      root_tag_ = ContentTag(i);
+    }
+  }
+  computed_root_ = true;
+}
+
+std::vector<ContentRevision> FeatureTree::GetVisibleContent() {
+  ResolveRoot();
+  std::vector<ContentRevision> result;
+  std::vector<ContentTag> stack;
+
+  // Node: Cycles are impossible here. The root node is guaranteed to
+  // not be a child. All other nodes have exactly one parent.
+  // It is possible for nodes to cycle, like A->B->A, but in this case there can
+  // be no valid root because all nodes have a parent.
+  stack.push_back(root_tag_);
+  while (!stack.empty()) {
+    const ContentTag tag = stack.back();
+    stack.pop_back();
+    const StreamNode* node = FindNode(tag);
+    if (!node || node->tombstoned)
+      continue;
+    if (!node->last_child.is_null()) {
+      for (ContentTag child_id = node->last_child; !child_id.is_null();
+           child_id = nodes_[child_id.value()].previous_sibling) {
+        stack.push_back(child_id);
+      }
+    }
+    if (!node->content_revision.is_null()) {
+      result.push_back(node->content_revision);
+    }
+  }
+  return result;
+}
+
+}  // namespace stream_model
+}  // namespace feed
diff --git a/components/feed/core/v2/stream_model/feature_tree.h b/components/feed/core/v2/stream_model/feature_tree.h
new file mode 100644
index 0000000..1bd3e214
--- /dev/null
+++ b/components/feed/core/v2/stream_model/feature_tree.h
@@ -0,0 +1,133 @@
+// Copyright 2020 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_FEED_CORE_V2_STREAM_MODEL_FEATURE_TREE_H_
+#define COMPONENTS_FEED_CORE_V2_STREAM_MODEL_FEATURE_TREE_H_
+
+#include <map>
+#include <vector>
+#include "base/util/type_safety/id_type.h"
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/v2/proto_util.h"
+
+namespace feed {
+namespace stream_model {
+
+// Uniquely identifies a feedwire::ContentId. Provided by |ContentIdMap|.
+using ContentTag = util::IdTypeU32<class ContentTagClass>;
+// Uniquely identifies a revision of a |feedstore::Content|. If Content changes,
+// it is assigned a new revision number.
+using ContentRevision = util::IdTypeU32<class ContentRevisionClass>;
+
+// Maps ContentId into ContentTag, and generates ContentRevision IDs.
+class ContentIdMap {
+ public:
+  ContentIdMap();
+  ~ContentIdMap();
+  ContentIdMap(const ContentIdMap&) = delete;
+  ContentIdMap& operator=(const ContentIdMap&) = delete;
+
+  ContentTag GetContentTag(const feedwire::ContentId& id);
+  ContentRevision NextContentRevision();
+
+ private:
+  ContentTag::Generator tag_generator_;
+  ContentRevision::Generator revision_generator_;
+  std::map<feedwire::ContentId, ContentTag, ContentIdCompareFunctor> mapping_;
+};
+
+// A node in FeatureTree.
+struct StreamNode {
+  StreamNode();
+  ~StreamNode();
+  StreamNode(const StreamNode&);
+  StreamNode& operator=(const StreamNode&);
+  // If true, this nodes has been removed and should be ignored.
+  bool tombstoned = false;
+  // Whether this is a STREAM node.
+  bool is_stream = false;
+  // Whether this node has a parent.
+  bool has_parent = false;
+  // If this node has content, this identifies it.
+  ContentRevision content_revision;
+  // Child relations are stored in linked-list fashion.
+  // The ID of the last child, or null.
+  ContentTag last_child;
+  // The ID of the sibling before this one.
+  ContentTag previous_sibling;
+};
+
+// The feature tree which underlies StreamModel.
+// This tree is different that most, the rules are as follows:
+// * A node may or may not have a parent, so this is more of a forest than a
+//   tree.
+// * When nodes are removed, their set of children are remembered. If the node
+//   is added again, it retains its old children.
+// * A node can be added multiple times, but subsequent adds will not change
+//   the node's parent.
+// * There is only one 'stream root' acknowledged, even though there can be many
+//   roots. The stream root is the last root node added of type STREAM. The
+//   stream root identifies the tree whose nodes are used to compute
+//   |GetVisibleContent()|.
+// * A tree can be constructed with a base tree. This copies features from base,
+//   but refers to content stored in base by reference.
+class FeatureTree {
+ public:
+  // Constructor. |id_map| is retained by FeatureTree, and must have a greater
+  // scope than FeatureTree.
+  explicit FeatureTree(ContentIdMap* id_map);
+  // Create a |FeatureTree| which starts as a copy of |base|.
+  // Copies structure from |base|, and keeps a reference for content access.
+  explicit FeatureTree(const FeatureTree* base);
+  ~FeatureTree();
+
+  FeatureTree(const FeatureTree& src) = delete;
+  FeatureTree& operator=(const FeatureTree& src) = delete;
+
+  // Mutations.
+
+  void ApplyStreamStructure(const feedstore::StreamStructure& structure);
+  void AddContent(feedstore::Content content);
+  void AddContent(ContentRevision revision_id, feedstore::Content content);
+
+  // Data access.
+
+  const StreamNode* FindNode(ContentTag id) const;
+  StreamNode* FindNode(ContentTag id);
+  const feedstore::Content* FindContent(ContentRevision id) const;
+  ContentTag GetContentTag(const feedwire::ContentId& id) {
+    return id_map_->GetContentTag(id);
+  }
+
+  // Returns the list of content that should be visible.
+  std::vector<ContentRevision> GetVisibleContent();
+
+ private:
+  StreamNode* GetOrMakeNode(ContentTag id);
+  void ResolveRoot();
+  void ResizeNodesIfNeeded(ContentTag id);
+  void RemoveFromParent(ContentTag node_id);
+  bool RemoveFromParent(StreamNode* parent, ContentTag node_id);
+
+  const FeatureTree* base_ = nullptr;  // Unowned.
+  ContentIdMap* id_map_;               // Unowned.
+  // Finding the root:
+  // We pick the root node as the last STREAM node which has no parent.
+  // In most cases, we can identify the root as the tree is built.
+  // But in some cases, we need to search all nodes to find the root.
+  // |computed_root_| is true if |root_tag_| is guaranteed to identify the root.
+  bool computed_root_ = true;
+  ContentTag root_tag_;
+  // All nodes in the forest, included removed nodes.
+  // This datastructure was selected to make copies efficient.
+  std::vector<StreamNode> nodes_;
+  // TODO(harringtond): It may be possible to remove old revisions of content
+  // to save memory.
+  std::map<ContentRevision, feedstore::Content> content_;
+};
+
+}  // namespace stream_model
+}  // namespace feed
+
+#endif  // COMPONENTS_FEED_CORE_V2_STREAM_MODEL_FEATURE_TREE_H_
diff --git a/components/feed/core/v2/stream_model_unittest.cc b/components/feed/core/v2/stream_model_unittest.cc
new file mode 100644
index 0000000..ffabf3c
--- /dev/null
+++ b/components/feed/core/v2/stream_model_unittest.cc
@@ -0,0 +1,417 @@
+// Copyright 2020 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/feed/core/v2/stream_model.h"
+
+#include "base/optional.h"
+#include "components/feed/core/proto/v2/store.pb.h"
+#include "components/feed/core/proto/wire/content_id.pb.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace feed {
+namespace {
+using feedwire::ContentId;
+using EphemeralChangeId = StreamModel::EphemeralChangeId;
+using ContentRevision = StreamModel::ContentRevision;
+
+ContentId MakeContentId(const std::string& domain, const std::string& table) {
+  ContentId id;
+  id.set_content_domain(domain);
+  id.set_table(table);
+  return id;
+}
+
+ContentId MakeClusterId(const std::string& name) {
+  return MakeContentId("cluster", name);
+}
+
+ContentId MakeContentContentId(const std::string& name) {
+  return MakeContentId("content", name);
+}
+ContentId MakeRootId(const std::string& name = "root") {
+  return MakeContentId("stream", name);
+}
+
+feedstore::StreamStructure MakeStream(const std::string& name = "root") {
+  feedstore::StreamStructure result;
+  result.set_type(feedstore::StreamStructure::STREAM);
+  result.set_operation(feedstore::StreamStructure::UPDATE_OR_APPEND);
+  *result.mutable_content_id() = MakeRootId(name);
+  return result;
+}
+
+feedstore::StreamStructure MakeCluster(const std::string& name,
+                                       ContentId parent) {
+  feedstore::StreamStructure result;
+  result.set_type(feedstore::StreamStructure::CLUSTER);
+  result.set_operation(feedstore::StreamStructure::UPDATE_OR_APPEND);
+  *result.mutable_content_id() = MakeClusterId(name);
+  *result.mutable_parent_id() = parent;
+  return result;
+}
+
+feedstore::StreamStructure MakeContentNode(const std::string& name,
+                                           ContentId parent) {
+  feedstore::StreamStructure result;
+  result.set_type(feedstore::StreamStructure::CONTENT);
+  result.set_operation(feedstore::StreamStructure::UPDATE_OR_APPEND);
+  *result.mutable_content_id() = MakeContentContentId(name);
+  *result.mutable_parent_id() = parent;
+  return result;
+}
+
+feedstore::StreamStructure MakeRemove(ContentId id) {
+  feedstore::StreamStructure result;
+  result.set_operation(feedstore::StreamStructure::REMOVE);
+  *result.mutable_content_id() = id;
+  return result;
+}
+
+feedstore::Content MakeContent(const std::string& name) {
+  feedstore::Content result;
+  *result.mutable_content_id() = MakeContentContentId(name);
+  result.set_frame("f:" + name);
+  return result;
+}
+
+feedstore::DataOperation MakeOperation(feedstore::StreamStructure structure) {
+  feedstore::DataOperation operation;
+  *operation.mutable_structure() = std::move(structure);
+  return operation;
+}
+
+feedstore::DataOperation MakeOperation(feedstore::Content content) {
+  feedstore::DataOperation operation;
+  *operation.mutable_content() = std::move(content);
+  return operation;
+}
+
+std::vector<std::string> GetContentFrames(const StreamModel& model) {
+  std::vector<std::string> frames;
+  for (ContentRevision rev : model.GetContentList()) {
+    const feedstore::Content* content = model.FindContent(rev);
+    if (content) {
+      frames.push_back(content->frame());
+    } else {
+      frames.push_back("<null>");
+    }
+  }
+  return frames;
+}
+
+std::vector<feedstore::DataOperation> MakeTypicalStreamOperations() {
+  return {
+      MakeOperation(MakeStream()),
+      MakeOperation(MakeCluster("A", MakeRootId())),
+      MakeOperation(MakeContentNode("A", MakeClusterId("A"))),
+      MakeOperation(MakeContent("A")),
+      MakeOperation(MakeCluster("B", MakeRootId())),
+      MakeOperation(MakeContentNode("B", MakeClusterId("B"))),
+      MakeOperation(MakeContent("B")),
+  };
+}
+
+class TestObserver : public StreamModel::Observer {
+ public:
+  void OnUiUpdate(const StreamModel::UiUpdate& update) override {
+    update_ = update;
+  }
+
+  const base::Optional<StreamModel::UiUpdate>& GetUiUpdate() const {
+    return update_;
+  }
+  bool ContentListChanged() const {
+    return update_ && update_->content_list_changed;
+  }
+  void Clear() { update_ = base::nullopt; }
+
+ private:
+  base::Optional<StreamModel::UiUpdate> update_;
+};
+
+TEST(StreamModelTest, ConstructEmptyModel) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  EXPECT_EQ(0UL, model.GetContentList().size());
+}
+
+// Typical stream (Stream -> Cluster -> Content).
+TEST(StreamModelTest, AddStreamClusterContent) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  model.ExecuteOperations(MakeTypicalStreamOperations());
+
+  EXPECT_TRUE(observer.ContentListChanged());
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, AddContentWithoutRoot) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations{
+      MakeOperation(MakeCluster("A", MakeRootId())),
+      MakeOperation(MakeContentNode("A", MakeClusterId("A"))),
+      MakeOperation(MakeContent("A")),
+  };
+  model.ExecuteOperations(operations);
+
+  // Without a root, no content is visible.
+  EXPECT_EQ(std::vector<std::string>({}), GetContentFrames(model));
+}
+
+// Verify Stream -> Content works.
+TEST(StreamModelTest, AddStreamContent) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations{
+      MakeOperation(MakeStream()),
+      MakeOperation(MakeContentNode("A", MakeRootId())),
+      MakeOperation(MakeContent("A")),
+  };
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({"f:A"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, AddRootAsChild) {
+  // When the root is added as a child, it's no longer considered a root.
+  TestObserver observer;
+  StreamModel model(&observer);
+  feedstore::StreamStructure stream_with_parent = MakeStream();
+  *stream_with_parent.mutable_parent_id() = MakeContentContentId("A");
+  std::vector<feedstore::DataOperation> operations{
+      MakeOperation(MakeStream()),
+      MakeOperation(MakeContentNode("A", MakeRootId())),
+      MakeOperation(MakeContent("A")),
+      MakeOperation(stream_with_parent),
+  };
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({}), GetContentFrames(model));
+}
+
+// Changing the STREAM root to CLUSTER means it is no longer eligible to be
+// the root.
+TEST(StreamModelTest, ChangeStreamToCluster) {
+  TestObserver observer;
+  StreamModel model(&observer);
+  feedstore::StreamStructure stream_as_cluster = MakeStream();
+  stream_as_cluster.set_type(feedstore::StreamStructure::CLUSTER);
+
+  std::vector<feedstore::DataOperation> operations{
+      MakeOperation(MakeStream()),
+      MakeOperation(MakeContentNode("A", MakeRootId())),
+      MakeOperation(MakeContent("A")),
+      MakeOperation(stream_as_cluster),
+  };
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RemoveCluster) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeRemove(MakeClusterId("A"))));
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({"f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RemoveContent) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeRemove(MakeContentContentId("A"))));
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({"f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RemoveRoot) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeRemove(MakeRootId())));
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>(), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RemoveAndAddRoot) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeRemove(MakeRootId())));
+  operations.push_back(MakeOperation(MakeStream()));
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, SwitchStreams) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeStream("root2")));
+  operations.push_back(
+      MakeOperation(MakeContentNode("F", MakeRootId("root2"))));
+  operations.push_back(MakeOperation(MakeContent("F")));
+
+  model.ExecuteOperations(operations);
+
+  // The last stream added becomes the root, so only children of 'root2' are
+  // included.
+  EXPECT_EQ(std::vector<std::string>({"f:F"}), GetContentFrames(model));
+
+  // Adding the original stream back will re-activate it.
+  model.ExecuteOperations({MakeOperation(MakeStream())});
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B"}), GetContentFrames(model));
+
+  // Removing 'root' will now make 'root2' active again.
+  model.ExecuteOperations({MakeOperation(MakeRemove(MakeRootId()))});
+  EXPECT_EQ(std::vector<std::string>({"f:F"}), GetContentFrames(model));
+  LOG(ERROR) << "VS:" << sizeof(std::vector<int>);
+}
+
+TEST(StreamModelTest, RemoveAndUpdateCluster) {
+  // Remove a cluster and add it back. Adding it back keeps its original
+  // placement.
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeRemove(MakeClusterId("A"))));
+  operations.push_back(MakeOperation(MakeCluster("A", MakeRootId())));
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RemoveAndAppendToNewParent) {
+  // Attempt to re-parent a node. This is not allowed, the old parent remains.
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  std::vector<feedstore::DataOperation> operations =
+      MakeTypicalStreamOperations();
+  operations.push_back(MakeOperation(MakeRemove(MakeClusterId("A"))));
+  operations.push_back(MakeOperation(MakeCluster("A", MakeClusterId("B"))));
+
+  model.ExecuteOperations(operations);
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, EphemeralNewCluster) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  model.ExecuteOperations(MakeTypicalStreamOperations());
+  observer.Clear();
+
+  model.CreateEphemeralChange({
+      MakeOperation(MakeCluster("C", MakeRootId())),
+      MakeOperation(MakeContentNode("C", MakeClusterId("C"))),
+      MakeOperation(MakeContent("C")),
+  });
+
+  EXPECT_TRUE(observer.ContentListChanged());
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B", "f:C"}),
+            GetContentFrames(model));
+}
+
+TEST(StreamModelTest, CommitEphemeralChange) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  model.ExecuteOperations(MakeTypicalStreamOperations());
+  EphemeralChangeId change_id = model.CreateEphemeralChange({
+      MakeOperation(MakeCluster("C", MakeRootId())),
+      MakeOperation(MakeContentNode("C", MakeClusterId("C"))),
+      MakeOperation(MakeContent("C")),
+  });
+
+  EXPECT_TRUE(model.CommitEphemeralChange(change_id));
+
+  // Can't reject after commit.
+  EXPECT_FALSE(model.RejectEphemeralChange(change_id));
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B", "f:C"}),
+            GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RejectEphemeralChange) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  model.ExecuteOperations(MakeTypicalStreamOperations());
+  EphemeralChangeId change_id = model.CreateEphemeralChange({
+      MakeOperation(MakeCluster("C", MakeRootId())),
+      MakeOperation(MakeContentNode("C", MakeClusterId("C"))),
+      MakeOperation(MakeContent("C")),
+  });
+  observer.Clear();
+
+  EXPECT_TRUE(model.RejectEphemeralChange(change_id));
+  EXPECT_TRUE(observer.ContentListChanged());
+  // Can't commit after reject.
+  EXPECT_FALSE(model.CommitEphemeralChange(change_id));
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B"}), GetContentFrames(model));
+}
+
+TEST(StreamModelTest, RejectFirstEphemeralChange) {
+  TestObserver observer;
+  StreamModel model(&observer);
+
+  model.ExecuteOperations(MakeTypicalStreamOperations());
+  EphemeralChangeId change_id1 = model.CreateEphemeralChange({
+      MakeOperation(MakeCluster("C", MakeRootId())),
+      MakeOperation(MakeContentNode("C", MakeClusterId("C"))),
+      MakeOperation(MakeContent("C")),
+  });
+
+  model.CreateEphemeralChange({
+      MakeOperation(MakeCluster("D", MakeRootId())),
+      MakeOperation(MakeContentNode("D", MakeClusterId("D"))),
+      MakeOperation(MakeContent("D")),
+  });
+  observer.Clear();
+
+  EXPECT_TRUE(model.RejectEphemeralChange(change_id1));
+  EXPECT_TRUE(observer.ContentListChanged());
+  // Can't commit after reject.
+  EXPECT_FALSE(model.CommitEphemeralChange(change_id1));
+
+  EXPECT_EQ(std::vector<std::string>({"f:A", "f:B", "f:D"}),
+            GetContentFrames(model));
+}
+
+}  // namespace
+}  // namespace feed
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index b1e6c03..2992583c 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -20006,7 +20006,8 @@
       'owners': ['file://chrome/browser/safe_browsing/OWNERS'],
       'type': 'main',
       'schema': { 'type': 'boolean' },
-      'supported_on': ['chrome.*:81-'],
+      'supported_on': ['chrome.*:81-81'],
+      'deprecated': True,
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
@@ -20015,7 +20016,9 @@
       'id': 667,
       'caption': '''Enable sending downloads to Google for deep scanning for users enrolled in the Advanced Protection program''',
       'tags': [],
-      'desc': '''This policy controls whether users enrolled in the Advanced Protection program are allowed to send their downloads to Google for malware scanning. If set to True or not set, enrolled users will be be prompted to send their files to Google for deep scanning. If the user selects 'Scan', their download will be sent to Google. If set to False, users will not be prompted and their downloads will not be sent to Google.''',
+      'desc': '''This policy is deprecated, and has been replaced with AdvancedProtectionExtraSecurityAllowed.
+
+      This policy controls whether users enrolled in the Advanced Protection program are allowed to send their downloads to Google for malware scanning. If set to True or not set, enrolled users will be be prompted to send their files to Google for deep scanning. If the user selects 'Scan', their download will be sent to Google. If set to False, users will not be prompted and their downloads will not be sent to Google.''',
     },
     {
       'name': 'SystemProxySettings',
@@ -20218,6 +20221,22 @@
       'tags': [],
       'desc': '''Specifies device-wide client certificates that should be enrolled using the device management protocol.''',
     },
+    {
+      'name': 'AdvancedProtectionExtraSecurityAllowed',
+      'owners': ['file://chrome/browser/safe_browsing/OWNERS'],
+      'type': 'main',
+      'schema': { 'type': 'boolean' },
+      'supported_on': ['chrome.*:82-', 'chrome_os:82-'],
+      'features': {
+        'dynamic_refresh': True,
+        'per_profile': True,
+      },
+      'example_value': True,
+      'id': 685,
+      'caption': '''Enable additional protections for users enrolled in the Advanced Protection program''',
+      'tags': [],
+      'desc': '''This policy controls whether users enrolled in the Advanced Protection program receive extra security features. Some of these features may involve the sharing of data with Google (for example, Advanced Protection users will be able to send their downloads to Google for malware scanning). If set to True or not set, enrolled users will receive extra security features. If set to False, Advanced Protection users will receive only the consumer security features.''',
+    },
   ],
 
   'messages': {
@@ -21059,6 +21078,6 @@
   ],
   'placeholders': [],
   'deleted_policy_ids': [412, 546, 562, 569, 578],
-  'highest_id_currently_used': 684,
+  'highest_id_currently_used': 685,
   'highest_atomic_group_id_currently_used': 38
 }
diff --git a/components/safe_browsing/content/password_protection/password_protection_request.cc b/components/safe_browsing/content/password_protection/password_protection_request.cc
index d50c8f3..83371f8 100644
--- a/components/safe_browsing/content/password_protection/password_protection_request.cc
+++ b/components/safe_browsing/content/password_protection/password_protection_request.cc
@@ -232,8 +232,7 @@
   request_proto_->set_content_type(web_contents_->GetContentsMimeType());
 
 #if BUILDFLAG(FULL_SAFE_BROWSING)
-  if ((password_protection_service_->IsExtendedReporting() ||
-       password_protection_service_->IsEnhancedProtection()) &&
+  if (password_protection_service_->IsExtendedReporting() &&
       !password_protection_service_->IsIncognito()) {
     gfx::Size content_area_size =
         password_protection_service_->GetCurrentContentAreaSize();
@@ -274,8 +273,7 @@
         LogSyncAccountType(reuse_event->sync_account_type());
       }
 
-      if ((password_protection_service_->IsExtendedReporting() ||
-           password_protection_service_->IsEnhancedProtection()) &&
+      if (password_protection_service_->IsExtendedReporting() &&
           !password_protection_service_->IsIncognito()) {
         for (const auto& domain : matching_domains_) {
           reuse_event->add_domains_matching_password(domain);
@@ -378,8 +376,7 @@
   // Once the DOM features are collected, either collect visual features, or go
   // straight to sending the ping.
   if (trigger_type_ == LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE &&
-      (password_protection_service_->IsExtendedReporting() ||
-       password_protection_service_->IsEnhancedProtection()) &&
+      password_protection_service_->IsExtendedReporting() &&
       zoom::ZoomController::GetZoomLevelForWebContents(web_contents_) <=
           kMaxZoomForVisualFeatures &&
       request_proto_->content_area_width() >= kMinWidthForVisualFeatures &&
diff --git a/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc b/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc
index 6ded1e56..7f091a2 100644
--- a/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc
+++ b/components/safe_browsing/content/password_protection/password_protection_service_unittest.cc
@@ -238,8 +238,7 @@
   DISALLOW_COPY_AND_ASSIGN(MockPasswordProtectionNavigationThrottle);
 };
 
-class PasswordProtectionServiceTest
-    : public ::testing::TestWithParam<testing::tuple<bool, bool>> {
+class PasswordProtectionServiceTest : public ::testing::TestWithParam<bool> {
  public:
   PasswordProtectionServiceTest()
       : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
@@ -269,9 +268,7 @@
                 &test_url_loader_factory_),
             content_setting_map_);
     EXPECT_CALL(*password_protection_service_, IsExtendedReporting())
-        .WillRepeatedly(Return(testing::get<0>(GetParam())));
-    EXPECT_CALL(*password_protection_service_, IsEnhancedProtection())
-        .WillRepeatedly(Return(testing::get<1>(GetParam())));
+        .WillRepeatedly(Return(GetParam()));
     EXPECT_CALL(*password_protection_service_, IsIncognito())
         .WillRepeatedly(Return(false));
     EXPECT_CALL(*password_protection_service_,
@@ -381,8 +378,7 @@
   void VerifyContentAreaSizeCollection(
       const LoginReputationClientRequest& request) {
     bool should_report_content_size =
-        (password_protection_service_->IsExtendedReporting() ||
-         password_protection_service_->IsEnhancedProtection()) &&
+        password_protection_service_->IsExtendedReporting() &&
         !password_protection_service_->IsIncognito();
     EXPECT_EQ(should_report_content_size, request.has_content_area_height());
     EXPECT_EQ(should_report_content_size, request.has_content_area_width());
@@ -1123,8 +1119,7 @@
   const auto& reuse_event = actual_request->password_reuse_event();
   EXPECT_FALSE(reuse_event.is_chrome_signin_password());
 
-  if ((password_protection_service_->IsExtendedReporting() ||
-       password_protection_service_->IsEnhancedProtection()) &&
+  if (password_protection_service_->IsExtendedReporting() &&
       !password_protection_service_->IsIncognito()) {
     ASSERT_EQ(2, reuse_event.domains_matching_password_size());
     EXPECT_EQ(kSavedDomain, reuse_event.domains_matching_password(0));
@@ -1405,9 +1400,8 @@
       LoginReputationClientRequest::UNFAMILIAR_LOGIN_PAGE, true);
   base::RunLoop().RunUntilIdle();
 
-  bool is_sber = testing::get<0>(GetParam());
-  bool is_enhanced_protection = testing::get<1>(GetParam());
-  if (is_sber || is_enhanced_protection) {
+  bool is_sber = GetParam();
+  if (is_sber) {
     password_protection_service_->WaitForResponse();
     ASSERT_NE(nullptr, password_protection_service_->GetLatestRequestProto());
     EXPECT_TRUE(password_protection_service_->GetLatestRequestProto()
@@ -1496,18 +1490,8 @@
 
 INSTANTIATE_TEST_SUITE_P(Regular,
                          PasswordProtectionServiceTest,
-                         testing::Combine(testing::Values(false),
-                                          testing::Values(false)));
+                         ::testing::Values(false));
 INSTANTIATE_TEST_SUITE_P(SBER,
                          PasswordProtectionServiceTest,
-                         testing::Combine(testing::Values(true),
-                                          testing::Values(false)));
-INSTANTIATE_TEST_SUITE_P(ENHANCED_PROTECTION,
-                         PasswordProtectionServiceTest,
-                         testing::Combine(testing::Values(false),
-                                          testing::Values(true)));
-INSTANTIATE_TEST_SUITE_P(SBER_ENHANCED_PROTECTION,
-                         PasswordProtectionServiceTest,
-                         testing::Combine(testing::Values(true),
-                                          testing::Values(true)));
+                         ::testing::Values(true));
 }  // namespace safe_browsing
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.cc b/components/safe_browsing/core/common/safe_browsing_prefs.cc
index 7640786..2c517cf9 100644
--- a/components/safe_browsing/core/common/safe_browsing_prefs.cc
+++ b/components/safe_browsing/core/common/safe_browsing_prefs.cc
@@ -130,15 +130,31 @@
     "safebrowsing.urls_to_not_check_for_malware_of_downloaded_content";
 const char kURLsToNotCheckComplianceOfUploadedContent[] =
     "policy.urls_to_not_check_compliance_of_uploaded_content";
-const char kAdvancedProtectionDeepScanningEnabled[] =
-    "safebrowsing.advanced_protection_deep_scanning_enabled";
+const char kAdvancedProtectionExtraSecurityAllowed[] =
+    "safebrowsing.advanced_protection_extra_security_allowed";
 
 }  // namespace prefs
 
 namespace safe_browsing {
 
+SafeBrowsingState GetSafeBrowsingState(const PrefService& prefs) {
+  if (IsEnhancedProtectionEnabled(prefs)) {
+    return ENHANCED_PROTECTION;
+  } else if (prefs.GetBoolean(prefs::kSafeBrowsingEnabled)) {
+    return STANDARD_PROTECTION;
+  } else {
+    return NO_SAFE_BROWSING;
+  }
+}
+
+bool IsSafeBrowsingEnabled(const PrefService& prefs) {
+  return prefs.GetBoolean(prefs::kSafeBrowsingEnabled) ||
+         IsEnhancedProtectionEnabled(prefs);
+}
+
 bool IsEnhancedProtectionEnabled(const PrefService& prefs) {
-  return prefs.GetBoolean(prefs::kSafeBrowsingEnhanced);
+  return prefs.GetBoolean(prefs::kSafeBrowsingEnhanced) &&
+         base::FeatureList::IsEnabled(kEnhancedProtection);
 }
 
 bool ExtendedReportingPrefExists(const PrefService& prefs) {
@@ -154,7 +170,8 @@
 }
 
 bool IsExtendedReportingEnabled(const PrefService& prefs) {
-  return prefs.GetBoolean(prefs::kSafeBrowsingScoutReportingEnabled);
+  return prefs.GetBoolean(prefs::kSafeBrowsingScoutReportingEnabled) ||
+         IsEnhancedProtectionEnabled(prefs);
 }
 
 bool IsExtendedReportingPolicyManaged(const PrefService& prefs) {
@@ -203,7 +220,7 @@
                                 false);
   registry->RegisterIntegerPref(prefs::kSafeBrowsingSendFilesForMalwareCheck,
                                 DO_NOT_SCAN);
-  registry->RegisterBooleanPref(prefs::kAdvancedProtectionDeepScanningEnabled,
+  registry->RegisterBooleanPref(prefs::kAdvancedProtectionExtraSecurityAllowed,
                                 true);
 }
 
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs.h b/components/safe_browsing/core/common/safe_browsing_prefs.h
index 31e41200..c6841b62 100644
--- a/components/safe_browsing/core/common/safe_browsing_prefs.h
+++ b/components/safe_browsing/core/common/safe_browsing_prefs.h
@@ -132,9 +132,9 @@
 // files.
 extern const char kURLsToNotCheckComplianceOfUploadedContent[];
 
-// Boolean that indicates if Chrome is allowed to prompt Advanced Protection
-// users to send their files to Google for malware scanning.
-extern const char kAdvancedProtectionDeepScanningEnabled[];
+// Boolean that indicates if Chrome is allowed to provide extra security
+// features to users enrolled in the Advanced Protection Program.
+extern const char kAdvancedProtectionExtraSecurityAllowed[];
 
 }  // namespace prefs
 
@@ -241,6 +241,23 @@
   DELAY_UPLOADS_AND_DOWNLOADS = 3,
 };
 
+enum SafeBrowsingState {
+  // The user is not opted into Safe Browsing.
+  NO_SAFE_BROWSING = 0,
+  // The user selected standard protection.
+  STANDARD_PROTECTION = 1,
+  // The user selected enhanced protection.
+  ENHANCED_PROTECTION = 2,
+};
+
+SafeBrowsingState GetSafeBrowsingState(const PrefService& prefs);
+
+// Returns whether Safe Browsing is enabled for the user.
+bool IsSafeBrowsingEnabled(const PrefService& prefs);
+
+// Returns whether Safe Browsing Standard Protection is enabled for the user.
+bool IsStandardProtectionEnabled(const PrefService& prefs);
+
 // Returns whether Safe Browsing enhanced protection is enabled for the user.
 bool IsEnhancedProtectionEnabled(const PrefService& prefs);
 
diff --git a/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc b/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc
index 70139ed..7730b55 100644
--- a/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc
+++ b/components/safe_browsing/core/common/safe_browsing_prefs_unittest.cc
@@ -126,7 +126,14 @@
   EXPECT_FALSE(IsEnhancedProtectionEnabled(prefs_));
 
   SetEnhancedProtectionPref(&prefs_, true);
-  EXPECT_TRUE(IsEnhancedProtectionEnabled(prefs_));
+  // If experiment is not on, the pref is not turned on.
+  EXPECT_FALSE(IsEnhancedProtectionEnabled(prefs_));
+  {
+    base::test::ScopedFeatureList scoped_feature_list;
+    scoped_feature_list.InitAndEnableFeature(
+        safe_browsing::kEnhancedProtection);
+    EXPECT_TRUE(IsEnhancedProtectionEnabled(prefs_));
+  }
 }
 
 TEST_F(SafeBrowsingPrefsTest, IsExtendedReportingPolicyManaged) {
diff --git a/components/safe_browsing/core/features.cc b/components/safe_browsing/core/features.cc
index ead4fad..e669be0 100644
--- a/components/safe_browsing/core/features.cc
+++ b/components/safe_browsing/core/features.cc
@@ -44,6 +44,9 @@
 const base::Feature kMalwareScanEnabled{"SafeBrowsingMalwareScanEnabled",
                                         base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kEnhancedProtection{"SafeBrowsingEnhancedProtection",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enable saved password protection by default only on desktop.
 #if BUILDFLAG(FULL_SAFE_BROWSING)
 const base::Feature kPasswordProtectionForSavedPasswords{
diff --git a/components/safe_browsing/core/features.h b/components/safe_browsing/core/features.h
index 3869156..fad4cad 100644
--- a/components/safe_browsing/core/features.h
+++ b/components/safe_browsing/core/features.h
@@ -47,6 +47,9 @@
 // sent for scanning.
 extern const base::Feature kContentComplianceEnabled;
 
+// Enable Chrome Safe Browsing enhanced protection.
+extern const base::Feature kEnhancedProtection;
+
 // Controls whether to do deep scanning for malware. If both this feature and
 // the enterprise policies are enabled, the downloaded and uploaded files are
 // sent for scanning.
diff --git a/components/safe_browsing/core/proto/realtimeapi.proto b/components/safe_browsing/core/proto/realtimeapi.proto
index bcd2df5..d3848c5 100644
--- a/components/safe_browsing/core/proto/realtimeapi.proto
+++ b/components/safe_browsing/core/proto/realtimeapi.proto
@@ -31,6 +31,8 @@
   optional ChromeUserPopulation population = 3;
 
   // Scoped OAuth token for throttling and validation.
+  // Note that this field is used only for displaying on chrome://safe-browsing.
+  // It is not set when the request is sent to the server.
   optional string scoped_oauth_token = 4;
 }
 
diff --git a/components/safe_browsing/core/realtime/BUILD.gn b/components/safe_browsing/core/realtime/BUILD.gn
index 5438b31..29b916c 100644
--- a/components/safe_browsing/core/realtime/BUILD.gn
+++ b/components/safe_browsing/core/realtime/BUILD.gn
@@ -14,6 +14,7 @@
     "//components/prefs",
     "//components/safe_browsing/core:realtimeapi_proto",
     "//components/safe_browsing/core:verdict_cache_manager",
+    "//components/safe_browsing/core/browser:token_fetcher",
     "//components/safe_browsing/core/common:thread_utils",
     "//components/safe_browsing/core/db:v4_protocol_manager_util",
     "//components/signin/public/identity_manager",
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.cc b/components/safe_browsing/core/realtime/url_lookup_service.cc
index 85fb9f1..92916014 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.cc
+++ b/components/safe_browsing/core/realtime/url_lookup_service.cc
@@ -7,14 +7,17 @@
 #include "base/base64url.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/strcat.h"
 #include "base/strings/string_piece.h"
 #include "base/task/post_task.h"
 #include "base/time/time.h"
 #include "components/prefs/pref_service.h"
+#include "components/safe_browsing/core/browser/safe_browsing_token_fetcher.h"
 #include "components/safe_browsing/core/common/thread_utils.h"
 #include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/core/realtime/policy_engine.h"
 #include "components/safe_browsing/core/verdict_cache_manager.h"
+#include "components/signin/public/identity_manager/consent_level.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
 #include "components/sync/driver/sync_service.h"
 #include "net/base/ip_address.h"
@@ -50,6 +53,8 @@
   return url.ReplaceComponents(replacements);
 }
 
+constexpr char kAuthHeaderBearer[] = "Bearer ";
+
 }  // namespace
 
 RealTimeUrlLookupService::RealTimeUrlLookupService(
@@ -68,6 +73,8 @@
   DCHECK(cache_manager_);
   DCHECK(identity_manager_);
   DCHECK(pref_service_);
+  token_fetcher_ =
+      std::make_unique<SafeBrowsingTokenFetcher>(identity_manager_);
 }
 
 void RealTimeUrlLookupService::StartLookup(
@@ -88,8 +95,37 @@
     return;
   }
 
-  std::unique_ptr<RTLookupRequest> request = FillRequestProto(url);
+  if (CanPerformFullURLLookupWithToken()) {
+    token_fetcher_->Start(
+        signin::ConsentLevel::kNotRequired,
+        base::BindOnce(&RealTimeUrlLookupService::OnGetAccessToken,
+                       weak_factory_.GetWeakPtr(), url,
+                       std::move(request_callback),
+                       std::move(response_callback)));
+  } else {
+    std::unique_ptr<RTLookupRequest> request = FillRequestProto(url);
+    SendRequest(url, /* access_token_info */ base::nullopt, std::move(request),
+                std::move(request_callback), std::move(response_callback));
+  }
+}
 
+void RealTimeUrlLookupService::OnGetAccessToken(
+    const GURL& url,
+    RTLookupRequestCallback request_callback,
+    RTLookupResponseCallback response_callback,
+    base::Optional<signin::AccessTokenInfo> access_token_info) {
+  std::unique_ptr<RTLookupRequest> request = FillRequestProto(url);
+  SendRequest(url, access_token_info, std::move(request),
+              std::move(request_callback), std::move(response_callback));
+}
+
+void RealTimeUrlLookupService::SendRequest(
+    const GURL& url,
+    base::Optional<signin::AccessTokenInfo> access_token_info,
+    std::unique_ptr<RTLookupRequest> request,
+    RTLookupRequestCallback request_callback,
+    RTLookupResponseCallback response_callback) {
+  DCHECK(CurrentlyOnThread(ThreadID::UI));
   std::string req_data;
   request->SerializeToString(&req_data);
   net::NetworkTrafficAnnotationTag traffic_annotation =
@@ -129,6 +165,13 @@
   resource_request->url = GURL(kRealTimeLookupUrlPrefix);
   resource_request->load_flags = net::LOAD_DISABLE_CACHE;
   resource_request->method = "POST";
+  // TODO(crbug.com/1041912): Verify if a header is still needed when oauth
+  // token is empty.
+  if (access_token_info.has_value()) {
+    resource_request->headers.SetHeader(
+        net::HttpRequestHeaders::kAuthorization,
+        base::StrCat({kAuthHeaderBearer, access_token_info.value().token}));
+  }
 
   std::unique_ptr<network::SimpleURLLoader> owned_loader =
       network::SimpleURLLoader::Create(std::move(resource_request),
@@ -144,6 +187,12 @@
 
   pending_requests_[owned_loader.release()] = std::move(response_callback);
 
+  // For displaying the token in chrome://safe-browsing, set the
+  // scoped_oauth_token field. Since the token is already set in the header,
+  // this field is set after the request is sent.
+  if (access_token_info.has_value()) {
+    request->set_scoped_oauth_token(access_token_info.value().token);
+  }
   base::PostTask(
       FROM_HERE, CreateTaskTraits(ThreadID::IO),
       base::BindOnce(std::move(request_callback), std::move(request)));
@@ -271,7 +320,6 @@
 }
 
 size_t RealTimeUrlLookupService::GetBackoffDurationInSeconds() const {
-  DCHECK(CurrentlyOnThread(ThreadID::IO));
   return did_successful_lookup_since_last_backoff_
              ? kMinBackOffResetDurationInSeconds
              : std::min(kMaxBackOffResetDurationInSeconds,
diff --git a/components/safe_browsing/core/realtime/url_lookup_service.h b/components/safe_browsing/core/realtime/url_lookup_service.h
index 9687a86..527435f 100644
--- a/components/safe_browsing/core/realtime/url_lookup_service.h
+++ b/components/safe_browsing/core/realtime/url_lookup_service.h
@@ -11,11 +11,13 @@
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/safe_browsing/core/db/v4_protocol_manager_util.h"
 #include "components/safe_browsing/core/proto/realtimeapi.pb.h"
+#include "components/signin/public/identity_manager/access_token_info.h"
 #include "url/gurl.h"
 
 namespace network {
@@ -43,6 +45,8 @@
 
 class VerdictCacheManager;
 
+class SafeBrowsingTokenFetcher;
+
 // This class implements the logic to decide whether the real time lookup
 // feature is enabled for a given user/profile.
 // TODO(crbug.com/1050859): Add RTLookupService check flow.
@@ -103,6 +107,26 @@
   std::unique_ptr<RTLookupResponse> GetCachedRealTimeUrlVerdict(
       const GURL& url);
 
+  // Called when the access token is obtained from |token_fetcher_|.
+  void OnGetAccessToken(
+      const GURL& url,
+      RTLookupRequestCallback request_callback,
+      RTLookupResponseCallback response_callback,
+      base::Optional<signin::AccessTokenInfo> access_token_info);
+
+  // Called to send the request to the Safe Browsing backend over the network.
+  // It also attached an auth header if |access_token_info| has a value.
+  void SendRequest(const GURL& url,
+                   base::Optional<signin::AccessTokenInfo> access_token_info,
+                   std::unique_ptr<RTLookupRequest> request,
+                   RTLookupRequestCallback request_callback,
+                   RTLookupResponseCallback response_callback);
+
+  // Called to get cache from |cache_manager|. If a cache is found, return true.
+  // Otherwise, return false.
+  bool GetCachedRealTimeUrlVerdict(const GURL& url,
+                                   RTLookupResponse* out_response);
+
   // Called to post a task to store the response keyed by the |url| in
   // |cache_manager|.
   void MayBeCacheRealTimeUrlVerdict(const GURL& url, RTLookupResponse response);
@@ -173,6 +197,9 @@
   // Unowned object used for getting preference settings.
   PrefService* pref_service_;
 
+  // The token fetcher used for getting access token.
+  std::unique_ptr<SafeBrowsingTokenFetcher> token_fetcher_;
+
   // A boolean indicates whether the profile associated with this
   // |url_lookup_service| is an off the record profile.
   bool is_off_the_record_;
diff --git a/content/browser/accessibility/browser_accessibility_manager.cc b/content/browser/accessibility/browser_accessibility_manager.cc
index 77f9e8d..634b331 100644
--- a/content/browser/accessibility/browser_accessibility_manager.cc
+++ b/content/browser/accessibility/browser_accessibility_manager.cc
@@ -197,7 +197,6 @@
     SetLastFocusedNode(nullptr);
   }
 
-  event_generator_.ReleaseTree();
   ui::AXTreeManagerMap::GetInstance().RemoveTreeManager(ax_tree_id_);
 }
 
diff --git a/content/browser/back_forward_cache_browsertest.cc b/content/browser/back_forward_cache_browsertest.cc
index b16335bf2..3ababa1 100644
--- a/content/browser/back_forward_cache_browsertest.cc
+++ b/content/browser/back_forward_cache_browsertest.cc
@@ -1538,8 +1538,14 @@
   }
 }
 
+// Flaky on Linux: https://crbug.com/1054194
+#if defined(OS_LINUX)
+#define MAYBE_DoesNotCacheIfRecordingAudio DISABLED_DoesNotCacheIfRecordingAudio
+#else
+#define MAYBE_DoesNotCacheIfRecordingAudio DoesNotCacheIfRecordingAudio
+#endif
 IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
-                       DoesNotCacheIfRecordingAudio) {
+                       MAYBE_DoesNotCacheIfRecordingAudio) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   // Navigate to an empty page.
diff --git a/content/browser/frame_host/navigation_entry_impl_unittest.cc b/content/browser/frame_host/navigation_entry_impl_unittest.cc
index fd5a04d..9ba0087 100644
--- a/content/browser/frame_host/navigation_entry_impl_unittest.cc
+++ b/content/browser/frame_host/navigation_entry_impl_unittest.cc
@@ -345,8 +345,9 @@
 }
 
 #if defined(OS_ANDROID)
+// Failing test, see crbug/1050906.
 // Test that content URIs correctly show the file display name as the title.
-TEST_F(NavigationEntryTest, NavigationEntryContentUri) {
+TEST_F(NavigationEntryTest, DISABLED_NavigationEntryContentUri) {
   base::FilePath image_path;
   EXPECT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &image_path));
   image_path = image_path.Append(FILE_PATH_LITERAL("content"));
diff --git a/content/browser/media/webaudio/audio_context_manager_browsertest.cc b/content/browser/media/webaudio/audio_context_manager_browsertest.cc
index e970345a..5832ba6 100644
--- a/content/browser/media/webaudio/audio_context_manager_browsertest.cc
+++ b/content/browser/media/webaudio/audio_context_manager_browsertest.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "build/build_config.h"
 #include "components/ukm/test_ukm_recorder.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/browser_test_utils.h"
@@ -70,7 +71,14 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(AudioContextManagerTest, AudioContextPlaybackRecorded) {
+// Flaky on Linux: https://crbug.com/1047163
+#if defined(OS_LINUX)
+#define MAYBE_AudioContextPlaybackRecorded DISABLED_AudioContextPlaybackRecorded
+#else
+#define MAYBE_AudioContextPlaybackRecorded AudioContextPlaybackRecorded
+#endif
+IN_PROC_BROWSER_TEST_F(AudioContextManagerTest,
+                       MAYBE_AudioContextPlaybackRecorded) {
   EXPECT_TRUE(NavigateToURL(
       shell(), content::GetTestUrl("media/webaudio/", "playback-test.html")));
 
@@ -89,7 +97,14 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(AudioContextManagerTest, AudioContextPlaybackTimeUkm) {
+// Flaky on Linux: https://crbug.com/941219
+#if defined(OS_LINUX)
+#define MAYBE_AudioContextPlaybackTimeUkm DISABLED_AudioContextPlaybackTimeUkm
+#else
+#define MAYBE_AudioContextPlaybackTimeUkm AudioContextPlaybackTimeUkm
+#endif
+IN_PROC_BROWSER_TEST_F(AudioContextManagerTest,
+                       MAYBE_AudioContextPlaybackTimeUkm) {
   ukm::TestAutoSetUkmRecorder test_ukm_recorder;
   using Entry = ukm::builders::Media_WebAudio_AudioContext_AudibleTime;
 
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 338ac21a..801826b5 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -253,7 +253,8 @@
 
   CONTENT_EXPORT static WebContents* FromRenderFrameHost(RenderFrameHost* rfh);
 
-  // Returns the WebContents associated with the |frame_tree_node_id|.
+  // Returns the WebContents associated with the |frame_tree_node_id|. This may
+  // return nullptr if the RenderFrameHost is shutting down.
   CONTENT_EXPORT static WebContents* FromFrameTreeNodeId(
       int frame_tree_node_id);
 
diff --git a/extensions/test/background_page_watcher.cc b/extensions/test/background_page_watcher.cc
index 215aac6..0a7a2b9 100644
--- a/extensions/test/background_page_watcher.cc
+++ b/extensions/test/background_page_watcher.cc
@@ -43,8 +43,8 @@
   bool* flag = wait_for_open ? &is_waiting_for_open_ : &is_waiting_for_close_;
   base::AutoReset<bool> set_flag(flag, true);
   base::RunLoop run_loop;
-  base::AutoReset<base::Closure> set_quit_run_loop(&quit_run_loop_,
-                                                   run_loop.QuitClosure());
+  base::AutoReset<base::OnceClosure> set_quit_run_loop(&quit_run_loop_,
+                                                       run_loop.QuitClosure());
   run_loop.Run();
   DCHECK_EQ(wait_for_open, IsBackgroundPageOpen());
 }
@@ -64,7 +64,7 @@
     content::RenderFrameHost* rfh) {
   if (is_waiting_for_open_ && extension_id == extension_id_ &&
       IsBackgroundPageOpen())
-    quit_run_loop_.Run();
+    std::move(quit_run_loop_).Run();
 }
 
 void BackgroundPageWatcher::OnExtensionFrameUnregistered(
@@ -72,7 +72,7 @@
     content::RenderFrameHost* rfh) {
   if (is_waiting_for_close_ && extension_id == extension_id_ &&
       !IsBackgroundPageOpen())
-    quit_run_loop_.Run();
+    std::move(quit_run_loop_).Run();
 }
 
 }  // namespace extensions
diff --git a/extensions/test/background_page_watcher.h b/extensions/test/background_page_watcher.h
index 928ed5f..effb2cd 100644
--- a/extensions/test/background_page_watcher.h
+++ b/extensions/test/background_page_watcher.h
@@ -49,7 +49,7 @@
 
   ProcessManager* process_manager_;
   const std::string extension_id_;
-  base::Closure quit_run_loop_;
+  base::OnceClosure quit_run_loop_;
   bool is_waiting_for_open_;
   bool is_waiting_for_close_;
 
diff --git a/extensions/test/extension_test_notification_observer.cc b/extensions/test/extension_test_notification_observer.cc
index 12d6d18..720b2fe 100644
--- a/extensions/test/extension_test_notification_observer.cc
+++ b/extensions/test/extension_test_notification_observer.cc
@@ -20,7 +20,7 @@
 
 // A callback that returns true if the condition has been met and takes no
 // arguments.
-using ConditionCallback = base::Callback<bool(void)>;
+using ConditionCallback = base::RepeatingCallback<bool(void)>;
 
 const Extension* GetNonTerminatedExtensions(const std::string& id,
                                             content::BrowserContext* context) {
@@ -191,7 +191,7 @@
 
   std::unique_ptr<base::CallbackList<void()>::Subscription> subscription;
   if (notification_set) {
-    subscription = notification_set->callback_list().Add(base::Bind(
+    subscription = notification_set->callback_list().Add(base::BindRepeating(
         &ExtensionTestNotificationObserver::MaybeQuit, base::Unretained(this)));
   }
   run_loop.Run();
@@ -202,7 +202,7 @@
 
 void ExtensionTestNotificationObserver::MaybeQuit() {
   if (condition_.Run())
-    quit_closure_.Run();
+    std::move(quit_closure_).Run();
 }
 
 }  // namespace extensions
diff --git a/extensions/test/extension_test_notification_observer.h b/extensions/test/extension_test_notification_observer.h
index 575d16e..c2016453 100644
--- a/extensions/test/extension_test_notification_observer.h
+++ b/extensions/test/extension_test_notification_observer.h
@@ -111,7 +111,7 @@
   // notifications to wait for and to check |condition| when observing. This
   // can be NULL if we are instead waiting for a different observer method, like
   // OnPageActionsUpdated().
-  void WaitForCondition(const base::Callback<bool(void)>& condition,
+  void WaitForCondition(const base::RepeatingCallback<bool(void)>& condition,
                         NotificationSet* notification_set);
 
   void WaitForNotification(int notification_type);
@@ -131,10 +131,10 @@
 
   // The condition for which we are waiting. This should be checked in any
   // observing methods that could trigger it.
-  base::Callback<bool(void)> condition_;
+  base::RepeatingCallback<bool(void)> condition_;
 
   // The closure to quit the currently-running message loop.
-  base::Closure quit_closure_;
+  base::OnceClosure quit_closure_;
 
   // Listens to extension loaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
diff --git a/extensions/test/extensions_unittests_main.cc b/extensions/test/extensions_unittests_main.cc
index f567da9a..429ab89 100644
--- a/extensions/test/extensions_unittests_main.cc
+++ b/extensions/test/extensions_unittests_main.cc
@@ -100,8 +100,7 @@
 
 int main(int argc, char** argv) {
   content::UnitTestTestSuite test_suite(new ExtensionsTestSuite(argc, argv));
-  return base::LaunchUnitTests(argc,
-                               argv,
-                               base::Bind(&content::UnitTestTestSuite::Run,
-                                          base::Unretained(&test_suite)));
+  return base::LaunchUnitTests(argc, argv,
+                               base::BindOnce(&content::UnitTestTestSuite::Run,
+                                              base::Unretained(&test_suite)));
 }
diff --git a/gpu/command_buffer/service/image_reader_gl_owner.cc b/gpu/command_buffer/service/image_reader_gl_owner.cc
index 0e7617f2..be8c59c8 100644
--- a/gpu/command_buffer/service/image_reader_gl_owner.cc
+++ b/gpu/command_buffer/service/image_reader_gl_owner.cc
@@ -93,13 +93,13 @@
   int32_t width = 1, height = 1;
 
   // This should be as small as possible to limit the memory usage.
-  // ImageReader needs 2 images to mimic the behavior of SurfaceTexture. For
-  // SurfaceControl we need 3 images instead of 2 since 1 frame(and hence image
-  // associated with it) will be with system compositor and 2 frames will be in
-  // flight. Also note that we always acquire an image before deleting the
-  // previous acquired image. This causes 2 acquired images to be in flight at
-  // the image acquisition point until the previous image is deleted.
-  max_images_ = IsSurfaceControl(mode) ? 3 : 2;
+  // ImageReader needs 1 image to mimic the behavior of SurfaceTexture. Ideally
+  // it should be 2 but that doesn't work on some devices
+  // (see crbug.com/1051705).
+  // For SurfaceControl we need 3 images instead of 2 since 1 frame (and hence
+  // image associated with it) will be with system compositor and 2 frames will
+  // be in flight.
+  max_images_ = IsSurfaceControl(mode) ? 3 : 1;
   AIMAGE_FORMATS format = mode == Mode::kAImageReaderSecureSurfaceControl
                               ? AIMAGE_FORMAT_PRIVATE
                               : AIMAGE_FORMAT_YUV_420_888;
@@ -221,7 +221,12 @@
 
   DCHECK(image_reader_);
 
-  // Acquire the latest image asynchronously
+  // Acquire the latest image asynchronously. We must release the current image
+  // before acquiring a new one if the ImageReader was initialized with one
+  // outstanding image at max.
+  if (max_images_ == 1)
+    current_image_ref_.reset();
+
   AImage* image = nullptr;
   int acquire_fence_fd = -1;
   media_status_t return_code = AMEDIA_OK;
diff --git a/gpu/command_buffer/service/image_reader_gl_owner_unittest.cc b/gpu/command_buffer/service/image_reader_gl_owner_unittest.cc
index db5ca97b..4c28a31e 100644
--- a/gpu/command_buffer/service/image_reader_gl_owner_unittest.cc
+++ b/gpu/command_buffer/service/image_reader_gl_owner_unittest.cc
@@ -141,14 +141,14 @@
   new_surface = nullptr;
 }
 
-// The max number of images used by the ImageReader must be 2 for non-Surface
+// The max number of images used by the ImageReader must be 1 for non-Surface
 // control.
 TEST_F(ImageReaderGLOwnerTest, MaxImageExpectation) {
   if (!IsImageReaderSupported())
     return;
   EXPECT_EQ(static_cast<ImageReaderGLOwner*>(image_reader_.get())
                 ->max_images_for_testing(),
-            2);
+            1);
 }
 
 class ImageReaderGLOwnerSecureSurfaceControlTest
diff --git a/ios/build/bots/scripts/wpr_runner.py b/ios/build/bots/scripts/wpr_runner.py
index ec70eee..07e7477 100644
--- a/ios/build/bots/scripts/wpr_runner.py
+++ b/ios/build/bots/scripts/wpr_runner.py
@@ -392,9 +392,10 @@
     """
 
     test_config = {}
+    test_config['invert'] = False
+    test_config['test_filter'] = []
     if test_app:
       if test_app.included_tests:
-        test_config['invert'] = False
         test_config['test_filter'] = test_app.included_tests
       elif test_app.excluded_tests:
         test_config['invert'] = True
diff --git a/media/filters/gav1_video_decoder.cc b/media/filters/gav1_video_decoder.cc
index a5db940..081fc9e 100644
--- a/media/filters/gav1_video_decoder.cc
+++ b/media/filters/gav1_video_decoder.cc
@@ -90,34 +90,17 @@
   return std::min(threads_by_height(coded_height), num_cores);
 }
 
-// Libgav1 frame buffer callbacks return 0 on success, -1 on failure.
-
-int OnFrameBufferSizeChangedImpl(void* /*callback_private_data*/,
-                                 int /*bitdepth*/,
-                                 libgav1::ImageFormat /*image_format*/,
-                                 int /*width*/,
-                                 int /*height*/,
-                                 int /*left_border*/,
-                                 int /*right_border*/,
-                                 int /*top_border*/,
-                                 int /*bottom_border*/,
-                                 int /*stride_alignment*/) {
-  // The libgav1 decoder calls this callback to provide information on the
-  // subsequent frames in the video. VideoFramePool ignores this information.
-  return 0;
-}
-
-int GetFrameBufferImpl(void* callback_private_data,
-                       int bitdepth,
-                       libgav1::ImageFormat image_format,
-                       int width,
-                       int height,
-                       int left_border,
-                       int right_border,
-                       int top_border,
-                       int bottom_border,
-                       int stride_alignment,
-                       Libgav1FrameBuffer2* frame_buffer) {
+libgav1::StatusCode GetFrameBufferImpl(void* callback_private_data,
+                                       int bitdepth,
+                                       libgav1::ImageFormat image_format,
+                                       int width,
+                                       int height,
+                                       int left_border,
+                                       int right_border,
+                                       int top_border,
+                                       int bottom_border,
+                                       int stride_alignment,
+                                       Libgav1FrameBuffer* frame_buffer) {
   DCHECK(callback_private_data);
   DCHECK(frame_buffer);
   DCHECK((stride_alignment & (stride_alignment - 1)) == 0);
@@ -129,7 +112,7 @@
   const VideoPixelFormat format =
       Libgav1ImageFormatToVideoPixelFormat(image_format, bitdepth);
   if (format == PIXEL_FORMAT_UNKNOWN)
-    return -1;
+    return libgav1::kStatusUnimplemented;
 
   // VideoFramePool aligns video_frame->data(i), but libgav1 needs
   // video_frame->visible_data(i) to be aligned. To accomplish that, pad
@@ -166,7 +149,7 @@
   auto video_frame =
       decoder->CreateVideoFrame(format, coded_size, visible_rect);
   if (!video_frame)
-    return -1;
+    return libgav1::kStatusInvalidArgument;
 
   for (int i = 0; i < 3; i++) {
     // frame_buffer->plane[i] points to the first byte of the frame proper,
@@ -177,7 +160,7 @@
   frame_buffer->private_data = video_frame.get();
   video_frame->AddRef();
 
-  return 0;
+  return libgav1::kStatusOk;
 }
 
 void ReleaseFrameBufferImpl(void* callback_private_data,
@@ -272,7 +255,6 @@
   libgav1::DecoderSettings settings;
   settings.threads = VideoDecoder::GetRecommendedThreadCount(
       GetDecoderThreadCounts(config.coded_size().height()));
-  settings.on_frame_buffer_size_changed = OnFrameBufferSizeChangedImpl;
   settings.get_frame_buffer = GetFrameBufferImpl;
   settings.release_frame_buffer = ReleaseFrameBufferImpl;
   settings.callback_private_data = this;
diff --git a/net/cert_net/cert_net_fetcher_url_request.cc b/net/cert_net/cert_net_fetcher_url_request.cc
index 4271453..a136c2c 100644
--- a/net/cert_net/cert_net_fetcher_url_request.cc
+++ b/net/cert_net/cert_net_fetcher_url_request.cc
@@ -506,6 +506,9 @@
   if (request_params_->http_method == HTTP_METHOD_POST)
     url_request_->set_method("POST");
   url_request_->set_allow_credentials(false);
+  // Disable secure DNS for hostname lookups triggered by certificate network
+  // fetches to prevent deadlock.
+  url_request_->SetDisableSecureDns(true);
   url_request_->Start();
 
   // Start a timer to limit how long the job runs for.
diff --git a/net/cert_net/cert_net_fetcher_url_request_unittest.cc b/net/cert_net/cert_net_fetcher_url_request_unittest.cc
index 3415dd45..13c6df91 100644
--- a/net/cert_net/cert_net_fetcher_url_request_unittest.cc
+++ b/net/cert_net/cert_net_fetcher_url_request_unittest.cc
@@ -26,6 +26,7 @@
 #include "net/test/url_request/url_request_hanging_read_job.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_interceptor.h"
 #include "net/url_request/url_request_job_factory_impl.h"
 #include "net/url_request/url_request_test_util.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -43,6 +44,8 @@
 const base::FilePath::CharType kDocRoot[] =
     FILE_PATH_LITERAL("net/data/cert_net_fetcher_impl_unittest");
 
+const char kMockSecureDnsHostname[] = "mock.secure.dns.check";
+
 // A non-mock URLRequestContext which can access http:// urls.
 class RequestContext : public URLRequestContext {
  public:
@@ -247,6 +250,47 @@
   void TearDown() override { URLRequestFilter::GetInstance()->ClearHandlers(); }
 };
 
+// Interceptor to check that secure DNS has been disabled.
+class SecureDnsInterceptor : public net::URLRequestInterceptor {
+ public:
+  explicit SecureDnsInterceptor(bool* invoked_interceptor)
+      : invoked_interceptor_(invoked_interceptor) {}
+  ~SecureDnsInterceptor() override = default;
+
+ private:
+  // URLRequestInterceptor implementation:
+  net::URLRequestJob* MaybeInterceptRequest(
+      net::URLRequest* request,
+      net::NetworkDelegate* network_delegate) const override {
+    EXPECT_TRUE(request->disable_secure_dns());
+    *invoked_interceptor_ = true;
+    return nullptr;
+  }
+
+  bool* invoked_interceptor_;
+};
+
+class CertNetFetcherURLRequestTestWithSecureDnsInterceptor
+    : public CertNetFetcherURLRequestTest,
+      public WithTaskEnvironment {
+ public:
+  CertNetFetcherURLRequestTestWithSecureDnsInterceptor()
+      : invoked_interceptor_(false) {}
+
+  void SetUp() override {
+    URLRequestFilter::GetInstance()->AddHostnameInterceptor(
+        "http", kMockSecureDnsHostname,
+        std::make_unique<SecureDnsInterceptor>(&invoked_interceptor_));
+  }
+
+  void TearDown() override { URLRequestFilter::GetInstance()->ClearHandlers(); }
+
+  bool invoked_interceptor() { return invoked_interceptor_; }
+
+ private:
+  bool invoked_interceptor_;
+};
+
 // Helper to start an AIA fetch using default parameters.
 WARN_UNUSED_RESULT std::unique_ptr<CertNetFetcher::Request> StartRequest(
     CertNetFetcher* fetcher,
@@ -653,6 +697,18 @@
   VerifyFailure(ERR_ABORTED, request.get());
 }
 
+TEST_F(CertNetFetcherURLRequestTestWithSecureDnsInterceptor,
+       SecureDnsDisabled) {
+  CreateFetcher();
+  std::unique_ptr<net::CertNetFetcher::Request> request = StartRequest(
+      fetcher(),
+      GURL("http://" + std::string(kMockSecureDnsHostname) + "/cert.crt"));
+  Error actual_error;
+  std::vector<uint8_t> actual_body;
+  request->WaitForResult(&actual_error, &actual_body);
+  EXPECT_TRUE(invoked_interceptor());
+}
+
 }  // namespace
 
 }  // namespace net
diff --git a/net/cert_net/nss_ocsp_session_url_request.cc b/net/cert_net/nss_ocsp_session_url_request.cc
index 5c75b40d..fe03425 100644
--- a/net/cert_net/nss_ocsp_session_url_request.cc
+++ b/net/cert_net/nss_ocsp_session_url_request.cc
@@ -258,6 +258,9 @@
         params->url, DEFAULT_PRIORITY, this, traffic_annotation);
     request_->SetLoadFlags(LOAD_DISABLE_CACHE);
     request_->set_allow_credentials(false);
+    // Disable secure DNS for hostname lookups triggered by certificate network
+    // fetches to prevent deadlock.
+    request_->SetDisableSecureDns(true);
 
     if (!params->extra_request_headers.IsEmpty())
       request_->SetExtraRequestHeaders(params->extra_request_headers);
diff --git a/net/cert_net/nss_ocsp_unittest.cc b/net/cert_net/nss_ocsp_unittest.cc
index 22170ee..35317b5 100644
--- a/net/cert_net/nss_ocsp_unittest.cc
+++ b/net/cert_net/nss_ocsp_unittest.cc
@@ -60,6 +60,7 @@
   URLRequestJob* MaybeInterceptRequest(
       URLRequest* request,
       NetworkDelegate* network_delegate) const override {
+    EXPECT_TRUE(request->disable_secure_dns());
     ++const_cast<AiaResponseHandler*>(this)->request_count_;
 
     return new URLRequestTestJob(request, network_delegate, headers_,
diff --git a/net/dns/dns_config_service_win.cc b/net/dns/dns_config_service_win.cc
index 73bac0c..e34aede 100644
--- a/net/dns/dns_config_service_win.cc
+++ b/net/dns/dns_config_service_win.cc
@@ -56,8 +56,19 @@
     STRING16_LITERAL("SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient");
 const base::char16 kPrimaryDnsSuffixPath[] =
     STRING16_LITERAL("SOFTWARE\\Policies\\Microsoft\\System\\DNSClient");
-const base::char16 kNRPTPath[] = STRING16_LITERAL(
+const base::char16 kNrptPath[] = STRING16_LITERAL(
     "SOFTWARE\\Policies\\Microsoft\\Windows NT\\DNSClient\\DnsPolicyConfig");
+const base::char16 kControlSetNrptPath[] = STRING16_LITERAL(
+    "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters"
+    "\\DnsPolicyConfig");
+const base::char16 kDnsConnectionsPath[] = STRING16_LITERAL(
+    "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters"
+    "\\DnsConnections");
+const base::char16 kDnsActiveIfs[] = STRING16_LITERAL(
+    "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters\\DnsActiveIfs");
+const base::char16 kDnsConnectionsProxies[] = STRING16_LITERAL(
+    "SYSTEM\\CurrentControlSet\\Services\\Dnscache\\Parameters"
+    "\\DnsConnectionsProxies");
 
 enum HostsParseWinResult {
   HOSTS_PARSE_WIN_OK = 0,
@@ -197,8 +208,21 @@
     return CONFIG_PARSE_WIN_READ_PRIMARY_SUFFIX;
   }
 
-  base::win::RegistryKeyIterator nrpt_rules(HKEY_LOCAL_MACHINE, kNRPTPath);
-  settings->have_name_resolution_policy = (nrpt_rules.SubkeyCount() > 0);
+  base::win::RegistryKeyIterator nrpt_rules(HKEY_LOCAL_MACHINE, kNrptPath);
+  base::win::RegistryKeyIterator cs_nrpt_rules(HKEY_LOCAL_MACHINE,
+                                               kControlSetNrptPath);
+  settings->have_name_resolution_policy =
+      (nrpt_rules.SubkeyCount() > 0 || cs_nrpt_rules.SubkeyCount() > 0);
+
+  base::win::RegistryKeyIterator dns_connections(HKEY_LOCAL_MACHINE,
+                                                 kDnsConnectionsPath);
+  base::win::RegistryKeyIterator dns_active_ifs(HKEY_LOCAL_MACHINE,
+                                                kDnsActiveIfs);
+  base::win::RegistryKeyIterator dns_connections_proxies(
+      HKEY_LOCAL_MACHINE, kDnsConnectionsProxies);
+  settings->have_proxy =
+      (dns_connections.SubkeyCount() > 0 || dns_active_ifs.SubkeyCount() > 0 ||
+       dns_connections_proxies.SubkeyCount() > 0);
 
   return CONFIG_PARSE_WIN_OK;
 }
@@ -424,8 +448,7 @@
       policy_devolution(),
       dnscache_devolution(),
       tcpip_devolution(),
-      append_to_multi_label_name(),
-      have_name_resolution_policy(false) {
+      append_to_multi_label_name() {
   policy_search_list.set = false;
   tcpip_search_list.set = false;
   tcpip_domain.set = false;
@@ -498,6 +521,7 @@
 ConfigParseWinResult ConvertSettingsToDnsConfig(
     const DnsSystemSettings& settings,
     DnsConfig* config) {
+  bool uses_vpn = false;
   *config = DnsConfig();
 
   // Use GetAdapterAddresses to get effective DNS server order and
@@ -505,11 +529,18 @@
   // The order of adapters is the network binding order, so stick to the
   // first good adapter.
   for (const IP_ADAPTER_ADDRESSES* adapter = settings.addresses.get();
-       adapter != nullptr && config->nameservers.empty();
-       adapter = adapter->Next) {
-    if (adapter->OperStatus != IfOperStatusUp)
-      continue;
-    if (adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK)
+       adapter != nullptr; adapter = adapter->Next) {
+    // Check each adapter for a VPN interface. Even if a single such interface
+    // is present, treat this as an unhandled configuration.
+    if (adapter->IfType == IF_TYPE_PPP) {
+      uses_vpn = true;
+    }
+
+    // Skip disconnected and loopback adapters. If a good configuration was
+    // previously found, skip processing another adapter.
+    if (adapter->OperStatus != IfOperStatusUp ||
+        adapter->IfType == IF_TYPE_SOFTWARE_LOOPBACK ||
+        !config->nameservers.empty())
       continue;
 
     for (const IP_ADAPTER_DNS_SERVER_ADDRESS* address =
@@ -552,11 +583,14 @@
         (settings.append_to_multi_label_name.value != 0);
   }
 
-  ConfigParseWinResult result = CONFIG_PARSE_WIN_OK;
   if (settings.have_name_resolution_policy) {
-    config->unhandled_options = true;
     // TODO(szym): only set this to true if NRPT has DirectAccess rules.
     config->use_local_ipv6 = true;
+  }
+
+  ConfigParseWinResult result = CONFIG_PARSE_WIN_OK;
+  if (settings.have_name_resolution_policy || settings.have_proxy || uses_vpn) {
+    config->unhandled_options = true;
     result = CONFIG_PARSE_WIN_UNHANDLED_OPTIONS;
   }
 
diff --git a/net/dns/dns_config_service_win.h b/net/dns/dns_config_service_win.h
index 098d476..8b64504a 100644
--- a/net/dns/dns_config_service_win.h
+++ b/net/dns/dns_config_service_win.h
@@ -9,6 +9,7 @@
 // include these headers here.
 #include <winsock2.h>
 #include <iphlpapi.h>
+#include <iptypes.h>
 
 #include <memory>
 #include <string>
@@ -99,7 +100,17 @@
 
   // True when the Name Resolution Policy Table (NRPT) has at least one rule:
   // SOFTWARE\Policies\Microsoft\Windows NT\DNSClient\DnsPolicyConfig\Rule*
-  bool have_name_resolution_policy;
+  // (or)
+  // SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig\Rule*
+  bool have_name_resolution_policy = false;
+
+  // True when a proxy is configured via at least one rule:
+  // SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsConnections
+  // (or)
+  // SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsActiveIfs
+  // (or)
+  // SYSTEM\CurrentControlSet\Services\Dnscache\Parameters\DnsConnectionsProxies
+  bool have_proxy = false;
 };
 
 enum ConfigParseWinResult {
diff --git a/net/dns/dns_config_service_win_unittest.cc b/net/dns/dns_config_service_win_unittest.cc
index 93e62afd..8b1bc05d 100644
--- a/net/dns/dns_config_service_win_unittest.cc
+++ b/net/dns/dns_config_service_win_unittest.cc
@@ -456,6 +456,48 @@
   }
 }
 
+// Setting have_proxy should set unhandled_options.
+TEST(DnsConfigServiceWinTest, HaveProxy) {
+  AdapterInfo infos[2] = {
+      {IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", {"1.0.0.1"}},
+      {0},
+  };
+
+  const struct TestCase {
+    bool have_proxy;
+    bool unhandled_options;
+    internal::ConfigParseWinResult result;
+  } cases[] = {
+      {false, false, internal::CONFIG_PARSE_WIN_OK},
+      {true, true, internal::CONFIG_PARSE_WIN_UNHANDLED_OPTIONS},
+  };
+
+  for (const auto& t : cases) {
+    internal::DnsSystemSettings settings;
+    settings.addresses = CreateAdapterAddresses(infos);
+    settings.have_proxy = t.have_proxy;
+    DnsConfig config;
+    EXPECT_EQ(t.result,
+              internal::ConvertSettingsToDnsConfig(settings, &config));
+    EXPECT_EQ(t.unhandled_options, config.unhandled_options);
+  }
+}
+
+// Setting uses_vpn should set unhandled_options.
+TEST(DnsConfigServiceWinTest, UsesVpn) {
+  AdapterInfo infos[3] = {
+      {IF_TYPE_USB, IfOperStatusUp, L"connection.suffix", {"1.0.0.1"}},
+      {IF_TYPE_PPP, IfOperStatusUp, L"connection.suffix", {"1.0.0.1"}},
+      {0},
+  };
+
+  internal::DnsSystemSettings settings;
+  settings.addresses = CreateAdapterAddresses(infos);
+  DnsConfig config;
+  EXPECT_EQ(internal::CONFIG_PARSE_WIN_UNHANDLED_OPTIONS,
+            internal::ConvertSettingsToDnsConfig(settings, &config));
+  EXPECT_TRUE(config.unhandled_options);
+}
 
 }  // namespace
 
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc
index c3908c3..f04635b0 100644
--- a/net/dns/dns_transaction.cc
+++ b/net/dns/dns_transaction.cc
@@ -399,15 +399,8 @@
     request_->SetExtraRequestHeaders(extra_request_headers);
     // Disable secure DNS for any DoH server hostname lookups to avoid deadlock.
     request_->SetDisableSecureDns(true);
-    // Bypass proxy settings and certificate-related network fetches (currently
-    // just OCSP and CRL requests) to avoid deadlock. AIA requests and the
-    // Negotiate scheme for HTTP authentication may also cause deadlocks, but
-    // these deadlocks can be resolved from the DoH server side (e.g. the server
-    // can send a certificate chain that is complete from the client's
-    // perspective to prevent the client from sending AIA requests).
     request_->SetLoadFlags(request_->load_flags() | LOAD_DISABLE_CACHE |
-                           LOAD_BYPASS_PROXY |
-                           LOAD_DISABLE_CERT_NETWORK_FETCHES);
+                           LOAD_BYPASS_PROXY);
     request_->set_allow_credentials(false);
   }
 
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index be28d4e..04a5b911 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -344,16 +344,17 @@
   return response_info->InitFromPickle(pickle, response_truncated);
 }
 
-void HttpCache::CloseAllConnections() {
+void HttpCache::CloseAllConnections(int net_error,
+                                    const char* net_log_reason_utf8) {
   HttpNetworkSession* session = GetSession();
   if (session)
-    session->CloseAllConnections();
+    session->CloseAllConnections(net_error, net_log_reason_utf8);
 }
 
-void HttpCache::CloseIdleConnections() {
+void HttpCache::CloseIdleConnections(const char* net_log_reason_utf8) {
   HttpNetworkSession* session = GetSession();
   if (session)
-    session->CloseIdleConnections();
+    session->CloseIdleConnections(net_log_reason_utf8);
 }
 
 void HttpCache::OnExternalCacheHit(
diff --git a/net/http/http_cache.h b/net/http/http_cache.h
index 59cf665b..b54d7134 100644
--- a/net/http/http_cache.h
+++ b/net/http/http_cache.h
@@ -216,10 +216,10 @@
   // Close currently active sockets so that fresh page loads will not use any
   // recycled connections.  For sockets currently in use, they may not close
   // immediately, but they will not be reusable. This is for debugging.
-  void CloseAllConnections();
+  void CloseAllConnections(int net_error, const char* net_log_reason_utf8);
 
   // Close all idle connections. Will close all sockets not in active use.
-  void CloseIdleConnections();
+  void CloseIdleConnections(const char* net_log_reason_utf8);
 
   // Called whenever an external cache in the system reuses the resource
   // referred to by |url| and |http_method| and |network_isolation_key|.
diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc
index 14b8ebad2..c7d36e62 100644
--- a/net/http/http_network_layer.cc
+++ b/net/http/http_network_layer.cc
@@ -55,7 +55,7 @@
 
 void HttpNetworkLayer::OnSuspend() {
   suspended_ = true;
-  session_->CloseIdleConnections();
+  session_->CloseIdleConnections("Entering suspend mode");
 }
 
 void HttpNetworkLayer::OnResume() {
diff --git a/net/http/http_network_session.cc b/net/http/http_network_session.cc
index 414036fc..321b65d 100644
--- a/net/http/http_network_session.cc
+++ b/net/http/http_network_session.cc
@@ -326,17 +326,19 @@
   return std::move(dict);
 }
 
-void HttpNetworkSession::CloseAllConnections() {
-  normal_socket_pool_manager_->FlushSocketPoolsWithError(ERR_ABORTED);
-  websocket_socket_pool_manager_->FlushSocketPoolsWithError(ERR_ABORTED);
-  spdy_session_pool_.CloseCurrentSessions(ERR_ABORTED);
-  quic_stream_factory_.CloseAllSessions(ERR_ABORTED,
-                                        quic::QUIC_PEER_GOING_AWAY);
+void HttpNetworkSession::CloseAllConnections(int net_error,
+                                             const char* net_log_reason_utf8) {
+  normal_socket_pool_manager_->FlushSocketPoolsWithError(net_error,
+                                                         net_log_reason_utf8);
+  websocket_socket_pool_manager_->FlushSocketPoolsWithError(
+      net_error, net_log_reason_utf8);
+  spdy_session_pool_.CloseCurrentSessions(static_cast<net::Error>(net_error));
+  quic_stream_factory_.CloseAllSessions(net_error, quic::QUIC_PEER_GOING_AWAY);
 }
 
-void HttpNetworkSession::CloseIdleConnections() {
-  normal_socket_pool_manager_->CloseIdleSockets();
-  websocket_socket_pool_manager_->CloseIdleSockets();
+void HttpNetworkSession::CloseIdleConnections(const char* net_log_reason_utf8) {
+  normal_socket_pool_manager_->CloseIdleSockets(net_log_reason_utf8);
+  websocket_socket_pool_manager_->CloseIdleSockets(net_log_reason_utf8);
   spdy_session_pool_.CloseCurrentIdleSessions();
 }
 
@@ -447,7 +449,7 @@
 
     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
     case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
-      CloseIdleConnections();
+      CloseIdleConnections("Low memory");
       break;
   }
 }
diff --git a/net/http/http_network_session.h b/net/http/http_network_session.h
index a901da64..306ede0 100644
--- a/net/http/http_network_session.h
+++ b/net/http/http_network_session.h
@@ -240,8 +240,8 @@
   // configuration.
   std::unique_ptr<base::Value> QuicInfoToValue() const;
 
-  void CloseAllConnections();
-  void CloseIdleConnections();
+  void CloseAllConnections(int net_error, const char* net_log_reason_utf8);
+  void CloseIdleConnections(const char* net_log_reason_utf8);
 
   // Returns the original Params used to construct this session.
   const Params& params() const { return params_; }
diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc
index 7a42bd3..163c66b 100644
--- a/net/http/http_network_transaction_unittest.cc
+++ b/net/http/http_network_transaction_unittest.cc
@@ -685,9 +685,9 @@
   void ReleaseSocket(const ClientSocketPool::GroupId& group_id,
                      std::unique_ptr<StreamSocket> socket,
                      int64_t generation) override {}
-  void CloseIdleSockets() override {}
-  void CloseIdleSocketsInGroup(
-      const ClientSocketPool::GroupId& group_id) override {}
+  void CloseIdleSockets(const char* net_log_reason_utf8) override {}
+  void CloseIdleSocketsInGroup(const ClientSocketPool::GroupId& group_id,
+                               const char* net_log_reason_utf8) override {}
   int IdleSocketCount() const override { return 0; }
   size_t IdleSocketCountInGroup(
       const ClientSocketPool::GroupId& group_id) const override {
@@ -3392,7 +3392,7 @@
                                  CONNECT_TIMING_HAS_SSL_TIMES);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the request-challenge-retry sequence for basic auth, over a connection
@@ -3516,7 +3516,7 @@
                                  CONNECT_TIMING_HAS_SSL_TIMES);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the request-challenge-retry sequence for basic auth, over a keep-alive
@@ -3627,7 +3627,7 @@
 
     // Flush the idle socket before the NetLog and HttpNetworkTransaction go
     // out of scope.
-    session->CloseAllConnections();
+    session->CloseAllConnections(ERR_FAILED, "Very good reason");
   }
 }
 
@@ -3739,7 +3739,7 @@
 
     // Flush the idle socket before the NetLog and HttpNetworkTransaction go
     // out of scope.
-    session->CloseAllConnections();
+    session->CloseAllConnections(ERR_FAILED, "Very good reason");
   }
 }
 
@@ -3867,7 +3867,7 @@
                                  CONNECT_TIMING_HAS_SSL_TIMES);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the case a proxy closes a socket while the challenge body is being
@@ -4019,7 +4019,7 @@
   EXPECT_THAT(rv, IsError(ERR_TUNNEL_CONNECTION_FAILED));
 
   // Flush the idle socket before the HttpNetworkTransaction goes out of scope.
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the no-tunnel HTTP auth case where proxy and server origins and realms
@@ -4158,7 +4158,7 @@
   EXPECT_EQ("hi", response_data);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the no-tunnel HTTP auth case where proxy and server origins and realms
@@ -4419,7 +4419,7 @@
   EXPECT_EQ("hi", response_data);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Much like the test above, but uses tunnelled connections.
@@ -4690,7 +4690,7 @@
   EXPECT_EQ("hi", response_data);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test that we don't pass extraneous headers from the proxy's response to the
@@ -4753,7 +4753,7 @@
   EXPECT_THAT(rv, IsError(ERR_TUNNEL_CONNECTION_FAILED));
 
   // Flush the idle socket before the HttpNetworkTransaction goes out of scope.
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test when a server (non-proxy) returns a 407 (proxy-authenticate).
@@ -4974,7 +4974,7 @@
                                  CONNECT_TIMING_HAS_SSL_TIMES);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test a proxy auth scheme that allows default credentials and a proxy server
@@ -5094,7 +5094,7 @@
                                  CONNECT_TIMING_HAS_SSL_TIMES);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test a proxy auth scheme that allows default credentials and a proxy server
@@ -5191,7 +5191,7 @@
   EXPECT_THAT(callback.GetResult(rv), IsError(ERR_EMPTY_RESPONSE));
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // This test exercises an odd edge case where the proxy closes the connection
@@ -5312,7 +5312,7 @@
   EXPECT_EQ(ERR_CONNECTION_CLOSED, callback.GetResult(rv));
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test a proxy auth scheme that allows default credentials and a proxy server
@@ -5415,7 +5415,7 @@
   EXPECT_TRUE(response->auth_challenge.has_value());
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // A more nuanced test than GenerateAuthToken test which asserts that
@@ -5545,7 +5545,7 @@
   EXPECT_EQ(200, response->headers->response_code());
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Proxy resolver that returns a proxy with the same host and port for different
@@ -5927,7 +5927,7 @@
   EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
 
   trans2.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the load timing for HTTPS requests with an HTTP proxy and a PAC script.
@@ -6030,7 +6030,7 @@
   EXPECT_EQ(load_timing_info1.socket_log_id, load_timing_info2.socket_log_id);
 
   trans2.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Make sure that NetworkIsolationKeys are passed down to the proxy layer.
@@ -10992,7 +10992,7 @@
                                  CONNECT_TIMING_HAS_SSL_TIMES);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test that an explicitly trusted SPDY proxy can push a resource from an
@@ -11116,7 +11116,7 @@
 
   trans.reset();
   push_trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test that an explicitly trusted SPDY proxy cannot push HTTPS content.
@@ -11199,7 +11199,7 @@
   EXPECT_EQ("hello!", response_data);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test that an explicitly trusted SPDY proxy can push same-origin HTTPS
@@ -11292,7 +11292,7 @@
   EXPECT_EQ("hello!", response_data);
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test HTTPS connections to a site with a bad certificate, going through an
@@ -19008,7 +19008,7 @@
   EXPECT_EQ(101, response->headers->response_code());
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Verify that proxy headers are not sent to the destination server when
@@ -19091,7 +19091,7 @@
   EXPECT_EQ(101, response->headers->response_code());
 
   trans.reset();
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // WebSockets over QUIC is not supported, including over QUIC proxies.
@@ -20822,7 +20822,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 TEST_F(HttpNetworkTransactionTest, ZeroRTTSyncConfirmSyncWrite) {
@@ -20891,7 +20891,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 TEST_F(HttpNetworkTransactionTest, ZeroRTTSyncConfirmAsyncWrite) {
@@ -20945,7 +20945,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 TEST_F(HttpNetworkTransactionTest, ZeroRTTAsyncConfirmSyncWrite) {
@@ -21013,7 +21013,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 TEST_F(HttpNetworkTransactionTest, ZeroRTTAsyncConfirmAsyncWrite) {
@@ -21067,7 +21067,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // 0-RTT rejects are handled at HttpNetworkTransaction.
@@ -21215,7 +21215,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 TEST_F(HttpNetworkTransactionTest, ZeroRTTConfirmErrorAsync) {
@@ -21262,7 +21262,7 @@
 
   trans.reset();
 
-  session->CloseAllConnections();
+  session->CloseAllConnections(ERR_FAILED, "Very good reason");
 }
 
 // Test the proxy and origin server each requesting both TLS client certificates
diff --git a/net/http/http_proxy_connect_job_unittest.cc b/net/http/http_proxy_connect_job_unittest.cc
index 158e631..a1b3525 100644
--- a/net/http/http_proxy_connect_job_unittest.cc
+++ b/net/http/http_proxy_connect_job_unittest.cc
@@ -593,7 +593,7 @@
 
     // Close the H2 session to prevent reuse.
     if (GetParam() == SPDY)
-      session_->CloseAllConnections();
+      session_->CloseAllConnections(ERR_FAILED, "Very good reason");
     // Also need to clear the auth cache before re-running the test.
     session_->http_auth_cache()->ClearAllEntries();
   }
@@ -737,7 +737,7 @@
 
     // Close the H2 session to prevent reuse.
     if (GetParam() == SPDY)
-      session_->CloseAllConnections();
+      session_->CloseAllConnections(ERR_FAILED, "Very good reason");
     // Also need to clear the auth cache before re-running the test.
     session_->http_auth_cache()->ClearAllEntries();
   }
@@ -806,7 +806,7 @@
 
     // Close the H2 session to prevent reuse.
     if (GetParam() == SPDY)
-      session_->CloseAllConnections();
+      session_->CloseAllConnections(ERR_FAILED, "Very good reason");
   }
 }
 
diff --git a/net/http/http_stream_factory_job.cc b/net/http/http_stream_factory_job.cc
index da13214..1bc1d174 100644
--- a/net/http/http_stream_factory_job.cc
+++ b/net/http/http_stream_factory_job.cc
@@ -1145,7 +1145,7 @@
   // Close idle sockets in this group, since subsequent requests will go over
   // |spdy_session|.
   if (connection_->socket()->IsConnected())
-    connection_->CloseIdleSocketsInGroup();
+    connection_->CloseIdleSocketsInGroup("Switching to HTTP2 session");
 
   // If |spdy_session_direct_| is false, then |proxy_info_| is guaranteed to
   // have a non-empty proxy list.
diff --git a/net/http/http_stream_factory_unittest.cc b/net/http/http_stream_factory_unittest.cc
index 1cc23a55..a72fb1b 100644
--- a/net/http/http_stream_factory_unittest.cc
+++ b/net/http/http_stream_factory_unittest.cc
@@ -467,7 +467,9 @@
                      int64_t generation) override {
     ADD_FAILURE();
   }
-  void CloseIdleSockets() override { ADD_FAILURE(); }
+  void CloseIdleSockets(const char* net_log_reason_utf8) override {
+    ADD_FAILURE();
+  }
   int IdleSocketCount() const override {
     ADD_FAILURE();
     return 0;
diff --git a/net/log/net_log_event_type_list.h b/net/log/net_log_event_type_list.h
index c70fb9a..5180a730 100644
--- a/net/log/net_log_event_type_list.h
+++ b/net/log/net_log_event_type_list.h
@@ -809,6 +809,12 @@
 //   }
 EVENT_TYPE(SOCKET_POOL_CONNECTING_N_SOCKETS)
 
+// Logged when a socket pool is closing a socket:
+//   {
+//      "reason": <Reason the socket was closed>,
+//   }
+EVENT_TYPE(SOCKET_POOL_CLOSING_SOCKET)
+
 // ------------------------------------------------------------------------
 // URLRequest
 // ------------------------------------------------------------------------
diff --git a/net/log/test_net_log.cc b/net/log/test_net_log.cc
index a238e9d..29a4c11 100644
--- a/net/log/test_net_log.cc
+++ b/net/log/test_net_log.cc
@@ -60,6 +60,21 @@
   return result;
 }
 
+std::vector<NetLogEntry> RecordingNetLogObserver::GetEntriesForSourceWithType(
+    NetLogSource source,
+    NetLogEventType type,
+    NetLogEventPhase phase) const {
+  base::AutoLock lock(lock_);
+  std::vector<NetLogEntry> result;
+  for (const auto& entry : entry_list_) {
+    if (entry.source.id == source.id && entry.type == type &&
+        entry.phase == phase) {
+      result.push_back(entry.Clone());
+    }
+  }
+  return result;
+}
+
 size_t RecordingNetLogObserver::GetSize() const {
   base::AutoLock lock(lock_);
   return entry_list_.size();
@@ -117,6 +132,13 @@
   return observer_.GetEntriesWithType(type);
 }
 
+std::vector<NetLogEntry> RecordingTestNetLog::GetEntriesForSourceWithType(
+    NetLogSource source,
+    NetLogEventType type,
+    NetLogEventPhase phase) const {
+  return observer_.GetEntriesForSourceWithType(source, type, phase);
+}
+
 size_t RecordingTestNetLog::GetSize() const {
   return observer_.GetSize();
 }
@@ -154,6 +176,13 @@
   return test_net_log_.GetEntriesWithType(type);
 }
 
+std::vector<NetLogEntry> RecordingBoundTestNetLog::GetEntriesForSourceWithType(
+    NetLogSource source,
+    NetLogEventType type,
+    NetLogEventPhase phase) const {
+  return test_net_log_.GetEntriesForSourceWithType(source, type, phase);
+}
+
 size_t RecordingBoundTestNetLog::GetSize() const {
   return test_net_log_.GetSize();
 }
diff --git a/net/log/test_net_log.h b/net/log/test_net_log.h
index e8eb239..2aebd279 100644
--- a/net/log/test_net_log.h
+++ b/net/log/test_net_log.h
@@ -14,6 +14,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "net/log/net_log.h"
+#include "net/log/net_log_event_type.h"
 #include "net/log/net_log_with_source.h"
 
 namespace net {
@@ -55,6 +56,12 @@
   // Returns all captured entries with the specified type.
   std::vector<NetLogEntry> GetEntriesWithType(NetLogEventType type) const;
 
+  // Returns all captured entries with the specified values.
+  std::vector<NetLogEntry> GetEntriesForSourceWithType(
+      NetLogSource source,
+      NetLogEventType type,
+      NetLogEventPhase phase) const;
+
   // Returns the number of entries in the log.
   size_t GetSize() const;
 
@@ -102,6 +109,10 @@
   std::vector<NetLogEntry> GetEntries() const;
   std::vector<NetLogEntry> GetEntriesForSource(NetLogSource source) const;
   std::vector<NetLogEntry> GetEntriesWithType(NetLogEventType type) const;
+  std::vector<NetLogEntry> GetEntriesForSourceWithType(
+      NetLogSource source,
+      NetLogEventType type,
+      NetLogEventPhase phase) const;
   size_t GetSize() const;
   void Clear();
 
@@ -135,6 +146,10 @@
   std::vector<NetLogEntry> GetEntries() const;
   std::vector<NetLogEntry> GetEntriesForSource(NetLogSource source) const;
   std::vector<NetLogEntry> GetEntriesWithType(NetLogEventType type) const;
+  std::vector<NetLogEntry> GetEntriesForSourceWithType(
+      NetLogSource source,
+      NetLogEventType type,
+      NetLogEventPhase phase) const;
   size_t GetSize() const;
   void Clear();
 
diff --git a/net/socket/client_socket_handle.cc b/net/socket/client_socket_handle.cc
index b87ed196..f23033c 100644
--- a/net/socket/client_socket_handle.cc
+++ b/net/socket/client_socket_handle.cc
@@ -125,9 +125,10 @@
   }
 }
 
-void ClientSocketHandle::CloseIdleSocketsInGroup() {
+void ClientSocketHandle::CloseIdleSocketsInGroup(
+    const char* net_log_reason_utf8) {
   if (pool_)
-    pool_->CloseIdleSocketsInGroup(group_id_);
+    pool_->CloseIdleSocketsInGroup(group_id_, net_log_reason_utf8);
 }
 
 bool ClientSocketHandle::GetLoadTimingInfo(
diff --git a/net/socket/client_socket_handle.h b/net/socket/client_socket_handle.h
index 7a32571..727972d 100644
--- a/net/socket/client_socket_handle.h
+++ b/net/socket/client_socket_handle.h
@@ -132,7 +132,7 @@
   void RemoveHigherLayeredPool(HigherLayeredPool* higher_pool);
 
   // Closes idle sockets that are in the same group with |this|.
-  void CloseIdleSocketsInGroup();
+  void CloseIdleSocketsInGroup(const char* net_log_reason_utf8);
 
   // Returns true when Init() has completed successfully.
   bool is_initialized() const { return is_initialized_; }
diff --git a/net/socket/client_socket_pool.h b/net/socket/client_socket_pool.h
index 94f900d..a1b6de3a 100644
--- a/net/socket/client_socket_pool.h
+++ b/net/socket/client_socket_pool.h
@@ -307,18 +307,22 @@
                              std::unique_ptr<StreamSocket> socket,
                              int64_t generation) = 0;
 
-  // This flushes all state from the ClientSocketPool.  This means that all
-  // idle and connecting sockets are discarded with the given |error|.
+  // This flushes all state from the ClientSocketPool.  Pending socket requests
+  // are failed with |error|, while |reason| is logged to the NetLog.
+  //
   // Active sockets being held by ClientSocketPool clients will be discarded
-  // when released back to the pool.
-  // Does not flush any pools wrapped by |this|.
-  virtual void FlushWithError(int error) = 0;
+  // when released back to the pool, though they will be closed with an error
+  // about being of the wrong generation, rather than |net_log_reason_utf8|.
+  virtual void FlushWithError(int error, const char* net_log_reason_utf8) = 0;
 
   // Called to close any idle connections held by the connection manager.
-  virtual void CloseIdleSockets() = 0;
+  // |reason| is logged to NetLog for debugging purposes.
+  virtual void CloseIdleSockets(const char* net_log_reason_utf8) = 0;
 
   // Called to close any idle connections held by the connection manager.
-  virtual void CloseIdleSocketsInGroup(const GroupId& group_id) = 0;
+  // |reason| is logged to NetLog for debugging purposes.
+  virtual void CloseIdleSocketsInGroup(const GroupId& group_id,
+                                       const char* net_log_reason_utf8) = 0;
 
   // The total number of idle sockets in the pool.
   virtual int IdleSocketCount() const = 0;
diff --git a/net/socket/client_socket_pool_base_unittest.cc b/net/socket/client_socket_pool_base_unittest.cc
index 147b766..831ef5b3 100644
--- a/net/socket/client_socket_pool_base_unittest.cc
+++ b/net/socket/client_socket_pool_base_unittest.cc
@@ -697,6 +697,21 @@
     test_base_.ReleaseAllConnections(keep_alive);
   }
 
+  // Expects a single NetLogEventType::SOCKET_POOL_CLOSING_SOCKET in |net_log_|.
+  // It should be logged for the provided source and have the indicated reason.
+  void ExpectSocketClosedWithReason(NetLogSource expected_source,
+                                    const char* expected_reason) {
+    auto entries = net_log_.GetEntriesForSourceWithType(
+        expected_source, NetLogEventType::SOCKET_POOL_CLOSING_SOCKET,
+        NetLogEventPhase::NONE);
+    ASSERT_EQ(1u, entries.size());
+    ASSERT_TRUE(entries[0].HasParams());
+    ASSERT_TRUE(entries[0].params.is_dict());
+    const std::string* reason = entries[0].params.FindStringKey("reason");
+    ASSERT_TRUE(reason);
+    EXPECT_EQ(expected_reason, *reason);
+  }
+
   TestSocketRequest* request(int i) { return test_base_.request(i); }
   size_t requests_size() const { return test_base_.requests_size(); }
   std::vector<std::unique_ptr<TestSocketRequest>>* requests() {
@@ -790,6 +805,41 @@
   EXPECT_TRUE(LogContainsEndEvent(entries, 3, NetLogEventType::SOCKET_POOL));
 }
 
+// Test releasing an open socket into the socket pool, telling the socket pool
+// to close the socket.
+TEST_F(ClientSocketPoolBaseTest, ReleaseAndCloseConnection) {
+  CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+
+  EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK));
+  ASSERT_TRUE(request(0)->handle()->socket());
+  net::NetLogSource source = request(0)->handle()->socket()->NetLog().source();
+  ReleaseOneConnection(ClientSocketPoolTest::NO_KEEP_ALIVE);
+
+  EXPECT_EQ(0, pool_->IdleSocketCount());
+  EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a")));
+
+  ExpectSocketClosedWithReason(
+      source, TransportClientSocketPool::kClosedConnectionReturnedToPool);
+}
+
+TEST_F(ClientSocketPoolBaseTest, SocketWithUnreadDataReturnedToPool) {
+  CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
+  connect_job_factory_->set_job_type(TestConnectJob::kMockUnreadDataJob);
+
+  EXPECT_THAT(StartRequest(TestGroupId("a"), LOWEST), IsError(OK));
+  ASSERT_TRUE(request(0)->handle()->socket());
+  net::NetLogSource source = request(0)->handle()->socket()->NetLog().source();
+  EXPECT_TRUE(request(0)->handle()->socket()->IsConnected());
+  EXPECT_FALSE(request(0)->handle()->socket()->IsConnectedAndIdle());
+  ReleaseOneConnection(ClientSocketPoolTest::KEEP_ALIVE);
+
+  EXPECT_EQ(0, pool_->IdleSocketCount());
+  EXPECT_FALSE(pool_->HasGroupForTesting(TestGroupId("a")));
+
+  ExpectSocketClosedWithReason(
+      source, TransportClientSocketPool::kDataReceivedUnexpectedly);
+}
+
 // Make sure different groups do not share sockets.
 TEST_F(ClientSocketPoolBaseTest, GroupSeparation) {
   base::test::ScopedFeatureList feature_list;
@@ -1898,6 +1948,8 @@
 }
 
 TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsForced) {
+  const char kReason[] = "Really nifty reason";
+
   CreatePool(kDefaultMaxSockets, kDefaultMaxSocketsPerGroup);
   ClientSocketHandle handle;
   TestCompletionCallback callback;
@@ -1907,9 +1959,12 @@
       ClientSocketPool::RespectLimits::ENABLED, callback.callback(),
       ClientSocketPool::ProxyAuthCallback(), pool_.get(), log.bound());
   EXPECT_THAT(rv, IsOk());
+  ASSERT_TRUE(handle.socket());
+  NetLogSource source = handle.socket()->NetLog().source();
   handle.Reset();
   EXPECT_EQ(1, pool_->IdleSocketCount());
-  pool_->CloseIdleSockets();
+  pool_->CloseIdleSockets(kReason);
+  ExpectSocketClosedWithReason(source, kReason);
 }
 
 TEST_F(ClientSocketPoolBaseTest, CloseIdleSocketsInGroupForced) {
@@ -1937,7 +1992,7 @@
   handle2.Reset();
   handle3.Reset();
   EXPECT_EQ(3, pool_->IdleSocketCount());
-  pool_->CloseIdleSocketsInGroup(TestGroupId("a"));
+  pool_->CloseIdleSocketsInGroup(TestGroupId("a"), "Very good reason");
   EXPECT_EQ(1, pool_->IdleSocketCount());
 }
 
@@ -1952,10 +2007,12 @@
       ClientSocketPool::ProxyAuthCallback(), pool_.get(), log.bound());
   EXPECT_THAT(rv, IsOk());
   StreamSocket* socket = handle.socket();
+  ASSERT_TRUE(socket);
   handle.Reset();
   EXPECT_EQ(1, pool_->IdleSocketCount());
 
   // Disconnect socket now to make the socket unusable.
+  NetLogSource source = socket->NetLog().source();
   socket->Disconnect();
   ClientSocketHandle handle2;
   rv = handle2.Init(TestGroupId("a"), params_, base::nullopt, LOWEST,
@@ -1964,6 +2021,13 @@
                     pool_.get(), log.bound());
   EXPECT_THAT(rv, IsOk());
   EXPECT_FALSE(handle2.is_reused());
+
+  // This is admittedly not an accurate error in this case, but normally code
+  // doesn't secretly keep a raw pointers to sockets returned to the socket pool
+  // and close them out of band, so discovering an idle socket was closed when
+  // trying to reuse it normally means it was closed by the remote side.
+  ExpectSocketClosedWithReason(
+      source, TransportClientSocketPool::kRemoteSideClosedConnection);
 }
 
 // Regression test for http://crbug.com/17985.
@@ -2000,7 +2064,7 @@
   // Closing idle sockets should not get us into trouble, but in the bug
   // we were hitting a CHECK here.
   EXPECT_EQ(0u, pool_->IdleSocketCountInGroup(TestGroupId("a")));
-  pool_->CloseIdleSockets();
+  pool_->CloseIdleSockets("Very good reason");
 
   // Run the released socket wakeups.
   base::RunLoop().RunUntilIdle();
@@ -2599,6 +2663,9 @@
 
   handle.Reset();
   ASSERT_THAT(callback2.WaitForResult(), IsOk());
+  // Get the NetLogSource for the socket, so the time out reason can be checked
+  // at the end of the test.
+  NetLogSource net_log_source2 = handle2.socket()->NetLog().source();
   // Use the socket.
   EXPECT_EQ(1, handle2.socket()->Write(nullptr, 1, CompletionOnceCallback(),
                                        TRAFFIC_ANNOTATION_FOR_TESTS));
@@ -2633,6 +2700,8 @@
   auto entries = log.GetEntries();
   EXPECT_FALSE(LogContainsEntryWithType(
       entries, 1, NetLogEventType::SOCKET_POOL_REUSED_AN_EXISTING_SOCKET));
+  ExpectSocketClosedWithReason(
+      net_log_source2, TransportClientSocketPool::kIdleTimeLimitExpired);
 }
 
 // Make sure that we process all pending requests even when we're stalling
@@ -2896,7 +2965,7 @@
                   callback.callback(), ClientSocketPool::ProxyAuthCallback(),
                   pool_.get(), NetLogWithSource()));
 
-  pool_->FlushWithError(ERR_NETWORK_CHANGED);
+  pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed");
 
   // We'll call back into this now.
   callback.WaitForResult();
@@ -2916,8 +2985,9 @@
                   pool_.get(), NetLogWithSource()));
   EXPECT_THAT(callback.WaitForResult(), IsOk());
   EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
+  NetLogSource source = handle.socket()->NetLog().source();
 
-  pool_->FlushWithError(ERR_NETWORK_CHANGED);
+  pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed");
 
   handle.Reset();
   base::RunLoop().RunUntilIdle();
@@ -2930,6 +3000,9 @@
                   pool_.get(), NetLogWithSource()));
   EXPECT_THAT(callback.WaitForResult(), IsOk());
   EXPECT_EQ(ClientSocketHandle::UNUSED, handle.reuse_type());
+
+  ExpectSocketClosedWithReason(
+      source, TransportClientSocketPool::kSocketGenerationOutOfDate);
 }
 
 class ConnectWithinCallback : public TestCompletionCallbackBase {
@@ -2990,7 +3063,7 @@
   // Second job will be started during the first callback, and will
   // asynchronously complete with OK.
   connect_job_factory_->set_job_type(TestConnectJob::kMockPendingJob);
-  pool_->FlushWithError(ERR_NETWORK_CHANGED);
+  pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed");
   EXPECT_THAT(callback.WaitForResult(), IsError(ERR_NETWORK_CHANGED));
   EXPECT_THAT(callback.WaitForNestedResult(), IsOk());
 }
@@ -5297,7 +5370,7 @@
 
   auth_helper.WaitForAuth();
 
-  pool_->FlushWithError(ERR_FAILED);
+  pool_->FlushWithError(ERR_FAILED, "Network changed");
   base::RunLoop().RunUntilIdle();
 
   // When flushing the socket pool, bound sockets should delay returning the
diff --git a/net/socket/client_socket_pool_manager.h b/net/socket/client_socket_pool_manager.h
index 9a4ea9d..496a6e6 100644
--- a/net/socket/client_socket_pool_manager.h
+++ b/net/socket/client_socket_pool_manager.h
@@ -72,8 +72,11 @@
   static base::TimeDelta unused_idle_socket_timeout(
       HttpNetworkSession::SocketPoolType pool_type);
 
-  virtual void FlushSocketPoolsWithError(int error) = 0;
-  virtual void CloseIdleSockets() = 0;
+  // The |net_error| is returned to clients of pending socket requests, while
+  // |reason| is logged at the socket layer.
+  virtual void FlushSocketPoolsWithError(int net_error,
+                                         const char* net_log_reason_utf8) = 0;
+  virtual void CloseIdleSockets(const char* net_log_reason_utf8) = 0;
 
   // Returns the socket pool for the specified ProxyServer (Which may be
   // ProxyServer::Direct()).
diff --git a/net/socket/client_socket_pool_manager_impl.cc b/net/socket/client_socket_pool_manager_impl.cc
index e76c1980..d1835a5 100644
--- a/net/socket/client_socket_pool_manager_impl.cc
+++ b/net/socket/client_socket_pool_manager_impl.cc
@@ -38,15 +38,18 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 }
 
-void ClientSocketPoolManagerImpl::FlushSocketPoolsWithError(int error) {
+void ClientSocketPoolManagerImpl::FlushSocketPoolsWithError(
+    int net_error,
+    const char* net_log_reason_utf8) {
   for (const auto& it : socket_pools_) {
-    it.second->FlushWithError(error);
+    it.second->FlushWithError(net_error, net_log_reason_utf8);
   }
 }
 
-void ClientSocketPoolManagerImpl::CloseIdleSockets() {
+void ClientSocketPoolManagerImpl::CloseIdleSockets(
+    const char* net_log_reason_utf8) {
   for (const auto& it : socket_pools_) {
-    it.second->CloseIdleSockets();
+    it.second->CloseIdleSockets(net_log_reason_utf8);
   }
 }
 
diff --git a/net/socket/client_socket_pool_manager_impl.h b/net/socket/client_socket_pool_manager_impl.h
index 21132763..dfeba2f 100644
--- a/net/socket/client_socket_pool_manager_impl.h
+++ b/net/socket/client_socket_pool_manager_impl.h
@@ -42,8 +42,9 @@
       HttpNetworkSession::SocketPoolType pool_type);
   ~ClientSocketPoolManagerImpl() override;
 
-  void FlushSocketPoolsWithError(int error) override;
-  void CloseIdleSockets() override;
+  void FlushSocketPoolsWithError(int net_error,
+                                 const char* net_log_reason_utf8) override;
+  void CloseIdleSockets(const char* net_log_reason_utf8) override;
 
   ClientSocketPool* GetSocketPool(const ProxyServer& proxy_server) override;
 
diff --git a/net/socket/mock_client_socket_pool_manager.cc b/net/socket/mock_client_socket_pool_manager.cc
index 15d95cc..41bea61 100644
--- a/net/socket/mock_client_socket_pool_manager.cc
+++ b/net/socket/mock_client_socket_pool_manager.cc
@@ -20,11 +20,14 @@
   socket_pools_[proxy_server] = std::move(pool);
 }
 
-void MockClientSocketPoolManager::FlushSocketPoolsWithError(int error) {
+void MockClientSocketPoolManager::FlushSocketPoolsWithError(
+    int error,
+    const char* net_log_reason_utf8) {
   NOTIMPLEMENTED();
 }
 
-void MockClientSocketPoolManager::CloseIdleSockets() {
+void MockClientSocketPoolManager::CloseIdleSockets(
+    const char* net_log_reason_utf8) {
   NOTIMPLEMENTED();
 }
 
diff --git a/net/socket/mock_client_socket_pool_manager.h b/net/socket/mock_client_socket_pool_manager.h
index 337bebb..782aea43 100644
--- a/net/socket/mock_client_socket_pool_manager.h
+++ b/net/socket/mock_client_socket_pool_manager.h
@@ -28,8 +28,9 @@
                      std::unique_ptr<ClientSocketPool> pool);
 
   // ClientSocketPoolManager methods:
-  void FlushSocketPoolsWithError(int error) override;
-  void CloseIdleSockets() override;
+  void FlushSocketPoolsWithError(int error,
+                                 const char* net_log_reason_utf8) override;
+  void CloseIdleSockets(const char* net_log_reason_utf8) override;
   ClientSocketPool* GetSocketPool(const ProxyServer& proxy_server) override;
   std::unique_ptr<base::Value> SocketPoolInfoToValue() const override;
   void DumpMemoryStats(
diff --git a/net/socket/transport_client_socket_pool.cc b/net/socket/transport_client_socket_pool.cc
index d1823b2..67da4cd4 100644
--- a/net/socket/transport_client_socket_pool.cc
+++ b/net/socket/transport_client_socket_pool.cc
@@ -49,6 +49,24 @@
 
 }  // namespace
 
+const char TransportClientSocketPool::kCertDatabaseChanged[] =
+    "Cert database changed";
+const char TransportClientSocketPool::kClosedConnectionReturnedToPool[] =
+    "Connection was closed when it was returned to the pool";
+const char TransportClientSocketPool::kDataReceivedUnexpectedly[] =
+    "Data received unexpectedly";
+const char TransportClientSocketPool::kIdleTimeLimitExpired[] =
+    "Idle time limit expired";
+const char TransportClientSocketPool::kNetworkChanged[] = "Network changed";
+const char TransportClientSocketPool::kRemoteSideClosedConnection[] =
+    "Remote side closed connection";
+const char TransportClientSocketPool::kSocketGenerationOutOfDate[] =
+    "Socket generation out of date";
+const char TransportClientSocketPool::kSocketPoolDestroyed[] =
+    "Socket pool destroyed";
+const char TransportClientSocketPool::kSslConfigChanged[] =
+    "SSL configuration changed";
+
 // ConnectJobFactory implementation that creates the standard ConnectJob
 // classes, using SocketParams.
 class TransportClientSocketPool::ConnectJobFactoryImpl
@@ -156,7 +174,7 @@
   // Clean up any idle sockets and pending connect jobs.  Assert that we have no
   // remaining active sockets or pending requests.  They should have all been
   // cleaned up prior to |this| being destroyed.
-  FlushWithError(ERR_ABORTED);
+  FlushWithError(ERR_ABORTED, kSocketPoolDestroyed);
   DCHECK(group_map_.empty());
   DCHECK(pending_callback_map_.empty());
   DCHECK_EQ(0, connecting_socket_count_);
@@ -258,7 +276,7 @@
       respect_limits, NORMAL, std::move(params), proxy_annotation_tag, net_log);
 
   // Cleanup any timed-out idle sockets.
-  CleanupIdleSockets(false);
+  CleanupIdleSockets(false, nullptr /* net_log_reason_utf8 */);
 
   request->net_log().BeginEvent(NetLogEventType::SOCKET_POOL);
 
@@ -307,7 +325,7 @@
                   proxy_annotation_tag, net_log);
 
   // Cleanup any timed-out idle sockets.
-  CleanupIdleSockets(false);
+  CleanupIdleSockets(false, nullptr /* net_log_reason_utf8 */);
 
   if (num_sockets > max_sockets_per_group_) {
     num_sockets = max_sockets_per_group_;
@@ -474,9 +492,13 @@
   //   the |idle_socket_it| will be set to the newest used idle socket.
   for (auto it = idle_sockets->begin(); it != idle_sockets->end();) {
     // Check whether socket is usable. Note that it's unlikely that the socket
-    // is not usuable because this function is always invoked after a
+    // is not usable because this function is always invoked after a
     // reusability check, but in theory socket can be closed asynchronously.
-    if (!it->IsUsable()) {
+    const char* net_log_reason_utf8;
+    if (!it->IsUsable(&net_log_reason_utf8)) {
+      it->socket->NetLog().AddEventWithStringParams(
+          NetLogEventType::SOCKET_POOL_CLOSING_SOCKET, "reason",
+          net_log_reason_utf8);
       DecrementIdleCount();
       delete it->socket;
       it = idle_sockets->erase(it);
@@ -601,19 +623,22 @@
   }
 }
 
-void TransportClientSocketPool::CloseIdleSockets() {
-  CleanupIdleSockets(true);
+void TransportClientSocketPool::CloseIdleSockets(
+    const char* net_log_reason_utf8) {
+  CleanupIdleSockets(true, net_log_reason_utf8);
   DCHECK_EQ(0, idle_socket_count_);
 }
 
 void TransportClientSocketPool::CloseIdleSocketsInGroup(
-    const GroupId& group_id) {
+    const GroupId& group_id,
+    const char* net_log_reason_utf8) {
   if (idle_socket_count_ == 0)
     return;
   auto it = group_map_.find(group_id);
   if (it == group_map_.end())
     return;
-  CleanupIdleSocketsInGroup(true, it->second, base::TimeTicks::Now());
+  CleanupIdleSocketsInGroup(true, it->second, base::TimeTicks::Now(),
+                            net_log_reason_utf8);
   if (it->second->IsEmpty())
     RemoveGroup(it);
 }
@@ -754,10 +779,26 @@
   }
 }
 
-bool TransportClientSocketPool::IdleSocket::IsUsable() const {
-  if (socket->WasEverUsed())
-    return socket->IsConnectedAndIdle();
-  return socket->IsConnected();
+bool TransportClientSocketPool::IdleSocket::IsUsable(
+    const char** net_log_reason_utf8) const {
+  DCHECK(net_log_reason_utf8);
+  if (socket->WasEverUsed()) {
+    if (!socket->IsConnectedAndIdle()) {
+      if (!socket->IsConnected()) {
+        *net_log_reason_utf8 = kRemoteSideClosedConnection;
+      } else {
+        *net_log_reason_utf8 = kDataReceivedUnexpectedly;
+      }
+      return false;
+    }
+    return true;
+  }
+
+  if (!socket->IsConnected()) {
+    *net_log_reason_utf8 = kRemoteSideClosedConnection;
+    return false;
+  }
+  return true;
 }
 
 TransportClientSocketPool::TransportClientSocketPool(
@@ -794,8 +835,11 @@
     bool is_cert_database_change) {
   // When the user changes the SSL config, flush all idle sockets so they won't
   // get re-used.
-  FlushWithError(is_cert_database_change ? ERR_CERT_DATABASE_CHANGED
-                                         : ERR_NETWORK_CHANGED);
+  if (is_cert_database_change) {
+    FlushWithError(ERR_CERT_DATABASE_CHANGED, kCertDatabaseChanged);
+  } else {
+    FlushWithError(ERR_NETWORK_CHANGED, kNetworkChanged);
+  }
 }
 
 void TransportClientSocketPool::OnSSLConfigForServerChanged(
@@ -820,7 +864,7 @@
                           to_refresh->first.destination() == server)) {
       refreshed_any = true;
       // Note this call may destroy the group and invalidate |to_refresh|.
-      RefreshGroup(to_refresh, now);
+      RefreshGroup(to_refresh, now, kSslConfigChanged);
     }
   }
 
@@ -838,7 +882,9 @@
   return base::Contains(group_map_, group_id);
 }
 
-void TransportClientSocketPool::CleanupIdleSockets(bool force) {
+void TransportClientSocketPool::CleanupIdleSockets(
+    bool force,
+    const char* net_log_reason_utf8) {
   if (idle_socket_count_ == 0)
     return;
 
@@ -848,7 +894,7 @@
 
   for (auto i = group_map_.begin(); i != group_map_.end();) {
     Group* group = i->second;
-    CleanupIdleSocketsInGroup(force, group, now);
+    CleanupIdleSocketsInGroup(force, group, now, net_log_reason_utf8);
     // Delete group if no longer needed.
     if (group->IsEmpty()) {
       auto old = i++;
@@ -879,19 +925,40 @@
 void TransportClientSocketPool::CleanupIdleSocketsInGroup(
     bool force,
     Group* group,
-    const base::TimeTicks& now) {
+    const base::TimeTicks& now,
+    const char* net_log_reason_utf8) {
+  // If |force| is true, a reason must be provided.
+  DCHECK(!force || net_log_reason_utf8);
+
   auto idle_socket_it = group->mutable_idle_sockets()->begin();
   while (idle_socket_it != group->idle_sockets().end()) {
+    bool should_clean_up = force;
+    const char* reason_for_closing_socket = net_log_reason_utf8;
     base::TimeDelta timeout = idle_socket_it->socket->WasEverUsed()
                                   ? used_idle_socket_timeout_
                                   : unused_idle_socket_timeout_;
-    bool timed_out = (now - idle_socket_it->start_time) >= timeout;
-    bool should_clean_up = force || timed_out || !idle_socket_it->IsUsable();
+
+    // Timeout errors take precedence over the reason for flushing sockets in
+    // the group, if applicable.
+    if (now - idle_socket_it->start_time >= timeout) {
+      should_clean_up = true;
+      reason_for_closing_socket = kIdleTimeLimitExpired;
+    }
+
+    // Usability errors take precedence over over other errors.
+    if (!idle_socket_it->IsUsable(&reason_for_closing_socket))
+      should_clean_up = true;
+
     if (should_clean_up) {
+      DCHECK(reason_for_closing_socket);
+      idle_socket_it->socket->NetLog().AddEventWithStringParams(
+          NetLogEventType::SOCKET_POOL_CLOSING_SOCKET, "reason",
+          reason_for_closing_socket);
       delete idle_socket_it->socket;
       idle_socket_it = group->mutable_idle_sockets()->erase(idle_socket_it);
       DecrementIdleCount();
     } else {
+      DCHECK(!reason_for_closing_socket);
       ++idle_socket_it;
     }
   }
@@ -954,13 +1021,32 @@
   CHECK_GT(group->active_socket_count(), 0);
   group->DecrementActiveSocketCount();
 
-  bool can_reuse =
-      socket->IsConnectedAndIdle() && group_generation == group->generation();
-  if (can_reuse) {
+  bool can_resuse_socket = false;
+  base::StringPiece not_reusable_reason;
+  if (!socket->IsConnectedAndIdle()) {
+    if (!socket->IsConnected()) {
+      not_reusable_reason = kClosedConnectionReturnedToPool;
+    } else {
+      not_reusable_reason = kDataReceivedUnexpectedly;
+    }
+  } else if (group_generation != group->generation()) {
+    not_reusable_reason = kSocketGenerationOutOfDate;
+  } else {
+    can_resuse_socket = true;
+  }
+
+  if (can_resuse_socket) {
+    DCHECK(not_reusable_reason.empty());
+
     // Add it to the idle list.
     AddIdleSocket(std::move(socket), group);
     OnAvailableSocketSlot(group_id, group);
   } else {
+    DCHECK(!not_reusable_reason.empty());
+
+    socket->NetLog().AddEventWithStringParams(
+        NetLogEventType::SOCKET_POOL_CLOSING_SOCKET, "reason",
+        not_reusable_reason);
     if (group->IsEmpty())
       RemoveGroup(i);
     socket.reset();
@@ -1032,12 +1118,14 @@
 }
 
 void TransportClientSocketPool::OnIPAddressChanged() {
-  FlushWithError(ERR_NETWORK_CHANGED);
+  FlushWithError(ERR_NETWORK_CHANGED, kNetworkChanged);
 }
 
-void TransportClientSocketPool::FlushWithError(int error) {
+void TransportClientSocketPool::FlushWithError(
+    int error,
+    const char* net_log_reason_utf8) {
   CancelAllConnectJobs();
-  CloseIdleSockets();
+  CloseIdleSockets(net_log_reason_utf8);
   CancelAllRequestsWithError(error);
   for (const auto& group : group_map_) {
     group.second->IncrementGeneration();
@@ -1360,9 +1448,10 @@
 }
 
 void TransportClientSocketPool::RefreshGroup(GroupMap::iterator it,
-                                             const base::TimeTicks& now) {
+                                             const base::TimeTicks& now,
+                                             const char* net_log_reason_utf8) {
   Group* group = it->second;
-  CleanupIdleSocketsInGroup(true /* force */, group, now);
+  CleanupIdleSocketsInGroup(true /* force */, group, now, net_log_reason_utf8);
 
   connecting_socket_count_ -= group->jobs().size();
   group->RemoveAllUnboundJobs();
diff --git a/net/socket/transport_client_socket_pool.h b/net/socket/transport_client_socket_pool.h
index cdfa587..bbf79b68 100644
--- a/net/socket/transport_client_socket_pool.h
+++ b/net/socket/transport_client_socket_pool.h
@@ -72,6 +72,17 @@
       public NetworkChangeNotifier::IPAddressObserver,
       public SSLClientContext::Observer {
  public:
+  // Reasons for closing sockets. Exposed here for testing.
+  static const char kCertDatabaseChanged[];
+  static const char kClosedConnectionReturnedToPool[];
+  static const char kDataReceivedUnexpectedly[];
+  static const char kIdleTimeLimitExpired[];
+  static const char kNetworkChanged[];
+  static const char kRemoteSideClosedConnection[];
+  static const char kSocketGenerationOutOfDate[];
+  static const char kSocketPoolDestroyed[];
+  static const char kSslConfigChanged[];
+
   using Flags = uint32_t;
 
   // Used to specify specific behavior for the ClientSocketPool.
@@ -217,9 +228,10 @@
   void ReleaseSocket(const GroupId& group_id,
                      std::unique_ptr<StreamSocket> socket,
                      int64_t group_generation) override;
-  void FlushWithError(int error) override;
-  void CloseIdleSockets() override;
-  void CloseIdleSocketsInGroup(const GroupId& group_id) override;
+  void FlushWithError(int error, const char* net_log_reason_utf8) override;
+  void CloseIdleSockets(const char* net_log_reason_utf8) override;
+  void CloseIdleSocketsInGroup(const GroupId& group_id,
+                               const char* net_log_reason_utf8) override;
   int IdleSocketCount() const override;
   size_t IdleSocketCountInGroup(const GroupId& group_id) const override;
   LoadState GetLoadState(const GroupId& group_id,
@@ -284,7 +296,10 @@
     // Note that a socket that has never been used before (like a preconnected
     // socket) may be used even with unread data.  This may be, e.g., a SPDY
     // SETTINGS frame.
-    bool IsUsable() const;
+    //
+    // If the socket is not usable, |net_log_reason_utf8| is set to a string
+    // indicating why the socket is not usable.
+    bool IsUsable(const char** net_log_reason_utf8) const;
 
     StreamSocket* socket;
     base::TimeTicks start_time;
@@ -631,7 +646,8 @@
 
   // Closes all idle sockets if |force| is true.  Else, only closes idle
   // sockets that timed out or can't be reused.  Made public for testing.
-  void CleanupIdleSockets(bool force);
+  // |reason| must be non-empty when |force| is true.
+  void CleanupIdleSockets(bool force, const char* net_log_reason_utf8);
 
   // Closes one idle socket.  Picks the first one encountered.
   // TODO(willchan): Consider a better algorithm for doing this.  Perhaps we
@@ -648,7 +664,8 @@
   // reused.
   void CleanupIdleSocketsInGroup(bool force,
                                  Group* group,
-                                 const base::TimeTicks& now);
+                                 const base::TimeTicks& now,
+                                 const char* net_log_reason_utf8);
 
   Group* GetOrCreateGroup(const GroupId& group_id);
   void RemoveGroup(const GroupId& group_id);
@@ -757,7 +774,9 @@
   // The group may be removed if this leaves the group empty. The caller must
   // call CheckForStalledSocketGroups() after all applicable groups have been
   // refreshed.
-  void RefreshGroup(GroupMap::iterator it, const base::TimeTicks& now);
+  void RefreshGroup(GroupMap::iterator it,
+                    const base::TimeTicks& now,
+                    const char* net_log_reason_utf8);
 
   GroupMap group_map_;
 
diff --git a/net/socket/transport_client_socket_pool_unittest.cc b/net/socket/transport_client_socket_pool_unittest.cc
index f2f5f6e..b4687129 100644
--- a/net/socket/transport_client_socket_pool_unittest.cc
+++ b/net/socket/transport_client_socket_pool_unittest.cc
@@ -1156,7 +1156,7 @@
     handle.Reset();
 
     // Close all pending connect jobs and existing sockets.
-    pool_->FlushWithError(ERR_NETWORK_CHANGED);
+    pool_->FlushWithError(ERR_NETWORK_CHANGED, "Network changed");
   }
 }
 
diff --git a/net/socket/websocket_transport_client_socket_pool.cc b/net/socket/websocket_transport_client_socket_pool.cc
index d773a8e..9a46a62 100644
--- a/net/socket/websocket_transport_client_socket_pool.cc
+++ b/net/socket/websocket_transport_client_socket_pool.cc
@@ -42,7 +42,7 @@
 
 WebSocketTransportClientSocketPool::~WebSocketTransportClientSocketPool() {
   // Clean up any pending connect jobs.
-  FlushWithError(ERR_ABORTED);
+  FlushWithError(ERR_ABORTED, "");
   DCHECK(pending_connects_.empty());
   DCHECK_EQ(0, handed_out_socket_count_);
   DCHECK(stalled_request_queue_.empty());
@@ -174,7 +174,9 @@
   ActivateStalledRequest();
 }
 
-void WebSocketTransportClientSocketPool::FlushWithError(int error) {
+void WebSocketTransportClientSocketPool::FlushWithError(
+    int error,
+    const char* net_log_reason_utf8) {
   DCHECK_NE(error, OK);
 
   // Sockets which are in LOAD_STATE_CONNECTING are in danger of unlocking
@@ -187,6 +189,9 @@
   for (auto it = pending_connects_.begin(); it != pending_connects_.end();) {
     InvokeUserCallbackLater(it->second->socket_handle(),
                             it->second->release_callback(), error);
+    it->second->connect_job_net_log().AddEventWithStringParams(
+        NetLogEventType::SOCKET_POOL_CLOSING_SOCKET, "reason",
+        net_log_reason_utf8);
     it = pending_connects_.erase(it);
   }
   for (auto it = stalled_request_queue_.begin();
@@ -198,12 +203,14 @@
   flushing_ = false;
 }
 
-void WebSocketTransportClientSocketPool::CloseIdleSockets() {
+void WebSocketTransportClientSocketPool::CloseIdleSockets(
+    const char* net_log_reason_utf8) {
   // We have no idle sockets.
 }
 
 void WebSocketTransportClientSocketPool::CloseIdleSocketsInGroup(
-    const GroupId& group_id) {
+    const GroupId& group_id,
+    const char* net_log_reason_utf8) {
   // We have no idle sockets.
 }
 
diff --git a/net/socket/websocket_transport_client_socket_pool.h b/net/socket/websocket_transport_client_socket_pool.h
index df0706ae..f9101cc9 100644
--- a/net/socket/websocket_transport_client_socket_pool.h
+++ b/net/socket/websocket_transport_client_socket_pool.h
@@ -84,9 +84,10 @@
   void ReleaseSocket(const GroupId& group_id,
                      std::unique_ptr<StreamSocket> socket,
                      int64_t generation) override;
-  void FlushWithError(int error) override;
-  void CloseIdleSockets() override;
-  void CloseIdleSocketsInGroup(const GroupId& group_id) override;
+  void FlushWithError(int error, const char* net_log_reason_utf8) override;
+  void CloseIdleSockets(const char* net_log_reason_utf8) override;
+  void CloseIdleSocketsInGroup(const GroupId& group_id,
+                               const char* net_log_reason_utf8) override;
   int IdleSocketCount() const override;
   size_t IdleSocketCountInGroup(const GroupId& group_id) const override;
   LoadState GetLoadState(const GroupId& group_id,
diff --git a/net/socket/websocket_transport_client_socket_pool_unittest.cc b/net/socket/websocket_transport_client_socket_pool_unittest.cc
index ae476b1..24ed21d 100644
--- a/net/socket/websocket_transport_client_socket_pool_unittest.cc
+++ b/net/socket/websocket_transport_client_socket_pool_unittest.cc
@@ -967,7 +967,7 @@
 TEST_F(WebSocketTransportClientSocketPoolTest,
        FlushWithErrorFlushesPendingConnections) {
   EXPECT_THAT(StartRequest(kDefaultPriority), IsError(ERR_IO_PENDING));
-  pool_.FlushWithError(ERR_FAILED);
+  pool_.FlushWithError(ERR_FAILED, "Very good reason");
   EXPECT_THAT(request(0)->WaitForResult(), IsError(ERR_FAILED));
 }
 
@@ -976,7 +976,7 @@
   for (int i = 0; i < kMaxSockets + 1; ++i) {
     EXPECT_THAT(StartRequest(kDefaultPriority), IsError(ERR_IO_PENDING));
   }
-  pool_.FlushWithError(ERR_FAILED);
+  pool_.FlushWithError(ERR_FAILED, "Very good reason");
   EXPECT_THAT(request(kMaxSockets)->WaitForResult(), IsError(ERR_FAILED));
 }
 
@@ -985,7 +985,7 @@
   for (int i = 0; i < kMaxSockets + 1; ++i) {
     EXPECT_THAT(StartRequest(kDefaultPriority), IsError(ERR_IO_PENDING));
   }
-  pool_.FlushWithError(ERR_FAILED);
+  pool_.FlushWithError(ERR_FAILED, "Very good reason");
   host_resolver_->set_synchronous_mode(true);
   EXPECT_THAT(StartRequest(kDefaultPriority), IsOk());
 }
@@ -1011,7 +1011,7 @@
   // Now we have one socket in STATE_TRANSPORT_CONNECT and the rest in
   // STATE_OBTAIN_LOCK. If any of the sockets in STATE_OBTAIN_LOCK is given the
   // lock, they will synchronously connect.
-  pool_.FlushWithError(ERR_FAILED);
+  pool_.FlushWithError(ERR_FAILED, "Very good reason");
   for (int i = 0; i < kMaxSockets; ++i) {
     EXPECT_THAT(request(i)->WaitForResult(), IsError(ERR_FAILED));
   }
@@ -1053,7 +1053,7 @@
       TransportConnectJob::kIPv6FallbackTimerInMs));
   // Now we have |kMaxSockets| IPv6 sockets and one IPv4 socket stalled in
   // connect, and |kMaxSockets - 1| IPv4 sockets waiting for the endpoint lock.
-  pool_.FlushWithError(ERR_FAILED);
+  pool_.FlushWithError(ERR_FAILED, "Very good reason");
   for (int i = 0; i < kMaxSockets; ++i) {
     EXPECT_THAT(request(i)->WaitForResult(), IsError(ERR_FAILED));
   }
@@ -1075,7 +1075,7 @@
 
   EXPECT_THAT(StartRequest(kDefaultPriority), IsError(ERR_IO_PENDING));
   // Now we have one socket handed out, and one pending.
-  pool_.FlushWithError(ERR_FAILED);
+  pool_.FlushWithError(ERR_FAILED, "Very good reason");
   EXPECT_THAT(request(1)->WaitForResult(), IsError(ERR_FAILED));
   // Socket owned by ClientSocketHandle is unaffected:
   EXPECT_TRUE(request(0)->handle()->socket());
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc
index 2bd864fa..622478f 100644
--- a/net/url_request/url_request_unittest.cc
+++ b/net/url_request/url_request_unittest.cc
@@ -8986,7 +8986,7 @@
   // Close all connections and clear the session cache to force a new handshake.
   default_context_.http_transaction_factory()
       ->GetSession()
-      ->CloseAllConnections();
+      ->CloseAllConnections(ERR_FAILED, "Very good reason");
   default_context_.http_transaction_factory()
       ->GetSession()
       ->ClearSSLSessionCache();
@@ -9068,7 +9068,7 @@
   // Close all connections and clear the session cache to force a new handshake.
   default_context_.http_transaction_factory()
       ->GetSession()
-      ->CloseAllConnections();
+      ->CloseAllConnections(ERR_FAILED, "Very good reason");
   default_context_.http_transaction_factory()
       ->GetSession()
       ->ClearSSLSessionCache();
@@ -9148,7 +9148,7 @@
   // Close all connections and clear the session cache to force a new handshake.
   default_context_.http_transaction_factory()
       ->GetSession()
-      ->CloseAllConnections();
+      ->CloseAllConnections(ERR_FAILED, "Very good reason");
   default_context_.http_transaction_factory()
       ->GetSession()
       ->ClearSSLSessionCache();
@@ -9210,8 +9210,8 @@
     EXPECT_EQ(1, d.response_started_count());
   }
 
-  reinterpret_cast<HttpCache*>(default_context_.http_transaction_factory())->
-    CloseAllConnections();
+  reinterpret_cast<HttpCache*>(default_context_.http_transaction_factory())
+      ->CloseAllConnections(ERR_FAILED, "Very good reason");
 
   {
     TestDelegate d;
@@ -9508,8 +9508,8 @@
     EXPECT_EQ(1, d.response_started_count());
   }
 
-  reinterpret_cast<HttpCache*>(default_context_.http_transaction_factory())->
-    CloseAllConnections();
+  reinterpret_cast<HttpCache*>(default_context_.http_transaction_factory())
+      ->CloseAllConnections(ERR_FAILED, "Very good reason");
 
   // Now change the certificate to be acceptable (so that the response is
   // loaded), and ensure that no session id is presented to the peer.
@@ -9589,6 +9589,22 @@
 // generates.
 static const char kOCSPTestCertPolicy[] = "1.3.6.1.4.1.11129.2.4.1";
 
+// Interceptor to check that secure DNS has been disabled.
+class SecureDnsInterceptor : public net::URLRequestInterceptor {
+ public:
+  SecureDnsInterceptor() = default;
+  ~SecureDnsInterceptor() override = default;
+
+ private:
+  // URLRequestInterceptor implementation:
+  net::URLRequestJob* MaybeInterceptRequest(
+      net::URLRequest* request,
+      net::NetworkDelegate* network_delegate) const override {
+    EXPECT_TRUE(request->disable_secure_dns());
+    return nullptr;
+  }
+};
+
 class HTTPSOCSPTest : public HTTPSRequestTest {
  public:
   HTTPSOCSPTest()
@@ -9609,6 +9625,9 @@
     cert_net_fetcher_->SetURLRequestContext(&context_);
     context_.cert_verifier()->SetConfig(GetCertVerifierConfig());
 
+    net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
+        "http", "127.0.0.1", std::make_unique<SecureDnsInterceptor>());
+
     scoped_refptr<X509Certificate> root_cert =
         ImportCertFromFile(GetTestCertsDirectory(), "ocsp-test-root.pem");
     ASSERT_TRUE(root_cert);
@@ -9619,6 +9638,10 @@
 #endif
   }
 
+  void TearDown() override {
+    net::URLRequestFilter::GetInstance()->ClearHandlers();
+  }
+
   void DoConnectionWithDelegate(
       const SpawnedTestServer::SSLOptions& ssl_options,
       TestDelegate* delegate,
@@ -11789,7 +11812,8 @@
     EXPECT_EQ("0", d.data_received());
   }
 
-  context_.http_transaction_factory()->GetSession()->CloseAllConnections();
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
 
   // 0-RTT inherently involves a race condition: if the server responds with the
   // ServerHello before the client sends the HTTP request (the client may be
@@ -11855,7 +11879,8 @@
     EXPECT_EQ("0", d.data_received());
   }
 
-  context_.http_transaction_factory()->GetSession()->CloseAllConnections();
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
 
   {
     TestDelegate d;
@@ -11931,7 +11956,8 @@
     EXPECT_FALSE(sent_425);
   }
 
-  context_.http_transaction_factory()->GetSession()->CloseAllConnections();
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
 
   // 0-RTT inherently involves a race condition: if the server responds with the
   // ServerHello before the client sends the HTTP request (the client may be
@@ -12001,7 +12027,8 @@
     EXPECT_EQ("0", d.data_received());
   }
 
-  context_.http_transaction_factory()->GetSession()->CloseAllConnections();
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
 
   // The certificate in the resumption is changed to confirm that the
   // certificate change is observed.
@@ -12065,7 +12092,8 @@
     EXPECT_EQ("0", d.data_received());
   }
 
-  context_.http_transaction_factory()->GetSession()->CloseAllConnections();
+  context_.http_transaction_factory()->GetSession()->CloseAllConnections(
+      ERR_FAILED, "Very good reason");
 
   // The certificate in the resumption is changed to confirm that the
   // certificate change is observed.
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 591d4ae7..4f37aaed 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -697,7 +697,9 @@
   DCHECK(http_session);
 
   http_session->http_auth_cache()->ClearEntriesAddedSince(start_time);
-  http_session->CloseAllConnections();
+  // TODO(mmenke): Use another error code for this, as ERR_ABORTED has somewhat
+  // magical handling with respect to navigations.
+  http_session->CloseAllConnections(net::ERR_ABORTED, "Clearing auth cache");
 
   std::move(callback).Run();
 }
@@ -886,7 +888,10 @@
       url_request_context_->http_transaction_factory()->GetSession();
   DCHECK(http_session);
 
-  http_session->CloseAllConnections();
+  // TODO(mmenke): Use another error code for this, as ERR_ABORTED has somewhat
+  // magical handling with respect to navigations.
+  http_session->CloseAllConnections(net::ERR_ABORTED,
+                                    "Embedder closing all connections");
 
   std::move(callback).Run();
 }
@@ -897,7 +902,7 @@
       url_request_context_->http_transaction_factory()->GetSession();
   DCHECK(http_session);
 
-  http_session->CloseIdleConnections();
+  http_session->CloseIdleConnections("Embedder closing idle connections");
 
   std::move(callback).Run();
 }
diff --git a/services/network/public/cpp/cert_verifier/cert_net_fetcher_test.cc b/services/network/public/cpp/cert_verifier/cert_net_fetcher_test.cc
index 0a9eac0b..bb90b17 100644
--- a/services/network/public/cpp/cert_verifier/cert_net_fetcher_test.cc
+++ b/services/network/public/cpp/cert_verifier/cert_net_fetcher_test.cc
@@ -30,7 +30,9 @@
 
 CertNetFetcherTestUtilRealLoader::CertNetFetcherTestUtilRealLoader()
     : test_shared_url_loader_factory_(
-          base::MakeRefCounted<network::TestSharedURLLoaderFactory>()),
+          base::MakeRefCounted<network::TestSharedURLLoaderFactory>(
+              nullptr /* network_service */,
+              true /* is_trusted */)),
       receiver_(test_shared_url_loader_factory_.get(),
                 std::move(pending_receiver_)) {}
 }  // namespace cert_verifier
diff --git a/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader.cc b/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader.cc
index d02fbe2b..00f06b81 100644
--- a/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader.cc
+++ b/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader.cc
@@ -487,6 +487,10 @@
   request->url = request_params_->url;
   if (request_params_->http_method == HTTP_METHOD_POST)
     request->method = net::HttpRequestHeaders::kPostMethod;
+  // Disable secure DNS for hostname lookups triggered by certificate network
+  // fetches to prevent deadlock.
+  request->trusted_params = network::ResourceRequest::TrustedParams();
+  request->trusted_params->disable_secure_dns = true;
   request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   url_loader_ =
       network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
diff --git a/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader_unittest.cc b/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader_unittest.cc
index a26305e9..a4a894be 100644
--- a/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader_unittest.cc
+++ b/services/network/public/cpp/cert_verifier/cert_net_fetcher_url_loader_unittest.cc
@@ -24,6 +24,8 @@
 #include "net/test/gtest_util.h"
 #include "net/test/test_with_task_environment.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_interceptor.h"
 #include "services/network/public/cpp/cert_verifier/cert_net_fetcher_test.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
 #include "services/network/test/test_url_loader_factory.h"
@@ -44,6 +46,8 @@
 
 const char kMockURL[] = "http://mock.hanging.read/";
 
+const char kMockSecureDnsHostname[] = "mock.secure.dns.check";
+
 // Wait for the request to complete, and verify that it completed successfully
 // with the indicated bytes.
 void VerifySuccess(const std::string& expected_body,
@@ -197,6 +201,49 @@
   bool use_hanging_url_loader_ = false;
 };
 
+// Interceptor to check that secure DNS has been disabled.
+class SecureDnsInterceptor : public net::URLRequestInterceptor {
+ public:
+  explicit SecureDnsInterceptor(bool* invoked_interceptor)
+      : invoked_interceptor_(invoked_interceptor) {}
+  ~SecureDnsInterceptor() override = default;
+
+ private:
+  // URLRequestInterceptor implementation:
+  net::URLRequestJob* MaybeInterceptRequest(
+      net::URLRequest* request,
+      net::NetworkDelegate* network_delegate) const override {
+    EXPECT_TRUE(request->disable_secure_dns());
+    *invoked_interceptor_ = true;
+    return nullptr;
+  }
+
+  bool* invoked_interceptor_;
+};
+
+class CertNetFetcherURLLoaderTestWithSecureDnsInterceptor
+    : public CertNetFetcherURLLoaderTest,
+      public net::WithTaskEnvironment {
+ public:
+  CertNetFetcherURLLoaderTestWithSecureDnsInterceptor()
+      : invoked_interceptor_(false) {}
+
+  void SetUp() override {
+    net::URLRequestFilter::GetInstance()->AddHostnameInterceptor(
+        "http", kMockSecureDnsHostname,
+        std::make_unique<SecureDnsInterceptor>(&invoked_interceptor_));
+  }
+
+  void TearDown() override {
+    net::URLRequestFilter::GetInstance()->ClearHandlers();
+  }
+
+  bool invoked_interceptor() { return invoked_interceptor_; }
+
+ private:
+  bool invoked_interceptor_;
+};
+
 // Helper to start an AIA fetch using default parameters.
 WARN_UNUSED_RESULT std::unique_ptr<net::CertNetFetcher::Request> StartRequest(
     net::CertNetFetcher* fetcher,
@@ -600,6 +647,17 @@
   VerifyFailure(net::ERR_ABORTED, request.get());
 }
 
+TEST_F(CertNetFetcherURLLoaderTestWithSecureDnsInterceptor, SecureDnsDisabled) {
+  CreateFetcher();
+  std::unique_ptr<net::CertNetFetcher::Request> request = StartRequest(
+      fetcher(),
+      GURL("http://" + std::string(kMockSecureDnsHostname) + "/cert.crt"));
+  net::Error actual_error;
+  std::vector<uint8_t> actual_body;
+  request->WaitForResult(&actual_error, &actual_body);
+  EXPECT_TRUE(invoked_interceptor());
+}
+
 }  // namespace
 
 }  // namespace cert_verifier
diff --git a/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader.cc b/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader.cc
index 0a95dacb..7f55632 100644
--- a/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader.cc
+++ b/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader.cc
@@ -97,6 +97,10 @@
   request->url = params->url;
   request->credentials_mode = network::mojom::CredentialsMode::kOmit;
   request->load_flags = net::LOAD_DISABLE_CACHE;
+  // Disable secure DNS for hostname lookups triggered by certificate network
+  // fetches to prevent deadlock.
+  request->trusted_params = network::ResourceRequest::TrustedParams();
+  request->trusted_params->disable_secure_dns = true;
 
   if (!params->extra_request_headers.IsEmpty())
     request->headers = params->extra_request_headers;
diff --git a/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader_unittest.cc b/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader_unittest.cc
index 34b4fe0..334db62 100644
--- a/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader_unittest.cc
+++ b/services/network/public/cpp/cert_verifier/nss_ocsp_session_url_loader_unittest.cc
@@ -110,6 +110,8 @@
     old_interceptor_ = base::BindLambdaForTesting(
         [this](const network::ResourceRequest& request) {
           EXPECT_EQ(request.url, intercept_url_);
+          EXPECT_TRUE(request.trusted_params.has_value());
+          EXPECT_TRUE(request.trusted_params->disable_secure_dns);
           num_loaders_created_++;
         });
     loader_factory_->SetInterceptor(old_interceptor_);
@@ -469,6 +471,8 @@
     loader_factory_.SetInterceptor(base::BindLambdaForTesting(
         [this](const network::ResourceRequest& request) {
           EXPECT_EQ(request.url, intercept_url_);
+          EXPECT_TRUE(request.trusted_params.has_value());
+          EXPECT_TRUE(request.trusted_params->disable_secure_dns);
           num_loaders_created_++;
         }));
 
diff --git a/services/service_manager/sandbox/features.cc b/services/service_manager/sandbox/features.cc
index f6ba5969..c29946b6 100644
--- a/services/service_manager/sandbox/features.cc
+++ b/services/service_manager/sandbox/features.cc
@@ -20,16 +20,14 @@
 #endif  // defined(OS_WIN) || defined(OS_MACOSX)
 };
 
+#if !defined(OS_MACOSX)
 // Enables network service sandbox.
 // (Only causes an effect when feature kNetworkService is enabled.)
 const base::Feature kNetworkServiceSandbox {
   "NetworkServiceSandbox",
-#if defined(OS_MACOSX)
-      base::FEATURE_ENABLED_BY_DEFAULT
-#else
       base::FEATURE_DISABLED_BY_DEFAULT
-#endif
 };
+#endif  // !defined(OS_MACOSX)
 
 #if defined(OS_WIN)
 // Emergency "off switch" for new Windows sandbox security mitigation,
diff --git a/services/service_manager/sandbox/features.h b/services/service_manager/sandbox/features.h
index 6ab993e..86735e2 100644
--- a/services/service_manager/sandbox/features.h
+++ b/services/service_manager/sandbox/features.h
@@ -17,8 +17,10 @@
 
 SERVICE_MANAGER_SANDBOX_EXPORT extern const base::Feature kAudioServiceSandbox;
 
+#if !defined(OS_MACOSX)
 SERVICE_MANAGER_SANDBOX_EXPORT extern const base::Feature
     kNetworkServiceSandbox;
+#endif
 
 #if defined(OS_WIN)
 SERVICE_MANAGER_SANDBOX_EXPORT extern const base::Feature
diff --git a/services/service_manager/sandbox/sandbox_type.cc b/services/service_manager/sandbox/sandbox_type.cc
index fbfb38b1c..4bfcee3c 100644
--- a/services/service_manager/sandbox/sandbox_type.cc
+++ b/services/service_manager/sandbox/sandbox_type.cc
@@ -29,8 +29,12 @@
     case SandboxType::kAudio:
       return !IsAudioSandboxEnabled();
     case SandboxType::kNetwork:
+#if defined(OS_MACOSX)
+      return false;
+#else
       return !base::FeatureList::IsEnabled(
           service_manager::features::kNetworkServiceSandbox);
+#endif  // defined(OS_MACOSX)
     case SandboxType::kInvalid:
     case SandboxType::kRenderer:
     case SandboxType::kUtility:
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index cd374307..0c172c26 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3313,21 +3313,6 @@
             ]
         }
     ],
-    "NetworkServiceSandboxMac": [
-        {
-            "platforms": [
-                "mac"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "NetworkServiceSandbox"
-                    ]
-                }
-            ]
-        }
-    ],
     "NewTabInProductHelp": [
         {
             "platforms": [
diff --git a/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc
index 8a6696f..3041ff6 100644
--- a/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc
+++ b/third_party/blink/renderer/controller/user_level_memory_pressure_signal_generator_test.cc
@@ -137,7 +137,13 @@
   }
 }
 
-TEST_F(UserLevelMemoryPressureSignalGeneratorTest, GenerationPauses) {
+// Flaky on Android, see crbug/1058178.
+#if defined(OS_ANDROID)
+#define MAYBE_GenerationPauses DISABLED_GenerationPauses
+#else
+#define MAYBE_GenerationPauses GenerationPauses
+#endif
+TEST_F(UserLevelMemoryPressureSignalGeneratorTest, MAYBE_GenerationPauses) {
   {
     std::unique_ptr<MockMemoryUsageMonitor> mock_memory_usage_monitor =
         std::make_unique<MockMemoryUsageMonitor>();
diff --git a/third_party/blink/renderer/core/dom/container_node.cc b/third_party/blink/renderer/core/dom/container_node.cc
index 7f74824..b946eea9 100644
--- a/third_party/blink/renderer/core/dom/container_node.cc
+++ b/third_party/blink/renderer/core/dom/container_node.cc
@@ -810,6 +810,11 @@
     GetDocument().NodeChildrenWillBeRemoved(*this);
   }
 
+  HeapVector<Member<Node>>* removed_nodes = nullptr;
+  if (ChildrenChangedAllChildrenRemovedNeedsList()) {
+    removed_nodes =
+        MakeGarbageCollected<HeapVector<Member<Node>>>(CountChildren());
+  }
   {
     HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose;
     TreeOrderedMap::RemoveScope tree_remove_scope;
@@ -822,6 +827,8 @@
       while (Node* child = first_child_) {
         RemoveBetween(nullptr, child->nextSibling(), *child);
         NotifyNodeRemoved(*child);
+        if (removed_nodes)
+          removed_nodes->push_back(child);
       }
     }
 
@@ -830,7 +837,7 @@
                              nullptr,
                              nullptr,
                              nullptr,
-                             nullptr};
+                             removed_nodes};
     ChildrenChanged(change);
   }
 
@@ -1036,6 +1043,10 @@
     inserted_node->SetStyleChangeOnInsertion();
 }
 
+bool ContainerNode::ChildrenChangedAllChildrenRemovedNeedsList() const {
+  return false;
+}
+
 void ContainerNode::CloneChildNodesFrom(const ContainerNode& node) {
   for (const Node& child : NodeTraversal::ChildrenOf(node))
     AppendChild(child.Clone(GetDocument(), CloneChildrenFlag::kClone));
diff --git a/third_party/blink/renderer/core/dom/container_node.h b/third_party/blink/renderer/core/dom/container_node.h
index 24ee041..c3582de6 100644
--- a/third_party/blink/renderer/core/dom/container_node.h
+++ b/third_party/blink/renderer/core/dom/container_node.h
@@ -366,15 +366,23 @@
     //  - siblingChanged.nextSibling after single node insertion
     //  - nextSibling of the last inserted node after multiple node insertion.
     Node* sibling_after_change = nullptr;
-    // TODO(crbug.com/1056094): This field is not used yet.
+    // List of removed nodes for ChildrenChangeType::kAllChildrenRemoved.
+    // This is available only if ChildrenChangedAllChildrenRemovedNeedsList()
+    // returns true.
     HeapVector<Member<Node>>* removed_nodes;
   };
 
   // Notifies the node that it's list of children have changed (either by adding
   // or removing child nodes), or a child node that is of the type
-  // CDATA_SECTION_NODE, TEXT_NODE or COMMENT_NODE has changed its value.
+  // kCdataSectionNode, kTextNode or kCommentNode has changed its value.
+  //
+  // ChildrenChanged() implementations may modify the DOM tree, and may dispatch
+  // synchronous events.
   virtual void ChildrenChanged(const ChildrenChange&);
 
+  // Provides ChildrenChange::removed_nodes for kAllChildrenRemoved.
+  virtual bool ChildrenChangedAllChildrenRemovedNeedsList() const;
+
   virtual bool ChildrenCanHaveStyle() const { return true; }
 
   void Trace(Visitor*) override;
diff --git a/third_party/blink/renderer/core/dom/node.h b/third_party/blink/renderer/core/dom/node.h
index e9b73073..1cb6f6a 100644
--- a/third_party/blink/renderer/core/dom/node.h
+++ b/third_party/blink/renderer/core/dom/node.h
@@ -767,16 +767,20 @@
   //
   // Blink notifies this callback regardless if the subtree of the node is a
   // document tree or a floating subtree.  Implementation can determine the type
-  // of subtree by seeing insertionPoint->isConnected().  For a performance
+  // of subtree by seeing insertion_point->isConnected().  For a performance
   // reason, notifications are delivered only to ContainerNode subclasses if the
-  // insertionPoint is out of document.
+  // insertion_point is out of document.
   //
-  // There are another callback named didNotifySubtreeInsertionsToDocument(),
+  // There are another callback named DidNotifySubtreeInsertionsToDocument(),
   // which is called after all the descendant is notified, if this node was
   // inserted into the document tree. Only a few subclasses actually need
   // this. To utilize this, the node should return
-  // InsertionShouldCallDidNotifySubtreeInsertions from insertedInto().
+  // kInsertionShouldCallDidNotifySubtreeInsertions from InsertedInto().
   //
+  // InsertedInto() implementations must not modify the DOM tree, and must not
+  // dispatch synchronous events. On the other hand,
+  // DidNotifySubtreeInsertionsToDocument() may modify the DOM tree, and may
+  // dispatch synchronous events.
   enum InsertionNotificationRequest {
     kInsertionDone,
     kInsertionShouldCallDidNotifySubtreeInsertions
@@ -788,10 +792,12 @@
 
   // Notifies the node that it is no longer part of the tree.
   //
-  // This is a dual of insertedInto(), and is similar to the
+  // This is a dual of InsertedInto(), and is similar to the
   // DOMNodeRemovedFromDocument DOM event, but does not require the overhead of
   // event dispatching, and is called _after_ the node is removed from the tree.
   //
+  // RemovedFrom() implementations must not modify the DOM tree, and must not
+  // dispatch synchronous events.
   virtual void RemovedFrom(ContainerNode& insertion_point);
 
   // FIXME(dominicc): This method is not debug-only--it is used by
diff --git a/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc b/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc
index 3c09fa5..ae6b2f6 100644
--- a/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_opt_group_element.cc
@@ -84,6 +84,30 @@
   return !IsDisabledFormControl();
 }
 
+void HTMLOptGroupElement::ChildrenChanged(const ChildrenChange& change) {
+  HTMLElement::ChildrenChanged(change);
+  auto* select = OwnerSelectElement();
+  if (!select)
+    return;
+  if (change.type == ChildrenChangeType::kElementInserted) {
+    if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed))
+      select->OptionInserted(*option, option->Selected());
+  } else if (change.type == ChildrenChangeType::kElementRemoved) {
+    if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed))
+      select->OptionRemoved(*option);
+  } else if (change.type == ChildrenChangeType::kAllChildrenRemoved) {
+    DCHECK(change.removed_nodes);
+    for (Node* node : *change.removed_nodes) {
+      if (auto* option = DynamicTo<HTMLOptionElement>(node))
+        select->OptionRemoved(*option);
+    }
+  }
+}
+
+bool HTMLOptGroupElement::ChildrenChangedAllChildrenRemovedNeedsList() const {
+  return true;
+}
+
 Node::InsertionNotificationRequest HTMLOptGroupElement::InsertedInto(
     ContainerNode& insertion_point) {
   HTMLElement::InsertedInto(insertion_point);
diff --git a/third_party/blink/renderer/core/html/forms/html_opt_group_element.h b/third_party/blink/renderer/core/html/forms/html_opt_group_element.h
index 693aa166..a03db9b 100644
--- a/third_party/blink/renderer/core/html/forms/html_opt_group_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_opt_group_element.h
@@ -52,6 +52,8 @@
   ~HTMLOptGroupElement() override;
 
   bool SupportsFocus() const override;
+  void ChildrenChanged(const ChildrenChange& change) override;
+  bool ChildrenChangedAllChildrenRemovedNeedsList() const override;
   void ParseAttribute(const AttributeModificationParams&) override;
   void AccessKeyAction(bool send_mouse_events) override;
   void DidAddUserAgentShadowRoot(ShadowRoot&) override;
diff --git a/third_party/blink/renderer/core/html/forms/html_option_element.cc b/third_party/blink/renderer/core/html/forms/html_option_element.cc
index 9ee3ba0..bce8b9e 100644
--- a/third_party/blink/renderer/core/html/forms/html_option_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_option_element.cc
@@ -339,30 +339,6 @@
   return String();
 }
 
-Node::InsertionNotificationRequest HTMLOptionElement::InsertedInto(
-    ContainerNode& insertion_point) {
-  HTMLElement::InsertedInto(insertion_point);
-  if (HTMLSelectElement* select = OwnerSelectElement()) {
-    if (&insertion_point == select ||
-        (IsA<HTMLOptGroupElement>(insertion_point) &&
-         insertion_point.parentNode() == select))
-      select->OptionInserted(*this, is_selected_);
-  }
-  return kInsertionDone;
-}
-
-void HTMLOptionElement::RemovedFrom(ContainerNode& insertion_point) {
-  if (auto* select = DynamicTo<HTMLSelectElement>(insertion_point)) {
-    if (!parentNode() || IsA<HTMLOptGroupElement>(*parentNode()))
-      select->OptionRemoved(*this);
-  } else if (IsA<HTMLOptGroupElement>(insertion_point)) {
-    select = DynamicTo<HTMLSelectElement>(insertion_point.parentNode());
-    if (select)
-      select->OptionRemoved(*this);
-  }
-  HTMLElement::RemovedFrom(insertion_point);
-}
-
 String HTMLOptionElement::CollectOptionInnerText() const {
   StringBuilder text;
   for (Node* node = firstChild(); node;) {
diff --git a/third_party/blink/renderer/core/html/forms/html_option_element.h b/third_party/blink/renderer/core/html/forms/html_option_element.h
index ce663f6..b627a08 100644
--- a/third_party/blink/renderer/core/html/forms/html_option_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_option_element.h
@@ -101,8 +101,6 @@
   bool MatchesDefaultPseudoClass() const override;
   bool MatchesEnabledPseudoClass() const override;
   void ParseAttribute(const AttributeModificationParams&) override;
-  InsertionNotificationRequest InsertedInto(ContainerNode&) override;
-  void RemovedFrom(ContainerNode&) override;
   void AccessKeyAction(bool) override;
   void ChildrenChanged(const ChildrenChange&) override;
 
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index 8367354..1c58e29 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -866,6 +866,37 @@
     ResetToDefaultSelection();
 }
 
+void HTMLSelectElement::ChildrenChanged(const ChildrenChange& change) {
+  HTMLFormControlElementWithState::ChildrenChanged(change);
+  if (change.type == ChildrenChangeType::kElementInserted) {
+    if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) {
+      OptionInserted(*option, option->Selected());
+    } else if (auto* optgroup =
+                   DynamicTo<HTMLOptGroupElement>(change.sibling_changed)) {
+      for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup))
+        OptionInserted(option, option.Selected());
+    }
+  } else if (change.type == ChildrenChangeType::kElementRemoved) {
+    if (auto* option = DynamicTo<HTMLOptionElement>(change.sibling_changed)) {
+      OptionRemoved(*option);
+    } else if (auto* optgroup =
+                   DynamicTo<HTMLOptGroupElement>(change.sibling_changed)) {
+      for (auto& option : Traversal<HTMLOptionElement>::ChildrenOf(*optgroup))
+        OptionRemoved(option);
+    }
+  } else if (change.type == ChildrenChangeType::kAllChildrenRemoved) {
+    DCHECK(change.removed_nodes);
+    for (Node* node : *change.removed_nodes) {
+      if (auto* option = DynamicTo<HTMLOptionElement>(node))
+        OptionRemoved(*option);
+    }
+  }
+}
+
+bool HTMLSelectElement::ChildrenChangedAllChildrenRemovedNeedsList() const {
+  return true;
+}
+
 void HTMLSelectElement::OptionInserted(HTMLOptionElement& option,
                                        bool option_is_selected) {
   DCHECK_EQ(option.OwnerSelectElement(), this);
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.h b/third_party/blink/renderer/core/html/forms/html_select_element.h
index 6f4579b3..ea84c20 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.h
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.h
@@ -212,6 +212,8 @@
   FormControlState SaveFormControlState() const override;
   void RestoreFormControlState(const FormControlState&) override;
 
+  void ChildrenChanged(const ChildrenChange& change) override;
+  bool ChildrenChangedAllChildrenRemovedNeedsList() const override;
   void ParseAttribute(const AttributeModificationParams&) override;
   bool IsPresentationAttribute(const QualifiedName&) const override;
 
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element_test.cc b/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
index df8adbc..f250ea8 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element_test.cc
@@ -459,4 +459,14 @@
   ASSERT_TRUE(select->GetLayoutObject());
 }
 
+TEST_F(HTMLSelectElementTest, SlotAssignmentRecalcDuringOptionRemoval) {
+  // crbug.com/1056094
+  // This test passes if no CHECK failure about IsSlotAssignmentRecalcForbidden.
+  SetHtmlInnerHTML("<div dir=auto><select><option>option0");
+  auto* select = GetDocument().body()->firstChild()->firstChild();
+  auto* option = select->firstChild();
+  select->appendChild(option);
+  option->remove();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
index 3799f403..ee055e2 100644
--- a/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_page_agent.cc
@@ -932,10 +932,12 @@
 
 void InspectorPageAgent::FrameStartedLoading(LocalFrame* frame) {
   GetFrontend()->frameStartedLoading(IdentifiersFactory::FrameId(frame));
+  GetFrontend()->flush();
 }
 
 void InspectorPageAgent::FrameStoppedLoading(LocalFrame* frame) {
   GetFrontend()->frameStoppedLoading(IdentifiersFactory::FrameId(frame));
+  GetFrontend()->flush();
 }
 
 void InspectorPageAgent::FrameRequestedNavigation(
@@ -945,6 +947,7 @@
   GetFrontend()->frameRequestedNavigation(
       IdentifiersFactory::FrameId(target_frame),
       ClientNavigationReasonToProtocol(reason), url.GetString());
+  GetFrontend()->flush();
 }
 
 void InspectorPageAgent::FrameScheduledNavigation(
@@ -955,11 +958,13 @@
   GetFrontend()->frameScheduledNavigation(
       IdentifiersFactory::FrameId(frame), delay.InSecondsF(),
       ClientNavigationReasonToProtocol(reason), url.GetString());
+  GetFrontend()->flush();
 }
 
 void InspectorPageAgent::FrameClearedScheduledNavigation(LocalFrame* frame) {
   GetFrontend()->frameClearedScheduledNavigation(
       IdentifiersFactory::FrameId(frame));
+  GetFrontend()->flush();
 }
 
 void InspectorPageAgent::WillRunJavaScriptDialog() {
@@ -992,6 +997,7 @@
   GetFrontend()->lifecycleEvent(IdentifiersFactory::FrameId(frame),
                                 IdentifiersFactory::LoaderId(loader), name,
                                 timestamp);
+  GetFrontend()->flush();
 }
 
 void InspectorPageAgent::PaintTiming(Document* document,
@@ -1028,6 +1034,7 @@
   GetFrontend()->windowOpen(completed_url.GetString(), window_name,
                             GetEnabledWindowFeatures(window_features),
                             user_gesture);
+  GetFrontend()->flush();
 }
 
 std::unique_ptr<protocol::Page::Frame> InspectorPageAgent::BuildObjectForFrame(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc
index 2737d6fa..a2f8092 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.cc
@@ -133,7 +133,7 @@
     DCHECK(item);
     if (item->IsText()) {
       PhysicalRect child_scroll_overflow = item->RectInContainerBlock();
-      if (UNLIKELY(has_hanging_)) {
+      if (UNLIKELY(has_hanging)) {
         AdjustScrollableOverflowForHanging(line.RectInContainerBlock(),
                                            container_writing_mode,
                                            &child_scroll_overflow);
diff --git a/third_party/blink/renderer/core/testing/dictionary_test.cc b/third_party/blink/renderer/core/testing/dictionary_test.cc
index d7d1c55..0eaeccb7 100644
--- a/third_party/blink/renderer/core/testing/dictionary_test.cc
+++ b/third_party/blink/renderer/core/testing/dictionary_test.cc
@@ -14,26 +14,6 @@
 #include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
 
 namespace blink {
-namespace {
-ScriptIterator GetIterator(const Dictionary& iterable,
-                           ExecutionContext* execution_context) {
-  v8::Local<v8::Value> iterator_getter;
-  v8::Isolate* isolate = iterable.GetIsolate();
-  if (!iterable.Get(v8::Symbol::GetIterator(isolate), iterator_getter) ||
-      !iterator_getter->IsFunction()) {
-    return ScriptIterator();
-  }
-  v8::Local<v8::Value> iterator;
-  if (!V8ScriptRunner::CallFunction(
-           v8::Local<v8::Function>::Cast(iterator_getter), execution_context,
-           iterable.V8Value(), 0, nullptr, isolate)
-           .ToLocal(&iterator))
-    return ScriptIterator();
-  if (!iterator->IsObject())
-    return ScriptIterator();
-  return ScriptIterator(isolate, v8::Local<v8::Object>::Cast(iterator));
-}
-}  // namespace
 
 DictionaryTest::DictionaryTest() : required_boolean_member_(false) {}
 
@@ -157,36 +137,6 @@
   return result;
 }
 
-String DictionaryTest::stringFromIterable(
-    ScriptState* script_state,
-    Dictionary iterable,
-    ExceptionState& exception_state) const {
-  StringBuilder result;
-  ExecutionContext* execution_context = ExecutionContext::From(script_state);
-  ScriptIterator iterator = GetIterator(iterable, execution_context);
-  if (iterator.IsNull())
-    return g_empty_string;
-
-  bool first_loop = true;
-  while (iterator.Next(execution_context, exception_state)) {
-    if (exception_state.HadException())
-      return g_empty_string;
-
-    if (first_loop)
-      first_loop = false;
-    else
-      result.Append(',');
-
-    v8::Local<v8::Value> value;
-    if (iterator.GetValue().ToLocal(&value)) {
-      result.Append(ToCoreString(
-          value->ToString(script_state->GetContext()).ToLocalChecked()));
-    }
-  }
-
-  return result.ToString();
-}
-
 void DictionaryTest::Reset() {
   long_member_ = base::nullopt;
   long_member_with_clamp_ = base::nullopt;
diff --git a/third_party/blink/renderer/core/testing/dictionary_test.h b/third_party/blink/renderer/core/testing/dictionary_test.h
index c514473e..4a9de65 100644
--- a/third_party/blink/renderer/core/testing/dictionary_test.h
+++ b/third_party/blink/renderer/core/testing/dictionary_test.h
@@ -44,10 +44,6 @@
   void setDerivedDerived(const InternalDictionaryDerivedDerived*);
   InternalDictionaryDerivedDerived* getDerivedDerived();
 
-  String stringFromIterable(ScriptState*,
-                            Dictionary iterable,
-                            ExceptionState&) const;
-
   void Trace(Visitor*) override;
 
  private:
diff --git a/third_party/blink/renderer/core/testing/dictionary_test.idl b/third_party/blink/renderer/core/testing/dictionary_test.idl
index 3d13200..dd29ba6 100644
--- a/third_party/blink/renderer/core/testing/dictionary_test.idl
+++ b/third_party/blink/renderer/core/testing/dictionary_test.idl
@@ -12,6 +12,4 @@
 
     void setDerivedDerived(InternalDictionaryDerivedDerived derived);
     InternalDictionaryDerivedDerived getDerivedDerived();
-
-    [CallWith=ScriptState, RaisesException] DOMString stringFromIterable(Dictionary iterableDictionary);
 };
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
index adb0a21..7b5ebdd 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.cc
@@ -112,7 +112,9 @@
       modification_count_(0),
       validation_message_axid_(0),
       relation_cache_(std::make_unique<AXRelationCache>(this)),
-      accessibility_event_permission_(mojom::PermissionStatus::ASK) {
+      accessibility_event_permission_(mojom::blink::PermissionStatus::ASK),
+      permission_service_(document.ToExecutionContext()),
+      permission_observer_receiver_(this, document.ToExecutionContext()) {
   if (document_->LoadEventFinished())
     AddPermissionStatusListener();
   documents_.insert(&document);
@@ -1839,14 +1841,18 @@
   if (permission_service_.is_bound())
     permission_service_.reset();
 
-  ConnectToPermissionService(document_->GetExecutionContext(),
-                             permission_service_.BindNewPipeAndPassReceiver());
+  ConnectToPermissionService(
+      document_->GetExecutionContext(),
+      permission_service_.BindNewPipeAndPassReceiver(
+          document_->GetTaskRunner(TaskType::kUserInteraction)));
 
   if (permission_observer_receiver_.is_bound())
     permission_observer_receiver_.reset();
 
   mojo::PendingRemote<mojom::blink::PermissionObserver> observer;
-  permission_observer_receiver_.Bind(observer.InitWithNewPipeAndPassReceiver());
+  permission_observer_receiver_.Bind(
+      observer.InitWithNewPipeAndPassReceiver(),
+      document_->GetTaskRunner(TaskType::kUserInteraction));
   permission_service_->AddPermissionObserver(
       CreatePermissionDescriptor(
           mojom::blink::PermissionName::ACCESSIBILITY_EVENTS),
@@ -1866,7 +1872,7 @@
   if (accessibility_event_permission_ != mojom::PermissionStatus::ASK)
     return;
 
-  if (!permission_service_)
+  if (!permission_service_.is_bound())
     return;
 
   permission_service_->RequestPermission(
@@ -1887,10 +1893,7 @@
   }
 }
 
-void AXObjectCacheImpl::ContextDestroyed() {
-  permission_service_.reset();
-  permission_observer_receiver_.reset();
-}
+void AXObjectCacheImpl::ContextDestroyed() {}
 
 void AXObjectCacheImpl::Trace(Visitor* visitor) {
   visitor->Trace(document_);
@@ -1899,6 +1902,8 @@
 
   visitor->Trace(objects_);
   visitor->Trace(notifications_to_post_);
+  visitor->Trace(permission_service_);
+  visitor->Trace(permission_observer_receiver_);
   visitor->Trace(documents_);
   visitor->Trace(tree_update_callback_queue_);
   AXObjectCache::Trace(visitor);
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
index 404be7b..c6bddb6 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
+++ b/third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h
@@ -33,8 +33,6 @@
 #include <utility>
 
 #include "base/macros.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-blink-forward.h"
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-blink.h"
@@ -44,6 +42,8 @@
 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
 #include "third_party/blink/renderer/modules/accessibility/ax_object.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
@@ -401,9 +401,9 @@
   mojom::PermissionStatus accessibility_event_permission_;
   // The permission service, enabling us to check for event listener
   // permission.
-  mojo::Remote<mojom::blink::PermissionService> permission_service_;
-  mojo::Receiver<mojom::blink::PermissionObserver>
-      permission_observer_receiver_{this};
+  HeapMojoRemote<mojom::blink::PermissionService> permission_service_;
+  HeapMojoReceiver<mojom::blink::PermissionObserver>
+      permission_observer_receiver_;
 
   // The main document, plus any page popups.
   HeapHashSet<WeakMember<Document>> documents_;
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.cc b/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.cc
index bb0f922..16a7f5e5 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.cc
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.cc
@@ -37,10 +37,16 @@
 
 BackgroundFetchBridge::BackgroundFetchBridge(
     ServiceWorkerRegistration& registration)
-    : Supplement<ServiceWorkerRegistration>(registration) {}
+    : Supplement<ServiceWorkerRegistration>(registration),
+      background_fetch_service_(registration.GetExecutionContext()) {}
 
 BackgroundFetchBridge::~BackgroundFetchBridge() = default;
 
+void BackgroundFetchBridge::Trace(Visitor* visitor) {
+  visitor->Trace(background_fetch_service_);
+  Supplement::Trace(visitor);
+}
+
 void BackgroundFetchBridge::GetIconDisplaySize(
     GetIconDisplaySizeCallback callback) {
   GetService()->GetIconDisplaySize(std::move(callback));
@@ -92,7 +98,7 @@
 }
 
 mojom::blink::BackgroundFetchService* BackgroundFetchBridge::GetService() {
-  if (!background_fetch_service_) {
+  if (!background_fetch_service_.is_bound()) {
     auto receiver = background_fetch_service_.BindNewPipeAndPassReceiver(
         GetSupplementable()->GetExecutionContext()->GetTaskRunner(
             TaskType::kBackgroundFetch));
diff --git a/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h b/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h
index 7c73538e..76a75654 100644
--- a/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h
+++ b/third_party/blink/renderer/modules/background_fetch/background_fetch_bridge.h
@@ -8,10 +8,10 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/background_fetch/background_fetch.mojom-blink.h"
 #include "third_party/blink/renderer/modules/service_worker/service_worker_registration.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
+#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
@@ -44,6 +44,7 @@
 
   explicit BackgroundFetchBridge(ServiceWorkerRegistration& registration);
   virtual ~BackgroundFetchBridge();
+  void Trace(Visitor* visitor) override;
 
   // Creates a new Background Fetch registration identified by |developer_id|
   // for the sequence of |requests|. The |callback| will be invoked when the
@@ -80,7 +81,8 @@
       mojom::blink::BackgroundFetchError error,
       mojom::blink::BackgroundFetchRegistrationPtr registration_ptr);
 
-  mojo::Remote<mojom::blink::BackgroundFetchService> background_fetch_service_;
+  HeapMojoRemote<mojom::blink::BackgroundFetchService>
+      background_fetch_service_;
 
   DISALLOW_COPY_AND_ASSIGN(BackgroundFetchBridge);
 };
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
index 0dc2f20..ec2155f 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
@@ -271,8 +271,19 @@
   }
   webrtc_encoding.active = encoding->active();
   webrtc_encoding.bitrate_priority = PriorityToDouble(encoding->priority());
-  webrtc_encoding.network_priority =
-      PriorityToDouble(encoding->networkPriority());
+  // TODO(deadbeef): Make helper function once network_priority changes from a
+  // double to an enum.
+  if (encoding->networkPriority() == "very-low") {
+    webrtc_encoding.network_priority = webrtc::Priority::kVeryLow;
+  } else if (encoding->networkPriority() == "low") {
+    webrtc_encoding.network_priority = webrtc::Priority::kLow;
+  } else if (encoding->networkPriority() == "medium") {
+    webrtc_encoding.network_priority = webrtc::Priority::kMedium;
+  } else if (encoding->networkPriority() == "high") {
+    webrtc_encoding.network_priority = webrtc::Priority::kHigh;
+  } else {
+    NOTREACHED();
+  }
   if (encoding->hasMaxBitrate()) {
     webrtc_encoding.max_bitrate_bps = clampTo<int>(encoding->maxBitrate());
   }
@@ -408,8 +419,21 @@
     }
     encoding->setPriority(
         PriorityFromDouble(webrtc_encoding.bitrate_priority).c_str());
-    encoding->setNetworkPriority(
-        PriorityFromDouble(webrtc_encoding.network_priority).c_str());
+    // TODO(deadbeef): Make helper function and use switch statement once
+    // network_priority changes from a double to an enum.
+    std::string network_priority;
+    if (webrtc_encoding.network_priority == webrtc::Priority::kVeryLow) {
+      network_priority = "very-low";
+    } else if (webrtc_encoding.network_priority == webrtc::Priority::kLow) {
+      network_priority = "low";
+    } else if (webrtc_encoding.network_priority == webrtc::Priority::kMedium) {
+      network_priority = "medium";
+    } else if (webrtc_encoding.network_priority == webrtc::Priority::kHigh) {
+      network_priority = "high";
+    } else {
+      NOTREACHED();
+    }
+    encoding->setNetworkPriority(network_priority.c_str());
     if (webrtc_encoding.num_temporal_layers) {
       if (*webrtc_encoding.num_temporal_layers == 2) {
         encoding->setScalabilityMode("L1T2");
diff --git a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
index 9594f5fe..8e9c4c6 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
+++ b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.cc
@@ -250,7 +250,7 @@
   PostCrossThreadTask(
       *task_runner_, FROM_HERE,
       CrossThreadBindOnce(&AudioScheduledSourceHandler::NotifyEnded,
-                          WrapRefCounted(this)));
+                          AsWeakPtr()));
 }
 
 void AudioScheduledSourceHandler::NotifyEnded() {
diff --git a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.h b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.h
index 2bf257c..3cd126a 100644
--- a/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.h
+++ b/third_party/blink/renderer/modules/webaudio/audio_scheduled_source_node.h
@@ -30,6 +30,7 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_WEBAUDIO_AUDIO_SCHEDULED_SOURCE_NODE_H_
 
 #include <atomic>
+#include "base/memory/weak_ptr.h"
 #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
 #include "third_party/blink/renderer/modules/webaudio/audio_node.h"
 
@@ -38,7 +39,9 @@
 class BaseAudioContext;
 class AudioBus;
 
-class AudioScheduledSourceHandler : public AudioHandler {
+class AudioScheduledSourceHandler
+    : public AudioHandler,
+      public base::SupportsWeakPtr<AudioScheduledSourceHandler> {
  public:
   // These are the possible states an AudioScheduledSourceNode can be in:
   //
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set.h b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
index 17e6f67..5bf2569e 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set.h
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set.h
@@ -1059,34 +1059,78 @@
 
   NewLinkedHashSetNode& operator=(NewLinkedHashSetNode&& other) = default;
 
-  wtf_size_t GetNextIndexForFreeNode() const { return next_index_; }
+  wtf_size_t GetNextIndexForFreeNode() const {
+#if DCHECK_IS_ON()
+    DCHECK(!is_used);
+#endif
+    return next_index_;
+  }
 
-  wtf_size_t GetPrevIndexForUsedNode() const { return prev_index_; }
+  wtf_size_t GetPrevIndexForUsedNode() const {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
+    return prev_index_;
+  }
 
-  wtf_size_t GetNextIndexForUsedNode() const { return next_index_; }
+  wtf_size_t GetNextIndexForUsedNode() const {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
+    return next_index_;
+  }
 
-  const ValueArg& GetValueForUsedNode() const { return value_; }
+  const ValueArg& GetValueForUsedNode() const {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
+    return value_;
+  }
 
   void SetNextIndexForFreeNode(wtf_size_t next_index) {
+#if DCHECK_IS_ON()
+    DCHECK(!is_used);
+#endif
     next_index_ = next_index;
   }
 
   void SetPrevIndexForUsedNode(wtf_size_t prev_index) {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
     prev_index_ = prev_index;
   }
 
   void SetNextIndexForUsedNode(wtf_size_t next_index) {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
     next_index_ = next_index;
   }
 
-  void SetValueForUsedNode(const ValueArg& value) { value_ = value; }
+  void SetValueForUsedNode(const ValueArg& value) {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
+    value_ = value;
+  }
 
-  void SetValueForUsedNode(ValueArg&& value) { value_ = std::move(value); }
+  void SetValueForUsedNode(ValueArg&& value) {
+#if DCHECK_IS_ON()
+    DCHECK(is_used);
+#endif
+    value_ = std::move(value);
+  }
 
  private:
   wtf_size_t prev_index_ = 0;
   wtf_size_t next_index_ = 0;
   ValueArg value_ = HashTraits<ValueArg>::EmptyValue();
+
+#if DCHECK_IS_ON()
+ public:
+  bool is_used = true;
+#endif
 };
 
 // This class is yet experimental. Do not use this class.
@@ -1098,6 +1142,9 @@
 // The linked list is implementing in a vector (with links being indexes instead
 // of pointers), to simplify the move of backing during GC compaction.
 
+// TODO(keinakashima): implement NewLinkedHashTraits (now we cannot insert
+// deleted/empty value) and add it to template parameter
+
 template <typename ValueArg>
 class NewLinkedHashSet {
   USING_FAST_MALLOC(NewLinkedHashSet);
@@ -1141,11 +1188,18 @@
 
   const Value& back() const;
   void pop_back();
-  // TODO(keinakashima): implement find, Contains
+
+  // TODO(keinakashima): implement find, Contains after implementing iterator
 
   template <typename IncomingValueType>
   AddResult insert(IncomingValueType&&);
 
+  template <typename IncomingValueType>
+  AddResult AppendOrMoveToLast(IncomingValueType&&);
+
+  template <typename IncomingValueType>
+  AddResult PrependOrMoveToFirst(IncomingValueType&&);
+
   void erase(ValuePeekInType);
   // TODO(keinakashima): implement erase that has an iterator as an argument
   // TODO(keinakashima): implement clear, RemoveAll, Trace
@@ -1166,6 +1220,16 @@
     return free_head_index_;
   }
 
+  // Inserts new value before given position to a linked list in vector
+  // Returns a pointer to the stored value
+  template <typename IncomingValueType>
+  const Value* InsertValueBeforeNode(wtf_size_t, IncomingValueType&&);
+
+  // Erases the node with the given index from the used list
+  // and prepends it to the free list as a free node.
+  // At this time, its value is set to be empty.
+  void FreeUsedNode(wtf_size_t);
+
   HashMap<Value, wtf_size_t> value_to_index_;
   Vector<Node> nodes_;
   wtf_size_t free_head_index_ = anchor_index_;
@@ -1181,6 +1245,9 @@
   nodes_.push_back(Node());
 }
 
+// TODO(keinakashima): add copy constructor after implementing iterator if
+// anybody uses it.
+
 template <typename T>
 inline NewLinkedHashSet<T>::NewLinkedHashSet(NewLinkedHashSet&& other) {
   Swap(other);
@@ -1215,41 +1282,77 @@
 }
 
 template <typename T>
+inline void NewLinkedHashSet<T>::RemoveFirst() {
+  DCHECK(!IsEmpty());
+  value_to_index_.erase(front());
+  FreeUsedNode(UsedFirstIndex());
+}
+
+template <typename T>
 inline const T& NewLinkedHashSet<T>::back() const {
   DCHECK(!IsEmpty());
   return nodes_[UsedLastIndex()].GetValueForUsedNode();
 }
 
 template <typename T>
+inline void NewLinkedHashSet<T>::pop_back() {
+  DCHECK(!IsEmpty());
+  value_to_index_.erase(back());
+  FreeUsedNode(UsedLastIndex());
+}
+
+template <typename T>
 template <typename IncomingValueType>
 typename NewLinkedHashSet<T>::AddResult NewLinkedHashSet<T>::insert(
     IncomingValueType&& value) {
   wtf_size_t new_entry_index = NewEntryIndex();
   typename Map::AddResult result =
-      value_to_index_.template insert(value, new_entry_index);
+      value_to_index_.insert(value, new_entry_index);
 
   if (!result.is_new_entry) {
     wtf_size_t index = result.stored_value->value;
     return AddResult(&(nodes_[index].GetValueForUsedNode()), false);
   }
 
-  wtf_size_t used_last_index = UsedLastIndex();
-  Node& used_last_node = nodes_[used_last_index];
-  Node& anchor = nodes_[anchor_index_];
-  used_last_node.SetNextIndexForUsedNode(new_entry_index);
-  anchor.SetPrevIndexForUsedNode(new_entry_index);
+  const T* stored_value = InsertValueBeforeNode(
+      anchor_index_, std::forward<IncomingValueType>(value));
+  return AddResult(stored_value, true);
+}
 
-  if (IsFreeListEmpty()) {
-    nodes_.push_back(Node(used_last_index, anchor_index_,
-                          std::forward<IncomingValueType>(value)));
-  } else {
-    DCHECK(free_head_index_ == new_entry_index);
-    Node& free_head = nodes_[free_head_index_];
-    free_head_index_ = free_head.GetNextIndexForFreeNode();
-    free_head = Node(used_last_index, anchor_index_, value);
+template <typename T>
+template <typename IncomingValueType>
+typename NewLinkedHashSet<T>::AddResult NewLinkedHashSet<T>::AppendOrMoveToLast(
+    IncomingValueType&& value) {
+  typename Map::AddResult result =
+      value_to_index_.insert(value, NewEntryIndex());
+
+  // TODO(keinakashima): just update prev/next indices to avoid reconstruct the
+  // same value
+  if (!result.is_new_entry) {
+    wtf_size_t index = result.stored_value->value;
+    FreeUsedNode(index);
   }
 
-  return AddResult(&(nodes_[new_entry_index].GetValueForUsedNode()), true);
+  const T* stored_value = InsertValueBeforeNode(
+      anchor_index_, std::forward<IncomingValueType>(value));
+  return AddResult(stored_value, result.is_new_entry);
+}
+
+template <typename T>
+template <typename IncomingValueType>
+typename NewLinkedHashSet<T>::AddResult
+NewLinkedHashSet<T>::PrependOrMoveToFirst(IncomingValueType&& value) {
+  typename Map::AddResult result =
+      value_to_index_.insert(value, NewEntryIndex());
+
+  if (!result.is_new_entry) {
+    wtf_size_t index = result.stored_value->value;
+    FreeUsedNode(index);
+  }
+
+  const T* stored_value = InsertValueBeforeNode(
+      UsedFirstIndex(), std::forward<IncomingValueType>(value));
+  return AddResult(stored_value, result.is_new_entry);
 }
 
 template <typename T>
@@ -1259,8 +1362,45 @@
     return;
 
   wtf_size_t index = it->value;
-  value_to_index_.erase(value);
+  value_to_index_.erase(it);
+  FreeUsedNode(index);
+}
+
+template <typename T>
+template <typename IncomingValueType>
+inline const T* NewLinkedHashSet<T>::InsertValueBeforeNode(
+    wtf_size_t before_index,
+    IncomingValueType&& new_value) {
+  wtf_size_t prev_index = nodes_[before_index].GetPrevIndexForUsedNode();
+  Node& prev = nodes_[prev_index];
+  Node& next = nodes_[before_index];
+
+  wtf_size_t new_entry_index = NewEntryIndex();
+  prev.SetNextIndexForUsedNode(new_entry_index);
+  next.SetPrevIndexForUsedNode(new_entry_index);
+
+  if (IsFreeListEmpty()) {
+    DCHECK(nodes_.size() == new_entry_index);
+    nodes_.push_back(Node(prev_index, before_index,
+                          std::forward<IncomingValueType>(new_value)));
+  } else {
+    DCHECK(free_head_index_ == new_entry_index);
+    Node& free_head = nodes_[free_head_index_];
+    free_head_index_ = free_head.GetNextIndexForFreeNode();
+    free_head = Node(prev_index, before_index,
+                     std::forward<IncomingValueType>(new_value));
+  }
+  return &(nodes_[new_entry_index].GetValueForUsedNode());
+}
+
+template <typename T>
+inline void NewLinkedHashSet<T>::FreeUsedNode(wtf_size_t index) {
+  DCHECK(index != anchor_index_);
   Node& node = nodes_[index];
+#if DCHECK_IS_ON()
+  DCHECK(node.is_used);
+#endif
+
   wtf_size_t prev_index = node.GetPrevIndexForUsedNode();
   wtf_size_t next_index = node.GetNextIndexForUsedNode();
 
@@ -1271,6 +1411,9 @@
   next_node.SetPrevIndexForUsedNode(prev_index);
 
   node.SetValueForUsedNode(HashTraits<T>::EmptyValue());
+#if DCHECK_IS_ON()
+  node.is_used = false;
+#endif
   node.SetNextIndexForFreeNode(free_head_index_);
   free_head_index_ = index;
 }
diff --git a/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc b/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc
index 437b832c..20f50beb 100644
--- a/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc
+++ b/third_party/blink/renderer/platform/wtf/linked_hash_set_test.cc
@@ -37,28 +37,28 @@
   using Set = NewLinkedHashSet<int>;
   Set set;
   Set::AddResult result = set.insert(1);  // set: 1 vector: anchor 1
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 1);
   EXPECT_EQ(set.front(), 1);
   EXPECT_EQ(set.back(), 1);
   EXPECT_EQ(set.size(), 1u);
 
   result = set.insert(2);  // set: 1, 2 vector: anchor 1, 2
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 2);
   EXPECT_EQ(set.front(), 1);
   EXPECT_EQ(set.back(), 2);
   EXPECT_EQ(set.size(), 2u);
 
   result = set.insert(1);  // set: 1, 2 vector: anchor 1, 2
-  EXPECT_EQ(result.is_new_entry, false);
+  EXPECT_FALSE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 1);
   EXPECT_EQ(set.front(), 1);
   EXPECT_EQ(set.back(), 2);
   EXPECT_EQ(set.size(), 2u);
 
   result = set.insert(3);  // set: 1, 2, 3 vector: anchor 1, 2, 3
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 3);
   EXPECT_EQ(set.front(), 1);
   EXPECT_EQ(set.back(), 3);
@@ -70,7 +70,7 @@
   EXPECT_EQ(set.size(), 2u);
 
   result = set.insert(1);  // set: 2, 3, 1 vector: anchor 1, 2, 3
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 1);
   EXPECT_EQ(set.front(), 2);
   EXPECT_EQ(set.back(), 1);
@@ -82,14 +82,14 @@
   EXPECT_EQ(set.size(), 2u);
 
   result = set.insert(4);  // set: 2, 1, 4 vector: anchor 1, 2, 4
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 4);
   EXPECT_EQ(set.front(), 2);
   EXPECT_EQ(set.back(), 4);
   EXPECT_EQ(set.size(), 3u);
 
   result = set.insert(5);  // set: 2, 1, 4, 5 vector: anchor 1, 2, 4, 5
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 5);
   EXPECT_EQ(set.front(), 2);
   EXPECT_EQ(set.back(), 5);
@@ -101,32 +101,133 @@
   set.erase(211);
 
   result = set.insert(3);  // set: 2, 1, 4, 5, 3 vector: anchor 1, 2, 4, 5, 3
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, 3);
   EXPECT_EQ(set.front(), 2);
   EXPECT_EQ(set.back(), 3);
   EXPECT_EQ(set.size(), 5u);
 }
 
+TEST(NewLinkedHashSetTest, RemoveFirstAndPopBackForInteger) {
+  using Set = NewLinkedHashSet<int>;
+  Set set;
+
+  for (int i = 1; i <= 5; i++) {
+    set.insert(i);
+  }
+  EXPECT_EQ(set.front(), 1);
+  EXPECT_EQ(set.back(), 5);
+
+  set.RemoveFirst();
+  EXPECT_EQ(set.front(), 2);
+  EXPECT_EQ(set.back(), 5);
+
+  set.pop_back();
+  EXPECT_EQ(set.front(), 2);
+  EXPECT_EQ(set.back(), 4);
+
+  set.RemoveFirst();
+  EXPECT_EQ(set.front(), 3);
+  EXPECT_EQ(set.back(), 4);
+
+  set.pop_back();
+  EXPECT_EQ(set.front(), 3);
+  EXPECT_EQ(set.back(), 3);
+
+  set.RemoveFirst();
+  EXPECT_TRUE(set.IsEmpty());
+
+  set.insert(1);
+  EXPECT_EQ(set.front(), 1);
+  EXPECT_EQ(set.back(), 1);
+  set.pop_back();
+  EXPECT_TRUE(set.IsEmpty());
+}
+
+TEST(NewLinkedHashSetTest, PrependOrMoveToFirstForInteger) {
+  using Set = NewLinkedHashSet<int>;
+  Set set;
+  Set::AddResult result =
+      set.PrependOrMoveToFirst(1);  // set: 1 vector: anchor 1
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 1);
+  EXPECT_EQ(set.front(), 1);
+  EXPECT_EQ(set.back(), 1);
+  EXPECT_EQ(set.size(), 1u);
+
+  result = set.insert(2);  // set: 1, 2 vector: anchor 1, 2
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 2);
+  EXPECT_EQ(set.front(), 1);
+  EXPECT_EQ(set.back(), 2);
+  EXPECT_EQ(set.size(), 2u);
+
+  result = set.PrependOrMoveToFirst(2);  // set: 2, 1 vector: anchor 1, 2
+  EXPECT_FALSE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 2);
+  EXPECT_EQ(set.front(), 2);
+  EXPECT_EQ(set.back(), 1);
+  EXPECT_EQ(set.size(), 2u);
+
+  result = set.PrependOrMoveToFirst(3);  // set: 3, 2, 1 vector: anchor 1, 2, 3
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 3);
+  EXPECT_EQ(set.front(), 3);
+  EXPECT_EQ(set.back(), 1);
+  EXPECT_EQ(set.size(), 3u);
+}
+
+TEST(NewLinkedHashSetTest, AppendOrMoveToLastForInteger) {
+  using Set = NewLinkedHashSet<int>;
+  Set set;
+  Set::AddResult result = set.AppendOrMoveToLast(1);  // set: 1 vector: anchor 1
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 1);
+  EXPECT_EQ(set.front(), 1);
+  EXPECT_EQ(set.back(), 1);
+  EXPECT_EQ(set.size(), 1u);
+
+  result = set.insert(2);  // set: 1, 2 vector: anchor 1, 2
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 2);
+  EXPECT_EQ(set.front(), 1);
+  EXPECT_EQ(set.back(), 2);
+  EXPECT_EQ(set.size(), 2u);
+
+  result = set.AppendOrMoveToLast(1);  // set: 2, 1 vector: anchor 1, 2
+  EXPECT_FALSE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 1);
+  EXPECT_EQ(set.front(), 2);
+  EXPECT_EQ(set.back(), 1);
+  EXPECT_EQ(set.size(), 2u);
+
+  result = set.AppendOrMoveToLast(3);  // set: 2, 1, 3 vector: anchor 1, 2, 3
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, 3);
+  EXPECT_EQ(set.front(), 2);
+  EXPECT_EQ(set.back(), 3);
+  EXPECT_EQ(set.size(), 3u);
+}
+
 TEST(NewLinkedHashSetTest, InsertAndEraseForString) {
   using Set = NewLinkedHashSet<String>;
   Set set;
   Set::AddResult result = set.insert("a");  // set: "a" vector: anchor "a"
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, "a");
   EXPECT_EQ(set.front(), "a");
   EXPECT_EQ(set.back(), "a");
   EXPECT_EQ(set.size(), 1u);
 
   result = set.insert("b");  // set: "a" "b" vector: anchor "a" "b"
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, "b");
   EXPECT_EQ(set.front(), "a");
   EXPECT_EQ(set.back(), "b");
   EXPECT_EQ(set.size(), 2u);
 
   result = set.insert("");  // set: "a" "b" "" vector: anchor "a" "b" ""
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, "");
   EXPECT_EQ(set.front(), "a");
   EXPECT_EQ(set.back(), "");
@@ -134,7 +235,7 @@
 
   result = set.insert(
       "abc");  // set: "a" "b" "" "abc" vector: anchor "a" "b" "" "abc"
-  EXPECT_EQ(result.is_new_entry, true);
+  EXPECT_TRUE(result.is_new_entry);
   EXPECT_EQ(*result.stored_value, "abc");
   EXPECT_EQ(set.front(), "a");
   EXPECT_EQ(set.back(), "abc");
@@ -166,4 +267,107 @@
   EXPECT_EQ(set.size(), 3u);
 }
 
+TEST(NewLinkedHashSetTest, RemoveFirstAndPopBackForString) {
+  using Set = NewLinkedHashSet<String>;
+  Set set;
+
+  set.insert("a");
+  set.insert("b");
+  set.insert("c");
+  set.insert("d");
+  set.insert("e");
+
+  EXPECT_EQ(set.front(), "a");
+  EXPECT_EQ(set.back(), "e");
+
+  set.RemoveFirst();
+  EXPECT_EQ(set.front(), "b");
+  EXPECT_EQ(set.back(), "e");
+
+  set.pop_back();
+  EXPECT_EQ(set.front(), "b");
+  EXPECT_EQ(set.back(), "d");
+
+  set.RemoveFirst();
+  EXPECT_EQ(set.front(), "c");
+  EXPECT_EQ(set.back(), "d");
+
+  set.pop_back();
+  EXPECT_EQ(set.front(), "c");
+  EXPECT_EQ(set.back(), "c");
+
+  set.RemoveFirst();
+  EXPECT_TRUE(set.IsEmpty());
+}
+
+TEST(NewLinkedHashSetTest, PrependOrMoveToFirstForString) {
+  using Set = NewLinkedHashSet<String>;
+  Set set;
+  Set::AddResult result =
+      set.PrependOrMoveToFirst("a");  // set: "a" vector: anchor "a"
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "a");
+  EXPECT_EQ(set.front(), "a");
+  EXPECT_EQ(set.back(), "a");
+  EXPECT_EQ(set.size(), 1u);
+
+  result = set.insert("b");  // set: "a", "b" vector: anchor "a", "b"
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "b");
+  EXPECT_EQ(set.front(), "a");
+  EXPECT_EQ(set.back(), "b");
+  EXPECT_EQ(set.size(), 2u);
+
+  result =
+      set.PrependOrMoveToFirst("b");  // set: "b", "a" vector: anchor "a", "b"
+  EXPECT_FALSE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "b");
+  EXPECT_EQ(set.front(), "b");
+  EXPECT_EQ(set.back(), "a");
+  EXPECT_EQ(set.size(), 2u);
+
+  result = set.PrependOrMoveToFirst(
+      "c");  // set: "c", "b", "a" vector: anchor "a", "b", "c"
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "c");
+  EXPECT_EQ(set.front(), "c");
+  EXPECT_EQ(set.back(), "a");
+  EXPECT_EQ(set.size(), 3u);
+}
+
+TEST(NewLinkedHashSetTest, AppendOrMoveToLastForString) {
+  using Set = NewLinkedHashSet<String>;
+  Set set;
+  Set::AddResult result =
+      set.AppendOrMoveToLast("a");  // set: "a" vector: anchor "a"
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "a");
+  EXPECT_EQ(set.front(), "a");
+  EXPECT_EQ(set.back(), "a");
+  EXPECT_EQ(set.size(), 1u);
+
+  result = set.insert("b");  // set: "a", "b" vector: anchor "a", "b"
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "b");
+  EXPECT_EQ(set.front(), "a");
+  EXPECT_EQ(set.back(), "b");
+  EXPECT_EQ(set.size(), 2u);
+
+  result =
+      set.AppendOrMoveToLast("a");  // set: "b", "a" vector: anchor "a", "b"
+  EXPECT_FALSE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "a");
+  EXPECT_EQ(set.front(), "b");
+  EXPECT_EQ(set.back(), "a");
+  EXPECT_EQ(set.size(), 2u);
+
+  result = set.AppendOrMoveToLast(
+      "c");  // set: "b", "a", "c" vector: anchor "a", "b", "c"
+  EXPECT_TRUE(result.is_new_entry);
+  EXPECT_EQ(*result.stored_value, "c");
+  EXPECT_EQ(set.front(), "b");
+  EXPECT_EQ(set.back(), "c");
+  EXPECT_EQ(set.size(), 3u);
+}
+
 }  // namespace WTF
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 0b0dc1a..410ca88b 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -6763,3 +6763,6 @@
 crbug.com/1058073 [ Mac ] http/tests/devtools/oopif/oopif-elements-navigate-out.js [ Pass Failure ]
 crbug.com/1058073 [ Mac ] http/tests/devtools/service-workers/sw-navigate-useragent.js [ Pass Failure ]
 crbug.com/1058137 virtual/threaded/http/tests/devtools/tracing/timeline-paint/timeline-paint.js [ Pass Failure ]
+
+# Sheriff 2020-03-04
+crbug.com/1058244 [ Mac ] virtual/compositor_threaded_scrollbar_scrolling/fast/scrolling/scrollbars/scrollbar-rtl-manipulation.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/bindings/dictionary-iterator.html b/third_party/blink/web_tests/bindings/dictionary-iterator.html
deleted file mode 100644
index 0da334b2..0000000
--- a/third_party/blink/web_tests/bindings/dictionary-iterator.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script>
-function createIterable(iterations) {
-  return {
-    [Symbol.iterator]() {
-      var i = 0;
-      return {next: () => iterations[i++]};
-    },
-  };
-}
-
-function assertIterationJoinEqual(iterable, description) {
-  test(() => {
-    assert_equals(internals.dictionaryTest().stringFromIterable(iterable), Array.from(iterable).join(','));
-  }, description);
-}
-
-assertIterationJoinEqual([1, 2, 3], 'Standard array of numbers.');
-assertIterationJoinEqual(new Set([1, 2, 3]), 'Standard set of numbers.');
-assertIterationJoinEqual([], 'Standard empty array.');
-assertIterationJoinEqual(new Set(), 'Standard empty set.');
-
-assertIterationJoinEqual(createIterable([
-  {done: false, value: 1},
-  {done: false, value: 2},
-  {done: false, value: 3},
-  {done: true},
-]), 'Custom list of numbers.');
-
-assertIterationJoinEqual(createIterable([
-  {done: false, value: 1},
-  {done: false, value: 2},
-  {done: false, value: 3},
-  {done: 'yes'},
-]), 'Truthy done signal.');
-
-assertIterationJoinEqual(createIterable([
-  {value: 1},
-  {value: 2},
-  {value: 3},
-  {done: true},
-]), 'Custom list of numbers with missing "done: false" signal.');
-
-assertIterationJoinEqual(createIterable([
-  {done: false, value: 1},
-  {done: false, value: 2},
-  {done: true},
-  {done: false, value: 3},
-]), 'Terminates when done is true.');
-
-assertIterationJoinEqual(createIterable([
-  {done: true},
-]), 'Empty list.');
-
-assertIterationJoinEqual({}, 'Non-iterable object.');
-
-test(() => {
-  var iterable = {[Symbol.iterator]() { return {}; }};
-  assert_throws_js(TypeError, () => Array.from(iterable));
-  assert_throws_js(TypeError, () => internals.dictionaryTest().stringFromIterable(iterable));
-}, 'Iterator object without next() function should throw.');
-
-test(() => {
-  var iterable = createIterable([
-    {done: false, value: 1},
-    {done: false, value: 2},
-    {done: false, value: 3},
-    1234,
-  ]);
-  assert_throws_js(TypeError, () => Array.from(iterable));
-  assert_throws_js(TypeError, () => internals.dictionaryTest().stringFromIterable(iterable));
-}, 'Non-object iterator.next() result should throw.');
-</script>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
index cc9db2f..0fedb21 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_7.json
@@ -153283,12 +153283,18 @@
    "css/css-transitions/CSSTransition-canceling.tentative-expected.txt": [
     []
    ],
+   "css/css-transitions/CSSTransition-effect.tentative-expected.txt": [
+    []
+   ],
    "css/css-transitions/Document-getAnimations.tentative-expected.txt": [
     []
    ],
    "css/css-transitions/KeyframeEffect-getKeyframes.tentative-expected.txt": [
     []
    ],
+   "css/css-transitions/KeyframeEffect-setKeyframes.tentative-expected.txt": [
+    []
+   ],
    "css/css-transitions/META.yml": [
     []
    ],
@@ -159568,12 +159574,6 @@
    "custom-elements/state.tentative/README.md": [
     []
    ],
-   "custom-elements/upgrading/Node-cloneNode-expected.txt": [
-    []
-   ],
-   "custom-elements/upgrading/upgrading-parser-created-element-expected.txt": [
-    []
-   ],
    "device-memory/META.yml": [
     []
    ],
@@ -233148,6 +233148,12 @@
      {}
     ]
    ],
+   "css/css-transitions/KeyframeEffect-setKeyframes.tentative.html": [
+    [
+     "css/css-transitions/KeyframeEffect-setKeyframes.tentative.html",
+     {}
+    ]
+   ],
    "css/css-transitions/KeyframeEffect-target.tentative.html": [
     [
      "css/css-transitions/KeyframeEffect-target.tentative.html",
@@ -264645,6 +264651,12 @@
      {}
     ]
    ],
+   "html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-alt-crash-001.html": [
+    [
+     "html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-alt-crash-001.html",
+     {}
+    ]
+   ],
    "html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html": [
     [
      "html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html",
@@ -463187,8 +463199,12 @@
    "8ee7215b7414722ec315826b54ceb650006552c5",
    "testharness"
   ],
+  "css/css-transitions/CSSTransition-effect.tentative-expected.txt": [
+   "e796389bc7886b59067991fcb766836d47cdc19f",
+   "support"
+  ],
   "css/css-transitions/CSSTransition-effect.tentative.html": [
-   "a38bc21bde05b95f14963d3a6543cef40f193e5a",
+   "5ccb201082e3c35014648ccda92b1bbf6eef3795",
    "testharness"
   ],
   "css/css-transitions/CSSTransition-finished.tentative.html": [
@@ -463227,6 +463243,14 @@
    "47b769b13a92971ccb02acd6ea61c1bda53f847f",
    "testharness"
   ],
+  "css/css-transitions/KeyframeEffect-setKeyframes.tentative-expected.txt": [
+   "bef7b146dd95e4ee968c1fb7e54ef9afd847cb59",
+   "support"
+  ],
+  "css/css-transitions/KeyframeEffect-setKeyframes.tentative.html": [
+   "df11c28234e0a984ead5ecf630f2a80ddff1c4b6",
+   "testharness"
+  ],
   "css/css-transitions/KeyframeEffect-target.tentative.html": [
    "dbb2a43f784db98317c4d37b32a376f6c27213ce",
    "testharness"
@@ -487283,10 +487307,6 @@
    "b80f90648d110a358bef090d3d9830077264cf70",
    "testharness"
   ],
-  "custom-elements/upgrading/Node-cloneNode-expected.txt": [
-   "6ad8ab1d12032ddad1f76c3f07959d429d9efd9a",
-   "support"
-  ],
   "custom-elements/upgrading/Node-cloneNode.html": [
    "364cecd76debd8b9657392641267be5f0918e84d",
    "testharness"
@@ -487295,10 +487315,6 @@
    "8238eee624afee25f19356b4de244713b5047038",
    "testharness"
   ],
-  "custom-elements/upgrading/upgrading-parser-created-element-expected.txt": [
-   "c520307dd7e3ce5be20fcb6fc6b0b7d8c90955d8",
-   "support"
-  ],
   "custom-elements/upgrading/upgrading-parser-created-element.html": [
    "0f7f95786dd11fd7013f057fd492cc9b7c924db9",
    "testharness"
@@ -508419,6 +508435,10 @@
    "42be8ce7a81b824f0e62553490ae406b6b736f9f",
    "testharness"
   ],
+  "html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-alt-crash-001.html": [
+   "b057967e7e967b1b3113c69f71f1298b86ce4df8",
+   "testharness"
+  ],
   "html/rendering/replaced-elements/attributes-for-embedded-content-and-images/img-aspect-ratio.html": [
    "eceef92a6418f3d74ffbd13a08d8e648dff42a3f",
    "testharness"
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative-expected.txt
new file mode 100644
index 0000000..e796389b
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative-expected.txt
@@ -0,0 +1,13 @@
+This is a testharness.js-based test.
+PASS After setting a transition's effect to null, it still reports the original transition property
+PASS After setting a transition's effect to null, it becomes finished
+PASS After setting a transition's effect to null, style is updated
+PASS After setting a transition's effect to null, a new transition can be started
+PASS After setting a transition's effect to null, it should be possible to interrupt that transition
+PASS After setting a new keyframe effect with a shorter duration, the transition becomes finished
+PASS After setting a new keyframe effect targeting different properties, the transition continues to report the original transition property
+PASS After setting a new keyframe effect on a play-pending transition, the transition remains pending
+PASS A transition with no effect still returns the original transitionProperty
+FAIL A transition with a replaced effect still exhibits the regular transition reversing behavior Cannot read property 'effect' of undefined
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative.html
index a38bc21b..5ccb2010 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transitions/CSSTransition-effect.tentative.html
@@ -179,7 +179,6 @@
   transition.effect = new KeyframeEffect(div,
                                          { marginLeft: [ '0px' , '100px'] },
                                          100 * MS_PER_SEC);
-  assert_equals(transition.transitionProperty, 'left');
   assert_true(transition.pending);
 
   // As a sanity check, check that the transition actually exits the
@@ -189,4 +188,52 @@
 }, 'After setting a new keyframe effect on a play-pending transition,'
    + ' the transition remains pending');
 
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+  transition.effect = null;
+
+  assert_equals(transition.transitionProperty, 'left');
+}, 'A transition with no effect still returns the original transitionProperty');
+
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+
+  // Seek to the middle and get the portion.
+  transition.currentTime = 50 * MS_PER_SEC;
+  const portion = transition.effect.getComputedTiming().progress;
+
+  // Replace the effect but keep the original timing
+  transition.effect = new KeyframeEffect(
+    div,
+    { top: ['200px', '300px', '100px'] },
+    transition.effect.getTiming()
+  );
+
+  // Reverse the transition
+  div.style.left = '0px';
+  const reversedTransition = div.getAnimations()[0];
+
+  const expectedDuration = 100 * MS_PER_SEC * portion;
+  assert_approx_equals(
+    reversedTransition.effect.getComputedTiming().activeDuration,
+    expectedDuration,
+    1
+  );
+}, 'A transition with a replaced effect still exhibits the regular transition'
+   + ' reversing behavior');
+
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/KeyframeEffect-setKeyframes.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-transitions/KeyframeEffect-setKeyframes.tentative-expected.txt
new file mode 100644
index 0000000..bef7b14
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transitions/KeyframeEffect-setKeyframes.tentative-expected.txt
@@ -0,0 +1,8 @@
+This is a testharness.js-based test.
+FAIL Keyframes set using setKeyframes() are reflected in computed style for a running transition Failed to execute 'setKeyframes' on 'KeyframeEffect': Calling setKeyframes on CSS Transitions is not yet supported
+FAIL A transition with replaced keyframes still returns the original transitionProperty Failed to execute 'setKeyframes' on 'KeyframeEffect': Calling setKeyframes on CSS Transitions is not yet supported
+FAIL A transition with no keyframes still returns the original transitionProperty Failed to execute 'setKeyframes' on 'KeyframeEffect': Calling setKeyframes on CSS Transitions is not yet supported
+FAIL A transition with replaced keyframes still exhibits the regular transition reversing behavior Failed to execute 'setKeyframes' on 'KeyframeEffect': Calling setKeyframes on CSS Transitions is not yet supported
+FAIL A transition with no keyframes still exhibits the regular transition reversing behavior Failed to execute 'setKeyframes' on 'KeyframeEffect': Calling setKeyframes on CSS Transitions is not yet supported
+Harness: the test ran to completion.
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transitions/KeyframeEffect-setKeyframes.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-transitions/KeyframeEffect-setKeyframes.tentative.html
new file mode 100644
index 0000000..df11c28
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-transitions/KeyframeEffect-setKeyframes.tentative.html
@@ -0,0 +1,117 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>KeyframeEffect.setKeyframes() for CSS transitions</title>
+<!-- TODO: Add a more specific link for this once it is specified. -->
+<link rel="help" href="https://drafts.csswg.org/css-transitions-2/#csstransition">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="support/helper.js"></script>
+<div id="log"></div>
+<script>
+'use strict';
+
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+  transition.effect.setKeyframes({ left: ['200px', '300px', '100px'] });
+
+  assert_equals(getComputedStyle(div).left, '200px');
+}, 'Keyframes set using setKeyframes() are reflected in computed style for'
+   + ' a running transition');
+
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+  transition.effect.setKeyframes({ top: ['0px', '100px', '300px'] });
+
+  assert_equals(transition.transitionProperty, 'left');
+}, 'A transition with replaced keyframes still returns the original'
+   + ' transitionProperty');
+
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+  transition.effect.setKeyframes({ });
+
+  assert_equals(transition.transitionProperty, 'left');
+}, 'A transition with no keyframes still returns the original'
+   + ' transitionProperty');
+
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+
+  // Seek to the middle and get the portion.
+  //
+  // We deliberately DON'T set transition-timing-function to linear so that we
+  // can test that it is applied correctly.
+  transition.currentTime = 50 * MS_PER_SEC;
+  const portion = transition.effect.getComputedTiming().progress;
+
+  transition.effect.setKeyframes({ top: ['200px', '300px', '100px'] });
+
+  // Reverse transition
+  div.style.left = '0px';
+  const reversedTransition = div.getAnimations()[0];
+
+  const expectedDuration = 100 * MS_PER_SEC * portion;
+  assert_approx_equals(
+    reversedTransition.effect.getComputedTiming().activeDuration,
+    expectedDuration,
+    1
+  );
+}, 'A transition with replaced keyframes still exhibits the regular transition'
+   + ' reversing behavior');
+
+test(t => {
+  const div = addDiv(t);
+
+  div.style.left = '0px';
+  getComputedStyle(div).transitionProperty;
+  div.style.transition = 'left 100s';
+  div.style.left = '100px';
+
+  const transition = div.getAnimations()[0];
+
+  transition.currentTime = 50 * MS_PER_SEC;
+  const portion = transition.effect.getComputedTiming().progress;
+
+  transition.effect.setKeyframes({ });
+
+  div.style.left = '0px';
+  const reversedTransition = div.getAnimations()[0];
+
+  const expectedDuration = 100 * MS_PER_SEC * portion;
+  assert_approx_equals(
+    reversedTransition.effect.getComputedTiming().activeDuration,
+    expectedDuration,
+    1
+  );
+}, 'A transition with no keyframes still exhibits the regular transition'
+   + ' reversing behavior');
+
+</script>
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/mixed-content-execution-contexts-1.js b/third_party/blink/web_tests/http/tests/inspector-protocol/mixed-content-execution-contexts-1.js
index e425847..b1b3a10 100644
--- a/third_party/blink/web_tests/http/tests/inspector-protocol/mixed-content-execution-contexts-1.js
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/mixed-content-execution-contexts-1.js
@@ -2,9 +2,11 @@
   const {page, session, dp} = await testRunner.startBlank(
       `Tests that execution contexts are reported for frames that were blocked due to mixed content when runtime is enabled *before* navigation.`);
   await dp.Runtime.enable();
+  let count = 0;
   dp.Runtime.onExecutionContextCreated(event => {
     testRunner.log(event);
+    if (++count === 2) // page context + frame context.
+      testRunner.completeTest();
   });
   await page.navigate('https://devtools.test:8443/inspector-protocol/resources/mixed-content.html');
-  testRunner.completeTest();
 })
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-expected.txt
new file mode 100644
index 0000000..abec7bd
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-expected.txt
@@ -0,0 +1,13 @@
+Tests that page events are associated with the input
+Before release
+{
+    method : Page.frameRequestedNavigation
+    params : {
+        frameId : <string>
+        reason : anchorClick
+        url : http://127.0.0.1:8000/inspector-protocol/resources/inspector-protocol-page.html
+    }
+    sessionId : <string>
+}
+After release
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-isolation-expected.txt b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-isolation-expected.txt
new file mode 100644
index 0000000..abec7bd
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-isolation-expected.txt
@@ -0,0 +1,13 @@
+Tests that page events are associated with the input
+Before release
+{
+    method : Page.frameRequestedNavigation
+    params : {
+        frameId : <string>
+        reason : anchorClick
+        url : http://127.0.0.1:8000/inspector-protocol/resources/inspector-protocol-page.html
+    }
+    sessionId : <string>
+}
+After release
+
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-isolation.js b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-isolation.js
new file mode 100644
index 0000000..17d5a47
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated-isolation.js
@@ -0,0 +1,19 @@
+(async function(testRunner) {
+  var {page, session, dp} = await testRunner.startHTML(`
+    <a href="">
+      <div style="width:1000px; height: 1000px" href="http://127.0.0.1:8000/inspector-protocol/resources/blank.html">LINK</div>
+    </a>
+  `, 'Tests that page events are associated with the input');
+
+  await dp.Page.enable();
+  dp.Page.onFrameRequestedNavigation(event => testRunner.log(event));
+
+  // Click.
+  await dp.Input.dispatchMouseEvent({type: 'mouseMoved', button: 'left', buttons: 0, clickCount: 1, x: 150, y: 150 });
+  await dp.Input.dispatchMouseEvent({type: 'mousePressed', button: 'left', buttons: 0, clickCount: 1, x: 150, y: 150 });
+  testRunner.log('Before release');
+  await dp.Input.dispatchMouseEvent({type: 'mouseReleased', button: 'left', buttons: 1, clickCount: 1, x: 150, y: 150 });
+  testRunner.log('After release');
+
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated.js b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated.js
new file mode 100644
index 0000000..b625b0e
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/inspector-protocol/page/page-events-associated.js
@@ -0,0 +1,19 @@
+(async function(testRunner) {
+  var {page, session, dp} = await testRunner.startHTML(`
+    <a href="">
+      <div style="width:1000px; height: 1000px" href="http://localhost:8000/inspector-protocol/resources/blank.html">LINK</div>
+    </a>
+  `, 'Tests that page events are associated with the input');
+
+  await dp.Page.enable();
+  dp.Page.onFrameRequestedNavigation(event => testRunner.log(event));
+
+  // Click.
+  await dp.Input.dispatchMouseEvent({type: 'mouseMoved', button: 'left', buttons: 0, clickCount: 1, x: 150, y: 150 });
+  await dp.Input.dispatchMouseEvent({type: 'mousePressed', button: 'left', buttons: 0, clickCount: 1, x: 150, y: 150 });
+  testRunner.log('Before release');
+  await dp.Input.dispatchMouseEvent({type: 'mouseReleased', button: 'left', buttons: 1, clickCount: 1, x: 150, y: 150 });
+  testRunner.log('After release');
+
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/inspector-protocol/page/frameAttachedDetached-expected.txt b/third_party/blink/web_tests/inspector-protocol/page/frameAttachedDetached-expected.txt
index d034cbf..bb7f099 100644
--- a/third_party/blink/web_tests/inspector-protocol/page/frameAttachedDetached-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/page/frameAttachedDetached-expected.txt
@@ -1,7 +1,7 @@
 Tests frame lifetime events.
 Attached
-RequestWillBeSent .../inspector-protocol/resources/blank.html
 Started loading
+RequestWillBeSent .../inspector-protocol/resources/blank.html
 Navigated
 Started loading
 Navigated
diff --git a/third_party/libgav1/README.chromium b/third_party/libgav1/README.chromium
index 33d14edab..0887ee8 100644
--- a/third_party/libgav1/README.chromium
+++ b/third_party/libgav1/README.chromium
@@ -2,9 +2,9 @@
 Short Name: libgav1
 URL: https://chromium.googlesource.com/codecs/libgav1/
 Version: 0
-Date: Tuesday February 18 2020
+Date: Thursday February 20 2020
 Branch: master
-Commit: 8e8c13b9e821f4590761487c4e0b96f432eaf051
+Commit: fa1c3c4e673cf12ffa22b8fbe4a7c79314571f1b
 License: Apache 2.0
 License File: libgav1/LICENSE
 Security Critical: yes
diff --git a/third_party/libgav1/libgav1_srcs.gni b/third_party/libgav1/libgav1_srcs.gni
index 86f6bcb..85d525d 100644
--- a/third_party/libgav1/libgav1_srcs.gni
+++ b/third_party/libgav1/libgav1_srcs.gni
@@ -10,8 +10,6 @@
   "//third_party/libgav1/src/src/film_grain.cc",
   "//third_party/libgav1/src/src/film_grain.h",
   "//third_party/libgav1/src/src/frame_buffer.cc",
-  "//third_party/libgav1/src/src/frame_buffer_callback_adaptor.cc",
-  "//third_party/libgav1/src/src/frame_buffer_callback_adaptor.h",
   "//third_party/libgav1/src/src/frame_buffer_utils.h",
   "//third_party/libgav1/src/src/frame_scratch_buffer.h",
   "//third_party/libgav1/src/src/internal_frame_buffer_list.cc",
@@ -157,7 +155,6 @@
   "//third_party/libgav1/src/src/gav1/decoder_buffer.h",
   "//third_party/libgav1/src/src/gav1/decoder_settings.h",
   "//third_party/libgav1/src/src/gav1/frame_buffer.h",
-  "//third_party/libgav1/src/src/gav1/frame_buffer2.h",
   "//third_party/libgav1/src/src/gav1/status_code.h",
   "//third_party/libgav1/src/src/gav1/symbol_visibility.h",
   "//third_party/libgav1/src/src/gav1/version.h",
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5a251ae..02cc9c86 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -19806,6 +19806,7 @@
   <int value="682" label="ReportDeviceMemoryInfo"/>
   <int value="683" label="UseLegacyFormControls"/>
   <int value="684" label="SafeBrowsingEnhancedProtection"/>
+  <int value="685" label="AdvancedProtectionExtraSecurityAllowed"/>
 </enum>
 
 <enum name="EnterprisePolicyDeviceIdValidity">
@@ -67503,6 +67504,8 @@
   <int value="7" label="App update failed"/>
   <int value="8" label="App updated"/>
   <int value="9" label="App is system web app"/>
+  <int value="10" label="Icon download failed"/>
+  <int value="11" label="Icon read from disk failed"/>
 </enum>
 
 <enum name="WebappUninstallDialogAction">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index e8db0b4..9eeefde4 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -859,7 +859,7 @@
 </histogram>
 
 <histogram name="AccountManager.LegacySetPrimaryAccountAndUpdateAccountInfo"
-    enum="BooleanUsage" expires_after="2020-06-28">
+    enum="BooleanUsage" expires_after="2020-08-30">
   <owner>sinhak@chromium.org</owner>
   <summary>
     Tracks the usage of the legacy Primary Account setting flow vs the new flow
@@ -1953,7 +1953,7 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskScheduled.Failure"
-    enum="BackgroundTaskId" expires_after="2020-05-31">
+    enum="BackgroundTaskId" expires_after="2020-08-30">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>
@@ -1962,7 +1962,7 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskScheduled.Success"
-    enum="BackgroundTaskId" expires_after="2020-05-31">
+    enum="BackgroundTaskId" expires_after="2020-08-30">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>
@@ -1971,7 +1971,7 @@
 </histogram>
 
 <histogram name="Android.BackgroundTaskScheduler.TaskStarted"
-    enum="BackgroundTaskId" expires_after="2020-05-24">
+    enum="BackgroundTaskId" expires_after="2020-08-30">
   <owner>fgorski@chromium.org</owner>
   <owner>nyquist@chromium.org</owner>
   <summary>Records that a specific background task has been started.</summary>
@@ -3108,7 +3108,7 @@
 </histogram>
 
 <histogram name="Android.IntentHeaders" enum="IntentHeadersResult"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>peconn@chromium.org</owner>
   <summary>
     Records the usage of the Browser.EXTRA_HEADERS field for Intents that Chrome
@@ -3140,7 +3140,7 @@
 </histogram>
 
 <histogram name="Android.KernelVersion" enum="AndroidKernelVersion"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>rsesek@chromium.org</owner>
   <summary>
     Reports the kernel major and minor version from the utsname.release field.
@@ -3517,7 +3517,7 @@
 </histogram>
 
 <histogram name="Android.Omnibox.InvalidMatch" enum="MatchResult"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>ender@chromium.org</owner>
   <owner>tedchoc@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
@@ -4304,7 +4304,7 @@
 </histogram>
 
 <histogram name="Android.SysUtilsLowEndMatches" enum="BooleanEqual"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>ssid@chromium.org</owner>
   <summary>
     Measures whether Chrome low-end detection logic based on RAM size matches
@@ -5805,7 +5805,7 @@
 </histogram>
 
 <histogram name="Apps.AppList.SearchQueryLength.Apps" units="characters"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>thanhdng@chromium.org</owner>
   <owner>jiameng@chromium.org</owner>
   <summary>
@@ -6372,7 +6372,7 @@
 </histogram>
 
 <histogram name="Apps.AppListSearchBoxActivated"
-    enum="SearchBoxActivationSource" expires_after="2020-06-28">
+    enum="SearchBoxActivationSource" expires_after="2020-08-30">
 <!-- Name completed by histogram_suffixes name="TabletOrClamshellMode" -->
 
   <owner>newcomer@chromium.org</owner>
@@ -6479,7 +6479,7 @@
 </histogram>
 
 <histogram name="Apps.AppListSearchResultOpenTypeV2" enum="AppListSearchResult"
-    expires_after="2020-05-03">
+    expires_after="2020-08-30">
 <!-- Name completed by histogram_suffixes name="TabletOrClamshellMode" -->
 
   <owner>newcomer@chromium.org</owner>
@@ -7117,7 +7117,7 @@
 
 <histogram base="true"
     name="Apps.StateTransition.Drag.PresentationTime.MaxLatency" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
 <!-- Name completed by histogram_suffixes
      name="TabletOrClamshellMode" -->
 
@@ -7293,7 +7293,7 @@
 </histogram>
 
 <histogram name="Arc.ContainerLifetimeEvent" enum="ArcContainerLifetimeEvent"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>elijahtaylor@google.com</owner>
   <owner>yusukes@google.com</owner>
   <summary>
@@ -7713,7 +7713,7 @@
 </histogram>
 
 <histogram name="Arc.OptInSilentAuthCode.Reauthorization"
-    enum="ArcOptInSilentAuthCode" expires_after="2020-06-28">
+    enum="ArcOptInSilentAuthCode" expires_after="2020-08-30">
   <owner>khmel@google.com</owner>
   <summary>
     Arc Silent Auth Code status. This status is set during the ARC
@@ -8263,7 +8263,7 @@
 </histogram>
 
 <histogram name="Ash.Accelerators.Rotation.Usage"
-    enum="ScreenRotationAcceleratorAction" expires_after="2020-06-28">
+    enum="ScreenRotationAcceleratorAction" expires_after="2020-08-30">
   <owner>baileyberro@chromium.org</owner>
   <summary>
     Captures the result of a user using the rotation accelerator -
@@ -8436,7 +8436,7 @@
 </histogram>
 
 <histogram name="Ash.Desks.RemoveDesk" enum="DesksCreationRemovalSource"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>afakhry@chromium.org</owner>
   <summary>
     Emitted when a virtual desk is removed to specify the source of this remove
@@ -9219,7 +9219,7 @@
 </histogram>
 
 <histogram name="Ash.Shelf.Palette.Usage.AutoOpened" enum="PaletteTrayOptions"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>jdufault@chromium.org</owner>
   <summary>
     Recorded every time that the palette option has been selected from the
@@ -9556,7 +9556,7 @@
 </histogram>
 
 <histogram name="Ash.TouchView.LidAngle" units="degrees"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>oshima@chromium.org</owner>
   <summary>
     Chrome OS only. The computed angle between the lid and the keyboard panel.
@@ -10590,7 +10590,7 @@
 </histogram>
 
 <histogram name="Assistant.ServiceStartTime" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>updowndota@chromium.org</owner>
   <summary>Amount of time spent in starting Assistant service.</summary>
 </histogram>
@@ -11120,7 +11120,7 @@
   </summary>
 </histogram>
 
-<histogram name="AsyncDNS.ServerCount" units="units" expires_after="2020-06-28">
+<histogram name="AsyncDNS.ServerCount" units="units" expires_after="2020-08-30">
   <owner>pauljensen@chromium.org</owner>
   <owner>mef@chromium.org</owner>
   <summary>
@@ -15083,7 +15083,7 @@
 </histogram>
 
 <histogram name="AutoScreenBrightness.PersonalCurveValid" enum="BooleanValid"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>tby@chromium.org</owner>
   <summary>
     Whether the user's personal brightness curve is valid. Chrome OS only.
@@ -16911,6 +16911,9 @@
 
 <histogram name="Blink.ColorSpace.Destination.ICCResult"
     enum="ICCProfileAnalyzeResult" expires_after="2020-03-08">
+  <obsolete>
+    No useful signal. Deprecated 2020-03-03.
+  </obsolete>
   <owner>brianosman@chromium.org</owner>
   <owner>mcasas@google.com</owner>
   <owner>ccameron@chromium.org</owner>
@@ -20503,7 +20506,7 @@
   </summary>
 </histogram>
 
-<histogram name="BlueZ.ChipLost2" units="seconds" expires_after="2020-06-28">
+<histogram name="BlueZ.ChipLost2" units="seconds" expires_after="2020-08-30">
   <owner>sonnysasaka@chromium.org</owner>
   <summary>
     This is specific to Chrome OS. Records a duration of a Bluetooth adapter
@@ -20586,7 +20589,7 @@
 </histogram>
 
 <histogram name="BlueZ.TimeLengthOfDiscovering" units="seconds"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>mcchou@chromium.org</owner>
   <summary>
     This is specific to Chrome OS. Records the duration for the local device to
@@ -20825,7 +20828,7 @@
 </histogram>
 
 <histogram name="Bookmarks.EntryPoint" enum="BookmarksEntryPoint"
-    expires_after="2020-06-07">
+    expires_after="2020-08-30">
   <owner>ianwen@chromium.org</owner>
   <summary>How users add a new bookmark.</summary>
 </histogram>
@@ -20987,7 +20990,7 @@
 </histogram>
 
 <histogram name="Browser.PaintPreview.Capture.NumberOfFramesCaptured"
-    units="units" expires_after="2020-06-01">
+    units="units" expires_after="2020-08-30">
   <owner>ckitagawa@chromium.org</owner>
   <owner>mahmoudi@chromium.org</owner>
   <owner>vollick@chromium.org</owner>
@@ -20997,7 +21000,7 @@
 </histogram>
 
 <histogram name="Browser.PaintPreview.Capture.Success" enum="BooleanSuccess"
-    expires_after="2020-06-01">
+    expires_after="2020-08-30">
   <owner>ckitagawa@chromium.org</owner>
   <owner>mahmoudi@chromium.org</owner>
   <owner>vollick@chromium.org</owner>
@@ -21018,7 +21021,7 @@
 </histogram>
 
 <histogram name="Browser.PaintPreview.CaptureExperiment.CompressedOnDiskSize"
-    units="KB" expires_after="2020-06-01">
+    units="KB" expires_after="2020-08-30">
   <owner>ckitagawa@chromium.org</owner>
   <owner>mahmoudi@chromium.org</owner>
   <owner>vollick@chromium.org</owner>
@@ -21028,7 +21031,7 @@
 </histogram>
 
 <histogram name="Browser.PaintPreview.CaptureExperiment.Success"
-    enum="BooleanSuccess" expires_after="2020-06-01">
+    enum="BooleanSuccess" expires_after="2020-08-30">
   <owner>ckitagawa@chromium.org</owner>
   <owner>mahmoudi@chromium.org</owner>
   <owner>vollick@chromium.org</owner>
@@ -21127,7 +21130,7 @@
 </histogram>
 
 <histogram base="true" name="Browser.Tabs.TabSwitchResult"
-    enum="TabSwitchResult" expires_after="2020-06-28">
+    enum="TabSwitchResult" expires_after="2020-08-30">
 <!-- Name completed by histogram_suffixes name="TabSwitchingType" -->
 
   <owner>fdoray@chromium.org</owner>
@@ -21147,7 +21150,7 @@
 </histogram>
 
 <histogram base="true" name="Browser.Tabs.TotalSwitchDuration" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
 <!-- Name completed by histogram_suffixes name="TabSwitchingType" -->
 
   <owner>ejoe@google.com</owner>
@@ -21267,7 +21270,7 @@
 </histogram>
 
 <histogram name="BrowserRenderProcessHost.ChildKills.OOM" enum="RendererType"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>oshima@chromium.org</owner>
   <summary>
     Out of BrowserRenderProcessHost.ChildKills, numer of kills due to SIGKILL,
@@ -23553,7 +23556,7 @@
 </histogram>
 
 <histogram name="ChromeOS.CWP.JankinessTriggerStatus"
-    enum="ChromeOSJankinessTriggerStatus" expires_after="2020-07-01">
+    enum="ChromeOSJankinessTriggerStatus" expires_after="2020-08-30">
   <owner>cywang@chromium.org</owner>
   <owner>chinglinyu@chromium.org</owner>
   <summary>
@@ -26910,7 +26913,7 @@
 </histogram>
 
 <histogram name="ContentSettings.Popups.BlockerActions"
-    enum="PopupBlockerAction" expires_after="2020-06-28">
+    enum="PopupBlockerAction" expires_after="2020-08-30">
   <owner>csharrison@chromium.org</owner>
   <summary>
     Counts of various events related to the popup blocker. Including blocked
@@ -26921,7 +26924,7 @@
 </histogram>
 
 <histogram name="ContentSettings.Popups.ClickThroughPosition"
-    enum="ListItemPosition" expires_after="2020-06-28">
+    enum="ListItemPosition" expires_after="2020-08-30">
   <owner>csharrison@chromium.org</owner>
   <summary>
     The blocked popup list contains a list of links that Chrome has blocked via
@@ -27831,7 +27834,7 @@
 </histogram>
 
 <histogram name="ContextMenu.LensSupportStatus"
-    enum="ContextMenuLensSupportStatus" expires_after="2020-06-30">
+    enum="ContextMenuLensSupportStatus" expires_after="2020-08-30">
   <owner>benwgold@google.com</owner>
   <owner>lens-chrome@google.com</owner>
   <summary>
@@ -27886,7 +27889,7 @@
 </histogram>
 
 <histogram base="true" name="ContextMenu.SelectedOptionDesktop"
-    enum="ContextMenuOptionDesktop" expires_after="2020-06-30">
+    enum="ContextMenuOptionDesktop" expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>mpearson@chromium.org</owner>
   <summary>
@@ -28720,7 +28723,7 @@
 </histogram>
 
 <histogram name="Cookie.Startup.NumberOfCookiesDeleted" units="units"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>erikchen@chromium.org</owner>
   <summary>
     The number of session cookies deleted on startup. This metric is emitted
@@ -28824,7 +28827,7 @@
   </summary>
 </histogram>
 
-<histogram name="Cookie.TimeInitializeDB" units="ms" expires_after="2020-06-28">
+<histogram name="Cookie.TimeInitializeDB" units="ms" expires_after="2020-08-30">
   <owner>nyquist@chromium.org</owner>
   <summary>The amount of time (ms) to initialize the cookies database.</summary>
 </histogram>
@@ -28858,7 +28861,7 @@
   </summary>
 </histogram>
 
-<histogram name="Cookie.TimeLoad" units="ms" expires_after="2020-06-28">
+<histogram name="Cookie.TimeLoad" units="ms" expires_after="2020-08-30">
   <owner>pwnall@chromium.org</owner>
   <summary>
     This histogram records the sum of the durations of all initial tasks loading
@@ -40313,7 +40316,7 @@
 </histogram>
 
 <histogram name="Download.ContentType.Audio" enum="DownloadAudioType"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>xingliu@chromium.org</owner>
   <summary>Types of audio files that are downloaded.</summary>
 </histogram>
@@ -40327,7 +40330,7 @@
 </histogram>
 
 <histogram name="Download.ContentType.Text" enum="DownloadTextType"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>xingliu@chromium.org</owner>
   <summary>Types of text files that are downloaded.</summary>
 </histogram>
@@ -41292,7 +41295,7 @@
 </histogram>
 
 <histogram name="Download.ParallelDownload.CreationFailureReason"
-    enum="InterruptReason" expires_after="2020-06-28">
+    enum="InterruptReason" expires_after="2020-08-30">
   <owner>qinmin@chromium.org</owner>
   <owner>xingliu@chromium.org</owner>
   <summary>
@@ -41795,7 +41798,7 @@
 </histogram>
 
 <histogram name="Download.Service.Recovery" enum="Download.Service.EntryStates"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>xingliu@chromium.org</owner>
   <summary>
     The state that the entry transitions to when recovery operation happens.
@@ -43603,7 +43606,7 @@
 </histogram>
 
 <histogram name="Enterprise.DMServerRequestSuccess"
-    enum="EnterpriseDMServerRequestSuccess" expires_after="2020-06-28">
+    enum="EnterpriseDMServerRequestSuccess" expires_after="2020-08-30">
   <owner>poromov@chromium.org</owner>
   <summary>
     Number of retries the client did to execute a DeviceManagementServer
@@ -44579,7 +44582,7 @@
 </histogram>
 
 <histogram name="Enterprise.UserSession.Logins"
-    enum="EnterpriseUserSessionLogins" expires_after="2020-06-28">
+    enum="EnterpriseUserSessionLogins" expires_after="2020-08-30">
   <owner>xiyuan@chromium.org</owner>
   <owner>sduraisamy@chromium.org</owner>
   <summary>Tracks the sign-in events on an enrolled device.</summary>
@@ -44709,7 +44712,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.IsEnterpriseUser" enum="BooleanEnabled"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>rogerta@chromium.org</owner>
   <summary>
     Whether the machine is considered an enterprise user. An enterprise user is
@@ -44751,7 +44754,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsCurrentUserDomainUser" enum="Boolean"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -44761,7 +44764,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsDeviceDomainJoined" enum="Boolean"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -44771,7 +44774,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsDeviceMDMEnrolledNew"
-    enum="EnterpriseMacMDMStatusNew" expires_after="2020-07-01">
+    enum="EnterpriseMacMDMStatusNew" expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -44782,7 +44785,7 @@
 </histogram>
 
 <histogram name="EnterpriseCheck.Mac.IsDeviceMDMEnrolledOld"
-    enum="EnterpriseMacMDMStatusOld" expires_after="2020-07-01">
+    enum="EnterpriseMacMDMStatusOld" expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>pastarmovj@chromium.org</owner>
   <summary>
@@ -44920,7 +44923,7 @@
 </histogram>
 
 <histogram name="Event.AsyncTargeting.AsyncClientDepth" units="clients"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>yigu@chromium.org</owner>
   <owner>event-targeting@chromium.org</owner>
   <summary>
@@ -46253,7 +46256,7 @@
 </histogram>
 
 <histogram name="Event.Latency.OS" units="microseconds"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>tdresser@chromium.org</owner>
   Team: input-dev@chromium.org.
   <summary>
@@ -46282,7 +46285,7 @@
 </histogram>
 
 <histogram name="Event.Latency.OS_NO_VALIDATION.POSITIVE" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>sullivan@chromium.org</owner>
   <owner>input-dev@chromium.org</owner>
   <summary>
@@ -57413,7 +57416,7 @@
 </histogram>
 
 <histogram name="GCM.SendWebPushMessageForbiddenBody"
-    enum="ForbiddenResponseBody" expires_after="2020-06-14">
+    enum="ForbiddenResponseBody" expires_after="2020-08-30">
   <owner>alexchau@chromium.org</owner>
   <owner>peter@chromium.org</owner>
   <summary>
@@ -59326,7 +59329,7 @@
 </histogram>
 
 <histogram name="GPU.EGLDisplayType" enum="EGLDisplayType"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>jbauman@chromium.org</owner>
   <summary>The display type used to ask for an EGLDisplay.</summary>
 </histogram>
@@ -59497,7 +59500,7 @@
 </histogram>
 
 <histogram name="GPU.GPUProcessInitialized" enum="BooleanSuccess"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>vmiura@chromium.org</owner>
   <summary>
     Whether the GPU process successfully initialized or failed and then exitted
@@ -59517,7 +59520,7 @@
 </histogram>
 
 <histogram name="GPU.GPUProcessLaunchTime" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>vmiura@chromium.org</owner>
   <summary>
     Startup time of the GPU process as measured by the GPU process host.
@@ -59832,7 +59835,7 @@
 </histogram>
 
 <histogram name="GPU.Output.MaxLuminance" units="lumens"
-    expires_after="2020-06-30">
+    expires_after="2020-08-30">
   <owner>hubbe@chromium.org</owner>
   <owner>media-dev@chromium.org</owner>
   <summary>
@@ -59864,7 +59867,7 @@
 </histogram>
 
 <histogram name="GPU.ProcessLifetimeEvents.HardwareAccelerated"
-    enum="GPUProcessLifetimeEvent" expires_after="2020-06-28">
+    enum="GPUProcessLifetimeEvent" expires_after="2020-08-30">
   <owner>vmiura@chromium.org</owner>
   <summary>
     Recorded once for every GPU process launch and crash when GPU process is
@@ -60082,7 +60085,7 @@
 </histogram>
 
 <histogram name="GPU.Sandbox.InitializedSuccessfully" enum="BooleanSuccess"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>vmiura@chromium.org</owner>
   <owner>wfh@chromium.org</owner>
   <summary>
@@ -66002,7 +66005,7 @@
 </histogram>
 
 <histogram name="IOS.NTP.Impression" enum="IOSNTPImpression"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>gambard@chromium.org</owner>
   <summary>
     The type of NTP impressions on iOS, split by type of suggestions shown
@@ -66484,7 +66487,7 @@
 </histogram>
 
 <histogram base="true" name="JSDialogs.OriginRelationship"
-    enum="DialogOriginRelationship" expires_after="2020-07-01">
+    enum="DialogOriginRelationship" expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <owner>meacer@chromium.org</owner>
@@ -66495,7 +66498,7 @@
 </histogram>
 
 <histogram base="true" name="JSDialogs.Scheme" enum="NavigationScheme"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>avi@chromium.org</owner>
   <owner>carlosil@chromium.org</owner>
   <owner>meacer@chromium.org</owner>
@@ -66801,7 +66804,7 @@
 </histogram>
 
 <histogram name="KeyboardAccessory.AccessorySheetSuggestionCount" units="count"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>fhorschig@chromium.org</owner>
   <summary>
     Android only. Records how many suggestions a user faced when opening a
@@ -66810,7 +66813,7 @@
 </histogram>
 
 <histogram name="KeyboardAccessory.AccessorySheetSuggestionsSelected"
-    enum="AccessorySuggestionType" expires_after="2020-06-28">
+    enum="AccessorySuggestionType" expires_after="2020-08-30">
   <owner>fhorschig@chromium.org</owner>
   <summary>
     Android only. Records which type of suggestion was selected from an open
@@ -66819,7 +66822,7 @@
 </histogram>
 
 <histogram name="KeyboardAccessory.AccessorySheetTriggered"
-    enum="AccessorySheetTrigger" expires_after="2020-06-28">
+    enum="AccessorySheetTrigger" expires_after="2020-08-30">
   <owner>fhorschig@chromium.org</owner>
   <summary>
     Android only. Records how often the bottom sheet was opened or closed by a
@@ -66868,7 +66871,7 @@
 </histogram>
 
 <histogram name="Kiosk.LaunchType" enum="KioskLaunchType"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>xiyuan@chromium.org</owner>
   <owner>aghuie@chromium.org</owner>
   <summary>
@@ -67291,7 +67294,7 @@
   </summary>
 </histogram>
 
-<histogram name="LevelDB.Open" enum="LevelDBStatus" expires_after="2020-06-28">
+<histogram name="LevelDB.Open" enum="LevelDBStatus" expires_after="2020-08-30">
   <owner>cmumford@chromium.org</owner>
   <summary>The result of an open attempt of a leveldb.</summary>
 </histogram>
@@ -67556,7 +67559,7 @@
   </summary>
 </histogram>
 
-<histogram name="Linux.Distro" enum="LinuxDistro" expires_after="2020-06-28">
+<histogram name="Linux.Distro" enum="LinuxDistro" expires_after="2020-08-30">
   <owner>thomasanderson@chromium.org</owner>
   <summary>The Linux distro used. Logged on each start up.</summary>
 </histogram>
@@ -68175,7 +68178,7 @@
 </histogram>
 
 <histogram name="Login.TokenCheckResponseTime" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>glevin@chromium.org</owner>
   <summary>
     Time between sending a request to, and receiving a reply from, GAIA token
@@ -102518,7 +102521,7 @@
 </histogram>
 
 <histogram name="NQE.RTT.OnECTComputation" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>bengr@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -116307,7 +116310,7 @@
   </summary>
 </histogram>
 
-<histogram name="PDF.PageCount" units="pages" expires_after="2020-04-19">
+<histogram name="PDF.PageCount" units="pages" expires_after="2020-08-30">
   <owner>hnakashima@chromium.org</owner>
   <summary>
     Tracks the number of pages in PDF documents opened in the PDF viewer.
@@ -124169,7 +124172,7 @@
 </histogram>
 
 <histogram name="Prerender.PrerenderStartToReleaseContentsTime" units="ms"
-    expires_after="2020-06-06">
+    expires_after="2020-08-30">
   <owner>justincohen@chromium.org</owner>
   <summary>
     This is the time from when a prerendered page begins to load to when it is
@@ -124948,7 +124951,7 @@
 </histogram>
 
 <histogram name="Previews.ServerLitePage.PreconnectedToPreviewServer"
-    enum="PreviewOrOriginServerConnection" expires_after="2020-06-28">
+    enum="PreviewOrOriginServerConnection" expires_after="2020-08-30">
   <owner>robertogden@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -124959,7 +124962,7 @@
 </histogram>
 
 <histogram name="Previews.ServerLitePage.PredictorToggled"
-    enum="BooleanToggled" expires_after="2020-06-28">
+    enum="BooleanToggled" expires_after="2020-08-30">
   <owner>robertogden@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -124970,7 +124973,7 @@
 </histogram>
 
 <histogram name="Previews.ServerLitePage.PreresolvedToPreviewServer"
-    enum="PreviewOrOriginServerConnection" expires_after="2020-06-28">
+    enum="PreviewOrOriginServerConnection" expires_after="2020-08-30">
   <owner>robertogden@chromium.org</owner>
   <owner>tbansal@chromium.org</owner>
   <summary>
@@ -125532,7 +125535,7 @@
 </histogram>
 
 <histogram name="PrintPreview.InitializationTime" units="ms"
-    expires_after="2020-04-19">
+    expires_after="2020-08-30">
   <owner>thestig@chromium.org</owner>
   <summary>
     Time from when print preview is intiated until the preview PDF generation is
@@ -125746,7 +125749,7 @@
 </histogram>
 
 <histogram name="PrintPreview.PrintSettings" enum="PrintSettings"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>thestig@chromium.org</owner>
   <summary>
     Track the popularity of print settings. (Settings when printing to PDF are
@@ -126060,7 +126063,7 @@
 </histogram>
 
 <histogram name="Profile.BrowserActive.PerProfile" enum="Profile"
-    expires_after="2020-06-30">
+    expires_after="2020-08-30">
   <owner>msarda@chromium.org</owner>
   <owner>tangltom@chromium.org</owner>
   <summary>
@@ -126627,6 +126630,9 @@
 
 <histogram name="Profile.RemoveUserWarningStatsTime" units="ms"
     expires_after="2020-03-01">
+  <obsolete>
+    Removed 02/2020 since the histogram indicators were stable.
+  </obsolete>
   <owner>dullweber@chromium.org</owner>
   <summary>
     The amount of time that elapsed during profile statistics calculation.
@@ -126634,7 +126640,7 @@
 </histogram>
 
 <histogram name="Profile.SessionDuration.PerProfile" enum="Profile"
-    expires_after="2020-06-30">
+    expires_after="2020-08-30">
   <owner>msarda@chromium.org</owner>
   <owner>tangltom@chromium.org</owner>
   <summary>
@@ -136531,7 +136537,7 @@
 </histogram>
 
 <histogram name="SB2.ResourceTypes2" enum="ContentResourceType2"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>vakh@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -136739,7 +136745,7 @@
 </histogram>
 
 <histogram name="SBClientDownload.ArchiveDownloadExtensions"
-    enum="SBClientDownloadExtensions" expires_after="2020-05-31">
+    enum="SBClientDownloadExtensions" expires_after="2020-08-30">
   <owner>drubery@chromium.org</owner>
   <owner>chrome-safebrowsing-alerts@google.com</owner>
   <summary>
@@ -153363,7 +153369,7 @@
 </histogram>
 
 <histogram name="Stability.Android.StrongBindingOomRemainingStrongBindingCount"
-    units="units" expires_after="2020-06-28">
+    units="units" expires_after="2020-08-30">
   <owner>boliu@chromium.org</owner>
   <owner>ssid@chromium.org</owner>
   <summary>
@@ -153415,7 +153421,7 @@
 </histogram>
 
 <histogram name="Stability.BadMessageTerminated.NaCl"
-    enum="BadMessageReasonNaCl" expires_after="2020-06-28">
+    enum="BadMessageReasonNaCl" expires_after="2020-08-30">
   <owner>creis@chromium.org</owner>
   <owner>jamescook@chromium.org</owner>
   <summary>
@@ -154112,7 +154118,7 @@
 </histogram>
 
 <histogram name="Startup.Android.StartupTabPreloader.TabLoaded" units="Boolean"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>skyostil@chromium.org</owner>
   <summary>
     Android: Whether or not creation of a profile lead to the
@@ -154123,7 +154129,7 @@
 </histogram>
 
 <histogram name="Startup.Android.StartupTabPreloader.TabTaken" units="Boolean"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>skyostil@chromium.org</owner>
   <summary>
     Android: Whether or not a tab speculatively created by the
@@ -154299,7 +154305,7 @@
 </histogram>
 
 <histogram name="Startup.BrowserMessageLoopStart.To.NonEmptyPaint2" units="ms"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>asvitkine@chromium.org</owner>
   <summary>
     Time between Startup.BrowserMessageLoopStartTime and
@@ -155704,7 +155710,7 @@
 </histogram>
 
 <histogram base="true" name="Storage.BytesWritten" units="bytes"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>michaeln@chromium.org</owner>
   <summary>The number of bytes written. Recorded on each write.</summary>
 </histogram>
@@ -157359,7 +157365,7 @@
 </histogram>
 
 <histogram name="Sync.ConfigureDataTypeManagerOption"
-    enum="SyncFeatureOrTransport" expires_after="2020-06-28">
+    enum="SyncFeatureOrTransport" expires_after="2020-08-30">
   <owner>treib@chromium.org</owner>
   <summary>
     Whether the full Sync feature or only the Sync transport layer is being
@@ -157642,7 +157648,7 @@
 </histogram>
 
 <histogram name="Sync.CustomEncryption" enum="SyncCustomEncryptionEvent"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>zea@chromium.org</owner>
   <summary>
     Histogram that keeps track of how users encrypt their sync data. All users
@@ -159748,7 +159754,7 @@
 </histogram>
 
 <histogram name="Sync.StopSource" enum="SyncStopSource"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>treib@chromium.org</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>
@@ -160068,7 +160074,7 @@
 </histogram>
 
 <histogram name="Sync.USSMigrationSuccess" enum="SyncModelTypes"
-    expires_after="2020-06-30">
+    expires_after="2020-08-30">
   <owner>treib@chromium.org</owner>
   <owner>mastiz@chromium.org</owner>
   <summary>Counts directory to USS migration successes per model type.</summary>
@@ -164610,7 +164616,7 @@
 </histogram>
 
 <histogram name="Translate.CLD3.LanguagePercentage" units="%"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>frechette@chromium.org</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -164627,7 +164633,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.AlwaysTranslate"
-    enum="CLD3LanguageCode" expires_after="2020-06-28">
+    enum="CLD3LanguageCode" expires_after="2020-08-30">
   <owner>anthonyvd@chromium.org</owner>
   <summary>
     Records the hashcode of the source language when always translate this
@@ -164636,7 +164642,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.MoreLanguages"
-    enum="CLD3LanguageCode" expires_after="2020-06-28">
+    enum="CLD3LanguageCode" expires_after="2020-08-30">
   <owner>anthonyvd@chromium.org</owner>
   <summary>
     Records the hashcode of the language clicked on the more languages menu.
@@ -164653,7 +164659,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.Language.PageNotIn"
-    enum="CLD3LanguageCode" expires_after="2020-06-28">
+    enum="CLD3LanguageCode" expires_after="2020-08-30">
   <owner>anthonyvd@chromium.org</owner>
   <summary>
     Records the hashcode of the language clicked on the menu to indicate the
@@ -164670,7 +164676,7 @@
 </histogram>
 
 <histogram name="Translate.CompactInfobar.TranslationsPerPage"
-    units="translations" expires_after="2020-06-28">
+    units="translations" expires_after="2020-08-30">
   <owner>anthonyvd@chromium.org</owner>
   <summary>
     Records the number of times a page is translated, every time the page is
@@ -164690,7 +164696,7 @@
 </histogram>
 
 <histogram name="Translate.ContentLanguage" enum="TranslateLanguage"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>kenjibaheux@google.com</owner>
   <summary>
     A page may provide a Content-Language HTTP header or a META tag. For each
@@ -164784,7 +164790,7 @@
 </histogram>
 
 <histogram name="Translate.HtmlLang" enum="TranslateLanguage"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>kenjibaheux@google.com</owner>
   <summary>
     A page may provide a lang attribute in html tag. For each page load,
@@ -164904,7 +164910,7 @@
 </histogram>
 
 <histogram name="Translate.ModifyOriginalLang" units="units"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>kenjibaheux@google.com</owner>
   <summary>
     The number of times the original language in the translate infobar has been
@@ -164922,7 +164928,7 @@
 </histogram>
 
 <histogram name="Translate.NeverTranslateLang" units="units"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>kenjibaheux@google.com</owner>
   <summary>
     The number of times the never translate option was selected in the translate
@@ -164931,7 +164937,7 @@
 </histogram>
 
 <histogram name="Translate.NeverTranslateSite" units="units"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>kenjibaheux@google.com</owner>
   <summary>
     The number of times the never translate site was selected in the translate
@@ -165089,7 +165095,7 @@
 </histogram>
 
 <histogram name="Translate.ShowErrorUI" enum="TranslateError"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>kenjibaheux@google.com</owner>
   <summary>
     Chrome Translate shows an error UI (infobar or bubble) when an error happens
@@ -165121,7 +165127,7 @@
 </histogram>
 
 <histogram name="Translate.TargetLanguage" enum="CLD3LanguageCode"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>yyushkina@google.com</owner>
   <summary>
     The number of requests sent to the Translate server, grouped by target
@@ -165130,7 +165136,7 @@
 </histogram>
 
 <histogram name="Translate.TargetLanguage.Origin"
-    enum="TranslateTargetLanguageOrigin" expires_after="2020-06-28">
+    enum="TranslateTargetLanguageOrigin" expires_after="2020-08-30">
   <owner>megjablon@google.com</owner>
   <owner>chrome-language@google.com</owner>
   <summary>
@@ -165397,7 +165403,7 @@
 </histogram>
 
 <histogram name="UKM.ConsentObserver.AllowedForAllProfiles" enum="Boolean"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>bcwhite@chromium.org</owner>
   <owner>rkaplow@chromium.org</owner>
   <owner>ukm-team@google.com</owner>
@@ -166852,7 +166858,7 @@
 </histogram>
 
 <histogram name="UMA.TruncatedEvents.UserAction" units="events"
-    expires_after="2020-08-23">
+    expires_after="2020-08-30">
   <owner>rkaplow@chromium.org</owner>
   <owner>src/base/metrics/OWNERS</owner>
   <summary>
@@ -168220,7 +168226,7 @@
   </summary>
 </histogram>
 
-<histogram name="V8.CodeCacheSizeRatio" units="%" expires_after="2020-06-28">
+<histogram name="V8.CodeCacheSizeRatio" units="%" expires_after="2020-08-30">
   <owner>yangguo@chromium.org</owner>
   <summary>Cache size to source size ratio when caching compiled code.</summary>
 </histogram>
@@ -168839,7 +168845,7 @@
 </histogram>
 
 <histogram name="V8.GCFinalizeMCReduceMemory" units="ms"
-    expires_after="2020-06-07">
+    expires_after="2020-08-30">
   <owner>ulan@chromium.org</owner>
   <owner>hpayer@chromium.org</owner>
   <summary>
@@ -169005,7 +169011,7 @@
 </histogram>
 
 <histogram name="V8.GCScavengeReason" enum="GarbageCollectionReason"
-    expires_after="2020-06-01">
+    expires_after="2020-08-30">
   <owner>ulan@chromium.org</owner>
   <summary>Reason a scavenge garbage collection was started in V8.</summary>
 </histogram>
@@ -171349,7 +171355,7 @@
 </histogram>
 
 <histogram name="VRSessionNavigationCount" units="units"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -171359,7 +171365,7 @@
   </summary>
 </histogram>
 
-<histogram name="VRSessionTime" units="ms" expires_after="2020-07-01">
+<histogram name="VRSessionTime" units="ms" expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -171381,7 +171387,7 @@
   </summary>
 </histogram>
 
-<histogram name="VRSessionVideoCount" units="units" expires_after="2020-07-01">
+<histogram name="VRSessionVideoCount" units="units" expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -171391,7 +171397,7 @@
   </summary>
 </histogram>
 
-<histogram name="VRSessionVideoTime" units="ms" expires_after="2020-07-01">
+<histogram name="VRSessionVideoTime" units="ms" expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -171402,7 +171408,7 @@
   </summary>
 </histogram>
 
-<histogram name="VRViewerType" enum="VRViewerType" expires_after="2020-07-01">
+<histogram name="VRViewerType" enum="VRViewerType" expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -171716,7 +171722,7 @@
 </histogram>
 
 <histogram name="WebApk.Install.GooglePlayErrorCode"
-    enum="WebApkInstallGooglePlayErrorCode" expires_after="2020-06-28">
+    enum="WebApkInstallGooglePlayErrorCode" expires_after="2020-08-30">
   <owner>hanxi@chromium.org</owner>
   <owner>pkotwicz@chromium.org</owner>
   <owner>yfriedman@chromium.org</owner>
@@ -174513,7 +174519,7 @@
 </histogram>
 
 <histogram name="WebCore.WebSocket.MessageSize.Send" units="bytes"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>yhirano@chromium.org</owner>
   <owner>ricea@chromium.org</owner>
   <summary>
@@ -175945,7 +175951,7 @@
 </histogram>
 
 <histogram name="WebRTC.AudioInputChannelLayout" enum="ChannelLayout"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>henrika@chromium.org</owner>
   <owner>webrtc-audio@google.com</owner>
   <summary>Audio input channel layout in WebRTC.</summary>
@@ -176015,7 +176021,7 @@
 </histogram>
 
 <histogram name="WebRTC.AudioOutputSampleRate" enum="AudioSampleRate"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>henrika@chromium.org</owner>
   <summary>Audio output sample rate for WebRTC (in Hz).</summary>
 </histogram>
@@ -176764,7 +176770,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.Simulcast.ApplyRemoteDescription"
-    enum="SimulcastApiVersion" expires_after="2020-06-28">
+    enum="SimulcastApiVersion" expires_after="2020-08-30">
   <owner>amithi@chromium.org</owner>
   <summary>
     Was simulcast applied to the remote description and with which API surface.
@@ -176781,7 +176787,7 @@
 </histogram>
 
 <histogram name="WebRTC.PeerConnection.Simulcast.NumberOfSendEncodings"
-    units="units" expires_after="2020-06-28">
+    units="units" expires_after="2020-08-30">
   <owner>amithi@chromium.org</owner>
   <summary>
     Counts the number of send encodings given to PeerConnection::AddTransceiver.
@@ -177219,7 +177225,7 @@
 </histogram>
 
 <histogram name="WebRTC.Video.DroppedFrames.Capturer" units="frames"
-    expires_after="2020-06-28">
+    expires_after="2020-08-30">
   <owner>ilnik@chromium.org</owner>
   <summary>
     Total number of frames dropped by a capturer for a sent video stream.
@@ -178460,7 +178466,7 @@
 </histogram>
 
 <histogram base="true" name="WebRtcTextLogging"
-    enum="WebRtcLoggingWebAppIdHash" expires_after="2020-07-01">
+    enum="WebRtcLoggingWebAppIdHash" expires_after="2020-08-30">
   <owner>armax@chromium.org</owner>
   <owner>guidou@chromium.org</owner>
   <summary>
@@ -179887,7 +179893,7 @@
 </histogram>
 
 <histogram name="XR.RuntimeAvailable" enum="XRRuntimeAvailable"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -179907,7 +179913,7 @@
 </histogram>
 
 <histogram name="XR.VRSession.StartAction" enum="VRSessionStartAction"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -179962,7 +179968,7 @@
 </histogram>
 
 <histogram name="XR.WebXR.PresentationSession" enum="VRPresentationStartAction"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
@@ -179984,7 +179990,7 @@
 </histogram>
 
 <histogram name="XR.WebXR.ReferenceSpace.Succeeded" enum="XRReferenceSpaceType"
-    expires_after="2020-07-01">
+    expires_after="2020-08-30">
   <owner>alcooper@chromium.org</owner>
   <owner>cassew@chromium.org</owner>
   <owner>xr-dev@chromium.org</owner>
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
index 106364a..871ffd51 100644
--- a/ui/accessibility/ax_event_generator.cc
+++ b/ui/accessibility/ax_event_generator.cc
@@ -101,23 +101,21 @@
 
 AXEventGenerator::AXEventGenerator(AXTree* tree) : tree_(tree) {
   if (tree_)
-    tree_->AddObserver(this);
+    tree_event_observer_.Add(tree_);
 }
 
-AXEventGenerator::~AXEventGenerator() {
-  if (tree_)
-    tree_->RemoveObserver(this);
-}
+AXEventGenerator::~AXEventGenerator() = default;
 
 void AXEventGenerator::SetTree(AXTree* new_tree) {
   if (tree_)
-    tree_->RemoveObserver(this);
+    tree_event_observer_.Remove(tree_);
   tree_ = new_tree;
   if (tree_)
-    tree_->AddObserver(this);
+    tree_event_observer_.Add(tree_);
 }
 
 void AXEventGenerator::ReleaseTree() {
+  tree_event_observer_.RemoveAll();
   tree_ = nullptr;
 }
 
diff --git a/ui/accessibility/ax_event_generator.h b/ui/accessibility/ax_event_generator.h
index 4016e91..3072ec0 100644
--- a/ui/accessibility/ax_event_generator.h
+++ b/ui/accessibility/ax_event_generator.h
@@ -10,6 +10,7 @@
 #include <set>
 #include <vector>
 
+#include "base/scoped_observer.h"
 #include "ui/accessibility/ax_export.h"
 #include "ui/accessibility/ax_tree.h"
 #include "ui/accessibility/ax_tree_observer.h"
@@ -232,6 +233,10 @@
   // Valid between the call to OnIntAttributeChanged and the call to
   // OnAtomicUpdateFinished. List of nodes whose active descendant changed.
   std::vector<AXNode*> active_descendant_changed_;
+
+  // Please make sure that this ScopedObserver is always declared last in order
+  // to prevent any use-after-free.
+  ScopedObserver<AXTree, AXTreeObserver> tree_event_observer_{this};
 };
 
 AX_EXPORT std::ostream& operator<<(std::ostream& os,
diff --git a/ui/base/clipboard/clipboard_test_template.h b/ui/base/clipboard/clipboard_test_template.h
index eb932c62..4876433a 100644
--- a/ui/base/clipboard/clipboard_test_template.h
+++ b/ui/base/clipboard/clipboard_test_template.h
@@ -466,24 +466,32 @@
       SkImageInfo::Make(1, 1, kRGBA_8888_SkColorType, kOpaque_SkAlphaType),
       &kRGBAOpaque, &kN32Opaque);
 }
-TYPED_TEST(ClipboardTest, DISABLED_Bitmap_BGRA_Premul) {
+#if !defined(OS_ANDROID)
+// TODO(https://crbug.com/1056650): Re-enable these tests after fixing the root
+// cause. This test only fails on Android.
+TYPED_TEST(ClipboardTest, Bitmap_BGRA_Premul) {
   TestBitmapWrite(
       &this->clipboard(),
       SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kPremul_SkAlphaType),
       &kBGRAPremul, &kN32);
 }
+#endif  // !defined(OS_ANDROID)
 TYPED_TEST(ClipboardTest, Bitmap_BGRA_Unpremul) {
   TestBitmapWrite(
       &this->clipboard(),
       SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kUnpremul_SkAlphaType),
       &kBGRAUnpremul, &kN32);
 }
-TYPED_TEST(ClipboardTest, DISABLED_Bitmap_BGRA_Opaque) {
+#if !defined(OS_ANDROID)
+// TODO(https://crbug.com/1056650): Re-enable these tests after fixing the root
+// cause. This test only fails on Android.
+TYPED_TEST(ClipboardTest, Bitmap_BGRA_Opaque) {
   TestBitmapWrite(
       &this->clipboard(),
       SkImageInfo::Make(1, 1, kBGRA_8888_SkColorType, kOpaque_SkAlphaType),
       &kBGRAOpaque, &kN32Opaque);
 }
+#endif  // !defined(OS_ANDROID)
 
 // Used by HTMLCanvasElement.
 TYPED_TEST(ClipboardTest, Bitmap_F16_Premul) {
diff --git a/ui/base/ui_base_features.cc b/ui/base/ui_base_features.cc
index 52d6c6f..444a5ff 100644
--- a/ui/base/ui_base_features.cc
+++ b/ui/base/ui_base_features.cc
@@ -16,6 +16,10 @@
     "CalculateNativeWinOcclusion", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif  // OW_WIN
 
+// Whether or not to delegate color queries to the color provider.
+const base::Feature kColorProviderRedirection = {
+    "ColorProviderRedirection", base::FEATURE_DISABLED_BY_DEFAULT};
+
 #if defined(OS_CHROMEOS)
 // Integrate input method specific settings to Chrome OS settings page.
 // https://crbug.com/895886.
diff --git a/ui/base/ui_base_features.h b/ui/base/ui_base_features.h
index fd6c7b0..87dae9d3 100644
--- a/ui/base/ui_base_features.h
+++ b/ui/base/ui_base_features.h
@@ -15,6 +15,8 @@
 // Keep sorted!
 
 COMPONENT_EXPORT(UI_BASE_FEATURES)
+extern const base::Feature kColorProviderRedirection;
+COMPONENT_EXPORT(UI_BASE_FEATURES)
 extern const base::Feature kCompositorThreadedScrollbarScrolling;
 COMPONENT_EXPORT(UI_BASE_FEATURES)
 extern const base::Feature kExperimentalFlingAnimation;
diff --git a/ui/base/x/x11_display_util.cc b/ui/base/x/x11_display_util.cc
index 803ca3c..fe73b11 100644
--- a/ui/base/x/x11_display_util.cc
+++ b/ui/base/x/x11_display_util.cc
@@ -339,7 +339,6 @@
       if (!display::Display::HasForceDisplayColorProfile()) {
         gfx::ICCProfile icc_profile = ui::GetICCProfileForMonitor(
             monitor_iter == output_to_monitor.end() ? 0 : monitor_iter->second);
-        icc_profile.HistogramDisplay(display.id());
         gfx::ColorSpace color_space = icc_profile.GetPrimariesOnlyColorSpace();
 
         // Most folks do not have an ICC profile set up, but we still want to
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm
index 585d02bf..de59943 100644
--- a/ui/display/mac/screen_mac.mm
+++ b/ui/display/mac/screen_mac.mm
@@ -103,7 +103,6 @@
             CFDataGetBytePtr(cf_icc_profile), CFDataGetLength(cf_icc_profile));
       }
     }
-    icc_profile.HistogramDisplay(display.id());
   }
   gfx::DisplayColorSpaces display_color_spaces(icc_profile.GetColorSpace(),
                                                gfx::BufferFormat::RGBA_8888);
diff --git a/ui/display/win/color_profile_reader.cc b/ui/display/win/color_profile_reader.cc
index 4276beb..212c7fa5 100644
--- a/ui/display/win/color_profile_reader.cc
+++ b/ui/display/win/color_profile_reader.cc
@@ -124,7 +124,6 @@
     DeviceToDataMap device_to_data_map) {
   DCHECK(update_in_flight_);
   update_in_flight_ = false;
-  has_read_profiles_ = true;
 
   display_id_to_profile_map_.clear();
   for (auto entry : device_to_data_map) {
@@ -147,8 +146,6 @@
   auto found = display_id_to_profile_map_.find(display_id);
   if (found != display_id_to_profile_map_.end())
     icc_profile = found->second;
-  if (has_read_profiles_)
-    icc_profile.HistogramDisplay(display_id);
   return icc_profile.IsValid() ? icc_profile.GetPrimariesOnlyColorSpace()
                                : gfx::ColorSpace::CreateSRGB();
 }
diff --git a/ui/display/win/color_profile_reader.h b/ui/display/win/color_profile_reader.h
index d18fde1..417c56b 100644
--- a/ui/display/win/color_profile_reader.h
+++ b/ui/display/win/color_profile_reader.h
@@ -62,9 +62,6 @@
   void ReadProfilesCompleted(DeviceToDataMap device_to_data_map);
 
   Client* const client_ = nullptr;
-  // Set to true once profiles have been read at least once, to avoid
-  // histogramming the default value.
-  bool has_read_profiles_ = false;
   bool update_in_flight_ = false;
   DeviceToPathMap device_to_path_map_;
   std::map<int64_t, gfx::ICCProfile> display_id_to_profile_map_;
diff --git a/ui/events/ozone/evdev/event_device_test_util.cc b/ui/events/ozone/evdev/event_device_test_util.cc
index fc42459a..d28c823 100644
--- a/ui/events/ozone/evdev/event_device_test_util.cc
+++ b/ui/events/ozone/evdev/event_device_test_util.cc
@@ -995,6 +995,39 @@
     base::size(kXboxEliteAxes),
 };
 
+const DeviceAbsoluteAxis kDrallionStylusAxes[] = {
+    {ABS_X, {0, 0, 30931, 0, 0, 100}},
+    {ABS_Y, {0, 0, 17399, 0, 0, 100}},
+    {ABS_PRESSURE, {0, 0, 4095, 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 kDrallionStylus = {
+    /* path */
+    "/sys/devices/pci0000:00/0000:00:15.0/i2c_designware.0/i2c-7/"
+    "i2c-WCOM48E2:00/0018:2D1F:4971.0001/input/input6/event5",
+    /* name */ "WCOM48E2:00 2D1F:4971 Pen",
+    /* phys */ "i2c-WCOM48E2:00",
+    /* uniq */ "",
+    /* bustype */ "0018",
+    /* vendor */ "2d1f",
+    /* product */ "4971",
+    /* version */ "0100",
+    /* prop */ "0",
+    /* ev */ "1b",
+    /* key */ "1c03 0 0 0 0 0",
+    /* rel */ "0",
+    /* abs */ "1000d000003",
+    /* msc */ "11",
+    /* sw */ "0",
+    /* led */ "0",
+    /* ff */ "0",
+    kDrallionStylusAxes,
+    base::size(kDrallionStylusAxes),
+};
+
 // NB: Please use the capture_device_capabilities.py script to add more
 // test data here. This will help ensure the data matches what the kernel
 // reports for a real device and is entered correctly.
diff --git a/ui/events/ozone/evdev/event_device_test_util.h b/ui/events/ozone/evdev/event_device_test_util.h
index 7495a5c..585eb43 100644
--- a/ui/events/ozone/evdev/event_device_test_util.h
+++ b/ui/events/ozone/evdev/event_device_test_util.h
@@ -88,6 +88,7 @@
 extern const DeviceCapabilities kKohakuTouchscreen;
 extern const DeviceCapabilities kKohakuStylus;
 extern const DeviceCapabilities kXboxElite;
+extern const DeviceCapabilities kDrallionStylus;
 }  // namspace ui
 
 #endif  // UI_EVENTS_OZONE_EVDEV_EVENT_DEVICE_TEST_UTIL_H_
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev.cc b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
index 0e0747f..a169706 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev.cc
@@ -364,8 +364,12 @@
     case BTN_TOOL_PEN:
     case BTN_TOOL_RUBBER:
       if (input.value > 0) {
+        if (events_[current_slot_].tool_code != 0)
+          break;
         events_[current_slot_].tool_code = input.code;
       } else {
+        if (events_[current_slot_].tool_code != input.code)
+          break;
         events_[current_slot_].tool_code = 0;
       }
       events_[current_slot_].altered = true;
diff --git a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
index 8d9dd1a..240f9c6 100644
--- a/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
+++ b/ui/events/ozone/evdev/touch_event_converter_evdev_unittest.cc
@@ -1624,6 +1624,55 @@
   EXPECT_EQ(0.f / 1024, event.pointer_details.force);
 }
 
+TEST_F(TouchEventConverterEvdevTest, ActiveStylusDrallionRubberSequence) {
+  ui::MockTouchEventConverterEvdev* dev = device();
+  EventDeviceInfo devinfo;
+  EXPECT_TRUE(CapabilitiesToDeviceInfo(kDrallionStylus, &devinfo));
+  dev->Initialize(devinfo);
+
+
+  struct input_event mock_kernel_queue[] = {
+    {{0, 0}, EV_KEY, BTN_TOOL_RUBBER, 1},
+    {{0, 0}, EV_KEY, BTN_TOUCH, 1},
+    {{0, 0}, EV_KEY, BTN_TOOL_PEN, 1},
+    {{0, 0}, EV_SYN, SYN_REPORT, 0},
+    {{0, 0}, EV_ABS, ABS_X, 4008},
+    {{0, 0}, EV_ABS, ABS_Y, 11247},
+    {{0, 0}, EV_SYN, SYN_REPORT, 0},
+    {{0, 0}, EV_ABS, ABS_X, 4004},
+    {{0, 0}, EV_ABS, ABS_Y, 11248},
+    {{0, 0}, EV_SYN, SYN_REPORT, 0},
+    {{0, 0}, EV_KEY, BTN_TOUCH, 0},
+    {{0, 0}, EV_KEY, BTN_TOOL_PEN, 0},
+    {{0, 0}, EV_KEY, BTN_TOOL_RUBBER, 0},
+    {{0, 0}, EV_SYN, SYN_REPORT, 0},
+  };
+
+  dev->ConfigureReadMock(mock_kernel_queue, base::size(mock_kernel_queue), 0);
+  dev->ReadNow();
+  EXPECT_EQ(4u, size());
+
+  ui::TouchEventParams event = dispatched_touch_event(0);
+  EXPECT_EQ(ET_TOUCH_PRESSED, event.type);
+  EXPECT_EQ(EventPointerType::POINTER_TYPE_ERASER,
+            event.pointer_details.pointer_type);
+
+  event = dispatched_touch_event(1);
+  EXPECT_EQ(ET_TOUCH_MOVED, event.type);
+  EXPECT_EQ(EventPointerType::POINTER_TYPE_ERASER,
+            event.pointer_details.pointer_type);
+
+  event = dispatched_touch_event(2);
+  EXPECT_EQ(ET_TOUCH_MOVED, event.type);
+  EXPECT_EQ(EventPointerType::POINTER_TYPE_ERASER,
+            event.pointer_details.pointer_type);
+
+  event = dispatched_touch_event(3);
+  EXPECT_EQ(ET_TOUCH_RELEASED, event.type);
+  EXPECT_EQ(EventPointerType::POINTER_TYPE_ERASER,
+            event.pointer_details.pointer_type);
+}
+
 TEST_F(TouchEventConverterEvdevTest, ActiveStylusBarrelButtonWhileHovering) {
   ui::MockTouchEventConverterEvdev* dev = device();
   EventDeviceInfo devinfo;
diff --git a/ui/file_manager/file_manager/background/js/launcher.js b/ui/file_manager/file_manager/background/js/launcher.js
index 44200846..84d69f9 100644
--- a/ui/file_manager/file_manager/background/js/launcher.js
+++ b/ui/file_manager/file_manager/background/js/launcher.js
@@ -5,6 +5,7 @@
 /**
  * @type {!Object}
  */
+// eslint-disable-next-line no-var
 var launcher = {};
 
 /**
@@ -82,7 +83,7 @@
     if (type == LaunchType.FOCUS_SAME_OR_CREATE ||
         type == LaunchType.FOCUS_ANY_OR_CREATE) {
       if (opt_appState) {
-        for (var key in window.appWindows) {
+        for (const key in window.appWindows) {
           if (!key.match(FILES_ID_PATTERN)) {
             continue;
           }
@@ -114,7 +115,7 @@
     // Focus any window if none is focused. Try restored first.
     if (type == LaunchType.FOCUS_ANY_OR_CREATE) {
       // If there is already a focused window, then finish.
-      for (var key in window.appWindows) {
+      for (const key in window.appWindows) {
         if (!key.match(FILES_ID_PATTERN)) {
           continue;
         }
@@ -134,7 +135,7 @@
         }
       }
       // Try to focus the first non-minimized window.
-      for (var key in window.appWindows) {
+      for (const key in window.appWindows) {
         if (!key.match(FILES_ID_PATTERN)) {
           continue;
         }
@@ -149,7 +150,7 @@
         }
       }
       // Restore and focus any window.
-      for (var key in window.appWindows) {
+      for (const key in window.appWindows) {
         if (!key.match(FILES_ID_PATTERN)) {
           continue;
         }
@@ -170,6 +171,11 @@
     nextFileManagerWindowID = Math.max(nextFileManagerWindowID, id + 1);
     const appId = FILES_ID_PREFIX + id;
 
+    // Make the files-ng frame color white.
+    if (util.isFilesNg()) {
+      FILE_MANAGER_WINDOW_CREATE_OPTIONS.frame.color = '#ffffff';
+    }
+
     const appWindow = new AppWindowWrapper(
         'main.html', appId, FILE_MANAGER_WINDOW_CREATE_OPTIONS);
     appWindow.launch(opt_appState || {}, false, () => {
diff --git a/ui/file_manager/file_manager/foreground/css/file_manager.css b/ui/file_manager/file_manager/foreground/css/file_manager.css
index 5df009f..fbe9977 100644
--- a/ui/file_manager/file_manager/foreground/css/file_manager.css
+++ b/ui/file_manager/file_manager/foreground/css/file_manager.css
@@ -1674,6 +1674,7 @@
 }
 
 body.files-ng .thumbnail-item .filename-label {
+  font-weight: normal;
   padding-inline-end: 12px;
 }
 
@@ -1713,6 +1714,7 @@
 }
 
 body.files-ng grid .thumbnail-bottom .detail-icon {
+  color: var(--google-grey-700);
   cursor: pointer;
   height: 40px;
   padding-inline-end: 6px;
@@ -1788,6 +1790,7 @@
 }
 
 body.files-ng .thumbnail-grid .img-container {
+  color: var(--google-grey-700);
   height: 120px;
 }
 
@@ -1846,18 +1849,39 @@
   background-color: rgb(232, 246, 253);
 }
 
-/** TODO(lucmult): Style the bg without focus */
 body.files-ng .thumbnail-grid .thumbnail-item[selected] .thumbnail-bottom,
 body.files-ng .thumbnail-grid .thumbnail-item[lead] .thumbnail-bottom,
 body.files-ng .thumbnail-grid .thumbnail-item[selected].directory
     .thumbnail-bottom,
 body.files-ng .thumbnail-grid .thumbnail-item[lead].directory
     .thumbnail-bottom {
+  background-color: var(--google-grey-100);
+}
+
+body.files-ng .thumbnail-grid:focus .thumbnail-item[selected] .thumbnail-bottom,
+body.files-ng .thumbnail-grid:focus .thumbnail-item[lead] .thumbnail-bottom,
+body.files-ng .thumbnail-grid:focus .thumbnail-item[selected].directory
+    .thumbnail-bottom,
+body.files-ng .thumbnail-grid:focus .thumbnail-item[lead].directory
+    .thumbnail-bottom {
   background-color: var(--google-blue-50);
+}
+
+body.check-select.files-ng .thumbnail-grid:focus .thumbnail-item[selected]
+    .thumbnail-bottom,
+body.check-select.files-ng .thumbnail-grid:focus .thumbnail-item[lead]
+    .thumbnail-bottom,
+body.check-select.files-ng .thumbnail-grid:focus
+    .thumbnail-item[selected].directory .thumbnail-bottom,
+body.check-select.files-ng .thumbnail-grid:focus .thumbnail-item[lead].directory
+    .thumbnail-bottom {
   color: var(--google-blue-600);
 }
 
-body.files-ng #list-container .thumbnail-grid .thumbnail-item[lead] {
+body.check-select.files-ng #list-container .thumbnail-grid:focus
+    .thumbnail-item[lead],
+body.files-ng #list-container .thumbnail-grid:focus
+    .thumbnail-item[lead]:not([selected]) {
   /* 2px border: 1px via box-shadow + 1px via border, to accommodate
      the difference between regular border 1px and selected border 2px.*/
   border-color: var(--google-blue-600);
@@ -2184,6 +2208,7 @@
 }
 
 body.files-ng #list-container list li .detail-icon {
+  color: var(--google-grey-700);
   height: 40px;
   margin-inline-end: 16px;
   margin-inline-start: 12px;
@@ -2193,6 +2218,7 @@
 #list-container li .detail-checkmark {
   background-position: center;
   background-repeat: no-repeat;
+  color: var(--google-blue-600);
   height: 28px;
   isolation: isolate;
   opacity: 0;
@@ -2629,6 +2655,7 @@
   -webkit-mask-repeat: no-repeat;
   background-color: currentColor;
   background-image: none;
+  color: var(--google-blue-600);
   height: 40px;
   width: 32px;
 }
diff --git a/ui/file_manager/file_manager/foreground/css/file_types.css b/ui/file_manager/file_manager/foreground/css/file_types.css
index 957f43a..cc80f4b1 100644
--- a/ui/file_manager/file_manager/foreground/css/file_types.css
+++ b/ui/file_manager/file_manager/foreground/css/file_types.css
@@ -31,7 +31,8 @@
 }
 
 body.files-ng [file-type-icon='archive'] {
-  background-image: url(../images/filetype/filetype_archive.svg);
+  -webkit-mask-image: url(../images/filetype/filetype_archive.svg);
+  background-color: currentColor;
 }
 
 [file-type-icon='audio'] {
@@ -321,24 +322,114 @@
   background-repeat: no-repeat;
 }
 
+body.files-ng .no-thumbnail[generic-thumbnail] {
+  -webkit-mask-position: center;
+  -webkit-mask-repeat: no-repeat;
+  -webkit-mask-size: 48px;
+  background-color: transparent;
+  background-image: url(../images/files/ui/filetype_placeholder_generic.svg);
+  background-position: center;
+  background-size: 48px;
+}
+
 [generic-thumbnail='audio'] {
   background-image: -webkit-image-set(
       url(../images/files/ui/filetype_placeholder_audio.png) 1x,
       url(../images/files/ui/2x/filetype_placeholder_audio.png) 2x);
 }
 
+body.files-ng .no-thumbnail[generic-thumbnail='audio'] {
+  background-image: url(../images/filetype/filetype_audio.svg);
+}
+
 [generic-thumbnail='image'] {
   background-image: -webkit-image-set(
       url(../images/files/ui/filetype_placeholder_image.png) 1x,
       url(../images/files/ui/2x/filetype_placeholder_image.png) 2x);
 }
 
+body.files-ng .no-thumbnail[generic-thumbnail='image'],
+body.files-ng .no-thumbnail[generic-thumbnail='raw'] {
+  background-image: url(../images/filetype/filetype_image.svg);
+}
+
 [generic-thumbnail='video'] {
   background-image: -webkit-image-set(
       url(../images/files/ui/filetype_placeholder_video.png) 1x,
       url(../images/files/ui/2x/filetype_placeholder_video.png) 2x);
 }
 
+body.files-ng .no-thumbnail[generic-thumbnail='video'] {
+  background-image: url(../images/filetype/filetype_video.svg);
+}
+
+/* grid view large icons for known types. */
+body.files-ng .no-thumbnail[generic-thumbnail='archive'] {
+  -webkit-mask-image: url(../images/filetype/filetype_archive.svg);
+  background-color: currentColor;
+  background-image: none;
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='tini'] {
+  background-image: url(../images/filetype/filetype_tini.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='excel'] {
+  background-image: url(../images/filetype/filetype_excel.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gdoc'] {
+  background-image: url(../images/filetype/filetype_gdoc.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gdraw'] {
+  background-image: url(../images/filetype/filetype_gdraw.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gsheet'] {
+  background-image: url(../images/filetype/filetype_gsheet.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gslides'] {
+  background-image: url(../images/filetype/filetype_gslides.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gtable'] {
+  background-image: url(../images/filetype/filetype_gtable.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gform'] {
+  background-image: url(../images/filetype/filetype_gform.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gmap'] {
+  background-image: url(../images/filetype/filetype_gmap.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='gsite'] {
+  background-image: url(../images/filetype/filetype_gsite.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='pdf'] {
+  background-image: url(../images/filetype/filetype_pdf.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='ppt'] {
+  background-image: url(../images/filetype/filetype_ppt.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='script'] {
+  background-image: url(../images/filetype/filetype_script.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='sites'] {
+  background-image: url(../images/filetype/filetype_sites.svg);
+}
+
+body.files-ng .no-thumbnail[generic-thumbnail='word'] {
+  background-image: url(../images/filetype/filetype_word.svg);
+}
+
 /* Icons for volume types. A ".tree-row > .file-row" component in any rules
    means the rule only matches in files-ng. */
 
diff --git a/ui/file_manager/file_manager/foreground/images/files/ui/filetype_placeholder_generic.svg b/ui/file_manager/file_manager/foreground/images/files/ui/filetype_placeholder_generic.svg
index fac3367..22da647 100644
--- a/ui/file_manager/file_manager/foreground/images/files/ui/filetype_placeholder_generic.svg
+++ b/ui/file_manager/file_manager/foreground/images/files/ui/filetype_placeholder_generic.svg
@@ -1,3 +1,3 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
-  <path d="M11 2l5 5v10.0044c0 .5499-.4558.9956-1.0025.9956h-9.995C4.4488 18 4 17.5554 4 16.9991V3.001C4 2.448 4.4379 2 5.003 2H11zm-1 2H6v12h8V8h-4V4z"/>
+  <path fill="#5f6368" d="M11 2l5 5v10.0044c0 .5499-.4558.9956-1.0025.9956h-9.995C4.4488 18 4 17.5554 4 16.9991V3.001C4 2.448 4.4379 2 5.003 2H11zm-1 2H6v12h8V8h-4V4z"/>
 </svg>
diff --git a/ui/file_manager/file_manager/foreground/images/filetype/filetype_generic.svg b/ui/file_manager/file_manager/foreground/images/filetype/filetype_generic.svg
index a8095f1..0dc3b46 100644
--- a/ui/file_manager/file_manager/foreground/images/filetype/filetype_generic.svg
+++ b/ui/file_manager/file_manager/foreground/images/filetype/filetype_generic.svg
@@ -1,3 +1,3 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
-  <path d="M11 2l5 5v10a1 1 0 01-1 1H5a1 1 0 01-1-1V3a1 1 0 011-1h6zm-1 2H6v12h8V8h-4V4z"/>
+  <path fill="#5f6368" d="M11 2l5 5v10a1 1 0 01-1 1H5a1 1 0 01-1-1V3a1 1 0 011-1h6zm-1 2H6v12h8V8h-4V4z"/>
 </svg>
diff --git a/ui/file_manager/file_manager/foreground/js/ui/file_grid.js b/ui/file_manager/file_manager/foreground/js/ui/file_grid.js
index 334974d2..516d475 100644
--- a/ui/file_manager/file_manager/foreground/js/ui/file_grid.js
+++ b/ui/file_manager/file_manager/foreground/js/ui/file_grid.js
@@ -222,8 +222,8 @@
                 .contentMimeType;
         if (!event.dataUrl) {
           FileGrid.clearThumbnailImage_(assertInstanceof(box, HTMLDivElement));
-          FileGrid.setGenericThumbnail_(
-              assertInstanceof(box, HTMLDivElement), entry);
+          this.setGenericThumbnail_(
+              assertInstanceof(box, HTMLDivElement), entry, mimeType);
         } else {
           FileGrid.setThumbnailImage_(
               assertInstanceof(box, HTMLDivElement), entry,
@@ -612,24 +612,28 @@
     li.appendChild(frame);
 
     const box = li.ownerDocument.createElement('div');
-    box.className = 'img-container';
+    box.classList.add('img-container', 'no-thumbnail');
     frame.appendChild(box);
     if (entry) {
       this.decorateThumbnailBox_(assertInstanceof(li, HTMLLIElement), entry);
     }
 
-    const shield = li.ownerDocument.createElement('div');
-    shield.className = 'shield';
-    frame.appendChild(shield);
+    if (!util.isFilesNg()) {
+      const shield = li.ownerDocument.createElement('div');
+      shield.className = 'shield';
+      frame.appendChild(shield);
+    }
 
     const isDirectory = entry && entry.isDirectory;
     if (!isDirectory) {
-      const activeCheckmark = li.ownerDocument.createElement('div');
-      activeCheckmark.className = 'checkmark active';
-      frame.appendChild(activeCheckmark);
-      const inactiveCheckmark = li.ownerDocument.createElement('div');
-      inactiveCheckmark.className = 'checkmark inactive';
-      frame.appendChild(inactiveCheckmark);
+      if (!util.isFilesNg()) {
+        const activeCheckmark = li.ownerDocument.createElement('div');
+        activeCheckmark.className = 'checkmark active';
+        frame.appendChild(activeCheckmark);
+        const inactiveCheckmark = li.ownerDocument.createElement('div');
+        inactiveCheckmark.className = 'checkmark inactive';
+        frame.appendChild(inactiveCheckmark);
+      }
     }
 
     const badge = li.ownerDocument.createElement('div');
@@ -644,7 +648,9 @@
     const locationInfo = this.volumeManager_.getLocationInfo(entry);
     const detailIcon = filelist.renderFileTypeIcon(
         li.ownerDocument, entry, locationInfo, mimeType);
-    if (isDirectory) {
+
+    // For FilesNg we add the checkmark in the same location.
+    if (isDirectory || util.isFilesNg()) {
       const checkmark = li.ownerDocument.createElement('div');
       checkmark.className = 'detail-checkmark';
       detailIcon.appendChild(checkmark);
@@ -676,7 +682,7 @@
     }
 
     if (entry.isDirectory) {
-      FileGrid.setGenericThumbnail_(box, entry);
+      this.setGenericThumbnail_(box, entry);
       return;
     }
 
@@ -685,21 +691,19 @@
     const thumbnailData = this.listThumbnailLoader_ ?
         this.listThumbnailLoader_.getThumbnailFromCache(entry) :
         null;
+    const mimeType =
+        this.metadataModel_.getCache([entry], ['contentMimeType'])[0]
+            .contentMimeType;
     if (thumbnailData && thumbnailData.dataUrl) {
-      const mimeType =
-          this.metadataModel_.getCache([entry], ['contentMimeType'])[0]
-              .contentMimeType;
       FileGrid.setThumbnailImage_(
           box, entry, thumbnailData.dataUrl, (thumbnailData.width || 0),
           (thumbnailData.height || 0), mimeType);
       li.classList.toggle('thumbnail-loaded', true);
     } else {
-      FileGrid.setGenericThumbnail_(box, entry);
+      this.setGenericThumbnail_(box, entry, mimeType);
       li.classList.toggle('thumbnail-loaded', false);
     }
-    const mimeType =
-        this.metadataModel_.getCache([entry], ['contentMimeType'])[0]
-            .contentMimeType;
+
     li.classList.toggle(
         'can-hide-filename',
         FileType.isImage(entry, mimeType) || FileType.isRaw(entry, mimeType));
@@ -766,6 +770,7 @@
   static setThumbnailImage_(box, entry, dataUrl, width, height, opt_mimeType) {
     const thumbnail = box.ownerDocument.createElement('div');
     thumbnail.classList.add('thumbnail');
+    box.classList.toggle('no-thumbnail', false);
 
     // If the image is JPEG or the thumbnail is larger than the grid size,
     // resize it to cover the thumbnail box.
@@ -796,6 +801,7 @@
     for (let i = 0; i < oldThumbnails.length; i++) {
       box.removeChild(oldThumbnails[i]);
     }
+    box.classList.toggle('no-thumbnail', true);
     return;
   }
 
@@ -803,14 +809,23 @@
    * Sets a generic thumbnail on the box.
    * @param {!HTMLDivElement} box A div element to hold thumbnails.
    * @param {!Entry} entry An entry of the thumbnail.
+   * @param {string=} opt_mimeType Optional mime type for the file.
    * @private
    */
-  static setGenericThumbnail_(box, entry) {
+  setGenericThumbnail_(box, entry, opt_mimeType) {
     if (entry.isDirectory) {
       box.setAttribute('generic-thumbnail', 'folder');
     } else {
-      const mediaType = FileType.getMediaType(entry);
-      box.setAttribute('generic-thumbnail', mediaType);
+      if (!util.isFilesNg()) {
+        const mediaType = FileType.getMediaType(entry);
+        box.setAttribute('generic-thumbnail', mediaType);
+      } else {
+        box.classList.toggle('no-thumbnail', true);
+        const locationInfo = this.volumeManager_.getLocationInfo(entry);
+        const icon =
+            FileType.getIcon(entry, opt_mimeType, locationInfo.rootType);
+        box.setAttribute('generic-thumbnail', icon);
+      }
     }
   }
 
diff --git a/ui/file_manager/integration_tests/file_manager/breadcrumbs.js b/ui/file_manager/integration_tests/file_manager/breadcrumbs.js
index a61506a..25e417f 100644
--- a/ui/file_manager/integration_tests/file_manager/breadcrumbs.js
+++ b/ui/file_manager/integration_tests/file_manager/breadcrumbs.js
@@ -121,6 +121,12 @@
     const appId = await setupAndWaitUntilReady(
         RootPath.DOWNLOADS, nestedFolderTestEntries, []);
 
+    // files-ng bail out: it does not have breadcrumb tooltips.
+    const body = await remoteCall.waitForElement(appId, 'body');
+    if (body.attributes['class'] === 'files-ng') {
+      return;
+    }
+
     // Navigate to deepest folder.
     const breadcrumb = '/My files/Downloads/' +
         nestedFolderTestEntries.map(e => e.nameText).join('/');
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.js
index 76228df..c84ad6dd 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.js
@@ -22,6 +22,30 @@
   };
 
   /**
+   * Waits for Quick View dialog to be open.
+   *
+   * @param {string} appId Files app windowId.
+   */
+  async function waitQuickViewOpen(appId) {
+    const caller = getCaller();
+
+    function checkQuickViewElementsDisplayBlock(elements) {
+      const haveElements = Array.isArray(elements) && elements.length !== 0;
+      if (!haveElements || elements[0].styles.display !== 'block') {
+        return pending(caller, 'Waiting for Quick View to open.');
+      }
+      return;
+    }
+
+    await repeatUntil(async () => {
+      const elements = ['#quick-view', '#dialog[open]'];
+      return checkQuickViewElementsDisplayBlock(
+          await remoteCall.callRemoteTestUtil(
+              'deepQueryAllElements', appId, [elements, ['display']]));
+    });
+  }
+
+  /**
    * Waits for Quick View dialog to be closed.
    *
    * @param {string} appId Files app windowId.
@@ -53,16 +77,6 @@
    * @param {string} name File name.
    */
   async function openQuickView(appId, name) {
-    const caller = getCaller();
-
-    function checkQuickViewElementsDisplayBlock(elements) {
-      const haveElements = Array.isArray(elements) && elements.length !== 0;
-      if (!haveElements || elements[0].styles.display !== 'block') {
-        return pending(caller, 'Waiting for Quick View to open.');
-      }
-      return;
-    }
-
     // Select file |name| in the file list.
     chrome.test.assertTrue(
         !!await remoteCall.callRemoteTestUtil('selectFile', appId, [name]),
@@ -75,12 +89,7 @@
         'fakeKeyDown failed');
 
     // Check: the Quick View dialog should be shown.
-    await repeatUntil(async () => {
-      const elements = ['#quick-view', '#dialog[open]'];
-      return checkQuickViewElementsDisplayBlock(
-          await remoteCall.callRemoteTestUtil(
-              'deepQueryAllElements', appId, [elements, ['display']]));
-    });
+    return waitQuickViewOpen(appId);
   }
 
   /**
@@ -105,17 +114,7 @@
     await remoteCall.waitAndClickElement(appId, getInfoMenuItem);
 
     // Check: the Quick View dialog should be shown.
-    const caller = getCaller();
-    await repeatUntil(async () => {
-      const query = ['#quick-view', '#dialog[open]'];
-      const elements = await remoteCall.callRemoteTestUtil(
-          'deepQueryAllElements', appId, [query, ['display']]);
-      const haveElements = Array.isArray(elements) && elements.length !== 0;
-      if (!haveElements || elements[0].styles.display !== 'block') {
-        return pending(caller, 'Waiting for Quick View to open.');
-      }
-      return true;
-    });
+    await waitQuickViewOpen(appId);
   }
 
   /**
@@ -126,16 +125,6 @@
    * @param {Array<string>} names File names.
    */
   async function openQuickViewMultipleSelection(appId, names) {
-    const caller = getCaller();
-
-    function checkQuickViewElementsDisplayBlock(elements) {
-      const haveElements = Array.isArray(elements) && elements.length !== 0;
-      if (!haveElements || elements[0].styles.display !== 'block') {
-        return pending(caller, 'Waiting for Quick View to open.');
-      }
-      return;
-    }
-
     // Get the file-list rows that are check-selected (multi-selected).
     const selectedRows = await remoteCall.callRemoteTestUtil(
         'deepQueryAllElements', appId, ['#file-list li[selected]']);
@@ -152,12 +141,7 @@
     await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, space);
 
     // Check: the Quick View dialog should be shown.
-    await repeatUntil(async () => {
-      const elements = ['#quick-view', '#dialog[open]'];
-      return checkQuickViewElementsDisplayBlock(
-          await remoteCall.callRemoteTestUtil(
-              'deepQueryAllElements', appId, [elements, ['display']]));
-    });
+    await waitQuickViewOpen(appId);
   }
 
   /**
@@ -2222,25 +2206,12 @@
         !!await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, ctrlA),
         'Ctrl+A failed');
 
-    function checkQuickViewElementsDisplayBlock(elements) {
-      const haveElements = Array.isArray(elements) && elements.length !== 0;
-      if (!haveElements || elements[0].styles.display !== 'block') {
-        return pending(caller, 'Waiting for Quick View to open.');
-      }
-      return;
-    }
-
     // Open Quick View via its keyboard shortcut.
     const space = ['#file-list', ' ', false, false, false];
     await remoteCall.callRemoteTestUtil('fakeKeyDown', appId, space);
 
     // Check: the Quick View dialog should be shown.
-    await repeatUntil(async () => {
-      const elements = ['#quick-view', '#dialog[open]'];
-      return checkQuickViewElementsDisplayBlock(
-          await remoteCall.callRemoteTestUtil(
-              'deepQueryAllElements', appId, [elements, ['display']]));
-    });
+    await waitQuickViewOpen(appId);
 
     // Press the up arrow to go to the last file in the selection.
     const quickViewArrowUp = ['#quick-view', 'ArrowUp', false, false, false];
diff --git a/ui/gfx/icc_profile.cc b/ui/gfx/icc_profile.cc
index 0a8bc69d..9fb66a9 100644
--- a/ui/gfx/icc_profile.cc
+++ b/ui/gfx/icc_profile.cc
@@ -10,7 +10,6 @@
 #include "base/command_line.h"
 #include "base/containers/mru_cache.h"
 #include "base/lazy_instance.h"
-#include "base/metrics/histogram_macros.h"
 #include "base/synchronization/lock.h"
 #include "third_party/skia/include/core/SkColorSpace.h"
 #include "third_party/skia/include/third_party/skcms/skcms.h"
@@ -38,16 +37,16 @@
 
 }  // namespace
 
-ICCProfile::Internals::AnalyzeResult ICCProfile::Internals::Initialize() {
+void ICCProfile::Internals::Initialize() {
   // Start out with no parametric data.
   if (data_.empty())
-    return kICCNoProfile;
+    return;
 
   // Parse the profile.
   skcms_ICCProfile profile;
   if (!skcms_Parse(data_.data(), data_.size(), &profile)) {
     DLOG(ERROR) << "Failed to parse ICC profile.";
-    return kICCFailedToParse;
+    return;
   }
 
   // We have seen many users with profiles that don't have a D50 white point.
@@ -64,9 +63,13 @@
   if (fabsf(wX - kD50_WhitePoint[0]) > 0.04f ||
       fabsf(wY - kD50_WhitePoint[1]) > 0.04f ||
       fabsf(wZ - kD50_WhitePoint[2]) > 0.04f) {
-    return kICCFailedToParse;
+    return;
   }
 
+  // At this point, the profile is considered valid. We still need to determine
+  // if it's representable with a parametric transfer function.
+  is_valid_ = true;
+
   // Extract the primary matrix, and assume that transfer function is sRGB until
   // we get something more precise.
   to_XYZD50_ = profile.toXYZD50;
@@ -78,7 +81,7 @@
   if (!skcms_MakeUsableAsDestinationWithSingleCurve(&profile)) {
     DLOG(ERROR) << "Parsed ICC profile but can't make usable as destination, "
                    "using sRGB gamma";
-    return kICCFailedToMakeUsable;
+    return;
   }
 
   // If SkColorSpace will treat the gamma as that of sRGB, then use the named
@@ -87,10 +90,14 @@
   if (!sk_color_space) {
     DLOG(ERROR) << "Parsed ICC profile but cannot create SkColorSpace from it, "
                    "using sRGB gamma.";
-    return kICCFailedToMakeUsable;
+    return;
   }
+
+  // We were able to get a parametric representation of the transfer function.
+  is_parametric_ = true;
+
   if (sk_color_space->gammaCloseToSRGB())
-    return kICCExtractedMatrixAndTrFn;
+    return;
 
   // We assume that if we accurately approximated the profile, then the
   // single-curve version (which may have higher error) is also okay. If we
@@ -98,7 +105,6 @@
   // we could check to see if the single-curve version is/ approximately equal
   // to the original (or to the multi-channel approximation).
   transfer_fn_ = profile.trc[0].parametric;
-  return kICCExtractedMatrixAndTrFn;
 }
 
 ICCProfile::ICCProfile() = default;
@@ -212,52 +218,10 @@
 
 ICCProfile::Internals::Internals(std::vector<char> data)
     : data_(std::move(data)) {
-  // Early out for empty entries.
-  if (data_.empty())
-    return;
-
   // Parse the ICC profile
-  analyze_result_ = Initialize();
-  switch (analyze_result_) {
-    case kICCExtractedMatrixAndTrFn:
-      // Successfully and accurately extracted color space.
-      is_valid_ = true;
-      is_parametric_ = true;
-      break;
-    case kICCFailedToMakeUsable:
-      // We have a usable gamut, but the transfer function may be messed up.
-      is_valid_ = true;
-      is_parametric_ = false;
-      break;
-    case kICCFailedToParse:
-    case kICCNoProfile:
-      // We can't use anything from this profile.
-      is_valid_ = false;
-      is_parametric_ = false;
-      break;
-  }
+  Initialize();
 }
 
 ICCProfile::Internals::~Internals() {}
 
-void ICCProfile::HistogramDisplay(int64_t display_id) const {
-  if (!internals_) {
-    // If this is an uninitialized profile, histogram it using an empty profile,
-    // so that we only histogram this display as empty once.
-    FromData(nullptr, 0).HistogramDisplay(display_id);
-  } else {
-    internals_->HistogramDisplay(display_id);
-  }
-}
-
-void ICCProfile::Internals::HistogramDisplay(int64_t display_id) {
-  // Ensure that we histogram this profile only once per display id.
-  if (histogrammed_display_ids_.count(display_id))
-    return;
-  histogrammed_display_ids_.insert(display_id);
-
-  UMA_HISTOGRAM_ENUMERATION("Blink.ColorSpace.Destination.ICCResult",
-                            analyze_result_);
-}
-
 }  // namespace gfx
diff --git a/ui/gfx/icc_profile.h b/ui/gfx/icc_profile.h
index 1025537..bbd64d1 100644
--- a/ui/gfx/icc_profile.h
+++ b/ui/gfx/icc_profile.h
@@ -65,30 +65,13 @@
   // Return the data for the profile.
   std::vector<char> GetData() const;
 
-  // Histogram how we this was approximated by a gfx::ColorSpace. Only
-  // histogram a given profile once per display.
-  void HistogramDisplay(int64_t display_id) const;
-
  private:
   class Internals : public base::RefCountedThreadSafe<ICCProfile::Internals> {
    public:
     explicit Internals(std::vector<char>);
-    void HistogramDisplay(int64_t display_id);
-
-    // This must match ICCProfileAnalyzeResult enum in histograms.xml.
-    enum AnalyzeResult {
-      kICCFailedToParse = 5,
-      kICCNoProfile = 10,
-      kICCFailedToMakeUsable = 11,
-      kICCExtractedMatrixAndTrFn = 12,
-      kMaxValue = kICCExtractedMatrixAndTrFn,
-    };
 
     const std::vector<char> data_;
 
-    // The result of attepting to extract a color space from the color profile.
-    AnalyzeResult analyze_result_ = kICCNoProfile;
-
     // True iff we can create a valid ColorSpace (and ColorTransform) from this
     // object. The transform may be LUT-based (using an SkColorSpaceXform to
     // compute the lut).
@@ -103,15 +86,9 @@
     skcms_Matrix3x3 to_XYZD50_;
     skcms_TransferFunction transfer_fn_;
 
-    // The set of display ids which have have caused this ICC profile to be
-    // recorded in UMA histograms. Only record an ICC profile once per display
-    // id (since the same profile will be re-read repeatedly, e.g, when displays
-    // are resized).
-    std::set<int64_t> histogrammed_display_ids_;
-
    protected:
     friend class base::RefCountedThreadSafe<ICCProfile::Internals>;
-    AnalyzeResult Initialize();
+    void Initialize();
     virtual ~Internals();
   };
   scoped_refptr<Internals> internals_;
diff --git a/ui/native_theme/BUILD.gn b/ui/native_theme/BUILD.gn
index 6de31e8..adc24fc 100644
--- a/ui/native_theme/BUILD.gn
+++ b/ui/native_theme/BUILD.gn
@@ -22,6 +22,7 @@
     "native_theme.h",
     "native_theme_base.cc",
     "native_theme_base.h",
+    "native_theme_color_id.h",
     "native_theme_export.h",
     "native_theme_features.cc",
     "native_theme_features.h",
@@ -123,7 +124,7 @@
 }
 
 test("native_theme_unittests") {
-  sources = []
+  sources = [ "native_theme_unittest.cc" ]
 
   if (use_aura) {
     sources += [ "native_theme_aura_unittest.cc" ]
diff --git a/ui/native_theme/native_theme.h b/ui/native_theme/native_theme.h
index 374bf1f..cd1742d 100644
--- a/ui/native_theme/native_theme.h
+++ b/ui/native_theme/native_theme.h
@@ -18,6 +18,7 @@
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/native_theme/caption_style.h"
+#include "ui/native_theme/native_theme_color_id.h"
 #include "ui/native_theme/native_theme_export.h"
 #include "ui/native_theme/native_theme_observer.h"
 
@@ -320,121 +321,9 @@
 
   // Colors for GetSystemColor().
   enum ColorId {
-    // Windows
-    kColorId_WindowBackground,
-    // Dialogs
-    kColorId_DialogBackground,
-    kColorId_DialogForeground,
-    kColorId_BubbleBackground,
-    kColorId_BubbleFooterBackground,
-    // FocusableBorder
-    kColorId_FocusedBorderColor,
-    kColorId_UnfocusedBorderColor,
-    // Button
-    kColorId_ButtonBorderColor,
-    kColorId_ButtonEnabledColor,
-    kColorId_ButtonDisabledColor,
-    kColorId_ButtonHoverColor,
-    kColorId_ButtonInkDropFillColor,
-    kColorId_ButtonInkDropShadowColor,
-    kColorId_ButtonPressedShade,
-    kColorId_ButtonUncheckedColor,
-    kColorId_ProminentButtonColor,
-    kColorId_ProminentButtonDisabledColor,
-    kColorId_ProminentButtonFocusedColor,
-    kColorId_ProminentButtonHoverColor,
-    kColorId_ProminentButtonInkDropShadowColor,
-    kColorId_ProminentButtonInkDropFillColor,
-    kColorId_TextOnProminentButtonColor,
-    // ToggleButton
-    kColorId_ToggleButtonShadowColor,
-    kColorId_ToggleButtonTrackColorOff,
-    kColorId_ToggleButtonTrackColorOn,
-    // MenuItem
-    kColorId_EnabledMenuItemForegroundColor,
-    kColorId_DisabledMenuItemForegroundColor,
-    kColorId_SelectedMenuItemForegroundColor,
-    kColorId_FocusedMenuItemBackgroundColor,
-    kColorId_MenuDropIndicator,
-    kColorId_MenuItemMinorTextColor,
-    kColorId_MenuSeparatorColor,
-    kColorId_MenuBackgroundColor,
-    kColorId_MenuBorderColor,
-    kColorId_HighlightedMenuItemBackgroundColor,
-    kColorId_HighlightedMenuItemForegroundColor,
-    kColorId_MenuItemAlertBackgroundColor,
-    // Dropdown
-    kColorId_DropdownBackgroundColor,
-    kColorId_DropdownForegroundColor,
-    kColorId_DropdownSelectedBackgroundColor,
-    kColorId_DropdownSelectedForegroundColor,
-    // Label
-    kColorId_LabelEnabledColor,
-    kColorId_LabelDisabledColor,
-    kColorId_LabelSecondaryColor,
-    kColorId_LabelTextSelectionColor,
-    kColorId_LabelTextSelectionBackgroundFocused,
-    // Link
-    kColorId_LinkDisabled,
-    kColorId_LinkEnabled,
-    kColorId_LinkPressed,
-    kColorId_OverlayScrollbarThumbBackground,
-    kColorId_OverlayScrollbarThumbForeground,
-    // Slider
-    kColorId_SliderThumbDefault,
-    kColorId_SliderTroughDefault,
-    kColorId_SliderThumbMinimal,
-    kColorId_SliderTroughMinimal,
-    // Separator
-    kColorId_SeparatorColor,
-    // TabbedPane
-    kColorId_TabTitleColorActive,
-    kColorId_TabTitleColorInactive,
-    kColorId_TabBottomBorder,
-    kColorId_TabHighlightBackground,
-    kColorId_TabHighlightFocusedBackground,
-    // Textfield
-    kColorId_TextfieldDefaultColor,
-    kColorId_TextfieldDefaultBackground,
-    kColorId_TextfieldReadOnlyColor,
-    kColorId_TextfieldReadOnlyBackground,
-    kColorId_TextfieldSelectionColor,
-    kColorId_TextfieldSelectionBackgroundFocused,
-    // Tooltip
-    kColorId_TooltipBackground,
-    kColorId_TooltipIcon,
-    kColorId_TooltipIconHovered,
-    kColorId_TooltipText,
-    // Tree
-    kColorId_TreeBackground,
-    kColorId_TreeText,
-    kColorId_TreeSelectedText,
-    kColorId_TreeSelectedTextUnfocused,
-    kColorId_TreeSelectionBackgroundFocused,
-    kColorId_TreeSelectionBackgroundUnfocused,
-    // Table
-    kColorId_TableBackground,
-    kColorId_TableText,
-    kColorId_TableSelectedText,
-    kColorId_TableSelectedTextUnfocused,
-    kColorId_TableSelectionBackgroundFocused,
-    kColorId_TableSelectionBackgroundUnfocused,
-    kColorId_TableGroupingIndicatorColor,
-    // Table Header
-    kColorId_TableHeaderText,
-    kColorId_TableHeaderBackground,
-    kColorId_TableHeaderSeparator,
-    // Colors for the material spinner (aka throbber).
-    kColorId_ThrobberSpinningColor,
-    kColorId_ThrobberWaitingColor,
-    kColorId_ThrobberLightColor,
-    // Colors for icons that alert, e.g. upgrade reminders.
-    kColorId_AlertSeverityLow,
-    kColorId_AlertSeverityMedium,
-    kColorId_AlertSeverityHigh,
-    // Colors for icons in secondary UI (content settings, help button, etc).
-    kColorId_DefaultIconColor,
-    // TODO(benrg): move other hardcoded colors here.
+#define OP(enum_name) enum_name
+    NATIVE_THEME_COLOR_IDS,
+#undef OP
 
     kColorId_NumColors,
   };
diff --git a/ui/native_theme/native_theme_color_id.h b/ui/native_theme/native_theme_color_id.h
new file mode 100644
index 0000000..72f1ea6
--- /dev/null
+++ b/ui/native_theme/native_theme_color_id.h
@@ -0,0 +1,127 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_NATIVE_THEME_NATIVE_THEME_COLOR_ID_H_
+#define UI_NATIVE_THEME_NATIVE_THEME_COLOR_ID_H_
+
+// Clang format mangles sectioned lists like the below badly.
+// clang-format off
+#define NATIVE_THEME_COLOR_IDS                                                 \
+  /* Windows */                                                                \
+  OP(kColorId_WindowBackground),                                               \
+  /* Dialogs */                                                                \
+  OP(kColorId_DialogBackground),                                               \
+  OP(kColorId_DialogForeground),                                               \
+  OP(kColorId_BubbleBackground),                                               \
+  OP(kColorId_BubbleFooterBackground),                                         \
+  /* FocusableBorder */                                                        \
+  OP(kColorId_FocusedBorderColor),                                             \
+  OP(kColorId_UnfocusedBorderColor),                                           \
+  /* Button */                                                                 \
+  OP(kColorId_ButtonBorderColor),                                              \
+  OP(kColorId_ButtonEnabledColor),                                             \
+  OP(kColorId_ButtonDisabledColor),                                            \
+  OP(kColorId_ButtonHoverColor),                                               \
+  OP(kColorId_ButtonInkDropFillColor),                                         \
+  OP(kColorId_ButtonInkDropShadowColor),                                       \
+  OP(kColorId_ButtonPressedShade),                                             \
+  OP(kColorId_ButtonUncheckedColor),                                           \
+  OP(kColorId_ProminentButtonColor),                                           \
+  OP(kColorId_ProminentButtonDisabledColor),                                   \
+  OP(kColorId_ProminentButtonFocusedColor),                                    \
+  OP(kColorId_ProminentButtonHoverColor),                                      \
+  OP(kColorId_ProminentButtonInkDropShadowColor),                              \
+  OP(kColorId_ProminentButtonInkDropFillColor),                                \
+  OP(kColorId_TextOnProminentButtonColor),                                     \
+  /* ToggleButton */                                                           \
+  OP(kColorId_ToggleButtonShadowColor),                                        \
+  OP(kColorId_ToggleButtonTrackColorOff),                                      \
+  OP(kColorId_ToggleButtonTrackColorOn),                                       \
+  /* MenuItem */                                                               \
+  OP(kColorId_EnabledMenuItemForegroundColor),                                 \
+  OP(kColorId_DisabledMenuItemForegroundColor),                                \
+  OP(kColorId_SelectedMenuItemForegroundColor),                                \
+  OP(kColorId_FocusedMenuItemBackgroundColor),                                 \
+  OP(kColorId_MenuDropIndicator),                                              \
+  OP(kColorId_MenuItemMinorTextColor),                                         \
+  OP(kColorId_MenuSeparatorColor),                                             \
+  OP(kColorId_MenuBackgroundColor),                                            \
+  OP(kColorId_MenuBorderColor),                                                \
+  OP(kColorId_HighlightedMenuItemBackgroundColor),                             \
+  OP(kColorId_HighlightedMenuItemForegroundColor),                             \
+  OP(kColorId_MenuItemAlertBackgroundColor),                                   \
+  /* Dropdown */                                                               \
+  OP(kColorId_DropdownBackgroundColor),                                        \
+  OP(kColorId_DropdownForegroundColor),                                        \
+  OP(kColorId_DropdownSelectedBackgroundColor),                                \
+  OP(kColorId_DropdownSelectedForegroundColor),                                \
+  /* Label */                                                                  \
+  OP(kColorId_LabelEnabledColor),                                              \
+  OP(kColorId_LabelDisabledColor),                                             \
+  OP(kColorId_LabelSecondaryColor),                                            \
+  OP(kColorId_LabelTextSelectionColor),                                        \
+  OP(kColorId_LabelTextSelectionBackgroundFocused),                            \
+  /* Link */                                                                   \
+  OP(kColorId_LinkDisabled),                                                   \
+  OP(kColorId_LinkEnabled),                                                    \
+  OP(kColorId_LinkPressed),                                                    \
+  OP(kColorId_OverlayScrollbarThumbBackground),                                \
+  OP(kColorId_OverlayScrollbarThumbForeground),                                \
+  /* Slider */                                                                 \
+  OP(kColorId_SliderThumbDefault),                                             \
+  OP(kColorId_SliderTroughDefault),                                            \
+  OP(kColorId_SliderThumbMinimal),                                             \
+  OP(kColorId_SliderTroughMinimal),                                            \
+  /* Separator */                                                              \
+  OP(kColorId_SeparatorColor),                                                 \
+  /* TabbedPane */                                                             \
+  OP(kColorId_TabTitleColorActive),                                            \
+  OP(kColorId_TabTitleColorInactive),                                          \
+  OP(kColorId_TabBottomBorder),                                                \
+  OP(kColorId_TabHighlightBackground),                                         \
+  OP(kColorId_TabHighlightFocusedBackground),                                  \
+  /* Textfield */                                                              \
+  OP(kColorId_TextfieldDefaultColor),                                          \
+  OP(kColorId_TextfieldDefaultBackground),                                     \
+  OP(kColorId_TextfieldReadOnlyColor),                                         \
+  OP(kColorId_TextfieldReadOnlyBackground),                                    \
+  OP(kColorId_TextfieldSelectionColor),                                        \
+  OP(kColorId_TextfieldSelectionBackgroundFocused),                            \
+  /* Tooltip */                                                                \
+  OP(kColorId_TooltipBackground),                                              \
+  OP(kColorId_TooltipIcon),                                                    \
+  OP(kColorId_TooltipIconHovered),                                             \
+  OP(kColorId_TooltipText),                                                    \
+  /* Tree */                                                                   \
+  OP(kColorId_TreeBackground),                                                 \
+  OP(kColorId_TreeText),                                                       \
+  OP(kColorId_TreeSelectedText),                                               \
+  OP(kColorId_TreeSelectedTextUnfocused),                                      \
+  OP(kColorId_TreeSelectionBackgroundFocused),                                 \
+  OP(kColorId_TreeSelectionBackgroundUnfocused),                               \
+  /* Table */                                                                  \
+  OP(kColorId_TableBackground),                                                \
+  OP(kColorId_TableText),                                                      \
+  OP(kColorId_TableSelectedText),                                              \
+  OP(kColorId_TableSelectedTextUnfocused),                                     \
+  OP(kColorId_TableSelectionBackgroundFocused),                                \
+  OP(kColorId_TableSelectionBackgroundUnfocused),                              \
+  OP(kColorId_TableGroupingIndicatorColor),                                    \
+  /* Table Header */                                                           \
+  OP(kColorId_TableHeaderText),                                                \
+  OP(kColorId_TableHeaderBackground),                                          \
+  OP(kColorId_TableHeaderSeparator),                                           \
+  /* Colors for the material spinner (aka throbber). */                        \
+  OP(kColorId_ThrobberSpinningColor),                                          \
+  OP(kColorId_ThrobberWaitingColor),                                           \
+  OP(kColorId_ThrobberLightColor),                                             \
+  /* Colors for icons that alert, e.g. upgrade reminders. */                   \
+  OP(kColorId_AlertSeverityLow),                                               \
+  OP(kColorId_AlertSeverityMedium),                                            \
+  OP(kColorId_AlertSeverityHigh),                                              \
+  /* Colors for icons in secondary UI (content settings, help button, etc). */ \
+  OP(kColorId_DefaultIconColor)
+// clang-format on
+
+#endif  // UI_NATIVE_THEME_NATIVE_THEME_COLOR_ID_H_
diff --git a/ui/native_theme/native_theme_unittest.cc b/ui/native_theme/native_theme_unittest.cc
new file mode 100644
index 0000000..12c112e
--- /dev/null
+++ b/ui/native_theme/native_theme_unittest.cc
@@ -0,0 +1,62 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/native_theme/native_theme.h"
+
+#include "base/test/scoped_feature_list.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/ui_base_features.h"
+#include "ui/native_theme/native_theme_color_id.h"
+
+namespace ui {
+
+namespace {
+
+constexpr const char* kColorIdStringName[] = {
+#define OP(enum_name) #enum_name
+    NATIVE_THEME_COLOR_IDS
+#undef OP
+};
+
+class NativeThemeRedirectedEquivalenceTest
+    : public testing::TestWithParam<NativeTheme::ColorId> {
+ public:
+  NativeThemeRedirectedEquivalenceTest() = default;
+
+  static std::string ParamInfoToString(
+      ::testing::TestParamInfo<NativeTheme::ColorId> param_info) {
+    NativeTheme::ColorId color_id = param_info.param;
+    if (color_id >= NativeTheme::ColorId::kColorId_NumColors) {
+      ADD_FAILURE() << "Invalid color value " << color_id;
+      return "Invalid";
+    }
+    return kColorIdStringName[color_id];
+  }
+};
+
+}  // namespace
+
+TEST_P(NativeThemeRedirectedEquivalenceTest, NativeUiGetSystemColor) {
+  // Verifies that colors with and without the Color Provider are the same.
+  NativeTheme* native_theme = NativeTheme::GetInstanceForNativeUi();
+  NativeTheme::ColorId color_id = GetParam();
+
+  SkColor original = native_theme->GetSystemColor(color_id);
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(features::kColorProviderRedirection);
+  SkColor redirected = native_theme->GetSystemColor(color_id);
+
+  EXPECT_EQ(original, redirected);
+}
+
+#define OP(enum_name) NativeTheme::ColorId::enum_name
+INSTANTIATE_TEST_SUITE_P(
+    ,
+    NativeThemeRedirectedEquivalenceTest,
+    ::testing::Values(NATIVE_THEME_COLOR_IDS),
+    NativeThemeRedirectedEquivalenceTest::ParamInfoToString);
+#undef OP
+
+}  // namespace ui
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_password_input.html b/ui/webui/resources/cr_components/chromeos/network/network_password_input.html
index de9a413e..3afeef1 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_password_input.html
+++ b/ui/webui/resources/cr_components/chromeos/network/network_password_input.html
@@ -6,7 +6,6 @@
 <link rel="import" href="chrome://resources/cr_elements/policy/cr_policy_network_indicator_mojo.html">
 <link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-tooltip/paper-tooltip.html">
 <link rel="import" href="network_config_element_behavior.html">
 <link rel="import" href="network_shared_css.html">
 
@@ -23,13 +22,6 @@
         flex: 1;
       }
 
-      paper-tooltip {
-        --paper-tooltip: {
-          @apply --cr-tooltip;
-          min-width: 0;
-        };
-      }
-
       cr-policy-network-indicator-mojo {
         --cr-tooltip-icon-margin-start: var(--cr-controlled-by-spacing);
       }
@@ -48,12 +40,8 @@
         <cr-icon-button id="icon" slot="suffix"
             class$="[[getIconClass_(showPassword)]]"
             on-click="onShowPasswordTap_"
-            aria-describedby="tooltip">
+            title="[[getShowPasswordTitle_(showPassword)]]">
         </cr-icon-button>
-        <paper-tooltip id="tooltip" for="icon" position="top"
-            fit-to-visible-bounds role="tooltip">
-          [[getShowPasswordTitle_(showPassword)]]
-        </paper-tooltip>
       </template>
       <template is="dom-if" if="[[showPolicyIndicator_]]" restamp>
         <cr-policy-network-indicator-mojo
diff --git a/url/origin.h b/url/origin.h
index 737f615..54945c9 100644
--- a/url/origin.h
+++ b/url/origin.h
@@ -145,6 +145,9 @@
   // 2. 'filesystem' URLs behave as 'blob' URLs (that is, the origin is parsed
   //    out of everything in the URL which follows the scheme).
   // 3. 'file' URLs all parse as ("file", "", 0).
+  //
+  // Note that the returned Origin may have a different scheme and host from
+  // |url| (e.g. in case of blob URLs - see OriginTest.ConstructFromGURL).
   static Origin Create(const GURL& url);
 
   // Creates an Origin for the resource |url| as if it were requested
diff --git a/weblayer/browser/android/javatests/BUILD.gn b/weblayer/browser/android/javatests/BUILD.gn
index fea2755..49de855 100644
--- a/weblayer/browser/android/javatests/BUILD.gn
+++ b/weblayer/browser/android/javatests/BUILD.gn
@@ -8,6 +8,7 @@
 android_library("weblayer_java_tests") {
   testonly = true
   sources = [
+    "src/org/chromium/weblayer/test/BoundedCountDownLatch.java",
     "src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java",
     "src/org/chromium/weblayer/test/CloseTabNewTabCallbackImpl.java",
     "src/org/chromium/weblayer/test/CrashReporterTest.java",
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BoundedCountDownLatch.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BoundedCountDownLatch.java
new file mode 100644
index 0000000..8e39155
--- /dev/null
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BoundedCountDownLatch.java
@@ -0,0 +1,34 @@
+// Copyright 2020 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.weblayer.test;
+
+import org.junit.Assert;
+
+import org.chromium.base.test.util.CallbackHelper;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A CountDownLatch with a default timeout.
+ */
+public class BoundedCountDownLatch extends CountDownLatch {
+    BoundedCountDownLatch(int count) {
+        super(count);
+    }
+
+    /**
+     * This fails more quickly and gracefully than {@link CountDownLatch#await()}, which has no
+     * timeout. It gives useful error output, whereas a test that times out in {@link await()} may
+     * leave no stack.
+     */
+    public void timedAwait() {
+        try {
+            Assert.assertTrue(super.await(CallbackHelper.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            Assert.fail(e.toString());
+        }
+    }
+}
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
index 5e841bd..3beb19d 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/BrowserFragmentLifecycleTest.java
@@ -23,8 +23,6 @@
 import org.chromium.weblayer.Tab;
 import org.chromium.weblayer.shell.InstrumentationActivity;
 
-import java.util.concurrent.CountDownLatch;
-
 /**
  * Tests that fragment lifecycle works as expected.
  */
@@ -53,7 +51,7 @@
 
     @Test
     @SmallTest
-    public void restoreAfterRecreate() throws InterruptedException {
+    public void restoreAfterRecreate() {
         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
         Tab tab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> activity.getTab());
 
@@ -63,7 +61,7 @@
         mActivityTestRule.recreateActivity();
 
         InstrumentationActivity newActivity = mActivityTestRule.getActivity();
-        CountDownLatch latch = new CountDownLatch(1);
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             Tab restoredTab = newActivity.getTab();
             // It's possible the NavigationController hasn't loaded yet, handle either scenario.
@@ -83,14 +81,14 @@
                 }
             });
         });
-        latch.await();
+        latch.timedAwait();
     }
 
     // https://crbug.com/1021041
     @Test
     @SmallTest
-    public void handlesFragmentDestroyWhileNavigating() throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
+    public void handlesFragmentDestroyWhileNavigating() {
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl("about:blank");
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             NavigationController navigationController = activity.getTab().getNavigationController();
@@ -106,10 +104,10 @@
             });
             navigationController.navigate(Uri.parse("data:text,foo"));
         });
-        latch.await();
+        latch.timedAwait();
     }
 
-    private void restoresPreviousSession(Bundle extras) throws InterruptedException {
+    private void restoresPreviousSession(Bundle extras) {
         extras.putString(InstrumentationActivity.EXTRA_PERSISTENCE_ID, "x");
         final String url = mActivityTestRule.getTestDataURL("simple_page.html");
         InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(url, extras);
@@ -119,7 +117,7 @@
         InstrumentationActivity newActivity = mActivityTestRule.getActivity();
         Tab tab = TestThreadUtils.runOnUiThreadBlockingNoException(() -> newActivity.getTab());
         Assert.assertNotNull(tab);
-        CountDownLatch latch = new CountDownLatch(1);
+        BoundedCountDownLatch latch = new BoundedCountDownLatch(1);
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             // It's possible the NavigationController hasn't loaded yet, handle either scenario.
             NavigationController navigationController = tab.getNavigationController();
@@ -138,7 +136,7 @@
                 }
             });
         });
-        latch.await();
+        latch.timedAwait();
     }
 
     @Test
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java
index 42a5635..2c5b08f6 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/FindInPageTest.java
@@ -17,16 +17,11 @@
 import org.chromium.weblayer.Tab;
 import org.chromium.weblayer.shell.InstrumentationActivity;
 
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
 /**
  * Tests the behavior of FindInPageController and FindInPageCallback.
  */
 @RunWith(WebLayerJUnit4ClassRunner.class)
 public class FindInPageTest {
-    private static final int COUNTDOWN_TIMEOUT_SECONDS = 6;
-
     @Rule
     public InstrumentationActivityTestRule mActivityTestRule =
             new InstrumentationActivityTestRule();
@@ -37,8 +32,8 @@
     private static class CallbackImpl extends FindInPageCallback {
         public int mNumberOfMatches;
         public int mActiveMatchIndex;
-        public CountDownLatch mResultCountDown;
-        public CountDownLatch mEndedCountDown;
+        public BoundedCountDownLatch mResultCountDown;
+        public BoundedCountDownLatch mEndedCountDown;
 
         @Override
         public void onFindResult(int numberOfMatches, int activeMatchIndex, boolean finalUpdate) {
@@ -54,14 +49,10 @@
     }
 
     private void searchFor(String text, boolean forward) {
-        mCallback.mResultCountDown = new CountDownLatch(1);
+        mCallback.mResultCountDown = new BoundedCountDownLatch(1);
         TestThreadUtils.runOnUiThreadBlocking(
                 () -> { mActivity.getTab().getFindInPageController().find(text, forward); });
-        try {
-            mCallback.mResultCountDown.await(COUNTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Assert.fail(e.toString());
-        }
+        mCallback.mResultCountDown.timedAwait();
     }
 
     private void searchFor(String text) {
@@ -136,33 +127,33 @@
 
     @Test
     @SmallTest
-    public void testHideOnNavigate() throws InterruptedException {
+    public void testHideOnNavigate() {
         setUp("shakespeare.html");
 
-        mCallback.mEndedCountDown = new CountDownLatch(1);
+        mCallback.mEndedCountDown = new BoundedCountDownLatch(1);
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivity.getTab().getNavigationController().navigate(Uri.parse("simple_page.html"));
         });
 
-        mCallback.mEndedCountDown.await(COUNTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        mCallback.mEndedCountDown.timedAwait();
         verifyFindSessionInactive();
     }
 
     @Test
     @SmallTest
-    public void testHideOnNewTab() throws InterruptedException {
+    public void testHideOnNewTab() {
         setUp("new_browser.html");
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mActivity.getBrowser().getActiveTab().setNewTabCallback(new NewTabCallbackImpl());
         });
 
-        mCallback.mEndedCountDown = new CountDownLatch(1);
+        mCallback.mEndedCountDown = new BoundedCountDownLatch(1);
 
         // This touch creates a new tab.
         EventUtils.simulateTouchCenterOfView(mActivity.getWindow().getDecorView());
 
-        mCallback.mEndedCountDown.await(COUNTDOWN_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        mCallback.mEndedCountDown.timedAwait();
     }
 }