diff --git a/DEPS b/DEPS
index c0b8a6a..112d1069 100644
--- a/DEPS
+++ b/DEPS
@@ -167,11 +167,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': '106e86516480c7519becf7da1c4e6e42040b62a3',
+  'skia_revision': '27c4369651a868d6d0dcf367456efc39eeaf2c98',
   # 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': 'ddd5171bc2abfa9eff5f957c2279268c324cef8c',
+  'v8_revision': '8be37a6d57ad9559e0a794b414a9d869f2b0a1a7',
   # 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.
@@ -179,7 +179,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': 'a21362f5a6ae8929899fb54fb646f395d141d98b',
+  'angle_revision': '741c0aa6f5a12659520437b381c6f522c5ee6d77',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -862,7 +862,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'af9c6ce85491f6cb57b6919cb8fb878020fc1612',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '4d4275fc6a5d6f61bd93b654a158f3d696700ab5',
       'condition': 'checkout_linux',
   },
 
@@ -887,7 +887,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'e3703bb8b8af7978ecf1bdfbfe8c3e6c910be8bc',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '12265a8f7de02667caa3961b95760c0f37d743dd',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1280,7 +1280,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'f5952b718415b26217c79b11afccfe850d281f96',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'dbcebd185da399ec3ea42630c8ed38d24d504b10',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1470,7 +1470,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '2701c130839edbeb226735b0775966b6423d9e83',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '5740f3e2b8fdee7288748907920d9fabe948e895',
+    Var('webrtc_git') + '/src.git' + '@' + '80f53b785b377ab4c781c36d371d84241c7d9b80',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
diff --git a/WATCHLISTS b/WATCHLISTS
index e4e33bf..5d61804 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2214,7 +2214,7 @@
     'certificate_transparency': ['certificate-transparency-chrome@googlegroups.com',
                                  'martijn+crwatch@martijnc.be',
                                  'rsleevi+watch@chromium.org'],
-    'chrome_cleaner': ['joenotcharles+watch@google.com'],
+    'chrome_cleaner': ['joenotcharles+watch@chromium.org'],
     'chrome_elf': ['caitkp+watch@chromium.org',
                    'pmonette+watch@chromium.org'],
     'chrome_grc': ['chrome-grc-reviews@chromium.org'],
diff --git a/android_webview/browser/tracing/aw_tracing_controller.cc b/android_webview/browser/tracing/aw_tracing_controller.cc
index 49910984..f51e7ac 100644
--- a/android_webview/browser/tracing/aw_tracing_controller.cc
+++ b/android_webview/browser/tracing/aw_tracing_controller.cc
@@ -43,7 +43,7 @@
 
   void ReceivedTraceFinalContents() override {
     base::PostTask(FROM_HERE, {content::BrowserThread::UI},
-                   base::BindOnce(std::move(completed_callback_)));
+                   std::move(completed_callback_));
   }
 
   void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
@@ -88,20 +88,21 @@
       base::android::ConvertJavaStringToUTF8(env, jcategories);
   base::trace_event::TraceConfig trace_config(
       categories, static_cast<base::trace_event::TraceRecordMode>(jmode));
-  // Required for filtering out potential PII.
-  trace_config.EnableArgumentFilter();
   return content::TracingController::GetInstance()->StartTracing(
       trace_config, content::TracingController::StartTracingDoneCallback());
 }
 
 bool AwTracingController::StopAndFlush(JNIEnv* env,
                                        const JavaParamRef<jobject>& obj) {
+  // privacy_filtering_enabled=true is required for filtering out potential PII.
   return content::TracingController::GetInstance()->StopTracing(
       AwTraceDataEndpoint::Create(
           base::BindRepeating(&AwTracingController::OnTraceDataReceived,
                               weak_factory_.GetWeakPtr()),
           base::BindOnce(&AwTracingController::OnTraceDataComplete,
-                         weak_factory_.GetWeakPtr())));
+                         weak_factory_.GetWeakPtr())),
+      /*agent_label=*/"",
+      /*privacy_filtering_enabled=*/true);
 }
 
 void AwTracingController::OnTraceDataComplete() {
diff --git a/android_webview/lib/aw_main_delegate.cc b/android_webview/lib/aw_main_delegate.cc
index f72b6fd..900846c 100644
--- a/android_webview/lib/aw_main_delegate.cc
+++ b/android_webview/lib/aw_main_delegate.cc
@@ -211,6 +211,8 @@
     // TODO(https://crbug.com/963653): SmsReceiver is not yet supported on
     // WebView.
     features.DisableIfNotSet(::features::kSmsReceiver);
+
+    features.DisableIfNotSet(::features::kWebXr);
   }
 
   android_webview::RegisterPathProvider();
diff --git a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
index 237cc53..6f0a395 100644
--- a/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
+++ b/android_webview/tools/system_webview_shell/test/data/webexposed/not-webview-exposed.txt
@@ -55,13 +55,14 @@
 
 # permissions API (crbug.com/490120), presentation API (crbug.com/521319),
 # share API (crbug.com/765923), custom scheme handlers (crbug.com/589502),
-# media session API (crbug.com/925997), and sms API (crbug.com/963653)
-# are not supported in webview.
+# media session API (crbug.com/925997), sms API (crbug.com/963653), and WebXr
+# API (crbug.com/1012899) are not supported in webview.
 interface Navigator
     getter mediaSession                # crbug.com/925997
     getter permissions                 # crbug.com/490120
     getter presentation                # crbug.com/521319
     getter sms                         # crbug.com/963653
+    getter xr                          # crbug.com/1012899
     method registerProtocolHandler     # crbug.com/589502
     method unregisterProtocolHandler   # crbug.com/589502
     method share                       # crbug.com/765923
@@ -129,6 +130,31 @@
 interface HTMLCanvasElement : HTMLElement
     method transferControlToOffscreen
 
+# WebXR dependent interfaces are not supported on WebView crbug.com/1012899
+interface XRView
+interface XRViewport
+interface XR : EventTarget
+interface XRFrame
+interface XRRigidTransform
+interface XRSpace : EventTarget
+interface XRInputSourcesChangeEvent : Event
+interface XRInputSource
+interface XRWebGLLayer
+interface XRInputSourceEvent : Event
+interface XRViewerPose : XRPose
+interface XRInputSourceArray
+interface XRRenderState
+interface XRPose
+interface XRSession : EventTarget
+interface XRReferenceSpaceEvent : Event
+interface XRBoundedReferenceSpace : XRReferenceSpace
+interface XRSessionEvent : Event
+interface XRReferenceSpace : XRSpace
+interface WebGLRenderingContext
+    method makeXRCompatible
+interface WebGL2RenderingContext
+    method makeXRCompatible
+
 [GLOBAL OBJECT]
     method openDatabase
     attribute eventSender                    # test only
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index d7e2cf15..ab1c431 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -3075,8 +3075,7 @@
   EnableTabletMode(true);
   GetAppListTestHelper()->CheckVisibility(true);
   std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window.get(), SplitViewController::LEFT);
   EXPECT_TRUE(split_view_controller->InSplitViewMode());
 
diff --git a/ash/app_menu/notification_item_view.cc b/ash/app_menu/notification_item_view.cc
index 2120ad4..f352705 100644
--- a/ash/app_menu/notification_item_view.cc
+++ b/ash/app_menu/notification_item_view.cc
@@ -9,6 +9,7 @@
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/text_elider.h"
 #include "ui/message_center/views/proportional_image_view.h"
+#include "ui/views/animation/slide_out_controller.h"
 #include "ui/views/background.h"
 #include "ui/views/border.h"
 #include "ui/views/controls/label.h"
@@ -45,16 +46,15 @@
 
 NotificationItemView::NotificationItemView(
     NotificationMenuView::Delegate* delegate,
-    message_center::SlideOutController::Delegate* slide_out_controller_delegate,
+    views::SlideOutControllerDelegate* slide_out_controller_delegate,
     const base::string16& title,
     const base::string16& message,
     const gfx::Image& icon,
     const std::string& notification_id)
     : delegate_(delegate),
-      slide_out_controller_(
-          std::make_unique<message_center::SlideOutController>(
-              this,
-              slide_out_controller_delegate)),
+      slide_out_controller_(std::make_unique<views::SlideOutController>(
+          this,
+          slide_out_controller_delegate)),
       title_(title),
       message_(message),
       notification_id_(notification_id) {
diff --git a/ash/app_menu/notification_item_view.h b/ash/app_menu/notification_item_view.h
index 2c81a97..c949bb5 100644
--- a/ash/app_menu/notification_item_view.h
+++ b/ash/app_menu/notification_item_view.h
@@ -11,7 +11,7 @@
 #include "ash/app_menu/app_menu_export.h"
 #include "ash/app_menu/notification_menu_view.h"
 #include "base/strings/string16.h"
-#include "ui/message_center/views/slide_out_controller.h"
+#include "ui/views/animation/slide_out_controller_delegate.h"
 #include "ui/views/view.h"
 
 namespace gfx {
@@ -25,6 +25,7 @@
 
 namespace views {
 class Label;
+class SlideOutController;
 }
 
 namespace ash {
@@ -32,13 +33,13 @@
 // The view which contains the details of a notification.
 class APP_MENU_EXPORT NotificationItemView : public views::View {
  public:
-  NotificationItemView(NotificationMenuView::Delegate* delegate,
-                       message_center::SlideOutController::Delegate*
-                           slide_out_controller_delegate,
-                       const base::string16& title,
-                       const base::string16& message,
-                       const gfx::Image& icon,
-                       const std::string& notification_id);
+  NotificationItemView(
+      NotificationMenuView::Delegate* delegate,
+      views::SlideOutControllerDelegate* slide_out_controller_delegate,
+      const base::string16& title,
+      const base::string16& message,
+      const gfx::Image& icon,
+      const std::string& notification_id);
 
   ~NotificationItemView() override;
 
@@ -79,7 +80,7 @@
   NotificationMenuView::Delegate* const delegate_;
 
   // Controls the sideways gesture drag behavior.
-  std::unique_ptr<message_center::SlideOutController> slide_out_controller_;
+  std::unique_ptr<views::SlideOutController> slide_out_controller_;
 
   // Notification properties.
   base::string16 title_;
diff --git a/ash/app_menu/notification_menu_controller.h b/ash/app_menu/notification_menu_controller.h
index 4714773..d6e622e 100644
--- a/ash/app_menu/notification_menu_controller.h
+++ b/ash/app_menu/notification_menu_controller.h
@@ -10,7 +10,7 @@
 #include "base/scoped_observer.h"
 #include "ui/message_center/message_center.h"
 #include "ui/message_center/message_center_observer.h"
-#include "ui/message_center/views/slide_out_controller.h"
+#include "ui/views/animation/slide_out_controller_delegate.h"
 
 namespace views {
 class MenuItemView;
@@ -25,7 +25,7 @@
 // as notifications come and go.
 class APP_MENU_EXPORT NotificationMenuController
     : public message_center::MessageCenterObserver,
-      public message_center::SlideOutController::Delegate,
+      public views::SlideOutControllerDelegate,
       public NotificationMenuView::Delegate {
  public:
   NotificationMenuController(const std::string& app_id,
@@ -40,7 +40,7 @@
   void OnNotificationRemoved(const std::string& notification_id,
                              bool by_user) override;
 
-  // message_center::SlideOutController::Delegate overrides:
+  // views::SlideOutControllerDelegate overrides:
   ui::Layer* GetSlideOutLayer() override;
   void OnSlideChanged(bool in_progress) override;
   void OnSlideOut() override;
diff --git a/ash/app_menu/notification_menu_view.cc b/ash/app_menu/notification_menu_view.cc
index f3c8e44..872e7922 100644
--- a/ash/app_menu/notification_menu_view.cc
+++ b/ash/app_menu/notification_menu_view.cc
@@ -22,7 +22,7 @@
 
 NotificationMenuView::NotificationMenuView(
     Delegate* notification_item_view_delegate,
-    message_center::SlideOutController::Delegate* slide_out_controller_delegate,
+    views::SlideOutControllerDelegate* slide_out_controller_delegate,
     const std::string& app_id)
     : app_id_(app_id),
       notification_item_view_delegate_(notification_item_view_delegate),
diff --git a/ash/app_menu/notification_menu_view.h b/ash/app_menu/notification_menu_view.h
index a785b6f..1c429d9 100644
--- a/ash/app_menu/notification_menu_view.h
+++ b/ash/app_menu/notification_menu_view.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "ash/app_menu/app_menu_export.h"
-#include "ui/message_center/views/slide_out_controller.h"
 #include "ui/views/view.h"
 
 namespace message_center {
@@ -18,6 +17,7 @@
 
 namespace views {
 class MenuSeparator;
+class SlideOutControllerDelegate;
 }
 
 namespace ash {
@@ -43,10 +43,10 @@
     virtual void OnOverflowAddedOrRemoved() = 0;
   };
 
-  NotificationMenuView(Delegate* notification_item_view_delegate,
-                       message_center::SlideOutController::Delegate*
-                           slide_out_controller_delegate,
-                       const std::string& app_id);
+  NotificationMenuView(
+      Delegate* notification_item_view_delegate,
+      views::SlideOutControllerDelegate* slide_out_controller_delegate,
+      const std::string& app_id);
   ~NotificationMenuView() override;
 
   // views::View:
@@ -103,8 +103,7 @@
   NotificationMenuView::Delegate* const notification_item_view_delegate_;
 
   // Owned by AppMenuModelAdapter.
-  message_center::SlideOutController::Delegate* const
-      slide_out_controller_delegate_;
+  views::SlideOutControllerDelegate* const slide_out_controller_delegate_;
 
   // The deque of NotificationItemViews. The front item in the deque is the view
   // which is shown.
diff --git a/ash/app_menu/notification_menu_view_unittest.cc b/ash/app_menu/notification_menu_view_unittest.cc
index 111c2b31..545eab01 100644
--- a/ash/app_menu/notification_menu_view_unittest.cc
+++ b/ash/app_menu/notification_menu_view_unittest.cc
@@ -28,9 +28,8 @@
 // The app id used in tests.
 constexpr char kTestAppId[] = "test-app-id";
 
-class MockNotificationMenuController
-    : public message_center::SlideOutController::Delegate,
-      public NotificationMenuView::Delegate {
+class MockNotificationMenuController : public views::SlideOutControllerDelegate,
+                                       public NotificationMenuView::Delegate {
  public:
   MockNotificationMenuController() = default;
   ~MockNotificationMenuController() override = default;
diff --git a/ash/display/screen_orientation_controller.cc b/ash/display/screen_orientation_controller.cc
index 5f63ed91..1ba237f 100644
--- a/ash/display/screen_orientation_controller.cc
+++ b/ash/display/screen_orientation_controller.cc
@@ -162,7 +162,8 @@
 OrientationLockType GetCurrentScreenOrientation() {
   // ScreenOrientationController might be nullptr during shutdown.
   // TODO(xdai|sammiequon): See if we can reorder so that users of the function
-  // (split_view_controller) get shutddown before screen orientation controller.
+  // |SplitViewController::Get| get shutdown before screen orientation
+  // controller.
   if (!Shell::Get()->screen_orientation_controller())
     return OrientationLockType::kAny;
   return Shell::Get()->screen_orientation_controller()->GetCurrentOrientation();
@@ -217,11 +218,11 @@
       user_rotation_(display::Display::ROTATE_0),
       current_rotation_(display::Display::ROTATE_0) {
   Shell::Get()->tablet_mode_controller()->AddObserver(this);
-  Shell::Get()->split_view_controller()->AddObserver(this);
+  SplitViewController::Get()->AddObserver(this);
 }
 
 ScreenOrientationController::~ScreenOrientationController() {
-  Shell::Get()->split_view_controller()->RemoveObserver(this);
+  SplitViewController::Get()->RemoveObserver(this);
   Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
   AccelerometerReader::GetInstance()->RemoveObserver(this);
   Shell::Get()->window_tree_host_manager()->RemoveObserver(this);
@@ -598,16 +599,14 @@
   if (!ScreenOrientationProviderSupported())
     return;
 
-  Shell* shell = Shell::Get();
-
-  if (shell->split_view_controller()->InTabletSplitViewMode()) {
+  if (SplitViewController::Get()->InTabletSplitViewMode()) {
     // While split view is enabled, ignore rotation lock set by windows.
     LockRotationToOrientation(user_locked_orientation_);
     return;
   }
 
   MruWindowTracker::WindowList mru_windows(
-      shell->mru_window_tracker()->BuildMruWindowList(kActiveDesk));
+      Shell::Get()->mru_window_tracker()->BuildMruWindowList(kActiveDesk));
 
   for (auto* window : mru_windows) {
     if (!window->TargetVisibility())
diff --git a/ash/display/screen_orientation_controller_unittest.cc b/ash/display/screen_orientation_controller_unittest.cc
index e3ef1af..dace31f 100644
--- a/ash/display/screen_orientation_controller_unittest.cc
+++ b/ash/display/screen_orientation_controller_unittest.cc
@@ -343,13 +343,13 @@
   Lock(child_window2.get(), OrientationLockType::kPortrait);
   ASSERT_TRUE(RotationLocked());
 
-  Shell::Get()->split_view_controller()->SnapWindow(focus_window1.get(),
-                                                    SplitViewController::LEFT);
-  Shell::Get()->split_view_controller()->SnapWindow(focus_window1.get(),
-                                                    SplitViewController::RIGHT);
+  SplitViewController::Get()->SnapWindow(focus_window1.get(),
+                                         SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(focus_window1.get(),
+                                         SplitViewController::RIGHT);
   EXPECT_FALSE(RotationLocked());
 
-  Shell::Get()->split_view_controller()->EndSplitView();
+  SplitViewController::Get()->EndSplitView();
   EXPECT_TRUE(RotationLocked());
 }
 
diff --git a/ash/home_screen/drag_window_from_shelf_controller.cc b/ash/home_screen/drag_window_from_shelf_controller.cc
index d69a6c0..9e75edc 100644
--- a/ash/home_screen/drag_window_from_shelf_controller.cc
+++ b/ash/home_screen/drag_window_from_shelf_controller.cc
@@ -87,8 +87,7 @@
 
   drag_started_ = false;
   OverviewController* overview_controller = Shell::Get()->overview_controller();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   const bool in_overview = overview_controller->InOverviewSession();
   const bool in_splitview = split_view_controller->InSplitViewMode();
 
@@ -152,8 +151,7 @@
 
   // If the dragged window is one of the snapped window in splitview, it needs
   // to be detached from splitview before start dragging.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->OnWindowDragStarted(window_);
   // Note SplitViewController::OnWindowDragStarted() may open overview.
   OverviewController* overview_controller = Shell::Get()->overview_controller();
@@ -177,12 +175,11 @@
         should_drop_window_in_overview,
         /*snap=*/snap_position != SplitViewController::NONE);
   }
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   if (split_view_controller->InSplitViewMode() ||
       snap_position != SplitViewController::NONE) {
-    Shell::Get()->split_view_controller()->OnWindowDragEnded(
-        window_, snap_position, location_in_screen);
+    split_view_controller->OnWindowDragEnded(window_, snap_position,
+                                             location_in_screen);
   }
   Shell::Get()->home_screen_controller()->OnWindowDragEnded();
 
@@ -286,7 +283,7 @@
     base::Optional<float> velocity_y) const {
   return velocity_y.has_value() && *velocity_y < 0 &&
          std::abs(*velocity_y) >= kVelocityToHomeScreenThreshold &&
-         !Shell::Get()->split_view_controller()->InSplitViewMode();
+         !SplitViewController::Get()->InSplitViewMode();
 }
 
 SplitViewController::SnapPosition
@@ -310,8 +307,7 @@
   if (ShouldGoToHomeScreen(velocity_y))
     return false;
 
-  const bool in_splitview =
-      Shell::Get()->split_view_controller()->InSplitViewMode();
+  const bool in_splitview = SplitViewController::Get()->InSplitViewMode();
   if (!in_splitview && ShouldRestoreToOriginalBounds(location_in_screen)) {
     return false;
   }
diff --git a/ash/home_screen/home_launcher_gesture_handler.cc b/ash/home_screen/home_launcher_gesture_handler.cc
index b77d5f6..427ff94 100644
--- a/ash/home_screen/home_launcher_gesture_handler.cc
+++ b/ash/home_screen/home_launcher_gesture_handler.cc
@@ -156,8 +156,7 @@
 // Returns the window of the widget of the split view divider. May be nullptr if
 // split view is not active.
 aura::Window* GetDividerWindow() {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   if (!split_view_controller->InSplitViewMode())
     return nullptr;
   return split_view_controller->split_view_divider()
@@ -181,8 +180,7 @@
     return nullptr;
 
   aura::Window* window = nullptr;
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   const bool is_in_splitview = split_view_controller->InSplitViewMode();
   const bool is_in_overview =
       Shell::Get()->overview_controller()->InOverviewSession();
@@ -536,9 +534,9 @@
   }
 
   // Explicitly exit split view if two windows are snapped.
-  if (is_final_state_show && Shell::Get()->split_view_controller()->state() ==
+  if (is_final_state_show && SplitViewController::Get()->state() ==
                                  SplitViewController::State::kBothSnapped) {
-    Shell::Get()->split_view_controller()->EndSplitView();
+    SplitViewController::Get()->EndSplitView();
   }
 
   if (is_final_state_show) {
@@ -795,8 +793,7 @@
     Mode mode,
     aura::Window* window,
     base::Optional<gfx::Point> location_in_screen) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   overview_active_on_gesture_start_ =
       Shell::Get()->overview_controller()->InOverviewSession();
   const bool split_view_active = split_view_controller->InSplitViewMode();
diff --git a/ash/home_screen/home_launcher_gesture_handler_unittest.cc b/ash/home_screen/home_launcher_gesture_handler_unittest.cc
index 8a9c55f..7eb7468 100644
--- a/ash/home_screen/home_launcher_gesture_handler_unittest.cc
+++ b/ash/home_screen/home_launcher_gesture_handler_unittest.cc
@@ -303,8 +303,7 @@
   // Snap one window and leave overview mode open with the other window.
   OverviewController* overview_controller = Shell::Get()->overview_controller();
   overview_controller->StartOverview();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   ASSERT_TRUE(overview_controller->InOverviewSession());
   ASSERT_TRUE(split_view_controller->InSplitViewMode());
@@ -350,8 +349,7 @@
   auto window2 = CreateWindowForTesting();
 
   // Snap two windows to start.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
   ASSERT_TRUE(split_view_controller->InSplitViewMode());
@@ -464,8 +462,7 @@
   // Test in splitview, depends on the drag position, the active dragged window
   // might be different.
   auto window2 = CreateWindowForTesting();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
   GetGestureHandler()->OnPressEvent(Mode::kDragWindowToHomeOrOverview,
@@ -514,8 +511,7 @@
   EXPECT_TRUE(window3->IsVisible());
 
   // In splitview mode, the snapped windows will stay visible during dragging.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
   GetGestureHandler()->OnPressEvent(Mode::kDragWindowToHomeOrOverview,
@@ -579,8 +575,7 @@
 
   // In splitview + overview case, drag from shelf in the overview side of the
   // screen also does nothing.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
   overview_controller->StartOverview();
@@ -649,8 +644,7 @@
   EXPECT_TRUE(overview_controller->InOverviewSession());
   GetGestureHandler()->OnReleaseEvent(gfx::Point(0, 200), base::nullopt);
   EXPECT_TRUE(overview_controller->InOverviewSession());
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   EXPECT_TRUE(split_view_controller->InSplitViewMode());
   EXPECT_TRUE(split_view_controller->IsWindowInSplitView(window1.get()));
   EXPECT_FALSE(window2->IsVisible());
@@ -755,8 +749,7 @@
 
   // The same thing should happen if splitview mode is active.
   auto window2 = CreateWindowForTesting();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
   GetGestureHandler()->OnPressEvent(Mode::kDragWindowToHomeOrOverview,
@@ -816,8 +809,7 @@
 
   auto window1 = CreateWindowForTesting();
   auto window2 = CreateWindowForTesting();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   OverviewController* overview_controller = Shell::Get()->overview_controller();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
diff --git a/ash/home_screen/home_screen_controller.cc b/ash/home_screen/home_screen_controller.cc
index befa63602..1ac0f8a 100644
--- a/ash/home_screen/home_screen_controller.cc
+++ b/ash/home_screen/home_screen_controller.cc
@@ -87,9 +87,9 @@
     return true;
   }
 
-  if (Shell::Get()->split_view_controller()->InSplitViewMode()) {
+  if (SplitViewController::Get()->InSplitViewMode()) {
     // End split view mode.
-    Shell::Get()->split_view_controller()->EndSplitView(
+    SplitViewController::Get()->EndSplitView(
         SplitViewController::EndReason::kHomeLauncherPressed);
     return true;
   }
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index 44428a1..69d9c4e 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -549,13 +549,18 @@
         selected_language_item_ = item;
     }
 
+    LoginMenuView* old_language_menu_view = language_menu_view_;
+
     language_menu_view_ = new LoginMenuView(
         language_items_, language_selection_ /*anchor_view*/,
         language_selection_ /*bubble_opener*/,
         base::BindRepeating(&RightPaneView::OnLanguageSelected,
                             weak_factory_.GetWeakPtr()));
-    login_views_utils::GetTopLevelParentView(this)->AddChildView(
+    login_views_utils::GetBubbleContainer(this)->AddChildView(
         language_menu_view_);
+
+    if (old_language_menu_view)
+      delete old_language_menu_view;
   }
 
   void PopulateKeyboardItems(
@@ -573,13 +578,18 @@
         selected_keyboard_item_ = item;
     }
 
+    LoginMenuView* old_keyboard_menu_view = keyboard_menu_view_;
+
     keyboard_menu_view_ = new LoginMenuView(
         keyboard_items_, keyboard_selection_ /*anchor_view*/,
         keyboard_selection_ /*bubble_opener*/,
         base::BindRepeating(&RightPaneView::OnKeyboardSelected,
                             weak_factory_.GetWeakPtr()));
-    login_views_utils::GetTopLevelParentView(this)->AddChildView(
+    login_views_utils::GetBubbleContainer(this)->AddChildView(
         keyboard_menu_view_);
+
+    if (old_keyboard_menu_view)
+      delete old_keyboard_menu_view;
   }
 
   LoginBaseBubbleView* GetLanguageMenuView() { return language_menu_view_; }
@@ -590,6 +600,10 @@
   void Reset() {
     show_advanced_changed_by_user_ = false;
     language_changed_by_user_ = false;
+    if (language_menu_view_ && language_menu_view_->GetVisible())
+      language_menu_view_->Hide();
+    if (keyboard_menu_view_ && keyboard_menu_view_->GetVisible())
+      keyboard_menu_view_->Hide();
   }
 
  private:
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index 5dde204..770636e 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -549,7 +549,7 @@
         menu_->GetBubbleOpener() && menu_->GetBubbleOpener()->HasFocus();
 
     if (!menu_->parent())
-      login_views_utils::GetTopLevelParentView(this)->AddChildView(menu_);
+      login_views_utils::GetBubbleContainer(this)->AddChildView(menu_);
 
     // Reset state in case the remove-user button was clicked once previously.
     menu_->ResetState();
diff --git a/ash/login/ui/views_utils.cc b/ash/login/ui/views_utils.cc
index 7032d08..1355bb4 100644
--- a/ash/login/ui/views_utils.cc
+++ b/ash/login/ui/views_utils.cc
@@ -4,13 +4,47 @@
 
 #include "ash/login/ui/views_utils.h"
 
+#include <algorithm>
+#include <memory>
+
 #include "ash/login/ui/non_accessible_view.h"
 #include "ui/display/display.h"
 #include "ui/display/screen.h"
 #include "ui/views/layout/box_layout.h"
+#include "ui/views/view_targeter_delegate.h"
 #include "ui/views/widget/widget.h"
 
 namespace ash {
+
+namespace {
+
+class ContainerView : public NonAccessibleView,
+                      public views::ViewTargeterDelegate {
+ public:
+  ContainerView() {
+    SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
+  }
+  ~ContainerView() override = default;
+
+  // views::ViewTargeterDelegate:
+  bool DoesIntersectRect(const views::View* target,
+                         const gfx::Rect& rect) const override {
+    const auto& children = target->children();
+    const auto hits_child = [target, rect](const views::View* child) {
+      gfx::RectF child_rect(rect);
+      views::View::ConvertRectToTarget(target, child, &child_rect);
+      return child->GetVisible() &&
+             child->HitTestRect(gfx::ToEnclosingRect(child_rect));
+    };
+    return std::any_of(children.cbegin(), children.cend(), hits_child);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ContainerView);
+};
+
+}  // namespace
+
 namespace login_views_utils {
 
 std::unique_ptr<views::View> WrapViewForPreferredSize(
@@ -75,13 +109,28 @@
   return label;
 }
 
-views::View* GetTopLevelParentView(views::View* view) {
+views::View* GetBubbleContainer(views::View* view) {
   views::View* v = view;
-
   while (v->parent() != nullptr)
     v = v->parent();
 
-  return v;
+  views::View* root_view = v;
+  // An arbitrary id that no other child of root view should use.
+  const int kMenuContainerId = 1000;
+  views::View* container = nullptr;
+  for (auto* child : root_view->children()) {
+    if (child->GetID() == kMenuContainerId) {
+      container = child;
+      break;
+    }
+  }
+
+  if (!container) {
+    container = root_view->AddChildView(std::make_unique<ContainerView>());
+    container->SetID(kMenuContainerId);
+  }
+
+  return container;
 }
 
 }  // namespace login_views_utils
diff --git a/ash/login/ui/views_utils.h b/ash/login/ui/views_utils.h
index 95945d8..729ff1a9 100644
--- a/ash/login/ui/views_utils.h
+++ b/ash/login/ui/views_utils.h
@@ -31,8 +31,8 @@
 // Creates a standard text label for use in the login bubbles.
 views::Label* CreateBubbleLabel(const base::string16& message, SkColor color);
 
-// Get the topmost level parent view for |view|.
-views::View* GetTopLevelParentView(views::View* view);
+// Get the bubble container for |view| to place a LoginBaseBubbleView.
+views::View* GetBubbleContainer(views::View* view);
 
 }  // namespace login_views_utils
 
diff --git a/ash/magnifier/docked_magnifier_controller_impl.cc b/ash/magnifier/docked_magnifier_controller_impl.cc
index aa252d2..e8f983b 100644
--- a/ash/magnifier/docked_magnifier_controller_impl.cc
+++ b/ash/magnifier/docked_magnifier_controller_impl.cc
@@ -625,7 +625,7 @@
   Shell* shell = Shell::Get();
   auto* overview_controller = shell->overview_controller();
   if (overview_controller->InOverviewSession()) {
-    auto* split_view_controller = shell->split_view_controller();
+    auto* split_view_controller = SplitViewController::Get();
     if (split_view_controller->InSplitViewMode()) {
       // In this case, we're in a single-split-view mode, i.e. a window is
       // snapped to one side of the split view, while the other side has
diff --git a/ash/magnifier/docked_magnifier_controller_impl_unittest.cc b/ash/magnifier/docked_magnifier_controller_impl_unittest.cc
index d796d33..9e04c3f 100644
--- a/ash/magnifier/docked_magnifier_controller_impl_unittest.cc
+++ b/ash/magnifier/docked_magnifier_controller_impl_unittest.cc
@@ -410,7 +410,7 @@
       CreateTestWindowInShell(SK_ColorWHITE, 100, gfx::Rect(0, 0, 200, 200)));
   WindowState::Get(window.get())->Maximize();
 
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   EXPECT_EQ(split_view_controller->state(),
             SplitViewController::State::kNoSnap);
   EXPECT_EQ(split_view_controller->InSplitViewMode(), false);
@@ -460,7 +460,7 @@
   overview_controller->StartOverview();
   EXPECT_TRUE(overview_controller->InOverviewSession());
 
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   EXPECT_EQ(split_view_controller->InSplitViewMode(), false);
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
diff --git a/ash/metrics/histogram_macros.cc b/ash/metrics/histogram_macros.cc
index 20ca3bfb..8f3e126 100644
--- a/ash/metrics/histogram_macros.cc
+++ b/ash/metrics/histogram_macros.cc
@@ -16,8 +16,7 @@
 }
 
 bool IsInSplitView() {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   return split_view_controller && split_view_controller->InSplitViewMode();
 }
 
diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc
index 20d5cb77..5e06d99 100644
--- a/ash/root_window_controller.cc
+++ b/ash/root_window_controller.cc
@@ -58,6 +58,7 @@
 #include "ash/wm/lock_layout_manager.h"
 #include "ash/wm/overlay_layout_manager.h"
 #include "ash/wm/root_window_layout_manager.h"
+#include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/stacking_controller.h"
 #include "ash/wm/switchable_windows.h"
 #include "ash/wm/system_modal_container_layout_manager.h"
@@ -782,6 +783,15 @@
 
 void RootWindowController::Init(RootWindowType root_window_type) {
   aura::Window* root_window = GetRootWindow();
+  // Create |split_view_controller_| for the primary display only.
+  // TODO(crbug.com/970013): If the
+  // |ash::features::kMultiDisplayOverviewAndSplitView| feature flag is enabled,
+  // create |split_view_controller_| for every display.
+  display::Screen* screen = display::Screen::GetScreen();
+  if (screen->GetDisplayNearestWindow(root_window).id() ==
+      screen->GetPrimaryDisplay().id()) {
+    split_view_controller_ = std::make_unique<SplitViewController>();
+  }
   Shell* shell = Shell::Get();
   shell->InitRootWindow(root_window);
   auto old_targeter =
diff --git a/ash/root_window_controller.h b/ash/root_window_controller.h
index 38dfbc5..631f5410 100644
--- a/ash/root_window_controller.h
+++ b/ash/root_window_controller.h
@@ -46,6 +46,7 @@
 class RootWindowLayoutManager;
 class Shelf;
 class ShelfLayoutManager;
+class SplitViewController;
 class StackingController;
 class StatusAreaWidget;
 class SystemModalContainerLayoutManager;
@@ -100,6 +101,10 @@
   aura::Window* GetRootWindow();
   const aura::Window* GetRootWindow() const;
 
+  SplitViewController* split_view_controller() const {
+    return split_view_controller_.get();
+  }
+
   Shelf* shelf() const { return shelf_.get(); }
 
   TouchHudDebug* touch_hud_debug() const { return touch_hud_debug_; }
@@ -284,6 +289,8 @@
 
   std::unique_ptr<StackingController> stacking_controller_;
 
+  std::unique_ptr<SplitViewController> split_view_controller_;
+
   // The shelf controller for this root window. Exists for the entire lifetime
   // of the RootWindowController so that it is safe for observers to be added
   // to it during construction of the shelf widget and status tray.
diff --git a/ash/shelf/scrollable_shelf_view.cc b/ash/shelf/scrollable_shelf_view.cc
index faad0f02..163324e 100644
--- a/ash/shelf/scrollable_shelf_view.cc
+++ b/ash/shelf/scrollable_shelf_view.cc
@@ -12,6 +12,7 @@
 #include "ash/system/status_area_widget.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "base/numerics/ranges.h"
+#include "chromeos/constants/chromeos_switches.h"
 #include "ui/compositor/paint_recorder.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/gfx/geometry/insets.h"
@@ -65,16 +66,21 @@
   return ShelfConfig::Get()->button_size() / 2;
 }
 
-// Returns the padding between the app icon and the end of the ScrollableShelf.
-int GetAppIconEndPadding() {
+bool IsInTabletMode() {
   TabletModeController* tablet_mode_controller =
       Shell::Get()->tablet_mode_controller();
 
   // TabletModeController is destructed before ScrollableShelfView.
   if (!tablet_mode_controller || !tablet_mode_controller->InTabletMode())
-    return 0;
+    return false;
 
-  return 4;
+  return true;
+}
+
+// Returns the padding between the app icon and the end of the ScrollableShelf.
+int GetAppIconEndPadding() {
+  return (chromeos::switches::ShouldShowShelfHotseat() && IsInTabletMode()) ? 4
+                                                                            : 0;
 }
 
 }  // namespace
@@ -200,6 +206,57 @@
 };
 
 ////////////////////////////////////////////////////////////////////////////////
+// ScrollableShelfContainerView
+
+class ScrollableShelfContainerView : public ShelfContainerView,
+                                     public views::ViewTargeterDelegate {
+ public:
+  explicit ScrollableShelfContainerView(
+      ScrollableShelfView* scrollable_shelf_view)
+      : ShelfContainerView(scrollable_shelf_view->shelf_view()),
+        scrollable_shelf_view_(scrollable_shelf_view) {
+    SetEventTargeter(std::make_unique<views::ViewTargeter>(this));
+  }
+  ~ScrollableShelfContainerView() override = default;
+
+ private:
+  // views::View:
+  void Layout() override;
+
+  // views::ViewTargeterDelegate:
+  bool DoesIntersectRect(const views::View* target,
+                         const gfx::Rect& rect) const override;
+
+  ScrollableShelfView* scrollable_shelf_view_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(ScrollableShelfContainerView);
+};
+
+void ScrollableShelfContainerView::Layout() {
+  // Should not use ShelfView::GetPreferredSize in replace of
+  // CalculateIdealSize. Because ShelfView::CalculatePreferredSize relies on the
+  // bounds of app icon. Meanwhile, the icon's bounds may be updated by
+  // animation.
+  const gfx::Rect ideal_bounds = gfx::Rect(CalculateIdealSize());
+
+  const gfx::Rect local_bounds = GetLocalBounds();
+  gfx::Rect shelf_view_bounds =
+      local_bounds.Contains(ideal_bounds) ? local_bounds : ideal_bounds;
+  shelf_view_->SetBoundsRect(shelf_view_bounds);
+}
+
+bool ScrollableShelfContainerView::DoesIntersectRect(
+    const views::View* target,
+    const gfx::Rect& rect) const {
+  // This view's layer is clipped. So the view should only handle the events
+  // within the area after cilp.
+
+  gfx::RectF bounds = gfx::RectF(scrollable_shelf_view_->visible_space());
+  views::View::ConvertRectToTarget(scrollable_shelf_view_, this, &bounds);
+  return ToEnclosedRect(bounds).Contains(rect);
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // ScrollableShelfFocusSearch
 
 class ScrollableShelfFocusSearch : public views::FocusSearch {
@@ -292,7 +349,7 @@
   // within the overlapping zone between the arrow button's tapping area and
   // the bounds of |shelf_container_view_|.
   shelf_container_view_ =
-      AddChildView(std::make_unique<ShelfContainerView>(shelf_view_));
+      AddChildView(std::make_unique<ScrollableShelfContainerView>(this));
   shelf_container_view_->Initialize();
 
   // Initialize the left arrow button.
@@ -560,32 +617,10 @@
     right_arrow_bounds.ClampToCenteredSize(arrow_button_size);
   }
 
-  // The layout offset before/after |shelf_container_view_|.
-  int shelf_container_before_offset = 0;
-  int shelf_container_after_offset = 0;
-
-  if (layout_strategy_ == kNotShowArrowButtons) {
-    // Paddings are within the shelf view. It makes sure that |shelf_view_|'s
-    // bounds are not changed under this layout strategy. It facilitates
-    // |shelf_view_| to create bounds animations when adding/removing app icons.
-    shelf_view_->set_app_icons_layout_offset(before_padding);
-  } else {
-    shelf_container_before_offset = before_padding;
-    shelf_container_after_offset = after_padding;
-    shelf_view_->set_app_icons_layout_offset(0);
-  }
-
-  shelf_container_bounds.Inset(
-      shelf_container_before_offset +
-          (left_arrow_bounds.IsEmpty() ? GetAppIconEndPadding()
-                                       : kArrowButtonGroupWidth) -
-          ShelfConfig::Get()->scrollable_shelf_ripple_padding(),
-      0,
-      shelf_container_after_offset +
-          (right_arrow_bounds.IsEmpty() ? GetAppIconEndPadding()
-                                        : kArrowButtonGroupWidth) -
-          ShelfConfig::Get()->scrollable_shelf_ripple_padding(),
-      0);
+  // Paddings are within the shelf view. It makes sure that |shelf_view_|'s
+  // bounds are never changed.
+  shelf_view_->set_app_icons_layout_offset(before_padding +
+                                           GetAppIconEndPadding());
 
   // Adjust the bounds when not showing in the horizontal
   // alignment.tShelf()->IsHorizontalAlignment()) {
@@ -636,32 +671,14 @@
   // Layout |shelf_container_view_|.
   shelf_container_view_->SetBoundsRect(shelf_container_bounds);
 
-  // When the left button shows, the origin of |shelf_container_view_| changes.
-  // So translate |shelf_container_view| to show the shelf view correctly.
-  gfx::Vector2d translate_vector;
-  if (!left_arrow_bounds.IsEmpty()) {
-    translate_vector =
-        GetShelf()->IsHorizontalAlignment()
-            ? gfx::Vector2d(shelf_container_bounds.x() -
-                                GetAppIconEndPadding() - before_padding,
-                            0)
-            : gfx::Vector2d(0, shelf_container_bounds.y() -
-                                   GetAppIconEndPadding() - before_padding);
-  }
+  // Updates the clip rectangle of |shelf_container_view_|. Note that
+  // |shelf_container_view_|'s bounds are the same with ScrollableShelfView's.
+  // It is why we can use |visible_space_| directly without coordinate
+  // transformation.
+  UpdateVisibleSpace();
+  shelf_container_view_->layer()->SetClipRect(visible_space_);
 
-  // If |layout_strategy_| is kShowLeftArrowButton, apply extra translation
-  // offset on |shelf_view_|, which assures the enough space for the ripple
-  // ring of the last shelf item.
-  if (layout_strategy_ == kShowLeftArrowButton) {
-    translate_vector +=
-        GetShelf()->IsHorizontalAlignment()
-            ? gfx::Vector2d(
-                  ShelfConfig::Get()->scrollable_shelf_ripple_padding(), 0)
-            : gfx::Vector2d(
-                  0, ShelfConfig::Get()->scrollable_shelf_ripple_padding());
-  }
-
-  gfx::Vector2dF total_offset = scroll_offset_ + translate_vector;
+  gfx::Vector2dF total_offset = scroll_offset_;
   if (ShouldAdaptToRTL())
     total_offset = -total_offset;
 
@@ -751,13 +768,15 @@
     return false;
 
   const gfx::Rect screen_bounds = view->GetBoundsInScreen();
-  const gfx::Rect visible_bounds = shelf_container_view_->GetBoundsInScreen();
-  return visible_bounds.Contains(screen_bounds);
+  gfx::Rect visible_bounds_in_screen = visible_space_;
+  views::View::ConvertRectToScreen(this, &visible_bounds_in_screen);
+
+  return visible_bounds_in_screen.Contains(screen_bounds);
 }
 
 bool ScrollableShelfView::ShouldHideTooltip(
     const gfx::Point& cursor_location) const {
-  return !available_space_.Contains(cursor_location);
+  return !visible_space_.Contains(cursor_location);
 }
 
 const std::vector<aura::Window*> ScrollableShelfView::GetOpenWindowsForView(
@@ -1171,10 +1190,14 @@
   }
 
   int actual_scroll_distance = GetActualScrollOffset();
+  const gfx::Insets ripple_insets = CalculateRipplePaddingInsets();
+  const int ripple_padding_sum = GetShelf()->IsHorizontalAlignment()
+                                     ? ripple_insets.width()
+                                     : ripple_insets.height();
   int shelf_container_available_space =
-      (GetShelf()->IsHorizontalAlignment() ? shelf_container_view_->width()
-                                           : shelf_container_view_->height()) -
-      kGradientZoneLength;
+      (GetShelf()->IsHorizontalAlignment() ? visible_space_.width()
+                                           : visible_space_.height()) -
+      ripple_padding_sum;
   if (layout_strategy_ == kShowRightArrowButton ||
       layout_strategy_ == kShowButtons) {
     first_tappable_app_index_ = actual_scroll_distance / GetUnit();
@@ -1264,4 +1287,47 @@
   return offset;
 }
 
+void ScrollableShelfView::UpdateVisibleSpace() {
+  const int before_padding =
+      (left_arrow_->GetVisible() ? kArrowButtonGroupWidth : 0);
+  const int after_padding =
+      (right_arrow_->GetVisible() ? kArrowButtonGroupWidth : 0);
+
+  gfx::Insets visible_space_insets;
+  if (ShouldAdaptToRTL()) {
+    visible_space_insets = gfx::Insets(0, after_padding, 0, before_padding);
+  } else {
+    visible_space_insets =
+        GetShelf()->IsHorizontalAlignment()
+            ? gfx::Insets(0, before_padding, 0, after_padding)
+            : gfx::Insets(before_padding, 0, after_padding, 0);
+  }
+  visible_space_insets -= CalculateRipplePaddingInsets();
+
+  visible_space_ =
+      ShouldAdaptToRTL() ? available_space_ : GetMirroredRect(available_space_);
+  visible_space_.Inset(visible_space_insets);
+}
+
+gfx::Insets ScrollableShelfView::CalculateRipplePaddingInsets() const {
+  // Indicates whether it is in tablet mode with hotseat enabled.
+  const bool in_hotseat_tablet =
+      chromeos::switches::ShouldShowShelfHotseat() && IsInTabletMode();
+
+  const int ripple_padding =
+      ShelfConfig::Get()->scrollable_shelf_ripple_padding();
+  const int before_padding =
+      (in_hotseat_tablet && !left_arrow_->GetVisible()) ? 0 : ripple_padding;
+  const int after_padding =
+      (in_hotseat_tablet && !right_arrow_->GetVisible()) ? 0 : ripple_padding;
+
+  if (ShouldAdaptToRTL())
+    return gfx::Insets(0, after_padding, 0, before_padding);
+  else {
+    return GetShelf()->IsHorizontalAlignment()
+               ? gfx::Insets(0, before_padding, 0, after_padding)
+               : gfx::Insets(before_padding, 0, after_padding, 0);
+  }
+}
+
 }  // namespace ash
diff --git a/ash/shelf/scrollable_shelf_view.h b/ash/shelf/scrollable_shelf_view.h
index 40894a3..675a7b6 100644
--- a/ash/shelf/scrollable_shelf_view.h
+++ b/ash/shelf/scrollable_shelf_view.h
@@ -86,6 +86,8 @@
     default_last_focusable_child_ = default_last_focusable_child;
   }
 
+  const gfx::Rect& visible_space() const { return visible_space_; }
+
   // Size of the arrow button.
   static int GetArrowButtonSize();
 
@@ -257,6 +259,12 @@
   // the correct UI.
   int CalculateAdjustedOffset() const;
 
+  void UpdateVisibleSpace();
+
+  // Calculates the padding insets which help to show the edging app icon's
+  // ripple ring correctly.
+  gfx::Insets CalculateRipplePaddingInsets() const;
+
   LayoutStrategy layout_strategy_ = kNotShowArrowButtons;
 
   // Child views Owned by views hierarchy.
@@ -264,9 +272,14 @@
   ScrollArrowView* right_arrow_ = nullptr;
   ShelfContainerView* shelf_container_view_ = nullptr;
 
-  // Available space to accommodate shelf icons.
+  // Available space to accommodate child views.
   gfx::Rect available_space_;
 
+  // Visible space of |shelf_container_view| in ScrollableShelfView's local
+  // coordinates. Different from |available_space_|, |visible_space_| only
+  // contains app icons and is mirrored for horizontal shelf under RTL.
+  gfx::Rect visible_space_;
+
   ShelfView* shelf_view_ = nullptr;
 
   gfx::Vector2dF scroll_offset_;
diff --git a/ash/shelf/scrollable_shelf_view_unittest.cc b/ash/shelf/scrollable_shelf_view_unittest.cc
index 988d32f..415a6d1 100644
--- a/ash/shelf/scrollable_shelf_view_unittest.cc
+++ b/ash/shelf/scrollable_shelf_view_unittest.cc
@@ -125,11 +125,11 @@
   const views::View* last_visible_icon =
       view_model->view_at(scrollable_shelf_view_->last_tappable_app_index());
   const gfx::Rect icon_bounds = last_visible_icon->GetBoundsInScreen();
-  const gfx::Rect shelf_container_bounds =
-      scrollable_shelf_view_->shelf_container_view()->GetBoundsInScreen();
+  gfx::Rect visible_space = scrollable_shelf_view_->visible_space();
+  views::View::ConvertRectToScreen(scrollable_shelf_view_, &visible_space);
   EXPECT_EQ(icon_bounds.right() +
                 ShelfConfig::Get()->scrollable_shelf_ripple_padding(),
-            shelf_container_bounds.right());
+            visible_space.right());
   EXPECT_FALSE(scrollable_shelf_view_->ShouldAdjustForTest());
 }
 
diff --git a/ash/shelf/shelf_container_view.cc b/ash/shelf/shelf_container_view.cc
index d5282d05..72ce9e69 100644
--- a/ash/shelf/shelf_container_view.cc
+++ b/ash/shelf/shelf_container_view.cc
@@ -31,29 +31,6 @@
   PreferredSizeChanged();
 }
 
-void ShelfContainerView::Layout() {
-  // Should not use ShelfView::GetPreferredSize in replace of
-  // CalculateIdealSize. Because ShelfView::CalculatePreferredSize relies on the
-  // bounds of app icon. Meanwhile, the icon's bounds may be updated by
-  // animation.
-  const gfx::Rect ideal_bounds = gfx::Rect(CalculateIdealSize());
-
-  const gfx::Rect local_bounds = GetLocalBounds();
-  gfx::Rect shelf_view_bounds =
-      local_bounds.Contains(ideal_bounds) ? local_bounds : ideal_bounds;
-
-  // Offsets |shelf_view_bounds| to ensure the sufficient space for the ripple
-  // ring of the first shelf item.
-  if (shelf_view_->shelf()->IsHorizontalAlignment())
-    shelf_view_bounds.Offset(
-        ShelfConfig::Get()->scrollable_shelf_ripple_padding(), 0);
-  else
-    shelf_view_bounds.Offset(
-        0, ShelfConfig::Get()->scrollable_shelf_ripple_padding());
-
-  shelf_view_->SetBoundsRect(shelf_view_bounds);
-}
-
 const char* ShelfContainerView::GetClassName() const {
   return "ShelfContainerView";
 }
diff --git a/ash/shelf/shelf_container_view.h b/ash/shelf/shelf_container_view.h
index f83e1e326..01dedcc6 100644
--- a/ash/shelf/shelf_container_view.h
+++ b/ash/shelf/shelf_container_view.h
@@ -27,7 +27,6 @@
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
   void ChildPreferredSizeChanged(views::View* child) override;
-  void Layout() override;
   const char* GetClassName() const override;
 
  protected:
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 7a15611..fe371a7 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -312,12 +312,12 @@
     Shell::Get()->app_list_controller()->RemoveObserver(this);
   if (Shell::Get()->overview_controller())
     Shell::Get()->overview_controller()->RemoveObserver(this);
-  Shell::Get()->split_view_controller()->RemoveObserver(this);
+  SplitViewController::Get()->RemoveObserver(this);
 }
 
 void ShelfLayoutManager::InitObservers() {
   Shell::Get()->AddShellObserver(this);
-  Shell::Get()->split_view_controller()->AddObserver(this);
+  SplitViewController::Get()->AddObserver(this);
   Shell::Get()->overview_controller()->AddObserver(this);
   Shell::Get()->app_list_controller()->AddObserver(this);
   Shell::Get()
@@ -610,9 +610,8 @@
     return SHELF_BACKGROUND_LOGIN;
   }
 
-  const bool in_split_view_mode =
-      Shell::Get()->split_view_controller() &&
-      Shell::Get()->split_view_controller()->InSplitViewMode();
+  const bool in_split_view_mode = SplitViewController::Get() &&
+                                  SplitViewController::Get()->InSplitViewMode();
   const bool maximized =
       in_split_view_mode ||
       state_.window_state == WorkspaceWindowState::kFullscreen ||
diff --git a/ash/shell.cc b/ash/shell.cc
index ca0c2c1b..5d61872f 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -135,7 +135,6 @@
 #include "ash/wm/overview/overview_controller.h"
 #include "ash/wm/resize_shadow_controller.h"
 #include "ash/wm/screen_pinning_controller.h"
-#include "ash/wm/splitview/split_view_controller.h"
 #include "ash/wm/system_gesture_event_filter.h"
 #include "ash/wm/system_modal_container_event_filter.h"
 #include "ash/wm/system_modal_container_layout_manager.h"
@@ -775,10 +774,6 @@
   // destruction of its owned RootWindowControllers relies on the value.
   window_tree_host_manager_->Shutdown();
 
-  // Destroy |SplitViewController| and |RootWindowController| at about the same
-  // time, as we plan for |RootWindowController| to own a |SplitViewController|.
-  // TODO(crbug.com/970013): Actually carry out that plan.
-  split_view_controller_.reset();
   // Depends on |focus_controller_|, so must be destroyed before.
   window_tree_host_manager_.reset();
 
@@ -1107,10 +1102,6 @@
   system_notification_controller_ =
       std::make_unique<SystemNotificationController>();
 
-  // Create |SplitViewController| and |RootWindowController| at about the same
-  // time, as we plan for |RootWindowController| to own a |SplitViewController|.
-  // TODO(crbug.com/970013): Actually carry out that plan.
-  split_view_controller_ = std::make_unique<SplitViewController>();
   window_tree_host_manager_->InitHosts();
 
   // Create virtual keyboard after WindowTreeHostManager::InitHosts() since
diff --git a/ash/shell.h b/ash/shell.h
index 15ca000..20f304c 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -173,7 +173,6 @@
 class ShutdownControllerImpl;
 class SmsObserver;
 class SnapController;
-class SplitViewController;
 class StickyKeysController;
 class SystemGestureEventFilter;
 class SystemModalContainerEventFilter;
@@ -457,9 +456,6 @@
   ShutdownControllerImpl* shutdown_controller() {
     return shutdown_controller_.get();
   }
-  SplitViewController* split_view_controller() {
-    return split_view_controller_.get();
-  }
   StickyKeysController* sticky_keys_controller() {
     return sticky_keys_controller_.get();
   }
@@ -767,9 +763,6 @@
 
   std::unique_ptr<DockedMagnifierControllerImpl> docked_magnifier_controller_;
 
-  // The split view controller for Chrome OS.
-  std::unique_ptr<SplitViewController> split_view_controller_;
-
   std::unique_ptr<SnapController> snap_controller_;
 
   // |native_cursor_manager_| is owned by |cursor_manager_|, but we keep a
diff --git a/ash/system/message_center/arc/arc_notification_view.h b/ash/system/message_center/arc/arc_notification_view.h
index 14ccfe7..0fe506ac 100644
--- a/ash/system/message_center/arc/arc_notification_view.h
+++ b/ash/system/message_center/arc/arc_notification_view.h
@@ -56,7 +56,7 @@
   void OnSnoozeButtonPressed(const ui::Event& event) override;
   void UpdateCornerRadius(int top_radius, int bottom_radius) override;
 
-  // message_center::SlideOutController::Delegate:
+  // views::SlideOutControllerDelegate:
   void OnSlideChanged(bool in_progress) override;
 
   // Overridden from views::View:
diff --git a/ash/system/overview/overview_button_tray.cc b/ash/system/overview/overview_button_tray.cc
index 5b24629..8bfc038 100644
--- a/ash/system/overview/overview_button_tray.cc
+++ b/ash/system/overview/overview_button_tray.cc
@@ -108,8 +108,7 @@
       // default side. The window which was dragged to either side to begin
       // splitview will remain untouched. Skip that window if it appears in the
       // mru list.
-      SplitViewController* split_view_controller =
-          Shell::Get()->split_view_controller();
+      SplitViewController* split_view_controller = SplitViewController::Get();
       if (split_view_controller->InSplitViewMode() &&
           mru_window_list.size() > 2u) {
         if (mru_window_list[0] ==
diff --git a/ash/system/overview/overview_button_tray_unittest.cc b/ash/system/overview/overview_button_tray_unittest.cc
index 3ef6215..21c60fe 100644
--- a/ash/system/overview/overview_button_tray_unittest.cc
+++ b/ash/system/overview/overview_button_tray_unittest.cc
@@ -388,8 +388,7 @@
   // Enter splitview mode. Snap |window1| to the left, this will be the default
   // splitview window.
   Shell::Get()->overview_controller()->StartOverview();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(window2.get(), SplitViewController::RIGHT);
   ASSERT_EQ(window1.get(), split_view_controller->GetDefaultSnappedWindow());
diff --git a/ash/wm/base_state.cc b/ash/wm/base_state.cc
index a07fdf2..b7306d4 100644
--- a/ash/wm/base_state.cc
+++ b/ash/wm/base_state.cc
@@ -187,7 +187,7 @@
   gfx::Rect bounds_in_parent;
   if (ShouldAllowSplitView()) {
     bounds_in_parent =
-        Shell::Get()->split_view_controller()->GetSnappedWindowBoundsInParent(
+        SplitViewController::Get()->GetSnappedWindowBoundsInParent(
             window, (state_type == WindowStateType::kLeftSnapped)
                         ? SplitViewController::LEFT
                         : SplitViewController::RIGHT);
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
index a332db8..3f1f03b 100644
--- a/ash/wm/desks/desks_controller.cc
+++ b/ash/wm/desks/desks_controller.cc
@@ -236,8 +236,7 @@
       Shell::Get()->overview_controller()->EndOverview(
           OverviewSession::EnterExitOverviewType::kImmediateExit);
     }
-    SplitViewController* split_view_controller =
-        Shell::Get()->split_view_controller();
+    SplitViewController* split_view_controller = SplitViewController::Get();
     split_view_controller->EndSplitView(
         SplitViewController::EndReason::kDesksChange);
 
@@ -286,8 +285,7 @@
     // We are removing the active desk, which may have split view active.
     // We will restore the split view state of the newly activated desk at the
     // end of the animation.
-    SplitViewController* split_view_controller =
-        Shell::Get()->split_view_controller();
+    SplitViewController* split_view_controller = SplitViewController::Get();
     split_view_controller->EndSplitView(
         SplitViewController::EndReason::kDesksChange);
 
@@ -714,8 +712,7 @@
 
     // Exit split view if active, before activating the new desk. We will
     // restore the split view state of the newly activated desk at the end.
-    SplitViewController* split_view_controller =
-        Shell::Get()->split_view_controller();
+    SplitViewController* split_view_controller = SplitViewController::Get();
     split_view_controller->EndSplitView(
         SplitViewController::EndReason::kDesksChange);
 
diff --git a/ash/wm/desks/desks_unittests.cc b/ash/wm/desks/desks_unittests.cc
index b78dea8..b71dd36 100644
--- a/ash/wm/desks/desks_unittests.cc
+++ b/ash/wm/desks/desks_unittests.cc
@@ -1611,8 +1611,7 @@
 
   auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
   auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(win2.get(), SplitViewController::RIGHT);
   EXPECT_EQ(win1.get(), split_view_controller->left_window());
@@ -1649,8 +1648,7 @@
   ASSERT_EQ(2u, desks_controller->desks().size());
   auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
   auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(win2.get(), SplitViewController::RIGHT);
   EXPECT_EQ(win1.get(), split_view_controller->left_window());
@@ -1726,8 +1724,7 @@
       &win3_delegate, /*id=*/-1, gfx::Rect(big)));
   OverviewController* overview_controller = Shell::Get()->overview_controller();
   EXPECT_TRUE(overview_controller->StartOverview());
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   EXPECT_EQ(win1.get(), split_view_controller->left_window());
   EXPECT_FALSE(CanSnapInSplitview(win2.get()));
@@ -1781,8 +1778,7 @@
   Desk* desk_2 = desks_controller->desks()[1].get();
   auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
   auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(win2.get(), SplitViewController::RIGHT);
   EXPECT_EQ(win1.get(), split_view_controller->left_window());
@@ -1813,8 +1809,7 @@
   ASSERT_EQ(2u, desks_controller->desks().size());
   Desk* desk_2 = desks_controller->desks()[1].get();
   auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   EXPECT_EQ(win1.get(), split_view_controller->left_window());
   EXPECT_EQ(nullptr, split_view_controller->right_window());
@@ -1838,8 +1833,7 @@
   ASSERT_EQ(2u, desks_controller->desks().size());
   Desk* desk_2 = desks_controller->desks()[1].get();
   auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   EXPECT_EQ(win1.get(), split_view_controller->left_window());
   EXPECT_EQ(nullptr, split_view_controller->right_window());
@@ -1868,8 +1862,7 @@
 
   auto win1 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
   auto win2 = CreateAppWindow(gfx::Rect(0, 0, 250, 100));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(win1.get(), SplitViewController::LEFT);
   split_view_controller->SnapWindow(win2.get(), SplitViewController::RIGHT);
   auto* desk_1_backdrop_controller =
@@ -1951,8 +1944,7 @@
   EXPECT_TRUE(CanSnapInSplitview(window.get()));
 
   // Snap the window in this orientation.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window.get(), SplitViewController::LEFT);
   EXPECT_EQ(window.get(), split_view_controller->left_window());
   EXPECT_TRUE(split_view_controller->InSplitViewMode());
@@ -2055,7 +2047,7 @@
 TEST_F(DesksWithSplitViewTest, SwitchToDeskWithSnappedActiveWindow) {
   auto* desks_controller = DesksController::Get();
   auto* overview_controller = Shell::Get()->overview_controller();
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
 
   // Two virtual desks: |desk_1| (active) and |desk_2|.
   NewDesk();
diff --git a/ash/wm/immersive_fullscreen_controller_unittest.cc b/ash/wm/immersive_fullscreen_controller_unittest.cc
index e05a65f3..077332a 100644
--- a/ash/wm/immersive_fullscreen_controller_unittest.cc
+++ b/ash/wm/immersive_fullscreen_controller_unittest.cc
@@ -727,10 +727,9 @@
 
   // Top-of-window views will not be revealed for snapped window in splitview
   // mode either.
-  Shell::Get()->split_view_controller()->SnapWindow(window(),
-                                                    SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(window(), SplitViewController::LEFT);
   EXPECT_TRUE(WindowState::Get(window())->IsSnapped());
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   AttemptReveal(MODALITY_GESTURE_SCROLL);
   EXPECT_FALSE(controller()->IsRevealed());
 }
diff --git a/ash/wm/overview/overview_controller.cc b/ash/wm/overview/overview_controller.cc
index 9c455cb5..77bfb34 100644
--- a/ash/wm/overview/overview_controller.cc
+++ b/ash/wm/overview/overview_controller.cc
@@ -154,7 +154,7 @@
   // in the overview grid for the display where the overview button was long
   // pressed, and the first window in that overview grid is snappable.
 
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   // Exit split view mode if we are already in it.
   if (split_view_controller->InSplitViewMode()) {
     // In some cases the window returned by window_util::GetActiveWindow will be
@@ -460,7 +460,7 @@
     // in case of |SplitViewController::State::kBothSnapped|, that function will
     // insert one of the two snapped windows to overview.
     const SplitViewController::State split_view_state =
-        Shell::Get()->split_view_controller()->state();
+        SplitViewController::Get()->state();
     should_focus_overview_ =
         split_view_state == SplitViewController::State::kNoSnap ||
         split_view_state == SplitViewController::State::kBothSnapped;
@@ -504,7 +504,7 @@
 
 bool OverviewController::CanEnterOverview() {
   // Prevent toggling overview during the split view divider snap animation.
-  if (Shell::Get()->split_view_controller()->IsDividerAnimating())
+  if (SplitViewController::Get()->IsDividerAnimating())
     return false;
 
   // Don't allow a window overview if the user session is not active (e.g.
@@ -521,8 +521,7 @@
 
 bool OverviewController::CanEndOverview(
     OverviewSession::EnterExitOverviewType type) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   // Prevent toggling overview during the split view divider snap animation.
   if (split_view_controller->IsDividerAnimating())
     return false;
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index fc61acdc..4d36025 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -252,8 +252,7 @@
     }
   }
 
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   if (!split_view_controller->InSplitViewMode())
     return work_area;
 
@@ -305,7 +304,7 @@
 gfx::Rect GetGridBoundsInScreenForSplitview(
     aura::Window* window,
     base::Optional<IndicatorState> indicator_state = base::nullopt) {
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   auto state = split_view_controller->state();
 
   // If we are in splitview mode already just use the given state, otherwise
@@ -361,7 +360,7 @@
       split_view_drag_indicators->current_indicator_state() ==
           IndicatorState::kDragArea &&
       !IsCurrentScreenOrientationLandscape() &&
-      !Shell::Get()->split_view_controller()->InSplitViewMode()) {
+      !SplitViewController::Get()->InSplitViewMode()) {
     desks_widget_root_bounds.Offset(0,
                                     overview_grid_screen_bounds.height() *
                                             kHighlightScreenPrimaryAxisRatio +
@@ -476,7 +475,7 @@
 OverviewGrid::~OverviewGrid() = default;
 
 void OverviewGrid::Shutdown() {
-  Shell::Get()->split_view_controller()->RemoveObserver(this);
+  SplitViewController::Get()->RemoveObserver(this);
   ScreenRotationAnimator::GetForRootWindow(root_window_)->RemoveObserver(this);
   Shell::Get()->wallpaper_controller()->RemoveObserver(this);
   grid_event_handler_.reset();
@@ -497,8 +496,7 @@
       !Shell::Get()->tablet_mode_controller()->InTabletMode();
 
   // OverviewGrid in splitscreen does not include the window to be activated.
-  if (!window_list_.empty() ||
-      Shell::Get()->split_view_controller()->InSplitViewMode()) {
+  if (!window_list_.empty() || SplitViewController::Get()->InSplitViewMode()) {
     // The following instance self-destructs when shutdown animation ends.
     new ShutdownAnimationFpsCounterObserver(
         root_window_->layer()->GetCompositor(), single_animation_in_clamshell);
@@ -517,7 +515,7 @@
 
   for (const auto& window : window_list_)
     window->PrepareForOverview();
-  Shell::Get()->split_view_controller()->AddObserver(this);
+  SplitViewController::Get()->AddObserver(this);
   if (Shell::Get()->tablet_mode_controller()->InTabletMode())
     ScreenRotationAnimator::GetForRootWindow(root_window_)->AddObserver(this);
 
@@ -917,7 +915,7 @@
 void OverviewGrid::OnDisplayMetricsChanged() {
   // In case of split view mode, the grid bounds and item positions will be
   // updated in |OnSplitViewDividerPositionChanged|.
-  if (Shell::Get()->split_view_controller()->InSplitViewMode())
+  if (SplitViewController::Get()->InSplitViewMode())
     return;
   SetBoundsAndUpdatePositions(
       GetGridBoundsInScreen(root_window_, /*divider_changed=*/false),
@@ -932,8 +930,7 @@
   if (!overview_controller->InOverviewSession())
     return;
 
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   const bool unsnappable_window_activated =
       state == SplitViewController::State::kNoSnap &&
       split_view_controller->end_reason() ==
@@ -1086,7 +1083,7 @@
             });
 
   SkRegion occluded_region;
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   if (split_view_controller->InSplitViewMode()) {
     // Snapped windows and the split view divider are not included in
     // |target_bounds| or |window_list_|, but can occlude other windows, so add
diff --git a/ash/wm/overview/overview_grid_unittest.cc b/ash/wm/overview/overview_grid_unittest.cc
index fa7a471..05bf35d8 100644
--- a/ash/wm/overview/overview_grid_unittest.cc
+++ b/ash/wm/overview/overview_grid_unittest.cc
@@ -226,12 +226,12 @@
   wm::ActivateWindow(window1.get());
 
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
-  Shell::Get()->split_view_controller()->SnapWindow(window1.get(),
-                                                    SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(window1.get(),
+                                         SplitViewController::LEFT);
 
   // Snap |window2| and check that |window3| is maximized.
-  Shell::Get()->split_view_controller()->SnapWindow(window2.get(),
-                                                    SplitViewController::RIGHT);
+  SplitViewController::Get()->SnapWindow(window2.get(),
+                                         SplitViewController::RIGHT);
   EXPECT_TRUE(WindowState::Get(window3.get())->IsMaximized());
 
   // Tests that |window3| is not animated even though its bounds are larger than
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index 149313f..c644bcc 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -277,8 +277,7 @@
   // target state in |SplitViewController::OnOverviewModeEnding|.
   // Unify the mechanism to control it and remove ifs.
   if (Shell::Get()->tablet_mode_controller()->InTabletMode() &&
-      !Shell::Get()->split_view_controller()->InSplitViewMode() &&
-      reset_transform) {
+      !SplitViewController::Get()->InSplitViewMode() && reset_transform) {
     MaximizeIfSnapped(GetWindow());
   }
 
@@ -552,7 +551,7 @@
     visible = false;
   } else {
     const SplitViewController::State state =
-        Shell::Get()->split_view_controller()->state();
+        SplitViewController::Get()->state();
     visible = state == SplitViewController::State::kLeftSnapped ||
               state == SplitViewController::State::kRightSnapped;
   }
@@ -659,9 +658,9 @@
   aura::Window* dragged_window = GetWindow();
   aura::Window* dragged_widget_window = item_widget_->GetNativeWindow();
   aura::Window* parent_window = dragged_widget_window->parent();
-  if (Shell::Get()->split_view_controller()->InSplitViewMode()) {
+  if (SplitViewController::Get()->InSplitViewMode()) {
     aura::Window* snapped_window =
-        Shell::Get()->split_view_controller()->GetDefaultSnappedWindow();
+        SplitViewController::Get()->GetDefaultSnappedWindow();
     if (snapped_window->parent() == parent_window &&
         dragged_window->parent() == parent_window) {
       parent_window->StackChildBelow(dragged_window, snapped_window);
diff --git a/ash/wm/overview/overview_session.cc b/ash/wm/overview/overview_session.cc
index 940cc9f8..7a968e114 100644
--- a/ash/wm/overview/overview_session.cc
+++ b/ash/wm/overview/overview_session.cc
@@ -188,7 +188,7 @@
 
   UMA_HISTOGRAM_COUNTS_100("Ash.WindowSelector.Items", num_items_);
 
-  Shell::Get()->split_view_controller()->AddObserver(this);
+  SplitViewController::Get()->AddObserver(this);
 
   display::Screen::GetScreen()->AddObserver(this);
   base::RecordAction(base::UserMetricsAction("WindowSelector_Overview"));
@@ -217,7 +217,7 @@
   // Stop observing split view state changes before restoring window focus.
   // Otherwise the activation of the window triggers OnSplitViewStateChanged()
   // that will call into this function again.
-  Shell::Get()->split_view_controller()->RemoveObserver(this);
+  SplitViewController::Get()->RemoveObserver(this);
 
   size_t remaining_items = 0;
   for (std::unique_ptr<OverviewGrid>& overview_grid : grid_list_) {
@@ -274,7 +274,7 @@
   if (!IsEmpty())
     return;
 
-  if (Shell::Get()->split_view_controller()->InTabletSplitViewMode())
+  if (SplitViewController::Get()->InTabletSplitViewMode())
     UpdateNoWindowsWidget();
   else
     EndOverview();
@@ -393,7 +393,7 @@
                                    const gfx::PointF& location_in_screen,
                                    bool is_touch_dragging) {
   if (Shell::Get()->overview_controller()->IsInStartAnimation() ||
-      Shell::Get()->split_view_controller()->IsDividerAnimating()) {
+      SplitViewController::Get()->IsDividerAnimating()) {
     return;
   }
   highlight_controller_->SetFocusHighlightVisibility(false);
@@ -583,7 +583,7 @@
         // We do not want an active window in overview. It will cause blatantly
         // broken behavior as in the video linked in crbug.com/992223.
         wm::ActivateWindow(
-            Shell::Get()->split_view_controller()->GetDefaultSnappedWindow());
+            SplitViewController::Get()->GetDefaultSnappedWindow());
       }
     }
   }
@@ -627,7 +627,7 @@
   // Do not cancel overview mode if the window activation happens when split
   // view mode is also active. SplitViewController will do the right thing to
   // handle the window activation change.
-  if (Shell::Get()->split_view_controller()->InSplitViewMode())
+  if (SplitViewController::Get()->InSplitViewMode())
     return;
 
   // Do not cancel overview mode if the window activation was caused while
@@ -760,7 +760,7 @@
   }
   // In case of split view mode, the no windows widget bounds will be updated in
   // |OnSplitViewDividerPositionChanged|.
-  if (Shell::Get()->split_view_controller()->InSplitViewMode())
+  if (SplitViewController::Get()->InSplitViewMode())
     return;
   RefreshNoWindowsWidgetBounds(/*animate=*/false);
 }
@@ -789,9 +789,8 @@
   // If the new window is added when splitscreen is active, do nothing.
   // SplitViewController will do the right thing to snap the window or end
   // overview mode.
-  if (Shell::Get()->split_view_controller()->InSplitViewMode() &&
-      new_window->GetRootWindow() == Shell::Get()
-                                         ->split_view_controller()
+  if (SplitViewController::Get()->InSplitViewMode() &&
+      new_window->GetRootWindow() == SplitViewController::Get()
                                          ->GetDefaultSnappedWindow()
                                          ->GetRootWindow()) {
     return;
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index c7838c02..d36e43d 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -1502,8 +1502,8 @@
   EnterTabletMode();
   ToggleOverview();
   ASSERT_TRUE(overview_controller()->InOverviewSession());
-  Shell::Get()->split_view_controller()->SnapWindow(window.get(),
-                                                    SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(window.get(),
+                                         SplitViewController::LEFT);
   ASSERT_TRUE(overview_controller()->InOverviewSession());
   SendKey(ui::VKEY_ESCAPE);
   EXPECT_TRUE(overview_controller()->InOverviewSession());
@@ -1636,7 +1636,7 @@
 
   // Tests that when snapping a window to the left in splitview, the no windows
   // indicator shows up in the middle of the right side of the screen.
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window.get(), SplitViewController::LEFT);
   no_windows_widget = overview_session()->no_windows_widget_for_testing();
   ASSERT_TRUE(no_windows_widget);
@@ -1663,7 +1663,7 @@
   std::unique_ptr<aura::Window> window(CreateTestWindow());
 
   ToggleOverview();
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   split_view_controller->SnapWindow(window.get(), SplitViewController::LEFT);
   EXPECT_TRUE(overview_session()->no_windows_widget_for_testing());
 
@@ -2800,7 +2800,7 @@
   }
 
   SplitViewController* split_view_controller() {
-    return Shell::Get()->split_view_controller();
+    return SplitViewController::Get();
   }
 
  protected:
@@ -3191,7 +3191,7 @@
   }
 
   SplitViewController* split_view_controller() {
-    return Shell::Get()->split_view_controller();
+    return SplitViewController::Get();
   }
 
   bool IsDividerAnimating() {
@@ -3770,7 +3770,7 @@
   EXPECT_TRUE(overview_controller()->InOverviewSession());
 
   EndSplitView();
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(overview_controller()->InOverviewSession());
 
   // Test that ToggleOverview() can end overview if we're not in split view
@@ -3783,10 +3783,10 @@
   ToggleOverview();
   overview_item1 = GetOverviewItemForWindow(window1.get());
   DragWindowTo(overview_item1, gfx::PointF());
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(overview_controller()->InOverviewSession());
   Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_FALSE(overview_controller()->InOverviewSession());
 
   // Test that closing all windows in overview can end overview if we're not in
@@ -4392,8 +4392,8 @@
   ToggleOverview();
   // Snap a window to the left and test dragging the divider towards the right
   // edge of the screen.
-  Shell::Get()->split_view_controller()->SnapWindow(window1.get(),
-                                                    SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(window1.get(),
+                                         SplitViewController::LEFT);
   OverviewGrid* grid = overview_session()->grid_list()[0].get();
   ASSERT_TRUE(grid);
 
@@ -4417,8 +4417,8 @@
   ToggleOverview();
   // Snap a window to the right and test dragging the divider towards the left
   // edge of the screen.
-  Shell::Get()->split_view_controller()->SnapWindow(window1.get(),
-                                                    SplitViewController::RIGHT);
+  SplitViewController::Get()->SnapWindow(window1.get(),
+                                         SplitViewController::RIGHT);
   grid = overview_session()->grid_list()[0].get();
   ASSERT_TRUE(grid);
 
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index 3535c9c..be00154 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -48,8 +48,7 @@
 }  // namespace
 
 bool CanCoverAvailableWorkspace(aura::Window* window) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   if (split_view_controller->InSplitViewMode())
     return CanSnapInSplitview(window);
   return WindowState::Get(window)->IsMaximizedOrFullscreenOrPinned();
diff --git a/ash/wm/overview/overview_window_drag_controller.cc b/ash/wm/overview/overview_window_drag_controller.cc
index 9463ed4..3dba108e 100644
--- a/ash/wm/overview/overview_window_drag_controller.cc
+++ b/ash/wm/overview/overview_window_drag_controller.cc
@@ -130,7 +130,7 @@
     OverviewItem* item,
     bool is_touch_dragging)
     : overview_session_(overview_session),
-      split_view_controller_(Shell::Get()->split_view_controller()),
+      split_view_controller_(SplitViewController::Get()),
       item_(item),
       on_desks_bar_item_size_(GetItemSizeWhenOnDesksBar(item)),
       display_count_(Shell::GetAllRootWindows().size()),
@@ -138,7 +138,7 @@
       should_allow_split_view_(ShouldAllowSplitView()),
       virtual_desks_bar_enabled_(GetVirtualDesksBarEnabled(item)) {
   DCHECK(!Shell::Get()->overview_controller()->IsInStartAnimation());
-  DCHECK(!Shell::Get()->split_view_controller()->IsDividerAnimating());
+  DCHECK(!SplitViewController::Get()->IsDividerAnimating());
 }
 
 OverviewWindowDragController::~OverviewWindowDragController() = default;
@@ -684,7 +684,7 @@
   DCHECK_NE(snap_position, SplitViewController::NONE);
 
   // |item_| will be deleted after SplitViewController::SnapWindow().
-  DCHECK(!Shell::Get()->split_view_controller()->IsDividerAnimating());
+  DCHECK(!SplitViewController::Get()->IsDividerAnimating());
   aura::Window* window = item_->GetWindow();
   split_view_controller_->SnapWindow(window, snap_position,
                                      /*use_divider_spawn_animation=*/true);
diff --git a/ash/wm/overview/overview_window_drag_controller_unittest.cc b/ash/wm/overview/overview_window_drag_controller_unittest.cc
index f5e39e0..a45066d 100644
--- a/ash/wm/overview/overview_window_drag_controller_unittest.cc
+++ b/ash/wm/overview/overview_window_drag_controller_unittest.cc
@@ -310,7 +310,7 @@
   }
 
   SplitViewController* split_view_controller() {
-    return Shell::Get()->split_view_controller();
+    return SplitViewController::Get();
   }
 
   OverviewSession* overview_session() {
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index 25d4339..1975747e 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -137,7 +137,7 @@
   // activated.
   // TODO(sammiequon): This does not handle the case if either the snapped
   // window or this window is an always on top window.
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   if (ShouldUseTabletModeGridLayout() &&
       split_view_controller->InSplitViewMode()) {
     aura::Window* snapped_window =
diff --git a/ash/wm/splitview/split_view_controller.cc b/ash/wm/splitview/split_view_controller.cc
index e642109..34051e1 100644
--- a/ash/wm/splitview/split_view_controller.cc
+++ b/ash/wm/splitview/split_view_controller.cc
@@ -12,6 +12,7 @@
 #include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/presentation_time_recorder.h"
 #include "ash/public/cpp/window_properties.h"
+#include "ash/root_window_controller.h"
 #include "ash/scoped_animation_disabler.h"
 #include "ash/screen_util.h"
 #include "ash/session/session_controller_impl.h"
@@ -272,6 +273,12 @@
   int ending_position_;
 };
 
+// static
+SplitViewController* SplitViewController::Get() {
+  DCHECK(Shell::GetPrimaryRootWindowController());
+  return Shell::GetPrimaryRootWindowController()->split_view_controller();
+}
+
 SplitViewController::SplitViewController() {
   Shell::Get()->accessibility_controller()->AddObserver(this);
   display::Screen::GetScreen()->AddObserver(this);
@@ -284,7 +291,11 @@
 }
 
 SplitViewController::~SplitViewController() {
+  if (Shell::Get()->tablet_mode_controller())
+    Shell::Get()->tablet_mode_controller()->RemoveObserver(this);
   display::Screen::GetScreen()->RemoveObserver(this);
+  if (Shell::Get()->accessibility_controller())
+    Shell::Get()->accessibility_controller()->RemoveObserver(this);
   EndSplitView();
 }
 
diff --git a/ash/wm/splitview/split_view_controller.h b/ash/wm/splitview/split_view_controller.h
index 7ecc77f3..fe6de29 100644
--- a/ash/wm/splitview/split_view_controller.h
+++ b/ash/wm/splitview/split_view_controller.h
@@ -94,6 +94,13 @@
     kBothSnapped,
   };
 
+  // For now, there is only one |SplitViewController|, regardless of whether the
+  // |ash::features::kMultiDisplayOverviewAndSplitView| feature flag is enabled.
+  // TODO(crbug.com/970013): When the feature flag is enabled, there shall be a
+  // |SplitViewController| for each root window. Instead of Get, this function
+  // shall be ForWindow, similar to |RootWindowController::ForWindow|.
+  static SplitViewController* Get();
+
   SplitViewController();
   ~SplitViewController() override;
 
diff --git a/ash/wm/splitview/split_view_controller_unittest.cc b/ash/wm/splitview/split_view_controller_unittest.cc
index 39a30d2..f6fd5c8 100644
--- a/ash/wm/splitview/split_view_controller_unittest.cc
+++ b/ash/wm/splitview/split_view_controller_unittest.cc
@@ -197,7 +197,7 @@
   }
 
   SplitViewController* split_view_controller() {
-    return Shell::Get()->split_view_controller();
+    return SplitViewController::Get();
   }
 
   SplitViewDivider* split_view_divider() {
diff --git a/ash/wm/splitview/split_view_divider.cc b/ash/wm/splitview/split_view_divider.cc
index a40739b..65192bb 100644
--- a/ash/wm/splitview/split_view_divider.cc
+++ b/ash/wm/splitview/split_view_divider.cc
@@ -75,7 +75,7 @@
 class DividerView : public views::View, public views::ViewTargeterDelegate {
  public:
   explicit DividerView(SplitViewDivider* divider)
-      : controller_(Shell::Get()->split_view_controller()), divider_(divider) {
+      : controller_(SplitViewController::Get()), divider_(divider) {
     divider_view_ = new views::View();
     divider_view_->SetPaintToLayer(ui::LAYER_SOLID_COLOR);
     divider_view_->layer()->SetColor(kSplitviewDividerColor);
diff --git a/ash/wm/splitview/split_view_divider_handler_view.cc b/ash/wm/splitview/split_view_divider_handler_view.cc
index 96fc7c2..a20c6020 100644
--- a/ash/wm/splitview/split_view_divider_handler_view.cc
+++ b/ash/wm/splitview/split_view_divider_handler_view.cc
@@ -124,7 +124,7 @@
   spawning_animation_.reset();
   SetVisible(true);
   selection_animation_->UpdateWhiteHandlerBounds();
-  if (Shell::Get()->split_view_controller()->is_resizing())
+  if (SplitViewController::Get()->is_resizing())
     selection_animation_->Show();
   else
     selection_animation_->Hide();
diff --git a/ash/wm/splitview/split_view_drag_indicators.cc b/ash/wm/splitview/split_view_drag_indicators.cc
index a9b357c4..2c321fc 100644
--- a/ash/wm/splitview/split_view_drag_indicators.cc
+++ b/ash/wm/splitview/split_view_drag_indicators.cc
@@ -169,7 +169,7 @@
   void OnIndicatorTypeChanged(IndicatorState indicator_state,
                               IndicatorState previous_indicator_state) {
     // In split view, the labels never show, and they do not need to be updated.
-    if (Shell::Get()->split_view_controller()->InSplitViewMode())
+    if (SplitViewController::Get()->InSplitViewMode())
       return;
 
     // On transition to a state with no indicators, any label that is showing
@@ -377,7 +377,7 @@
     if (IsPreviewAreaState(indicator_state_) || nix_preview_inset) {
       // Get the preview area bounds from the split view controller.
       preview_area_bounds =
-          Shell::Get()->split_view_controller()->GetSnappedWindowBoundsInScreen(
+          SplitViewController::Get()->GetSnappedWindowBoundsInScreen(
               GetWidget()->GetNativeWindow(),
               preview_state == IndicatorState::kPreviewAreaLeft
                   ? SplitViewController::LEFT
diff --git a/ash/wm/splitview/split_view_drag_indicators_unittest.cc b/ash/wm/splitview/split_view_drag_indicators_unittest.cc
index fee3fe3..c5eb799 100644
--- a/ash/wm/splitview/split_view_drag_indicators_unittest.cc
+++ b/ash/wm/splitview/split_view_drag_indicators_unittest.cc
@@ -64,7 +64,7 @@
   }
 
   SplitViewController* split_view_controller() {
-    return Shell::Get()->split_view_controller();
+    return SplitViewController::Get();
   }
 
   IndicatorState indicator_state() {
diff --git a/ash/wm/splitview/split_view_highlight_view.cc b/ash/wm/splitview/split_view_highlight_view.cc
index dfab8f3..bcdcb5a 100644
--- a/ash/wm/splitview/split_view_highlight_view.cc
+++ b/ash/wm/splitview/split_view_highlight_view.cc
@@ -233,7 +233,7 @@
         SplitViewDragIndicators::IsPreviewAreaOnLeftTopOfScreen(
             previous_indicator_state);
     DoSplitviewOpacityAnimation(
-        layer(), Shell::Get()->split_view_controller()->InSplitViewMode()
+        layer(), SplitViewController::Get()->InSplitViewMode()
                      ? SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_OUT
                      : (was_this_the_preview
                             ? SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN
@@ -244,10 +244,10 @@
   SetColor(SplitViewDragIndicators::IsCannotSnapState(indicator_state)
                ? SK_ColorBLACK
                : SK_ColorWHITE);
-  DoSplitviewOpacityAnimation(
-      layer(), Shell::Get()->split_view_controller()->InSplitViewMode()
-                   ? SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_OUT
-                   : SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN);
+  DoSplitviewOpacityAnimation(layer(),
+                              SplitViewController::Get()->InSplitViewMode()
+                                  ? SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_OUT
+                                  : SPLITVIEW_ANIMATION_HIGHLIGHT_FADE_IN);
 }
 
 }  // namespace ash
diff --git a/ash/wm/splitview/split_view_test_api.cc b/ash/wm/splitview/split_view_test_api.cc
index 5c05d03b..5e66fc5 100644
--- a/ash/wm/splitview/split_view_test_api.cc
+++ b/ash/wm/splitview/split_view_test_api.cc
@@ -4,7 +4,6 @@
 
 #include "ash/public/cpp/split_view_test_api.h"
 
-#include "ash/shell.h"
 #include "ash/wm/splitview/split_view_controller.h"
 
 namespace ash {
@@ -28,11 +27,11 @@
       position = SplitViewController::RIGHT;
       break;
   }
-  Shell::Get()->split_view_controller()->SnapWindow(window, position);
+  SplitViewController::Get()->SnapWindow(window, position);
 }
 
 void SplitViewTestApi::SwapWindows() {
-  Shell::Get()->split_view_controller()->SwapWindows();
+  SplitViewController::Get()->SwapWindows();
 }
 
 }  // namespace ash
diff --git a/ash/wm/splitview/split_view_utils.cc b/ash/wm/splitview/split_view_utils.cc
index 14b4957..12ededd 100644
--- a/ash/wm/splitview/split_view_utils.cc
+++ b/ash/wm/splitview/split_view_utils.cc
@@ -241,8 +241,7 @@
   // could snap windows that were not in split view. Also, a window may have
   // become full screen, and if so, then it would be better not to reactivate
   // split view. See https://crbug.com/944134.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
 
   if (refresh_snapped_windows) {
     const MruWindowTracker::WindowList windows =
diff --git a/ash/wm/tablet_mode/tablet_mode_controller.cc b/ash/wm/tablet_mode/tablet_mode_controller.cc
index 993497e6..3169b0a 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller.cc
@@ -943,7 +943,7 @@
   // there is a window snapped on one side but no window snapped on the other
   // side, then overview mode should be started (to be seen on the side with
   // no snapped window).
-  const auto state = Shell::Get()->split_view_controller()->state();
+  const auto state = SplitViewController::Get()->state();
   if (state == SplitViewController::State::kLeftSnapped ||
       state == SplitViewController::State::kRightSnapped) {
     Shell::Get()->overview_controller()->StartOverview();
diff --git a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
index f120c87..b53ee88 100644
--- a/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_controller_unittest.cc
@@ -1138,8 +1138,7 @@
 // Test that if the active window is not snapped before tablet mode, then split
 // view is not activated.
 TEST_P(TabletModeControllerTest, StartTabletActiveNoSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> window = CreateTestWindow();
   tablet_mode_controller()->SetEnabledForTest(true);
   EXPECT_EQ(SplitViewController::State::kNoSnap,
@@ -1150,8 +1149,7 @@
 // Test that if the active window is snapped on the left before tablet mode,
 // then split view is activated with the active window on the left.
 TEST_P(TabletModeControllerTest, StartTabletActiveLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> window = CreateDesktopWindowSnappedLeft();
   tablet_mode_controller()->SetEnabledForTest(true);
   EXPECT_EQ(SplitViewController::State::kLeftSnapped,
@@ -1164,8 +1162,7 @@
 // Test that if the active window is snapped on the right before tablet mode,
 // then split view is activated with the active window on the right.
 TEST_P(TabletModeControllerTest, StartTabletActiveRightSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> window = CreateDesktopWindowSnappedRight();
   tablet_mode_controller()->SetEnabledForTest(true);
   EXPECT_EQ(SplitViewController::State::kRightSnapped,
@@ -1179,8 +1176,7 @@
 // the previous window is snapped on the right, then split view is activated
 // with the active window on the left and the previous window on the right.
 TEST_P(TabletModeControllerTest, StartTabletActiveLeftSnapPreviousRightSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
   std::unique_ptr<aura::Window> right_window =
       CreateDesktopWindowSnappedRight();
@@ -1198,8 +1194,7 @@
 // and the previous window is snapped on the left, then split view is activated
 // with the active window on the right and the previous window on the left.
 TEST_P(TabletModeControllerTest, StartTabletActiveRightSnapPreviousLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
   std::unique_ptr<aura::Window> right_window =
       CreateDesktopWindowSnappedRight();
@@ -1218,8 +1213,7 @@
 // is not activated.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveArcLeftSnapPreviousRightSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
   left_window->SetProperty(aura::client::kAppType,
                            static_cast<int>(AppType::ARC_APP));
@@ -1238,8 +1232,7 @@
 // window), then split view is activated with the active window on the left.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveLeftSnapPreviousArcRightSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
   std::unique_ptr<aura::Window> right_window =
       CreateDesktopWindowSnappedRight();
@@ -1261,8 +1254,7 @@
 // window snapped on the left, then split view is activated with the parent
 // snapped on the left.
 TEST_P(TabletModeControllerTest, StartTabletActiveTransientChildOfLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> parent = CreateDesktopWindowSnappedLeft();
   std::unique_ptr<aura::Window> child =
       CreateTestWindow(gfx::Rect(), aura::client::WINDOW_TYPE_POPUP);
@@ -1280,8 +1272,7 @@
 // previous window is snapped on the left, then split view is activated with the
 // previous window on the left.
 TEST_P(TabletModeControllerTest, StartTabletActiveAppListPreviousLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> window = CreateDesktopWindowSnappedLeft();
   Shell::Get()->app_list_controller()->ShowAppList();
   ASSERT_TRUE(wm::IsActiveWindow(
@@ -1298,8 +1289,7 @@
 // previous window is snapped on the left, then split view is activated with the
 // previous window on the left.
 TEST_P(TabletModeControllerTest, StartTabletActiveDraggedPreviousLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> dragged_window = CreateTestWindow();
   std::unique_ptr<aura::Window> snapped_window =
       CreateDesktopWindowSnappedLeft();
@@ -1320,8 +1310,7 @@
 // with the previous window on the left.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveHiddenFromOverviewPreviousLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> window_hidden_from_overview =
       CreateTestWindow();
   window_hidden_from_overview->SetProperty(kHideInOverviewKey, true);
@@ -1341,8 +1330,7 @@
 // split view is activated with the parent on the left.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveDraggedPreviousTransientChildOfLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> dragged_window = CreateTestWindow();
   std::unique_ptr<aura::Window> parent = CreateDesktopWindowSnappedLeft();
   std::unique_ptr<aura::Window> child =
@@ -1366,8 +1354,7 @@
 // window is snapped on the right, then split view is not activated.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveDesktopOnlyLeftSnapPreviousRightSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   aura::test::TestWindowDelegate left_window_delegate;
   std::unique_ptr<aura::Window> left_window(CreateTestWindowInShellWithDelegate(
       &left_window_delegate, /*id=*/-1, /*bounds=*/gfx::Rect(0, 0, 400, 400)));
@@ -1395,8 +1382,7 @@
 // previous window is snapped on the left, then split view is not activated.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveDesktopOnlyRightSnapPreviousLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
   aura::test::TestWindowDelegate right_window_delegate;
   std::unique_ptr<aura::Window> right_window(
@@ -1426,8 +1412,7 @@
 // the active window on the left.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveLeftSnapPreviousDesktopOnlyRightSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> left_window = CreateDesktopWindowSnappedLeft();
   aura::test::TestWindowDelegate right_window_delegate;
   std::unique_ptr<aura::Window> right_window(
@@ -1459,8 +1444,7 @@
 // the active window on the right.
 TEST_P(TabletModeControllerTest,
        StartTabletActiveRightSnapPreviousDesktopOnlyLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   aura::test::TestWindowDelegate left_window_delegate;
   std::unique_ptr<aura::Window> left_window(CreateTestWindowInShellWithDelegate(
       &left_window_delegate, /*id=*/-1, /*bounds=*/gfx::Rect(0, 0, 400, 400)));
@@ -1501,8 +1485,7 @@
 // the left before tablet mode, then split view is activated with the active
 // window on the left.
 TEST_P(TabletModeControllerTest, StartTabletActiveLeftSnapPreviousLeftSnap) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   std::unique_ptr<aura::Window> window1 = CreateDesktopWindowSnappedLeft();
   std::unique_ptr<aura::Window> window2 = CreateDesktopWindowSnappedLeft();
   wm::ActivateWindow(window1.get());
diff --git a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
index 6c28bc0..794e9bb 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_drag_delegate.cc
@@ -95,7 +95,7 @@
 }  // namespace
 
 TabletModeWindowDragDelegate::TabletModeWindowDragDelegate()
-    : split_view_controller_(Shell::Get()->split_view_controller()),
+    : split_view_controller_(SplitViewController::Get()),
       split_view_drag_indicators_(std::make_unique<SplitViewDragIndicators>()) {
 }
 
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager.cc b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
index 8ff0ac89..bea2323 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager.cc
@@ -132,7 +132,7 @@
       return left_window ? work_area.height() - left_window_bounds.height()
                          : right_window_bounds.height();
     default:
-      return Shell::Get()->split_view_controller()->GetDefaultDividerPosition(
+      return SplitViewController::Get()->GetDefaultDividerPosition(
           left_window ? left_window : right_window);
   }
 }
@@ -144,8 +144,7 @@
   if (windows.empty())
     return;
 
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   // If split view mode is already active, use its own divider position.
   if (!split_view_controller->InSplitViewMode())
     split_view_controller->InitDividerPositionForTransition(divider_position);
@@ -245,7 +244,7 @@
   }
   AddWindowCreationObservers();
   display::Screen::GetScreen()->AddObserver(this);
-  Shell::Get()->split_view_controller()->AddObserver(this);
+  SplitViewController::Get()->AddObserver(this);
   Shell::Get()->session_controller()->AddObserver(this);
   Shell::Get()->overview_controller()->AddObserver(this);
   accounts_since_entering_tablet_.insert(
@@ -286,14 +285,13 @@
     // single split case to match the clamshell split view behavior. (there is
     // no both snapped state or single split state in clamshell split view). The
     // windows will still be kept snapped though.
-    SplitViewController* split_view_controller =
-        Shell::Get()->split_view_controller();
+    SplitViewController* split_view_controller = SplitViewController::Get();
     if (split_view_controller->InSplitViewMode()) {
       OverviewController* overview_controller =
           Shell::Get()->overview_controller();
       if (!overview_controller->InOverviewSession() ||
           overview_controller->overview_session()->IsEmpty()) {
-        Shell::Get()->split_view_controller()->EndSplitView(
+        SplitViewController::Get()->EndSplitView(
             SplitViewController::EndReason::kExitTabletMode);
         overview_controller->EndOverview();
       }
@@ -303,7 +301,7 @@
   for (aura::Window* window : added_windows_)
     window->RemoveObserver(this);
   added_windows_.clear();
-  Shell::Get()->split_view_controller()->RemoveObserver(this);
+  SplitViewController::Get()->RemoveObserver(this);
   Shell::Get()->session_controller()->RemoveObserver(this);
   Shell::Get()->overview_controller()->RemoveObserver(this);
   display::Screen::GetScreen()->RemoveObserver(this);
@@ -361,7 +359,7 @@
   if (canceled)
     return;
 
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
 
   // Maximize all snapped windows upon exiting overview mode except snapped
   // windows in splitview mode. Note the snapped window might not be tracked in
@@ -383,7 +381,7 @@
     SplitViewController::State state) {
   if (state != SplitViewController::State::kNoSnap)
     return;
-  switch (Shell::Get()->split_view_controller()->end_reason()) {
+  switch (SplitViewController::Get()->end_reason()) {
     case SplitViewController::EndReason::kNormal:
     case SplitViewController::EndReason::kUnsnappableWindowActivated:
     case SplitViewController::EndReason::kPipExpanded:
@@ -516,8 +514,7 @@
 
 void TabletModeWindowManager::OnActiveUserSessionChanged(
     const AccountId& account_id) {
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
 
   // There is only one SplitViewController object for all user sessions, but
   // functionally, each user session independently can be in split view or not.
diff --git a/ash/wm/tablet_mode/tablet_mode_window_manager_unittest.cc b/ash/wm/tablet_mode/tablet_mode_window_manager_unittest.cc
index 90348363..40c24f9 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_manager_unittest.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_manager_unittest.cc
@@ -1837,7 +1837,7 @@
   // After transition, we should be in single split screen.
   CreateTabletModeWindowManager();
   EXPECT_TRUE(overview_controller->InOverviewSession());
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
 
   // 6. Tablet -> Clamshell. Since clamshell splitscreen is not enabled, oveview
@@ -1845,7 +1845,7 @@
   // state.
   DestroyTabletModeWindowManager();
   EXPECT_FALSE(overview_controller->InOverviewSession());
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
 
   // Create another normal state window to test additional scenarios.
@@ -1861,13 +1861,13 @@
 
   // 8. Tablet -> Clamshell. If the two windows are in splitscreen in tablet
   // mode, after transition they will restore to their old window states.
-  Shell::Get()->split_view_controller()->SnapWindow(window.get(),
-                                                    SplitViewController::LEFT);
-  Shell::Get()->split_view_controller()->SnapWindow(window2.get(),
-                                                    SplitViewController::RIGHT);
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  SplitViewController::Get()->SnapWindow(window.get(),
+                                         SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(window2.get(),
+                                         SplitViewController::RIGHT);
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   DestroyTabletModeWindowManager();
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
   EXPECT_FALSE(WindowState::Get(window2.get())->IsSnapped());
 
@@ -1876,16 +1876,16 @@
   const WMEvent event2(WM_EVENT_SNAP_RIGHT);
   WindowState::Get(window2.get())->OnWMEvent(&event2);
   CreateTabletModeWindowManager();
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
 
   // 10. Tablet -> Clamshell. If overview and splitview are both active, they
   // will be both ended after the transition.
   overview_controller->StartOverview();
   EXPECT_TRUE(overview_controller->InOverviewSession());
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   DestroyTabletModeWindowManager();
   EXPECT_FALSE(overview_controller->InOverviewSession());
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
 }
 
 // The class to test TabletModeWindowManagerTest related functionalities when
@@ -1952,14 +1952,14 @@
   // After transition, we should be in single split screen.
   CreateTabletModeWindowManager();
   EXPECT_TRUE(overview_controller->InOverviewSession());
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
 
   // 6. Tablet -> Clamshell. Since there is only 1 window, splitview and
   // overview will be both ended. The window will be kept snapped.
   DestroyTabletModeWindowManager();
   EXPECT_FALSE(overview_controller->InOverviewSession());
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
 
   // Create another normal state window to test additional scenarios.
@@ -1974,22 +1974,22 @@
 
   // 8. Tablet -> Clamshell. If tablet splitscreen is active with two snapped
   // windows, the two windows will remain snapped in clamshell mode.
-  Shell::Get()->split_view_controller()->SnapWindow(window.get(),
-                                                    SplitViewController::LEFT);
-  Shell::Get()->split_view_controller()->SnapWindow(window2.get(),
-                                                    SplitViewController::RIGHT);
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  SplitViewController::Get()->SnapWindow(window.get(),
+                                         SplitViewController::LEFT);
+  SplitViewController::Get()->SnapWindow(window2.get(),
+                                         SplitViewController::RIGHT);
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_FALSE(overview_controller->InOverviewSession());
   DestroyTabletModeWindowManager();
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
   EXPECT_TRUE(WindowState::Get(window2.get())->IsSnapped());
-  EXPECT_FALSE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_FALSE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_FALSE(overview_controller->InOverviewSession());
 
   // 9. Clamshell -> Tablet. If two window are snapped to two sides of the
   // screen, they will carry over to splitscreen in tablet mode.
   CreateTabletModeWindowManager();
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_FALSE(overview_controller->InOverviewSession());
   EXPECT_TRUE(WindowState::Get(window.get())->IsSnapped());
   EXPECT_TRUE(WindowState::Get(window2.get())->IsSnapped());
@@ -1997,15 +1997,15 @@
   // 10. Tablet -> Clamshell. If overview and splitview are both active, after
   // transition, they will remain both active.
   overview_controller->StartOverview();
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(overview_controller->InOverviewSession());
   DestroyTabletModeWindowManager();
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(overview_controller->InOverviewSession());
 
   // 11. Clamshell -> Tablet. The same as 10.
   CreateTabletModeWindowManager();
-  EXPECT_TRUE(Shell::Get()->split_view_controller()->InSplitViewMode());
+  EXPECT_TRUE(SplitViewController::Get()->InSplitViewMode());
   EXPECT_TRUE(overview_controller->InOverviewSession());
 }
 
@@ -2016,8 +2016,7 @@
   gfx::Rect rect(10, 10, 200, 50);
   std::unique_ptr<aura::Window> window(
       CreateWindow(aura::client::WINDOW_TYPE_NORMAL, rect));
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   OverviewController* overview_controller = Shell::Get()->overview_controller();
 
   // First test 1 window case.
diff --git a/ash/wm/tablet_mode/tablet_mode_window_state.cc b/ash/wm/tablet_mode/tablet_mode_window_state.cc
index f8feb49e..570ff101 100644
--- a/ash/wm/tablet_mode/tablet_mode_window_state.cc
+++ b/ash/wm/tablet_mode/tablet_mode_window_state.cc
@@ -85,17 +85,13 @@
   }
 
   if (state_object->GetStateType() == WindowStateType::kLeftSnapped) {
-    return Shell::Get()
-        ->split_view_controller()
-        ->GetSnappedWindowBoundsInParent(state_object->window(),
-                                         SplitViewController::LEFT);
+    return SplitViewController::Get()->GetSnappedWindowBoundsInParent(
+        state_object->window(), SplitViewController::LEFT);
   }
 
   if (state_object->GetStateType() == WindowStateType::kRightSnapped) {
-    return Shell::Get()
-        ->split_view_controller()
-        ->GetSnappedWindowBoundsInParent(state_object->window(),
-                                         SplitViewController::RIGHT);
+    return SplitViewController::Get()->GetSnappedWindowBoundsInParent(
+        state_object->window(), SplitViewController::RIGHT);
   }
 
   gfx::Rect bounds_in_parent;
diff --git a/ash/wm/window_util.cc b/ash/wm/window_util.cc
index 9fa60d4..b462556 100644
--- a/ash/wm/window_util.cc
+++ b/ash/wm/window_util.cc
@@ -233,7 +233,7 @@
   // overview mode. The default snap position is the position where the window
   // was first snapped. See |default_snap_position_| in SplitViewController for
   // more detail.
-  auto* split_view_controller = Shell::Get()->split_view_controller();
+  auto* split_view_controller = SplitViewController::Get();
   if (split_view_controller->InTabletSplitViewMode() &&
       window == split_view_controller->GetDefaultSnappedWindow()) {
     return true;
diff --git a/ash/wm/wm_shadow_controller_delegate.cc b/ash/wm/wm_shadow_controller_delegate.cc
index c201afa..bbaa2019 100644
--- a/ash/wm/wm_shadow_controller_delegate.cc
+++ b/ash/wm/wm_shadow_controller_delegate.cc
@@ -23,8 +23,7 @@
 bool WmShadowControllerDelegate::ShouldShowShadowForWindow(
     const aura::Window* window) {
   // Hide the shadow if it is one of the splitscreen snapped windows.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   if (split_view_controller &&
       split_view_controller->IsWindowInSplitView(window)) {
     return false;
diff --git a/ash/wm/workspace/backdrop_controller.cc b/ash/wm/workspace/backdrop_controller.cc
index 5682c74..83e3a75 100644
--- a/ash/wm/workspace/backdrop_controller.cc
+++ b/ash/wm/workspace/backdrop_controller.cc
@@ -81,8 +81,7 @@
 
   // For the active desk, only use the windows snapped in SplitViewController if
   // SplitView mode is active.
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   if (desks_util::IsActiveDeskContainer(desk_container) &&
       split_view_controller->InSplitViewMode()) {
     aura::Window* left_window = split_view_controller->left_window();
@@ -113,7 +112,7 @@
     : container_(container) {
   DCHECK(container_);
   auto* shell = Shell::Get();
-  shell->split_view_controller()->AddObserver(this);
+  SplitViewController::Get()->AddObserver(this);
   shell->overview_controller()->AddObserver(this);
   shell->accessibility_controller()->AddObserver(this);
   shell->wallpaper_controller()->AddObserver(this);
@@ -129,7 +128,7 @@
   shell->wallpaper_controller()->RemoveObserver(this);
   if (shell->overview_controller())
     shell->overview_controller()->RemoveObserver(this);
-  shell->split_view_controller()->RemoveObserver(this);
+  SplitViewController::Get()->RemoveObserver(this);
   // TODO(oshima): animations won't work right with mus:
   // http://crbug.com/548396.
   Hide(/*destroy=*/true);
@@ -428,8 +427,7 @@
   // TODO(afakhry): Define the correct behavior and revise this in a follow-up
   // CL.
   aura::Window* window = GetTopmostWindowWithBackdrop();
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   SplitViewController::State state = split_view_controller->state();
   if ((state == SplitViewController::State::kLeftSnapped &&
        window == split_view_controller->left_window()) ||
@@ -444,8 +442,7 @@
 gfx::Rect BackdropController::GetBackdropBounds() {
   DCHECK(!BackdropShouldFullscreen());
 
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
   SplitViewController::State state = split_view_controller->state();
   DCHECK(state == SplitViewController::State::kLeftSnapped ||
          state == SplitViewController::State::kRightSnapped);
diff --git a/ash/wm/workspace/workspace_layout_manager_unittest.cc b/ash/wm/workspace/workspace_layout_manager_unittest.cc
index 576b96e..5736a9e 100644
--- a/ash/wm/workspace/workspace_layout_manager_unittest.cc
+++ b/ash/wm/workspace/workspace_layout_manager_unittest.cc
@@ -1814,8 +1814,7 @@
     return window;
   };
 
-  SplitViewController* split_view_controller =
-      Shell::Get()->split_view_controller();
+  SplitViewController* split_view_controller = SplitViewController::Get();
 
   const gfx::Rect bounds(0, 0, 400, 400);
   std::unique_ptr<aura::Window> window1(CreateWindow(bounds));
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 3c92548..96d11be 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -2490,7 +2490,7 @@
     "android/unguessable_token_android_unittest.cc",
     "at_exit_unittest.cc",
     "atomicops_unittest.cc",
-    "auto_reset_unittests.cc",
+    "auto_reset_unittest.cc",
     "barrier_closure_unittest.cc",
     "base64_unittest.cc",
     "base64url_unittest.cc",
diff --git a/base/auto_reset.h b/base/auto_reset.h
index 0ab0af7..d8ce87b6 100644
--- a/base/auto_reset.h
+++ b/base/auto_reset.h
@@ -7,8 +7,6 @@
 
 #include <utility>
 
-#include "base/macros.h"
-
 // base::AutoReset<> is useful for setting a variable to a new value only within
 // a particular scope. An base::AutoReset<> object resets a variable to its
 // original value upon destruction, making it an alternative to writing
@@ -20,19 +18,23 @@
 
 namespace base {
 
-template<typename T>
+template <typename T>
 class AutoReset {
  public:
-  AutoReset(T* scoped_variable, T new_value)
+  template <typename U>
+  AutoReset(T* scoped_variable, U&& new_value)
       : scoped_variable_(scoped_variable),
-        original_value_(std::move(*scoped_variable)) {
-    *scoped_variable_ = std::move(new_value);
-  }
+        original_value_(
+            std::exchange(*scoped_variable_, std::forward<U>(new_value))) {}
 
   AutoReset(AutoReset&& other)
-      : scoped_variable_(other.scoped_variable_),
-        original_value_(std::move(other.original_value_)) {
-    other.scoped_variable_ = nullptr;
+      : scoped_variable_(std::exchange(other.scoped_variable_, nullptr)),
+        original_value_(std::move(other.original_value_)) {}
+
+  AutoReset& operator=(AutoReset&& rhs) {
+    scoped_variable_ = std::exchange(rhs.scoped_variable_, nullptr);
+    original_value_ = std::move(rhs.original_value_);
+    return *this;
   }
 
   ~AutoReset() {
@@ -40,20 +42,9 @@
       *scoped_variable_ = std::move(original_value_);
   }
 
-  AutoReset& operator=(AutoReset&& rhs) {
-    if (this != &rhs) {
-      scoped_variable_ = rhs.scoped_variable_;
-      rhs.scoped_variable_ = nullptr;
-      original_value_ = std::move(rhs.original_value_);
-    }
-    return *this;
-  }
-
  private:
   T* scoped_variable_;
   T original_value_;
-
-  DISALLOW_COPY_AND_ASSIGN(AutoReset);
 };
 
 }  // namespace base
diff --git a/base/auto_reset_unittests.cc b/base/auto_reset_unittest.cc
similarity index 100%
rename from base/auto_reset_unittests.cc
rename to base/auto_reset_unittest.cc
diff --git a/base/threading/thread_restrictions.h b/base/threading/thread_restrictions.h
index c0dd1ec..a405d67 100644
--- a/base/threading/thread_restrictions.h
+++ b/base/threading/thread_restrictions.h
@@ -205,6 +205,7 @@
 }
 }
 namespace printing {
+class PrintJobWorker;
 class PrinterQuery;
 }
 namespace rlz_lib {
@@ -352,6 +353,7 @@
   friend class memory_instrumentation::OSMetrics;
   friend class module_installer::ScopedAllowModulePakLoad;
   friend class mojo::CoreLibraryInitializer;
+  friend class printing::PrintJobWorker;
   friend class resource_coordinator::TabManagerDelegate;  // crbug.com/778703
   friend class ui::MaterialDesignController;
   friend class web::WebSubThread;
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index e479d21..93ad68a 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -2039,6 +2039,12 @@
       "/Ob0",  # Disable all inlining (on by default).
       "/GF",  # Enable string pooling (off by default).
     ]
+
+    if (target_cpu == "arm64") {
+      # Disable omitting frame pointers for no_optimize build because stack
+      # trace on Windows ARM64 relies on it.
+      cflags += [ "/Oy-" ]
+    }
   } else if (is_android && !android_full_debug) {
     # On Android we kind of optimize some things that don't affect debugging
     # much even when optimization is disabled to get the binary size down.
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 64226d8..876c992 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8899905896794786240
\ No newline at end of file
+8899880327732877040
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index d037ab1b..63856f3 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8899912590972143056
\ No newline at end of file
+8899879555102631152
\ No newline at end of file
diff --git a/cc/metrics/frame_sequence_tracker.cc b/cc/metrics/frame_sequence_tracker.cc
index ee9c8e2f..4c48b4b 100644
--- a/cc/metrics/frame_sequence_tracker.cc
+++ b/cc/metrics/frame_sequence_tracker.cc
@@ -125,16 +125,15 @@
 
 void FrameSequenceTrackerCollection::NotifyBeginImplFrame(
     const viz::BeginFrameArgs& args) {
-  for (auto& tracker : frame_trackers_) {
+  RecreateTrackers(args);
+  for (auto& tracker : frame_trackers_)
     tracker.second->ReportBeginImplFrame(args);
-  }
 }
 
 void FrameSequenceTrackerCollection::NotifyBeginMainFrame(
     const viz::BeginFrameArgs& args) {
-  for (auto& tracker : frame_trackers_) {
+  for (auto& tracker : frame_trackers_)
     tracker.second->ReportBeginMainFrame(args);
-  }
 }
 
 void FrameSequenceTrackerCollection::NotifyImplFrameCausedNoDamage(
@@ -185,6 +184,24 @@
       });
 }
 
+void FrameSequenceTrackerCollection::RecreateTrackers(
+    const viz::BeginFrameArgs& args) {
+  std::vector<FrameSequenceTrackerType> recreate_trackers;
+  for (const auto& tracker : frame_trackers_) {
+    if (tracker.second->ShouldReportMetricsNow(args))
+      recreate_trackers.push_back(tracker.first);
+  }
+
+  for (const auto& tracker_type : recreate_trackers) {
+    // StopSequence put the tracker in the |removal_trackers_|, which will
+    // report its throughput data when its frame is presented.
+    StopSequence(tracker_type);
+    // The frame sequence is still active, so create a new tracker to keep
+    // tracking this sequence.
+    StartSequence(tracker_type);
+  }
+}
+
 FrameSequenceTracker* FrameSequenceTrackerCollection::GetTrackerForTesting(
     FrameSequenceTrackerType type) {
   if (!frame_trackers_.contains(type))
@@ -268,6 +285,9 @@
                          args.sequence_number);
   impl_throughput_.frames_expected +=
       begin_impl_frame_data_.previous_sequence_delta;
+
+  if (first_frame_timestamp_.is_null())
+    first_frame_timestamp_ = args.frame_time;
 }
 
 void FrameSequenceTracker::ReportBeginMainFrame(
@@ -487,6 +507,14 @@
   return dict;
 }
 
+bool FrameSequenceTracker::ShouldReportMetricsNow(
+    const viz::BeginFrameArgs& args) const {
+  if (!first_frame_timestamp_.is_null() &&
+      args.frame_time - first_frame_timestamp_ >= time_delta_to_report_)
+    return true;
+  return false;
+}
+
 base::Optional<int> FrameSequenceTracker::ThroughputData::ReportHistogram(
     FrameSequenceTrackerType sequence_type,
     const char* thread_name,
diff --git a/cc/metrics/frame_sequence_tracker.h b/cc/metrics/frame_sequence_tracker.h
index bc57f51..5004aa00 100644
--- a/cc/metrics/frame_sequence_tracker.h
+++ b/cc/metrics/frame_sequence_tracker.h
@@ -96,6 +96,8 @@
  private:
   friend class FrameSequenceTrackerTest;
 
+  void RecreateTrackers(const viz::BeginFrameArgs& args);
+
   const bool is_single_threaded_;
   // The callsite can use the type to manipulate the tracker.
   base::flat_map<FrameSequenceTrackerType,
@@ -167,6 +169,9 @@
 
   TerminationStatus termination_status() const { return termination_status_; }
 
+  // Returns true if we should ask this tracker to report its throughput data.
+  bool ShouldReportMetricsNow(const viz::BeginFrameArgs& args) const;
+
  private:
   friend class FrameSequenceTrackerCollection;
   friend class FrameSequenceTrackerTest;
@@ -272,6 +277,13 @@
   // Keeps track of the last sequence-number that produced a frame from the
   // main-thread.
   uint64_t last_submitted_main_sequence_ = 0;
+
+  // The time when this tracker is created, or the time when it was previously
+  // scheduled to report histogram.
+  base::TimeTicks first_frame_timestamp_;
+
+  // Report the throughput metrics every 5 seconds.
+  const base::TimeDelta time_delta_to_report_ = base::TimeDelta::FromSeconds(5);
 };
 
 }  // namespace cc
diff --git a/cc/metrics/frame_sequence_tracker_unittest.cc b/cc/metrics/frame_sequence_tracker_unittest.cc
index a2f081f..53d9b62 100644
--- a/cc/metrics/frame_sequence_tracker_unittest.cc
+++ b/cc/metrics/frame_sequence_tracker_unittest.cc
@@ -37,9 +37,10 @@
         FrameSequenceTrackerType::kTouchScroll);
   }
 
-  viz::BeginFrameArgs CreateBeginFrameArgs(uint64_t source_id,
-                                           uint64_t sequence_number) {
-    auto now = base::TimeTicks::Now();
+  viz::BeginFrameArgs CreateBeginFrameArgs(
+      uint64_t source_id,
+      uint64_t sequence_number,
+      base::TimeTicks now = base::TimeTicks::Now()) {
     auto interval = base::TimeDelta::FromMilliseconds(16);
     auto deadline = now + interval;
     return viz::BeginFrameArgs::Create(BEGINFRAME_FROM_HERE, source_id,
@@ -164,6 +165,17 @@
         "Graphics.Smoothness.Throughput.SlowerThread.TouchScroll", 3u);
   }
 
+  base::TimeDelta TimeDeltaToReort() const {
+    return tracker_->time_delta_to_report_;
+  }
+
+  unsigned NumberOfTrackers() const {
+    return collection_.frame_trackers_.size();
+  }
+  unsigned NumberOfRemovalTrackers() const {
+    return collection_.removal_trackers_.size();
+  }
+
  protected:
   uint32_t number_of_frames_checkerboarded() const {
     return tracker_->checkerboarding_.frames_checkerboarded;
@@ -331,4 +343,26 @@
   ReportMetrics();
 }
 
+TEST_F(FrameSequenceTrackerTest, ReportMetricsAtFixedInterval) {
+  const uint64_t source = 1;
+  uint64_t sequence = 0;
+  base::TimeDelta first_time_delta = base::TimeDelta::FromSeconds(1);
+  auto args = CreateBeginFrameArgs(source, ++sequence,
+                                   base::TimeTicks::Now() + first_time_delta);
+
+  // args.frame_time is less than 5s of the tracker creation time, so won't
+  // schedule this tracker to report its throughput.
+  collection_.NotifyBeginImplFrame(args);
+  EXPECT_EQ(NumberOfTrackers(), 1u);
+  EXPECT_EQ(NumberOfRemovalTrackers(), 0u);
+
+  // Now args.frame_time is 5s since the tracker creation time, so this tracker
+  // should be scheduled to report its throughput.
+  args = CreateBeginFrameArgs(source, ++sequence,
+                              args.frame_time + TimeDeltaToReort());
+  collection_.NotifyBeginImplFrame(args);
+  EXPECT_EQ(NumberOfTrackers(), 1u);
+  EXPECT_EQ(NumberOfRemovalTrackers(), 1u);
+}
+
 }  // namespace cc
diff --git a/cc/paint/element_id.cc b/cc/paint/element_id.cc
index 15d7883..69d3237 100644
--- a/cc/paint/element_id.cc
+++ b/cc/paint/element_id.cc
@@ -26,7 +26,7 @@
   res->EndDictionary();
 }
 
-ElementIdType ElementId::GetInternalValue() const {
+ElementIdType ElementId::GetStableId() const {
   return id_;
 }
 
diff --git a/cc/paint/element_id.h b/cc/paint/element_id.h
index 59e0158..05ecd23 100644
--- a/cc/paint/element_id.h
+++ b/cc/paint/element_id.h
@@ -61,7 +61,7 @@
   void AddToTracedValue(base::trace_event::TracedValue* res) const;
   std::unique_ptr<base::Value> AsValue() const;
 
-  ElementIdType GetInternalValue() const;
+  ElementIdType GetStableId() const;
 
   std::string ToString() const;
 
diff --git a/cc/trees/effect_node.cc b/cc/trees/effect_node.cc
index 6560daea..b84952f 100644
--- a/cc/trees/effect_node.cc
+++ b/cc/trees/effect_node.cc
@@ -138,7 +138,7 @@
 
 void EffectNode::AsValueInto(base::trace_event::TracedValue* value) const {
   value->SetInteger("backdrop_mask_element_id",
-                    backdrop_mask_element_id.GetInternalValue());
+                    backdrop_mask_element_id.GetStableId());
   value->SetInteger("id", id);
   value->SetInteger("parent_id", parent_id);
   value->SetInteger("stable_id", stable_id);
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index a624cef..6facf98 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -3908,7 +3908,7 @@
 
     ElementId current_native_scrolling_element =
         scroll_state->data()->current_native_scrolling_element();
-    if (current_native_scrolling_element.GetInternalValue() != 0) {
+    if (current_native_scrolling_element.GetStableId() != 0) {
       auto& scroll_tree = active_tree_->property_trees()->scroll_tree;
       scrolling_node =
           scroll_tree.FindNodeFromElementId(current_native_scrolling_element);
diff --git a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java
index 5548929..eb2d7fa7 100644
--- a/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java
+++ b/chrome/android/features/autofill_assistant/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantBottomSheetContent.java
@@ -85,11 +85,6 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
-        return true;
-    }
-
-    @Override
     public boolean wrapContentEnabled() {
         return true;
     }
diff --git a/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml b/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
index ab83e2c..c9928dc 100644
--- a/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
+++ b/chrome/android/features/tab_ui/java/res/layout/bottom_tab_grid_toolbar.xml
@@ -6,6 +6,7 @@
 <org.chromium.chrome.browser.tasks.tab_management.TabGroupUiToolbarView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/tab_group_toolbar"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -15,23 +16,27 @@
         android:layout_width="match_parent"
         android:layout_height="@dimen/bottom_sheet_peek_height"
         android:orientation="horizontal"
-        android:gravity="center_vertical"
-        android:clickable="true">
+        android:gravity="center_vertical">
         <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_left_button"
             style="@style/BottomToolbarButton"
             android:src="@drawable/ic_expand_more_black_24dp"
             app:tint="@color/standard_mode_tint"
             android:contentDescription="@string/accessibility_bottom_tab_grid_close_tab_sheet" />
-         <TextView
-             android:id="@+id/title"
-             android:layout_height="wrap_content"
-             android:layout_width="0dp"
-             android:layout_weight="1"
-             android:singleLine="true"
-             android:ellipsize="end"
-             android:textAppearance="@style/TextAppearance.BlackTitle1"
-             android:gravity="center"/>
+        <EditText
+            tools:ignore="LabelFor"
+            android:id="@+id/title"
+            android:cursorVisible="false"
+            android:background="@android:color/transparent"
+            android:layout_height="@dimen/bottom_sheet_peek_height"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.BlackTitle1"
+            android:inputType="text|textNoSuggestions"
+            android:imeOptions="actionDone"
+            android:gravity="center"/>
         <org.chromium.ui.widget.ChromeImageView
             android:id="@+id/toolbar_right_button"
             style="@style/BottomToolbarButton"
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
index 2adcfc41..5f82dde 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupUtils.java
@@ -9,6 +9,7 @@
 import android.content.SharedPreferences;
 import android.view.View;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 
 import org.chromium.base.ApplicationStatus;
@@ -149,6 +150,7 @@
      * @param title       The tab group title to store.
      */
     public static void storeTabGroupTitle(int tabRootId, String title) {
+        assert tabRootId != Tab.INVALID_TAB_ID;
         getSharedPreferences().edit().putString(String.valueOf(tabRootId), title).apply();
     }
 
@@ -157,6 +159,7 @@
      * @param tabRootId  The tab root ID whose related tab group title will be deleted.
      */
     public static void deleteTabGroupTitle(int tabRootId) {
+        assert tabRootId != Tab.INVALID_TAB_ID;
         getSharedPreferences().edit().remove(String.valueOf(tabRootId)).apply();
     }
 
@@ -165,7 +168,9 @@
      * @param tabRootId  The tab root ID whose related tab group title will be fetched.
      * @return The stored title of the target tab group, default value is null.
      */
+    @Nullable
     public static String getTabGroupTitle(int tabRootId) {
+        assert tabRootId != Tab.INVALID_TAB_ID;
         return getSharedPreferences().getString(String.valueOf(tabRootId), null);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
index 4923f92..c8b1ae8 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogCoordinator.java
@@ -38,7 +38,8 @@
             TabContentManager tabContentManager, TabCreatorManager tabCreatorManager,
             ViewGroup containerView, TabSwitcherMediator.ResetHandler resetHandler,
             TabListMediator.GridCardOnClickListenerProvider gridCardOnClickListenerProvider,
-            TabGridDialogMediator.AnimationParamsProvider animationParamsProvider) {
+            TabGridDialogMediator.AnimationParamsProvider animationParamsProvider,
+            TabGroupTitleEditor tabGroupTitleEditor) {
         mComponentName = animationParamsProvider == null ? "TabGridDialogFromStrip"
                                                          : "TabGridDialogInSwitcher";
 
@@ -51,7 +52,8 @@
 
         mMediator = new TabGridDialogMediator(context, this, mToolbarPropertyModel,
                 tabModelSelector, tabCreatorManager, resetHandler, animationParamsProvider,
-                mTabSelectionEditorCoordinator.getController(), mComponentName);
+                mTabSelectionEditorCoordinator.getController(), tabGroupTitleEditor,
+                mComponentName);
 
         mTabListCoordinator = new TabListCoordinator(TabListCoordinator.TabListMode.GRID, context,
                 tabModelSelector, tabContentManager::getTabThumbnailWithCallback, null, false, null,
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
index 2218c11..b73c06ab 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediator.java
@@ -9,6 +9,8 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.support.v7.content.res.AppCompatResources;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -32,6 +34,7 @@
 import org.chromium.chrome.browser.widget.ScrimView;
 import org.chromium.chrome.tab_ui.R;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
@@ -89,18 +92,22 @@
     private final DialogController mDialogController;
     private final TabSwitcherMediator.ResetHandler mTabSwitcherResetHandler;
     private final AnimationParamsProvider mAnimationParamsProvider;
+    private final TabGroupTitleEditor mTabGroupTitleEditor;
     private final DialogHandler mTabGridDialogHandler;
     private final TabSelectionEditorCoordinator
             .TabSelectionEditorController mTabSelectionEditorController;
     private final String mComponentName;
+    private KeyboardVisibilityDelegate.KeyboardVisibilityListener mKeyboardVisibilityListener;
     private int mCurrentTabId = Tab.INVALID_TAB_ID;
+    private boolean mIsUpdatingTitle;
+    private String mCurrentGroupModifiedTitle;
 
     TabGridDialogMediator(Context context, DialogController dialogController, PropertyModel model,
             TabModelSelector tabModelSelector, TabCreatorManager tabCreatorManager,
             TabSwitcherMediator.ResetHandler tabSwitcherResetHandler,
             AnimationParamsProvider animationParamsProvider,
             TabSelectionEditorCoordinator.TabSelectionEditorController tabSelectionEditorController,
-            String componentName) {
+            TabGroupTitleEditor tabGroupTitleEditor, String componentName) {
         mContext = context;
         mModel = model;
         mTabModelSelector = tabModelSelector;
@@ -108,6 +115,7 @@
         mDialogController = dialogController;
         mTabSwitcherResetHandler = tabSwitcherResetHandler;
         mAnimationParamsProvider = animationParamsProvider;
+        mTabGroupTitleEditor = tabGroupTitleEditor;
         mTabGridDialogHandler = new DialogHandler();
         mTabSelectionEditorController = tabSelectionEditorController;
         mComponentName = componentName;
@@ -188,12 +196,15 @@
         assert mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter()
                         instanceof TabGroupModelFilter;
 
-        // Setup toolbar property model.
+        // Setup toolbar button click listeners.
         setupToolbarClickHandlers();
 
         // Setup dialog selection editor.
         setupDialogSelectionEditor();
 
+        // Setup toolbar edit text.
+        setupToolbarEditText();
+
         // Setup ScrimView observer.
         setupScrimViewObserver();
     }
@@ -208,6 +219,7 @@
             }
         }
         mTabSelectionEditorController.hide();
+        saveCurrentGroupModifiedTitle();
         mDialogController.resetWithListOfTabs(null);
     }
 
@@ -243,6 +255,8 @@
                     mTabModelObserver);
         }
         mTabModelSelector.removeObserver(mTabModelSelectorObserver);
+        KeyboardVisibilityDelegate.getInstance().removeKeyboardVisibilityListener(
+                mKeyboardVisibilityListener);
     }
 
     boolean isVisible() {
@@ -263,6 +277,13 @@
             hideDialog(true);
             return;
         }
+        assert mTabGroupTitleEditor != null;
+        Tab currentTab = mTabModelSelector.getTabById(mCurrentTabId);
+        String storedTitle = mTabGroupTitleEditor.getTabGroupTitle(currentTab.getRootId());
+        if (storedTitle != null && relatedTabs.size() > 1) {
+            mModel.set(TabGridPanelProperties.HEADER_TITLE, storedTitle);
+            return;
+        }
         mModel.set(TabGridPanelProperties.HEADER_TITLE,
                 mContext.getResources().getQuantityString(
                         R.plurals.bottom_tab_grid_title_placeholder, tabsCount, tabsCount));
@@ -298,6 +319,36 @@
         mTabSelectionEditorController.configureToolbar(actionButtonText, actionProvider, 1, null);
     }
 
+    private void setupToolbarEditText() {
+        mKeyboardVisibilityListener = isShowing -> {
+            mModel.set(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY, isShowing);
+            if (!isShowing) {
+                saveCurrentGroupModifiedTitle();
+            }
+        };
+        KeyboardVisibilityDelegate.getInstance().addKeyboardVisibilityListener(
+                mKeyboardVisibilityListener);
+
+        TextWatcher textWatcher = new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+            @Override
+            public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+            @Override
+            public void afterTextChanged(Editable s) {
+                if (!mIsUpdatingTitle) return;
+                mCurrentGroupModifiedTitle = s.toString();
+            }
+        };
+        mModel.set(TabGridPanelProperties.TITLE_TEXT_WATCHER, textWatcher);
+
+        View.OnFocusChangeListener onFocusChangeListener =
+                (v, hasFocus) -> mIsUpdatingTitle = hasFocus;
+        mModel.set(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER, onFocusChangeListener);
+    }
+
     private void setupScrimViewObserver() {
         ScrimView.ScrimObserver scrimObserver = new ScrimView.ScrimObserver() {
             @Override
@@ -357,6 +408,18 @@
                 .getRelatedTabList(tabId);
     }
 
+    private void saveCurrentGroupModifiedTitle() {
+        if (mCurrentGroupModifiedTitle == null) return;
+        assert mTabGroupTitleEditor != null;
+        assert mCurrentTabId != Tab.INVALID_TAB_ID;
+
+        Tab currentTab = mTabModelSelector.getTabById(mCurrentTabId);
+        mTabGroupTitleEditor.storeTabGroupTitle(currentTab.getRootId(), mCurrentGroupModifiedTitle);
+        mTabGroupTitleEditor.updateTabGroupTitle(currentTab, mCurrentGroupModifiedTitle);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, mCurrentGroupModifiedTitle);
+        mCurrentGroupModifiedTitle = null;
+    }
+
     TabListMediator.TabGridDialogHandler getTabGridDialogHandler() {
         return mTabGridDialogHandler;
     }
@@ -387,4 +450,20 @@
     void setCurrentTabIdForTest(int tabId) {
         mCurrentTabId = tabId;
     }
+
+    @VisibleForTesting
+    KeyboardVisibilityDelegate.KeyboardVisibilityListener
+    getKeyboardVisibilityListenerForTesting() {
+        return mKeyboardVisibilityListener;
+    }
+
+    @VisibleForTesting
+    boolean getIsUpdatingTitleForTesting() {
+        return mIsUpdatingTitle;
+    }
+
+    @VisibleForTesting
+    String getCurrentGroupModifiedTitleForTesting() {
+        return mCurrentGroupModifiedTitle;
+    }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
index 73f9b31..40ddf4e6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogParent.java
@@ -174,6 +174,7 @@
         mScrimView = new ScrimView(context, null, mTabGridDialogParentView);
         mPopupWindow = new PopupWindow(mTabGridDialogParentView,
                 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+        mPopupWindow.setFocusable(true);
         updateDialogWithOrientation(context, context.getResources().getConfiguration().orientation);
     }
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java
index 504c649..0168d2c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelProperties.java
@@ -5,6 +5,8 @@
 package org.chromium.chrome.browser.tasks.tab_management;
 
 import android.content.res.ColorStateList;
+import android.text.TextWatcher;
+import android.view.View;
 import android.view.View.OnClickListener;
 
 import org.chromium.chrome.browser.widget.ScrimView;
@@ -60,10 +62,18 @@
     public static final PropertyModel
             .WritableObjectPropertyKey<OnClickListener> MENU_CLICK_LISTENER =
             new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel.WritableObjectPropertyKey<TextWatcher> TITLE_TEXT_WATCHER =
+            new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel
+            .WritableObjectPropertyKey<View.OnFocusChangeListener> TITLE_TEXT_ON_FOCUS_LISTENER =
+            new PropertyModel.WritableObjectPropertyKey<>();
+    public static final PropertyModel.WritableBooleanPropertyKey TITLE_CURSOR_VISIBILITY =
+            new PropertyModel.WritableBooleanPropertyKey();
     public static final PropertyKey[] ALL_KEYS = new PropertyKey[] {COLLAPSE_CLICK_LISTENER,
             ADD_CLICK_LISTENER, HEADER_TITLE, CONTENT_TOP_MARGIN, PRIMARY_COLOR, TINT,
             IS_DIALOG_VISIBLE, SCRIMVIEW_OBSERVER, ANIMATION_PARAMS, UNGROUP_BAR_STATUS,
             DIALOG_BACKGROUND_RESOUCE_ID, DIALOG_UNGROUP_BAR_BACKGROUND_COLOR_ID,
             DIALOG_UNGROUP_BAR_HOVERED_BACKGROUND_COLOR_ID, DIALOG_UNGROUP_BAR_TEXT_APPEARANCE,
-            INITIAL_SCROLL_INDEX, IS_MAIN_CONTENT_VISIBLE, MENU_CLICK_LISTENER};
+            INITIAL_SCROLL_INDEX, IS_MAIN_CONTENT_VISIBLE, MENU_CLICK_LISTENER, TITLE_TEXT_WATCHER,
+            TITLE_TEXT_ON_FOCUS_LISTENER, TITLE_CURSOR_VISIBILITY};
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java
index be62ff0..b362ef7 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinder.java
@@ -20,6 +20,9 @@
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.PRIMARY_COLOR;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.SCRIMVIEW_OBSERVER;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.TINT;
+import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.TITLE_CURSOR_VISIBILITY;
+import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER;
+import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.TITLE_TEXT_WATCHER;
 import static org.chromium.chrome.browser.tasks.tab_management.TabGridPanelProperties.UNGROUP_BAR_STATUS;
 
 import android.support.v7.widget.LinearLayoutManager;
@@ -117,6 +120,13 @@
             viewHolder.contentView.setVisibility(View.VISIBLE);
         } else if (MENU_CLICK_LISTENER == propertyKey) {
             viewHolder.toolbarView.setMenuButtonOnClickListener(model.get(MENU_CLICK_LISTENER));
+        } else if (TITLE_TEXT_WATCHER == propertyKey) {
+            viewHolder.toolbarView.setTitleTextOnChangedListener(model.get(TITLE_TEXT_WATCHER));
+        } else if (TITLE_TEXT_ON_FOCUS_LISTENER == propertyKey) {
+            viewHolder.toolbarView.setTitleTextOnFocusChangeListener(
+                    model.get(TITLE_TEXT_ON_FOCUS_LISTENER));
+        } else if (TITLE_CURSOR_VISIBILITY == propertyKey) {
+            viewHolder.toolbarView.setTitleCursorVisibility(model.get(TITLE_CURSOR_VISIBILITY));
         }
     }
 }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetContent.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetContent.java
index ba7f16e..3453737 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetContent.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGridSheetContent.java
@@ -6,6 +6,7 @@
 
 import android.view.View;
 
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.ContentPriority;
 import org.chromium.chrome.tab_ui.R;
@@ -54,8 +55,8 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
-        return false;
+    public int getPeekHeight() {
+        return BottomSheet.HeightMode.DISABLED;
     }
 
     @Override
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
index c3ceef34..db6262cd 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiCoordinator.java
@@ -88,9 +88,9 @@
             // and the dialog here.
             mTabGridSheetCoordinator = null;
 
-            mTabGridDialogCoordinator =
-                    new TabGridDialogCoordinator(mContext, tabModelSelector, tabContentManager,
-                            activity, activity.getCompositorViewHolder(), null, null, null);
+            mTabGridDialogCoordinator = new TabGridDialogCoordinator(mContext, tabModelSelector,
+                    tabContentManager, activity, activity.getCompositorViewHolder(), null, null,
+                    null, mTabStripCoordinator.getTabGroupTitleEditor());
         } else {
             mTabGridSheetCoordinator =
                     new TabGridSheetCoordinator(mContext, activity.getBottomSheetController(),
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
index 204c89bc..4cc1f3c 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabGroupUiToolbarView.java
@@ -7,13 +7,14 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.support.v4.widget.TextViewCompat;
+import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.chrome.tab_ui.R;
@@ -28,7 +29,7 @@
     private ChromeImageView mLeftButton;
     private ChromeImageView mMenuButton;
     private ViewGroup mContainerView;
-    private TextView mTitleTextView;
+    private EditText mTitleTextView;
     private LinearLayout mMainContent;
 
     public TabGroupUiToolbarView(Context context, AttributeSet attrs) {
@@ -43,7 +44,7 @@
         mRightButton = findViewById(R.id.toolbar_right_button);
         mMenuButton = findViewById(R.id.toolbar_menu_button);
         mContainerView = (ViewGroup) findViewById(R.id.toolbar_container_view);
-        mTitleTextView = (TextView) findViewById(R.id.title);
+        mTitleTextView = (EditText) findViewById(R.id.title);
         mMainContent = findViewById(R.id.main_content);
     }
 
@@ -59,6 +60,18 @@
         mMenuButton.setOnClickListener(listener);
     }
 
+    void setTitleTextOnChangedListener(TextWatcher textWatcher) {
+        mTitleTextView.addTextChangedListener(textWatcher);
+    }
+
+    void setTitleTextOnFocusChangeListener(OnFocusChangeListener listener) {
+        mTitleTextView.setOnFocusChangeListener(listener);
+    }
+
+    void setTitleCursorVisibility(boolean isVisible) {
+        mTitleTextView.setCursorVisible(isVisible);
+    }
+
     ViewGroup getViewContainer() {
         return mContainerView;
     }
@@ -100,6 +113,8 @@
         if (!isDialog) {
             // We don't support toolbar menu for TabGridSheet.
             mMainContent.removeView(mMenuButton);
+            // We don't support tab group naming for TabGridSheet.
+            mTitleTextView.setFocusable(false);
             return;
         }
         Context context = getContext();
@@ -108,7 +123,7 @@
                 (int) context.getResources().getDimension(R.dimen.tab_group_toolbar_topic_margin);
         MarginLayoutParams params = (MarginLayoutParams) mTitleTextView.getLayoutParams();
         params.setMarginStart(topicMargin);
-        mTitleTextView.setGravity(Gravity.START);
+        mTitleTextView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
         TextViewCompat.setTextAppearance(
                 mTitleTextView, org.chromium.chrome.R.style.TextAppearance_BlackHeadline);
     }
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
index d3c839a9..9eacbea3 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListCoordinator.java
@@ -235,6 +235,13 @@
     }
 
     /**
+     * @return The editor {@link TabGroupTitleEditor} that is used to update tab group title.
+     */
+    TabGroupTitleEditor getTabGroupTitleEditor() {
+        return mMediator.getTabGroupTitleEditor();
+    }
+
+    /**
      * @see TabListMediator#resetWithListOfTabs(List, boolean, boolean)
      */
     boolean resetWithListOfTabs(@Nullable List<Tab> tabs, boolean quickMode, boolean mruMode) {
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
index d8da896b..e176c58 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java
@@ -329,7 +329,7 @@
         public void onTitleUpdated(Tab updatedTab) {
             int index = mModel.indexFromId(updatedTab.getId());
             if (index == TabModel.INVALID_TAB_INDEX) return;
-            mModel.get(index).model.set(TabProperties.TITLE, mTitleProvider.getTitle(updatedTab));
+            mModel.get(index).model.set(TabProperties.TITLE, getLatestTitleForTab(updatedTab));
         }
 
         @Override
@@ -340,6 +340,8 @@
 
     private final TabModelObserver mTabModelObserver;
 
+    private TabGroupTitleEditor mTabGroupTitleEditor;
+
     private TabGroupModelFilter.Observer mTabGroupObserver;
 
     /**
@@ -668,6 +670,35 @@
             }
         };
 
+        if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
+            mTabGroupTitleEditor = new TabGroupTitleEditor(mTabModelSelector) {
+                @Override
+                protected void updateTabGroupTitle(Tab tab, String title) {
+                    // Only update title in PropertyModel for tab switcher.
+                    if (!mActionsOnAllRelatedTabs) return;
+                    Tab currentGroupSelectedTab =
+                            TabGroupUtils.getSelectedTabInGroupForTab(mTabModelSelector, tab);
+                    int index = mModel.indexFromId(currentGroupSelectedTab.getId());
+                    if (index == TabModel.INVALID_TAB_INDEX) return;
+                    mModel.get(index).model.set(TabProperties.TITLE, title);
+                }
+
+                @Override
+                protected void deleteTabGroupTitle(int tabRootId) {
+                    TabGroupUtils.deleteTabGroupTitle(tabRootId);
+                }
+
+                @Override
+                protected String getTabGroupTitle(int tabRootId) {
+                    return TabGroupUtils.getTabGroupTitle(tabRootId);
+                }
+
+                @Override
+                protected void storeTabGroupTitle(int tabRootId, String title) {
+                    TabGroupUtils.storeTabGroupTitle(tabRootId, title);
+                }
+            };
+        }
         mTabGridItemTouchHelperCallback =
                 new TabGridItemTouchHelperCallback(mModel, mTabModelSelector, mTabClosedListener,
                         mTabGridDialogHandler, mComponentName, mActionsOnAllRelatedTabs);
@@ -861,7 +892,7 @@
         mModel.get(index).model.set(
                 TabProperties.CREATE_GROUP_LISTENER, getCreateGroupButtonListener(tab, isSelected));
         mModel.get(index).model.set(TabProperties.IS_SELECTED, isSelected);
-        mModel.get(index).model.set(TabProperties.TITLE, mTitleProvider.getTitle(tab));
+        mModel.get(index).model.set(TabProperties.TITLE, getLatestTitleForTab(tab));
 
         updateFaviconForTab(tab, null);
         boolean forceUpdate = isSelected && !quickMode;
@@ -916,6 +947,15 @@
     }
 
     /**
+     * Exposes a {@link TabGroupTitleEditor} to modify the title of a tab group.
+     * @return The {@link TabGroupTitleEditor} used to modify the title of a tab group.
+     */
+    @Nullable
+    TabGroupTitleEditor getTabGroupTitleEditor() {
+        return mTabGroupTitleEditor;
+    }
+
+    /**
      * Destroy any members that needs clean up.
      */
     public void destroy() {
@@ -940,6 +980,9 @@
         if (mComponentCallbacks != null) {
             mContext.unregisterComponentCallbacks(mComponentCallbacks);
         }
+        if (mTabGroupTitleEditor != null) {
+            mTabGroupTitleEditor.destroy();
+        }
     }
 
     private void addTabInfoToModel(final Tab tab, int index, boolean isSelected) {
@@ -970,7 +1013,7 @@
         PropertyModel tabInfo =
                 new PropertyModel.Builder(TabProperties.ALL_KEYS_TAB_GRID)
                         .with(TabProperties.TAB_ID, tab.getId())
-                        .with(TabProperties.TITLE, mTitleProvider.getTitle(tab))
+                        .with(TabProperties.TITLE, getLatestTitleForTab(tab))
                         .with(TabProperties.FAVICON,
                                 mTabListFaviconProvider.getDefaultFaviconDrawable(
                                         tab.isIncognito()))
@@ -1051,6 +1094,18 @@
         return createGroupButtonOnClickListener;
     }
 
+    @VisibleForTesting
+    String getLatestTitleForTab(Tab tab) {
+        String originalTitle = mTitleProvider.getTitle(tab);
+        if (!mActionsOnAllRelatedTabs || mTabGroupTitleEditor == null) return originalTitle;
+        // If the group degrades to a single tab, delete the stored title.
+        if (getRelatedTabsForId(tab.getId()).size() <= 1) {
+            return originalTitle;
+        }
+        String storedTitle = mTabGroupTitleEditor.getTabGroupTitle(tab.getRootId());
+        return storedTitle == null ? originalTitle : storedTitle;
+    }
+
     int selectedTabId() {
         if (mNextTabId != Tab.INVALID_TAB_ID) {
             return mNextTabId;
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
index 4012109e..dd394710 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabSwitcherCoordinator.java
@@ -95,19 +95,6 @@
         mMediator = new TabSwitcherMediator(this, containerViewModel, tabModelSelector,
                 fullscreenManager, container, mTabSelectionEditorCoordinator.getController(), mode);
 
-        if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
-            mTabGridDialogCoordinator = new TabGridDialogCoordinator(context, tabModelSelector,
-                    tabContentManager, tabCreatorManager, container, this, mMediator,
-                    this::getTabGridDialogAnimationParams);
-
-            mUndoGroupSnackbarController =
-                    new UndoGroupSnackbarController(context, tabModelSelector, snackbarManageable);
-
-            mMediator.setTabGridDialogController(mTabGridDialogCoordinator.getDialogController());
-        } else {
-            mTabGridDialogCoordinator = null;
-            mUndoGroupSnackbarController = null;
-        }
         mMultiThumbnailCardProvider =
                 new MultiThumbnailCardProvider(context, tabContentManager, tabModelSelector);
 
@@ -129,6 +116,21 @@
         mContainerViewChangeProcessor = PropertyModelChangeProcessor.create(containerViewModel,
                 mTabListCoordinator.getContainerView(), TabListContainerViewBinder::bind);
 
+        if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()) {
+            mTabGridDialogCoordinator = new TabGridDialogCoordinator(context, tabModelSelector,
+                    tabContentManager, tabCreatorManager, container, this, mMediator,
+                    this::getTabGridDialogAnimationParams,
+                    mTabListCoordinator.getTabGroupTitleEditor());
+
+            mUndoGroupSnackbarController =
+                    new UndoGroupSnackbarController(context, tabModelSelector, snackbarManageable);
+
+            mMediator.setTabGridDialogController(mTabGridDialogCoordinator.getDialogController());
+        } else {
+            mTabGridDialogCoordinator = null;
+            mUndoGroupSnackbarController = null;
+        }
+
         if (FeatureUtilities.isTabGroupsAndroidUiImprovementsEnabled()
                 && mode == TabListCoordinator.TabListMode.GRID
                 && !FeatureUtilities.isStartSurfaceEnabled()) {
diff --git a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
index 32ea41b..47467a5 100644
--- a/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
+++ b/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/TabGridPanelViewBinderTest.java
@@ -10,15 +10,17 @@
 import android.os.Build;
 import android.provider.Settings;
 import android.support.test.annotation.UiThreadTest;
-import android.support.test.filters.MediumTest;
+import android.support.test.filters.SmallTest;
 import android.support.v4.content.ContextCompat;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.EditText;
 import android.widget.FrameLayout;
-import android.widget.TextView;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -51,7 +53,7 @@
     private TabGridDialogParent mTabGridDialogParent;
     private ChromeImageView mRightButton;
     private ChromeImageView mLeftButton;
-    private TextView mTitleTextView;
+    private EditText mTitleTextView;
     private View mMainContent;
     private ViewGroup mTabGridDialogParentView;
 
@@ -81,7 +83,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetCollapseClickListener() {
         AtomicBoolean leftButtonClicked = new AtomicBoolean();
@@ -97,7 +99,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetAddClickListener() {
         AtomicBoolean rightButtonClicked = new AtomicBoolean();
@@ -113,7 +115,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetHeaderTitle() {
         String title = "1024 tabs";
@@ -121,11 +123,11 @@
 
         mModel.set(TabGridPanelProperties.HEADER_TITLE, title);
 
-        Assert.assertEquals(title, mTitleTextView.getText());
+        Assert.assertEquals(title, mTitleTextView.getText().toString());
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testContentTopMargin() {
         // Since setting content top margin is only used in sheet, we can assume that the parent is
@@ -143,7 +145,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetPrimaryColor() {
         int color = ContextCompat.getColor(getActivity(), R.color.modern_blue_300);
@@ -157,7 +159,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetTint() {
         ColorStateList tint = ColorUtils.getThemedToolbarIconTint(getActivity(), true);
@@ -177,7 +179,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetScrimViewObserver() {
         AtomicBoolean scrimViewClicked = new AtomicBoolean();
@@ -204,7 +206,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     public void testSetDialogVisibility() {
         Assert.assertFalse(mTabGridDialogParent.getPopupWindowForTesting().isShowing());
         Assert.assertNull(mTabGridDialogParent.getCurrentDialogAnimatorForTesting());
@@ -237,7 +239,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetAnimationParams() {
         // Initially, the show animation set is empty.
@@ -269,7 +271,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetUngroupbarStatus() {
         // Default status for ungroup bar is hidden.
@@ -288,7 +290,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetDialogBackgroundResource() {
         int normalResourceId = R.drawable.tab_grid_dialog_background;
@@ -304,7 +306,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetUngroupbarBackgroundColor() {
         int normalColorId = R.color.tab_grid_dialog_background_color;
@@ -320,7 +322,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetUngroupbarHoveredBackgroundColor() {
         int normalColorId = R.color.tab_grid_card_selected_color;
@@ -337,7 +339,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetUngroupbarTextAppearance() {
         int normalStyleId = R.style.TextAppearance_BlueTitle2;
@@ -353,7 +355,7 @@
     }
 
     @Test
-    @MediumTest
+    @SmallTest
     @UiThreadTest
     public void testSetMainContentVisibility() {
         mContentView.setVisibility(View.INVISIBLE);
@@ -364,6 +366,59 @@
         Assert.assertEquals(View.VISIBLE, mContentView.getVisibility());
     }
 
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testSetTitleTextWatcher() {
+        String title = "cool tabs";
+        AtomicBoolean titleTextUpdated = new AtomicBoolean();
+        titleTextUpdated.set(false);
+
+        TextWatcher textWatcher = new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                titleTextUpdated.set(true);
+            }
+        };
+        mModel.set(TabGridPanelProperties.TITLE_TEXT_WATCHER, textWatcher);
+
+        mTitleTextView.setText(title);
+        Assert.assertTrue(titleTextUpdated.get());
+    }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testSetTitleTextOnFocusListener() {
+        AtomicBoolean textFocusChanged = new AtomicBoolean();
+        textFocusChanged.set(false);
+        Assert.assertFalse(mTitleTextView.isFocused());
+
+        View.OnFocusChangeListener listener = (view, b) -> textFocusChanged.set(true);
+        mModel.set(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER, listener);
+        mTitleTextView.requestFocus();
+
+        Assert.assertTrue(mTitleTextView.isFocused());
+        Assert.assertTrue(textFocusChanged.get());
+    }
+
+    @Test
+    @SmallTest
+    @UiThreadTest
+    public void testSetCursorVisibility() {
+        mTitleTextView.setCursorVisible(false);
+
+        mModel.set(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY, true);
+
+        Assert.assertTrue(mTitleTextView.isCursorVisible());
+    }
+
     @Override
     public void tearDownTest() throws Exception {
         mMCP.destroy();
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
index 9dc410f..0fad718 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabGridDialogMediatorUnitTest.java
@@ -10,6 +10,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.doNothing;
@@ -17,11 +18,15 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.view.View;
+import android.widget.EditText;
 
 import org.junit.After;
 import org.junit.Before;
@@ -51,6 +56,7 @@
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.testing.local.LocalRobolectricTestRunner;
+import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.ArrayList;
@@ -72,6 +78,7 @@
     private static final String DIALOG_TITLE1 = "1 Tab";
     private static final String DIALOG_TITLE2 = "2 Tabs";
     private static final String REMOVE_BUTTON_STRING = "Remove";
+    private static final String CUSTOMIZED_DIALOG_TITLE = "Cool Tabs";
     private static final int TAB1_ID = 456;
     private static final int TAB2_ID = 789;
     private static final int TAB3_ID = 123;
@@ -108,6 +115,12 @@
     TabModel mTabModel;
     @Mock
     TabSelectionEditorCoordinator.TabSelectionEditorController mTabSelectionEditorController;
+    @Mock
+    TabGroupTitleEditor mTabGroupTitleEditor;
+    @Mock
+    EditText mTitleTextView;
+    @Mock
+    Editable mEditable;
     @Captor
     ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
 
@@ -167,11 +180,13 @@
         doReturn(REMOVE_BUTTON_STRING)
                 .when(mContext)
                 .getString(R.string.tab_grid_dialog_selection_mode_remove);
+        doReturn(mEditable).when(mTitleTextView).getText();
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mEditable).toString();
 
         mModel = new PropertyModel(TabGridPanelProperties.ALL_KEYS);
         mMediator = new TabGridDialogMediator(mContext, mDialogController, mModel,
                 mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler,
-                mAnimationParamsProvider, mTabSelectionEditorController, "");
+                mAnimationParamsProvider, mTabSelectionEditorController, mTabGroupTitleEditor, "");
     }
 
     @After
@@ -242,6 +257,111 @@
     }
 
     @Test
+    public void onTitleTextChange_WithoutFocus() {
+        TextWatcher textWatcher = mModel.get(TabGridPanelProperties.TITLE_TEXT_WATCHER);
+        // Mock tab1 is the current tab for the dialog.
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+        assertThat(mEditable.toString(), equalTo(CUSTOMIZED_DIALOG_TITLE));
+
+        textWatcher.afterTextChanged(mEditable);
+
+        // TabGroupTitleEditor should not react to text change when there is no focus.
+        verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), any(String.class));
+        verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
+        assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(), equalTo(null));
+    }
+
+    @Test
+    public void onTitleTextChange_WithFocus() {
+        TextWatcher textWatcher = mModel.get(TabGridPanelProperties.TITLE_TEXT_WATCHER);
+        // Mock tab1 is the current tab for the dialog.
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+        assertThat(mEditable.toString(), equalTo(CUSTOMIZED_DIALOG_TITLE));
+
+        // Focus on title TextView.
+        View.OnFocusChangeListener listener =
+                mModel.get(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
+        listener.onFocusChange(mTitleTextView, true);
+
+        textWatcher.afterTextChanged(mEditable);
+
+        assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(),
+                equalTo(CUSTOMIZED_DIALOG_TITLE));
+    }
+
+    @Test
+    public void onTitleTextFocusChange() {
+        View.OnFocusChangeListener listener =
+                mModel.get(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
+        assertThat(mMediator.getIsUpdatingTitleForTesting(), equalTo(false));
+
+        listener.onFocusChange(mTitleTextView, true);
+
+        assertThat(mMediator.getIsUpdatingTitleForTesting(), equalTo(true));
+    }
+
+    @Test
+    public void onKeyBoardVisibilityChanged_ChangeCursorVisibility() {
+        KeyboardVisibilityDelegate.KeyboardVisibilityListener listener =
+                mMediator.getKeyboardVisibilityListenerForTesting();
+        mModel.set(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY, false);
+
+        listener.keyboardVisibilityChanged(true);
+        assertThat(mModel.get(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY), equalTo(true));
+
+        listener.keyboardVisibilityChanged(false);
+        assertThat(mModel.get(TabGridPanelProperties.TITLE_CURSOR_VISIBILITY), equalTo(false));
+    }
+
+    @Test
+    public void onKeyBoardVisibilityChanged_StoreGroupTitle() {
+        KeyboardVisibilityDelegate.KeyboardVisibilityListener keyboardVisibilityListener =
+                mMediator.getKeyboardVisibilityListenerForTesting();
+        TextWatcher textWatcher = mModel.get(TabGridPanelProperties.TITLE_TEXT_WATCHER);
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+
+        // Mock that keyboard shows and group title is updated.
+        keyboardVisibilityListener.keyboardVisibilityChanged(true);
+        View.OnFocusChangeListener onFocusChangeListener =
+                mModel.get(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
+        onFocusChangeListener.onFocusChange(mTitleTextView, true);
+        textWatcher.afterTextChanged(mEditable);
+        assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(),
+                equalTo(CUSTOMIZED_DIALOG_TITLE));
+
+        keyboardVisibilityListener.keyboardVisibilityChanged(false);
+
+        verify(mTabGroupTitleEditor).storeTabGroupTitle(eq(TAB1_ID), eq(CUSTOMIZED_DIALOG_TITLE));
+        verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(CUSTOMIZED_DIALOG_TITLE));
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
+    }
+
+    @Test
+    public void onKeyBoardVisibilityChanged_NoFocus_NotStoreGroupTitle() {
+        KeyboardVisibilityDelegate.KeyboardVisibilityListener keyboardVisibilityListener =
+                mMediator.getKeyboardVisibilityListenerForTesting();
+        TextWatcher textWatcher = mModel.get(TabGridPanelProperties.TITLE_TEXT_WATCHER);
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+
+        // Mock that keyboard shows but title edit text is not focused.
+        keyboardVisibilityListener.keyboardVisibilityChanged(true);
+        textWatcher.afterTextChanged(mEditable);
+        assertThat(mMediator.getIsUpdatingTitleForTesting(), equalTo(false));
+
+        keyboardVisibilityListener.keyboardVisibilityChanged(false);
+
+        verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), anyString());
+        verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
+    }
+
+    @Test
     public void tabAddition() {
         Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
         // Mock that the animation params is not null.
@@ -255,13 +375,13 @@
 
     @Test
     public void tabClosure_NotLast_NotCurrent() {
-        // Assume that tab1 and tab2 are in the same group, but tab2 just gets closed.
+        // Mock that tab1 and tab2 are in the same group, but tab2 just gets closed.
         doReturn(new ArrayList<>(Arrays.asList(mTab1)))
                 .when(mTabGroupModelFilter)
                 .getRelatedTabList(TAB2_ID);
-        // Assume tab1 is the current tab for the dialog.
+        // Mock tab1 is the current tab for the dialog.
         mMediator.setCurrentTabIdForTest(TAB1_ID);
-        // Assume dialog title is null and the dialog is showing.
+        // Mock dialog title is null and the dialog is showing.
         mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, true);
 
@@ -275,13 +395,13 @@
 
     @Test
     public void tabClosure_NotLast_Current() {
-        // Assume that tab1 and tab2 are in the same group, but tab2 just gets closed.
+        // Mock that tab1 and tab2 are in the same group, but tab2 just gets closed.
         doReturn(new ArrayList<>(Arrays.asList(mTab1)))
                 .when(mTabGroupModelFilter)
                 .getRelatedTabList(TAB2_ID);
-        // Assume tab2 is the current tab for the dialog.
+        // Mock tab2 is the current tab for the dialog.
         mMediator.setCurrentTabIdForTest(TAB2_ID);
-        // Assume dialog title is null and the dialog is showing.
+        // Mock dialog title is null and the dialog is showing.
         mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, true);
 
@@ -295,11 +415,11 @@
 
     @Test
     public void tabClosure_Last_Current() {
-        // Assume that tab1 is the last tab in the group and it just gets closed.
+        // Mock that tab1 is the last tab in the group and it just gets closed.
         doReturn(new ArrayList<>()).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
         // As last tab in the group, tab1 is definitely the current tab for the dialog.
         mMediator.setCurrentTabIdForTest(TAB1_ID);
-        // Assume the dialog is showing and the source animation params is not null.
+        // Mock the dialog is showing and the source animation params is not null.
         mModel.set(TabGridPanelProperties.ANIMATION_PARAMS, mAnimationParams);
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, true);
 
@@ -316,13 +436,13 @@
 
     @Test
     public void tabClosure_NotLast_Current_WithDialogHidden() {
-        // Assume that tab1 and tab2 are in the same group, but tab2 just gets closed.
+        // Mock that tab1 and tab2 are in the same group, but tab2 just gets closed.
         doReturn(new ArrayList<>(Arrays.asList(mTab1)))
                 .when(mTabGroupModelFilter)
                 .getRelatedTabList(TAB2_ID);
-        // Assume tab2 is the current tab for the dialog.
+        // Mock tab2 is the current tab for the dialog.
         mMediator.setCurrentTabIdForTest(TAB2_ID);
-        // Assume dialog title is null and the dialog is hidden.
+        // Mock dialog title is null and the dialog is hidden.
         mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
 
@@ -338,6 +458,81 @@
     }
 
     @Test
+    public void tabClosure_NonRootTab_StillGroupAfterClosure_WithStoredTitle() {
+        // Mock that tab1, tab2 and newTab are in the same group and tab1 is the root tab.
+        Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
+        createTabGroup(tabgroup, TAB1_ID);
+
+        // Mock that newTab just get closed.
+        List<Tab> tabgroupAfterClosure = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        doReturn(tabgroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
+        doReturn(tabgroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
+
+        // Mock that newTab is the current tab for the dialog.
+        mMediator.setCurrentTabIdForTest(TAB3_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
+
+        assertThat(mTabGroupTitleEditor.getTabGroupTitle(mTab1.getRootId()),
+                equalTo(CUSTOMIZED_DIALOG_TITLE));
+        mTabModelObserverCaptor.getValue().willCloseTab(newTab, false);
+
+        // Dialog title should still be the stored title.
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
+    }
+
+    @Test
+    public void tabClosure_RootTab_StillGroupAfterClosure_WithStoredTitle() {
+        // Mock that tab1, tab2 and newTab are in the same group and newTab is the root tab.
+        Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2, newTab));
+        createTabGroup(tabgroup, TAB3_ID);
+
+        // Mock that newTab just get closed.
+        List<Tab> tabgroupAfterClosure = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        doReturn(tabgroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
+        doReturn(tabgroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB2_ID);
+
+        // Mock that newTab is the current tab for the dialog.
+        mMediator.setCurrentTabIdForTest(TAB3_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of newTab.
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB3_ID);
+
+        mTabModelObserverCaptor.getValue().willCloseTab(newTab, false);
+
+        // Dialog title should still be the stored title even if the root tab is closed.
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
+    }
+
+    @Test
+    public void tabClosure_SingleTabAfterClosure_WithStoredTitle() {
+        // Mock that tab1, tab2 are in the same group and tab1 is the root tab.
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabgroup, TAB1_ID);
+
+        // Mock that tab2 just get closed.
+        List<Tab> tabgroupAfterClosure = new ArrayList<>(Arrays.asList(mTab1));
+        doReturn(tabgroupAfterClosure).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
+
+        // Mock that tab2 is the current tab for the dialog.
+        mMediator.setCurrentTabIdForTest(TAB2_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
+
+        mTabModelObserverCaptor.getValue().willCloseTab(mTab2, false);
+
+        // Even if there is a stored title for tab1, it is now a single tab, so we won't show the
+        // stored title.
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
+    }
+
+    @Test
     public void tabClosureUndone() {
         // Mock that the dialog is showing.
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, true);
@@ -351,6 +546,28 @@
     }
 
     @Test
+    public void tabClosureUndone_WithStoredTitle() {
+        // Mock that the dialog is showing.
+        mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, true);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
+
+        // Mock that tab1 and tab2 are in the same group, and we are undoing tab2.
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabgroup, TAB1_ID);
+        mTabModelObserverCaptor.getValue().tabClosureUndone(mTab2);
+
+        // If current group has a stored title, dialog title should be set to stored title when
+        // undoing a closure.
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
+        verify(mTabSwitcherResetHandler).resetWithTabList(mTabGroupModelFilter, false, false);
+    }
+
+    @Test
     public void tabClosureUndone_WithDialogHidden() {
         // Mock that the dialog is hidden.
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
@@ -403,6 +620,41 @@
     }
 
     @Test
+    public void hideDialog_StoreModifiedGroupTitle() {
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+
+        // Mock that we have a modified group title before dialog is hidden.
+        TextWatcher textWatcher = mModel.get(TabGridPanelProperties.TITLE_TEXT_WATCHER);
+        View.OnFocusChangeListener onFocusChangeListener =
+                mModel.get(TabGridPanelProperties.TITLE_TEXT_ON_FOCUS_LISTENER);
+        onFocusChangeListener.onFocusChange(mTitleTextView, true);
+        textWatcher.afterTextChanged(mEditable);
+        assertThat(mMediator.getCurrentGroupModifiedTitleForTesting(),
+                equalTo(CUSTOMIZED_DIALOG_TITLE));
+
+        mMediator.hideDialog(false);
+
+        verify(mTabGroupTitleEditor).storeTabGroupTitle(eq(TAB1_ID), eq(CUSTOMIZED_DIALOG_TITLE));
+        verify(mTabGroupTitleEditor).updateTabGroupTitle(eq(mTab1), eq(CUSTOMIZED_DIALOG_TITLE));
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
+    }
+
+    @Test
+    public void hideDialog_NoModifiedGroupTitle() {
+        mMediator.setCurrentTabIdForTest(TAB1_ID);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, TAB1_TITLE);
+
+        mMediator.hideDialog(false);
+
+        // When title is not updated, don't store title when hide dialog.
+        verify(mTabGroupTitleEditor, never()).storeTabGroupTitle(anyInt(), anyString());
+        verify(mTabGroupTitleEditor, never()).updateTabGroupTitle(any(Tab.class), anyString());
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(TAB1_TITLE));
+    }
+
+    @Test
     public void hideDialog_onReset() {
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, true);
 
@@ -417,14 +669,40 @@
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
         mModel.set(TabGridPanelProperties.ANIMATION_PARAMS, null);
         mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
+        // Mock that tab1 and tab2 are in a group.
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabgroup, TAB1_ID);
 
-        mMediator.onReset(Arrays.asList(mTab1));
+        mMediator.onReset(tabgroup);
 
         assertThat(mModel.get(TabGridPanelProperties.IS_DIALOG_VISIBLE), equalTo(true));
         // Animation source Rect should be updated with specific Rect.
         assertThat(mModel.get(TabGridPanelProperties.ANIMATION_PARAMS), equalTo(mAnimationParams));
         // Dialog title should be updated.
-        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
+    }
+
+    @Test
+    public void showDialog_FromGTS_WithStoredTitle() {
+        // Mock that the dialog is hidden and animation source Rect and header title are all null.
+        mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
+        mModel.set(TabGridPanelProperties.ANIMATION_PARAMS, null);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
+        // Mock that tab1 and tab2 are in a group.
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabgroup, TAB1_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
+
+        mMediator.onReset(tabgroup);
+
+        assertThat(mModel.get(TabGridPanelProperties.IS_DIALOG_VISIBLE), equalTo(true));
+        // Animation source Rect should be updated with specific Rect.
+        assertThat(mModel.get(TabGridPanelProperties.ANIMATION_PARAMS), equalTo(mAnimationParams));
+        // Dialog title should be updated with stored title.
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
     }
 
     @Test
@@ -433,19 +711,51 @@
         // the animationParamsProvider is null.
         mMediator = new TabGridDialogMediator(mContext, mDialogController, mModel,
                 mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler, null,
-                mTabSelectionEditorController, "");
+                mTabSelectionEditorController, mTabGroupTitleEditor, "");
+
         // Mock that the dialog is hidden and animation source Rect and header title are all null.
         mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
         mModel.set(TabGridPanelProperties.ANIMATION_PARAMS, null);
         mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
+        // Mock that tab1 and tab2 are in a group.
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabgroup, TAB1_ID);
 
-        mMediator.onReset(Arrays.asList(mTab1));
+        mMediator.onReset(tabgroup);
 
         assertThat(mModel.get(TabGridPanelProperties.IS_DIALOG_VISIBLE), equalTo(true));
         // Animation params should not be specified.
         assertThat(mModel.get(TabGridPanelProperties.ANIMATION_PARAMS), equalTo(null));
         // Dialog title should be updated.
-        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(DIALOG_TITLE1));
+        assertThat(mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(DIALOG_TITLE2));
+    }
+
+    @Test
+    public void showDialog_FromStrip_WithStoredTitle() {
+        // For strip we don't play zoom-in/zoom-out for show/hide dialog, and thus
+        // the animationParamsProvider is null.
+        mMediator = new TabGridDialogMediator(mContext, mDialogController, mModel,
+                mTabModelSelector, mTabCreatorManager, mTabSwitcherResetHandler, null,
+                mTabSelectionEditorController, mTabGroupTitleEditor, "");
+        // Mock that the dialog is hidden and animation source Rect and header title are all null.
+        mModel.set(TabGridPanelProperties.IS_DIALOG_VISIBLE, false);
+        mModel.set(TabGridPanelProperties.ANIMATION_PARAMS, null);
+        mModel.set(TabGridPanelProperties.HEADER_TITLE, null);
+        // Mock that tab1 and tab2 are in a group.
+        List<Tab> tabgroup = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabgroup, TAB1_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        doReturn(CUSTOMIZED_DIALOG_TITLE).when(mTabGroupTitleEditor).getTabGroupTitle(TAB1_ID);
+
+        mMediator.onReset(tabgroup);
+
+        assertThat(mModel.get(TabGridPanelProperties.IS_DIALOG_VISIBLE), equalTo(true));
+        // Animation params should not be specified.
+        assertThat(mModel.get(TabGridPanelProperties.ANIMATION_PARAMS), equalTo(null));
+        // Dialog title should be updated with stored title.
+        assertThat(
+                mModel.get(TabGridPanelProperties.HEADER_TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE));
     }
 
     @Test
@@ -459,9 +769,17 @@
     private Tab prepareTab(int id, String title) {
         Tab tab = mock(Tab.class);
         doReturn(id).when(tab).getId();
+        doReturn(id).when(tab).getRootId();
         doReturn("").when(tab).getUrl();
         doReturn(title).when(tab).getTitle();
         doReturn(true).when(tab).isIncognito();
         return tab;
     }
+
+    private void createTabGroup(List<Tab> tabs, int rootId) {
+        for (Tab tab : tabs) {
+            when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs);
+            doReturn(rootId).when(tab).getRootId();
+        }
+    }
 }
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
index ecc7fd79..d6db552 100644
--- a/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
+++ b/chrome/android/features/tab_ui/junit/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediatorUnitTest.java
@@ -28,6 +28,8 @@
 
 import android.app.Activity;
 import android.content.ComponentCallbacks;
+import android.content.Context;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Color;
@@ -38,6 +40,8 @@
 import android.support.v7.widget.helper.ItemTouchHelper;
 import android.view.View;
 
+import androidx.annotation.IntDef;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -51,6 +55,7 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.Callback;
+import org.chromium.base.ContextUtils;
 import org.chromium.base.UserDataHost;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
@@ -78,6 +83,8 @@
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.SimpleRecyclerViewAdapter;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -96,12 +103,24 @@
     private static final String TAB2_TITLE = "Tab2";
     private static final String TAB3_TITLE = "Tab3";
     private static final String NEW_TITLE = "New title";
+    private static final String CUSTOMIZED_DIALOG_TITLE1 = "Cool Tabs";
+    private static final String TAB_GROUP_TITLES_FILE_NAME = "tab_group_titles";
     private static final int TAB1_ID = 456;
     private static final int TAB2_ID = 789;
     private static final int TAB3_ID = 123;
     private static final int POSITION1 = 0;
     private static final int POSITION2 = 1;
 
+    @IntDef({TabListMediatorType.TAB_SWITCHER, TabListMediatorType.TAB_STRIP,
+            TabListMediatorType.TAB_GRID_DIALOG})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TabListMediatorType {
+        int TAB_SWITCHER = 0;
+        int TAB_STRIP = 1;
+        int TAB_GRID_DIALOG = 2;
+        int NUM_ENTRIES = 3;
+    }
+
     @Mock
     TabContentManager mTabContentManager;
     @Mock
@@ -142,6 +161,16 @@
     Profile mProfile;
     @Mock
     Tracker mTracker;
+    @Mock
+    TabListMediator.TitleProvider mTitleProvider;
+    @Mock
+    SharedPreferences mSharedPreferences;
+    @Mock
+    SharedPreferences.Editor mEditor;
+    @Mock
+    SharedPreferences.Editor mPutStringEditor;
+    @Mock
+    SharedPreferences.Editor mRemoveEditor;
     @Captor
     ArgumentCaptor<TabModelObserver> mTabModelObserverCaptor;
     @Captor
@@ -165,6 +194,7 @@
     private RecyclerView.ViewHolder mDummyViewHolder2;
     private View mItemView1 = mock(View.class);
     private View mItemView2 = mock(View.class);
+    private TabGroupModelFilter.Observer mMediatorTabGroupModelFilterObserver;
 
     @Before
     public void setUp() {
@@ -173,6 +203,8 @@
 
         MockitoAnnotations.initMocks(this);
 
+        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(false);
+        FeatureUtilities.setStartSurfaceEnabledForTesting(false);
         mTab1 = prepareTab(TAB1_ID, TAB1_TITLE);
         mTab2 = prepareTab(TAB2_ID, TAB2_TITLE);
         mViewHolder1 = prepareViewHolder(TAB1_ID, POSITION1);
@@ -223,20 +255,29 @@
                 .openTabGridDialog(any(Tab.class));
         doNothing().when(mContext).registerComponentCallbacks(mComponentCallbacksCaptor.capture());
         doReturn(mGridLayoutManager).when(mRecyclerView).getLayoutManager();
+        doReturn(mSharedPreferences)
+                .when(mContext)
+                .getSharedPreferences(TAB_GROUP_TITLES_FILE_NAME, Context.MODE_PRIVATE);
+        doReturn(mEditor).when(mSharedPreferences).edit();
+        doReturn(mRemoveEditor).when(mEditor).remove(any(String.class));
+        doReturn(mPutStringEditor).when(mEditor).putString(any(String.class), any(String.class));
 
         mModel = new TabListModel();
         mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                false, null, null, mGridCardOnClickListenerProvider, null,
+                mTabContentManager::getTabThumbnailWithCallback, mTitleProvider,
+                mTabListFaviconProvider, false, null, null, mGridCardOnClickListenerProvider, null,
                 getClass().getSimpleName(), 0);
         mMediator.registerOrientationListener(mGridLayoutManager);
         TrackerFactory.setTrackerForTests(mTracker);
+        ContextUtils.initApplicationContextForTests(mContext);
     }
 
     @After
     public void tearDown() {
         RecordUserAction.setDisabledForTests(false);
         RecordHistogram.setDisabledForTests(false);
+        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(null);
+        FeatureUtilities.setStartSurfaceEnabledForTesting(null);
     }
 
     @Test
@@ -245,18 +286,41 @@
     }
 
     @Test
-    public void updatesTitle() {
+    public void updatesTitle_WithoutStoredTitle() {
         initAndAssertAllProperties();
 
         assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
 
-        doReturn(NEW_TITLE).when(mTab1).getTitle();
+        doReturn(NEW_TITLE).when(mTitleProvider).getTitle(mTab1);
         mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
 
         assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(NEW_TITLE));
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void updatesTitle_WithStoredTitle_TabGroup() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+
+        // Mock that tab1 and new tab are in the same group with root ID as TAB1_ID.
+        Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
+        createTabGroup(tabs, TAB1_ID);
+
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        when(mSharedPreferences.getString(String.valueOf(mTab1.getRootId()), null))
+                .thenReturn(CUSTOMIZED_DIALOG_TITLE1);
+        assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+
+        mTabObserverCaptor.getValue().onTitleUpdated(mTab1);
+
+        assertThat(mModel.get(0).model.get(TabProperties.TITLE), equalTo(CUSTOMIZED_DIALOG_TITLE1));
+    }
+
+    @Test
     public void updatesFavicon_SingleTab_GTS() {
         initAndAssertAllProperties();
         mMediator.setActionOnAllRelatedTabsForTesting(true);
@@ -339,11 +403,8 @@
     }
 
     @Test
-    @Features.DisableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
-            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
     public void sendsMoveTabSignalCorrectlyWithoutGroup() {
         initAndAssertAllProperties();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(false);
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         doReturn(mEmptyTabModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
 
@@ -356,8 +417,8 @@
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
             ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
     public void sendsMoveTabSignalCorrectlyWithGroup() {
-        initAndAssertAllProperties();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
 
@@ -372,10 +433,7 @@
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
             ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
     public void sendsMoveTabSignalCorrectlyWithinGroup() {
-        initAndAssertAllProperties();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
-
-        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
 
         getItemTouchHelperCallback().onMove(mRecyclerView, mViewHolder1, mViewHolder2);
 
@@ -386,16 +444,14 @@
     @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
             ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
     public void sendsMergeTabSignalCorrectly() {
-        initAndAssertAllProperties();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
-        mMediator.setActionOnAllRelatedTabsForTesting(true);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
         itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
         itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
 
-        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
 
         // Simulate the drop action.
@@ -411,7 +467,7 @@
     @Features.DisableFeatures({TAB_GROUPS_ANDROID, TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
     public void neverSendsMergeTabSignal_Without_Group() {
         initAndAssertAllProperties();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(false);
+
         mMediator.setActionOnAllRelatedTabsForTesting(true);
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
@@ -419,14 +475,12 @@
         itemTouchHelperCallback.setSelectedTabIndexForTesting(POSITION2);
         itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
 
-        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
 
         // Simulate the drop action.
         itemTouchHelperCallback.onSelectedChanged(
                 mDummyViewHolder1, ItemTouchHelper.ACTION_STATE_IDLE);
 
-        verify(mTabGroupModelFilter, never()).mergeTabsToGroup(anyInt(), anyInt());
         verify(mGridLayoutManager, never()).removeView(any(View.class));
     }
 
@@ -434,9 +488,8 @@
     @Features.EnableFeatures({TAB_GROUPS_ANDROID})
     @Features.DisableFeatures({TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
     public void neverSendsMergeTabSignal_With_Group_Without_Group_Improvement() {
-        initAndAssertAllProperties();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
-        mMediator.setActionOnAllRelatedTabsForTesting(true);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(true);
         itemTouchHelperCallback.setHoveredTabIndexForTesting(POSITION1);
@@ -460,16 +513,13 @@
     // clang-format off
     public void sendsUngroupSignalCorrectly() {
         // clang-format on
-        initAndAssertAllProperties();
-        setUpForTabGroupOperation();
-        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
 
         TabGridItemTouchHelperCallback itemTouchHelperCallback = getItemTouchHelperCallback();
         itemTouchHelperCallback.setActionsOnAllRelatedTabsForTesting(false);
         itemTouchHelperCallback.setUnGroupTabIndexForTesting(POSITION1);
         itemTouchHelperCallback.getMovementFlags(mRecyclerView, mDummyViewHolder1);
 
-        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
         doReturn(mAdapter).when(mRecyclerView).getAdapter();
         doReturn(1).when(mAdapter).getItemCount();
 
@@ -702,18 +752,17 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMergeIntoGroup() {
-        setUpForTabGroupOperation();
-        // Setup the mediator with a CreateGroupButtonProvider.
-        mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                true, mCreateGroupButtonProvider, null, null, null, getClass().getSimpleName(), 0);
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume that moveTab in TabModel is finished. Selected tab in the group becomes mTab1.
         doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
         doReturn(mTab2).when(mTabModel).getTabAt(POSITION1);
         doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
-        doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
 
         // Assume that reset in TabGroupModelFilter is finished.
         doReturn(new ArrayList<>(Arrays.asList(mTab1, mTab2)))
@@ -728,7 +777,7 @@
         assertNotNull(mModel.get(0).model.get(TabProperties.FAVICON));
         assertNotNull(mModel.get(1).model.get(TabProperties.FAVICON));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMergeTabToGroup(mTab1, TAB2_ID);
+        mMediatorTabGroupModelFilterObserver.didMergeTabToGroup(mTab1, TAB2_ID);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -737,12 +786,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMoveOutOfGroup_GTS_Moved_Tab_Selected() {
-        setUpForTabGroupOperation();
-        // Setup the mediator with a CreateGroupButtonProvider.
-        mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                true, mCreateGroupButtonProvider, null, null, null, getClass().getSimpleName(), 0);
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume that two tabs are in the same group before ungroup.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab2));
@@ -757,7 +806,7 @@
         doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION2);
         doReturn(2).when(mTabGroupModelFilter).getCount();
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION1);
 
         assertThat(mModel.size(), equalTo(2));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -769,12 +818,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMoveOutOfGroup_GTS_Origin_Tab_Selected() {
-        setUpForTabGroupOperation();
-        // Setup the mediator with a CreateGroupButtonProvider.
-        mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                true, mCreateGroupButtonProvider, null, null, null, getClass().getSimpleName(), 0);
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume that two tabs are in the same group before ungroup.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
@@ -789,7 +838,7 @@
         doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
         doReturn(2).when(mTabGroupModelFilter).getCount();
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab2, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab2, POSITION1);
 
         assertThat(mModel.size(), equalTo(2));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -801,8 +850,13 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMoveOutOfGroup_GTS_LastTab() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+
         // Assume that tab1 is a single tab.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
         mMediator.resetWithListOfTabs(tabs, false, false);
@@ -811,7 +865,7 @@
         doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
 
         // Ungroup the single tab.
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION1);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB1_ID));
@@ -819,13 +873,13 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMoveOutOfGroup_Dialog() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
 
-        // Setup the mediator with a DialogHandler.
-        mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                false, null, null, null, mTabGridDialogHandler, getClass().getSimpleName(), 0);
         // Assume that filter is already updated.
         doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION1);
 
@@ -835,7 +889,7 @@
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION1);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -844,13 +898,13 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMoveOutOfGroup_Dialog_LastTab() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
 
-        // Setup the mediator with a DialogHandler.
-        mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                false, null, null, null, mTabGridDialogHandler, getClass().getSimpleName(), 0);
         // Assume that tab1 is a single tab.
         List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
         mMediator.resetWithListOfTabs(tabs, false, false);
@@ -859,18 +913,19 @@
         doReturn(tabs).when(mTabGroupModelFilter).getRelatedTabList(TAB1_ID);
 
         // Ungroup the single tab.
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION1);
 
         verify(mTabGridDialogHandler).updateDialogContent(Tab.INVALID_TAB_ID);
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMoveOutOfGroup_Strip() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_STRIP);
 
-        mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                false, null, null, null, null, getClass().getSimpleName(), 0);
         // Assume that filter is already updated.
         doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION1);
 
@@ -880,7 +935,7 @@
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION1);
 
         assertThat(mModel.size(), equalTo(1));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -923,8 +978,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMovementWithGroup_Forward() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume that moveTab in TabModel is finished.
         doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
@@ -935,7 +994,7 @@
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabGroup(mTab2, POSITION2, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabGroup(mTab2, POSITION2, POSITION1);
 
         assertThat(mModel.size(), equalTo(2));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -943,8 +1002,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMovementWithGroup_Backward() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume that moveTab in TabModel is finished.
         doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
@@ -955,7 +1018,7 @@
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabGroup(mTab1, POSITION1, POSITION2);
+        mMediatorTabGroupModelFilterObserver.didMoveTabGroup(mTab1, POSITION1, POSITION2);
 
         assertThat(mModel.size(), equalTo(2));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -963,8 +1026,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMovementWithinGroup_Forward() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
 
         // Assume that moveTab in TabModel is finished.
         doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
@@ -977,8 +1044,7 @@
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveWithinGroup(
-                mTab2, POSITION2, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveWithinGroup(mTab2, POSITION2, POSITION1);
 
         assertThat(mModel.size(), equalTo(2));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -986,8 +1052,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void tabMovementWithinGroup_Backward() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
 
         // Assume that moveTab in TabModel is finished.
         doReturn(mTab1).when(mTabModel).getTabAt(POSITION2);
@@ -1000,8 +1070,7 @@
         assertThat(mModel.get(1).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
         assertThat(mModel.get(1).model.get(TabProperties.TITLE), equalTo(TAB2_TITLE));
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveWithinGroup(
-                mTab1, POSITION1, POSITION2);
+        mMediatorTabGroupModelFilterObserver.didMoveWithinGroup(mTab1, POSITION1, POSITION2);
 
         assertThat(mModel.size(), equalTo(2));
         assertThat(mModel.get(0).model.get(TabProperties.TAB_ID), equalTo(TAB2_ID));
@@ -1009,8 +1078,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void undoGrouped_One_Adjacent_Tab() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume there are 3 tabs in TabModel, mTab2 just grouped with mTab1;
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE);
@@ -1024,7 +1097,7 @@
         doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
         doReturn(tab3).when(mTabGroupModelFilter).getTabAt(2);
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab2, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab2, POSITION1);
 
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
@@ -1033,8 +1106,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void undoForwardGrouped_One_Tab() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume there are 3 tabs in TabModel, tab3 just grouped with mTab1;
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE);
@@ -1048,7 +1125,7 @@
         doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
         doReturn(tab3).when(mTabGroupModelFilter).getTabAt(2);
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(tab3, POSITION1);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(tab3, POSITION1);
 
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
@@ -1057,8 +1134,12 @@
     }
 
     @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
     public void undoBackwardGrouped_One_Tab() {
-        setUpForTabGroupOperation();
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
 
         // Assume there are 3 tabs in TabModel, mTab1 just grouped with mTab2;
         Tab tab3 = prepareTab(TAB3_ID, TAB3_TITLE);
@@ -1072,7 +1153,7 @@
         doReturn(mTab2).when(mTabGroupModelFilter).getTabAt(POSITION2);
         doReturn(tab3).when(mTabGroupModelFilter).getTabAt(2);
 
-        mTabGroupModelFilterObserverCaptor.getValue().didMoveTabOutOfGroup(mTab1, POSITION2);
+        mMediatorTabGroupModelFilterObserver.didMoveTabOutOfGroup(mTab1, POSITION2);
 
         assertThat(mModel.size(), equalTo(3));
         assertThat(mModel.indexFromId(TAB1_ID), equalTo(0));
@@ -1159,6 +1240,121 @@
         assertThat(mMediator.indexOfTab(TAB2_ID), equalTo(1));
     }
 
+    @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void getLatestTitle_NotGTS() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_GRID_DIALOG);
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        when(mSharedPreferences.getString(String.valueOf(mTab1.getRootId()), null))
+                .thenReturn(CUSTOMIZED_DIALOG_TITLE1);
+        assertThat(mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
+                equalTo(CUSTOMIZED_DIALOG_TITLE1));
+
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID);
+
+        // Even if we have a stored title, we only show it in tab switcher.
+        assertThat(mMediator.getLatestTitleForTab(mTab1), equalTo(TAB1_TITLE));
+    }
+
+    @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void getLatestTitle_SingleTab_GTS() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        when(mSharedPreferences.getString(String.valueOf(mTab1.getRootId()), null))
+                .thenReturn(CUSTOMIZED_DIALOG_TITLE1);
+        assertThat(mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
+                equalTo(CUSTOMIZED_DIALOG_TITLE1));
+
+        // Mock that tab1 is a single tab.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1));
+        createTabGroup(tabs, TAB1_ID);
+
+        // We never show stored title for single tab.
+        assertThat(mMediator.getLatestTitleForTab(mTab1), equalTo(TAB1_TITLE));
+    }
+
+    @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void getLatestTitle_Stored_GTS() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+        // Mock that we have a stored title stored with reference to root ID of tab1.
+        when(mSharedPreferences.getString(String.valueOf(mTab1.getRootId()), null))
+                .thenReturn(CUSTOMIZED_DIALOG_TITLE1);
+        assertThat(mMediator.getTabGroupTitleEditor().getTabGroupTitle(mTab1.getRootId()),
+                equalTo(CUSTOMIZED_DIALOG_TITLE1));
+
+        // Mock that tab1 and tab2 are in the same group and group root id is TAB1_ID.
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, mTab2));
+        createTabGroup(tabs, TAB1_ID);
+
+        assertThat(mMediator.getLatestTitleForTab(mTab1), equalTo(CUSTOMIZED_DIALOG_TITLE1));
+    }
+
+    @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void updateTabGroupTitle_GTS() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+        assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE), equalTo(TAB1_TITLE));
+
+        // Mock that tab1 and newTab are in the same group and group root id is TAB1_ID.
+        Tab newTab = prepareTab(TAB3_ID, TAB3_TITLE);
+        List<Tab> tabs = new ArrayList<>(Arrays.asList(mTab1, newTab));
+        createTabGroup(tabs, TAB1_ID);
+        doReturn(mTab1).when(mTabGroupModelFilter).getTabAt(POSITION1);
+        doReturn(POSITION1).when(mTabGroupModelFilter).indexOf(mTab1);
+
+        mMediator.getTabGroupTitleEditor().updateTabGroupTitle(mTab1, CUSTOMIZED_DIALOG_TITLE1);
+
+        assertThat(mModel.get(POSITION1).model.get(TabProperties.TITLE),
+                equalTo(CUSTOMIZED_DIALOG_TITLE1));
+    }
+
+    @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void tabGroupTitleEditor_storeTitle() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+        TabGroupTitleEditor tabGroupTitleEditor = mMediator.getTabGroupTitleEditor();
+
+        tabGroupTitleEditor.storeTabGroupTitle(mTab1.getRootId(), CUSTOMIZED_DIALOG_TITLE1);
+
+        verify(mEditor).putString(
+                eq(String.valueOf(mTab1.getRootId())), eq(CUSTOMIZED_DIALOG_TITLE1));
+        verify(mPutStringEditor).apply();
+    }
+
+    @Test
+    @Features.EnableFeatures({ChromeFeatureList.TAB_GROUPS_ANDROID,
+            ChromeFeatureList.TAB_GROUPS_UI_IMPROVEMENTS_ANDROID})
+    // clang-format off
+    public void tabGroupTitleEditor_deleteTitle() {
+        // clang-format on
+        setUpForTabGroupOperation(TabListMediatorType.TAB_SWITCHER);
+        TabGroupTitleEditor tabGroupTitleEditor = mMediator.getTabGroupTitleEditor();
+
+        tabGroupTitleEditor.deleteTabGroupTitle(mTab1.getRootId());
+
+        verify(mEditor).remove(eq(String.valueOf(mTab1.getRootId())));
+        verify(mRemoveEditor).apply();
+    }
+
     private void initAndAssertAllProperties() {
         List<Tab> tabs = new ArrayList<>();
         for (int i = 0; i < mTabModel.getCount(); i++) {
@@ -1204,9 +1400,11 @@
         when(tab.getView()).thenReturn(mock(View.class));
         when(tab.getUserDataHost()).thenReturn(new UserDataHost());
         doReturn(id).when(tab).getId();
+        doReturn(id).when(tab).getRootId();
         doReturn("").when(tab).getUrl();
         doReturn(title).when(tab).getTitle();
         doReturn(true).when(tab).isIncognito();
+        doReturn(title).when(mTitleProvider).getTitle(tab);
         return tab;
     }
 
@@ -1232,7 +1430,7 @@
                 0f, 0f, 0f, mProfile);
     }
 
-    private void setUpForTabGroupOperation() {
+    private void setUpForTabGroupOperation(@TabListMediatorType int type) {
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getCurrentTabModelFilter();
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getTabModelFilter(true);
         doReturn(mTabGroupModelFilter).when(mTabModelFilterProvider).getTabModelFilter(false);
@@ -1240,10 +1438,29 @@
                 .when(mTabGroupModelFilter)
                 .addTabGroupObserver(mTabGroupModelFilterObserverCaptor.capture());
 
+        TabListMediator.TabGridDialogHandler handler =
+                type == TabListMediatorType.TAB_GRID_DIALOG ? mTabGridDialogHandler : null;
+        boolean actionOnRelatedTabs = type == TabListMediatorType.TAB_SWITCHER;
+        FeatureUtilities.setTabGroupsAndroidEnabledForTesting(true);
+
         mMediator = new TabListMediator(mContext, mModel, mTabModelSelector,
-                mTabContentManager::getTabThumbnailWithCallback, null, mTabListFaviconProvider,
-                true, null, null, null, null, getClass().getSimpleName(), 0);
+                mTabContentManager::getTabThumbnailWithCallback, mTitleProvider,
+                mTabListFaviconProvider, actionOnRelatedTabs, null, null, null, handler,
+                getClass().getSimpleName(), 0);
+
+        // There are two TabGroupModelFilter.Observer added when initializing TabListMediator, one
+        // from TabListMediator and the other from TabGroupTitleEditor. Here we only test the one
+        // from TabListMediator.
+        mMediatorTabGroupModelFilterObserver =
+                mTabGroupModelFilterObserverCaptor.getAllValues().get(0);
 
         initAndAssertAllProperties();
     }
+
+    private void createTabGroup(List<Tab> tabs, int rootId) {
+        for (Tab tab : tabs) {
+            when(mTabGroupModelFilter.getRelatedTabList(tab.getId())).thenReturn(tabs);
+            doReturn(rootId).when(tab).getRootId();
+        }
+    }
 }
diff --git a/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml b/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml
index 0bfcc57..8f5ae7d 100644
--- a/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml
+++ b/chrome/android/java/res/layout/ephemeral_tab_toolbar.xml
@@ -7,14 +7,14 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:paddingBottom="30dp"
     android:layout_width="match_parent"
-    android:layout_height="86dp">
+    android:layout_height="100dp"
+    android:paddingBottom="30dp">
 
     <LinearLayout
         android:id="@+id/toolbar"
         android:layout_width="match_parent"
-        android:layout_height="@dimen/toolbar_height_no_shadow"
+        android:layout_height="@dimen/preview_tab_toolbar_height"
         android:orientation="vertical">
 
         <View
@@ -23,16 +23,16 @@
 
         <RelativeLayout
             android:layout_width="match_parent"
-            android:layout_height="48dp"
+            android:layout_height="62dp"
             android:orientation="horizontal">
 
             <org.chromium.ui.widget.ChromeImageView
                 android:id="@+id/favicon"
                 android:layout_width="48dp"
                 android:layout_height="48dp"
-                android:layout_centerVertical="true"
                 android:layout_marginStart="8dp"
                 android:layout_marginEnd="8dp"
+                android:layout_marginTop="12dp"
                 android:padding="12dp"
                 android:src="@drawable/ic_chrome"
                 app:tint="@color/default_icon_color_blue"
@@ -57,19 +57,41 @@
                 android:layout_centerVertical="true"
                 android:padding="12dp"
                 android:src="@drawable/open_in_new_tab"
-                app:tint="@color/default_icon_color"
-                tools:ignore="ContentDescription" />
+                android:contentDescription="@string/contextmenu_open_in_new_tab"
+                app:tint="@color/default_icon_color" />
 
             <TextView
                 android:id="@+id/ephemeral_tab_text"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="12dp"
+                android:layout_marginTop="16dp"
                 android:layout_toStartOf="@id/open_in_new_tab"
                 android:layout_toEndOf="@id/favicon"
                 android:ellipsize="end"
                 android:singleLine="true"
-                android:textAppearance="@style/TextAppearance.BlackTitle1" />
+                android:textAppearance="@style/TextAppearance.BlackBodyDefault" />
+
+            <org.chromium.ui.widget.ChromeImageView
+                android:id="@+id/security_icon"
+                android:layout_width="16dp"
+                android:layout_height="16dp"
+                android:layout_below="@id/ephemeral_tab_text"
+                android:layout_toEndOf="@id/favicon"
+                android:layout_marginTop="4dp"
+                app:tint="@color/default_icon_color" />
+
+            <TextView
+                android:id="@+id/ephemeral_tab_caption"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_toStartOf="@id/open_in_new_tab"
+                android:layout_toEndOf="@id/security_icon"
+                android:layout_below="@id/ephemeral_tab_text"
+                android:layout_marginLeft="4dp"
+                android:ellipsize="start"
+                android:singleLine="true"
+                android:textAppearance="@style/TextAppearance.BlackHint2" />
+
         </RelativeLayout>
 
         <View
@@ -90,6 +112,6 @@
         android:id="@+id/shadow"
         android:layout_width="match_parent"
         android:layout_height="@dimen/action_bar_shadow_height"
-        android:layout_marginTop="@dimen/toolbar_height_no_shadow"/>
+        android:layout_marginTop="@dimen/preview_tab_toolbar_height"/>
 
 </FrameLayout>
diff --git a/chrome/android/java/res/layout/navigation_sheet_toolbar.xml b/chrome/android/java/res/layout/navigation_sheet_toolbar.xml
index f6c5ad7..92a00ba 100644
--- a/chrome/android/java/res/layout/navigation_sheet_toolbar.xml
+++ b/chrome/android/java/res/layout/navigation_sheet_toolbar.xml
@@ -6,8 +6,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:layout_marginTop="8dp"
-    android:paddingBottom="@dimen/navigation_sheet_toolbar_bottom_padding">
+    android:layout_marginTop="8dp">
     <ImageView
         android:id="@+id/drag_handlebar"
         android:layout_width="wrap_content"
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index c60bb3d..9215347 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -136,6 +136,9 @@
     <!-- Overlay panel dimensions -->
     <dimen name="overlay_panel_bar_height_legacy">56dp</dimen>
     <dimen name="overlay_panel_bar_height">70dp</dimen>
+
+    <!-- Preview tab dimensions -->
+    <dimen name="preview_tab_toolbar_height">70dp</dimen>
     <dimen name="preview_tab_favicon_size">24dp</dimen>
 
     <!-- Autofill keyboard accessory dimensions -->
@@ -673,10 +676,10 @@
     <dimen name="navigation_bubble_text_bottom_padding">5dp</dimen>
     <dimen name="navigation_bubble_text_start_padding">2dp</dimen>
     <dimen name="navigation_bubble_text_end_padding">8dp</dimen>
-    <dimen name="navigation_sheet_toolbar_bottom_padding">44dp</dimen>
     <dimen name="navigation_sheet_content_top_padding">18dp</dimen>
     <dimen name="navigation_sheet_content_bottom_padding">4dp</dimen>
     <dimen name="navigation_sheet_content_wrap_padding">12dp</dimen>
+    <dimen name="navigation_sheet_peek_height">64dp</dimen>
 
     <!-- ChromeTextInputLayout dimensions -->
     <dimen name="text_input_layout_padding_start">3dp</dimen>
diff --git a/chrome/android/java/res_download/layout/download_manager_section_header.xml b/chrome/android/java/res_download/layout/download_manager_section_header.xml
index 9ab6962..9eec352 100644
--- a/chrome/android/java/res_download/layout/download_manager_section_header.xml
+++ b/chrome/android/java/res_download/layout/download_manager_section_header.xml
@@ -6,7 +6,6 @@
 
 <RelativeLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content">
     <View
@@ -28,25 +27,4 @@
         android:maxLines="1"
         android:textAppearance="@style/TextAppearance.BlackTitle1"/>
 
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@+id/date"
-        android:paddingStart="@dimen/list_item_default_margin"
-        android:textAppearance="@style/TextAppearance.BlackHint2"
-        android:maxLines="1" />
-
-    <Space
-        android:id="@+id/bottom_space"
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_below="@+id/title" />
-
-    <include layout="@layout/list_menu_button"
-        android:layout_width="48dp"
-        android:layout_height="wrap_content"
-        android:layout_alignParentEnd="true"
-        android:layout_centerVertical="true" />
-
 </RelativeLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayContentDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayContentDelegate.java
index 5401c19..b048cf5d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayContentDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayContentDelegate.java
@@ -60,6 +60,12 @@
     public void onVisibilityChanged(boolean isVisible) {}
 
     /**
+     * Called when the SSL state changes.
+     */
+
+    public void onSSLStateUpdated() {}
+
+    /**
      * Called once the WebContents has been seen.
      */
     public void onContentViewSeen() {}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
index 4bd5329..8b82c5d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelContent.java
@@ -197,6 +197,11 @@
             }
 
             @Override
+            public void visibleSSLStateChanged() {
+                mContentDelegate.onSSLStateUpdated();
+            }
+
+            @Override
             public void enterFullscreenModeForTab(boolean prefersNavigationBar) {
                 mIsFullscreen = true;
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java
index 5dda11b..e2009f8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java
@@ -7,6 +7,8 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.support.annotation.DrawableRes;
+import android.text.TextUtils;
 
 import org.chromium.base.Callback;
 import org.chromium.chrome.R;
@@ -17,18 +19,19 @@
 import org.chromium.chrome.browser.favicon.FaviconHelper;
 import org.chromium.chrome.browser.favicon.FaviconUtils;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.ssl.SecurityStateModel;
 import org.chromium.chrome.browser.tabmodel.TabLaunchType;
 import org.chromium.chrome.browser.ui.widget.RoundedIconGenerator;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
 import org.chromium.components.embedder_support.view.ContentView;
+import org.chromium.components.security_state.ConnectionSecurityLevel;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.NavigationHandle;
+import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.ui.base.PageTransition;
 
-import java.net.MalformedURLException;
-import java.net.URL;
-
 /**
  * Central class for ephemeral tab, responsible for spinning off other classes necessary to display
  * the preview tab UI.
@@ -42,6 +45,7 @@
     private final BottomSheetController mBottomSheetController;
     private final FaviconLoader mFaviconLoader;
     private OverlayPanelContent mPanelContent;
+    private WebContentsObserver mWebContentsObserver;
     private EphemeralTabSheetContent mSheetContent;
     private boolean mIsIncognito;
     private String mUrl;
@@ -81,9 +85,10 @@
 
         getContent().loadUrl(url, true);
         getContent().updateBrowserControlsState(true);
+        if (mWebContentsObserver == null) mWebContentsObserver = createWebContentsObserver();
         mSheetContent.attachWebContents(
                 getContent().getWebContents(), (ContentView) getContent().getContainerView());
-        mSheetContent.setTitleText(title);
+        mSheetContent.updateTitle(title);
         mBottomSheetController.requestShowContent(mSheetContent, true);
 
         // TODO(donnd): Collect UMA with OverlayPanel.StateChangeReason.CLICK.
@@ -109,6 +114,11 @@
             mPanelContent.destroy();
             mPanelContent = null;
         }
+
+        if (mWebContentsObserver != null) {
+            mWebContentsObserver.destroy();
+            mWebContentsObserver = null;
+        }
     }
 
     private void openInNewTab() {
@@ -136,6 +146,40 @@
         mSheetContent.startFaviconAnimation(drawable);
     }
 
+    private WebContentsObserver createWebContentsObserver() {
+        return new WebContentsObserver(mPanelContent.getWebContents()) {
+            @Override
+            public void titleWasSet(String title) {
+                mSheetContent.updateTitle(title);
+            }
+
+            @Override
+            public void didFinishNavigation(NavigationHandle navigation) {
+                if (navigation.hasCommitted() && navigation.isInMainFrame()) {
+                    mSheetContent.updateURL(mPanelContent.getWebContents().getVisibleUrl());
+                }
+            }
+        };
+    }
+
+    @DrawableRes
+    private static int getSecurityIconResource(@ConnectionSecurityLevel int securityLevel) {
+        switch (securityLevel) {
+            case ConnectionSecurityLevel.NONE:
+            case ConnectionSecurityLevel.WARNING:
+                return R.drawable.omnibox_info;
+            case ConnectionSecurityLevel.DANGEROUS:
+                return R.drawable.omnibox_https_invalid;
+            case ConnectionSecurityLevel.SECURE_WITH_POLICY_INSTALLED_CERT:
+            case ConnectionSecurityLevel.SECURE:
+            case ConnectionSecurityLevel.EV_SECURE:
+                return R.drawable.omnibox_https_valid;
+            default:
+                assert false;
+        }
+        return 0;
+    }
+
     /**
      * Observes the ephemeral tab web contents and loads the associated favicon.
      */
@@ -144,16 +188,18 @@
 
         @Override
         public void onMainFrameLoadStarted(String url, boolean isExternalUrl) {
-            try {
-                String newHost = new URL(url).getHost();
-                String curHost = mCurrentUrl == null ? null : new URL(mCurrentUrl).getHost();
-                if (!newHost.equals(curHost)) {
-                    mCurrentUrl = url;
-                    mFaviconLoader.loadFavicon(url, (drawable) -> onFaviconAvailable(drawable));
-                }
-            } catch (MalformedURLException e) {
-                assert false : "Malformed URL should not be passed.";
-            }
+            if (TextUtils.equals(mCurrentUrl, url)) return;
+
+            mCurrentUrl = url;
+            mFaviconLoader.loadFavicon(url, (drawable) -> onFaviconAvailable(drawable));
+        }
+
+        @Override
+        public void onSSLStateUpdated() {
+            int securityLevel = SecurityStateModel.getSecurityLevelForWebContents(
+                    mPanelContent.getWebContents());
+            mSheetContent.setSecurityIcon(getSecurityIconResource(securityLevel));
+            mSheetContent.updateURL(mPanelContent.getWebContents().getVisibleUrl());
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
index 294b5f29..86a6dd7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabSheetContent.java
@@ -7,6 +7,7 @@
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.TransitionDrawable;
+import android.support.annotation.DrawableRes;
 import android.support.annotation.Nullable;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -24,6 +25,7 @@
 import org.chromium.chrome.browser.ui.widget.FadingShadowView;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.components.embedder_support.view.ContentView;
+import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.RenderCoordinates;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.ActivityWindowAndroid;
@@ -32,6 +34,8 @@
  * Represents ephemeral tab content and the toolbar, which can be included inside the bottom sheet.
  */
 public class EphemeralTabSheetContent implements BottomSheet.BottomSheetContent {
+    private static final float PEEK_TOOLBAR_HEIGHT_MULTIPLE = 2.f;
+
     private final Context mContext;
     private final Runnable mOpenNewTabCallback;
     private final Runnable mToolbarClickCallback;
@@ -87,7 +91,7 @@
         mSheetContentView.addView(mThinWebView.getView());
 
         int topPadding =
-                mContext.getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow);
+                mContext.getResources().getDimensionPixelSize(R.dimen.preview_tab_toolbar_height);
         mSheetContentView.setPadding(0, topPadding, 0, 0);
     }
 
@@ -127,9 +131,21 @@
     }
 
     /** Sets the ephemeral tab title text. */
-    public void setTitleText(String text) {
+    public void updateTitle(String title) {
         TextView toolbarText = mToolbarView.findViewById(R.id.ephemeral_tab_text);
-        toolbarText.setText(text);
+        toolbarText.setText(title);
+    }
+
+    /** Sets the ephemeral tab URL. */
+    public void updateURL(String url) {
+        TextView caption = mToolbarView.findViewById(R.id.ephemeral_tab_caption);
+        caption.setText(UrlFormatter.formatUrlForSecurityDisplayOmitScheme(url));
+    }
+
+    /** Sets the security icon. */
+    public void setSecurityIcon(@DrawableRes int resId) {
+        ImageView securityIcon = mToolbarView.findViewById(R.id.security_icon);
+        securityIcon.setImageResource(resId);
     }
 
     /** Sets the progress percentage on the progress bar. */
@@ -178,8 +194,10 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
-        return true;
+    public int getPeekHeight() {
+        int toolbarHeight =
+                mContext.getResources().getDimensionPixelSize(R.dimen.toolbar_height_no_shadow);
+        return (int) (toolbarHeight * PEEK_TOOLBAR_HEIGHT_MULTIPLE);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
index bc4e1976..42e576b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/DownloadManagerUiConfig.java
@@ -54,9 +54,6 @@
     /** Whether or not rename feature should be shown in UI. */
     public final boolean isRenameEnabled;
 
-    /** Whether or not section headers should be shown in UI. */
-    public final boolean showSectionHeaders;
-
     /** Constructor. */
     private DownloadManagerUiConfig(Builder builder) {
         isOffTheRecord = builder.mIsOffTheRecord;
@@ -69,7 +66,6 @@
         maxThumbnailScaleFactor = builder.mMaxThumbnailScaleFactor;
         justNowThresholdSeconds = builder.mJustNowThresholdSeconds;
         isRenameEnabled = builder.mIsRenameEnabled;
-        showSectionHeaders = builder.mShowSectionHeaders;
     }
 
     /** Helper class for building a {@link DownloadManagerUiConfig}. */
@@ -89,7 +85,6 @@
         private float mMaxThumbnailScaleFactor = 1.5f; /* hdpi scale factor. */
         private long mJustNowThresholdSeconds;
         private boolean mIsRenameEnabled;
-        private boolean mShowSectionHeaders;
 
         public Builder() {
             readParamsFromFinch();
@@ -140,11 +135,6 @@
             return this;
         }
 
-        public Builder setShowSectionHeaders(boolean showSectionHeaders) {
-            mShowSectionHeaders = showSectionHeaders;
-            return this;
-        }
-
         public DownloadManagerUiConfig build() {
             return new DownloadManagerUiConfig(this);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
index cbb1fe3..824c72a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMediator.java
@@ -31,7 +31,6 @@
 import org.chromium.chrome.browser.download.home.list.DateOrderedListCoordinator.DeleteController;
 import org.chromium.chrome.browser.download.home.metrics.OfflineItemStartupLogger;
 import org.chromium.chrome.browser.download.home.metrics.UmaUtils;
-import org.chromium.chrome.browser.download.home.metrics.UmaUtils.ImagesMenuAction;
 import org.chromium.chrome.browser.download.home.metrics.UmaUtils.ViewAction;
 import org.chromium.chrome.browser.widget.ThumbnailProvider;
 import org.chromium.chrome.browser.widget.ThumbnailProvider.ThumbnailRequest;
@@ -195,15 +194,11 @@
         mModel.getProperties().set(ListProperties.CALLBACK_RESUME, this ::onResumeItem);
         mModel.getProperties().set(ListProperties.CALLBACK_CANCEL, this ::onCancelItem);
         mModel.getProperties().set(ListProperties.CALLBACK_SHARE, this ::onShareItem);
-        mModel.getProperties().set(ListProperties.CALLBACK_SHARE_ALL, this ::onShareItems);
         mModel.getProperties().set(ListProperties.CALLBACK_REMOVE, this ::onDeleteItem);
-        mModel.getProperties().set(ListProperties.CALLBACK_REMOVE_ALL, this ::onDeleteItems);
         mModel.getProperties().set(ListProperties.PROVIDER_VISUALS, this ::getVisuals);
         mModel.getProperties().set(ListProperties.CALLBACK_SELECTION, this ::onSelection);
         mModel.getProperties().set(ListProperties.CALLBACK_RENAME,
                 mUiConfig.isRenameEnabled ? this::onRenameItem : null);
-        mModel.getProperties().set(
-                ListProperties.CALLBACK_START_SELECTION, this ::onStartSelection);
     }
 
     /** Tears down this mediator. */
@@ -285,14 +280,6 @@
         mSelectionDelegate.toggleSelectionForItem(item);
     }
 
-    private void onStartSelection() {
-        // We are hard coding that this is coming from the Photos section, as that is the only
-        // one that supports a section menu.  If that changes we need to support a wider array
-        // of metrics.
-        UmaUtils.recordImagesMenuAction(ImagesMenuAction.MENU_START_SELECTING);
-        mSelectionDelegate.setSelectionModeEnabledForZeroItems(true);
-    }
-
     private void onOpenItem(OfflineItem item) {
         UmaUtils.recordItemAction(ViewAction.OPEN);
         mProvider.openItem(item);
@@ -323,22 +310,6 @@
         shareItemsInternal(CollectionUtil.newHashSet(item));
     }
 
-    private void onShareItems(List<OfflineItem> items) {
-        // We are hard coding that this is coming from the Photos section, as that is the only
-        // one that supports a section menu.  If that changes we need to support a wider array
-        // of metrics.
-        UmaUtils.recordImagesMenuAction(ImagesMenuAction.MENU_SHARE_ALL);
-        shareItemsInternal(items);
-    }
-
-    private void onDeleteItems(List<OfflineItem> items) {
-        // We are hard coding that this is coming from the Photos section, as that is the only
-        // one that supports a section menu.  If that changes we need to support a wider array
-        // of metrics.
-        UmaUtils.recordImagesMenuAction(ImagesMenuAction.MENU_DELETE_ALL);
-        deleteItemsInternal(items);
-    }
-
     private void onRenameItem(OfflineItem item) {
         UmaUtils.recordItemAction(ViewAction.MENU_RENAME);
         mRenameController.rename(item.title, (newName, renameCallback) -> {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
index 6feec725..a917fcd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutator.java
@@ -47,17 +47,9 @@
     private final Map<Date, DateGroup> mDateGroups =
             new TreeMap<>((lhs, rhs) -> { return rhs.compareTo(lhs); });
 
-    // Whether we should hide the section headers and only show the ones that show a new date. Title
-    // and menu button are not present.
-    private final boolean mHideSectionHeaders;
-
     // Whether we shouldn't have any headers. Meant to be used for prefetch tab.
     private boolean mHideAllHeaders;
 
-    // Meant to be used when a non-all chip is selected. Shouldn't have titles, only have dates. Can
-    // have menu button.
-    private boolean mHideTitleFromSectionHeaders;
-
     /**
      * Creates an DateOrderedList instance that will reflect {@code source}.
      * @param source The source of data for this list.
@@ -70,7 +62,6 @@
         mModel = model;
         mConfig = config;
         mJustNowProvider = justNowProvider;
-        mHideSectionHeaders = !mConfig.showSectionHeaders;
         source.addObserver(this);
         onItemsAdded(source.getItems());
     }
@@ -81,7 +72,6 @@
      */
     public void onFilterTypeSelected(@Filters.FilterType int filter) {
         mHideAllHeaders = filter == Filters.FilterType.PREFETCHED;
-        mHideTitleFromSectionHeaders = filter != Filters.FilterType.NONE;
     }
 
     // OfflineItemFilterObserver implementation.
@@ -124,19 +114,14 @@
         } else {
             addOrUpdateItemToDateGroups(item);
 
-            int sectionHeaderIndex = -1;
             for (int i = 0; i < mModel.size(); i++) {
                 ListItem listItem = mModel.get(i);
-                if (listItem instanceof SectionHeaderListItem) sectionHeaderIndex = i;
                 if (!(listItem instanceof OfflineItemListItem)) continue;
 
                 OfflineItemListItem existingItem = (OfflineItemListItem) listItem;
                 if (item.id.equals(existingItem.item.id)) {
                     existingItem.item = item;
                     mModel.update(i, existingItem);
-                    if (oldItem.state != item.state) {
-                        updateSectionHeader(sectionHeaderIndex, i);
-                    }
                     break;
                 }
             }
@@ -155,16 +140,6 @@
         dateGroup.addOrUpdateItem(item);
     }
 
-    private void updateSectionHeader(int sectionHeaderIndex, int offlineItemIndex) {
-        if (sectionHeaderIndex < 0 || mHideSectionHeaders) return;
-
-        SectionHeaderListItem sectionHeader =
-                (SectionHeaderListItem) mModel.get(sectionHeaderIndex);
-        OfflineItem offlineItem = ((OfflineItemListItem) mModel.get(offlineItemIndex)).item;
-        sectionHeader.items.set(offlineItemIndex - sectionHeaderIndex - 1, offlineItem);
-        mModel.update(sectionHeaderIndex, sectionHeader);
-    }
-
     // Flattens out the hierarchical data and adds items to the model in the order they should be
     // displayed. Date headers and section headers are added wherever necessary. The existing items
     // in the model are replaced by the new set of items computed.
@@ -180,16 +155,10 @@
                 Section section = dateGroup.sections.get(filter);
 
                 // Add a section header.
-                if (!mHideAllHeaders && (!mHideSectionHeaders || sectionIndex == 0)) {
-                    SectionHeaderListItem sectionHeaderItem = new SectionHeaderListItem(filter,
-                            date.getTime(), sectionIndex == 0 /* showDate */,
-                            date.equals(JUST_NOW_DATE) /* isJustNow */,
+                if (!mHideAllHeaders && sectionIndex == 0) {
+                    SectionHeaderListItem sectionHeaderItem = new SectionHeaderListItem(
+                            date.getTime(), date.equals(JUST_NOW_DATE) /* isJustNow */,
                             sectionIndex == 0 && dateIndex > 0 /* showDivider */);
-                    if (!mHideSectionHeaders) {
-                        sectionHeaderItem.showTitle = !mHideTitleFromSectionHeaders;
-                        sectionHeaderItem.showMenu = filter == OfflineItemFilter.IMAGE;
-                        sectionHeaderItem.items = new ArrayList<>(section.items);
-                    }
                     listItems.add(sectionHeaderItem);
                 }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
index 9aa4dde..0c6ed1c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListItem.java
@@ -10,14 +10,10 @@
 import org.chromium.chrome.browser.download.home.StableIds;
 import org.chromium.components.offline_items_collection.OfflineItem;
 
-import java.util.Calendar;
 import java.util.Date;
-import java.util.List;
 
 /** An abstract class that represents a variety of possible list items to show in downloads home. */
 public abstract class ListItem {
-    private static final long DATE_SEPARATOR_HASH_CODE_OFFSET = 10;
-    private static final long SECTION_SEPARATOR_HASH_CODE_OFFSET = 100;
     private static final long SECTION_HEADER_HASH_CODE_OFFSET = 1000;
 
     public final long stableId;
@@ -56,51 +52,27 @@
             super(stableId);
             this.date = date;
         }
-
-        /**
-         * Creates a {@link DateListItem} instance around a particular calendar day.  This will
-         * automatically generate the {@link ListItem#stableId} from {@code calendar}.
-         * @param calendar
-         */
-        public DateListItem(Calendar calendar) {
-            this(generateStableIdForDayOfYear(calendar), calendar.getTime());
-        }
-
-        @VisibleForTesting
-        static long generateStableIdForDayOfYear(Calendar calendar) {
-            return (calendar.get(Calendar.YEAR) << 16) + calendar.get(Calendar.DAY_OF_YEAR);
-        }
     }
 
     /** A {@link ListItem} representing a section header. */
     public static class SectionHeaderListItem extends DateListItem {
-        public final int filter;
-        public boolean showDate;
-        public boolean showTitle;
-        public boolean showMenu;
         public boolean isJustNow;
         public boolean showDivider;
-        public List<OfflineItem> items;
 
         /**
-         * Creates a {@link SectionHeaderListItem} instance for a given {@code filter} and
-         * {@code timestamp}.
+         * Creates a {@link SectionHeaderListItem} instance for a given {@code timestamp}.
          */
-        public SectionHeaderListItem(int filter, long timestamp, boolean showDate,
-                boolean isJustNow, boolean showDivider) {
-            super(isJustNow && showDate ? StableIds.JUST_NOW_SECTION
-                                        : generateStableId(timestamp, filter),
+        public SectionHeaderListItem(long timestamp, boolean isJustNow, boolean showDivider) {
+            super(isJustNow ? StableIds.JUST_NOW_SECTION : generateStableId(timestamp),
                     new Date(timestamp));
-            this.filter = filter;
-            this.showDate = showDate;
             this.isJustNow = isJustNow;
             this.showDivider = showDivider;
         }
 
         @VisibleForTesting
-        static long generateStableId(long timestamp, int filter) {
+        static long generateStableId(long timestamp) {
             long hash = new Date(timestamp).hashCode();
-            return hash + filter + SECTION_HEADER_HASH_CODE_OFFSET;
+            return hash + SECTION_HEADER_HASH_CODE_OFFSET;
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java
index 7ac8b30..83f62941 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/ListProperties.java
@@ -12,8 +12,6 @@
 import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
 import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
 
-import java.util.List;
-
 /**
  * The properties required to build a {@link ListItem} which contain two types of properties for the
  * download manager: (1) A set of properties that act directly on the list view itself. (2) A set of
@@ -57,18 +55,10 @@
     WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_SHARE =
             new WritableObjectPropertyKey<>();
 
-    /** The callback for when a UI action should share all selected {@link OfflineItem}s. */
-    WritableObjectPropertyKey < Callback < List<OfflineItem>>> CALLBACK_SHARE_ALL =
-            new WritableObjectPropertyKey<>();
-
     /** The callback for when a UI action should remove a {@link OfflineItem}. */
     WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_REMOVE =
             new WritableObjectPropertyKey<>();
 
-    /** The callback for when a UI action should remove all selected {@link OfflineItem}s. */
-    WritableObjectPropertyKey < Callback < List<OfflineItem>>> CALLBACK_REMOVE_ALL =
-            new WritableObjectPropertyKey<>();
-
     /** The callback for when a UI action should rename a {@link OfflineItem}. */
     WritableObjectPropertyKey<Callback<OfflineItem>> CALLBACK_RENAME =
             new WritableObjectPropertyKey<>();
@@ -83,16 +73,7 @@
     /** Whether or not selection mode is currently active. */
     WritableBooleanPropertyKey SELECTION_MODE_ACTIVE = new WritableBooleanPropertyKey();
 
-    /**
-     * The callback to trigger when a UI action starts general selection mode.  This is different
-     * from {@link #CALLBACK_SELECTION} in that it should be triggered when the UI enters selection
-     * mode without any particularly attached {@link ListItem}.
-     */
-    WritableObjectPropertyKey<Runnable> CALLBACK_START_SELECTION =
-            new WritableObjectPropertyKey<>();
-
     PropertyKey[] ALL_KEYS = new PropertyKey[] {ENABLE_ITEM_ANIMATIONS, CALLBACK_OPEN,
-            CALLBACK_PAUSE, CALLBACK_RESUME, CALLBACK_CANCEL, CALLBACK_SHARE, CALLBACK_SHARE_ALL,
-            CALLBACK_REMOVE, CALLBACK_REMOVE_ALL, CALLBACK_RENAME, PROVIDER_VISUALS,
-            CALLBACK_SELECTION, SELECTION_MODE_ACTIVE, CALLBACK_START_SELECTION};
+            CALLBACK_PAUSE, CALLBACK_RESUME, CALLBACK_CANCEL, CALLBACK_SHARE, CALLBACK_REMOVE,
+            CALLBACK_RENAME, PROVIDER_VISUALS, CALLBACK_SELECTION, SELECTION_MODE_ACTIVE};
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
index 0a65012..9309d8df 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/list/holder/SectionTitleViewHolder.java
@@ -4,8 +4,6 @@
 
 package org.chromium.chrome.browser.download.home.list.holder;
 
-import android.content.Context;
-import android.content.res.Resources;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -13,38 +11,17 @@
 
 import org.chromium.chrome.browser.download.home.list.ListItem;
 import org.chromium.chrome.browser.download.home.list.ListItem.SectionHeaderListItem;
-import org.chromium.chrome.browser.download.home.list.ListProperties;
-import org.chromium.chrome.browser.download.home.list.ListUtils;
 import org.chromium.chrome.browser.download.home.list.UiUtils;
-import org.chromium.chrome.browser.ui.widget.ListMenuButton;
 import org.chromium.chrome.download.R;
-import org.chromium.components.offline_items_collection.OfflineItem;
-import org.chromium.components.offline_items_collection.OfflineItemState;
 import org.chromium.ui.modelutil.PropertyModel;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
 /**
  * A {@link ViewHolder} specifically meant to display a section header.
  */
-public class SectionTitleViewHolder extends ListItemViewHolder implements ListMenuButton.Delegate {
+public class SectionTitleViewHolder extends ListItemViewHolder {
     private final View mDivider;
     private final TextView mDate;
-    private final TextView mTitle;
-    private final ListMenuButton mMore;
-    private final View mTopSpace;
-    private final View mBottomSpace;
 
-    private Runnable mShareCallback;
-    private Runnable mDeleteCallback;
-    private Runnable mShareAllCallback;
-    private Runnable mDeleteAllCallback;
-    private Runnable mSelectCallback;
-
-    private boolean mHasMultipleItems;
-    private boolean mCanSelectItems;
 
     /** Create a new {@link SectionTitleViewHolder} instance. */
     public static SectionTitleViewHolder create(ViewGroup parent) {
@@ -57,110 +34,17 @@
         super(view);
         mDivider = view.findViewById(R.id.divider);
         mDate = (TextView) view.findViewById(R.id.date);
-        mTitle = (TextView) view.findViewById(R.id.title);
-        mMore = (ListMenuButton) view.findViewById(R.id.more);
-        mTopSpace = view.findViewById(R.id.top_space);
-        mBottomSpace = view.findViewById(R.id.bottom_space);
-        if (mMore != null) mMore.setDelegate(this);
     }
 
     // ListItemViewHolder implementation.
     @Override
     public void bind(PropertyModel properties, ListItem item) {
         SectionHeaderListItem sectionItem = (SectionHeaderListItem) item;
-        mTitle.setText(ListUtils.getTextForSection(sectionItem.filter));
 
-        if (sectionItem.showDate) {
-            mDate.setText(sectionItem.isJustNow ? itemView.getContext().getResources().getString(
-                                                          R.string.download_manager_just_now)
-                                                : UiUtils.dateToHeaderString(sectionItem.date));
-        }
+        mDate.setText(sectionItem.isJustNow ? itemView.getContext().getResources().getString(
+                              R.string.download_manager_just_now)
+                                            : UiUtils.dateToHeaderString(sectionItem.date));
 
-        updateTopBottomSpacing(sectionItem.showMenu);
         mDivider.setVisibility(sectionItem.showDivider ? ViewGroup.VISIBLE : ViewGroup.GONE);
-        mDate.setVisibility(sectionItem.showDate ? View.VISIBLE : View.GONE);
-        mTitle.setVisibility((sectionItem.showTitle ? View.VISIBLE : View.GONE));
-        if (mMore != null) mMore.setVisibility(sectionItem.showMenu ? View.VISIBLE : View.GONE);
-
-        if (sectionItem.items != null) {
-            mHasMultipleItems = sectionItem.items.size() > 1;
-            mCanSelectItems = !getCompletedItems(sectionItem.items).isEmpty();
-        }
-
-        if (sectionItem.showMenu && mMore != null) {
-            assert sectionItem.items.size() > 0;
-            mShareCallback = ()
-                    -> properties.get(ListProperties.CALLBACK_SHARE)
-                               .onResult(sectionItem.items.get(0));
-            mDeleteCallback = ()
-                    -> properties.get(ListProperties.CALLBACK_REMOVE)
-                               .onResult(sectionItem.items.get(0));
-
-            mShareAllCallback = ()
-                    -> properties.get(ListProperties.CALLBACK_SHARE_ALL)
-                               .onResult(getCompletedItems(sectionItem.items));
-            mDeleteAllCallback = ()
-                    -> properties.get(ListProperties.CALLBACK_REMOVE_ALL)
-                               .onResult(sectionItem.items);
-            mSelectCallback = properties.get(ListProperties.CALLBACK_START_SELECTION);
-
-            mMore.setClickable(!properties.get(ListProperties.SELECTION_MODE_ACTIVE));
-        }
-    }
-
-    @Override
-    public ListMenuButton.Item[] getItems() {
-        Context context = itemView.getContext();
-        if (mHasMultipleItems) {
-            return new ListMenuButton.Item[] {
-                    new ListMenuButton.Item(context, R.string.select, mCanSelectItems),
-                    new ListMenuButton.Item(context, R.string.share_group, mCanSelectItems),
-                    new ListMenuButton.Item(context, R.string.delete_group, true)};
-        } else {
-            return new ListMenuButton.Item[] {
-                    new ListMenuButton.Item(context, R.string.share, mCanSelectItems),
-                    new ListMenuButton.Item(context, R.string.delete, true)};
-        }
-    }
-
-    @Override
-    public void onItemSelected(ListMenuButton.Item item) {
-        if (item.getTextId() == R.string.select) {
-            mSelectCallback.run();
-        } else if (item.getTextId() == R.string.share) {
-            mShareCallback.run();
-        } else if (item.getTextId() == R.string.delete) {
-            mDeleteCallback.run();
-        } else if (item.getTextId() == R.string.share_group) {
-            mShareAllCallback.run();
-        } else if (item.getTextId() == R.string.delete_group) {
-            mDeleteAllCallback.run();
-        }
-    }
-
-    private void updateTopBottomSpacing(boolean showMenu) {
-        Resources resources = itemView.getContext().getResources();
-        ViewGroup.LayoutParams topSpaceParams = mTopSpace.getLayoutParams();
-        ViewGroup.LayoutParams bottomSpaceParams = mBottomSpace.getLayoutParams();
-
-        topSpaceParams.height = resources.getDimensionPixelSize(showMenu
-                        ? R.dimen.download_manager_section_title_padding_image
-                        : R.dimen.download_manager_section_title_padding_top);
-        bottomSpaceParams.height = resources.getDimensionPixelSize(showMenu
-                        ? R.dimen.download_manager_section_title_padding_image
-                        : R.dimen.download_manager_section_title_padding_bottom);
-
-        mTopSpace.setLayoutParams(topSpaceParams);
-        mBottomSpace.setLayoutParams(bottomSpaceParams);
-    }
-
-    private static List<OfflineItem> getCompletedItems(Collection<OfflineItem> items) {
-        List<OfflineItem> completedItems = new ArrayList<>();
-        for (OfflineItem item : items) {
-            if (item.state != OfflineItemState.COMPLETE) continue;
-            completedItems.add(item);
-        }
-
-        return completedItems;
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/download/home/metrics/UmaUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/download/home/metrics/UmaUtils.java
index 46330b2b..ac9f5b9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/download/home/metrics/UmaUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/download/home/metrics/UmaUtils.java
@@ -56,18 +56,6 @@
     }
 
     // Please treat this list as append only and keep it in sync with
-    // Android.DownloadManager.List.Section.Menu.Actions in enums.xml.
-    @IntDef({ImagesMenuAction.MENU_START_SELECTING, ImagesMenuAction.MENU_SHARE_ALL,
-            ImagesMenuAction.MENU_DELETE_ALL})
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ImagesMenuAction {
-        int MENU_START_SELECTING = 0;
-        int MENU_SHARE_ALL = 1;
-        int MENU_DELETE_ALL = 2;
-        int NUM_ENTRIES = 3;
-    }
-
-    // Please treat this list as append only and keep it in sync with
     // Android.Download.Rename.Dialog.Action in enums.xml.
     @IntDef({RenameDialogAction.RENAME_DIALOG_CONFIRM, RenameDialogAction.RENAME_DIALOG_CANCEL,
             RenameDialogAction.RENAME_DIALOG_OTHER,
@@ -86,34 +74,6 @@
     }
 
     /**
-     * Called to record metrics for the given images section menu action.
-     * @param action The given menu action.
-     */
-    public static void recordImagesMenuAction(@ImagesMenuAction int action) {
-        String userActionSuffix;
-        switch (action) {
-            case ImagesMenuAction.MENU_START_SELECTING:
-                userActionSuffix = "StartSelecting";
-                break;
-            case ImagesMenuAction.MENU_SHARE_ALL:
-                userActionSuffix = "ShareAll";
-                break;
-            case ImagesMenuAction.MENU_DELETE_ALL:
-                userActionSuffix = "DeleteAll";
-                break;
-            default:
-                assert false : "Unexpected action " + action + " passed to recordImagesMenuAction.";
-                return;
-        }
-
-        RecordHistogram.recordEnumeratedHistogram(
-                "Android.DownloadManager.List.Section.Menu.Images.Action", action,
-                ImagesMenuAction.NUM_ENTRIES);
-        RecordUserAction.record(
-                "Android.DownloadManager.List.Selection.Menu.Images.Action." + userActionSuffix);
-    }
-
-    /**
      * Called to record metrics for the given list item action.
      * @param action The given list item action.
      */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegate.java
index 196e101..7499b473 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegate.java
@@ -48,13 +48,6 @@
     int countSpecializedHandlers(List<ResolveInfo> infos);
 
     /**
-     * Returns the package name of the first valid WebAPK in {@link infos}.
-     * @param infos ResolveInfos to search.
-     * @return The package name of the first valid WebAPK. Null if no valid WebAPK was found.
-     */
-    String findFirstWebApkPackageName(List<ResolveInfo> infos);
-
-    /**
      * Start an activity for the intent. Used for intents that must be handled externally.
      * @param intent The intent we want to send.
      * @param proxy Whether we need to proxy the intent through AuthenticatedProxyActivity (this is
@@ -163,4 +156,10 @@
      * @return Whether the Intent points to an app that we trust and that launched Chrome.
      */
     boolean isIntentForTrustedCallingApp(Intent intent);
+
+    /**
+     * @param packageName The package to check.
+     * @return Whether the package is a valid WebAPK package.
+     */
+    boolean isValidWebApk(String packageName);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
index 2d56240..5b33682f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
@@ -299,11 +299,6 @@
     }
 
     @Override
-    public String findFirstWebApkPackageName(List<ResolveInfo> infos) {
-        return WebApkValidator.findFirstWebApkPackage(mApplicationContext, infos);
-    }
-
-    @Override
     public void startActivity(Intent intent, boolean proxy) {
         try {
             forcePdfViewerAsIntentHandlerIfNeeded(intent);
@@ -644,4 +639,9 @@
     public boolean isIntentForTrustedCallingApp(Intent intent) {
         return false;
     }
+
+    @Override
+    public boolean isValidWebApk(String packageName) {
+        return WebApkValidator.isValidWebApk(ContextUtils.getApplicationContext(), packageName);
+    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java
index 1b73c28c..1d534d1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandler.java
@@ -9,6 +9,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.SystemClock;
@@ -44,6 +45,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 
@@ -74,21 +76,6 @@
     @VisibleForTesting
     static final String EXTRA_MARKET_REFERRER = "market_referrer";
 
-    @IntDef({WebApkLaunchDecision.LAUNCHED, WebApkLaunchDecision.LAUNCH_FAILED,
-            WebApkLaunchDecision.ALREADY_IN_WEBAPK,
-            WebApkLaunchDecision.WEBAPK_NOT_SOLE_INTENT_HANDLER})
-    public @interface WebApkLaunchDecision {
-        int LAUNCHED = 0;
-        int LAUNCH_FAILED = 1;
-
-        // User is either in target WebAPK or in CCT launched by the target WebAPK.
-        int ALREADY_IN_WEBAPK = 2;
-
-        // The WebAPK either cannot handle intent or there are multiple non-browser apps which
-        // can handle the intent.
-        int WEBAPK_NOT_SOLE_INTENT_HANDLER = 3;
-    }
-
     // These values are persisted in histograms. Please do not renumber. Append only.
     @IntDef({AiaIntent.FALLBACK_USED, AiaIntent.SERP, AiaIntent.OTHER})
     @Retention(RetentionPolicy.SOURCE)
@@ -186,18 +173,34 @@
                 && (params.getRedirectHandler() == null
                         // For instance, if this is a chained fallback URL, we ignore it.
                         || !params.getRedirectHandler().shouldNotOverrideUrlLoading())) {
-            if (InstantAppsHandler.isIntentToInstantApp(targetIntent)) {
-                RecordHistogram.recordEnumeratedHistogram(
-                        "Android.InstantApps.DirectInstantAppsIntent", AiaIntent.FALLBACK_USED,
-                        AiaIntent.NUM_ENTRIES);
-            }
-
-            result = clobberCurrentTabWithFallbackUrl(browserFallbackUrl, params);
+            result = handleFallbackUrl(params, targetIntent, browserFallbackUrl);
         }
         if (DEBUG) printDebugShouldOverrideUrlLoadingResult(result);
         return result;
     }
 
+    private @OverrideUrlLoadingResult int handleFallbackUrl(
+            ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) {
+        if (InstantAppsHandler.isIntentToInstantApp(targetIntent)) {
+            RecordHistogram.recordEnumeratedHistogram("Android.InstantApps.DirectInstantAppsIntent",
+                    AiaIntent.FALLBACK_USED, AiaIntent.NUM_ENTRIES);
+        }
+        // Launch WebAPK if it can handle the URL.
+        try {
+            Intent intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME);
+            sanitizeQueryIntentActivitiesIntent(intent);
+            List<ResolveInfo> resolvingInfos = mDelegate.queryIntentActivities(intent);
+            if (!shouldStayInWebApkCCT(params, resolvingInfos)
+                    && !isAlreadyInTargetWebApk(resolvingInfos, params)
+                    && launchWebApkIfSoleIntentHandler(resolvingInfos, intent)) {
+                return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
+            }
+        } catch (Exception e) {
+            if (DEBUG) Log.i(TAG, "Could not parse fallback url as intent");
+        }
+        return clobberCurrentTabWithFallbackUrl(browserFallbackUrl, params);
+    }
+
     private void printDebugShouldOverrideUrlLoadingResult(int result) {
         String resultString;
         switch (result) {
@@ -475,46 +478,14 @@
      */
     private int handleUnresolvableIntent(
             ExternalNavigationParams params, Intent targetIntent, String browserFallbackUrl) {
-        if (browserFallbackUrl != null) {
-            return handleFallbackUrl(params, targetIntent, browserFallbackUrl);
-        }
+        // Fallback URL will be handled by the caller of shouldOverrideUrlLoadingInternal.
+        if (browserFallbackUrl != null) return OverrideUrlLoadingResult.NO_OVERRIDE;
         if (targetIntent.getPackage() != null) return handleWithMarketIntent(params, targetIntent);
 
         if (DEBUG) Log.i(TAG, "Could not find an external activity to use");
         return OverrideUrlLoadingResult.NO_OVERRIDE;
     }
 
-    private @OverrideUrlLoadingResult int handleFallbackUrl(
-            ExternalNavigationParams params, Intent intent, String browserFallbackUrl) {
-        // Launch WebAPK if it can handle the URL.
-        if (!TextUtils.isEmpty(intent.getPackage())
-                || (intent.getSelector() != null
-                        && !TextUtils.isEmpty(intent.getSelector().getPackage()))) {
-            try {
-                intent = Intent.parseUri(browserFallbackUrl, Intent.URI_INTENT_SCHEME);
-            } catch (Exception e) {
-                if (DEBUG) Log.i(TAG, "Could not parse fallback url");
-                return OverrideUrlLoadingResult.NO_OVERRIDE;
-            }
-            sanitizeQueryIntentActivitiesIntent(intent);
-            List<ResolveInfo> resolvingInfos = mDelegate.queryIntentActivities(intent);
-            switch (launchWebApkIfSoleIntentHandler(params, resolvingInfos, intent)) {
-                case WebApkLaunchDecision.ALREADY_IN_WEBAPK:
-                    if (DEBUG) Log.i(TAG, "Already in WebAPK");
-                    return OverrideUrlLoadingResult.NO_OVERRIDE;
-                case WebApkLaunchDecision.LAUNCH_FAILED:
-                    if (DEBUG) Log.i(TAG, "WebAPK launch failed");
-                    return OverrideUrlLoadingResult.NO_OVERRIDE;
-                case WebApkLaunchDecision.LAUNCHED:
-                    if (DEBUG) Log.i(TAG, "Launched WebAPK");
-                    return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
-                case WebApkLaunchDecision.WEBAPK_NOT_SOLE_INTENT_HANDLER:
-                    break;
-            }
-        }
-        return clobberCurrentTabWithFallbackUrl(browserFallbackUrl, params);
-    }
-
     private @OverrideUrlLoadingResult int handleWithMarketIntent(
             ExternalNavigationParams params, Intent intent) {
         String marketReferrer = IntentUtils.safeGetStringExtra(intent, EXTRA_MARKET_REFERRER);
@@ -698,6 +669,60 @@
         return OverrideUrlLoadingResult.NO_OVERRIDE;
     }
 
+    /**
+     * If some third-party app launched Chrome with an intent, and the URL got redirected, and the
+     * user explicitly chose Chrome over other intent handlers, stay in Chrome unless there was a
+     * new intent handler after redirection or Chrome cannot handle it any more.
+     * Custom tabs are an exception to this rule, since at no point, the user sees an intent picker
+     * and "picking Chrome" is handled inside the support library.
+     */
+    private boolean shouldKeepIntentRedirectInChrome(ExternalNavigationParams params,
+            boolean incomingIntentRedirect, Intent targetIntent, boolean isExternalProtocol) {
+        if (params.getRedirectHandler() != null && incomingIntentRedirect && !isExternalProtocol
+                && !params.getRedirectHandler().isFromCustomTabIntent()
+                && !params.getRedirectHandler().hasNewResolver(targetIntent)) {
+            if (DEBUG) Log.i(TAG, "Custom tab redirect no handled");
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether the activity belongs to a WebAPK and the URL is within the scope of the
+     * WebAPK. The WebAPK's main activity is a bouncer that redirects to WebApkActivity in Chrome.
+     * In order to avoid bouncing indefinitely, we should not override the navigation if we are
+     * currently showing the WebAPK (params#nativeClientPackageName()) that we will redirect to.
+     */
+    private boolean isAlreadyInTargetWebApk(
+            List<ResolveInfo> resolveInfos, ExternalNavigationParams params) {
+        String currentName = params.nativeClientPackageName();
+        if (currentName == null) return false;
+        for (ResolveInfo resolveInfo : resolveInfos) {
+            ActivityInfo info = resolveInfo.activityInfo;
+            if (info != null && currentName.equals(info.packageName)) {
+                if (DEBUG) Log.i(TAG, "Already in WebAPK");
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean launchExternalIntent(Intent targetIntent, boolean shouldProxyForInstantApps) {
+        try {
+            if (!mDelegate.startActivityIfNeeded(targetIntent, shouldProxyForInstantApps)) {
+                if (DEBUG) Log.i(TAG, "The current Activity was the only targeted Activity.");
+                return false;
+            }
+        } catch (ActivityNotFoundException e) {
+            // The targeted app must have been uninstalled/disabled since we queried for Activities
+            // to handle this intent.
+            if (DEBUG) Log.i(TAG, "Activity not found.");
+            return false;
+        }
+        if (DEBUG) Log.i(TAG, "startActivityIfNeeded");
+        return true;
+    }
+
     private @OverrideUrlLoadingResult int shouldOverrideUrlLoadingInternal(
             ExternalNavigationParams params, Intent targetIntent,
             @Nullable String browserFallbackUrl) {
@@ -814,45 +839,22 @@
                     targetIntent, params, browserFallbackUrl, shouldProxyForInstantApps);
         }
 
-        // Some third-party app launched Chrome with an intent, and the URL got redirected. The
-        // user has explicitly chosen Chrome over other intent handlers, so stay in Chrome
-        // unless there was a new intent handler after redirection or Chrome cannot handle it
-        // any more.
-        // Custom tabs are an exception to this rule, since at no point, the user sees an intent
-        // picker and "picking Chrome" is handled inside the support library.
-        if (params.getRedirectHandler() != null && incomingIntentRedirect) {
-            if (!isExternalProtocol && !params.getRedirectHandler().isFromCustomTabIntent()
-                    && !params.getRedirectHandler().hasNewResolver(targetIntent)) {
-                if (DEBUG) Log.i(TAG, "Custom tab redirect no handled");
-                return OverrideUrlLoadingResult.NO_OVERRIDE;
-            }
-        }
-
-        switch (launchWebApkIfSoleIntentHandler(params, resolvingInfos, targetIntent)) {
-            case WebApkLaunchDecision.ALREADY_IN_WEBAPK:
-                if (DEBUG) Log.i(TAG, "Already in WebAPK");
-                return OverrideUrlLoadingResult.NO_OVERRIDE;
-            case WebApkLaunchDecision.LAUNCH_FAILED:
-                if (DEBUG) Log.i(TAG, "WebAPK launch failed");
-                return OverrideUrlLoadingResult.NO_OVERRIDE;
-            case WebApkLaunchDecision.LAUNCHED:
-                if (DEBUG) Log.i(TAG, "Launched WebAPK");
-                return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
-            case WebApkLaunchDecision.WEBAPK_NOT_SOLE_INTENT_HANDLER:
-                break;
-        }
-
-        try {
-            if (mDelegate.startActivityIfNeeded(targetIntent, shouldProxyForInstantApps)) {
-                // Assume the browser can handle it if there's no activity for this intent.
-                if (DEBUG) Log.i(TAG, "startActivityIfNeeded");
-                return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
-            }
-        } catch (ActivityNotFoundException e) {
-            if (DEBUG) Log.i(TAG, "Activity not found.");
+        if (shouldKeepIntentRedirectInChrome(
+                    params, incomingIntentRedirect, targetIntent, isExternalProtocol)) {
             return OverrideUrlLoadingResult.NO_OVERRIDE;
         }
 
+        if (shouldStayInWebApkCCT(params, resolvingInfos)) {
+            return OverrideUrlLoadingResult.NO_OVERRIDE;
+        }
+        if (isAlreadyInTargetWebApk(resolvingInfos, params)) {
+            return OverrideUrlLoadingResult.NO_OVERRIDE;
+        } else if (launchWebApkIfSoleIntentHandler(resolvingInfos, targetIntent)) {
+            return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
+        }
+        if (launchExternalIntent(targetIntent, shouldProxyForInstantApps)) {
+            return OverrideUrlLoadingResult.OVERRIDE_WITH_EXTERNAL_INTENT;
+        }
         return OverrideUrlLoadingResult.NO_OVERRIDE;
     }
 
@@ -1022,50 +1024,36 @@
                 tab.getActivity().getIntent(), Browser.EXTRA_APPLICATION_ID);
         if (appId == null) return false;
 
-        try {
-            Intent.parseUri(params.getUrl(), Intent.URI_INTENT_SCHEME);
-        } catch (URISyntaxException ex) {
-            return false;
-        }
-        return !ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(handlers, appId)
+        boolean webApkHasSpecializedHandler =
+                ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(handlers, appId)
                         .isEmpty();
+        if (webApkHasSpecializedHandler) return false;
+        if (DEBUG) Log.i(TAG, "Staying in WebApk CCT.");
+        return true;
     }
 
     /**
      * Launches WebAPK if the WebAPK is the sole non-browser handler for the given intent.
-     * Returns whether a WebAPK was launched and if it was not launched returns why.
+     * @return Whether a WebAPK was launched.
      */
-    private @WebApkLaunchDecision int launchWebApkIfSoleIntentHandler(
-            ExternalNavigationParams params, List<ResolveInfo> resolvingInfos, Intent intent) {
-        if (shouldStayInWebApkCCT(params, resolvingInfos)) {
-            return WebApkLaunchDecision.ALREADY_IN_WEBAPK;
-        }
-
-        String targetWebApkPackageName = mDelegate.findFirstWebApkPackageName(resolvingInfos);
-
-        // We can't rely on this falling through to startActivityIfNeeded and behaving
-        // correctly for WebAPKs. This is because the target of the intent is the WebApk's main
-        // activity but that's just a bouncer which will redirect to WebApkActivity in chrome.
-        // To avoid bouncing indefinitely, don't override the navigation if we are currently
-        // showing the WebApk |params.webApkPackageName()| that we will redirect to.
-        if (targetWebApkPackageName != null
-                && targetWebApkPackageName.equals(params.nativeClientPackageName())) {
-            return WebApkLaunchDecision.ALREADY_IN_WEBAPK;
-        }
-
-        if (targetWebApkPackageName == null
-                || mDelegate.countSpecializedHandlers(resolvingInfos) != 1) {
-            return WebApkLaunchDecision.WEBAPK_NOT_SOLE_INTENT_HANDLER;
-        }
-
-        intent.setPackage(targetWebApkPackageName);
+    private boolean launchWebApkIfSoleIntentHandler(
+            List<ResolveInfo> resolvingInfos, Intent targetIntent) {
+        ArrayList<String> packages =
+                ExternalNavigationDelegateImpl.getSpecializedHandlersWithFilter(
+                        resolvingInfos, null);
+        if (packages.size() != 1 || !mDelegate.isValidWebApk(packages.get(0))) return false;
+        Intent webApkIntent = new Intent(targetIntent);
+        webApkIntent.setPackage(packages.get(0));
         try {
-            if (mDelegate.startActivityIfNeeded(intent, false)) {
-                return WebApkLaunchDecision.LAUNCHED;
-            }
+            mDelegate.startActivity(webApkIntent, false);
+            if (DEBUG) Log.i(TAG, "Launched WebAPK");
+            return true;
         } catch (ActivityNotFoundException e) {
+            // The WebApk must have been uninstalled/disabled since we queried for Activities to
+            // handle this intent.
+            if (DEBUG) Log.i(TAG, "WebAPK launch failed");
+            return false;
         }
-        return WebApkLaunchDecision.LAUNCH_FAILED;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java
index 6c68a6b6..439013d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/NavigationSheetCoordinator.java
@@ -293,11 +293,13 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
+    public int getPeekHeight() {
         // Makes peek state as 'not present' when bottom sheet is in expanded state (i.e. animating
         // from expanded to close state). It avoids the sheet animating in two distinct steps, which
         // looks awkward.
-        return !mBottomSheetController.get().getBottomSheet().isSheetOpen();
+        return !mBottomSheetController.get().getBottomSheet().isSheetOpen()
+                ? getSizePx(mParentView.getContext(), R.dimen.navigation_sheet_peek_height)
+                : BottomSheet.HeightMode.DISABLED;
     }
 
     @Override
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 d7405795..f9f4bf7 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
@@ -12,6 +12,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.WebContentsFactory;
 import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
@@ -194,10 +195,14 @@
     }
 
     private static String getUrlFromIntent(Intent intent) {
-        if (Intent.ACTION_VIEW.equals(intent.getAction())
-                || Intent.ACTION_MAIN.equals(intent.getAction())) {
+        String action = intent.getAction();
+        if (Intent.ACTION_VIEW.equals(action) || Intent.ACTION_MAIN.equals(action)
+                || (action == null
+                        && ChromeTabbedActivity.MAIN_LAUNCHER_ACTIVITY_NAME.equals(
+                                intent.getComponent().getClassName()))) {
             // TODO(alexclarke): For ACTION_MAIN maybe refactor TabPersistentStore so we can
-            // instantiate (a subset of that) here to extract the URL.
+            // instantiate (a subset of that) here to extract the URL if it's not set in the
+            // intent.
             return IntentHandler.getUrlFromIntent(intent);
         } else {
             return null;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerView.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerView.java
index df9632d..3bc9c19b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/handler/PaymentHandlerView.java
@@ -52,8 +52,8 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
-        return false;
+    public int getPeekHeight() {
+        return BottomSheet.HeightMode.DISABLED;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/micro/MicrotransactionView.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/micro/MicrotransactionView.java
index c83f1a7..799e7b02 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/micro/MicrotransactionView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/micro/MicrotransactionView.java
@@ -15,6 +15,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.HeightMode;
 
 /** Microtransaction UI. */
 /* package */ class MicrotransactionView implements BottomSheetContent {
@@ -113,8 +114,8 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
-        return mIsPeekStateEnabled;
+    public int getPeekHeight() {
+        return mIsPeekStateEnabled ? HeightMode.DEFAULT : HeightMode.DISABLED;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/DevicePickerBottomSheetContent.java b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/DevicePickerBottomSheetContent.java
index 9829c66..35abb8c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/DevicePickerBottomSheetContent.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/send_tab_to_self/DevicePickerBottomSheetContent.java
@@ -105,9 +105,9 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
+    public int getPeekHeight() {
         // Return false to ensure that the entire bottom sheet is shown.
-        return false;
+        return BottomSheet.HeightMode.DISABLED;
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
index c988892..92948d0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/util/FeatureUtilities.java
@@ -74,7 +74,6 @@
     private static Map<String, Boolean> sFlags = new HashMap<>();
     private static Boolean sHasGoogleAccountAuthenticator;
     private static Boolean sHasRecognitionIntentHandler;
-    private static Boolean sIsTabGroupsAndroidEnabled;
     private static Boolean sIsTabToGtsAnimationEnabled;
     private static String sReachedCodeProfilerTrialGroup;
 
@@ -480,22 +479,15 @@
                                 || ChromeFeatureList.isEnabled(
                                         ChromeFeatureList.TAB_GROUPS_ANDROID))
                         && TabManagementModuleProvider.getDelegate() != null
-                        && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID));
+                        && ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_GROUPS_ANDROID)
+                        && isHighEndPhone());
     }
 
     /**
      * @return Whether the tab group feature is enabled and available for use.
      */
     public static boolean isTabGroupsAndroidEnabled() {
-        if (sIsTabGroupsAndroidEnabled == null) {
-            ChromePreferenceManager preferenceManager = ChromePreferenceManager.getInstance();
-
-            sIsTabGroupsAndroidEnabled = preferenceManager.readBoolean(
-                    ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY, false);
-            sIsTabGroupsAndroidEnabled &= isHighEndPhone();
-        }
-
-        return sIsTabGroupsAndroidEnabled;
+        return isFlagEnabled(ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY, false);
     }
 
     /**
@@ -504,7 +496,16 @@
      */
     @VisibleForTesting
     public static void setTabGroupsAndroidEnabledForTesting(@Nullable Boolean available) {
-        sIsTabGroupsAndroidEnabled = available;
+        sFlags.put(ChromePreferenceManager.TAB_GROUPS_ANDROID_ENABLED_KEY, available);
+    }
+
+    /**
+     * Toggles whether the StartSurface is enabled for testing. Should be reset back to null after
+     * the test has finished.
+     */
+    @VisibleForTesting
+    public static void setStartSurfaceEnabledForTesting(@Nullable Boolean isEnabled) {
+        sFlags.put(ChromePreferenceManager.START_SURFACE_ENABLED_KEY, isEnabled);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
index 56e3218..6b77aeaca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheet.java
@@ -81,6 +81,28 @@
         int SCROLLING = 4;
     }
 
+    /** The different possible height modes for a given state. */
+    @IntDef({HeightMode.DEFAULT, HeightMode.WRAP_CONTENT, HeightMode.DISABLED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HeightMode {
+        /**
+         * The sheet will use the stock behavior for the {@link SheetState} this is used for.
+         * Typically this means a pre-defined height ratio, peek being the exception that uses the
+         * feature's toolbar height.
+         */
+        int DEFAULT = 0;
+        /**
+         * The sheet will set its height so the content is completely visible. This mode cannot
+         * be used for the peek state.
+         */
+        int WRAP_CONTENT = -1;
+        /**
+         * The state this mode is used for will be disabled. For example, disabling the peek state
+         * would cause the sheet to automatically expand when triggered.
+         */
+        int DISABLED = -2;
+    }
+
     /** The different reasons that the sheet's state can change. */
     @IntDef({StateChangeReason.NONE, StateChangeReason.SWIPE, StateChangeReason.BACK_PRESS,
             StateChangeReason.TAP_SCRIM, StateChangeReason.NAVIGATION,
@@ -157,10 +179,10 @@
     private ValueAnimator mSettleAnimator;
 
     /** The width of the view that contains the bottom sheet. */
-    private float mContainerWidth;
+    private int mContainerWidth;
 
     /** The height of the view that contains the bottom sheet. */
-    private float mContainerHeight;
+    private int mContainerHeight;
 
     /** The desired height of the current content view. */
     private float mContentDesiredHeight = HEIGHT_UNSPECIFIED;
@@ -266,11 +288,6 @@
         boolean swipeToDismissEnabled();
 
         /**
-         * @return Whether the peek state is enabled.
-         */
-        boolean isPeekStateEnabled();
-
-        /**
          * @return Whether the bottom sheet should wrap its content, i.e. its height in the FULL
          *         state is the minimum height required such that the content is visible. If this
          *         behavior is enabled, the HALF state of the sheet is disabled.
@@ -296,6 +313,16 @@
         }
 
         /**
+         * @return The height of the peeking state for the content in px or one of the values in
+         *         {@link HeightMode}. If {@link HeightMode#DEFAULT}, the system expects
+         *         {@link #getToolbarView} to be non-null, where it will then use its height as the
+         *         peeking height.
+         */
+        default int getPeekHeight() {
+            return HeightMode.DEFAULT;
+        }
+
+        /**
          * TODO(jinsukkim): Revise the API in favor of those specifying the height and its behavior
          *         for each state.
          * @return Height of the sheet in half state with respect to the container height.
@@ -423,8 +450,7 @@
      */
     public boolean handleBackPress() {
         if (isSheetOpen()) {
-            int sheetState =
-                    mSheetContent.isPeekStateEnabled() ? SheetState.PEEK : SheetState.HIDDEN;
+            int sheetState = getMinSwipableSheetState();
             setSheetState(sheetState, true, StateChangeReason.BACK_PRESS);
             return true;
         }
@@ -503,8 +529,7 @@
      * @param root The container of the bottom sheet.
      * @param tabProvider A means of accessing the active tab.
      * @param fullscreenManager A fullscreen manager for persisting browser controls and
-     *         determining\
-     *                          their offset.
+     *                          determining their offset.
      * @param window Android window for getting insets.
      * @param keyboardDelegate Delegate for hiding the keyboard.
      */
@@ -527,6 +552,9 @@
         mBottomSheetContentContainer.setBottomSheet(this);
         mBottomSheetContentContainer.setBackgroundResource(R.drawable.top_round);
 
+        mContainerWidth = root.getWidth();
+        mContainerHeight = root.getHeight();
+
         // Listen to height changes on the root.
         root.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
             private int mPreviousKeyboardHeight;
@@ -536,8 +564,8 @@
                     int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 // Compute the new height taking the keyboard into account.
                 // TODO(mdjones): Share this logic with LocationBarLayout: crbug.com/725725.
-                float previousWidth = mContainerWidth;
-                float previousHeight = mContainerHeight;
+                int previousWidth = mContainerWidth;
+                int previousHeight = mContainerHeight;
                 mContainerWidth = right - left;
                 mContainerHeight = bottom - top;
 
@@ -617,7 +645,7 @@
                     // open over the fullscreen video. See crbug.com/740499.
                     if (mFullscreenManager != null
                             && mFullscreenManager.getPersistentFullscreenMode() && isSheetOpen()) {
-                        setSheetState(SheetState.PEEK, false);
+                        setSheetState(getMinSwipableSheetState(), false);
                     } else {
                         if (isRunningSettleAnimation()) return;
                         setSheetState(mCurrentState, false);
@@ -736,9 +764,26 @@
      * @return The minimum sheet state that the user can swipe to. i.e. flinging down will either
      *         close the sheet or peek it.
      */
-    private @SheetState int getMinSwipableSheetState() {
-        return swipeToDismissEnabled() || !mSheetContent.isPeekStateEnabled() ? SheetState.HIDDEN
-                                                                              : SheetState.PEEK;
+    @SheetState
+    int getMinSwipableSheetState() {
+        return swipeToDismissEnabled() || !isPeekStateEnabled() ? SheetState.HIDDEN
+                                                                : SheetState.PEEK;
+    }
+
+    /**
+     * Get the state that the bottom sheet should open to with the provided content.
+     * @return The minimum opened state for the current content.
+     */
+    @SheetState
+    int getOpeningState() {
+        if (mSheetContent == null) {
+            return SheetState.HIDDEN;
+        } else if (isPeekStateEnabled()) {
+            return SheetState.PEEK;
+        } else if (!shouldSkipHalfState()) {
+            return SheetState.HALF;
+        }
+        return SheetState.FULL;
     }
 
     @Override
@@ -889,7 +934,9 @@
      * @return Get the height in px that the peeking bar is offset due to the browser controls.
      */
     private float getOffsetFromBrowserControls() {
-        if (mSheetContent != null && !mSheetContent.hideOnScroll()) return 0;
+        if (mSheetContent == null || mSheetContent.hideOnScroll() || !isPeekStateEnabled()) {
+            return 0;
+        }
 
         float peekHeight = getPeekRatio() * mContainerHeight;
         return peekHeight * mFullscreenManager.getBrowserControlHiddenRatio();
@@ -906,7 +953,7 @@
         // browser controls do.
         float translationY = (mContainerHeight - mCurrentOffsetPx) + getOffsetFromBrowserControls();
 
-        if (MathUtils.areFloatsEqual(translationY, getTranslationY())) return;
+        if (isSheetOpen() && MathUtils.areFloatsEqual(translationY, getTranslationY())) return;
 
         setTranslationY(translationY);
 
@@ -917,11 +964,19 @@
             mSheetContainer.addView(this);
         }
 
-        float peekHeight = getSheetHeightForState(SheetState.PEEK);
-        boolean isAtPeekingHeight = MathUtils.areFloatsEqual(getCurrentOffsetPx(), peekHeight);
-        if (isSheetOpen() && (getCurrentOffsetPx() < peekHeight || isAtPeekingHeight)) {
+        // Do open/close computation based on the minimum allowed state by the sheet's content.
+        float minScrollableHeight = getSheetHeightForState(getMinSwipableSheetState());
+        boolean isAtMinHeight = MathUtils.areFloatsEqual(getCurrentOffsetPx(), minScrollableHeight);
+        boolean heightLessThanPeek = getCurrentOffsetPx() < minScrollableHeight;
+        // Trigger the onSheetClosed event when the sheet is moving toward the hidden state if peek
+        // is disabled. This should be fine since touch is disabled when the sheet's target is
+        // hidden.
+        boolean triggerCloseWithHidden = !isPeekStateEnabled() && mTargetState == SheetState.HIDDEN;
+
+        if (isSheetOpen() && (heightLessThanPeek || isAtMinHeight || triggerCloseWithHidden)) {
             onSheetClosed(reason);
-        } else if (!isSheetOpen() && getCurrentOffsetPx() > peekHeight) {
+        } else if (!isSheetOpen() && mTargetState != SheetState.HIDDEN
+                && getCurrentOffsetPx() > minScrollableHeight) {
             onSheetOpened(reason);
         }
 
@@ -977,11 +1032,27 @@
         return 0;
     }
 
+    /** @return Whether the peeking state for the sheet's content is enabled. */
+    private boolean isPeekStateEnabled() {
+        return mSheetContent != null && mSheetContent.getPeekHeight() != HeightMode.DISABLED;
+    }
+
     /**
      * @return The ratio of the height of the screen that the peeking state is.
      */
     public float getPeekRatio() {
-        if (mContainerHeight <= 0) return 0;
+        if (mContainerHeight <= 0 || !isPeekStateEnabled()) return 0;
+
+        // If the content has a custom peek ratio set, use that instead of computing one.
+        if (mSheetContent != null && mSheetContent.getPeekHeight() != HeightMode.DEFAULT) {
+            assert mSheetContent.getPeekHeight()
+                    != HeightMode.WRAP_CONTENT : "The peek mode can't wrap content.";
+            float ratio = mSheetContent.getPeekHeight() / (float) mContainerHeight;
+            assert ratio > 0 && ratio <= 1 : "Custom peek ratios must be in the range of (0, 1].";
+            return ratio;
+        }
+        assert getToolbarView() != null : "Using default peek height requires a non-null toolbar";
+
         View toolbarView = getToolbarView();
         int toolbarHeight = toolbarView.getHeight();
         if (toolbarHeight == 0) {
@@ -993,7 +1064,7 @@
                     toolbarHeight = layoutParams.height;
                 } else {
                     toolbarView.measure(
-                            MeasureSpec.makeMeasureSpec((int) mContainerWidth, MeasureSpec.EXACTLY),
+                            MeasureSpec.makeMeasureSpec(mContainerWidth, MeasureSpec.EXACTLY),
                             MeasureSpec.makeMeasureSpec(
                                     (int) mContainerHeight, MeasureSpec.AT_MOST));
                     toolbarHeight = toolbarView.getMeasuredHeight();
@@ -1070,8 +1141,9 @@
             o.onSheetOffsetChanged(mLastOffsetRatioSent, getCurrentOffsetPx());
         }
 
-        if (MathUtils.areFloatsEqual(
-                    offsetWithBrowserControls, getSheetHeightForState(SheetState.PEEK))) {
+        if (isPeekStateEnabled()
+                && MathUtils.areFloatsEqual(
+                        offsetWithBrowserControls, getSheetHeightForState(SheetState.PEEK))) {
             for (BottomSheetObserver o : mObservers) o.onSheetFullyPeeked();
         }
     }
@@ -1079,6 +1151,7 @@
     /**
      * @see #setSheetState(int, boolean, int)
      */
+    @VisibleForTesting
     public void setSheetState(@SheetState int state, boolean animate) {
         setSheetState(state, animate, StateChangeReason.NONE);
     }
@@ -1093,8 +1166,7 @@
      *               observers that a more specific event has occurred, otherwise
      *               STATE_CHANGE_REASON_NONE can be used.
      */
-    public void setSheetState(
-            @SheetState int state, boolean animate, @StateChangeReason int reason) {
+    void setSheetState(@SheetState int state, boolean animate, @StateChangeReason int reason) {
         assert state != SheetState.NONE;
 
         // Setting state to SCROLLING is not a valid operation. This can happen only when
@@ -1104,9 +1176,7 @@
             return;
         }
 
-        if (state == SheetState.HALF && shouldSkipHalfState()) {
-            state = SheetState.FULL;
-        }
+        if (state == SheetState.HALF && shouldSkipHalfState()) state = SheetState.FULL;
 
         mTargetState = state;
 
@@ -1226,8 +1296,8 @@
         }
 
         mSheetContent.getContentView().measure(
-                MeasureSpec.makeMeasureSpec((int) mContainerWidth, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec((int) mContainerHeight, MeasureSpec.AT_MOST));
+                MeasureSpec.makeMeasureSpec(mContainerWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mContainerHeight, MeasureSpec.AT_MOST));
         mContentDesiredHeight = mSheetContent.getContentView().getMeasuredHeight();
     }
 
@@ -1286,7 +1356,7 @@
         int prevState = nextState;
         for (@SheetState int i = getMinSwipableSheetState(); i <= SheetState.FULL; i++) {
             if (i == SheetState.HALF && shouldSkipHalfState) continue;
-            if (i == SheetState.PEEK && !mSheetContent.isPeekStateEnabled()) continue;
+            if (i == SheetState.PEEK && !isPeekStateEnabled()) continue;
             prevState = nextState;
             nextState = i;
             // The values in PanelState are ascending, they should be kept that way in order for
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java
index d19d5eb..00663b64 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetController.java
@@ -142,10 +142,7 @@
             public void onScrimClick() {
                 if (!mBottomSheet.isSheetOpen()) return;
                 mBottomSheet.setSheetState(
-                        mBottomSheet.getCurrentSheetContent().isPeekStateEnabled()
-                                ? BottomSheet.SheetState.PEEK
-                                : BottomSheet.SheetState.HIDDEN,
-                        true, StateChangeReason.TAP_SCRIM);
+                        mBottomSheet.getMinSwipableSheetState(), true, StateChangeReason.TAP_SCRIM);
             }
 
             @Override
@@ -223,7 +220,7 @@
         mIsSuppressed = false;
 
         if (mBottomSheet.getCurrentSheetContent() != null) {
-            mBottomSheet.setSheetState(BottomSheet.SheetState.PEEK, true);
+            mBottomSheet.setSheetState(mBottomSheet.getOpeningState(), true);
         } else {
             // In the event the previous content was hidden, try to show the next one.
             showNextContent(true);
@@ -322,7 +319,7 @@
 
         BottomSheetContent nextContent = mContentQueue.poll();
         mBottomSheet.showContent(nextContent);
-        mBottomSheet.setSheetState(BottomSheet.SheetState.PEEK, animate);
+        mBottomSheet.setSheetState(mBottomSheet.getOpeningState(), animate);
     }
 
     /**
diff --git a/chrome/android/java/strings/android_chrome_strings.grd b/chrome/android/java/strings/android_chrome_strings.grd
index a4e7ee5..328cdfb 100644
--- a/chrome/android/java/strings/android_chrome_strings.grd
+++ b/chrome/android/java/strings/android_chrome_strings.grd
@@ -170,9 +170,6 @@
       <message name="IDS_DELETE" desc="Label for a delete button. Used in multiple contexts. [CHAR-LIMIT=20]">
         Delete
       </message>
-      <message name="IDS_DELETE_GROUP" desc="Label for button that deletes a group of items. [CHAR-LIMIT=20]">
-        Delete group
-      </message>
       <message name="IDS_REMOVE" desc="Label for a button to remove an item (e.g. a bookmark) from a list. [CHAR-LIMIT=20]">
         Remove
       </message>
@@ -230,9 +227,6 @@
       <message name="IDS_SHARE" desc="Content description for a button to share item(s). [CHAR-LIMIT=20]">
         Share
       </message>
-      <message name="IDS_SHARE_GROUP" desc="Content description for a button to share a group item(s). [CHAR-LIMIT=20]">
-        Share group
-      </message>
       <message name="IDS_SEARCH" desc="The label for a search button.">
         Search
       </message>
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java
index dd960489..ac3c682 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/externalnav/ExternalNavigationHandlerTest.java
@@ -8,6 +8,7 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -1599,6 +1600,14 @@
         return ri;
     }
 
+    private static ResolveInfo newSpecializedResolveInfo(
+            String packageName, IntentActivity activity) {
+        ResolveInfo info = newResolveInfo(packageName);
+        info.filter = new IntentFilter(Intent.ACTION_VIEW);
+        info.filter.addDataAuthority(activity.mUrlPrefix, null);
+        return info;
+    }
+
     private static WebappInfo newWebappInfoFromScope(String scope) {
         Intent webappIntent = WebappTestHelper.createMinimalWebappIntent("" /* id */, "" /* url */);
         webappIntent.putExtra(ShortcutHelper.EXTRA_SCOPE, scope);
@@ -1660,7 +1669,8 @@
             }
             for (IntentActivity intentActivity : mIntentActivities) {
                 if (dataString.startsWith(intentActivity.urlPrefix())) {
-                    list.add(newResolveInfo(intentActivity.packageName()));
+                    list.add(newSpecializedResolveInfo(
+                            intentActivity.packageName(), intentActivity));
                 }
             }
             if (!list.isEmpty()) return list;
@@ -1716,17 +1726,6 @@
             return count;
         }
 
-        @Override
-        public String findFirstWebApkPackageName(List<ResolveInfo> infos) {
-            List<IntentActivity> matchingIntentActivities = findMatchingIntentActivities(infos);
-            for (IntentActivity intentActivity : matchingIntentActivities) {
-                if (intentActivity.isWebApk()) {
-                    return intentActivity.packageName();
-                }
-            }
-            return null;
-        }
-
         private ArrayList<IntentActivity> findMatchingIntentActivities(List<ResolveInfo> infos) {
             ArrayList<IntentActivity> outList = new ArrayList<IntentActivity>();
             for (ResolveInfo info : infos) {
@@ -1823,6 +1822,16 @@
             return mIsCallingAppTrusted;
         }
 
+        @Override
+        public boolean isValidWebApk(String packageName) {
+            for (IntentActivity activity : mIntentActivities) {
+                if (activity.packageName().equals(packageName)) {
+                    return activity.isWebApk();
+                }
+            }
+            return false;
+        }
+
         public void reset() {
             startActivityIntent = null;
             startIncognitoIntentCalled = false;
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java
index 6444c736..cb9e5445 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/gesturenav/NavigationSheetTest.java
@@ -16,6 +16,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import org.chromium.base.ThreadUtils;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.base.test.util.UrlUtils;
@@ -33,7 +34,6 @@
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
-import org.chromium.content_public.browser.test.util.TouchCommon;
 import org.chromium.ui.modelutil.MVCListAdapter.ListItem;
 import org.chromium.ui.test.util.UiRestriction;
 
@@ -138,10 +138,11 @@
         NavigationSheetCoordinator sheet = (NavigationSheetCoordinator) showPopup(controller);
         ListView listview = sheet.getContentView().findViewById(R.id.navigation_entries);
 
-        CriteriaHelper.pollUiThread(sheet::isExpanded);
+        CriteriaHelper.pollUiThread(() -> listview.getChildCount() >= 2);
         Assert.assertEquals(INVALID_NAVIGATION_INDEX, controller.mNavigatedIndex);
 
-        TouchCommon.singleClickView(listview.getChildAt(1));
+        ThreadUtils.runOnUiThreadBlocking(() -> listview.getChildAt(1).callOnClick());
+
         CriteriaHelper.pollUiThread(sheet::isHidden);
         CriteriaHelper.pollUiThread(
                 Criteria.equals(NAVIGATION_INDEX_2, () -> controller.mNavigatedIndex));
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 d58ccce8..a4b9bb8 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
@@ -4,6 +4,7 @@
 
 package org.chromium.chrome.browser.init;
 
+import android.content.ComponentName;
 import android.content.Intent;
 import android.net.Uri;
 import android.support.test.filters.SmallTest;
@@ -38,6 +39,11 @@
     private static final String INVALID_SCHEME = "javascript:alert()";
     private static final Intent VIEW_INTENT =
             new Intent(Intent.ACTION_VIEW).setData(Uri.parse(SITE_A));
+    private static final Intent CHROME_MAIN_COMPONENT_INTENT =
+            new Intent()
+                    .setComponent(new ComponentName("com.google.android.apps.chrome",
+                            "com.google.android.apps.chrome.Main"))
+                    .setData(Uri.parse(SITE_A));
     private static final Intent INCOGNITO_VIEW_INTENT =
             new Intent(Intent.ACTION_VIEW)
                     .setData(Uri.parse(SITE_A))
@@ -97,6 +103,14 @@
     @Test
     @SmallTest
     @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
+    public void testShouldLoadTab_AllowChromeMainComponentIntentWithUrl() {
+        Assert.assertTrue(createStartupTabPreloader(CHROME_MAIN_COMPONENT_INTENT, sChromeTabCreator)
+                                  .shouldLoadTab());
+    }
+
+    @Test
+    @SmallTest
+    @EnableFeatures(ChromeFeatureList.PRIORITIZE_BOOTSTRAP_TASKS)
     public void testShouldLoadTab_AllowMainIntentsWithUrl() {
         Assert.assertTrue(
                 createStartupTabPreloader(MAIN_INTENT_WITH_URL, sChromeTabCreator).shouldLoadTab());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
index 6dac1ad..d88930a 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrInputTest.java
@@ -125,7 +125,7 @@
             @Restriction(RESTRICTION_TYPE_SVR)
             @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void
             testScreenTapsNotRegistered_WebXr() throws InterruptedException {
@@ -210,7 +210,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testControllerClicksRegisteredOnDaydream_WebXr() {
         EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
@@ -246,7 +246,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testControllerExposedAsGamepadOnDaydream_WebXr() {
         EmulatedVrController controller = new EmulatedVrController(mTestRule.getActivity());
@@ -397,7 +397,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testScreenTapsRegisteredOnCardboard_WebXr() {
         mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
@@ -435,7 +435,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testTransientScreenTapsRegisteredOnCardboard_WebXr() {
         mWebXrVrTestFramework.loadUrlAndAwaitInitialization(
@@ -482,7 +482,7 @@
     @MediumTest
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testPresentationLocksFocus_WebXr() {
         presentationLocksFocusImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile(
@@ -519,7 +519,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             public void testAppButtonExitsPresentation_WebXr() {
         appButtonExitsPresentationImpl(
                 WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
@@ -572,7 +572,7 @@
     @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             public void testAppButtonNoopsWhenBrowsingDisabled_WebXr() throws ExecutionException {
         appButtonNoopsTestImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
                 mWebXrVrTestFramework);
@@ -589,7 +589,7 @@
             XrActivityRestriction.SupportedActivity.CCT})
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             public void
             testAppButtonNoopsWhenBrowsingNotSupported_WebXr() throws ExecutionException {
         appButtonNoopsTestImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile("generic_webxr_page"),
@@ -671,7 +671,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             public void testAppButtonAfterPageStopsSubmitting_WebXr() {
         appButtonAfterPageStopsSubmittingImpl(
                 WebXrVrTestFramework.getFileUrlForHtmlTestFile("webxr_page_submits_once"),
@@ -698,7 +698,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testWebXrInputSourceHasGamepad() {
         webxrGamepadSupportImpl(true /* daydream */);
@@ -714,7 +714,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_NON_DAYDREAM)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testWebXrInputSourceWithoutGamepad_Cardboard() {
         webxrGamepadSupportImpl(false /* daydream */);
@@ -763,7 +763,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
             public void testAppButtonLongPressDisplaysPermissions() throws InterruptedException {
         testAppButtonLongPressDisplaysPermissionsImpl();
@@ -778,7 +778,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA})
             public void testAppButtonLongPressDisplaysPermissionsIncognito()
             throws InterruptedException {
@@ -852,7 +852,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             // TODO(https://crbug.com/901494): Make this run everywhere when permissions are
             // unbroken.
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA})
@@ -865,7 +865,7 @@
     @Restriction(RESTRICTION_TYPE_VIEWER_DAYDREAM_OR_STANDALONE)
     @CommandLineFlags
             .Remove({"enable-webvr"})
-            @CommandLineFlags.Add({"enable-features=WebXR"})
+            @CommandLineFlags.Add({"enable-features=WebXR,WebXrGamepadModule"})
             @XrActivityRestriction({XrActivityRestriction.SupportedActivity.CTA})
             public void testInSessionPermissionRequestsIncognito() {
         mWebXrVrTestFramework.openIncognitoTab("about:blank");
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
index 7b6cba49..677f019 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/vr/WebXrVrTransitionTest.java
@@ -183,11 +183,11 @@
      */
     @Test
     @MediumTest
-    @CommandLineFlags.Remove({"enable-webvr"})
-    @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
-    public void testWebXrDisabledWithoutFlagSet() {
-        // TODO(bsheedy): Remove this test once WebXR is on by default without
-        // requiring an origin trial.
+    @CommandLineFlags
+            .Add({"disable-features=WebXR"})
+            @CommandLineFlags.Remove({"enable-webvr"})
+            @XrActivityRestriction({XrActivityRestriction.SupportedActivity.ALL})
+            public void testWebXrDisabledWithoutFlagSet() {
         apiDisabledWithoutFlagSetImpl(WebXrVrTestFramework.getFileUrlForHtmlTestFile(
                                               "test_webxr_disabled_without_flag_set"),
                 mWebXrVrTestFramework);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
index 1398ed0..6993a46f 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetControllerTest.java
@@ -85,9 +85,9 @@
             mHighPriorityContent = new TestBottomSheetContent(
                     mActivityTestRule.getActivity(), ContentPriority.HIGH, false);
 
-            mPeekableContent = new TestBottomSheetContent(mActivityTestRule.getActivity(), true);
-            mNonPeekableContent =
-                    new TestBottomSheetContent(mActivityTestRule.getActivity(), false);
+            mPeekableContent = new TestBottomSheetContent(mActivityTestRule.getActivity());
+            mNonPeekableContent = new TestBottomSheetContent(mActivityTestRule.getActivity());
+            mNonPeekableContent.setPeekHeight(BottomSheet.HeightMode.DISABLED);
         });
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetObserverTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetObserverTest.java
index 1b618a8..33ff028 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetObserverTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/BottomSheetObserverTest.java
@@ -5,6 +5,7 @@
 package org.chromium.chrome.browser.widget.bottomsheet;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.support.test.filters.MediumTest;
@@ -21,6 +22,8 @@
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Restriction;
 import org.chromium.chrome.browser.util.MathUtils;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.HeightMode;
+import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.SheetState;
 import org.chromium.chrome.test.BottomSheetTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.ui.test.util.UiRestriction;
@@ -29,96 +32,143 @@
 
 /** This class tests the functionality of the {@link BottomSheetObserver}. */
 @RunWith(ChromeJUnit4ClassRunner.class)
-@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) // ChromeHome is only enabled on phones
+@Restriction(UiRestriction.RESTRICTION_TYPE_PHONE) // Bottom sheet is only used on phones.
 public class BottomSheetObserverTest {
     @Rule
     public BottomSheetTestRule mBottomSheetTestRule = new BottomSheetTestRule();
     private BottomSheetTestRule.Observer mObserver;
+    private TestBottomSheetContent mSheetContent;
 
     @Before
     public void setUp() throws Exception {
-        mBottomSheetTestRule.startMainActivityOnBottomSheet(BottomSheet.SheetState.PEEK);
+        mBottomSheetTestRule.startMainActivityOnBottomSheet(SheetState.HIDDEN);
         ThreadUtils.runOnUiThreadBlocking(() -> {
-            mBottomSheetTestRule.getBottomSheet().showContent(new TestBottomSheetContent(
-                    mBottomSheetTestRule.getActivity(), BottomSheet.ContentPriority.HIGH, false));
+            mSheetContent = new TestBottomSheetContent(
+                    mBottomSheetTestRule.getActivity(), BottomSheet.ContentPriority.HIGH, false);
+            mBottomSheetTestRule.getBottomSheet().showContent(mSheetContent);
         });
         mObserver = mBottomSheetTestRule.getObserver();
     }
 
+    /** Test that the onSheetClosed event is triggered if the sheet is closed without animation. */
+    @Test
+    @MediumTest
+    public void testCloseEventCalled_noAnimation() throws TimeoutException {
+        runCloseEventTest(false, true);
+    }
+
     /**
-     * Test that the onSheetClosed event is triggered if the sheet is closed without animation.
+     * Test that the onSheetClosed event is triggered if the sheet is closed without animation and
+     * without a peeking state.
      */
     @Test
     @MediumTest
-    public void testCloseEventCalledNoAnimation() throws TimeoutException {
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.FULL, false);
+    public void testCloseEventCalled_noAnimationNoPeekState() throws TimeoutException {
+        runCloseEventTest(false, false);
+    }
+
+    /** Test that the onSheetClosed event is triggered if the sheet is closed with animation. */
+    @Test
+    @MediumTest
+    public void testCloseEventCalled_withAnimation() throws TimeoutException {
+        runCloseEventTest(true, true);
+    }
+
+    /**
+     * Test that the onSheetClosed event is triggered if the sheet is closed with animation but
+     * without a peeking state.
+     */
+    @Test
+    @MediumTest
+    public void testCloseEventCalled_withAnimationNoPeekState() throws TimeoutException {
+        runCloseEventTest(true, false);
+    }
+
+    /**
+     * Run different versions of the onSheetClosed event test.
+     * @param animationEnabled Whether to run the test with animation.
+     * @param peekStateEnabled Whether the sheet's content has a peek state.
+     */
+    private void runCloseEventTest(boolean animationEnabled, boolean peekStateEnabled)
+            throws TimeoutException {
+        mBottomSheetTestRule.setSheetState(SheetState.FULL, false);
+
+        mSheetContent.setPeekHeight(peekStateEnabled ? HeightMode.DEFAULT : HeightMode.DISABLED);
 
         CallbackHelper closedCallbackHelper = mObserver.mClosedCallbackHelper;
 
         int initialOpenedCount = mObserver.mOpenedCallbackHelper.getCallCount();
 
         int closedCallbackCount = closedCallbackHelper.getCallCount();
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.PEEK, false);
+        mBottomSheetTestRule.setSheetState(
+                peekStateEnabled ? SheetState.PEEK : SheetState.HIDDEN, animationEnabled);
         closedCallbackHelper.waitForCallback(closedCallbackCount, 1);
 
         assertEquals(initialOpenedCount, mObserver.mOpenedCallbackHelper.getCallCount());
     }
 
+    /** Test that the onSheetOpened event is triggered if the sheet is opened without animation. */
+    @Test
+    @MediumTest
+    public void testOpenedEventCalled_noAnimation() throws TimeoutException {
+        runOpenEventTest(false, true);
+    }
+
     /**
-     * Test that the onSheetClosed event is triggered if the sheet is closed with animation.
+     * Test that the onSheetOpened event is triggered if the sheet is opened without animation and
+     * without a peeking state.
      */
     @Test
     @MediumTest
-    public void testCloseEventCalledWithAnimation() throws TimeoutException {
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.FULL, false);
+    public void testOpenedEventCalled_noAnimationNoPeekState() throws TimeoutException {
+        runOpenEventTest(false, false);
+    }
 
+    /** Test that the onSheetOpened event is triggered if the sheet is opened with animation. */
+    @Test
+    @MediumTest
+    public void testOpenedEventCalled_withAnimation() throws TimeoutException {
+        runOpenEventTest(true, true);
+    }
+
+    /**
+     * Test that the onSheetOpened event is triggered if the sheet is opened with animation and
+     * without a peek state.
+     */
+    @Test
+    @MediumTest
+    public void testOpenedEventCalled_withAnimationNoPeekState() throws TimeoutException {
+        runOpenEventTest(true, false);
+    }
+
+    /**
+     * Run different versions of the onSheetOpened event test.
+     * @param animationEnabled Whether to run the test with animation.
+     * @param peekStateEnabled Whether the sheet's content has a peek state.
+     */
+    private void runOpenEventTest(boolean animationEnabled, boolean peekStateEnabled)
+            throws TimeoutException {
+        mSheetContent.setPeekHeight(peekStateEnabled ? HeightMode.DEFAULT : HeightMode.DISABLED);
+
+        CallbackHelper openedCallbackHelper = mObserver.mOpenedCallbackHelper;
+        int openedCallbackCount = openedCallbackHelper.getCallCount();
         CallbackHelper closedCallbackHelper = mObserver.mClosedCallbackHelper;
+        int initialClosedCount = closedCallbackHelper.getCallCount();
 
-        int initialOpenedCount = mObserver.mOpenedCallbackHelper.getCallCount();
+        mBottomSheetTestRule.setSheetState(
+                mBottomSheetTestRule.getBottomSheet().getOpeningState(), false);
 
-        int closedCallbackCount = closedCallbackHelper.getCallCount();
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.PEEK, true);
-        closedCallbackHelper.waitForCallback(closedCallbackCount, 1);
+        assertNotEquals("Sheet should not be hidden.",
+                mBottomSheetTestRule.getBottomSheet().getSheetState(), SheetState.HIDDEN);
+        if (!peekStateEnabled) {
+            assertNotEquals("Sheet should be above the peeking state when peek is disabled.",
+                    mBottomSheetTestRule.getBottomSheet().getSheetState(), SheetState.PEEK);
+        }
 
-        assertEquals(initialOpenedCount, mObserver.mOpenedCallbackHelper.getCallCount());
-    }
-
-    /**
-     * Test that the onSheetOpened event is triggered if the sheet is opened without animation.
-     */
-    @Test
-    @MediumTest
-    public void testOpenedEventCalledNoAnimation() throws TimeoutException {
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.PEEK, false);
-
-        CallbackHelper openedCallbackHelper = mObserver.mOpenedCallbackHelper;
-
-        int initialClosedCount = mObserver.mClosedCallbackHelper.getCallCount();
-
-        int openedCallbackCount = openedCallbackHelper.getCallCount();
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.FULL, false);
+        mBottomSheetTestRule.setSheetState(SheetState.FULL, animationEnabled);
         openedCallbackHelper.waitForCallback(openedCallbackCount, 1);
 
-        assertEquals(initialClosedCount, mObserver.mClosedCallbackHelper.getCallCount());
-    }
-
-    /**
-     * Test that the onSheetOpened event is triggered if the sheet is opened with animation.
-     */
-    @Test
-    @MediumTest
-    public void testOpenedEventCalledWithAnimation() throws TimeoutException {
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.PEEK, false);
-
-        CallbackHelper openedCallbackHelper = mObserver.mOpenedCallbackHelper;
-
-        int initialClosedCount = mObserver.mClosedCallbackHelper.getCallCount();
-
-        int openedCallbackCount = openedCallbackHelper.getCallCount();
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.FULL, true);
-        openedCallbackHelper.waitForCallback(openedCallbackCount, 1);
-
-        assertEquals(initialClosedCount, mObserver.mClosedCallbackHelper.getCallCount());
+        assertEquals(initialClosedCount, closedCallbackHelper.getCallCount());
     }
 
     /**
@@ -127,7 +177,7 @@
     @Test
     @MediumTest
     public void testOffsetChangedEvent() throws TimeoutException {
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.FULL, false);
+        mBottomSheetTestRule.setSheetState(SheetState.FULL, false);
         CallbackHelper callbackHelper = mObserver.mOffsetChangedCallbackHelper;
 
         BottomSheet bottomSheet = mBottomSheetTestRule.getBottomSheet();
@@ -199,8 +249,8 @@
         callbackHelper.waitForCallback(callCount);
 
         // HALF state is forbidden when wrapping the content.
-        mBottomSheetTestRule.setSheetState(BottomSheet.SheetState.HALF, false);
-        assertEquals(BottomSheet.SheetState.FULL, bottomSheet.getSheetState());
+        mBottomSheetTestRule.setSheetState(SheetState.HALF, false);
+        assertEquals(SheetState.FULL, bottomSheet.getSheetState());
 
         // Check the offset.
         assertEquals(wrappedContentHeight + bottomSheet.getToolbarShadowHeight(),
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java
index 1baecf9..5fe0e1d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/widget/bottomsheet/TestBottomSheetContent.java
@@ -30,20 +30,19 @@
     /** Whether this content is browser specific. */
     private boolean mHasCustomLifecycle;
 
-    /** Whether this content's peek state is enabled. */
-    private boolean mPeekStateEnabled;
+    /** The peek height of this content. */
+    private int mPeekHeight;
 
     /**
      * @param context A context to inflate views with.
      * @param priority The content's priority.
      * @param hasCustomLifecycle Whether the content is browser specific.
-     * @param peekStateEnabled Whether the content's peek state is enabled.
      */
-    public TestBottomSheetContent(Context context, @ContentPriority int priority,
-            boolean hasCustomLifecycle, boolean peekStateEnabled) {
+    public TestBottomSheetContent(
+            Context context, @ContentPriority int priority, boolean hasCustomLifecycle) {
+        mPeekHeight = BottomSheet.HeightMode.DEFAULT;
         mPriority = priority;
         mHasCustomLifecycle = hasCustomLifecycle;
-        mPeekStateEnabled = peekStateEnabled;
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mToolbarView = new View(context);
             ViewGroup.LayoutParams params =
@@ -61,20 +60,9 @@
 
     /**
      * @param context A context to inflate views with.
-     * @param priority The content's priority.
-     * @param hasCustomLifecycle Whether the content is browser specific.
      */
-    public TestBottomSheetContent(
-            Context context, @ContentPriority int priority, boolean hasCustomLifecycle) {
-        this(context, priority, hasCustomLifecycle, true);
-    }
-
-    /**
-     * @param context A context to inflate views with.
-     * @param peekStateEnabled Whether the content's peek state is enabled.
-     */
-    public TestBottomSheetContent(Context context, boolean peekStateEnabled) {
-        this(/*TestBottomSheetContent(*/ context, ContentPriority.LOW, false, peekStateEnabled);
+    public TestBottomSheetContent(Context context) {
+        this(/*TestBottomSheetContent(*/ context, ContentPriority.LOW, false);
     }
 
     @Override
@@ -106,9 +94,13 @@
         return false;
     }
 
+    public void setPeekHeight(int height) {
+        mPeekHeight = height;
+    }
+
     @Override
-    public boolean isPeekStateEnabled() {
-        return mPeekStateEnabled;
+    public int getPeekHeight() {
+        return mPeekHeight;
     }
 
     @Override
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
index 33b23d6..caa73b7 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/download/home/list/DateOrderedListMutatorTest.java
@@ -84,7 +84,6 @@
     /**
      * Action                               List
      * 1. Set(item1 @ 1:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
-     *                                        SECTION @ Video,
      *                                        item1   @ 1:00 1/1/2018 ]
      */
     @Test
@@ -94,15 +93,14 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 2:00 1/1/2018,        [ DATE    @ 0:00 1/1/2018,
-     *        item2 @ 1:00 1/1/2018)          SECTION @ Video,
+     *        item2 @ 1:00 1/1/2018)
      *                                        item1   @ 2:00 1/1/2018,
      *                                        item2   @ 1:00 1/1/2018 ]
      */
@@ -114,8 +112,7 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(3, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
     }
@@ -123,9 +120,7 @@
     /**
      * Action                                     List
      * 1. Set(item1 @ 2:00 1/1/2018 Video,        [ DATE    @ 0:00 1/1/2018,
-     *        item2 @ 1:00 1/1/2018 Audio)          SECTION @ Video,
-     *                                              item1   @ 2:00 1/1/2018,
-     *                                              SECTION @ Audio,
+     *        item2 @ 1:00 1/1/2018 Audio)          item1   @ 2:00 1/1/2018,
      *                                              item2   @ 1:00 1/1/2018 ]
      */
     @Test
@@ -135,73 +130,16 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
-        Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        Assert.assertEquals(3, mModel.size());
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.AUDIO, false, false);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
-    }
-
-    /**
-     * Action                                     List
-     * 1. Set(item1 @ 2:00 1/1/2018 Video,        [ DATE    @ 0:00 1/1/2018,
-     *        item2 @ 1:00 1/1/2018 Image)          SECTION @ Video,
-     *                                              item1   @ 2:00 1/1/2018,
-     *                                              SECTION @ Image,
-     *                                              item2   @ 1:00 1/1/2018 ]
-     */
-    @Test
-    public void testShowMenuButtonForImageSectionWithoutDate() {
-        OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 2), OfflineItemFilter.VIDEO);
-        OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.IMAGE);
-        when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
-        DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
-
-        Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
-        Assert.assertFalse(((SectionHeaderListItem) mModel.get(0)).showMenu);
-
-        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.IMAGE, false, false);
-        Assert.assertTrue(((SectionHeaderListItem) mModel.get(2)).showMenu);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
-    }
-
-    /**
-     * Action                                     List
-     * 1. Set(item1 @ 2:00 1/1/2018 Image,        [ DATE    @ 0:00 1/1/2018,
-     *        item2 @ 1:00 1/1/2018 Page)           SECTION @ Image,
-     *                                              item1   @ 2:00 1/1/2018,
-     *                                              SECTION @ Page,
-     *                                              item2   @ 1:00 1/1/2018 ]
-     */
-    @Test
-    public void testShowMenuButtonForImageSectionWithDate() {
-        OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 1, 2), OfflineItemFilter.IMAGE);
-        OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 1, 1), OfflineItemFilter.PAGE);
-        when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
-        DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
-
-        Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.IMAGE, true, false);
-        Assert.assertTrue(((SectionHeaderListItem) mModel.get(0)).showMenu);
-
-        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 2), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.PAGE, false, false);
-        Assert.assertFalse(((SectionHeaderListItem) mModel.get(2)).showMenu);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 1:00 1/1/2018         [ DATE    Just Now,
-     *        IN_PROGRESS)                    SECTION @ Video,
+     *        IN_PROGRESS)
      *                                        item1   @ 1:00 1/1/2018 ]
      */
     @Test
@@ -212,16 +150,16 @@
         DateOrderedListMutator list = createMutatorWithJustNowProvider();
 
         Assert.assertEquals(2, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 1:00 1/1/2018         [ DATE    Just Now,
-     *              Video IN_PROGRESS,        SECTION @ Video,
+     *              Video IN_PROGRESS,
      *        item2 @ 1:00 1/1/2018           item1   @ 1:00 1/1/2018,
-     *              Audio COMPLETE Recent)    SECTION @ Audio,
+     *              Audio COMPLETE Recent)
      *                                        item2   @ 1:00 1/1/2018 ]
      */
     @Test
@@ -234,26 +172,25 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
         DateOrderedListMutator list = createMutatorWithJustNowProvider();
 
-        Assert.assertEquals(4, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        Assert.assertEquals(3, mModel.size());
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
-        assertJustNowSection(mModel.get(2), OfflineItemFilter.AUDIO, false, false);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 1), item2);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 1:00 1/1/2018         [ DATE    Just Now,
-     *        PAUSED)                         SECTION @ Video,
+     *        PAUSED)
      *                                        item1   @ 1:00 1/1/2018 ]
      * 2. Update(item1 @ 1:00 1/1/2018      [ DATE    Just Now,
-     *        Resume --> IN_PROGRESS)         SECTION @ Video,
+     *        Resume --> IN_PROGRESS)
      *                                        item1   @ 1:00 1/1/2018 ]
      * 3. Update(item1 @ 1:00 1/1/2018      [ DATE    Just Now,
-     *       COMPLETE, completion time now)   SECTION @ Video,
+     *       COMPLETE, completion time now)
      *                                        item1   @ 1:00 1/1/2018 ]
      * 4. Update(item1 @ 1:00 1/1/2018      [ DATE    Just Now,
-     *    COMPLETE, completion time 1/1/2017) SECTION @ Video,
+     *    COMPLETE, completion time 1/1/2017)
      *                                        item1   @ 1:00 1/1/2018 ]
      */
     @Test
@@ -265,7 +202,7 @@
         mModel.addObserver(mObserver);
 
         Assert.assertEquals(2, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), item1);
 
         // Resume the download.
@@ -275,7 +212,7 @@
         list.onItemUpdated(item1, update1);
 
         Assert.assertEquals(2, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), update1);
 
         // Complete the download.
@@ -286,7 +223,7 @@
         list.onItemUpdated(update1, update2);
 
         Assert.assertEquals(2, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), update2);
 
         // Too much time has passed since completion of the download.
@@ -297,17 +234,16 @@
         list.onItemUpdated(update2, update3);
 
         Assert.assertEquals(2, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 1), update3);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 1:00 2/1/2018         [ DATE    Just Now,
-     *              Video IN_PROGRESS,        SECTION @ Video,
+     *              Video IN_PROGRESS,
      *        item2 @ 1:00 1/1/2018           item1   @ 1:00 2/1/2018,
      *              Audio COMPLETE)           DATE    1/1/2018
-     *                                        SECTION @ Audio,
      *                                        item2   @ 1:00 1/1/2018 ]
      */
     @Test
@@ -319,20 +255,19 @@
         DateOrderedListMutator list = createMutatorWithJustNowProvider();
 
         Assert.assertEquals(4, mModel.size());
-        assertJustNowSection(mModel.get(0), OfflineItemFilter.VIDEO, true, false);
+        assertJustNowSection(mModel.get(0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 2, 1, 1), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.AUDIO, true, true);
+        assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 1), item2);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 0:00 1/2/2018,        [ DATE    @ 0:00 1/2/2018,
-     *        item2 @ 0:00 1/1/2018)          SECTION @ Video,
+     *        item2 @ 0:00 1/1/2018)
      *                                        item1   @ 0:00 1/2/2018,
      *                                        DATE  @ 0:00 1/1/2018,
-     *                                        SECTION @ Audio,
+     *
      *                                        item2   @ 0:00 1/1/2018 ]
      */
     @Test
@@ -343,18 +278,16 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 0), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.AUDIO, true, true);
+        assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 0), item2);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 4:00 1/1/2018,        [ DATE    @ 0:00 1/1/2018,
-     *        item2 @ 5:00 1/1/2018)          SECTION @ Video,
+     *        item2 @ 5:00 1/1/2018)
      *                                        item2   @ 5:00 1/1/2018,
      *                                        item1   @ 4:00 1/1/2018 ]
      */
@@ -366,8 +299,7 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(3, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 5), item2);
         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item1);
     }
@@ -375,10 +307,9 @@
     /**
      * Action                                      List
      * 1. Set(item1 @ 4:00 1/2/2018 Video,         [ DATE      @ 0:00 1/2/2018,
-     *        item2 @ 5:00 1/1/2018 Video)           SECTION   @ Video,
+     *        item2 @ 5:00 1/1/2018 Video)
      *                                               item2     @ 4:00 1/2/2018,
      *                                               DATE      @ 0:00 1/1/2018,
-     *                                               SECTION   @ Video,
      *                                               item1     @ 5:00 1/1/2018 ]
      */
     @Test
@@ -389,21 +320,18 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, true);
+        assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 5), item2);
     }
 
     /**
      * Action                                      List
      * 1. Set(item1 @ 4:00 1/2/2018 Video,         [ DATE      @ 0:00 1/2/2018,
-     *        item2 @ 5:00 1/1/2018 Page )           SECTION   @ Video,
+     *        item2 @ 5:00 1/1/2018 Page )
      *                                               item2     @ 4:00 1/2/2018,
      *                                               DATE      @ 0:00 1/1/2018,
-     *                                               SECTION   @ Page,
      *                                               item1     @ 5:00 1/1/2018 ]
      */
     @Test
@@ -414,21 +342,18 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 4), item1);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.PAGE, true, true);
+        assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 5), item2);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 4:00 1/1/2018,        [ DATE   @ 0:00 1/2/2018,
-     *        item2 @ 3:00 1/2/2018)          SECTION   @ Video,
+     *        item2 @ 3:00 1/2/2018)
      *                                        item2  @ 3:00 1/2/2018,
      *                                        DATE   @ 0:00 1/1/2018,
-     *                                        SECTION   @ Video,
      *                                        item1  @ 4:00 1/1/2018 ]
      */
     @Test
@@ -439,11 +364,9 @@
         DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
 
         Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 3), item2);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, true);
+        assertSectionHeader(mModel.get(2), buildCalendar(2018, 1, 1, 0), true);
         assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 4), item1);
     }
 
@@ -452,7 +375,6 @@
      * 1. Set()                             [ ]
      *
      * 2. Add(item1 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
-     *                                        SECTION   @ Video,
      *                                        item1  @ 4:00 1/1/2018 ]
      */
     @Test
@@ -472,17 +394,13 @@
     /**
      * Action                               List
      * 1. Set(item1 @ 1:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item1  @ 1:00 1/2/2018 ]
      * 2. Add(item2 @ 2:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item2  @ 2:00 1/2/2018
      *                                        item1  @ 1:00 1/2/2018 ]
      * 3. Add(item3 @ 2:00 1/3/2018)        [ DATE    @ 0:00 1/3/2018,
-     *                                        SECTION @ Video,
      *                                        item3  @ 2:00 1/3/2018
      *                                        DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item2  @ 2:00 1/2/2018
      *                                        item1  @ 1:00 1/2/2018 ]
      */
@@ -516,20 +434,16 @@
     /**
      * Action                               List
      * 1. Set(item1 @ 4:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item1  @ 4:00 1/2/2018 ]
      *
      * 2. Add(item2 @ 3:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item1  @ 4:00 1/2/2018
      *                                        item2  @ 3:00 1/2/2018 ]
      *
      * 3. Add(item3 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item1  @ 4:00 1/2/2018
      *                                        item2  @ 3:00 1/2/2018,
      *                                        DATE    @ 0:00 1/1/2018,
-     *                                        SECTION @ Video,
      *                                        item3  @ 4:00 1/1/2018
      */
     @Test
@@ -562,7 +476,7 @@
     /**
      * Action                               List
      * 1. Set(item1 @ 2:00 1/2/2018)        [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
+     *
      *                                        item1  @ 2:00 1/2/2018 ]
      *
      * 2. Remove(item1)                     [ ]
@@ -583,12 +497,11 @@
     /**
      * Action                               List
      * 1. Set(item1 @ 3:00 1/2/2018,        [ DATE    @ 0:00 1/2/2018,
-     *        item2 @ 2:00 1/2/2018)          SECTION @ Video,
+     *        item2 @ 2:00 1/2/2018)
      *                                        item1  @ 3:00 1/2/2018,
      *                                        item2  @ 2:00 1/2/2018 ]
      *
      * 2. Remove(item1)                     [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item2  @ 2:00 1/2/2018 ]
      */
     @Test
@@ -603,20 +516,18 @@
         list.onItemsRemoved(CollectionUtil.newArrayList(item1));
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 3:00 1/2/2018,        [ DATE    @ 0:00 1/2/2018,
-     *        item2 @ 2:00 1/2/2018)          SECTION @ Video,
+     *        item2 @ 2:00 1/2/2018)
      *                                        item1  @ 3:00 1/2/2018,
      *                                        item2  @ 2:00 1/2/2018 ]
      *
      * 2. Remove(item2)                     [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item1  @ 3:00 1/2/2018 ]
      */
     @Test
@@ -631,52 +542,19 @@
         list.onItemsRemoved(CollectionUtil.newArrayList(item2));
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 3), item1);
     }
 
     /**
      * Action                               List
-     * 1. Set(item1 @ 3:00 1/2/2018 Video,  [ DATE    @ 0:00 1/2/2018,
-     *        item2 @ 2:00 1/2/2018 Image)    SECTION @ Video,
-     *                                        item1  @ 3:00 1/2/2018,
-     *                                        SECTION @ Image,
-     *                                        item2  @ 2:00 1/2/2018 ]
-     *
-     * 2. Remove(item1)                     [ DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Image,
-     *                                        item2  @ 2:00 1/2/2018 ]
-     */
-    @Test
-    public void testRemoveOnlyItemInSection() {
-        OfflineItem item1 = buildItem("1", buildCalendar(2018, 1, 2, 3), OfflineItemFilter.VIDEO);
-        OfflineItem item2 = buildItem("2", buildCalendar(2018, 1, 2, 2), OfflineItemFilter.IMAGE);
-        when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item1, item2));
-        DateOrderedListMutator list = createMutatorWithoutJustNowProvider();
-        mModel.addObserver(mObserver);
-        Assert.assertEquals(4, mModel.size());
-
-        when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(item2));
-        list.onItemsRemoved(CollectionUtil.newArrayList(item1));
-
-        Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.IMAGE, true, false);
-        assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 2), item2);
-    }
-
-    /**
-     * Action                               List
      * 1. Set(item1 @ 3:00 1/3/2018,        [ DATE    @ 0:00 1/3/2018,
-     *        item2 @ 2:00 1/2/2018)          SECTION @ Video,
+     *        item2 @ 2:00 1/2/2018)
      *                                        item1  @ 3:00 1/3/2018,
      *                                        DATE    @ 0:00 1/2/2018,
-     *                                        SECTION @ Video,
      *                                        item2  @ 2:00 1/2/2018 ]
      *
      * 2. Remove(item2)                     [ DATE    @ 0:00 1/3/2018,
-     *                                        SECTION @ Video,
      *                                        item1  @ 3:00 1/3/2018 ]
      */
     @Test
@@ -691,8 +569,7 @@
         list.onItemsRemoved(CollectionUtil.newArrayList(item2));
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 3, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 3, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 3, 3), item1);
     }
 
@@ -701,11 +578,11 @@
      * 1. Set()                             [ ]
      *
      * 2. Add(item1 @ 6:00  1/1/2018,       [ DATE    @ 0:00  1/2/2018,
-     *        item2 @ 4:00  1/1/2018,         SECTION @ Video,
+     *        item2 @ 4:00  1/1/2018,
      *        item3 @ 10:00 1/2/2018,         item4  @ 12:00 1/2/2018,
      *        item4 @ 12:00 1/2/2018)         item3  @ 10:00 1/2/2018
      *                                        DATE    @ 0:00  1/1/2018,
-     *                                        SECTION @ Video,
+     *
      *                                        item1  @ 6:00  1/1/2018,
      *                                        item2  @ 4:00  1/1/2018 ]
      */
@@ -725,12 +602,10 @@
         list.onItemsAdded(CollectionUtil.newArrayList(item1, item2, item3, item4));
 
         Assert.assertEquals(6, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 12), item4);
         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 2, 10), item3);
-        assertSectionHeader(
-                mModel.get(3), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, true);
+        assertSectionHeader(mModel.get(3), buildCalendar(2018, 1, 1, 0), true);
         assertOfflineItem(mModel.get(4), buildCalendar(2018, 1, 1, 6), item1);
         assertOfflineItem(mModel.get(5), buildCalendar(2018, 1, 1, 4), item2);
     }
@@ -740,16 +615,13 @@
      * 1. Set()                             [ ]
      *
      * 2. Add(item3 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
-     *                                        SECTION   @ Video,
      *                                        item3  @ 4:00 1/1/2018 ]
      *
      * 3. Add(item1 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
-     *                                        SECTION   @ Video,
      *                                        item1  @ 4:00 1/1/2018,
      *                                        item3  @ 4:00 1/1/2018 ]
      *
      * 4. Add(item2 @ 4:00 1/1/2018)        [ DATE    @ 0:00 1/1/2018,
-     *                                        SECTION   @ Video,
      *                                        item1  @ 4:00 1/1/2018,
      *                                        item2  @ 4:00 1/1/2018,
      *                                        item3  @ 4:00 1/1/2018 ]
@@ -788,12 +660,12 @@
     /**
      * Action                                          List
      * 1. Set(item1 @ 6:00 IN_PROGRESS 1/1/2018)       [ DATE    @ 0:00 1/1/2018,
-     *                                                   SECTION @ Video,
+     *
      *                                                   item1  @ 3:00 1/1/2018 IN_PROGRESS]
      *
      * 2. Update(item1 @ 6:00 COMPLETE 1/1/2018)
      *
-     * 3. Add(item2 @ 4:00 IN_PROGRESS 1/1/2018)       [ SECTION @ Video,
+     * 3. Add(item2 @ 4:00 IN_PROGRESS 1/1/2018)       [
      *                                                   DATE    @ 0:00  1/1/2018,
      *                                                   item1  @ 6:00  1/1/2018 COMPLETE,
      *                                                   item2  @ 4:00  1/1/2018 IN_PROGRESS]
@@ -810,8 +682,7 @@
         list.onItemsAdded(CollectionUtil.newArrayList(item1));
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), item1);
 
         // Complete the download.
@@ -825,8 +696,7 @@
         list.onItemsAdded(CollectionUtil.newArrayList(item2));
 
         Assert.assertEquals(3, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), update1);
         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 4), item2);
     }
@@ -834,16 +704,16 @@
     /**
      * Action                               List
      * 2. Set(item1 @ 6:00  1/1/2018,       [ DATE    @ 0:00  1/2/2018,
-     *        item2 @ 4:00  1/1/2018,         SECTION @ Video,
+     *        item2 @ 4:00  1/1/2018,
      *        item3 @ 10:00 1/2/2018,         item4  @ 12:00 1/2/2018,
      *        item4 @ 12:00 1/2/2018)         item3  @ 10:00 1/2/2018
      *                                        DATE    @ 0:00  1/1/2018,
-     *                                        SECTION @ Video,
+     *
      *                                        item1  @ 6:00  1/1/2018,
      *                                        item2  @ 4:00  1/1/2018 ]
      *
      * 2. Remove(item2,                     [ DATE    @ 0:00  1/1/2018,
-     *           item3,                       SECTION @ Video,
+     *           item3,
      *           item4)                       item1  @ 6:00  1/1/2018 ]
      */
     @Test
@@ -862,20 +732,19 @@
         list.onItemsRemoved(CollectionUtil.newArrayList(item2, item3, item4));
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 6), item1);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 4:00 1/1/2018)        [ DATE      @ 0:00  1/1/2018,
-     *                                        SECTION @ Video,
+     *
      *                                        item1     @ 4:00  1/1/2018 ]
      *
      * 2. Update (item1,
      *            newItem1 @ 4:00 1/1/2018) [ DATE      @ 0:00  1/1/2018,
-     *                                        SECTION @ Video,
+     *
      *                                        newItem1  @ 4:00  1/1/2018 ]
      */
     @Test
@@ -893,20 +762,19 @@
         list.onItemUpdated(item1, newItem1);
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), newItem1);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 5:00 1/1/2018,        [ DATE      @ 0:00  1/1/2018,
-     *        item2 @ 4:00 1/1/2018)          SECTION @ Video,
+     *        item2 @ 4:00 1/1/2018)
      *                                        item1     @ 5:00  1/1/2018,
      *                                        item2     @ 4:00  1/1/2018
      * 2. Update (item1,
      *            newItem1 @ 3:00 1/1/2018) [ DATE      @ 0:00  1/1/2018,
-     *                                        SECTION @ Video,
+     *
      *                                        item2     @ 4:00  1/1/2018,
      *                                        newItem1  @ 3:00  1/1/2018 ]
      */
@@ -926,8 +794,7 @@
         list.onItemUpdated(item1, newItem1);
 
         Assert.assertEquals(3, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item2);
         assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 3), newItem1);
     }
@@ -935,14 +802,13 @@
     /**
      * Action                                   List
      * 1. Set(item1 @ 5:00 1/1/2018,            [ DATE      @ 0:00  1/1/2018,
-     *        item2 @ 4:00 1/1/2018)              SECTION @ Video,
+     *        item2 @ 4:00 1/1/2018)
      *                                            item1     @ 5:00  1/1/2018,
      *                                            item2     @ 4:00  1/1/2018
      * 2. Update (item1,
      *            newItem1 @ 3:00 1/1/2018 Image) [ DATE      @ 0:00  1/1/2018,
-     *                                              SECTION @ Video,
+     *
      *                                              item2     @ 4:00  1/1/2018,
-     *                                              SECTION @ Image,
      *                                              newItem1  @ 3:00  1/1/2018 ]
      */
     @Test
@@ -960,24 +826,19 @@
         when(mSource.getItems()).thenReturn(CollectionUtil.newArrayList(newItem1, item2));
         list.onItemUpdated(item1, newItem1);
 
-        Assert.assertEquals(4, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.VIDEO, true, false);
+        Assert.assertEquals(3, mModel.size());
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 1, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 1, 4), item2);
-        assertSectionHeader(
-                mModel.get(2), buildCalendar(2018, 1, 1, 0), OfflineItemFilter.IMAGE, false, false);
-        assertOfflineItem(mModel.get(3), buildCalendar(2018, 1, 1, 3), newItem1);
+        assertOfflineItem(mModel.get(2), buildCalendar(2018, 1, 1, 3), newItem1);
     }
 
     /**
      * Action                               List
      * 1. Set(item1 @ 4:00 1/1/2018)        [ DATE      @ 0:00  1/1/2018,
-     *                                        SECTION @ Video,
      *                                        item1     @ 4:00  1/1/2018 ]
      *
      * 2. Update (item1,
      *            newItem1 @ 6:00 1/2/2018) [ DATE      @ 0:00  1/2/2018,
-     *                                        SECTION @ Video,
      *                                        newItem1  @ 6:00  1/2/2018 ]
      */
     @Test
@@ -995,8 +856,7 @@
         list.onItemUpdated(item1, newItem1);
 
         Assert.assertEquals(2, mModel.size());
-        assertSectionHeader(
-                mModel.get(0), buildCalendar(2018, 1, 2, 0), OfflineItemFilter.VIDEO, true, false);
+        assertSectionHeader(mModel.get(0), buildCalendar(2018, 1, 2, 0), false);
         assertOfflineItem(mModel.get(1), buildCalendar(2018, 1, 2, 6), newItem1);
     }
 
@@ -1017,19 +877,18 @@
     }
 
     private DateOrderedListMutator createMutatorWithoutJustNowProvider() {
-        DownloadManagerUiConfig config =
-                new DownloadManagerUiConfig.Builder().setShowSectionHeaders(true).build();
-        return new DateOrderedListMutator(mSource, mModel, config, new JustNowProvider(config) {
+        DownloadManagerUiConfig config = new DownloadManagerUiConfig.Builder().build();
+        JustNowProvider justNowProvider = new JustNowProvider(config) {
             @Override
             public boolean isJustNowItem(OfflineItem item) {
                 return false;
             }
-        });
+        };
+        return new DateOrderedListMutator(mSource, mModel, config, justNowProvider);
     }
 
     private DateOrderedListMutator createMutatorWithJustNowProvider() {
-        DownloadManagerUiConfig config =
-                new DownloadManagerUiConfig.Builder().setShowSectionHeaders(true).build();
+        DownloadManagerUiConfig config = new DownloadManagerUiConfig.Builder().build();
         return new DateOrderedListMutator(mSource, mModel, config, new JustNowProvider(config));
     }
 
@@ -1047,33 +906,20 @@
         Assert.assertEquals(offlineItem, ((OfflineItemListItem) item).item);
     }
 
-    private static void assertSectionHeader(ListItem item, Calendar calendar,
-            @OfflineItemFilter int filter, boolean showDate, boolean showDivider) {
+    private static void assertSectionHeader(ListItem item, Calendar calendar, boolean showDivider) {
         Assert.assertTrue(item instanceof SectionHeaderListItem);
         SectionHeaderListItem sectionHeader = (SectionHeaderListItem) item;
         assertDatesAreEqual(sectionHeader.date, calendar);
-        Assert.assertEquals(filter, sectionHeader.filter);
         Assert.assertEquals(
-                SectionHeaderListItem.generateStableId(calendar.getTimeInMillis(), filter),
-                item.stableId);
-        Assert.assertEquals(sectionHeader.showDate, showDate);
+                SectionHeaderListItem.generateStableId(calendar.getTimeInMillis()), item.stableId);
         Assert.assertEquals(sectionHeader.showDivider, showDivider);
     }
 
-    private static void assertJustNowSection(
-            ListItem item, @OfflineItemFilter int filter, boolean showDate, boolean showDivider) {
+    private static void assertJustNowSection(ListItem item, boolean showDivider) {
         Assert.assertTrue(item instanceof SectionHeaderListItem);
         SectionHeaderListItem sectionHeader = (SectionHeaderListItem) item;
-        Assert.assertEquals(filter, sectionHeader.filter);
         Assert.assertTrue(sectionHeader.isJustNow);
-        Assert.assertEquals(sectionHeader.showDate, showDate);
         Assert.assertEquals(sectionHeader.showDivider, showDivider);
-        if (showDate) {
-            Assert.assertEquals(StableIds.JUST_NOW_SECTION, item.stableId);
-        } else {
-            Assert.assertEquals(SectionHeaderListItem.generateStableId(
-                                        new Date(Long.MAX_VALUE).getTime(), filter),
-                    item.stableId);
-        }
+        Assert.assertEquals(StableIds.JUST_NOW_SECTION, item.stableId);
     }
 }
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 16938cb..29a7cd7 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -765,6 +765,8 @@
     "media/webrtc/window_icon_util_chromeos.cc",
     "media/webrtc/window_icon_util_mac.mm",
     "media/webrtc/window_icon_util_win.cc",
+    "memory/chrome_browser_main_extra_parts_memory.cc",
+    "memory/chrome_browser_main_extra_parts_memory.h",
     "memory/enterprise_memory_limit_evaluator.cc",
     "memory/enterprise_memory_limit_evaluator.h",
     "memory/enterprise_memory_limit_pref_observer.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a2948865..790f469c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3077,11 +3077,6 @@
      flag_descriptions::kChromeColorsCustomColorPickerDescription, kOsDesktop,
      FEATURE_VALUE_TYPE(features::kChromeColorsCustomColorPicker)},
 
-    {"grid-layout-for-ntp-shortcuts",
-     flag_descriptions::kGridLayoutForNtpShortcutsName,
-     flag_descriptions::kGridLayoutForNtpShortcutsDescription, kOsDesktop,
-     FEATURE_VALUE_TYPE(features::kGridLayoutForNtpShortcuts)},
-
     {"ntp-customization-menu-v2",
      flag_descriptions::kNtpCustomizationMenuV2Name,
      flag_descriptions::kNtpCustomizationMenuV2Description, kOsDesktop,
@@ -4365,7 +4360,7 @@
 
     {"safety-tips", flag_descriptions::kSafetyTipName,
      flag_descriptions::kSafetyTipDescription, kOsAll,
-     FEATURE_VALUE_TYPE(features::kSafetyTipUI)},
+     FEATURE_VALUE_TYPE(security_state::features::kSafetyTipUI)},
 
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
     {"animated-avatar-button", flag_descriptions::kAnimatedAvatarButtonName,
diff --git a/chrome/browser/android/vr/arcore_device/arcore.h b/chrome/browser/android/vr/arcore_device/arcore.h
index 5870424..0f7231a9 100644
--- a/chrome/browser/android/vr/arcore_device/arcore.h
+++ b/chrome/browser/android/vr/arcore_device/arcore.h
@@ -45,6 +45,9 @@
   // when the camera image was updated successfully.
   virtual mojom::VRPosePtr Update(bool* camera_updated) = 0;
 
+  // Return latest estimate for the floor height.
+  virtual float GetEstimatedFloorHeight() = 0;
+
   // Returns information about all planes detected in the current frame.
   virtual mojom::XRPlaneDetectionDataPtr GetDetectedPlanesData() = 0;
 
@@ -55,6 +58,15 @@
       const mojom::XRRayPtr& ray,
       std::vector<mojom::XRHitResultPtr>* hit_results) = 0;
 
+  virtual base::Optional<uint32_t> SubscribeToHitTest(
+      mojom::XRNativeOriginInformationPtr native_origin_information,
+      mojom::XRRayPtr ray) = 0;
+
+  virtual mojom::XRHitTestSubscriptionResultsDataPtr
+  GetHitTestSubscriptionResults(const device::mojom::VRPosePtr& pose) = 0;
+
+  virtual void UnsubscribeFromHitTest(uint32_t subscription_id) = 0;
+
   virtual base::Optional<uint32_t> CreateAnchor(
       const mojom::VRPosePtr& pose) = 0;
   virtual base::Optional<uint32_t> CreateAnchor(const mojom::VRPosePtr& pose,
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.cc b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
index d7e3146..4c1164b14 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.cc
@@ -44,6 +44,8 @@
 constexpr std::array<float, 6> kDisplayCoordinatesForTransform = {
     0.f, 0.f, 1.f, 0.f, 0.f, 1.f};
 
+constexpr uint32_t kInputSourceId = 1;
+
 gfx::Transform ConvertUvsToTransformMatrix(const std::vector<float>& uvs) {
   // We're creating a matrix that transforms viewport UV coordinates (for a
   // screen-filling quad, origin at bottom left, u=1 at right, v=1 at top) to
@@ -367,15 +369,17 @@
   have_camera_image_ = true;
   mojom::XRFrameDataPtr frame_data = mojom::XRFrameData::New();
 
-  // Check if floor height estimate has changed. The estimate might eventually
-  // be provided by |arcore_| - for now, use hard-coded value.
-  if (floor_height_estimate_changed_) {
+  // Check if floor height estimate has changed.
+  float new_floor_height_estimate = arcore_->GetEstimatedFloorHeight();
+  if (!floor_height_estimate_ ||
+      *floor_height_estimate_ != new_floor_height_estimate) {
+    floor_height_estimate_ = new_floor_height_estimate;
+
     frame_data->stage_parameters_updated = true;
     frame_data->stage_parameters = mojom::VRStageParameters::New();
     frame_data->stage_parameters->standing_transform = gfx::Transform();
     frame_data->stage_parameters->standing_transform.Translate3d(
-        0, floor_height_estimate_, 0);
-    floor_height_estimate_changed_ = false;
+        0, *floor_height_estimate_, 0);
   }
 
   frame_data->frame_id = webxr_->StartFrameAnimating();
@@ -583,6 +587,46 @@
   hit_test_requests_.push_back(std::move(request));
 }
 
+void ArCoreGl::SubscribeToHitTest(
+    mojom::XRNativeOriginInformationPtr native_origin_information,
+    mojom::XRRayPtr ray,
+    mojom::XREnvironmentIntegrationProvider::SubscribeToHitTestCallback
+        callback) {
+  DVLOG(2) << __func__ << ": ray origin=" << ray->origin.ToString()
+           << ", ray direction=" << ray->direction.ToString();
+
+  // Input source state information is known to ArCoreGl and not to ArCore -
+  // check if we recognize the input source id.
+
+  if (native_origin_information->is_input_source_id()) {
+    if (native_origin_information->get_input_source_id() != kInputSourceId) {
+      DVLOG(1) << __func__ << ": incorrect input source ID passed";
+      std::move(callback).Run(
+          device::mojom::SubscribeToHitTestResult::FAILURE_GENERIC, 0);
+      return;
+    }
+  }
+
+  base::Optional<uint32_t> maybe_subscription_id = arcore_->SubscribeToHitTest(
+      std::move(native_origin_information), std::move(ray));
+
+  if (maybe_subscription_id) {
+    DVLOG(2) << __func__ << ": subscription_id=" << *maybe_subscription_id;
+    std::move(callback).Run(device::mojom::SubscribeToHitTestResult::SUCCESS,
+                            *maybe_subscription_id);
+  } else {
+    DVLOG(1) << __func__ << ": subscription failed";
+    std::move(callback).Run(
+        device::mojom::SubscribeToHitTestResult::FAILURE_GENERIC, 0);
+  }
+}
+
+void ArCoreGl::UnsubscribeFromHitTest(uint32_t subscription_id) {
+  DVLOG(2) << __func__;
+
+  arcore_->UnsubscribeFromHitTest(subscription_id);
+}
+
 void ArCoreGl::CreateAnchor(mojom::VRPosePtr anchor_pose,
                             CreateAnchorCallback callback) {
   DVLOG(2) << __func__;
@@ -646,6 +690,10 @@
       input_states_.push_back(std::move(input_state));
       frame_data->pose->input_state = std::move(input_states_);
     }
+
+    // Get results for hit test subscriptions.
+    frame_data->hit_test_subscription_results =
+        arcore_->GetHitTestSubscriptionResults(frame_data->pose);
   }
 
   // The timing requirements for hit-test are documented here:
@@ -699,7 +747,7 @@
       device::mojom::XRInputSourceState::New();
 
   // Only one controller is supported, so the source id can be static.
-  state->source_id = 1;
+  state->source_id = kInputSourceId;
 
   state->primary_input_pressed = screen_touch_active_;
   if (!screen_touch_active_ && screen_touch_pending_) {
diff --git a/chrome/browser/android/vr/arcore_device/arcore_gl.h b/chrome/browser/android/vr/arcore_device/arcore_gl.h
index 6607816..e2576a3 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_gl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_gl.h
@@ -8,6 +8,7 @@
 #include <memory>
 #include <utility>
 #include <vector>
+
 #include "base/cancelable_callback.h"
 #include "base/containers/queue.h"
 #include "base/macros.h"
@@ -110,6 +111,14 @@
       mojom::XRRayPtr,
       mojom::XREnvironmentIntegrationProvider::RequestHitTestCallback) override;
 
+  void SubscribeToHitTest(
+      mojom::XRNativeOriginInformationPtr native_origin_information,
+      mojom::XRRayPtr ray,
+      mojom::XREnvironmentIntegrationProvider::SubscribeToHitTestCallback
+          callback) override;
+
+  void UnsubscribeFromHitTest(uint32_t subscription_id) override;
+
   void CreateAnchor(mojom::VRPosePtr anchor_pose,
                     CreateAnchorCallback callback) override;
   void CreatePlaneAnchor(mojom::VRPosePtr anchor_pose,
@@ -218,11 +227,8 @@
   mojom::VRDisplayInfoPtr display_info_;
   bool display_info_changed_ = false;
 
-  // True if floor height estimate should be passed to blink via XRFrameData
-  // returned by subsequent call to |GetFrameData()|.
-  bool floor_height_estimate_changed_ = true;
   // Currently estimated floor height.
-  float floor_height_estimate_ = 1.2;
+  base::Optional<float> floor_height_estimate_;
 
   std::vector<device::mojom::XRInputSourceStatePtr> input_states_;
   gfx::PointF screen_last_touch_;
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.cc b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
index fa0cdb33..7263644 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.cc
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.cc
@@ -67,10 +67,22 @@
   return result;
 }
 
+constexpr float kDefaultFloorHeightEstimation = 1.2;
+
 }  // namespace
 
 namespace device {
 
+HitTestSubscriptionData::HitTestSubscriptionData(
+    mojom::XRNativeOriginInformationPtr native_origin_information,
+    mojom::XRRayPtr ray)
+    : native_origin_information(std::move(native_origin_information)),
+      ray(std::move(ray)) {}
+
+HitTestSubscriptionData::HitTestSubscriptionData(
+    HitTestSubscriptionData&& other) = default;
+HitTestSubscriptionData::~HitTestSubscriptionData() = default;
+
 ArCoreImpl::ArCoreImpl()
     : gl_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
 
@@ -589,17 +601,239 @@
   return result;
 }
 
+float ArCoreImpl::GetEstimatedFloorHeight() {
+  return kDefaultFloorHeightEstimation;
+}
+
+base::Optional<uint32_t> ArCoreImpl::SubscribeToHitTest(
+    mojom::XRNativeOriginInformationPtr native_origin_information,
+    mojom::XRRayPtr ray) {
+  // First, check if we recognize the type of the native origin.
+
+  if (native_origin_information->is_reference_space_category()) {
+    // Reference spaces are implicitly recognized and don't carry an ID.
+  } else if (native_origin_information->is_input_source_id()) {
+    // Input source IDs are verified in the higher layer as ArCoreImpl does
+    // not carry input source state.
+  } else if (native_origin_information->is_plane_id()) {
+    // Validate that we know which plane's space the hit test is interested in
+    // tracking.
+    if (plane_id_to_plane_object_.count(
+            PlaneId(native_origin_information->get_plane_id())) == 0) {
+      return base::nullopt;
+    }
+  } else if (native_origin_information->is_anchor_id()) {
+    // Validate that we know which anchor's space the hit test is interested
+    // in tracking.
+    if (anchor_id_to_anchor_object_.count(
+            AnchorId(native_origin_information->get_anchor_id())) == 0) {
+      return base::nullopt;
+    }
+  } else {
+    NOTREACHED();
+    return base::nullopt;
+  }
+
+  auto maybe_subscription_id = CreateHitTestSubscriptionId();
+  if (!maybe_subscription_id) {
+    return base::nullopt;
+  }
+
+  hit_test_subscription_id_to_data_.emplace(
+      *maybe_subscription_id,
+      HitTestSubscriptionData{std::move(native_origin_information),
+                              std::move(ray)});
+
+  return maybe_subscription_id->GetUnsafeValue();
+}
+
+mojom::XRHitTestSubscriptionResultsDataPtr
+ArCoreImpl::GetHitTestSubscriptionResults(
+    const device::mojom::VRPosePtr& pose) {
+  mojom::XRHitTestSubscriptionResultsDataPtr result =
+      mojom::XRHitTestSubscriptionResultsData::New();
+
+  for (auto& subscription_id_and_data : hit_test_subscription_id_to_data_) {
+    // First, check if we can find the current transformation for a ray. If not,
+    // skip processing this subscription.
+    auto maybe_mojo_from_native_origin = GetMojoFromNativeOrigin(
+        subscription_id_and_data.second.native_origin_information, pose);
+
+    if (!maybe_mojo_from_native_origin) {
+      continue;
+    }
+
+    // Since we have a transform, let's use it to obtain hit test results.
+    result->results.push_back(GetHitTestSubscriptionResult(
+        HitTestSubscriptionId(subscription_id_and_data.first),
+        *subscription_id_and_data.second.ray, *maybe_mojo_from_native_origin));
+  }
+
+  return result;
+}
+
+device::mojom::XRHitTestSubscriptionResultDataPtr
+ArCoreImpl::GetHitTestSubscriptionResult(
+    HitTestSubscriptionId id,
+    const mojom::XRRay& native_origin_ray,
+    const gfx::Transform& mojo_from_native_origin) {
+  // Transform the ray according to the latest transform based on the XRSpace
+  // used in hit test subscription.
+
+  gfx::Point3F origin = native_origin_ray.origin;
+  mojo_from_native_origin.TransformPoint(&origin);
+
+  gfx::Vector3dF direction = native_origin_ray.direction;
+  mojo_from_native_origin.TransformVector(&direction);
+
+  std::vector<mojom::XRHitResultPtr> hit_results;
+  RequestHitTest(origin, direction, &hit_results);
+
+  return mojom::XRHitTestSubscriptionResultData::New(id.GetUnsafeValue(),
+                                                     std::move(hit_results));
+}
+
+base::Optional<gfx::Transform> ArCoreImpl::GetMojoFromReferenceSpace(
+    device::mojom::XRReferenceSpaceCategory category,
+    const device::mojom::VRPosePtr& mojo_from_viewer) {
+  switch (category) {
+    case device::mojom::XRReferenceSpaceCategory::LOCAL:
+      return gfx::Transform{};
+    case device::mojom::XRReferenceSpaceCategory::LOCAL_FLOOR: {
+      auto result = gfx::Transform{};
+      result.Translate3d(0, -GetEstimatedFloorHeight(), 0);
+      return result;
+    }
+    case device::mojom::XRReferenceSpaceCategory::VIEWER:
+      return mojo::ConvertTo<gfx::Transform>(mojo_from_viewer);
+    case device::mojom::XRReferenceSpaceCategory::BOUNDED_FLOOR:
+      return base::nullopt;
+    case device::mojom::XRReferenceSpaceCategory::UNBOUNDED:
+      return base::nullopt;
+  }
+}
+
+base::Optional<gfx::Transform> ArCoreImpl::GetMojoFromNativeOrigin(
+    const mojom::XRNativeOriginInformationPtr& native_origin_information,
+    const device::mojom::VRPosePtr& mojo_from_viewer) {
+  if (native_origin_information->is_input_source_id()) {
+    if (!mojo_from_viewer->input_state) {
+      return base::nullopt;
+    }
+
+    // Linear search should be fine for ARCore device as it only has one input
+    // source (for now).
+    for (auto& input_source_state : *mojo_from_viewer->input_state) {
+      if (input_source_state->source_id ==
+          native_origin_information->get_input_source_id()) {
+        if (!input_source_state->description->pointer_offset) {
+          return base::nullopt;
+        }
+
+        auto view_from_pointer =
+            *input_source_state->description->pointer_offset;
+
+        auto mojo_from_view = mojo::ConvertTo<gfx::Transform>(mojo_from_viewer);
+
+        return mojo_from_view * view_from_pointer;
+      }
+    }
+
+    return base::nullopt;
+  } else if (native_origin_information->is_reference_space_category()) {
+    return GetMojoFromReferenceSpace(
+        native_origin_information->get_reference_space_category(),
+        mojo_from_viewer);
+  } else if (native_origin_information->is_plane_id()) {
+    auto plane_it = plane_id_to_plane_object_.find(
+        PlaneId(native_origin_information->get_plane_id()));
+    if (plane_it == plane_id_to_plane_object_.end()) {
+      return base::nullopt;
+    }
+
+    // Naked pointer is fine as we don't own the object and ArAsPlane does not
+    // increase the refcount.
+    ArPlane* plane = ArAsPlane(plane_it->second.get());
+
+    internal::ScopedArCoreObject<ArPose*> ar_pose;
+    ArPose_create(
+        arcore_session_.get(), nullptr,
+        internal::ScopedArCoreObject<ArPose*>::Receiver(ar_pose).get());
+    ArPlane_getCenterPose(arcore_session_.get(), plane, ar_pose.get());
+    mojom::VRPosePtr mojo_pose =
+        GetMojomPoseFromArPose(arcore_session_.get(), std::move(ar_pose));
+
+    return mojo::ConvertTo<gfx::Transform>(mojo_pose);
+  } else if (native_origin_information->is_anchor_id()) {
+    auto anchor_it = anchor_id_to_anchor_object_.find(
+        AnchorId(native_origin_information->get_anchor_id()));
+    if (anchor_it == anchor_id_to_anchor_object_.end()) {
+      return base::nullopt;
+    }
+
+    internal::ScopedArCoreObject<ArPose*> ar_pose;
+    ArPose_create(
+        arcore_session_.get(), nullptr,
+        internal::ScopedArCoreObject<ArPose*>::Receiver(ar_pose).get());
+
+    ArAnchor_getPose(arcore_session_.get(), anchor_it->second.get(),
+                     ar_pose.get());
+    mojom::VRPosePtr mojo_pose =
+        GetMojomPoseFromArPose(arcore_session_.get(), std::move(ar_pose));
+
+    return mojo::ConvertTo<gfx::Transform>(mojo_pose);
+  } else {
+    NOTREACHED();
+    return base::nullopt;
+  }
+}  // namespace device
+
+void ArCoreImpl::UnsubscribeFromHitTest(uint32_t subscription_id) {
+  auto it = hit_test_subscription_id_to_data_.find(
+      HitTestSubscriptionId(subscription_id));
+  if (it == hit_test_subscription_id_to_data_.end()) {
+    return;
+  }
+
+  hit_test_subscription_id_to_data_.erase(it);
+}
+
+base::Optional<HitTestSubscriptionId>
+ArCoreImpl::CreateHitTestSubscriptionId() {
+  if (next_id_ == std::numeric_limits<uint32_t>::max()) {
+    return base::nullopt;
+  }
+
+  uint32_t current_id = next_id_++;
+
+  return HitTestSubscriptionId(current_id);
+}
+
 bool ArCoreImpl::RequestHitTest(
     const mojom::XRRayPtr& ray,
     std::vector<mojom::XRHitResultPtr>* hit_results) {
-  DVLOG(2) << __func__ << ": ray origin=" << ray->origin.ToString()
-           << ", direction=" << ray->direction.ToString();
+  DCHECK(ray);
+  return RequestHitTest(ray->origin, ray->direction, hit_results);
+}
+
+bool ArCoreImpl::RequestHitTest(
+    const gfx::Point3F& origin,
+    const gfx::Vector3dF& direction,
+    std::vector<mojom::XRHitResultPtr>* hit_results) {
+  DVLOG(2) << __func__ << ": origin=" << origin.ToString()
+           << ", direction=" << direction.ToString();
 
   DCHECK(hit_results);
   DCHECK(IsOnGlThread());
   DCHECK(arcore_session_.is_valid());
   DCHECK(arcore_frame_.is_valid());
 
+  // ArCore returns hit-results in sorted order, thus providing the guarantee
+  // of sorted results promised by the WebXR spec for requestHitTest().
+  std::array<float, 3> origin_array = {origin.x(), origin.y(), origin.z()};
+  std::array<float, 3> direction_array = {direction.x(), direction.y(),
+                                          direction.z()};
+
   internal::ScopedArCoreObject<ArHitResultList*> arcore_hit_result_list;
   ArHitResultList_create(
       arcore_session_.get(),
@@ -611,14 +845,9 @@
     return false;
   }
 
-  // ArCore returns hit-results in sorted order, thus providing the guarantee
-  // of sorted results promised by the WebXR spec for requestHitTest().
-  float origin[3] = {ray->origin.x(), ray->origin.y(), ray->origin.z()};
-  float direction[3] = {ray->direction.x(), ray->direction.y(),
-                        ray->direction.z()};
-
-  ArFrame_hitTestRay(arcore_session_.get(), arcore_frame_.get(), origin,
-                     direction, arcore_hit_result_list.get());
+  ArFrame_hitTestRay(arcore_session_.get(), arcore_frame_.get(),
+                     origin_array.data(), direction_array.data(),
+                     arcore_hit_result_list.get());
 
   int arcore_hit_result_list_size = 0;
   ArHitResultList_getSize(arcore_session_.get(), arcore_hit_result_list.get(),
diff --git a/chrome/browser/android/vr/arcore_device/arcore_impl.h b/chrome/browser/android/vr/arcore_device/arcore_impl.h
index 21fd467b..a1377b4 100644
--- a/chrome/browser/android/vr/arcore_device/arcore_impl.h
+++ b/chrome/browser/android/vr/arcore_device/arcore_impl.h
@@ -91,6 +91,18 @@
 
 using PlaneId = util::IdTypeU32<class PlaneTag>;
 using AnchorId = util::IdTypeU32<class AnchorTag>;
+using HitTestSubscriptionId = util::IdTypeU32<class HitTestSubscriptionTag>;
+
+struct HitTestSubscriptionData {
+  mojom::XRNativeOriginInformationPtr native_origin_information;
+  mojom::XRRayPtr ray;
+
+  HitTestSubscriptionData(
+      mojom::XRNativeOriginInformationPtr native_origin_information,
+      mojom::XRRayPtr ray);
+  HitTestSubscriptionData(HitTestSubscriptionData&& other);
+  ~HitTestSubscriptionData();
+};
 
 // This class should be created and accessed entirely on a Gl thread.
 class ArCoreImpl : public ArCore {
@@ -115,9 +127,25 @@
   void Pause() override;
   void Resume() override;
 
+  float GetEstimatedFloorHeight() override;
+
   bool RequestHitTest(const mojom::XRRayPtr& ray,
                       std::vector<mojom::XRHitResultPtr>* hit_results) override;
 
+  // Helper.
+  bool RequestHitTest(const gfx::Point3F& origin,
+                      const gfx::Vector3dF& direction,
+                      std::vector<mojom::XRHitResultPtr>* hit_results);
+
+  base::Optional<uint32_t> SubscribeToHitTest(
+      mojom::XRNativeOriginInformationPtr nativeOriginInformation,
+      mojom::XRRayPtr ray) override;
+
+  mojom::XRHitTestSubscriptionResultsDataPtr GetHitTestSubscriptionResults(
+      const device::mojom::VRPosePtr& pose) override;
+
+  void UnsubscribeFromHitTest(uint32_t subscription_id) override;
+
   base::Optional<uint32_t> CreateAnchor(
       const device::mojom::VRPosePtr& pose) override;
   base::Optional<uint32_t> CreateAnchor(const device::mojom::VRPosePtr& pose,
@@ -184,6 +212,9 @@
   std::map<AnchorId, device::internal::ScopedArCoreObject<ArAnchor*>>
       anchor_id_to_anchor_object_;
 
+  std::map<HitTestSubscriptionId, HitTestSubscriptionData>
+      hit_test_subscription_id_to_data_;
+
   // Returns tuple containing plane id and a boolean signifying that the plane
   // was created. The result will be a nullopt in case the ID should be assigned
   // but next ID would result in an integer overflow.
@@ -192,6 +223,23 @@
   base::Optional<std::pair<AnchorId, bool>> CreateOrGetAnchorId(
       void* anchor_address);
 
+  base::Optional<HitTestSubscriptionId> CreateHitTestSubscriptionId();
+
+  // Returns hit test subscription results for a single subscription given
+  // current XRSpace transformation.
+  device::mojom::XRHitTestSubscriptionResultDataPtr
+  GetHitTestSubscriptionResult(HitTestSubscriptionId id,
+                               const mojom::XRRay& ray,
+                               const gfx::Transform& ray_transformation);
+
+  base::Optional<gfx::Transform> GetMojoFromNativeOrigin(
+      const mojom::XRNativeOriginInformationPtr& native_origin_information,
+      const device::mojom::VRPosePtr& pose);
+
+  base::Optional<gfx::Transform> GetMojoFromReferenceSpace(
+      device::mojom::XRReferenceSpaceCategory category,
+      const device::mojom::VRPosePtr& pose);
+
   // Executes |fn| for each still tracked, non-subsumed plane present in
   // |arcore_planes_|.
   template <typename FunctionType>
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.cc b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
index c3919a2..b1519527 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.cc
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.cc
@@ -196,6 +196,10 @@
   return pose;
 }
 
+float FakeArCore::GetEstimatedFloorHeight() {
+  return 2.0;
+}
+
 bool FakeArCore::RequestHitTest(
     const mojom::XRRayPtr& ray,
     std::vector<mojom::XRHitResultPtr>* hit_results) {
@@ -207,6 +211,23 @@
   return true;
 }
 
+base::Optional<uint32_t> FakeArCore::SubscribeToHitTest(
+    mojom::XRNativeOriginInformationPtr nativeOriginInformation,
+    mojom::XRRayPtr ray) {
+  NOTREACHED();
+  return base::nullopt;
+}
+
+mojom::XRHitTestSubscriptionResultsDataPtr
+FakeArCore::GetHitTestSubscriptionResults(
+    const device::mojom::VRPosePtr& pose) {
+  return nullptr;
+}
+
+void FakeArCore::UnsubscribeFromHitTest(uint32_t subscription_id) {
+  NOTREACHED();
+}
+
 mojom::XRPlaneDetectionDataPtr FakeArCore::GetDetectedPlanesData() {
   std::vector<mojom::XRPlaneDataPtr> result;
 
diff --git a/chrome/browser/android/vr/arcore_device/fake_arcore.h b/chrome/browser/android/vr/arcore_device/fake_arcore.h
index 6735393f..0b9860f 100644
--- a/chrome/browser/android/vr/arcore_device/fake_arcore.h
+++ b/chrome/browser/android/vr/arcore_device/fake_arcore.h
@@ -31,12 +31,24 @@
       const base::span<const float> uvs) override;
   gfx::Transform GetProjectionMatrix(float near, float far) override;
   mojom::VRPosePtr Update(bool* camera_updated) override;
+
   void Pause() override;
   void Resume() override;
 
+  float GetEstimatedFloorHeight() override;
+
   bool RequestHitTest(const mojom::XRRayPtr& ray,
                       std::vector<mojom::XRHitResultPtr>* hit_results) override;
 
+  base::Optional<uint32_t> SubscribeToHitTest(
+      mojom::XRNativeOriginInformationPtr nativeOriginInformation,
+      mojom::XRRayPtr ray) override;
+
+  mojom::XRHitTestSubscriptionResultsDataPtr GetHitTestSubscriptionResults(
+      const device::mojom::VRPosePtr& pose) override;
+
+  void UnsubscribeFromHitTest(uint32_t subscription_id) override;
+
   mojom::XRPlaneDetectionDataPtr GetDetectedPlanesData() override;
   mojom::XRAnchorsDataPtr GetAnchorsData() override;
 
diff --git a/chrome/browser/android/vr/arcore_device/type_converters.cc b/chrome/browser/android/vr/arcore_device/type_converters.cc
index f27ef30..535c4d6 100644
--- a/chrome/browser/android/vr/arcore_device/type_converters.cc
+++ b/chrome/browser/android/vr/arcore_device/type_converters.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/android/vr/arcore_device/type_converters.h"
 
+#include "ui/gfx/transform_util.h"
+
 namespace mojo {
 
 device::mojom::XRPlaneOrientation
@@ -18,4 +20,20 @@
   }
 }
 
+gfx::Transform TypeConverter<gfx::Transform, device::mojom::VRPosePtr>::Convert(
+    const device::mojom::VRPosePtr& pose) {
+  gfx::DecomposedTransform decomposed;
+  if (pose->orientation) {
+    decomposed.quaternion = *pose->orientation;
+  }
+
+  if (pose->position) {
+    decomposed.translate[0] = pose->position->x();
+    decomposed.translate[1] = pose->position->y();
+    decomposed.translate[2] = pose->position->z();
+  }
+
+  return gfx::ComposeTransform(decomposed);
+}
+
 }  // namespace mojo
diff --git a/chrome/browser/android/vr/arcore_device/type_converters.h b/chrome/browser/android/vr/arcore_device/type_converters.h
index c8cf665..3cf27d5 100644
--- a/chrome/browser/android/vr/arcore_device/type_converters.h
+++ b/chrome/browser/android/vr/arcore_device/type_converters.h
@@ -7,6 +7,7 @@
 
 #include "chrome/browser/android/vr/arcore_device/arcore_sdk.h"
 #include "device/vr/public/mojom/vr_service.mojom.h"
+#include "ui/gfx/transform.h"
 
 namespace mojo {
 
@@ -15,6 +16,11 @@
   static device::mojom::XRPlaneOrientation Convert(ArPlaneType plane_type);
 };
 
+template <>
+struct TypeConverter<gfx::Transform, device::mojom::VRPosePtr> {
+  static gfx::Transform Convert(const device::mojom::VRPosePtr& pose);
+};
+
 }  // namespace mojo
 
 #endif  // CHROME_BROWSER_ANDROID_VR_ARCORE_DEVICE_TYPE_CONVERTERS_H_
diff --git a/chrome/browser/autofill/autofill_provider_browsertest.cc b/chrome/browser/autofill/autofill_provider_browsertest.cc
index 35b20b2..e2ea63c3 100644
--- a/chrome/browser/autofill/autofill_provider_browsertest.cc
+++ b/chrome/browser/autofill/autofill_provider_browsertest.cc
@@ -283,38 +283,50 @@
   EXPECT_EQ("\"SUBMISSION_FINISHED\"", message);
 }
 
-IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTest,
-                       LabelTagChangeImpactFormComparingWithFlagOn) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kAutofillSkipComparingInferredLabels);
+class AutofillProviderBrowserTestWithSkipFlagOn
+    : public AutofillProviderBrowserTest {
+ public:
+  AutofillProviderBrowserTestWithSkipFlagOn() {
+    feature_list_.InitAndEnableFeature(
+        features::kAutofillSkipComparingInferredLabels);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class AutofillProviderBrowserTestWithSkipFlagOff
+    : public AutofillProviderBrowserTest {
+ public:
+  AutofillProviderBrowserTestWithSkipFlagOff() {
+    feature_list_.InitAndDisableFeature(
+        features::kAutofillSkipComparingInferredLabels);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTestWithSkipFlagOn,
+                       LabelTagChangeImpactFormComparing) {
   SetLabelChangeExpectationAndTriggerQuery();
   ChangeLabelAndCheckResult("label_id", false /*expect_forms_same*/);
 }
 
-IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTest,
-                       InferredLabelChangeNotImpactFormComparingWithFlagOn) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kAutofillSkipComparingInferredLabels);
+IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTestWithSkipFlagOn,
+                       InferredLabelChangeNotImpactFormComparing) {
   SetLabelChangeExpectationAndTriggerQuery();
   ChangeLabelAndCheckResult("p_id", true /*expect_forms_same*/);
 }
 
-IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTest,
-                       LabelTagChangeImpactFormComparingWithFlagOff) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      features::kAutofillSkipComparingInferredLabels);
+IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTestWithSkipFlagOff,
+                       LabelTagChangeImpactFormComparing) {
   SetLabelChangeExpectationAndTriggerQuery();
   ChangeLabelAndCheckResult("label_id", false /*expect_forms_same*/);
 }
 
-IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTest,
-                       InferredLabelChangeImpactFormComparingWithFlagOff) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      features::kAutofillSkipComparingInferredLabels);
+IN_PROC_BROWSER_TEST_F(AutofillProviderBrowserTestWithSkipFlagOff,
+                       InferredLabelChangeImpactFormComparing) {
   SetLabelChangeExpectationAndTriggerQuery();
   ChangeLabelAndCheckResult("p_id", false /*expect_forms_same*/);
 }
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 6bad9bc..33ea655 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -8,7 +8,9 @@
   </outputs>
   <release seq="1">
     <structures>
-      <structure name="IDR_SIGNIN_SHARED_CSS_HTML" file="resources\signin\signin_shared_css.html" preprocess="true" allowexternalscript="true" type="chrome_html" />
+      <if expr="is_win or is_macosx or desktop_linux">
+        <structure name="IDR_SIGNIN_SHARED_CSS_HTML" file="resources\signin\signin_shared_css.html" preprocess="true" allowexternalscript="true" type="chrome_html" />
+      </if>
       <if expr="not is_android">
         <structure name="IDR_INCOGNITO_TAB_HTML" file="resources\ntp4\incognito_tab.html" flattenhtml="true" type="chrome_html" />
         <structure name="IDR_GUEST_TAB_HTML" file="resources\ntp4\guest_tab.html" flattenhtml="true" type="chrome_html" />
@@ -322,18 +324,20 @@
       <include name="IDR_SITE_ENGAGEMENT_JS" file="resources\engagement\site_engagement.js" flattenhtml="true" type="BINDATA" compress="gzip" />
       <include name="IDR_SITE_ENGAGEMENT_DETAILS_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\engagement\site_engagement_details.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
       <include name="IDR_URL_MOJOM_LITE_JS" file="${root_gen_dir}\url\mojom\url.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
-      <include name="IDR_SYNC_DISABLED_CONFIRMATION_HTML" file="resources\signin\sync_confirmation\sync_disabled_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_SYNC_DISABLED_CONFIRMATION_JS" file="resources\signin\sync_confirmation\sync_disabled_confirmation.js" type="BINDATA" />
-      <include name="IDR_SYNC_CONFIRMATION_HTML" file="resources\signin\sync_confirmation\sync_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_SYNC_CONFIRMATION_JS" file="resources\signin\sync_confirmation\sync_confirmation.js" type="BINDATA" />
-      <include name="IDR_SYNC_CONFIRMATION_BROWSER_PROXY_HTML" file="resources\signin\sync_confirmation\sync_confirmation_browser_proxy.html" type="BINDATA" />
-      <include name="IDR_SYNC_CONFIRMATION_BROWSER_PROXY_JS" file="resources\signin\sync_confirmation\sync_confirmation_browser_proxy.js" type="BINDATA" />
-      <include name="IDR_SYNC_CONFIRMATION_APP_HTML" file="resources\signin\sync_confirmation\sync_confirmation_app.html" type="BINDATA" flattenhtml="true" allowexternalscript="true" />
-      <include name="IDR_SYNC_CONFIRMATION_APP_JS" file="resources\signin\sync_confirmation\sync_confirmation_app.js" type="BINDATA" />
-      <include name="IDR_SIGNIN_EMAIL_CONFIRMATION_HTML" file="resources\signin\signin_email_confirmation\signin_email_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_SIGNIN_EMAIL_CONFIRMATION_JS" file="resources\signin\signin_email_confirmation\signin_email_confirmation.js" type="BINDATA" />
-      <include name="IDR_SIGNIN_ERROR_HTML" file="resources\signin\signin_error\signin_error.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
-      <include name="IDR_SIGNIN_ERROR_JS" file="resources\signin\signin_error\signin_error.js" type="BINDATA" />
+      <if expr="is_win or is_macosx or desktop_linux">
+        <include name="IDR_SYNC_DISABLED_CONFIRMATION_HTML" file="resources\signin\sync_confirmation\sync_disabled_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+        <include name="IDR_SYNC_DISABLED_CONFIRMATION_JS" file="resources\signin\sync_confirmation\sync_disabled_confirmation.js" type="BINDATA" />
+        <include name="IDR_SYNC_CONFIRMATION_HTML" file="resources\signin\sync_confirmation\sync_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+        <include name="IDR_SYNC_CONFIRMATION_JS" file="resources\signin\sync_confirmation\sync_confirmation.js" type="BINDATA" />
+        <include name="IDR_SYNC_CONFIRMATION_BROWSER_PROXY_HTML" file="resources\signin\sync_confirmation\sync_confirmation_browser_proxy.html" type="BINDATA" />
+        <include name="IDR_SYNC_CONFIRMATION_BROWSER_PROXY_JS" file="resources\signin\sync_confirmation\sync_confirmation_browser_proxy.js" type="BINDATA" />
+        <include name="IDR_SYNC_CONFIRMATION_APP_HTML" file="resources\signin\sync_confirmation\sync_confirmation_app.html" type="BINDATA" flattenhtml="true" allowexternalscript="true" />
+        <include name="IDR_SYNC_CONFIRMATION_APP_JS" file="resources\signin\sync_confirmation\sync_confirmation_app.js" type="BINDATA" />
+        <include name="IDR_SIGNIN_EMAIL_CONFIRMATION_HTML" file="resources\signin\signin_email_confirmation\signin_email_confirmation.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+        <include name="IDR_SIGNIN_EMAIL_CONFIRMATION_JS" file="resources\signin\signin_email_confirmation\signin_email_confirmation.js" type="BINDATA" />
+        <include name="IDR_SIGNIN_ERROR_HTML" file="resources\signin\signin_error\signin_error.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
+        <include name="IDR_SIGNIN_ERROR_JS" file="resources\signin\signin_error\signin_error.js" type="BINDATA" />
+      </if>
       <include name="IDR_USB_ENUMERATION_OPTIONS_MOJOM_LITE_JS" file="${root_gen_dir}\services\device\public\mojom\usb_enumeration_options.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_DEVICE_MANAGER_CLIENT_MOJOM_LITE_JS" file="${root_gen_dir}\services\device\public\mojom\usb_manager_client.mojom-lite.js" use_base_dir="false" flattenhtml="true" allowexternalscript="true" type="BINDATA" compress="gzip" />
       <include name="IDR_USB_DEVICE_MANAGER_MOJOM_LITE_JS" file="${root_gen_dir}\services\device\public\mojom\usb_manager.mojom-lite.js" use_base_dir="false" type="BINDATA" compress="gzip" />
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index a48dcf5..164df34 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -41,8 +41,6 @@
 #include "build/branding_buildflags.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/first_run/first_run.h"
-#include "chrome/browser/memory/memory_pressure_monitor.h"
-#include "chrome/browser/memory/swap_thrashing_monitor.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/profiles/profile_shortcut_manager.h"
 #include "chrome/browser/safe_browsing/chrome_cleaner/settings_resetter_win.h"
@@ -610,17 +608,6 @@
   base::PostDelayedTask(FROM_HERE, {content::BrowserThread::UI},
                         base::BindOnce(&DetectFaultTolerantHeap),
                         base::TimeDelta::FromMinutes(1));
-
-  // Start the swap thrashing monitor if it's enabled.
-  //
-  // TODO(sebmarchand): Delay the initialization of this monitor once we start
-  // using this feature by default, this is currently enabled at startup to make
-  // it easier to experiment with this monitor.
-  if (base::FeatureList::IsEnabled(features::kSwapThrashingMonitor))
-    memory::SwapThrashingMonitor::Initialize();
-
-  if (base::FeatureList::IsEnabled(features::kNewMemoryPressureMonitor))
-    memory_pressure_monitor_ = memory::MemoryPressureMonitor::Create();
 }
 
 // static
diff --git a/chrome/browser/chrome_browser_main_win.h b/chrome/browser/chrome_browser_main_win.h
index 382216c..70639aec 100644
--- a/chrome/browser/chrome_browser_main_win.h
+++ b/chrome/browser/chrome_browser_main_win.h
@@ -19,10 +19,6 @@
 class CommandLine;
 }
 
-namespace memory {
-class MemoryPressureMonitor;
-}
-
 // Handle uninstallation when given the appropriate the command-line switch.
 // If |chrome_still_running| is true a modal dialog will be shown asking the
 // user to close the other chrome instance.
@@ -78,11 +74,6 @@
   // Watches module load events and forwards them to the ModuleDatabase.
   std::unique_ptr<ModuleWatcher> module_watcher_;
 
-  // The memory pressure monitor. This is currently only being used to record
-  // metrics, the base::MemoryPressureMonitor is still being used to emit memory
-  // pressure signals.
-  std::unique_ptr<memory::MemoryPressureMonitor> memory_pressure_monitor_;
-
   DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainPartsWin);
 };
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 62a90a4..a89707a 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -72,6 +72,7 @@
 #include "chrome/browser/media/webrtc/audio_debug_recordings_handler.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
 #include "chrome/browser/media/webrtc/webrtc_logging_controller.h"
+#include "chrome/browser/memory/chrome_browser_main_extra_parts_memory.h"
 #include "chrome/browser/metrics/chrome_browser_main_extra_parts_metrics.h"
 #include "chrome/browser/metrics/chrome_feature_list_creator.h"
 #include "chrome/browser/nacl_host/nacl_browser_delegate_impl.h"
@@ -1251,6 +1252,8 @@
 
   main_parts->AddParts(new ChromeBrowserMainExtraPartsProfiling);
 
+  main_parts->AddParts(new ChromeBrowserMainExtraPartsMemory);
+
   chrome::AddMetricsExtraParts(main_parts.get());
 
   return main_parts;
diff --git a/chrome/browser/chrome_content_browser_client_browsertest.cc b/chrome/browser/chrome_content_browser_client_browsertest.cc
index 0f4cf5f5..8a22d3b 100644
--- a/chrome/browser/chrome_content_browser_client_browsertest.cc
+++ b/chrome/browser/chrome_content_browser_client_browsertest.cc
@@ -11,6 +11,7 @@
 #include "base/bind.h"
 #include "base/command_line.h"
 #include "base/macros.h"
+#include "base/no_destructor.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/system/sys_info.h"
@@ -234,10 +235,68 @@
       contents->GetMainFrame()->GetProcess()->GetID()));
 }
 
+enum class SitePerProcessMemoryThreshold {
+  kNone,
+  k128MB,
+  k768MB,
+};
+
+enum class SitePerProcessMode {
+  kDisabled,
+  kEnabled,
+  kIsolatedOrigin,
+};
+
+struct SitePerProcessMemoryThresholdBrowserTestParams {
+  SitePerProcessMemoryThreshold threshold;
+  SitePerProcessMode mode;
+};
+
+const url::Origin& GetTrialOrigin() {
+  static base::NoDestructor<url::Origin> origin{
+      url::Origin::Create(GURL("http://foo.com/"))};
+  return *origin;
+}
+
 // Helper class to run tests on a simulated 512MB low-end device.
-class SitePerProcessMemoryThresholdBrowserTest : public InProcessBrowserTest {
+class SitePerProcessMemoryThresholdBrowserTest
+    : public InProcessBrowserTest,
+      public ::testing::WithParamInterface<
+          SitePerProcessMemoryThresholdBrowserTestParams> {
  public:
-  SitePerProcessMemoryThresholdBrowserTest() = default;
+  SitePerProcessMemoryThresholdBrowserTest() {
+    switch (GetParam().threshold) {
+      case SitePerProcessMemoryThreshold::kNone:
+        break;
+      case SitePerProcessMemoryThreshold::k128MB:
+        threshold_feature_.InitAndEnableFeatureWithParameters(
+            features::kSitePerProcessOnlyForHighMemoryClients,
+            {{features::kSitePerProcessOnlyForHighMemoryClientsParamName,
+              "128"}});
+        break;
+      case SitePerProcessMemoryThreshold::k768MB:
+        threshold_feature_.InitAndEnableFeatureWithParameters(
+            features::kSitePerProcessOnlyForHighMemoryClients,
+            {{features::kSitePerProcessOnlyForHighMemoryClientsParamName,
+              "768"}});
+        break;
+    }
+
+    switch (GetParam().mode) {
+      case SitePerProcessMode::kDisabled:
+        mode_feature_.InitAndDisableFeature(features::kSitePerProcess);
+        break;
+      case SitePerProcessMode::kEnabled:
+        mode_feature_.InitAndEnableFeature(features::kSitePerProcess);
+        break;
+      case SitePerProcessMode::kIsolatedOrigin:
+        mode_feature_.InitAndEnableFeatureWithParameters(
+            features::kIsolateOrigins,
+            {{features::kIsolateOriginsFieldTrialParamName,
+              GetTrialOrigin().Serialize()}});
+        break;
+    }
+  }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     InProcessBrowserTest::SetUpCommandLine(command_line);
@@ -297,142 +356,83 @@
 #endif
 
  private:
+  base::test::ScopedFeatureList threshold_feature_;
+  base::test::ScopedFeatureList mode_feature_;
+
   DISALLOW_COPY_AND_ASSIGN(SitePerProcessMemoryThresholdBrowserTest);
 };
 
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       SitePerProcessEnabled_HighThreshold) {
+using SitePerProcessMemoryThresholdBrowserTestNoIsolation =
+    SitePerProcessMemoryThresholdBrowserTest;
+IN_PROC_BROWSER_TEST_P(SitePerProcessMemoryThresholdBrowserTestNoIsolation,
+                       NoIsolation) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
-  // 512MB of physical memory that the test simulates is below the 768MB
-  // threshold.
-  base::test::ScopedFeatureList memory_feature;
-  memory_feature.InitAndEnableFeatureWithParameters(
-      features::kSitePerProcessOnlyForHighMemoryClients,
-      {{features::kSitePerProcessOnlyForHighMemoryClientsParamName, "768"}});
-
-  base::test::ScopedFeatureList site_per_process;
-  site_per_process.InitAndEnableFeature(features::kSitePerProcess);
-
-  // Despite enabled site-per-process trial, there should be no isolation
-  // because the device has too little memory.
+  // Isolation should be disabled given the set of parameters used to
+  // instantiate these tests.
   EXPECT_FALSE(
       content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
 }
 
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       SitePerProcessEnabled_LowThreshold) {
+using SitePerProcessMemoryThresholdBrowserTestIsolation =
+    SitePerProcessMemoryThresholdBrowserTest;
+IN_PROC_BROWSER_TEST_P(SitePerProcessMemoryThresholdBrowserTestIsolation,
+                       Isolation) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
-  // 512MB of physical memory that the test simulates is above the 128MB
-  // threshold.
-  base::test::ScopedFeatureList memory_feature;
-  memory_feature.InitAndEnableFeatureWithParameters(
-      features::kSitePerProcessOnlyForHighMemoryClients,
-      {{features::kSitePerProcessOnlyForHighMemoryClientsParamName, "128"}});
-
-  base::test::ScopedFeatureList site_per_process;
-  site_per_process.InitAndEnableFeature(features::kSitePerProcess);
-
-  // site-per-process trial is enabled, and the memory threshold is above the
-  // memory present on the device.
+  // Isolation should be enabled given the set of parameters used to
+  // instantiate these tests.
   EXPECT_TRUE(content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
 }
 
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       SitePerProcessEnabled_NoThreshold) {
-  if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
-    return;
-
-  base::test::ScopedFeatureList site_per_process;
-  site_per_process.InitAndEnableFeature(features::kSitePerProcess);
-
+INSTANTIATE_TEST_SUITE_P(
+    NoIsolation,
+    SitePerProcessMemoryThresholdBrowserTestNoIsolation,
+    testing::Values(
 #if defined(OS_ANDROID)
-  // Expect false on Android because 512MB physical memory triggered by
-  // kEnableLowEndDeviceMode in SetUpCommandLine() is below the 1024MB Android
-  // specific memory limit which disbles site isolation for all sites.
-  EXPECT_FALSE(
-      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
-#else
-  EXPECT_TRUE(content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
+        // Expect no isolation on Android because 512MB physical memory
+        // triggered by kEnableLowEndDeviceMode in SetUpCommandLine() is below
+        // the 1024MB Android specific memory limit which disables site
+        // isolation for all sites.
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::kNone, SitePerProcessMode::kEnabled},
 #endif
-}
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::k768MB,
+            SitePerProcessMode::kEnabled},
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::kNone,
+            SitePerProcessMode::kDisabled},
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::k128MB,
+            SitePerProcessMode::kDisabled},
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::k768MB,
+            SitePerProcessMode::kDisabled}));
 
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       SitePerProcessDisabled_HighThreshold) {
+INSTANTIATE_TEST_SUITE_P(Isolation,
+                         SitePerProcessMemoryThresholdBrowserTestIsolation,
+                         testing::Values(
+#if !defined(OS_ANDROID)
+                             // See the note above regarding why this
+                             // expectation is different on Android.
+                             SitePerProcessMemoryThresholdBrowserTestParams{
+                                 SitePerProcessMemoryThreshold::kNone,
+                                 SitePerProcessMode::kEnabled},
+#endif
+                             SitePerProcessMemoryThresholdBrowserTestParams{
+                                 SitePerProcessMemoryThreshold::k128MB,
+                                 SitePerProcessMode::kEnabled}));
+
+using SitePerProcessMemoryThresholdBrowserTestNoIsolatedOrigin =
+    SitePerProcessMemoryThresholdBrowserTest;
+IN_PROC_BROWSER_TEST_P(SitePerProcessMemoryThresholdBrowserTestNoIsolatedOrigin,
+                       TrialNoIsolatedOrigin) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
-  // 512MB of physical memory that the test simulates is below the 768MB
-  // threshold.
-  base::test::ScopedFeatureList memory_feature;
-  memory_feature.InitAndEnableFeatureWithParameters(
-      features::kSitePerProcessOnlyForHighMemoryClients,
-      {{features::kSitePerProcessOnlyForHighMemoryClientsParamName, "768"}});
-
-  base::test::ScopedFeatureList site_per_process;
-  site_per_process.InitAndDisableFeature(features::kSitePerProcess);
-
-  // site-per-process trial is disabled, so isolation should be disabled
-  // (i.e. the memory threshold should be ignored).
-  EXPECT_FALSE(
-      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
-}
-
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       SitePerProcessDisabled_LowThreshold) {
-  if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
-    return;
-
-  // 512MB of physical memory that the test simulates is above the 128MB
-  // threshold.
-  base::test::ScopedFeatureList memory_feature;
-  memory_feature.InitAndEnableFeatureWithParameters(
-      features::kSitePerProcessOnlyForHighMemoryClients,
-      {{features::kSitePerProcessOnlyForHighMemoryClientsParamName, "128"}});
-
-  base::test::ScopedFeatureList site_per_process;
-  site_per_process.InitAndDisableFeature(features::kSitePerProcess);
-
-  // site-per-process trial is disabled, so isolation should be disabled
-  // (i.e. the memory threshold should be ignored).
-  EXPECT_FALSE(
-      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
-}
-
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       SitePerProcessDisabled_NoThreshold) {
-  if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
-    return;
-
-  base::test::ScopedFeatureList site_per_process;
-  site_per_process.InitAndDisableFeature(features::kSitePerProcess);
-
-  // site-per-process trial is disabled, so isolation should be disabled
-  // (i.e. the memory threshold should be ignored).
-  EXPECT_FALSE(
-      content::SiteIsolationPolicy::UseDedicatedProcessesForAllSites());
-}
-
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       TrialIsolatedOrigins_HighThreshold) {
-  if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
-    return;
-
-  // 512MB of physical memory that the test simulates is below the 768MB
-  // threshold.
-  base::test::ScopedFeatureList memory_feature;
-  memory_feature.InitAndEnableFeatureWithParameters(
-      features::kSitePerProcessOnlyForHighMemoryClients,
-      {{features::kSitePerProcessOnlyForHighMemoryClientsParamName, "768"}});
-
-  const url::Origin trial_origin = url::Origin::Create(GURL("http://foo.com/"));
-  base::test::ScopedFeatureList isolated_origins_feature;
-  isolated_origins_feature.InitAndEnableFeatureWithParameters(
-      features::kIsolateOrigins, {{features::kIsolateOriginsFieldTrialParamName,
-                                   trial_origin.Serialize()}});
   SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();
 
   auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
@@ -446,26 +446,16 @@
 
   // Verify that the trial origin is not present.
   EXPECT_THAT(isolated_origins,
-              ::testing::Not(::testing::Contains(trial_origin)));
+              ::testing::Not(::testing::Contains(GetTrialOrigin())));
 }
 
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       TrialIsolatedOrigins_LowThreshold) {
+using SitePerProcessMemoryThresholdBrowserTestIsolatedOrigin =
+    SitePerProcessMemoryThresholdBrowserTest;
+IN_PROC_BROWSER_TEST_P(SitePerProcessMemoryThresholdBrowserTestIsolatedOrigin,
+                       TrialIsolatedOrigin) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
-  // 512MB of physical memory that the test simulates is above the 128MB
-  // threshold.
-  base::test::ScopedFeatureList memory_feature;
-  memory_feature.InitAndEnableFeatureWithParameters(
-      features::kSitePerProcessOnlyForHighMemoryClients,
-      {{features::kSitePerProcessOnlyForHighMemoryClientsParamName, "128"}});
-
-  const url::Origin trial_origin = url::Origin::Create(GURL("http://foo.com/"));
-  base::test::ScopedFeatureList isolated_origins_feature;
-  isolated_origins_feature.InitAndEnableFeatureWithParameters(
-      features::kIsolateOrigins, {{features::kIsolateOriginsFieldTrialParamName,
-                                   trial_origin.Serialize()}});
   SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();
 
   auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
@@ -475,36 +465,37 @@
               ::testing::IsSubsetOf(isolated_origins));
 
   // Verify that the trial origin is present.
-  EXPECT_THAT(isolated_origins, ::testing::Contains(trial_origin));
+  EXPECT_THAT(isolated_origins, ::testing::Contains(GetTrialOrigin()));
 }
 
-IN_PROC_BROWSER_TEST_F(SitePerProcessMemoryThresholdBrowserTest,
-                       TrialIsolatedOrigins_NoThreshold) {
-  if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
-    return;
+INSTANTIATE_TEST_SUITE_P(
+    TrialNoIsolatedOrigin,
+    SitePerProcessMemoryThresholdBrowserTestNoIsolatedOrigin,
+    testing::Values(
+#if defined(OS_ANDROID)
+        // The 512MB the test simulates is below the global Android threshold of
+        // 1024MB, so the test origin should not be isolated.
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::kNone,
+            SitePerProcessMode::kIsolatedOrigin},
+#endif
+        SitePerProcessMemoryThresholdBrowserTestParams{
+            SitePerProcessMemoryThreshold::k768MB,
+            SitePerProcessMode::kIsolatedOrigin}));
 
-  const url::Origin trial_origin = url::Origin::Create(GURL("http://foo.com/"));
-  base::test::ScopedFeatureList isolated_origins_feature;
-  isolated_origins_feature.InitAndEnableFeatureWithParameters(
-      features::kIsolateOrigins, {{features::kIsolateOriginsFieldTrialParamName,
-                                   trial_origin.Serialize()}});
-  SiteIsolationPolicy::ApplyGlobalIsolatedOrigins();
-
-  auto* cpsp = content::ChildProcessSecurityPolicy::GetInstance();
-  std::vector<url::Origin> isolated_origins = cpsp->GetIsolatedOrigins();
-  EXPECT_EQ(kExpectedTrialOrigins + expected_embedder_origins_.size(),
-            isolated_origins.size());
-  EXPECT_THAT(expected_embedder_origins_,
-              ::testing::IsSubsetOf(isolated_origins));
-
-  if (kExpectedTrialOrigins > 0) {
-    // Verify that the trial origin is present.
-    EXPECT_THAT(isolated_origins, ::testing::Contains(trial_origin));
-  } else {
-    EXPECT_THAT(isolated_origins,
-                ::testing::Not(::testing::Contains(trial_origin)));
-  }
-}
+INSTANTIATE_TEST_SUITE_P(TrialIsolatedOrigin,
+                         SitePerProcessMemoryThresholdBrowserTestIsolatedOrigin,
+                         testing::Values(
+#if defined(OS_ANDROID)
+                             // See the note above regarding why this
+                             // expectation is different on Android.
+                             SitePerProcessMemoryThresholdBrowserTestParams{
+                                 SitePerProcessMemoryThreshold::kNone,
+                                 SitePerProcessMode::kIsolatedOrigin},
+#endif
+                             SitePerProcessMemoryThresholdBrowserTestParams{
+                                 SitePerProcessMemoryThreshold::k128MB,
+                                 SitePerProcessMode::kIsolatedOrigin}));
 
 // Helper class to run tests with password-triggered site isolation initialized
 // via a regular field trial and *not* via a command-line override.  It
@@ -571,7 +562,7 @@
 };
 
 IN_PROC_BROWSER_TEST_F(EnabledPasswordSiteIsolationFieldTrialTest,
-                       BelowThreshold) {
+                       DISABLED_BelowThreshold) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
@@ -603,7 +594,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(EnabledPasswordSiteIsolationFieldTrialTest,
-                       AboveThreshold) {
+                       DISABLED_AboveThreshold) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
@@ -636,7 +627,7 @@
 // via field trials but force-enabled via command line, it takes effect even
 // when below the memory threshold.  See https://crbug.com/1009828.
 IN_PROC_BROWSER_TEST_F(DisabledPasswordSiteIsolationFieldTrialTest,
-                       CommandLineOverride_BelowThreshold) {
+                       DISABLED_CommandLineOverride_BelowThreshold) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
@@ -670,7 +661,7 @@
 // Similar to the test above, but with device memory being above memory
 // threshold.
 IN_PROC_BROWSER_TEST_F(DisabledPasswordSiteIsolationFieldTrialTest,
-                       CommandLineOverride_AboveThreshold) {
+                       DISABLED_CommandLineOverride_AboveThreshold) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
@@ -760,7 +751,7 @@
 // the device is above the memory threshold, disabling it via the command line
 // takes precedence.
 IN_PROC_BROWSER_TEST_F(EnabledStrictOriginIsolationFieldTrialTest,
-                       DisabledViaCommandLineOverride) {
+                       DISABLED_DisabledViaCommandLineOverride) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
@@ -791,7 +782,7 @@
 // via field trials but force-enabled via command line, it takes effect even
 // when below the memory threshold.  See https://crbug.com/1009828.
 IN_PROC_BROWSER_TEST_F(DisabledStrictOriginIsolationFieldTrialTest,
-                       EnabledViaCommandLineOverride_BelowThreshold) {
+                       DISABLED_EnabledViaCommandLineOverride_BelowThreshold) {
   if (ShouldSkipBecauseOfConflictingCommandLineSwitches())
     return;
 
@@ -893,7 +884,9 @@
 class PrefersColorSchemeTest : public testing::WithParamInterface<bool>,
                                public InProcessBrowserTest {
  protected:
-  PrefersColorSchemeTest() : theme_client_(&test_theme_) {}
+  PrefersColorSchemeTest() : theme_client_(&test_theme_) {
+    feature_list_.InitWithFeatureState(features::kWebUIDarkMode, GetParam());
+  }
 
   ~PrefersColorSchemeTest() {
     CHECK_EQ(&theme_client_, SetBrowserClientForTesting(original_client_));
@@ -934,6 +927,7 @@
     const ui::NativeTheme* const theme_;
   };
 
+  base::test::ScopedFeatureList feature_list_;
   ChromeContentBrowserClientWithWebTheme theme_client_;
 };
 
@@ -957,9 +951,6 @@
 IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, FeatureOverridesChromeSchemes) {
   test_theme_.SetDarkMode(true);
 
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatureState(features::kWebUIDarkMode, GetParam());
-
   ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIDownloadsURL));
 
   bool matches;
@@ -976,9 +967,6 @@
 IN_PROC_BROWSER_TEST_P(PrefersColorSchemeTest, FeatureOverridesPdfUI) {
   test_theme_.SetDarkMode(true);
 
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatureState(features::kWebUIDarkMode, GetParam());
-
   std::string pdf_extension_url(extensions::kExtensionScheme);
   pdf_extension_url.append(url::kStandardSchemeSeparator);
   pdf_extension_url.append(extension_misc::kPdfExtensionId);
diff --git a/chrome/browser/dom_distiller/dom_distiller_service_factory.cc b/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
index 8ed88c0..0ac7729 100644
--- a/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
+++ b/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
@@ -13,7 +13,6 @@
 #include "components/dom_distiller/content/browser/distiller_page_web_contents.h"
 #include "components/dom_distiller/core/article_entry.h"
 #include "components/dom_distiller/core/distiller.h"
-#include "components/dom_distiller/core/dom_distiller_store.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/storage_partition.h"
@@ -22,12 +21,10 @@
 namespace dom_distiller {
 
 DomDistillerContextKeyedService::DomDistillerContextKeyedService(
-    std::unique_ptr<DomDistillerStoreInterface> store,
     std::unique_ptr<DistillerFactory> distiller_factory,
     std::unique_ptr<DistillerPageFactory> distiller_page_factory,
     std::unique_ptr<DistilledPagePrefs> distilled_page_prefs)
-    : DomDistillerService(std::move(store),
-                          std::move(distiller_factory),
+    : DomDistillerService(std::move(distiller_factory),
                           std::move(distiller_page_factory),
                           std::move(distilled_page_prefs)) {}
 
@@ -62,8 +59,6 @@
   base::FilePath database_dir(
       context->GetPath().Append(FILE_PATH_LITERAL("Articles")));
 
-  auto dom_distiller_store = std::make_unique<DomDistillerStore>();
-
   std::unique_ptr<DistillerPageFactory> distiller_page_factory(
       new DistillerPageWebContentsFactory(context));
   std::unique_ptr<DistillerURLFetcherFactory> distiller_url_fetcher_factory(
@@ -87,9 +82,9 @@
       new DistilledPagePrefs(profile->GetPrefs()));
 
   DomDistillerContextKeyedService* service =
-      new DomDistillerContextKeyedService(
-          std::move(dom_distiller_store), std::move(distiller_factory),
-          std::move(distiller_page_factory), std::move(distilled_page_prefs));
+      new DomDistillerContextKeyedService(std::move(distiller_factory),
+                                          std::move(distiller_page_factory),
+                                          std::move(distilled_page_prefs));
 
   return service;
 }
diff --git a/chrome/browser/dom_distiller/dom_distiller_service_factory.h b/chrome/browser/dom_distiller/dom_distiller_service_factory.h
index db65e63..e28ad13f 100644
--- a/chrome/browser/dom_distiller/dom_distiller_service_factory.h
+++ b/chrome/browser/dom_distiller/dom_distiller_service_factory.h
@@ -24,7 +24,6 @@
                                         public DomDistillerService {
  public:
   DomDistillerContextKeyedService(
-      std::unique_ptr<DomDistillerStoreInterface> store,
       std::unique_ptr<DistillerFactory> distiller_factory,
       std::unique_ptr<DistillerPageFactory> distiller_page_factory,
       std::unique_ptr<DistilledPagePrefs> distilled_page_prefs);
diff --git a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
index 4e35444..006d5aa 100644
--- a/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
+++ b/chrome/browser/dom_distiller/dom_distiller_viewer_source_browsertest.cc
@@ -28,7 +28,6 @@
 #include "components/dom_distiller/core/distilled_page_prefs.h"
 #include "components/dom_distiller/core/distiller.h"
 #include "components/dom_distiller/core/dom_distiller_service.h"
-#include "components/dom_distiller/core/dom_distiller_store.h"
 #include "components/dom_distiller/core/dom_distiller_switches.h"
 #include "components/dom_distiller/core/fake_distiller.h"
 #include "components/dom_distiller/core/fake_distiller_page.h"
@@ -84,17 +83,6 @@
     "window.domAutomationController.send("
     "typeof distiller == 'object')";
 
-ArticleEntry CreateEntry(const std::string& entry_id,
-                         const std::string& page_url) {
-  ArticleEntry entry;
-  entry.set_entry_id(entry_id);
-  if (!page_url.empty()) {
-    ArticleEntryPage* page = entry.add_pages();
-    page->set_url(page_url);
-  }
-  return entry;
-}
-
 void ExpectBodyHasThemeAndFont(content::WebContents* contents,
                                const std::string& expected_theme,
                                const std::string& expected_font) {
@@ -128,7 +116,6 @@
     auto distiller_page_factory = std::make_unique<MockDistillerPageFactory>();
     auto* distiller_page_factory_raw = distiller_page_factory.get();
     auto service = std::make_unique<DomDistillerContextKeyedService>(
-        std::make_unique<DomDistillerStore>(store_model_),
         std::move(distiller_factory), std::move(distiller_page_factory),
         std::make_unique<DistilledPagePrefs>(
             Profile::FromBrowserContext(context)->GetPrefs()));
@@ -153,27 +140,12 @@
   void PrefTest(bool is_error_page);
 
   // Database entries.
-  std::vector<ArticleEntry> store_model_;
   bool expect_distillation_ = false;
   bool expect_distiller_page_ = false;
   MockDistillerFactory* distiller_factory_ = nullptr;
 };
 
 // The DomDistillerViewerSource renders untrusted content, so ensure no bindings
-// are enabled when the article exists in the database.
-IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
-                       NoWebUIBindingsArticleExists) {
-  // Ensure there is one item in the database, which will trigger distillation.
-  const ArticleEntry entry = CreateEntry("DISTILLED", "http://example.com/1");
-  store_model_.push_back(entry);
-  expect_distillation_ = true;
-  expect_distiller_page_ = true;
-  const GURL url = url_utils::GetDistillerViewUrlFromEntryId(
-      kDomDistillerScheme, entry.entry_id());
-  ViewSingleDistilledPage(url, "text/html");
-}
-
-// The DomDistillerViewerSource renders untrusted content, so ensure no bindings
 // are enabled when the article is not found.
 IN_PROC_BROWSER_TEST_F(DomDistillerViewerSourceBrowserTest,
                        NoWebUIBindingsArticleNotFound) {
diff --git a/chrome/browser/download/download_browsertest.cc b/chrome/browser/download/download_browsertest.cc
index 5dfb413..b9fe9d10 100644
--- a/chrome/browser/download/download_browsertest.cc
+++ b/chrome/browser/download/download_browsertest.cc
@@ -95,6 +95,7 @@
 #include "components/safe_browsing/common/safe_browsing_prefs.h"
 #include "components/safe_browsing/proto/csd.pb.h"
 #include "components/safe_browsing/safe_browsing_service_interface.h"
+#include "components/security_state/core/features.h"
 #include "components/security_state/core/security_state.h"
 #include "components/services/quarantine/test_support.h"
 #include "content/public/browser/browser_task_traits.h"
@@ -3886,9 +3887,11 @@
  public:
   DownloadTestWithOptionalSafetyTipsFeature() {
     if (GetParam())
-      feature_list_.InitAndEnableFeature(features::kSafetyTipUI);
+      feature_list_.InitAndEnableFeature(
+          security_state::features::kSafetyTipUI);
     else
-      feature_list_.InitAndDisableFeature(features::kSafetyTipUI);
+      feature_list_.InitAndDisableFeature(
+          security_state::features::kSafetyTipUI);
   }
 
  private:
diff --git a/chrome/browser/download/download_offline_content_provider.cc b/chrome/browser/download/download_offline_content_provider.cc
index 40217cc..c5b8482a 100644
--- a/chrome/browser/download/download_offline_content_provider.cc
+++ b/chrome/browser/download/download_offline_content_provider.cc
@@ -422,7 +422,8 @@
   observers_.RemoveObserver(observer);
 }
 
-void DownloadOfflineContentProvider::OnManagerGoingDown() {
+void DownloadOfflineContentProvider::OnManagerGoingDown(
+    SimpleDownloadManagerCoordinator* manager) {
   std::vector<DownloadItem*> all_items;
   GetAllDownloads(&all_items);
 
diff --git a/chrome/browser/download/download_offline_content_provider.h b/chrome/browser/download/download_offline_content_provider.h
index 6047469..289a56a9 100644
--- a/chrome/browser/download/download_offline_content_provider.h
+++ b/chrome/browser/download/download_offline_content_provider.h
@@ -108,7 +108,7 @@
 
   // SimpleDownloadManagerCoordinator::Observer overrides
   void OnDownloadsInitialized(bool active_downloads_only) override;
-  void OnManagerGoingDown() override;
+  void OnManagerGoingDown(SimpleDownloadManagerCoordinator* manager) override;
 
   void GetAllDownloads(std::vector<DownloadItem*>* all_items);
   DownloadItem* GetDownload(const std::string& download_guid);
diff --git a/chrome/browser/extensions/activity_log/activity_log.cc b/chrome/browser/extensions/activity_log/activity_log.cc
index 62328d8..b4696bd1 100644
--- a/chrome/browser/extensions/activity_log/activity_log.cc
+++ b/chrome/browser/extensions/activity_log/activity_log.cc
@@ -768,29 +768,19 @@
       // we want to ensure the activity log is inactive unless the switch
       // or the app (covered by active_consumers_) is present.
       (has_listeners_ && has_switch);
-  const bool needs_db = has_consumer || has_switch;
-  const bool should_be_active = needs_db || has_consumer;
+  const bool should_be_active = has_consumer || has_switch;
 
   if (should_be_active == is_active_)
     return;
 
   bool has_db = db_enabled_ && database_policy_;
-  bool old_is_active = is_active_;
+  db_enabled_ = should_be_active;
 
-  if (should_be_active) {
-    if (needs_db && !has_db) {
-      db_enabled_ = true;
-      ChooseDatabasePolicy();
-    }
+  if (should_be_active && !has_db)
+    ChooseDatabasePolicy();
 
-    is_active_ = true;
-  } else {
-    if (has_db && !needs_db)
-      db_enabled_ = false;
-    is_active_ = false;
-  }
+  is_active_ = should_be_active;
 
-  DCHECK_NE(is_active_, old_is_active);
   for (content::RenderProcessHost::iterator iter(
            content::RenderProcessHost::AllHostsIterator());
        !iter.IsAtEnd(); iter.Advance()) {
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
index ede8794..a2fabc3 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_apitest.cc
@@ -400,17 +400,38 @@
       "ContentSettings.ExtensionNonEmbeddedSettingSet", 2);
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest, EmbeddedSettings) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(features::kPermissionDelegation);
+class ExtensionContentSettingsApiTestWithPermissionDelegationDisabled
+    : public ExtensionContentSettingsApiTest {
+ public:
+  ExtensionContentSettingsApiTestWithPermissionDelegationDisabled() {
+    feature_list_.InitAndDisableFeature(features::kPermissionDelegation);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class ExtensionContentSettingsApiTestWithPermissionDelegationEnabled
+    : public ExtensionContentSettingsApiTest {
+ public:
+  ExtensionContentSettingsApiTestWithPermissionDelegationEnabled() {
+    feature_list_.InitAndEnableFeature(features::kPermissionDelegation);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    ExtensionContentSettingsApiTestWithPermissionDelegationDisabled,
+    EmbeddedSettings) {
   const char kExtensionPath[] = "content_settings/embeddedsettings";
   EXPECT_TRUE(RunExtensionSubtest(kExtensionPath, "test.html")) << message_;
 }
 
-IN_PROC_BROWSER_TEST_F(ExtensionContentSettingsApiTest,
-                       EmbeddedSettingsPermissionDelegation) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kPermissionDelegation);
+IN_PROC_BROWSER_TEST_F(
+    ExtensionContentSettingsApiTestWithPermissionDelegationEnabled,
+    EmbeddedSettings) {
   const char kExtensionPath[] = "content_settings/embeddedsettings";
   EXPECT_TRUE(
       RunExtensionSubtest(kExtensionPath, "test.html?permission_delegation"))
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
index 8d091f2..3e6af3e 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
@@ -64,15 +64,23 @@
   DISALLOW_COPY_AND_ASSIGN(TestProcessManagerObserver);
 };
 
+class NativeMessagingApiTest : public ExtensionApiTest {
+ public:
+  NativeMessagingApiTest() {
+    feature_list_.InitAndEnableFeature(features::kOnConnectNative);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Disabled on Windows due to timeouts; see https://crbug.com/984897.
 #if defined(OS_WIN)
 #define MAYBE_NativeMessagingLaunch DISABLED_NativeMessagingLaunch
 #else
 #define MAYBE_NativeMessagingLaunch NativeMessagingLaunch
 #endif
-IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_NativeMessagingLaunch) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kOnConnectNative);
+IN_PROC_BROWSER_TEST_F(NativeMessagingApiTest, MAYBE_NativeMessagingLaunch) {
   ProcessManager::SetEventPageIdleTimeForTesting(1);
   ProcessManager::SetEventPageSuspendingTimeForTesting(1);
   extensions::ScopedTestNativeMessagingHost test_host;
@@ -113,10 +121,8 @@
 // natively-initiated connections is not allowed. The test extension expects the
 // channel to be immediately closed with an error.
 IN_PROC_BROWSER_TEST_F(
-    ExtensionApiTest,
+    NativeMessagingApiTest,
     NativeMessagingLaunch_LaunchFromNativeUnsupportedByNativeHost) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kOnConnectNative);
   ProcessManager::SetEventPageIdleTimeForTesting(1);
   ProcessManager::SetEventPageSuspendingTimeForTesting(1);
   extensions::ScopedTestNativeMessagingHost test_host;
diff --git a/chrome/browser/extensions/content_script_apitest.cc b/chrome/browser/extensions/content_script_apitest.cc
index 84549235..c697c4c 100644
--- a/chrome/browser/extensions/content_script_apitest.cc
+++ b/chrome/browser/extensions/content_script_apitest.cc
@@ -505,10 +505,19 @@
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
-IN_PROC_BROWSER_TEST_F(ContentScriptApiTest,
+class ContentScriptApiTestWithTrustedDOMTypesEnabled
+    : public ContentScriptApiTest {
+ public:
+  ContentScriptApiTestWithTrustedDOMTypesEnabled() {
+    feature_list_.InitAndEnableFeature(features::kTrustedDOMTypes);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(ContentScriptApiTestWithTrustedDOMTypesEnabled,
                        ContentScriptBypassPageTrustedTypes) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kTrustedDOMTypes);
   ASSERT_TRUE(StartEmbeddedTestServer());
   extensions::ResultCatcher catcher;
   ASSERT_TRUE(RunExtensionTest("content_scripts/bypass_page_trusted_types"))
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 17195be4..b974987950 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2257,11 +2257,6 @@
     "expiry_milestone": 82
   },
   {
-    "name": "grid-layout-for-ntp-shortcuts",
-    "owners": [ "kristipark", "ramyan" ],
-    "expiry_milestone": 78
-  },
-  {
     "name": "handwriting-gesture",
     "owners": [ "essential-inputs-team@google.com" ],
     "expiry_milestone": 80
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8f2ff3d0..252d3b0 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2870,12 +2870,6 @@
     "Enables the native DIAL Media Route Provider implementation to be used "
     "instead of the implementation in the Media Router component extension.";
 
-const char kGridLayoutForNtpShortcutsName[] =
-    "Enable grid layout for NTP shortcuts";
-const char kGridLayoutForNtpShortcutsDescription[] =
-    "Enables better animations for the shortcuts, including improved "
-    "drag-and-drop.";
-
 const char kNtpCustomizationMenuV2Name[] = "NTP customization menu version 2";
 const char kNtpCustomizationMenuV2Description[] =
     "Use the second version of the NTP customization menu.";
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index d9b73ef..7ddc665 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -1685,9 +1685,6 @@
 extern const char kDialMediaRouteProviderName[];
 extern const char kDialMediaRouteProviderDescription[];
 
-extern const char kGridLayoutForNtpShortcutsName[];
-extern const char kGridLayoutForNtpShortcutsDescription[];
-
 extern const char kNtpCustomizationMenuV2Name[];
 extern const char kNtpCustomizationMenuV2Description[];
 
diff --git a/chrome/browser/geolocation/geolocation_browsertest.cc b/chrome/browser/geolocation/geolocation_browsertest.cc
index 09b97310..a2feb74 100644
--- a/chrome/browser/geolocation/geolocation_browsertest.cc
+++ b/chrome/browser/geolocation/geolocation_browsertest.cc
@@ -538,14 +538,23 @@
   ExpectPosition(fake_latitude(), fake_longitude());
 }
 
-IN_PROC_BROWSER_TEST_F(GeolocationBrowserTest, IFramesWithFreshPosition) {
-  // When permission delegation is enabled, there isn't a way to have a pending
-  // permission prompt when permission has already been granted in another frame
-  // on the same page. That means that this test isn't relevant and can be
-  // deleted after the feature is enabled by default.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(features::kPermissionDelegation);
+// When permission delegation is enabled, there isn't a way to have a pending
+// permission prompt when permission has already been granted in another frame
+// on the same page. That means that once the feature is enabled by default,
+// tests which use this fixture are no longer relevant and can be deleted.
+class GeolocationBrowserTestWithNoPermissionDelegation
+    : public GeolocationBrowserTest {
+ public:
+  GeolocationBrowserTestWithNoPermissionDelegation() {
+    feature_list_.InitAndDisableFeature(features::kPermissionDelegation);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(GeolocationBrowserTestWithNoPermissionDelegation,
+                       IFramesWithFreshPosition) {
   set_html_for_tests("/geolocation/two_iframes.html");
   ASSERT_NO_FATAL_FAILURE(Initialize(INITIALIZATION_DEFAULT));
   LoadIFrames();
@@ -599,12 +608,8 @@
   ExpectPosition(cached_position_latitude, cached_position_lognitude);
 }
 
-IN_PROC_BROWSER_TEST_F(GeolocationBrowserTest, CancelPermissionForFrame) {
-  // When permission delegation is removed, iframe requests are made for the top
-  // level frame. Navigating the iframe should not cancel the request. This
-  // test can be removed after the feature is enabled by default.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(features::kPermissionDelegation);
+IN_PROC_BROWSER_TEST_F(GeolocationBrowserTestWithNoPermissionDelegation,
+                       CancelPermissionForFrame) {
   set_html_for_tests("/geolocation/two_iframes.html");
   ASSERT_NO_FATAL_FAILURE(Initialize(INITIALIZATION_DEFAULT));
   LoadIFrames();
diff --git a/chrome/browser/lookalikes/safety_tips/reputation_service.cc b/chrome/browser/lookalikes/safety_tips/reputation_service.cc
index 7209553..5fe6099 100644
--- a/chrome/browser/lookalikes/safety_tips/reputation_service.cc
+++ b/chrome/browser/lookalikes/safety_tips/reputation_service.cc
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
 #include "base/memory/singleton.h"
+#include "base/metrics/field_trial_params.h"
 #include "chrome/browser/lookalikes/lookalike_url_interstitial_page.h"
 #include "chrome/browser/lookalikes/lookalike_url_navigation_throttle.h"
 #include "chrome/browser/lookalikes/lookalike_url_service.h"
@@ -17,15 +18,16 @@
 #include "chrome/browser/lookalikes/safety_tips/safety_tips_config.h"
 #include "chrome/browser/profiles/incognito_helpers.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/common/chrome_features.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "components/safe_browsing/db/v4_protocol_manager_util.h"
+#include "components/security_state/core/features.h"
 #include "url/url_constants.h"
 
 namespace {
 
 using chrome_browser_safety_tips::FlaggedPage;
+using chrome_browser_safety_tips::UrlPattern;
 using lookalikes::DomainInfo;
 using lookalikes::LookalikeUrlNavigationThrottle;
 using lookalikes::LookalikeUrlService;
@@ -34,9 +36,10 @@
 using safety_tips::ReputationService;
 
 const base::FeatureParam<bool> kEnableLookalikeEditDistance{
-    &features::kSafetyTipUI, "editdistance", false};
+    &security_state::features::kSafetyTipUI, "editdistance", false};
 const base::FeatureParam<bool> kEnableLookalikeEditDistanceSiteEngagement{
-    &features::kSafetyTipUI, "editdistance_siteengagement", false};
+    &security_state::features::kSafetyTipUI, "editdistance_siteengagement",
+    false};
 
 bool ShouldTriggerSafetyTipFromLookalike(
     const GURL& url,
@@ -162,6 +165,43 @@
   return security_state::SafetyTipStatus::kNone;
 }
 
+// Returns whether or not the Safety Tip should be suppressed for the given URL.
+// Checks SafeBrowsing-style permutations of |url| against the component updater
+// allowlist and returns whether the URL is explicitly allowed. Fails closed, so
+// that warnings are suppressed if the component is unavailable.
+bool ShouldSuppressWarning(const GURL& url) {
+  std::vector<std::string> patterns;
+  UrlToPatterns(url, &patterns);
+
+  auto* proto = safety_tips::GetRemoteConfigProto();
+  if (!proto) {
+    // This happens when the component hasn't downloaded yet. This should only
+    // happen for a short time after initial upgrade to M79.
+    //
+    // Disable all Safety Tips during that time. Otherwise, we would continue to
+    // flag on any known false positives until the client received the update.
+    return true;
+  }
+
+  auto allowed_pages = proto->allowed_pattern();
+  for (const auto& pattern : patterns) {
+    UrlPattern search_target;
+    search_target.set_pattern(pattern);
+
+    auto lower = std::lower_bound(
+        allowed_pages.begin(), allowed_pages.end(), search_target,
+        [](const UrlPattern& a, const UrlPattern& b) -> bool {
+          return a.pattern() < b.pattern();
+        });
+
+    if (lower != allowed_pages.end() && pattern == lower->pattern()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 }  // namespace
 
 namespace safety_tips {
@@ -219,6 +259,15 @@
     const std::vector<DomainInfo>& engaged_sites) {
   const DomainInfo navigated_domain = lookalikes::GetDomainInfo(url);
 
+  // 0. Server-side warning suppression.
+  // If the URL is on the allowlist list, do nothing else. This is only used to
+  // mitigate false positives, so no further processing should be done.
+  if (ShouldSuppressWarning(url)) {
+    std::move(callback).Run(security_state::SafetyTipStatus::kNone,
+                            IsIgnored(url), url, GURL());
+    return;
+  }
+
   // 1. Engagement check
   // Ensure that this URL is not already engaged. We can't use the synchronous
   // SiteEngagementService::IsEngagementAtLeast as it has side effects.  This
diff --git a/chrome/browser/lookalikes/safety_tips/reputation_web_contents_observer.cc b/chrome/browser/lookalikes/safety_tips/reputation_web_contents_observer.cc
index 10f8b41..b0cfd80 100644
--- a/chrome/browser/lookalikes/safety_tips/reputation_web_contents_observer.cc
+++ b/chrome/browser/lookalikes/safety_tips/reputation_web_contents_observer.cc
@@ -11,7 +11,7 @@
 #include "base/metrics/histogram_macros.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/common/chrome_features.h"
+#include "components/security_state/core/features.h"
 #include "content/public/browser/navigation_entry.h"
 
 namespace {
@@ -159,10 +159,14 @@
   // triggered when a committed navigation finishes.
   last_safety_tip_navigation_entry_id_ =
       web_contents()->GetController().GetLastCommittedEntry()->GetUniqueID();
+  // Update the visible security state, since we downgrade indicator when a
+  // safety tip is triggered. This has to happen after
+  // last_safety_tip_navigation_entry_id_ is updated.
+  web_contents()->DidChangeVisibleSecurityState();
 
   MaybeCallReputationCheckCallback();
 
-  if (!base::FeatureList::IsEnabled(features::kSafetyTipUI)) {
+  if (!base::FeatureList::IsEnabled(security_state::features::kSafetyTipUI)) {
     return;
   }
   ShowSafetyTipDialog(
diff --git a/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.cc b/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.cc
index a5f1c41f..bb2bcf93 100644
--- a/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.cc
+++ b/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.cc
@@ -4,15 +4,37 @@
 
 #include "chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.h"
 
+#include <algorithm>
+#include <memory>
+#include <utility>
+
 #include "chrome/browser/lookalikes/safety_tips/safety_tips_config.h"
 
+namespace {
+
+// Retrieve existing config proto if set, or create a new one otherwise.
+std::unique_ptr<chrome_browser_safety_tips::SafetyTipsConfig> GetConfig() {
+  auto* old = safety_tips::GetRemoteConfigProto();
+  if (old) {
+    return std::make_unique<chrome_browser_safety_tips::SafetyTipsConfig>(*old);
+  }
+
+  auto conf = std::make_unique<chrome_browser_safety_tips::SafetyTipsConfig>();
+  // Any version ID will do.
+  conf->set_version_id(4);
+  return conf;
+}
+
+}  // namespace
+
+void InitializeSafetyTipConfig() {
+  safety_tips::SetRemoteConfigProto(GetConfig());
+}
+
 void SetSafetyTipPatternsWithFlagType(
     std::vector<std::string> patterns,
     chrome_browser_safety_tips::FlaggedPage::FlagType type) {
-  std::unique_ptr<chrome_browser_safety_tips::SafetyTipsConfig> config_proto =
-      std::make_unique<chrome_browser_safety_tips::SafetyTipsConfig>();
-  // Any version ID will do.
-  config_proto->set_version_id(4);
+  auto config_proto = GetConfig();
   std::sort(patterns.begin(), patterns.end());
   for (const auto& pattern : patterns) {
     chrome_browser_safety_tips::FlaggedPage* page =
@@ -28,3 +50,14 @@
   SetSafetyTipPatternsWithFlagType(
       patterns, chrome_browser_safety_tips::FlaggedPage::BAD_REP);
 }
+
+void SetSafetyTipAllowlistPatterns(std::vector<std::string> patterns) {
+  auto config_proto = GetConfig();
+  std::sort(patterns.begin(), patterns.end());
+  for (const auto& pattern : patterns) {
+    chrome_browser_safety_tips::UrlPattern* page =
+        config_proto->add_allowed_pattern();
+    page->set_pattern(pattern);
+  }
+  safety_tips::SetRemoteConfigProto(std::move(config_proto));
+}
diff --git a/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.h b/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.h
index 59143634..b8686f89 100644
--- a/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.h
+++ b/chrome/browser/lookalikes/safety_tips/safety_tip_test_utils.h
@@ -6,9 +6,14 @@
 #define CHROME_BROWSER_LOOKALIKES_SAFETY_TIPS_SAFETY_TIP_TEST_UTILS_H_
 
 #include <string>
+#include <vector>
 
 #include "chrome/browser/lookalikes/safety_tips/safety_tips.pb.h"
 
+// Initialize component configuration. Necessary to enable Safety Tips for
+// testing, as no heuristics trigger if the allowlist is inaccessible.
+void InitializeSafetyTipConfig();
+
 // Sets the patterns included in component with the given flag type for tests.
 void SetSafetyTipPatternsWithFlagType(
     std::vector<std::string> pattern,
@@ -18,4 +23,7 @@
 // calls SetSafetyTipPatternsWithFlagType with BAD_REPUTATION as the type.
 void SetSafetyTipBadRepPatterns(std::vector<std::string> pattern);
 
+// Sets allowlist patterns in the given proto for testing.
+void SetSafetyTipAllowlistPatterns(std::vector<std::string> patterns);
+
 #endif  // CHROME_BROWSER_LOOKALIKES_SAFETY_TIPS_SAFETY_TIP_TEST_UTILS_H_
diff --git a/chrome/browser/media/media_engagement_autoplay_browsertest.cc b/chrome/browser/media/media_engagement_autoplay_browsertest.cc
index b9920a0..eddeac1 100644
--- a/chrome/browser/media/media_engagement_autoplay_browsertest.cc
+++ b/chrome/browser/media/media_engagement_autoplay_browsertest.cc
@@ -68,6 +68,13 @@
     http_server_.ServeFilesFromSourceDirectory(kMediaEngagementTestDataPath);
     http_server_origin2_.ServeFilesFromSourceDirectory(
         kMediaEngagementTestDataPath);
+
+    // Enable or disable MEI based on the test parameter.
+    if (GetParam()) {
+      scoped_feature_list_.InitWithFeatures(kFeatures, {});
+    } else {
+      scoped_feature_list_.InitWithFeatures({}, kFeatures);
+    }
   }
 
   ~MediaEngagementAutoplayBrowserTest() override = default;
@@ -82,13 +89,6 @@
     ASSERT_TRUE(http_server_.Start());
     ASSERT_TRUE(http_server_origin2_.Start());
 
-    // Enable or disable MEI based on the test parameter.
-    if (GetParam()) {
-      scoped_feature_list_.InitWithFeatures(kFeatures, {});
-    } else {
-      scoped_feature_list_.InitWithFeatures({}, kFeatures);
-    }
-
     InProcessBrowserTest::SetUp();
 
     // Clear any preloaded MEI data.
@@ -367,11 +367,19 @@
   ExpectAutoplayAllowedIfEnabled();
 }
 
-IN_PROC_BROWSER_TEST_P(MediaEngagementAutoplayBrowserTest,
-                       BypassAutoplayHighEngagement_HTTPSOnly) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(media::kMediaEngagementHTTPSOnly);
+class MediaEngagementAutoplayBrowserTestHttpsOnly
+    : public MediaEngagementAutoplayBrowserTest {
+ public:
+  MediaEngagementAutoplayBrowserTestHttpsOnly() {
+    feature_list_.InitAndEnableFeature(media::kMediaEngagementHTTPSOnly);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(MediaEngagementAutoplayBrowserTestHttpsOnly,
+                       BypassAutoplayHighEngagement) {
   SetScores(PrimaryOrigin(), 20, 20);
   LoadTestPage("engagement_autoplay_test.html");
   ExpectAutoplayDenied();
@@ -380,3 +388,7 @@
 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
                          MediaEngagementAutoplayBrowserTest,
                          testing::Bool());
+
+INSTANTIATE_TEST_SUITE_P(/* no prefix */,
+                         MediaEngagementAutoplayBrowserTestHttpsOnly,
+                         testing::Bool());
diff --git a/chrome/browser/media/unified_autoplay_browsertest.cc b/chrome/browser/media/unified_autoplay_browsertest.cc
index 7a8c6704..9af87f15 100644
--- a/chrome/browser/media/unified_autoplay_browsertest.cc
+++ b/chrome/browser/media/unified_autoplay_browsertest.cc
@@ -60,11 +60,13 @@
 // conflict with "AutoplayBrowserTest" in extensions code.
 class UnifiedAutoplayBrowserTest : public InProcessBrowserTest {
  public:
+  UnifiedAutoplayBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(media::kUnifiedAutoplay);
+  }
+
   ~UnifiedAutoplayBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
-    scoped_feature_list_.InitAndEnableFeature(media::kUnifiedAutoplay);
-
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
 
@@ -417,13 +419,16 @@
 
 class UnifiedAutoplaySettingBrowserTest : public UnifiedAutoplayBrowserTest {
  public:
+  UnifiedAutoplaySettingBrowserTest() {
+    scoped_feature_list_.InitWithFeatures(
+        {media::kAutoplayDisableSettings, media::kAutoplayWhitelistSettings},
+        {});
+  }
+
   ~UnifiedAutoplaySettingBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
     UnifiedAutoplayBrowserTest::SetUpOnMainThread();
-    scoped_feature_list_.InitWithFeatures(
-        {media::kAutoplayDisableSettings, media::kAutoplayWhitelistSettings},
-        {});
   }
 
   bool AutoplayAllowed(const content::ToRenderFrameHost& adapter) {
diff --git a/chrome/browser/memory/chrome_browser_main_extra_parts_memory.cc b/chrome/browser/memory/chrome_browser_main_extra_parts_memory.cc
new file mode 100644
index 0000000..728d5f2
--- /dev/null
+++ b/chrome/browser/memory/chrome_browser_main_extra_parts_memory.cc
@@ -0,0 +1,46 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/memory/chrome_browser_main_extra_parts_memory.h"
+
+#include "base/memory/memory_pressure_monitor.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/memory/enterprise_memory_limit_pref_observer.h"
+
+#if defined(OS_WIN)
+#include "chrome/browser/memory/swap_thrashing_monitor.h"
+#endif
+
+ChromeBrowserMainExtraPartsMemory::ChromeBrowserMainExtraPartsMemory() = default;
+
+ChromeBrowserMainExtraPartsMemory::~ChromeBrowserMainExtraPartsMemory() =
+    default;
+
+void ChromeBrowserMainExtraPartsMemory::PostBrowserStart() {
+#if defined(OS_WIN)
+  // Start the swap thrashing monitor if it's enabled.
+  //
+  // TODO(sebmarchand): Delay the initialization of this monitor once we start
+  // using this feature by default, this is currently enabled at startup to make
+  // it easier to experiment with this monitor.
+  if (base::FeatureList::IsEnabled(features::kSwapThrashingMonitor))
+    memory::SwapThrashingMonitor::Initialize();
+#endif
+
+  // The MemoryPressureMonitor might not be available in some tests.
+  if (base::MemoryPressureMonitor::Get() &&
+      memory::EnterpriseMemoryLimitPrefObserver::PlatformIsSupported()) {
+    memory_limit_pref_observer_ =
+        std::make_unique<memory::EnterpriseMemoryLimitPrefObserver>(
+            g_browser_process->local_state());
+  }
+}
+
+void ChromeBrowserMainExtraPartsMemory::PostMainMessageLoopRun() {
+  // |memory_limit_pref_observer_| must be destroyed before its |pref_service_|
+  // is destroyed, as the observer's PrefChangeRegistrar's destructor uses the
+  // pref_service.
+  memory_limit_pref_observer_.reset();
+}
diff --git a/chrome/browser/memory/chrome_browser_main_extra_parts_memory.h b/chrome/browser/memory/chrome_browser_main_extra_parts_memory.h
new file mode 100644
index 0000000..6cbdea4
--- /dev/null
+++ b/chrome/browser/memory/chrome_browser_main_extra_parts_memory.h
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEMORY_CHROME_BROWSER_MAIN_EXTRA_PARTS_MEMORY_H_
+#define CHROME_BROWSER_MEMORY_CHROME_BROWSER_MAIN_EXTRA_PARTS_MEMORY_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "chrome/browser/chrome_browser_main_extra_parts.h"
+
+namespace memory {
+class EnterpriseMemoryLimitPrefObserver;
+}  // namespace memory
+
+// Wrapper that owns and initialize the browser memory-related extra parts.
+class ChromeBrowserMainExtraPartsMemory : public ChromeBrowserMainExtraParts {
+ public:
+  ChromeBrowserMainExtraPartsMemory();
+  ~ChromeBrowserMainExtraPartsMemory() override;
+
+ private:
+  // ChromeBrowserMainExtraParts overrides.
+  void PostBrowserStart() override;
+  void PostMainMessageLoopRun() override;
+
+  // Tracks changes to the MemoryLimitMbEnabled enterprise policy, and
+  // starts/stops the EnterpriseMemoryLimitEvaluator accordingly.
+  //
+  // Only supported on some platforms, see
+  // EnterpriseMemoryLimitPrefObserver::PlatformIsSupported for the list of
+  // supported platforms.
+  std::unique_ptr<memory::EnterpriseMemoryLimitPrefObserver>
+      memory_limit_pref_observer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainExtraPartsMemory);
+};
+
+#endif  // CHROME_BROWSER_MEMORY_CHROME_BROWSER_MAIN_EXTRA_PARTS_MEMORY_H_
diff --git a/chrome/browser/memory/enterprise_memory_limit_pref_observer.cc b/chrome/browser/memory/enterprise_memory_limit_pref_observer.cc
index 0fe5ed9..4fd5d4f7 100644
--- a/chrome/browser/memory/enterprise_memory_limit_pref_observer.cc
+++ b/chrome/browser/memory/enterprise_memory_limit_pref_observer.cc
@@ -7,8 +7,14 @@
 #include "base/bind.h"
 #include "base/memory/memory_pressure_monitor.h"
 #include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
+#include "build/build_config.h"
+#include "chrome/browser/resource_coordinator/utils.h"
 #include "chrome/common/pref_names.h"
 
+#if !defined(OS_ANDROID)
+#include "chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h"
+#endif
+
 namespace memory {
 
 namespace {
@@ -35,8 +41,20 @@
 
 EnterpriseMemoryLimitPrefObserver::~EnterpriseMemoryLimitPrefObserver() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (evaluator_->IsRunning())
+  if (evaluator_->IsRunning()) {
+#if !defined(OS_ANDROID)
+    resource_coordinator::GetTabLifecycleUnitSource()
+        ->SetMemoryLimitEnterprisePolicyFlag(false);
+#endif
     evaluator_->Stop();
+  }
+}
+
+bool EnterpriseMemoryLimitPrefObserver::PlatformIsSupported() {
+#if defined(OS_WIN) || defined(OS_MACOSX)
+  return true;
+#endif
+  return false;
 }
 
 // static
@@ -59,6 +77,11 @@
   } else if (evaluator_->IsRunning()) {
     evaluator_->Stop();
   }
+
+#if !defined(OS_ANDROID)
+  resource_coordinator::GetTabLifecycleUnitSource()
+      ->SetMemoryLimitEnterprisePolicyFlag(pref->IsManaged());
+#endif
 }
 
 }  // namespace memory
diff --git a/chrome/browser/memory/enterprise_memory_limit_pref_observer.h b/chrome/browser/memory/enterprise_memory_limit_pref_observer.h
index a5cfecb..d2448b2 100644
--- a/chrome/browser/memory/enterprise_memory_limit_pref_observer.h
+++ b/chrome/browser/memory/enterprise_memory_limit_pref_observer.h
@@ -20,6 +20,9 @@
   explicit EnterpriseMemoryLimitPrefObserver(PrefService* pref_service);
   ~EnterpriseMemoryLimitPrefObserver();
 
+  // Returns true if the current platform is supported, false otherwise.
+  static bool PlatformIsSupported();
+
   // Registers the TotalMemoryLimitMb pref with the provided PrefRegistry.
   // Should only be called by RegisterLocalState() in
   // chrome/browser/prefs/browser_prefs.cc.
diff --git a/chrome/browser/notifications/notification_interactive_uitest.cc b/chrome/browser/notifications/notification_interactive_uitest.cc
index 9b324fa..e58a9ed9 100644
--- a/chrome/browser/notifications/notification_interactive_uitest.cc
+++ b/chrome/browser/notifications/notification_interactive_uitest.cc
@@ -265,9 +265,8 @@
   EXPECT_EQ("denied", QueryPermissionStatus(browser()));
 }
 
-IN_PROC_BROWSER_TEST_F(NotificationsTest, TestPermissionEmbargo) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  EnablePermissionsEmbargo(&scoped_feature_list);
+IN_PROC_BROWSER_TEST_F(NotificationsTestWithPermissionsEmbargo,
+                       TestPermissionEmbargo) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   ui_test_utils::NavigateToURL(browser(), GetTestPageURL());
diff --git a/chrome/browser/notifications/notification_interactive_uitest_support.cc b/chrome/browser/notifications/notification_interactive_uitest_support.cc
index 564b439..f371521 100644
--- a/chrome/browser/notifications/notification_interactive_uitest_support.cc
+++ b/chrome/browser/notifications/notification_interactive_uitest_support.cc
@@ -112,6 +112,7 @@
 };
 
 MessageCenterChangeObserver::MessageCenterChangeObserver() : impl_(new Impl) {}
+
 MessageCenterChangeObserver::~MessageCenterChangeObserver() = default;
 
 bool MessageCenterChangeObserver::Wait() {
@@ -128,9 +129,7 @@
   return last_displayed_id_;
 }
 
-void NotificationsTest::SetUpDefaultCommandLine(
-    base::CommandLine* command_line) {
-  InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
+NotificationsTest::NotificationsTest() {
 // Temporary change while the whole support class is changed to deal
 // with native notifications. crbug.com/714679
 #if BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
@@ -306,17 +305,15 @@
   return browser->tab_strip_model()->GetActiveWebContents();
 }
 
-void NotificationsTest::EnablePermissionsEmbargo(
-    base::test::ScopedFeatureList* scoped_feature_list) {
+NotificationsTestWithPermissionsEmbargo ::
+    NotificationsTestWithPermissionsEmbargo() {
 #if BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
-  scoped_feature_list->InitWithFeatures(
-      {features::kBlockPromptsIfDismissedOften,
-       features::kBlockPromptsIfIgnoredOften},
-      {features::kNativeNotifications});
+  feature_list_.InitWithFeatures({features::kBlockPromptsIfDismissedOften,
+                                  features::kBlockPromptsIfIgnoredOften},
+                                 {features::kNativeNotifications});
 #else
-  scoped_feature_list->InitWithFeatures(
-      {features::kBlockPromptsIfDismissedOften,
-       features::kBlockPromptsIfIgnoredOften},
-      {});
+  feature_list_.InitWithFeatures({features::kBlockPromptsIfDismissedOften,
+                                  features::kBlockPromptsIfIgnoredOften},
+                                 {});
 #endif  //  BUILDFLAG(ENABLE_NATIVE_NOTIFICATIONS)
 }
diff --git a/chrome/browser/notifications/notification_interactive_uitest_support.h b/chrome/browser/notifications/notification_interactive_uitest_support.h
index b604d90..63b9262 100644
--- a/chrome/browser/notifications/notification_interactive_uitest_support.h
+++ b/chrome/browser/notifications/notification_interactive_uitest_support.h
@@ -45,12 +45,9 @@
 
 class NotificationsTest : public InProcessBrowserTest {
  public:
-  NotificationsTest() {}
+  NotificationsTest();
 
  protected:
-  // InProcessBrowserTest overrides.
-  void SetUpDefaultCommandLine(base::CommandLine* command_line) override;
-
   int GetNotificationCount();
   int GetNotificationPopupCount();
 
@@ -86,10 +83,6 @@
   GURL GetTestPageURL() const;
   content::WebContents* GetActiveWebContents(Browser* browser);
 
- protected:
-  void EnablePermissionsEmbargo(
-      base::test::ScopedFeatureList* scoped_feature_list);
-
  private:
   std::string RequestAndRespondToPermission(
       Browser* browser,
@@ -98,4 +91,12 @@
   base::test::ScopedFeatureList feature_list_;
 };
 
+class NotificationsTestWithPermissionsEmbargo : public NotificationsTest {
+ public:
+  NotificationsTestWithPermissionsEmbargo();
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 #endif  // CHROME_BROWSER_NOTIFICATIONS_NOTIFICATION_INTERACTIVE_UITEST_SUPPORT_H_
diff --git a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
index 826f0b6..8f1f4fe 100644
--- a/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
+++ b/chrome/browser/page_load_metrics/observers/ad_metrics/ads_page_load_metrics_observer_browsertest.cc
@@ -915,12 +915,22 @@
       blink::mojom::WebFeature::kHeavyAdIntervention, 1);
 }
 
+class AdsPageLoadMetricsObserverResourceBrowserTestWithoutHeavyAdIntervention
+    : public AdsPageLoadMetricsObserverResourceBrowserTest {
+ public:
+  AdsPageLoadMetricsObserverResourceBrowserTestWithoutHeavyAdIntervention() {
+    feature_list_.InitAndDisableFeature(features::kHeavyAdIntervention);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Check that when the heavy ad feature is disabled we don't navigate
 // the frame.
-IN_PROC_BROWSER_TEST_F(AdsPageLoadMetricsObserverResourceBrowserTest,
-                       HeavyAdInterventionDisabled_ErrorPageNotLoaded) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(features::kHeavyAdIntervention);
+IN_PROC_BROWSER_TEST_F(
+    AdsPageLoadMetricsObserverResourceBrowserTestWithoutHeavyAdIntervention,
+    ErrorPageNotLoaded) {
   base::HistogramTester histogram_tester;
   auto incomplete_resource_response =
       std::make_unique<net::test_server::ControllableHttpResponse>(
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index b1c7e21a..047b2168 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -611,8 +611,8 @@
 TouchToFillController*
 ChromePasswordManagerClient::GetOrCreateTouchToFillController() {
   if (!touch_to_fill_controller_) {
-    touch_to_fill_controller_ =
-        std::make_unique<TouchToFillController>(web_contents());
+    touch_to_fill_controller_ = std::make_unique<TouchToFillController>(
+        web_contents(), GetFaviconService());
   }
 
   return touch_to_fill_controller_.get();
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 107e4b5..32800e40 100644
--- a/chrome/browser/password_manager/password_manager_browsertest.cc
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc
@@ -2457,7 +2457,21 @@
   CheckThatCredentialsStored("temp", "new_pw");
 }
 
-IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest,
+// This fixture disable autofill. If a password is autofilled, then all the
+// Javascript changes are discarded and test below would not be able to feed a
+// new password to the form.
+class PasswordManagerBrowserTestWithAutofillDisabled
+    : public PasswordManagerBrowserTest {
+ public:
+  PasswordManagerBrowserTestWithAutofillDisabled() {
+    feature_list_.InitAndEnableFeature(features::kFillOnAccountSelect);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTestWithAutofillDisabled,
                        PasswordOverridenUpdateBubbleShown) {
   // At first let us save credentials to the PasswordManager.
   scoped_refptr<password_manager::TestPasswordStore> password_store =
@@ -2471,12 +2485,6 @@
   signin_form.password_value = base::ASCIIToUTF16("pw");
   password_store->AddLogin(signin_form);
 
-  // Disable autofill. If a password is autofilled then all the Javacript
-  // changes are discarded. The test would not be able to feed the new password
-  // below.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kFillOnAccountSelect);
-
   // Check that password update bubble is shown.
   NavigateToFile("/password/password_form.html");
   NavigationObserver observer(WebContents());
diff --git a/chrome/browser/password_manager/touch_to_fill_controller.cc b/chrome/browser/password_manager/touch_to_fill_controller.cc
index 2eb44e0..1431e267 100644
--- a/chrome/browser/password_manager/touch_to_fill_controller.cc
+++ b/chrome/browser/password_manager/touch_to_fill_controller.cc
@@ -8,6 +8,7 @@
 
 #include "base/logging.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view.h"
+#include "components/favicon/core/favicon_service.h"
 #include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
 #include "components/password_manager/core/browser/origin_credential_store.h"
 #include "components/password_manager/core/browser/password_manager_driver.h"
@@ -19,8 +20,22 @@
 using password_manager::CredentialPair;
 using password_manager::PasswordManagerDriver;
 
-TouchToFillController::TouchToFillController(content::WebContents* web_contents)
-    : web_contents_(web_contents) {}
+namespace {
+
+void OnImageFetched(base::OnceCallback<void(const gfx::Image&)> callback,
+                    const favicon_base::FaviconRawBitmapResult& bitmap_result) {
+  gfx::Image image;
+  if (bitmap_result.is_valid())
+    image = gfx::Image::CreateFrom1xPNGBytes(bitmap_result.bitmap_data);
+  std::move(callback).Run(image);
+}
+
+}  // namespace
+
+TouchToFillController::TouchToFillController(
+    content::WebContents* web_contents,
+    favicon::FaviconService* favicon_service)
+    : web_contents_(web_contents), favicon_service_(favicon_service) {}
 
 TouchToFillController::~TouchToFillController() = default;
 
@@ -47,8 +62,9 @@
 
   password_manager::metrics_util::LogFilledCredentialIsFromAndroidApp(
       password_manager::IsValidAndroidFacetURI(credential.origin_url.spec()));
-  driver_->FillSuggestion(credential.username, credential.password);
-  std::exchange(driver_, nullptr)->TouchToFillDismissed();
+  driver_->TouchToFillDismissed();
+  std::exchange(driver_, nullptr)
+      ->FillSuggestion(credential.username, credential.password);
 }
 
 void TouchToFillController::OnDismiss() {
@@ -61,3 +77,14 @@
 gfx::NativeView TouchToFillController::GetNativeView() {
   return web_contents_->GetNativeView();
 }
+
+void TouchToFillController::FetchFavicon(
+    const std::string& credential_origin,
+    int desired_size_in_pixel,
+    base::OnceCallback<void(const gfx::Image&)> callback) {
+  favicon_service_->GetRawFaviconForPageURL(
+      GURL(credential_origin), {favicon_base::IconType::kFavicon},
+      desired_size_in_pixel,
+      /* fallback_to_host = */ true,
+      base::BindOnce(&OnImageFetched, std::move(callback)), &favicon_tracker_);
+}
diff --git a/chrome/browser/password_manager/touch_to_fill_controller.h b/chrome/browser/password_manager/touch_to_fill_controller.h
index a088ef5a..af8d850 100644
--- a/chrome/browser/password_manager/touch_to_fill_controller.h
+++ b/chrome/browser/password_manager/touch_to_fill_controller.h
@@ -11,14 +11,20 @@
 
 #include "base/containers/span.h"
 #include "base/memory/weak_ptr.h"
+#include "base/task/cancelable_task_tracker.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view_factory.h"
+#include "components/favicon_base/favicon_types.h"
 #include "ui/gfx/native_widget_types.h"
 
 namespace content {
 class WebContents;
 }  // namespace content
 
+namespace favicon {
+class FaviconService;
+}  // namespace favicon
+
 namespace password_manager {
 class PasswordManagerDriver;
 struct CredentialPair;
@@ -26,7 +32,8 @@
 
 class TouchToFillController {
  public:
-  explicit TouchToFillController(content::WebContents* web_contents);
+  TouchToFillController(content::WebContents* web_contents,
+                        favicon::FaviconService* favicon_service);
   TouchToFillController(const TouchToFillController&) = delete;
   TouchToFillController& operator=(const TouchToFillController&) = delete;
   ~TouchToFillController();
@@ -47,6 +54,12 @@
   // The web page view containing the focused field.
   gfx::NativeView GetNativeView();
 
+  // Obtains a favicon for the origin and invokes the callback with a favicon
+  // image or with an empty image if a favicon could not be retrieved.
+  void FetchFavicon(const std::string& credential_origin,
+                    int desired_size_in_pixel,
+                    base::OnceCallback<void(const gfx::Image&)> callback);
+
 #if defined(UNIT_TEST)
   void set_view(std::unique_ptr<TouchToFillView> view) {
     view_ = std::move(view);
@@ -63,6 +76,12 @@
   // OnCredentialSelected() or OnDismissed() gets called.
   base::WeakPtr<password_manager::PasswordManagerDriver> driver_;
 
+  // The favicon service used to retrieve icons for a given origin.
+  favicon::FaviconService* favicon_service_ = nullptr;
+
+  // Used to track requested favicons. On destruction, requests are cancelled.
+  base::CancelableTaskTracker favicon_tracker_;
+
   // View used to communicate with the Android frontend. Lazily instantiated so
   // that it can be injected by tests.
   std::unique_ptr<TouchToFillView> view_;
diff --git a/chrome/browser/password_manager/touch_to_fill_controller_unittest.cc b/chrome/browser/password_manager/touch_to_fill_controller_unittest.cc
index fc4a40d..88a46b2c 100644
--- a/chrome/browser/password_manager/touch_to_fill_controller_unittest.cc
+++ b/chrome/browser/password_manager/touch_to_fill_controller_unittest.cc
@@ -68,7 +68,7 @@
  private:
   MockTouchToFillView* mock_view_ = nullptr;
   MockPasswordManagerDriver driver_;
-  TouchToFillController touch_to_fill_controller_{nullptr};
+  TouchToFillController touch_to_fill_controller_{nullptr, nullptr};
 };
 
 TEST_F(TouchToFillControllerTest, Show_And_Fill) {
diff --git a/chrome/browser/payments/has_enrolled_instrument_browsertest.cc b/chrome/browser/payments/has_enrolled_instrument_browsertest.cc
index 64d95bf7..c0bc33b 100644
--- a/chrome/browser/payments/has_enrolled_instrument_browsertest.cc
+++ b/chrome/browser/payments/has_enrolled_instrument_browsertest.cc
@@ -64,20 +64,6 @@
     PlatformBrowserTest::SetUpOnMainThread();
   }
 
-  std::unique_ptr<base::test::ScopedFeatureList>
-  EnableStrictHasEnrolledAutofillInstrument() {
-    auto features = std::make_unique<base::test::ScopedFeatureList>();
-    features->InitWithFeatures(
-        /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument},
-        /*disabled_features=*/{
-          features::kPaymentRequestSkipToGPay,
-#if defined(OS_ANDROID)
-              ::chrome::android::kNoCreditCardAbort,
-#endif  // OS_ANDROID
-        });
-    return features;
-  }
-
   content::WebContents* GetActiveWebContents() {
     return chrome_test_utils::GetActiveWebContents(this);
   }
@@ -94,6 +80,24 @@
   DISALLOW_COPY_AND_ASSIGN(HasEnrolledInstrumentTest);
 };
 
+class HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument
+    : public HasEnrolledInstrumentTest {
+ public:
+  HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument() {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument},
+        /*disabled_features=*/{
+          features::kPaymentRequestSkipToGPay,
+#if defined(OS_ANDROID)
+              ::chrome::android::kNoCreditCardAbort,
+#endif
+        });
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, NoCard) {
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
@@ -103,28 +107,28 @@
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    NoCard) {
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
-                                     "hasEnrolledInstrument()"));
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
-
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, NoBillingAddress) {
@@ -139,28 +143,31 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    NoBillingAddress) {
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      autofill::test::GetCreditCard());
 
-    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
-                                     "hasEnrolledInstrument()"));
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest,
@@ -178,28 +185,33 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    HaveShippingNoBillingAddress) {
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           autofill::test::GetFullProfile());
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      autofill::test::GetCreditCard());
 
-    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
-                                     "hasEnrolledInstrument()"));
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest,
@@ -218,19 +230,25 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    HaveShippingAndBillingAddress) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      GetCardWithBillingAddress(address));
 
-    EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
-                                    "hasEnrolledInstrument()"));
-    EXPECT_EQ(true,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(true, content::EvalJs(
-                        GetActiveWebContents(),
-                        "hasEnrolledInstrument({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, InvalidCardNumber) {
@@ -250,28 +268,36 @@
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    InvalidCardNumber) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  autofill::CreditCard card = GetCardWithBillingAddress(address);
+  card.SetRawInfo(autofill::ServerFieldType::CREDIT_CARD_NUMBER,
+                  base::ASCIIToUTF16("1111111111111111"));
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(), card);
 
-    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
-                                     "hasEnrolledInstrument()"));
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, ExpiredCard) {
@@ -290,28 +316,35 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    ExpiredCard) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  autofill::CreditCard card = GetCardWithBillingAddress(address);
+  card.SetExpirationYear(2000);
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(), card);
 
-    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
-                                     "hasEnrolledInstrument()"));
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 // TODO(https://crbug.com/994799): Unify autofill data validation and returned
@@ -337,41 +370,50 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    HaveNoNameShippingAndBillingAddress) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+
+  address.SetRawInfo(autofill::ServerFieldType::NAME_FIRST, base::string16());
+  address.SetRawInfo(autofill::ServerFieldType::NAME_MIDDLE, base::string16());
+  address.SetRawInfo(autofill::ServerFieldType::NAME_LAST, base::string16());
+
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      GetCardWithBillingAddress(address));
 
 // TODO(https://crbug.com/994799): Unify autofill data requirements between
 // desktop and Android.
 #if defined(OS_ANDROID)
-    // Android requires the billing address to have a name.
-    bool is_no_name_billing_address_valid = false;
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
+  // Android requires the billing address to have a name.
+  bool is_no_name_billing_address_valid = false;
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 #else
-    // Desktop does not require the billing address to have a name.
-    bool is_no_name_billing_address_valid = true;
+  // Desktop does not require the billing address to have a name.
+  bool is_no_name_billing_address_valid = true;
 #endif  // OS_ANDROID
 
-    EXPECT_EQ(
-        is_no_name_billing_address_valid,
-        content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-    EXPECT_EQ(
-        is_no_name_billing_address_valid,
-        content::EvalJs(GetActiveWebContents(),
-                        "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(is_no_name_billing_address_valid,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(is_no_name_billing_address_valid,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    // Shipping address requires recipient name on all platforms.
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-  }
+  // Shipping address requires recipient name on all platforms.
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest,
@@ -392,29 +434,37 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    HaveNoStreetShippingAndBillingAddress) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+  address.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_STREET_ADDRESS,
+                     base::string16());
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      GetCardWithBillingAddress(address));
 
-    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
-                                     "hasEnrolledInstrument()"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
 
-    EXPECT_EQ(false,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(), "show()"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestShipping:true})"));
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(), "show()"));
+  EXPECT_EQ(
+      not_supported_message(),
+      content::EvalJs(GetActiveWebContents(), "show({requestShipping:true})"));
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, NoEmailAddress) {
@@ -434,23 +484,31 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    NoEmailAddress) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+  address.SetRawInfo(autofill::ServerFieldType::EMAIL_ADDRESS,
+                     base::string16());
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      GetCardWithBillingAddress(address));
 
-    EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
-                                    "hasEnrolledInstrument()"));
-    EXPECT_EQ(true,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, InvalidEmailAddress) {
@@ -470,23 +528,31 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
+}
 
-  {
-    auto features = EnableStrictHasEnrolledAutofillInstrument();
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentTestWithStrictHasEnrolledAutofillInstrument,
+    InvalidEmailAddress) {
+  autofill::AutofillProfile address = autofill::test::GetFullProfile();
+  address.SetRawInfo(autofill::ServerFieldType::EMAIL_ADDRESS,
+                     base::ASCIIToUTF16("this-is-not-a-valid-email-address"));
+  test::AddAutofillProfile(GetActiveWebContents()->GetBrowserContext(),
+                           address);
+  test::AddCreditCard(GetActiveWebContents()->GetBrowserContext(),
+                      GetCardWithBillingAddress(address));
 
-    EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
-                                    "hasEnrolledInstrument()"));
-    EXPECT_EQ(true,
-              content::EvalJs(GetActiveWebContents(),
-                              "hasEnrolledInstrument({requestShipping:true})"));
-    EXPECT_EQ(false, content::EvalJs(
-                         GetActiveWebContents(),
-                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+  EXPECT_EQ(true,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestShipping:true})"));
+  EXPECT_EQ(false,
+            content::EvalJs(GetActiveWebContents(),
+                            "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-    EXPECT_EQ(not_supported_message(),
-              content::EvalJs(GetActiveWebContents(),
-                              "show({requestPayerEmail:true})"));
-  }
+  EXPECT_EQ(not_supported_message(),
+            content::EvalJs(GetActiveWebContents(),
+                            "show({requestPayerEmail:true})"));
 }
 
 }  // namespace
diff --git a/chrome/browser/payments/has_enrolled_instrument_query_quota_browsertest.cc b/chrome/browser/payments/has_enrolled_instrument_query_quota_browsertest.cc
index 3815a64..7004fe56 100644
--- a/chrome/browser/payments/has_enrolled_instrument_query_quota_browsertest.cc
+++ b/chrome/browser/payments/has_enrolled_instrument_query_quota_browsertest.cc
@@ -57,23 +57,29 @@
     return chrome_test_utils::GetActiveWebContents(this);
   }
 
- protected:
-  base::test::ScopedFeatureList features_;
-
  private:
   net::EmbeddedTestServer https_server_;
 
   DISALLOW_COPY_AND_ASSIGN(HasEnrolledInstrumentQueryQuotaTest);
 };
 
+class HasEnrolledInstrumentQueryQuotaTestNoFlags
+    : public HasEnrolledInstrumentQueryQuotaTest {
+ public:
+  HasEnrolledInstrumentQueryQuotaTestNoFlags() {
+    features_.InitWithFeatures(
+        /*enabled_features=*/{}, /*disabled_features=*/{
+            features::kStrictHasEnrolledAutofillInstrument,
+            features::kWebPaymentsPerMethodCanMakePaymentQuota});
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
 // Payment options do not trigger query quota when the strict autofill data
 // check is disabled. Per-method query quota is also disabled in this test.
-IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTest, NoFlags) {
-  features_.InitWithFeatures(
-      /*enabled_features=*/{}, /*disabled_features=*/{
-          features::kStrictHasEnrolledAutofillInstrument,
-          features::kWebPaymentsPerMethodCanMakePaymentQuota});
-
+IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTestNoFlags, NoFlags) {
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
   EXPECT_EQ(false,
@@ -81,13 +87,24 @@
                             "hasEnrolledInstrument({requestShipping:true})"));
 }
 
+class HasEnrolledInstrumentQueryQuotaTestPerMethodQuota
+    : public HasEnrolledInstrumentQueryQuotaTest {
+ public:
+  HasEnrolledInstrumentQueryQuotaTestPerMethodQuota() {
+    features_.InitWithFeatures(
+        /*enabled_features=*/{features::
+                                  kWebPaymentsPerMethodCanMakePaymentQuota},
+        /*disabled_features=*/{features::kStrictHasEnrolledAutofillInstrument});
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
 // Payment options do not trigger query quota when the strict autofill data
 // check is disabled. Per-method query quota is enabled in this test.
-IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTest, PerMethodQuota) {
-  features_.InitWithFeatures(
-      /*enabled_features=*/{features::kWebPaymentsPerMethodCanMakePaymentQuota},
-      /*disabled_features=*/{features::kStrictHasEnrolledAutofillInstrument});
-
+IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTestPerMethodQuota,
+                       PerMethodQuota) {
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
   EXPECT_EQ(false,
@@ -95,15 +112,25 @@
                             "hasEnrolledInstrument({requestShipping:true})"));
 }
 
+class HasEnrolledInstrumentQueryQuotaTestStrictAutofillDataCheck
+    : public HasEnrolledInstrumentQueryQuotaTest {
+ public:
+  HasEnrolledInstrumentQueryQuotaTestStrictAutofillDataCheck() {
+    features_.InitWithFeatures(
+        /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument},
+        /*disabled_features=*/{
+            features::kWebPaymentsPerMethodCanMakePaymentQuota});
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
 // Payment options trigger query quota for Basic Card when the strict autofill
 // data check is enabled. Per-method query quota is disabled in this test.
-IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTest,
-                       StrictAutofillDataCheck) {
-  features_.InitWithFeatures(
-      /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument},
-      /*disabled_features=*/{
-          features::kWebPaymentsPerMethodCanMakePaymentQuota});
-
+IN_PROC_BROWSER_TEST_F(
+    HasEnrolledInstrumentQueryQuotaTestStrictAutofillDataCheck,
+    StrictAutofillDataCheck) {
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
   EXPECT_EQ("NotAllowedError: Exceeded query quota for hasEnrolledInstrument",
@@ -111,14 +138,25 @@
                             "hasEnrolledInstrument({requestShipping:true})"));
 }
 
+class HasEnrolledInstrumentQueryQuotaTestBothFlags
+    : public HasEnrolledInstrumentQueryQuotaTest {
+ public:
+  HasEnrolledInstrumentQueryQuotaTestBothFlags() {
+    features_.InitWithFeatures(
+        /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument,
+                              features::
+                                  kWebPaymentsPerMethodCanMakePaymentQuota},
+        /*disabled_features=*/{});
+  }
+
+ private:
+  base::test::ScopedFeatureList features_;
+};
+
 // Payment options trigger query quota for Basic Card when the strict autofill
 // data check is enabled. Per-method query quota is also enabled in this test.
-IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTest, BothFlags) {
-  features_.InitWithFeatures(
-      /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument,
-                            features::kWebPaymentsPerMethodCanMakePaymentQuota},
-      /*disabled_features=*/{});
-
+IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentQueryQuotaTestBothFlags,
+                       BothFlags) {
   EXPECT_EQ(false,
             content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
   EXPECT_EQ("NotAllowedError: Exceeded query quota for hasEnrolledInstrument",
diff --git a/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc b/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc
index 000b8525..0d1bf078 100644
--- a/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc
+++ b/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc
@@ -167,7 +167,6 @@
  private:
   PaymentRequestTestController payment_request_controller_;
 
-  base::test::ScopedFeatureList feature_list_;
   syncer::TestSyncService sync_service_;
   std::unique_ptr<net::EmbeddedTestServer> https_server_;
   std::unique_ptr<autofill::EventWaiter<TestEvent>> event_waiter_;
@@ -229,13 +228,23 @@
   ExpectBodyContains({"false"});
 }
 
+class PaymentRequestCanMakePaymentQueryTestWithGooglePayCardsDisabled
+    : public PaymentRequestCanMakePaymentQueryTest {
+ public:
+  PaymentRequestCanMakePaymentQueryTestWithGooglePayCardsDisabled() {
+    feature_list_.InitAndDisableFeature(
+        payments::features::kReturnGooglePayInBasicCard);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Visa is required, and user has a masked visa instrument, and Google Pay cards
 // in basic-card is disabled.
-IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryTest,
-                       CanMakePayment_Supported_GooglePayCardsDisabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      payments::features::kReturnGooglePayInBasicCard);
+IN_PROC_BROWSER_TEST_F(
+    PaymentRequestCanMakePaymentQueryTestWithGooglePayCardsDisabled,
+    CanMakePayment_Supported) {
   NavigateTo("/payment_request_can_make_payment_query_test.html");
   autofill::CreditCard card = autofill::test::GetMaskedServerCard();
   card.SetNumber(base::ASCIIToUTF16("4111111111111111"));  // We need a visa.
@@ -252,13 +261,23 @@
   ExpectBodyContains({"false"});
 }
 
+class PaymentRequestCanMakePaymentQueryTestWithGooglePayCardsEnabled
+    : public PaymentRequestCanMakePaymentQueryTest {
+ public:
+  PaymentRequestCanMakePaymentQueryTestWithGooglePayCardsEnabled() {
+    feature_list_.InitAndEnableFeature(
+        payments::features::kReturnGooglePayInBasicCard);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Visa is required, and user has a masked visa instrument, and Google Pay cards
 // in basic-card is enabled.
-IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryTest,
-                       CanMakePayment_Supported_GooglePayCardsEnabled) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      payments::features::kReturnGooglePayInBasicCard);
+IN_PROC_BROWSER_TEST_F(
+    PaymentRequestCanMakePaymentQueryTestWithGooglePayCardsEnabled,
+    CanMakePayment_Supported) {
   NavigateTo("/payment_request_can_make_payment_query_test.html");
   autofill::CreditCard card = autofill::test::GetMaskedServerCard();
   card.SetNumber(base::ASCIIToUTF16("4111111111111111"));  // We need a visa.
@@ -371,8 +390,6 @@
   }
 
  private:
-  base::test::ScopedFeatureList feature_list_;
-
   DISALLOW_COPY_AND_ASSIGN(PaymentRequestCanMakePaymentQueryCCTest);
 };
 
@@ -597,13 +614,22 @@
   ExpectBodyContains({"NotAllowedError"});
 }
 
+class PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuota
+    : public PaymentRequestCanMakePaymentQueryPMITest {
+ public:
+  PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuota() {
+    feature_list_.InitAndEnableFeature(
+        features::kWebPaymentsPerMethodCanMakePaymentQuota);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // If the device does not have any payment apps installed, canMakePayment() and
 // hasEnrolledInstrument() should return false for them.
-IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryPMITest,
+IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuota,
                        QueryQuotaForPaymentApps) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kWebPaymentsPerMethodCanMakePaymentQuota);
   NavigateTo("/payment_request_payment_method_identifier_test.html");
 
   CallCanMakePayment(CheckFor::ALICE_PAY);
@@ -664,17 +690,29 @@
   ExpectBodyContains({"NotAllowedError"});
 }
 
+class
+    PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuotaAndServiceWorkerPayment
+    : public PaymentRequestCanMakePaymentQueryPMITest {
+ public:
+  PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuotaAndServiceWorkerPayment() {
+    feature_list_.InitWithFeatures(
+        /*enable_features=*/{::features::kServiceWorkerPaymentApps,
+                             features::
+                                 kWebPaymentsPerMethodCanMakePaymentQuota},
+        /*disable_features=*/{});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Querying for payment apps in incognito returns result as normal mode to avoid
 // incognito mode detection. Multiple queries for different apps are rejected
 // with NotSupportedError to avoid user fingerprinting.
-IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryPMITest,
-                       QueryQuotaForPaymentAppsInIncognitoMode) {
+IN_PROC_BROWSER_TEST_F(
+    PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuotaAndServiceWorkerPayment,
+    QueryQuotaForPaymentAppsInIncognitoMode) {
   NavigateTo("/payment_request_payment_method_identifier_test.html");
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatures(
-      /*enable_features=*/{::features::kServiceWorkerPaymentApps,
-                           features::kWebPaymentsPerMethodCanMakePaymentQuota},
-      /*disable_features=*/{});
 
   SetIncognito(true);
 
@@ -703,11 +741,8 @@
 // as in normal mode to avoid incognito mode detection. Multiple queries for
 // different payment methods are rejected with NotSupportedError to avoid user
 // fingerprinting.
-IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryPMITest,
+IN_PROC_BROWSER_TEST_F(PaymentRequestCanMakePaymentQueryPMITestWithPaymentQuota,
                        NoQueryQuotaForPaymentAppsAndCardsInIncognito) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kWebPaymentsPerMethodCanMakePaymentQuota);
   NavigateTo("/payment_request_payment_method_identifier_test.html");
   SetIncognito(true);
 
diff --git a/chrome/browser/permissions/permission_delegation_browsertest.cc b/chrome/browser/permissions/permission_delegation_browsertest.cc
index bd74202..85267591 100644
--- a/chrome/browser/permissions/permission_delegation_browsertest.cc
+++ b/chrome/browser/permissions/permission_delegation_browsertest.cc
@@ -21,11 +21,13 @@
  public:
   PermissionDelegationBrowserTest()
       : geolocation_overrider_(
-            std::make_unique<device::ScopedGeolocationOverrider>(0, 0)) {}
+            std::make_unique<device::ScopedGeolocationOverrider>(0, 0)) {
+    scoped_feature_list_.InitAndEnableFeature(features::kPermissionDelegation);
+  }
+
   ~PermissionDelegationBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
-    scoped_feature_list_.InitAndEnableFeature(features::kPermissionDelegation);
     PermissionRequestManager* manager =
         PermissionRequestManager::FromWebContents(GetWebContents());
     mock_permission_prompt_factory_.reset(
diff --git a/chrome/browser/permissions/permission_request_manager_browsertest.cc b/chrome/browser/permissions/permission_request_manager_browsertest.cc
index 0adf3ce..b555578f8 100644
--- a/chrome/browser/permissions/permission_request_manager_browsertest.cc
+++ b/chrome/browser/permissions/permission_request_manager_browsertest.cc
@@ -52,15 +52,17 @@
 
 class PermissionRequestManagerBrowserTest : public InProcessBrowserTest {
  public:
-  PermissionRequestManagerBrowserTest() = default;
+  PermissionRequestManagerBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kBlockRepeatedNotificationPermissionPrompts);
+  }
+
   ~PermissionRequestManagerBrowserTest() override = default;
 
   void SetUpOnMainThread() override {
     PermissionRequestManager* manager = GetPermissionRequestManager();
     mock_permission_prompt_factory_.reset(
         new MockPermissionPromptFactory(manager));
-    scoped_feature_list_.InitAndEnableFeature(
-        features::kBlockRepeatedNotificationPermissionPrompts);
 
     host_resolver()->AddRule("*", "127.0.0.1");
   }
diff --git a/chrome/browser/policy/e2e_test/.vpython b/chrome/browser/policy/e2e_test/.vpython
index bc935fa..b68566c 100644
--- a/chrome/browser/policy/e2e_test/.vpython
+++ b/chrome/browser/policy/e2e_test/.vpython
@@ -11,8 +11,8 @@
 
 wheel: <
   name: "infra/celab/celab/windows-amd64"
-  # Source: https://ci.chromium.org/p/celab/builders/ci/Windows/b8902045658105865392
-  version: "mHamQ8UpCgqQTQZyuKIFVlvYy3KSkDCsUSGXQtL5E-YC"
+  # Source: https://ci.chromium.org/p/celab/builders/ci/Windows/b8900225462594660384
+  version: "Q1ebL0sPA1vX4tcIGj6d0IXxXa02M5CsBm3p0SbeGZUC"
 >
 
 # googleapiclient
diff --git a/chrome/browser/policy/e2e_test/run_tests.py b/chrome/browser/policy/e2e_test/run_tests.py
index f74071c..220bc341 100644
--- a/chrome/browser/policy/e2e_test/run_tests.py
+++ b/chrome/browser/policy/e2e_test/run_tests.py
@@ -31,6 +31,16 @@
       default='*',
       help='Fully qualified names of TestCases to run (supports my.package.*)')
   parser.add_argument(
+      '--include',
+      metavar='<categoryA;...>',
+      default=None,
+      help='Categories of tests to include')
+  parser.add_argument(
+      '--exclude',
+      metavar='<categoryA;...>',
+      default=None,
+      help='Categories of tests to exclude')
+  parser.add_argument(
       '--noprogress',
       dest='show_progress',
       default=True,
@@ -106,6 +116,10 @@
   tests = ArgsParser.ParseTestsArg(args.tests)
   logging.debug('Found tests: %s', tests)
 
+  if args.include or args.exclude:
+    tests = ArgsParser.ProcessTestFilterArg(tests, args.include, args.exclude)
+    logging.debug('Got filtered tests: %s', tests)
+
   hostFiles = ArgsParser.ParseHostsArg(args.hosts)
   logging.debug('Found hosts: %s', hostFiles)
 
diff --git a/chrome/browser/printing/print_job_worker.cc b/chrome/browser/printing/print_job_worker.cc
index 13f9d7a..ab32d547 100644
--- a/chrome/browser/printing/print_job_worker.cc
+++ b/chrome/browser/printing/print_job_worker.cc
@@ -22,11 +22,13 @@
 #include "chrome/browser/chrome_notification_types.h"
 #include "chrome/browser/printing/print_job.h"
 #include "chrome/grit/generated_resources.h"
+#include "components/crash/core/common/crash_keys.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
+#include "printing/backend/print_backend.h"
 #include "printing/print_job_constants.h"
 #include "printing/printed_document.h"
 #include "printing/printing_utils.h"
@@ -39,6 +41,7 @@
 #endif
 
 #if defined(OS_WIN)
+#include "base/threading/thread_restrictions.h"
 #include "printing/printed_page_win.h"
 #endif
 
@@ -203,6 +206,21 @@
 void PrintJobWorker::UpdatePrintSettings(base::Value new_settings,
                                          SettingsCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+  std::unique_ptr<crash_keys::ScopedPrinterInfo> crash_key;
+  if (new_settings.FindIntKey(kSettingPrinterType).value() == kLocalPrinter) {
+#if defined(OS_WIN)
+    // Blocking is needed here because Windows printer drivers are oftentimes
+    // not thread-safe and have to be accessed on the UI thread.
+    base::ScopedAllowBlocking allow_blocking;
+#endif
+    scoped_refptr<PrintBackend> print_backend =
+        PrintBackend::CreateInstance(nullptr);
+    std::string printer_name = *new_settings.FindStringKey(kSettingDeviceName);
+    crash_key = std::make_unique<crash_keys::ScopedPrinterInfo>(
+        print_backend->GetPrinterDriverInfo(printer_name));
+  }
+
   PrintingContext::Result result =
       printing_context_->UpdatePrintSettings(std::move(new_settings));
   GetSettingsDone(std::move(callback), result);
diff --git a/chrome/browser/push_messaging/push_messaging_browsertest.cc b/chrome/browser/push_messaging/push_messaging_browsertest.cc
index 11faf8e..9a556b5 100644
--- a/chrome/browser/push_messaging/push_messaging_browsertest.cc
+++ b/chrome/browser/push_messaging/push_messaging_browsertest.cc
@@ -1610,11 +1610,19 @@
   ASSERT_EQ(0u, GetNotificationCount());
 }
 
-IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTest,
-                       PushEventIgnoresScheduledNotificationsForEnforcement) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kNotificationTriggers);
+class PushMessagingBrowserTestWithNotificationTriggersEnabled
+    : public PushMessagingBrowserTest {
+ public:
+  PushMessagingBrowserTestWithNotificationTriggersEnabled() {
+    feature_list_.InitAndEnableFeature(features::kNotificationTriggers);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PushMessagingBrowserTestWithNotificationTriggersEnabled,
+                       PushEventIgnoresScheduledNotificationsForEnforcement) {
   std::string script_result;
 
   ASSERT_NO_FATAL_FAILURE(SubscribeSuccessfully());
diff --git a/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc b/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc
index 963f8a6..04dcba5 100644
--- a/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc
+++ b/chrome/browser/resource_coordinator/tab_activity_watcher_browsertest.cc
@@ -68,7 +68,11 @@
 // to UKM logs.
 class TabActivityWatcherTest : public InProcessBrowserTest {
  protected:
-  TabActivityWatcherTest() = default;
+  TabActivityWatcherTest() {
+    scoped_feature_list_.InitAndEnableFeatureWithParameters(
+        features::kTabRanker,
+        {{"disable_background_log_with_TabRanker", "false"}});
+  }
 
   // TabActivityWatcherTest:
   void PreRunTestOnMainThread() override {
@@ -79,9 +83,6 @@
 
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
-    scoped_feature_list_.InitAndEnableFeatureWithParameters(
-        features::kTabRanker,
-        {{"disable_background_log_with_TabRanker", "false"}});
     ASSERT_TRUE(embedded_test_server()->Start());
     test_urls_ = {embedded_test_server()->GetURL("/title1.html"),
                   embedded_test_server()->GetURL("/title2.html"),
@@ -173,20 +174,31 @@
   }
 
   std::vector<GURL> test_urls_;
-  base::test::ScopedFeatureList scoped_feature_list_;
   std::unique_ptr<UkmEntryChecker> ukm_entry_checker_;
   std::unique_ptr<ukm::TestAutoSetUkmRecorder> test_ukm_recorder_;
 
  private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+
   DISALLOW_COPY_AND_ASSIGN(TabActivityWatcherTest);
 };
 
+class TabActivityWatcherTestWithBackgroundLogDisabled
+    : public TabActivityWatcherTest {
+ public:
+  TabActivityWatcherTestWithBackgroundLogDisabled() {
+    feature_list_.InitAndEnableFeatureWithParameters(
+        features::kTabRanker,
+        {{"disable_background_log_with_TabRanker", "true"}});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests calculating tab scores using the Tab Ranker.
-IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, CalculateReactivationScore) {
-  base::test::ScopedFeatureList scoped_feature_list_overrides;
-  scoped_feature_list_overrides.InitAndEnableFeatureWithParameters(
-      features::kTabRanker,
-      {{"disable_background_log_with_TabRanker", "true"}});
+IN_PROC_BROWSER_TEST_F(TabActivityWatcherTestWithBackgroundLogDisabled,
+                       CalculateReactivationScore) {
   // Use test clock so tabs have non-zero backgrounded times.
   base::SimpleTestTickClock test_clock;
   ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing(&test_clock);
@@ -389,15 +401,23 @@
   EXPECT_EQ(0, ukm_entry_checker_->NumNewEntriesRecorded(kFOCEntryName));
 }
 
-// Tests discarded tab is recorded correctly.
-IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest,
-                       DiscardedTabGetsPreviousSourceId) {
-  base::test::ScopedFeatureList scoped_feature_list_overrides;
-  scoped_feature_list_overrides.InitAndEnableFeatureWithParameters(
-      features::kTabRanker,
-      {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
-       {"disable_background_log_with_TabRanker", "false"}});
+class TabActivityWatcherTestWithBackgroundLogEnabled
+    : public TabActivityWatcherTest {
+ public:
+  TabActivityWatcherTestWithBackgroundLogEnabled() {
+    feature_list_.InitAndEnableFeatureWithParameters(
+        features::kTabRanker,
+        {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
+         {"disable_background_log_with_TabRanker", "false"}});
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Tests discarded tab is recorded correctly.
+IN_PROC_BROWSER_TEST_F(TabActivityWatcherTestWithBackgroundLogEnabled,
+                       DiscardedTabGetsPreviousSourceId) {
   ukm::SourceId ukm_source_id_for_tab_0 = 0;
   ukm::SourceId ukm_source_id_for_tab_1 = 0;
 
@@ -506,19 +526,29 @@
   }
 }
 
-// Test the query time logging is correct.
-IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, LogOldestNTabFeatures) {
-  // Set Feature params for this test.
-  // (1) background log is disabled, so that only query time logging and
-  // corresponding labels should be logged.
-  // (2) number of oldest tabs to log is set to 1, so that only 1 tab should be
-  // logged.
-  base::test::ScopedFeatureList scoped_feature_list_overrides;
-  scoped_feature_list_overrides.InitAndEnableFeatureWithParameters(
-      features::kTabRanker,
-      {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
-       {"disable_background_log_with_TabRanker", "true"}});
+class TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab
+    : public TabActivityWatcherTest {
+ public:
+  TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab() {
+    // Set Feature params for this test.
+    // (1) background log is disabled, so that only query time logging and
+    // corresponding labels should be logged.
+    // (2) number of oldest tabs to log is set to 1, so that only 1 tab should
+    // be logged.
+    feature_list_.InitAndEnableFeatureWithParameters(
+        features::kTabRanker,
+        {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
+         {"disable_background_log_with_TabRanker", "true"}});
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// Test the query time logging is correct.
+IN_PROC_BROWSER_TEST_F(
+    TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab,
+    LogOldestNTabFeatures) {
   // Use test clock so tabs have non-zero backgrounded times.
   base::SimpleTestTickClock test_clock;
   ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing(&test_clock);
@@ -610,13 +640,9 @@
 }
 
 // Tests label id is recorded correctly for discarded tabs.
-IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest, DiscardedTabGetsCorrectLabelId) {
-  base::test::ScopedFeatureList scoped_feature_list_overrides;
-  scoped_feature_list_overrides.InitAndEnableFeatureWithParameters(
-      features::kTabRanker,
-      {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
-       {"disable_background_log_with_TabRanker", "true"}});
-
+IN_PROC_BROWSER_TEST_F(
+    TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab,
+    DiscardedTabGetsCorrectLabelId) {
   ui_test_utils::NavigateToURL(browser(), test_urls_[0]);
   AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK);
   // No TabMetrics events are logged till now.
@@ -692,14 +718,9 @@
 
 // Tests label_id is incremented if the LogOldestNTabFeatures is called second
 // times without logging the label first.
-IN_PROC_BROWSER_TEST_F(TabActivityWatcherTest,
-                       TabsAlreadyHaveLabelIdGetIncrementalLabelIds) {
-  base::test::ScopedFeatureList scoped_feature_list_overrides;
-  scoped_feature_list_overrides.InitAndEnableFeatureWithParameters(
-      features::kTabRanker,
-      {{"number_of_oldest_tabs_to_log_with_TabRanker", "1"},
-       {"disable_background_log_with_TabRanker", "true"}});
-
+IN_PROC_BROWSER_TEST_F(
+    TabActivityWatcherTestWithBackgroundLogDisabledAndOnlyOneOldestTab,
+    TabsAlreadyHaveLabelIdGetIncrementalLabelIds) {
   ui_test_utils::NavigateToURL(browser(), test_urls_[0]);
   AddTabAtIndex(1, test_urls_[1], ui::PAGE_TRANSITION_LINK);
   // No TabMetrics events are logged till now.
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc
index a9333d0e..d30b781 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit.cc
@@ -425,11 +425,18 @@
 
 void TabLifecycleUnitSource::TabLifecycleUnit::SetIsHoldingWebLock(
     bool is_holding_weblock) {
+  // Unfreeze the tab if it receive a lock while being frozen.
+  if (is_holding_weblock && IsFrozenOrPendingFreeze(GetState()))
+    Unfreeze();
+
   is_holding_weblock_ = is_holding_weblock;
 }
 
 void TabLifecycleUnitSource::TabLifecycleUnit::SetIsHoldingIndexedDBLock(
     bool is_holding_indexeddb_lock) {
+  // Unfreeze the tab if it receive a lock while being frozen.
+  if (is_holding_indexeddb_lock && IsFrozenOrPendingFreeze(GetState()))
+    Unfreeze();
   is_holding_indexeddb_lock_ = is_holding_indexeddb_lock;
 }
 
@@ -602,12 +609,16 @@
 // NOTE: These do not currently provide DecisionDetails!
 #if !defined(OS_CHROMEOS)
   if (reason == LifecycleUnitDiscardReason::URGENT) {
-    // Limit urgent discarding to once only.
-    if (GetDiscardCount() > 0)
+    // Limit urgent discarding to once only, unless discarding for the
+    // enterprise memory limit feature.
+    if (GetDiscardCount() > 0 &&
+        !GetTabSource()->memory_limit_enterprise_policy())
       return false;
     // Protect non-visible tabs from urgent discarding for a period of time.
     if (web_contents()->GetVisibility() != content::Visibility::VISIBLE) {
       base::TimeDelta time_in_bg = NowTicks() - GetWallTimeWhenHidden();
+      // TODO(sebmarchand): Check if this should be lowered when the enterprise
+      // memory limit feature is set.
       if (time_in_bg < kBackgroundUrgentProtectionTime)
         return false;
     }
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
index 6b62c42..806bb9d 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.cc
@@ -222,6 +222,10 @@
   UpdateFocusedTab();
 }
 
+void TabLifecycleUnitSource::SetMemoryLimitEnterprisePolicyFlag(bool enabled) {
+  memory_limit_enterprise_policy_ = enabled;
+}
+
 void TabLifecycleUnitSource::OnFirstLifecycleUnitCreated() {
   // In production builds monitor the policy override of the lifecycles feature.
   // This class owns the monitor so it is okay to use base::Unretained. Note
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h
index 8de7043..f26ead1 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h
@@ -74,6 +74,13 @@
     return tab_lifecycles_enterprise_policy_;
   }
 
+  // Returns the state of the MemoryLimitMbEnabled enterprise policy.
+  bool memory_limit_enterprise_policy() const {
+    return memory_limit_enterprise_policy_;
+  }
+
+  void SetMemoryLimitEnterprisePolicyFlag(bool enabled);
+
  protected:
   class TabLifecycleUnitHolder;
 
@@ -187,6 +194,9 @@
   // The enterprise policy for overriding the tab lifecycles feature.
   bool tab_lifecycles_enterprise_policy_ = true;
 
+  // The enterprise policy for setting a limit on total physical memory usage.
+  bool memory_limit_enterprise_policy_ = false;
+
   // In official production builds this monitors policy settings and reflects
   // them in |tab_lifecycles_enterprise_policy_|.
   std::unique_ptr<TabLifecylesEnterprisePreferenceMonitor>
diff --git a/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc b/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc
index 468baac..c82addd 100644
--- a/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc
+++ b/chrome/browser/resource_coordinator/tab_lifecycle_unit_unittest.cc
@@ -373,6 +373,11 @@
                        LifecycleUnitDiscardReason::PROACTIVE);
   ExpectCanDiscardFalseTrivial(&tab_lifecycle_unit,
                                LifecycleUnitDiscardReason::URGENT);
+
+  // The tab should be discardable a second time when the memory limit
+  // enterprise policy is set.
+  GetTabLifecycleUnitSource()->SetMemoryLimitEnterprisePolicyFlag(true);
+  ExpectCanDiscardTrue(&tab_lifecycle_unit, LifecycleUnitDiscardReason::URGENT);
 }
 #endif  // !defined(OS_CHROMEOS)
 
@@ -990,4 +995,62 @@
   tab_lifecycle_unit.SetIsHoldingIndexedDBLock(false);
 }
 
+TEST_F(TabLifecycleUnitTest, TabUnfreezeOnWebLockAcquisition) {
+  TabLifecycleUnit tab_lifecycle_unit(GetTabLifecycleUnitSource(), &observers_,
+                                      usage_clock_.get(), web_contents_,
+                                      tab_strip_model_.get());
+  TabLoadTracker::Get()->TransitionStateForTesting(web_contents_,
+                                                   LoadingState::LOADED);
+  // Advance time enough that the tab is urgent discardable.
+  test_clock_.Advance(kBackgroundUrgentProtectionTime);
+  ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit);
+
+  DecisionDetails decision_details;
+  EXPECT_TRUE(tab_lifecycle_unit.CanFreeze(&decision_details));
+
+  // Freeze the tab.
+  EXPECT_CALL(observer_, OnFrozenStateChange(web_contents_, true));
+  tab_lifecycle_unit.Freeze();
+  ::testing::Mock::VerifyAndClear(&observer_);
+  EXPECT_EQ(LifecycleUnitState::PENDING_FREEZE, tab_lifecycle_unit.GetState());
+
+  // Indicates that the tab has acquired a WebLock, this should unfreeze it.
+  EXPECT_CALL(observer_, OnFrozenStateChange(web_contents_, false));
+  tab_lifecycle_unit.SetIsHoldingWebLock(true);
+  ::testing::Mock::VerifyAndClear(&observer_);
+  EXPECT_EQ(LifecycleUnitState::PENDING_UNFREEZE,
+            tab_lifecycle_unit.GetState());
+
+  tab_lifecycle_unit.SetIsHoldingWebLock(false);
+}
+
+TEST_F(TabLifecycleUnitTest, TabUnfreezeOnIndexedDBLockAcquisition) {
+  TabLifecycleUnit tab_lifecycle_unit(GetTabLifecycleUnitSource(), &observers_,
+                                      usage_clock_.get(), web_contents_,
+                                      tab_strip_model_.get());
+  TabLoadTracker::Get()->TransitionStateForTesting(web_contents_,
+                                                   LoadingState::LOADED);
+  // Advance time enough that the tab is urgent discardable.
+  test_clock_.Advance(kBackgroundUrgentProtectionTime);
+  ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit);
+
+  DecisionDetails decision_details;
+  EXPECT_TRUE(tab_lifecycle_unit.CanFreeze(&decision_details));
+
+  // Freeze the tab.
+  EXPECT_CALL(observer_, OnFrozenStateChange(web_contents_, true));
+  tab_lifecycle_unit.Freeze();
+  ::testing::Mock::VerifyAndClear(&observer_);
+  EXPECT_EQ(LifecycleUnitState::PENDING_FREEZE, tab_lifecycle_unit.GetState());
+
+  // Indicates that the tab has acquired a WebLock, this should unfreeze it.
+  EXPECT_CALL(observer_, OnFrozenStateChange(web_contents_, false));
+  tab_lifecycle_unit.SetIsHoldingIndexedDBLock(true);
+  ::testing::Mock::VerifyAndClear(&observer_);
+  EXPECT_EQ(LifecycleUnitState::PENDING_UNFREEZE,
+            tab_lifecycle_unit.GetState());
+
+  tab_lifecycle_unit.SetIsHoldingIndexedDBLock(false);
+}
+
 }  // namespace resource_coordinator
diff --git a/chrome/browser/resources/chromeos/login/oobe_dialog.js b/chrome/browser/resources/chromeos/login/oobe_dialog.js
index 4702732d..86f3f56 100644
--- a/chrome/browser/resources/chromeos/login/oobe_dialog.js
+++ b/chrome/browser/resources/chromeos/login/oobe_dialog.js
@@ -76,6 +76,14 @@
   },
 
   /**
+   * Scroll to the bottom of footer container.
+   */
+  scrollToBottom: function() {
+    var el = this.$$('#footer-container');
+    el.scrollTop = el.scrollHeight;
+  },
+
+  /**
    * This is called from oobe_welcome when this dialog is shown.
    */
   show: function() {
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.css b/chrome/browser/resources/chromeos/login/oobe_eula.css
index b78674a..548c85d 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.css
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.css
@@ -19,6 +19,7 @@
 #installationSettings,
 #logging {
   font-size: 13px;
+  min-height: unset;
 }
 
 #crosEulaFrame {
diff --git a/chrome/browser/resources/chromeos/login/oobe_eula.js b/chrome/browser/resources/chromeos/login/oobe_eula.js
index 939879b..8f44440 100644
--- a/chrome/browser/resources/chromeos/login/oobe_eula.js
+++ b/chrome/browser/resources/chromeos/login/oobe_eula.js
@@ -58,10 +58,10 @@
   },
 
   /**
-   * Flag that ensures that OOBE configuration is applied only once.
+   * Flag that ensures that eula screen set up once.
    * @private {boolean}
    */
-  configuration_applied_: false,
+  initialized_: false,
 
   focus: function() {
     if (this.eulaLoadingScreenShown) {
@@ -77,7 +77,19 @@
       if (behavior.onBeforeShow)
         behavior.onBeforeShow.call(this);
     });
-    window.setTimeout(this.applyOobeConfiguration_.bind(this), 0);
+    window.setTimeout(this.initializeScreen_.bind(this), 0);
+  },
+
+  /**
+   * Set up dialog before shown it for the first time.
+   * @private
+   */
+  initializeScreen_: function() {
+    if (this.initialized_)
+      return;
+    this.$.eulaDialog.scrollToBottom();
+    this.applyOobeConfiguration_();
+    this.initialized_ = true;
   },
 
   /**
@@ -85,8 +97,6 @@
    * @private
    */
   applyOobeConfiguration_: function() {
-    if (this.configuration_applied_)
-      return;
     var configuration = Oobe.getInstance().getOobeConfiguration();
     if (!configuration)
       return;
@@ -96,7 +106,6 @@
     if (configuration.eulaAutoAccept) {
       this.eulaAccepted_();
     }
-    this.configuration_applied_ = true;
   },
 
   /**
diff --git a/chrome/browser/resources/print_preview/data/model.js b/chrome/browser/resources/print_preview/data/model.js
index 1d71350..e248222 100644
--- a/chrome/browser/resources/print_preview/data/model.js
+++ b/chrome/browser/resources/print_preview/data/model.js
@@ -189,9 +189,9 @@
     /**
      * Object containing current settings of Print Preview, for use by Polymer
      * controls.
-     * Initialize settings that are only available on some printers to
-     * unavailable, and settings that are provided by PDF generation to
-     * available.
+     * Initialize all settings to available so that more settings always stays
+     * in a collapsed state during startup, when document information and
+     * printer capabilities may arrive at slightly different times.
      * @type {!print_preview.Settings}
      */
     settings: {
@@ -243,7 +243,7 @@
             value: true, /* color */
             unavailableValue: false,
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'isColorEnabled',
@@ -256,7 +256,7 @@
               height_microns: 279400,
             },
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'mediaSize',
@@ -286,7 +286,7 @@
             value: {},
             unavailableValue: {},
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'dpi',
@@ -326,7 +326,7 @@
             value: true,
             unavailableValue: false,
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'isDuplexEnabled',
@@ -386,7 +386,7 @@
             value: {},
             unavailableValue: {},
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'vendorOptions',
@@ -441,7 +441,7 @@
             value: false,
             unavailableValue: false,
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'isPinEnabled',
@@ -451,7 +451,7 @@
             value: '',
             unavailableValue: '',
             valid: true,
-            available: false,
+            available: true,
             setByPolicy: false,
             setFromUi: false,
             key: 'pinValue',
diff --git a/chrome/browser/resources/settings/os_settings_resources.grd b/chrome/browser/resources/settings/os_settings_resources.grd
index 2fd5e507..d6eaa9c 100644
--- a/chrome/browser/resources/settings/os_settings_resources.grd
+++ b/chrome/browser/resources/settings/os_settings_resources.grd
@@ -775,6 +775,18 @@
       <structure name="IDR_OS_SETTINGS_CUPS_PRINTERS_ENTRY_LIST_JS"
                  file="printing_page/cups_printers_entry_list.js"
                  type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_CUPS_PRINTERS_ENTRY_LIST_BEHAVIOR_HTML"
+                 file="printing_page/cups_printers_entry_list_behavior.html"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_CUPS_PRINTERS_ENTRY_LIST_BEHAVIOR_JS"
+                 file="printing_page/cups_printers_entry_list_behavior.js"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_CUPS_PRINTERS_ENTRY_MANAGER_HTML"
+                 file="printing_page/cups_printers_entry_manager.html"
+                 type="chrome_html" />
+      <structure name="IDR_OS_SETTINGS_CUPS_PRINTERS_ENTRY_MANAGER_JS"
+                 file="printing_page/cups_printers_entry_manager.js"
+                 type="chrome_html" />
       <structure name="IDR_OS_SETTINGS_CUPS_PRINTERS_LIST_HTML"
                  file="printing_page/cups_printers_list.html"
                  type="chrome_html" />
diff --git a/chrome/browser/resources/settings/printing_page/BUILD.gn b/chrome/browser/resources/settings/printing_page/BUILD.gn
index e761995..acd3a3b 100644
--- a/chrome/browser/resources/settings/printing_page/BUILD.gn
+++ b/chrome/browser/resources/settings/printing_page/BUILD.gn
@@ -22,6 +22,8 @@
       ":cups_printers_browser_proxy",
       ":cups_printers_entry",
       ":cups_printers_entry_list",
+      ":cups_printers_entry_list_behavior",
+      ":cups_printers_entry_manager",
       ":cups_printers_list",
       ":cups_saved_printers",
       ":printing_browser_proxy",
@@ -82,6 +84,9 @@
     deps = [
       ":cups_printer_types",
       ":cups_printers_browser_proxy",
+      ":cups_printers_entry_list_behavior",
+      ":cups_printers_entry_manager",
+      "//ui/webui/resources/js:list_property_update_behavior",
       "//ui/webui/resources/js:web_ui_listener_behavior",
     ]
   }
@@ -102,6 +107,7 @@
     deps = [
       ":cups_nearby_printers",
       ":cups_printers_browser_proxy",
+      ":cups_printers_entry_manager",
       ":cups_saved_printers",
       "..:route",
       "//ui/webui/resources/cr_components/chromeos/network:mojo_interface_provider",
@@ -136,6 +142,22 @@
     ]
   }
 
+  js_library("cups_printers_entry_list_behavior") {
+    deps = [
+      ":cups_printer_types",
+      "//ui/webui/resources/js:list_property_update_behavior",
+    ]
+  }
+
+  js_library("cups_printers_entry_manager") {
+    deps = [
+      ":cups_printer_types",
+      ":cups_printers_browser_proxy",
+      "//ui/webui/resources/js:assert",
+      "//ui/webui/resources/js:cr",
+    ]
+  }
+
   js_library("cups_printers_list") {
     deps = [
       ":cups_printers_browser_proxy",
@@ -149,7 +171,10 @@
     deps = [
       ":cups_printer_types",
       ":cups_printers_browser_proxy",
+      ":cups_printers_entry_list_behavior",
+      ":cups_printers_entry_manager",
       "//ui/webui/resources/cr_elements/cr_action_menu:cr_action_menu",
+      "//ui/webui/resources/js:list_property_update_behavior",
       "//ui/webui/resources/js:web_ui_listener_behavior",
     ]
   }
diff --git a/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html b/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html
index 923cb42..920ad26 100644
--- a/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html
+++ b/chrome/browser/resources/settings/printing_page/cups_nearby_printers.html
@@ -1,14 +1,17 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="cups_printer_types.html">
 <link rel="import" href="cups_printers_browser_proxy.html">
 <link rel="import" href="cups_printers_entry_list.html">
+<link rel="import" href="cups_printers_entry_list_behavior.html">
+<link rel="import" href="cups_printers_entry_manager.html">
 <link rel="import" href="../settings_shared_css.html">
 
 <dom-module id="settings-cups-nearby-printers">
   <template>
-    <settings-cups-printers-entry-list printers="[[nearbyPrinters_]]"
+    <settings-cups-printers-entry-list printers="[[nearbyPrinters]]"
         search-term="[[searchTerm]]">
     </settings-cups-printers-entry-list>
   </template>
diff --git a/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js b/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js
index 9f6f7b6..b5c3644 100644
--- a/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js
+++ b/chrome/browser/resources/settings/printing_page/cups_nearby_printers.js
@@ -9,20 +9,16 @@
 Polymer({
   is: 'settings-cups-nearby-printers',
 
-  behaviors: [WebUIListenerBehavior],
+  // ListPropertyUpdateBehavior is used in CupsPrintersEntryListBehavior.
+  behaviors: [
+      CupsPrintersEntryListBehavior,
+      ListPropertyUpdateBehavior,
+      WebUIListenerBehavior,
+  ],
 
   properties: {
     /**
-     * @type {!Array<!PrinterListEntry>}
-     * @private
-     */
-    nearbyPrinters_: {
-      type: Array,
-      value: () => [],
-    },
-
-    /**
-     * Search term for filtering |nearbyPrinters_|.
+     * Search term for filtering |nearbyPrinters|.
      * @type {string}
      */
     searchTerm: {
@@ -30,6 +26,12 @@
       value: '',
     },
 
+    /** @type {?CupsPrinterInfo} */
+    activePrinter: {
+      type: Object,
+      notify: true,
+    },
+
     /**
      * @type {number}
      * @private
@@ -38,51 +40,12 @@
       type: Number,
       value: -1,
     },
-
-    /** @type {?CupsPrinterInfo} */
-    activePrinter: {
-      type: Object,
-      notify: true,
-    },
   },
 
   listeners: {
     'add-automatic-printer': 'onAddAutomaticPrinter_',
   },
 
-  /** @override */
-  attached: function() {
-    settings.CupsPrintersBrowserProxyImpl.getInstance()
-        .startDiscoveringPrinters();
-    this.addWebUIListener(
-        'on-nearby-printers-changed', this.onNearbyPrintersChanged_.bind(this));
-  },
-
-  /**
-   * @param {!Array<!CupsPrinterInfo>} automaticPrinters
-   * @param {!Array<!CupsPrinterInfo>} discoveredPrinters
-   * @private
-   */
-  onNearbyPrintersChanged_: function(automaticPrinters, discoveredPrinters) {
-    if (!automaticPrinters && !discoveredPrinters) {
-      return;
-    }
-
-    const printers = /** @type{!Array<!PrinterListEntry>} */ ([]);
-
-    for (const printer of automaticPrinters) {
-      printers.push({printerInfo: printer,
-                     printerType: PrinterType.AUTOMATIC});
-    }
-
-    for (const printer of discoveredPrinters) {
-      printers.push({printerInfo: printer,
-                     printerType: PrinterType.DISCOVERED});
-    }
-
-    this.nearbyPrinters_ = printers;
-  },
-
   /**
    * @param {!CustomEvent<{item: !PrinterListEntry}>} e
    * @private
@@ -106,12 +69,11 @@
    * @private
    */
   setActivePrinter_: function(item) {
-    this.activePrinterListEntryIndex_ =
-        this.nearbyPrinters_.findIndex(
-            printer => printer.printerInfo == item.printerInfo);
+    this.activePrinterListEntryIndex_ = this.nearbyPrinters.findIndex(
+        printer => printer.printerInfo.printerId == item.printerInfo.printerId);
 
     this.activePrinter =
-        this.get(['nearbyPrinters_', this.activePrinterListEntryIndex_])
+        this.get(['nearbyPrinters', this.activePrinterListEntryIndex_])
         .printerInfo;
   },
 
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers.html b/chrome/browser/resources/settings/printing_page/cups_printers.html
index 6777282..c5b866a 100644
--- a/chrome/browser/resources/settings/printing_page/cups_printers.html
+++ b/chrome/browser/resources/settings/printing_page/cups_printers.html
@@ -16,6 +16,7 @@
 <link rel="import" href="cups_add_printer_dialog.html">
 <link rel="import" href="cups_edit_printer_dialog.html">
 <link rel="import" href="cups_printer_shared_css.html">
+<link rel="import" href="cups_printers_entry_manager.html">
 <link rel="import" href="cups_printers_list.html">
 <link rel="import" href="cups_saved_printers.html">
 <link rel="import" href="cups_nearby_printers.html">
@@ -142,7 +143,6 @@
 
           <settings-cups-saved-printers id="savedPrinters"
               active-printer="{{activePrinter}}"
-              saved-printers="[[savedPrinters_]]"
               search-term="[[searchTerm]]">
           </settings-cups-saved-printers>
         </div>
@@ -197,7 +197,7 @@
       </settings-cups-edit-printer-dialog>
     </template>
 
-    <cr-toast id="errorToast" duration="3000">
+    <cr-toast id="errorToast" duration="3000" role="alert">
       <div class="error-message" id="addPrinterDoneMessage">
         [[addPrinterResultText_]]
       </div>
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers.js b/chrome/browser/resources/settings/printing_page/cups_printers.js
index 8b39c62..ebd1009 100644
--- a/chrome/browser/resources/settings/printing_page/cups_printers.js
+++ b/chrome/browser/resources/settings/printing_page/cups_printers.js
@@ -81,11 +81,16 @@
   /** @private {?chromeos.networkConfig.mojom.CrosNetworkConfigRemote} */
   networkConfig_: null,
 
+  /** @private {settings.printing.CupsPrintersEntryManager} */
+  entryManager_: null,
+
   /** @override */
   created: function() {
     this.networkConfig_ =
         network_config.MojoInterfaceProviderImpl.getInstance()
             .getMojoServiceRemote();
+    this.entryManager_ =
+        settings.printing.CupsPrintersEntryManager.getInstance();
   },
 
   /** @override */
@@ -110,7 +115,6 @@
     this.updateCupsPrintersList_();
   },
 
-
   /**
    * settings.RouteObserverBehavior
    * @param {!settings.Route} route
@@ -119,10 +123,14 @@
   currentRouteChanged: function(route) {
     if (route != settings.routes.CUPS_PRINTERS) {
       cr.removeWebUIListener('on-printers-changed');
+      this.entryManager_.removeWebUIListeners();
       return;
     }
+
+    this.entryManager_.addWebUIListeners();
     cr.addWebUIListener(
         'on-printers-changed', this.onPrintersChanged_.bind(this));
+    this.updateCupsPrintersList_();
   },
 
   /**
@@ -152,13 +160,11 @@
     const printerName = event.detail.printerName;
     switch (event.detail.resultCode) {
       case PrinterSetupResult.SUCCESS:
-        this.updateCupsPrintersList_();
         this.addPrinterResultText_ =
             loadTimeData.getStringF('printerAddedSuccessfulMessage',
                                     printerName);
         break;
       case PrinterSetupResult.EDIT_SUCCESS:
-        this.updateCupsPrintersList_();
         this.addPrinterResultText_ =
             loadTimeData.getStringF('printerEditedSuccessfulMessage',
                                     printerName);
@@ -203,6 +209,7 @@
           printer => /** @type {!PrinterListEntry} */({
               printerInfo: printer,
               printerType: PrinterType.SAVED}));
+      this.entryManager_.setSavedPrintersList(this.savedPrinters_);
     } else {
       this.printers = cupsPrintersList.printerList;
     }
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_entry_list.js b/chrome/browser/resources/settings/printing_page/cups_printers_entry_list.js
index 3fe5f69..573655d 100644
--- a/chrome/browser/resources/settings/printing_page/cups_printers_entry_list.js
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_entry_list.js
@@ -69,12 +69,14 @@
     // |filteredPrinters_| is just |printers|.
     const updatedPrinters = this.searchTerm ?
         this.printers.filter(
-            item =>this.matchesSearchTerm_(item.printerInfo,this.searchTerm)) :
+            item =>
+                this.matchesSearchTerm_(item.printerInfo, this.searchTerm)) :
         this.printers.slice();
 
     updatedPrinters.sort(this.sortPrinters_);
 
-    this.updateList('filteredPrinters_', printer => printer.printerInfo,
+    this.updateList(
+        'filteredPrinters_', printer => printer.printerInfo.printerId,
         updatedPrinters);
 
     this.showNoSearchResultsMessage_ =
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html b/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html
new file mode 100644
index 0000000..6f303194
--- /dev/null
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.html
@@ -0,0 +1,2 @@
+<link rel="import" href="cups_printer_types.html">
+<script src="cups_printers_entry_list_behavior.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js b/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js
new file mode 100644
index 0000000..9b557ff
--- /dev/null
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_entry_list_behavior.js
@@ -0,0 +1,81 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * @fileoverview Polymer behavior for observing CupsPrintersEntryManager events.
+ * Use this behavior if you want to receive a dynamically updated list of both
+ * saved and nearby printers.
+ */
+
+/** @polymerBehavior */
+const CupsPrintersEntryListBehavior = {
+  properties: {
+    /** @private {!settings.printing.CupsPrintersEntryManager} */
+    entryManager_: Object,
+
+    /** @type {!Array<!PrinterListEntry>} */
+    savedPrinters: {
+      type: Array,
+      value: () => [],
+    },
+
+    /** @type {!Array<!PrinterListEntry>} */
+    nearbyPrinters: {
+      type: Array,
+      value: () => [],
+    },
+  },
+
+  /** @override */
+  created: function() {
+    this.entryManager_ =
+        settings.printing.CupsPrintersEntryManager.getInstance();
+  },
+
+  /** @override */
+  attached: function() {
+    this.entryManager_.addOnSavedPrintersChangedListener(
+        this.onSavedPrintersChanged_.bind(this));
+    this.entryManager_.addOnNearbyPrintersChangedListener(
+        this.onNearbyPrintersChanged_.bind(this));
+
+    // Initialize saved and nearby printers list.
+    this.onSavedPrintersChanged_(
+        this.entryManager_.savedPrinters, [] /* printerAdded */,
+        [] /* printerRemoved */);
+    this.onNearbyPrintersChanged_(this.entryManager_.nearbyPrinters);
+  },
+
+  /** @override */
+  detached: function() {
+    this.entryManager_.removeOnSavedPrintersChangedListener(
+        this.onSavedPrintersChanged_.bind(this));
+    this.entryManager_.removeOnNearbyPrintersChangedListener(
+        this.onNearbyPrintersChanged_.bind(this));
+  },
+
+  /**
+   * Non-empty/null fields indicate the applicable change to be notified.
+   * @param {!Array<!PrinterListEntry>} savedPrinters
+   * @param {!Array<!PrinterListEntry>} addedPrinters
+   * @param {!Array<!PrinterListEntry>} removedPrinters
+   * @private
+   */
+  onSavedPrintersChanged_: function(
+      savedPrinters, addedPrinters, removedPrinters) {
+    this.updateList(
+        'savedPrinters', printer => printer.printerInfo.printerId,
+        savedPrinters);
+  },
+
+  /**
+   * @param {!Array<!PrinterListEntry>} printerList
+   * @private
+   */
+  onNearbyPrintersChanged_: function(printerList) {
+    this.updateList(
+        'nearbyPrinters', printer => printer.printerInfo.printerId,
+        printerList);
+  }
+};
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html b/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html
new file mode 100644
index 0000000..d8e4aac
--- /dev/null
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.html
@@ -0,0 +1,5 @@
+<link rel="import" href="chrome://resources/html/assert.html">
+<link rel="import" href="chrome://resources/html/cr.html">
+<link rel="import" href="cups_printer_types.html">
+<link rel="import" href="cups_printers_browser_proxy.html">
+<script src="cups_printers_entry_manager.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js b/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js
new file mode 100644
index 0000000..0beacd37
--- /dev/null
+++ b/chrome/browser/resources/settings/printing_page/cups_printers_entry_manager.js
@@ -0,0 +1,191 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/**
+ * Function which provides the client with metadata about a change
+ * to a list of saved printers. The first parameter is the updated list of
+ * printers after the change, the second parameter is the newly-added printer
+ * (if it exists), and the third parameter is the newly-removed printer
+ * (if it exists).
+ * @typedef {!function(!Array<!PrinterListEntry>, !Array<!PrinterListEntry>,
+ *     !Array<!PrinterListEntry>): void}
+ */
+let PrintersListWithDeltasCallback;
+
+/**
+ * Function which provides the client with a list that contains the nearby
+ * printers list. The parameter is the updated list of printers after any
+ * changes.
+ * @typedef {function(!Array<!PrinterListEntry>): void}
+ */
+let PrintersListCallback;
+
+cr.define('settings.printing', function() {
+  /**
+   * @param {!PrinterListEntry} first
+   * @param {!PrinterListEntry} second
+   * @return {boolean}
+   * @private
+   */
+  function arePrinterIdsEqual_(first, second) {
+    return first.printerInfo.printerId == second.printerInfo.printerId;
+  }
+
+  /**
+   * Finds the printers that are in |firstArr| but not in |secondArr|.
+   * @param {!Array<!PrinterListEntry>} firstArr
+   * @param {!Array<!PrinterListEntry>} secondArr
+   * @return {!Array<!PrinterListEntry>}
+   * @private
+   */
+  function findDifference_(firstArr, secondArr) {
+    return firstArr.filter((firstArrEntry) => {
+      return !secondArr.some(arePrinterIdsEqual_.bind(this, firstArrEntry));
+    });
+  }
+
+  /**
+   * Class for managing printer entries. Holds both Saved and Nearby printers
+   * and notifies observers of any applicable changes to either printer lists.
+   */
+  class CupsPrintersEntryManager {
+    constructor() {
+      /** @private {!Array<!PrinterListEntry>} */
+      this.savedPrinters_ = [];
+
+      /** @private {!Array<!PrinterListEntry>} */
+      this.nearbyPrinters_ = [];
+
+      /** @private {!Array<PrintersListWithDeltasCallback>} */
+      this.onSavedPrintersChangedListeners_ = [];
+
+      /** @type {!Array<PrintersListCallback>} */
+      this.onNearbyPrintersChangedListeners_ = [];
+    }
+
+    addWebUIListeners() {
+      // TODO(1005905): Add on-printers-changed listener here once legacy code
+      // is removed.
+      cr.addWebUIListener(
+          'on-nearby-printers-changed', this.setNearbyPrintersList.bind(this));
+      settings.CupsPrintersBrowserProxyImpl.getInstance()
+          .startDiscoveringPrinters();
+    }
+
+    removeWebUIListeners() {
+      cr.removeWebUIListener('on-nearby-printers-changed');
+    }
+
+    /** @return {!Array<!PrinterListEntry>} */
+    get savedPrinters() {
+      return this.savedPrinters_;
+    }
+
+    /** @return {!Array<!PrinterListEntry>} */
+    get nearbyPrinters() {
+      return this.nearbyPrinters_;
+    }
+
+    /** @param {PrintersListWithDeltasCallback} listener */
+    addOnSavedPrintersChangedListener(listener) {
+      this.onSavedPrintersChangedListeners_.push(listener);
+    }
+
+    /** @param {PrintersListWithDeltasCallback} listener */
+    removeOnSavedPrintersChangedListener(listener) {
+      this.onSavedPrintersChangedListeners_ =
+          this.onSavedPrintersChangedListeners_.filter(lis => lis != listener);
+    }
+
+    /** @param {PrintersListCallback} listener */
+    addOnNearbyPrintersChangedListener(listener) {
+      this.onNearbyPrintersChangedListeners_.push(listener);
+    }
+
+    /** @param {PrintersListCallback} listener */
+    removeOnNearbyPrintersChangedListener(listener) {
+      this.onNearbyPrintersChangedListeners_ =
+          this.onNearbyPrintersChangedListeners_.filter(lis => lis != listener);
+    }
+
+    /**
+     * Sets the saved printers list and notifies observers of any applicable
+     * changes.
+     * @param {!Array<!PrinterListEntry>} printerList
+     */
+    setSavedPrintersList(printerList) {
+      if (printerList.length > this.savedPrinters_.length) {
+        const diff = findDifference_(printerList, this.savedPrinters_);
+        this.savedPrinters_ = printerList;
+        this.notifyOnSavedPrintersChangedListeners_(
+            this.savedPrinters_, diff, [] /* printersRemoved */);
+        return;
+      }
+
+      if (printerList.length < this.savedPrinters_.length) {
+        const diff = findDifference_(this.savedPrinters_, printerList);
+        this.savedPrinters_ = printerList;
+        this.notifyOnSavedPrintersChangedListeners_(
+            this.savedPrinters_, [] /* printersAdded */, diff);
+        return;
+      }
+
+      this.savedPrinters_ = printerList;
+      this.notifyOnSavedPrintersChangedListeners_(
+          this.savedPrinters_, [] /* printersAdded */,
+          [] /* printersRemoved */);
+    }
+
+    /**
+     * Sets the nearby printers list and notifies observers of any applicable
+     * changes.
+     * @param {!Array<!CupsPrinterInfo>} automaticPrinters
+     * @param {!Array<!CupsPrinterInfo>} discoveredPrinters
+     */
+    setNearbyPrintersList(automaticPrinters, discoveredPrinters) {
+      if (!automaticPrinters && !discoveredPrinters) {
+        return;
+      }
+
+      this.nearbyPrinters_ = [];
+
+      for (const printer of automaticPrinters) {
+        this.nearbyPrinters_.push(
+            {printerInfo: printer, printerType: PrinterType.AUTOMATIC});
+      }
+
+      for (const printer of discoveredPrinters) {
+        this.nearbyPrinters_.push(
+            {printerInfo: printer, printerType: PrinterType.DISCOVERED});
+      }
+
+      this.notifyOnNearbyPrintersChangedListeners_();
+    }
+
+    /**
+     * Non-empty/null fields indicate the applicable change to be notified.
+     * @param {!Array<!PrinterListEntry>} savedPrinters
+     * @param {!Array<!PrinterListEntry>} addedPrinter
+     * @param {!Array<!PrinterListEntry>} removedPrinter
+     * @private
+     */
+    notifyOnSavedPrintersChangedListeners_(
+        savedPrinters, addedPrinter, removedPrinter) {
+      this.onSavedPrintersChangedListeners_.forEach(
+          listener => listener(savedPrinters, addedPrinter, removedPrinter));
+    }
+
+    /** @private */
+    notifyOnNearbyPrintersChangedListeners_() {
+      this.onNearbyPrintersChangedListeners_.forEach(
+          listener => listener(this.nearbyPrinters_));
+    }
+  }
+
+  cr.addSingletonGetter(CupsPrintersEntryManager);
+
+  return {
+    CupsPrintersEntryManager: CupsPrintersEntryManager,
+  };
+});
\ No newline at end of file
diff --git a/chrome/browser/resources/settings/printing_page/cups_saved_printers.html b/chrome/browser/resources/settings/printing_page/cups_saved_printers.html
index 1bb09c08..6d89dcd 100644
--- a/chrome/browser/resources/settings/printing_page/cups_saved_printers.html
+++ b/chrome/browser/resources/settings/printing_page/cups_saved_printers.html
@@ -1,10 +1,12 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
 <link rel="import" href="chrome://resources/cr_elements/cr_action_menu/cr_action_menu.html">
+<link rel="import" href="chrome://resources/html/list_property_update_behavior.html">
 <link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="cups_printer_types.html">
 <link rel="import" href="cups_printers_browser_proxy.html">
 <link rel="import" href="cups_printers_entry_list.html">
+<link rel="import" href="cups_printers_entry_list_behavior.html">
 <link rel="import" href="../settings_shared_css.html">
 
 <dom-module id="settings-cups-saved-printers">
diff --git a/chrome/browser/resources/settings/printing_page/cups_saved_printers.js b/chrome/browser/resources/settings/printing_page/cups_saved_printers.js
index 85ef694..77c68a95 100644
--- a/chrome/browser/resources/settings/printing_page/cups_saved_printers.js
+++ b/chrome/browser/resources/settings/printing_page/cups_saved_printers.js
@@ -9,16 +9,14 @@
 Polymer({
   is: 'settings-cups-saved-printers',
 
+  // ListPropertyUpdateBehavior is used in CupsPrintersEntryListBehavior.
   behaviors: [
-      WebUIListenerBehavior,
+    CupsPrintersEntryListBehavior,
+    ListPropertyUpdateBehavior,
+    WebUIListenerBehavior,
   ],
 
   properties: {
-    /** @type {!Array<!PrinterListEntry>} */
-    savedPrinters: {
-      type: Array,
-    },
-
     /**
      * Search term for filtering |savedPrinters|.
      * @type {string}
@@ -28,6 +26,12 @@
       value: '',
     },
 
+    /** @type {?CupsPrinterInfo} */
+    activePrinter: {
+      type: Object,
+      notify: true,
+    },
+
     /**
      * @type {number}
      * @private
@@ -36,12 +40,6 @@
       type: Number,
       value: -1,
     },
-
-    /** @type {?CupsPrinterInfo} */
-    activePrinter: {
-      type: Object,
-      notify: true,
-    },
   },
 
   listeners: {
@@ -62,9 +60,8 @@
    */
   onOpenActionMenu_: function(e) {
     const item = /** @type {!PrinterListEntry} */(e.detail.item);
-    this.activePrinterListEntryIndex_ =
-        this.savedPrinters.findIndex(
-            printer => printer.printerInfo == item.printerInfo);
+    this.activePrinterListEntryIndex_ = this.savedPrinters.findIndex(
+        printer => printer.printerInfo.printerId == item.printerInfo.printerId);
     this.activePrinter =
         this.get(['savedPrinters', this.activePrinterListEntryIndex_])
         .printerInfo;
@@ -92,5 +89,6 @@
   /** @private */
   closeActionMenu_: function() {
     this.$$('cr-action-menu').close();
-  }
+  },
+
 });
diff --git a/chrome/browser/resources/settings/settings_resources.grd b/chrome/browser/resources/settings/settings_resources.grd
index fc7e730..9db64f2 100644
--- a/chrome/browser/resources/settings/settings_resources.grd
+++ b/chrome/browser/resources/settings/settings_resources.grd
@@ -987,6 +987,18 @@
         <structure name="IDR_SETTINGS_CUPS_PRINTERS_ENTRY_LIST_JS"
                    file="printing_page/cups_printers_entry_list.js"
                    type="chrome_html" />
+        <structure name="IDR_SETTINGS_CUPS_PRINTERS_ENTRY_LIST_BEHAVIOR_HTML"
+                   file="printing_page/cups_printers_entry_list_behavior.html"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CUPS_PRINTERS_ENTRY_LIST_BEHAVIOR_JS"
+                   file="printing_page/cups_printers_entry_list_behavior.js"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CUPS_PRINTERS_ENTRY_MANAGER_HTML"
+                   file="printing_page/cups_printers_entry_manager.html"
+                   type="chrome_html" />
+        <structure name="IDR_SETTINGS_CUPS_PRINTERS_ENTRY_MANAGER_JS"
+                   file="printing_page/cups_printers_entry_manager.js"
+                   type="chrome_html" />
         <structure name="IDR_SETTINGS_CUPS_PRINTERS_LIST_HTML"
                    file="printing_page/cups_printers_list.html"
                    type="chrome_html" />
diff --git a/chrome/browser/resources/settings/site_settings/category_default_setting.js b/chrome/browser/resources/settings/site_settings/category_default_setting.js
index 5426cf0..a3e2d32 100644
--- a/chrome/browser/resources/settings/site_settings/category_default_setting.js
+++ b/chrome/browser/resources/settings/site_settings/category_default_setting.js
@@ -177,10 +177,22 @@
     if (update.source !== undefined &&
         update.source != ContentSettingProvider.PREFERENCE) {
       basePref.enforcement = chrome.settingsPrivate.Enforcement.ENFORCED;
-      basePref.controlledBy =
-          update.source == ContentSettingProvider.EXTENSION ?
-          chrome.settingsPrivate.ControlledBy.EXTENSION :
-          chrome.settingsPrivate.ControlledBy.USER_POLICY;
+      switch (update.source) {
+        case ContentSettingProvider.POLICY:
+          basePref.controlledBy =
+              chrome.settingsPrivate.ControlledBy.DEVICE_POLICY;
+          break;
+        case ContentSettingProvider.SUPERVISED_USER:
+          basePref.controlledBy = chrome.settingsPrivate.ControlledBy.PARENT;
+          break;
+        case ContentSettingProvider.EXTENSION:
+          basePref.controlledBy = chrome.settingsPrivate.ControlledBy.EXTENSION;
+          break;
+        default:
+          basePref.controlledBy =
+              chrome.settingsPrivate.ControlledBy.USER_POLICY;
+          break;
+      }
     }
 
     const prefValue = this.computeIsSettingEnabled(update.setting);
diff --git a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
index 2da1d57b..2b826c3 100644
--- a/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
+++ b/chrome/browser/resources/settings/site_settings/site_settings_prefs_browser_proxy.js
@@ -14,8 +14,16 @@
  * @enum {string}
  */
 const ContentSettingProvider = {
+  POLICY: 'policy',
+  SUPERVISED_USER: 'supervised_user',
   EXTENSION: 'extension',
+  INSTALLED_WEBAPP_PROVIDER: 'installed_webapp_provider',
+  NOTIFICATION_ANDROID: 'notification_android',
+  EPHEMERAL: 'ephemeral',
   PREFERENCE: 'preference',
+  DEFAULT: 'default',
+  TESTS: 'tests',
+  TESTS_OTHER: 'tests_other'
 };
 
 /**
diff --git a/chrome/browser/resources/webapks/BUILD.gn b/chrome/browser/resources/webapks/BUILD.gn
index 6e4c0a5..2a50290 100644
--- a/chrome/browser/resources/webapks/BUILD.gn
+++ b/chrome/browser/resources/webapks/BUILD.gn
@@ -12,9 +12,8 @@
 
 js_library("about_webapks") {
   deps = [
-    "//ui/webui/resources/js:cr",
-    "//ui/webui/resources/js:load_time_data",
-    "//ui/webui/resources/js:util",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:util.m",
   ]
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
diff --git a/chrome/browser/resources/webapks/about_webapks.html b/chrome/browser/resources/webapks/about_webapks.html
index f09ce2c5d..4e91c52 100644
--- a/chrome/browser/resources/webapks/about_webapks.html
+++ b/chrome/browser/resources/webapks/about_webapks.html
@@ -11,12 +11,8 @@
     <title>About WebAPKs</title>
     <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
     <link rel="stylesheet" href="about_webapks.css">
-    <link rel="import" href="chrome://resources/html/cr.html">
-    <link rel="import" href="chrome://resources/html/load_time_data.html">
-    <link rel="import" href="chrome://resources/html/util.html">
 
-    <script src="chrome://webapks/webapks.js"></script>
-    <script src="chrome://webapks/strings.js"></script>
+    <script type="module" src="webapks.js"></script>
   </head>
 
   <body>
diff --git a/chrome/browser/resources/webapks/about_webapks.js b/chrome/browser/resources/webapks/about_webapks.js
index b21be02..10bc849 100644
--- a/chrome/browser/resources/webapks/about_webapks.js
+++ b/chrome/browser/resources/webapks/about_webapks.js
@@ -2,6 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import './strings.m.js';
+
+import {addWebUIListener} from 'chrome://resources/js/cr.m.js';
+import {$, createElementWithClassName} from 'chrome://resources/js/util.m.js';
+
 /**
  * @typedef {{
  *   name: string,
@@ -54,17 +59,6 @@
 }
 
 /**
- * Callback from the backend with the information of a WebAPK to display.
- * This will be called once per WebAPK.
- *
- * @param {!WebApkInfo} webApkInfo Object with information about an
- * installed WebAPK.
- */
-function returnWebApkInfo(webApkInfo) {
-  addWebApk(webApkInfo);
-}
-
-/**
  * @param {HTMLElement} webApkList List of elements which contain WebAPK
  * attributes.
  * @param {string} label Text that identifies the new element.
@@ -96,7 +90,7 @@
 /**
  * Adds a new entry to the page with the information of a WebAPK.
  *
- * @param {WebApkInfo} webApkInfo Information about an installed WebAPK.
+ * @param {!WebApkInfo} webApkInfo Information about an installed WebAPK.
  */
 function addWebApk(webApkInfo) {
   /** @type {HTMLElement} */ const webApkList = $('webapk-list');
@@ -160,5 +154,8 @@
 }
 
 document.addEventListener('DOMContentLoaded', function() {
+  // Add a WebUI listener for the 'web-apk-info' event emmitted from the
+  // backend. This will be triggered once per WebAPK.
+  addWebUIListener('web-apk-info', addWebApk);
   chrome.send('requestWebApksInfo');
 });
diff --git a/chrome/browser/safe_browsing/download_protection/download_reporter.cc b/chrome/browser/safe_browsing/download_protection/download_reporter.cc
index 356314c..8bfb96d7 100644
--- a/chrome/browser/safe_browsing/download_protection/download_reporter.cc
+++ b/chrome/browser/safe_browsing/download_protection/download_reporter.cc
@@ -90,62 +90,34 @@
 DownloadReporter::DownloadReporter() {
   profiles_registrar_.Add(this, chrome::NOTIFICATION_PROFILE_CREATED,
                           content::NotificationService::AllSources());
-  profiles_registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
-                          content::NotificationService::AllSources());
 }
 
-DownloadReporter::~DownloadReporter() {
-  profiles_registrar_.RemoveAll();
-
-  for (download::SimpleDownloadManagerCoordinator* coordinator :
-       observed_coordinators_) {
-    coordinator->RemoveObserver(this);
-  }
-
-  for (download::DownloadItem* download_item : observed_downloads_) {
-    download_item->RemoveObserver(this);
-  }
-}
+DownloadReporter::~DownloadReporter() = default;
 
 void DownloadReporter::Observe(int type,
                                const content::NotificationSource& source,
                                const content::NotificationDetails& details) {
-  switch (type) {
-    case chrome::NOTIFICATION_PROFILE_CREATED: {
-      DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-      Profile* profile = content::Source<Profile>(source).ptr();
-      download::SimpleDownloadManagerCoordinator* coordinator =
-          SimpleDownloadManagerCoordinatorFactory::GetForKey(
-              profile->GetProfileKey());
-      coordinator->AddObserver(this);
-      observed_coordinators_.insert(coordinator);
-      break;
-    }
-    case chrome::NOTIFICATION_PROFILE_DESTROYED: {
-      DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-      Profile* profile = content::Source<Profile>(source).ptr();
-      download::SimpleDownloadManagerCoordinator* coordinator =
-          SimpleDownloadManagerCoordinatorFactory::GetForKey(
-              profile->GetProfileKey());
-      coordinator->RemoveObserver(this);
-      observed_coordinators_.erase(coordinator);
-      break;
-    }
-    default:
-      NOTREACHED();
-  }
+  DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_CREATED);
+  Profile* profile = content::Source<Profile>(source).ptr();
+  download::SimpleDownloadManagerCoordinator* coordinator =
+      SimpleDownloadManagerCoordinatorFactory::GetForKey(
+          profile->GetProfileKey());
+  observed_coordinators_.Add(coordinator);
+}
+
+void DownloadReporter::OnManagerGoingDown(
+    download::SimpleDownloadManagerCoordinator* coordinator) {
+  observed_coordinators_.Remove(coordinator);
 }
 
 void DownloadReporter::OnDownloadCreated(download::DownloadItem* download) {
   danger_types_[download] = download->GetDangerType();
-  download->AddObserver(this);
-  observed_downloads_.insert(download);
+  observed_downloads_.Add(download);
 }
 
 void DownloadReporter::OnDownloadDestroyed(download::DownloadItem* download) {
-  download->RemoveObserver(this);
+  observed_downloads_.Remove(download);
   danger_types_.erase(download);
-  observed_downloads_.erase(download);
 }
 
 void DownloadReporter::OnDownloadUpdated(download::DownloadItem* download) {
diff --git a/chrome/browser/safe_browsing/download_protection/download_reporter.h b/chrome/browser/safe_browsing/download_protection/download_reporter.h
index ab2bb7b..0815bc5 100644
--- a/chrome/browser/safe_browsing/download_protection/download_reporter.h
+++ b/chrome/browser/safe_browsing/download_protection/download_reporter.h
@@ -5,6 +5,8 @@
 #ifndef CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_DOWNLOAD_REPORTER_H_
 #define CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_DOWNLOAD_REPORTER_H_
 
+#include "base/macros.h"
+#include "base/scoped_observer.h"
 #include "components/download/public/common/download_item.h"
 #include "components/download/public/common/simple_download_manager_coordinator.h"
 #include "content/public/browser/notification_observer.h"
@@ -28,6 +30,8 @@
                const content::NotificationDetails& details) override;
 
   // SimpleDownloadManagerCoordinator::Observer implementation:
+  void OnManagerGoingDown(
+      download::SimpleDownloadManagerCoordinator* coordinator) override;
   void OnDownloadCreated(download::DownloadItem* download) override;
 
   // DownloadItem::Observer implementation:
@@ -38,8 +42,13 @@
   content::NotificationRegistrar profiles_registrar_;
   base::flat_map<download::DownloadItem*, download::DownloadDangerType>
       danger_types_;
-  std::set<download::SimpleDownloadManagerCoordinator*> observed_coordinators_;
-  std::set<download::DownloadItem*> observed_downloads_;
+  ScopedObserver<download::SimpleDownloadManagerCoordinator,
+                 download::SimpleDownloadManagerCoordinator::Observer>
+      observed_coordinators_{this};
+  ScopedObserver<download::DownloadItem, download::DownloadItem::Observer>
+      observed_downloads_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(DownloadReporter);
 };
 
 }  // namespace safe_browsing
diff --git a/chrome/browser/search/ntp_features.cc b/chrome/browser/search/ntp_features.cc
index 0f7d2d5..fe2de2e 100644
--- a/chrome/browser/search/ntp_features.cc
+++ b/chrome/browser/search/ntp_features.cc
@@ -23,7 +23,7 @@
 // If enabled, the NTP shortcut layout will be replaced with a grid layout that
 // enables better animations.
 const base::Feature kGridLayoutForNtpShortcuts{
-    "GridLayoutForNtpShortcuts", base::FEATURE_DISABLED_BY_DEFAULT};
+    "GridLayoutForNtpShortcuts", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // If enabled, the user will see the second version of the customization picker.
 const base::Feature kNtpCustomizationMenuV2{"NtpCustomizationMenuV2",
diff --git a/chrome/browser/search/search_suggest/search_suggest_service.cc b/chrome/browser/search/search_suggest/search_suggest_service.cc
index 3a0a2365..655a01f 100644
--- a/chrome/browser/search/search_suggest/search_suggest_service.cc
+++ b/chrome/browser/search/search_suggest/search_suggest_service.cc
@@ -15,6 +15,7 @@
 #include "chrome/browser/search/search.h"
 #include "chrome/browser/search/search_suggest/search_suggest_loader.h"
 #include "chrome/common/pref_names.h"
+#include "components/omnibox/browser/omnibox_field_trial.h"
 #include "components/omnibox/common/omnibox_features.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h"
@@ -97,10 +98,16 @@
 
 // static
 bool SearchSuggestService::IsEnabled() {
-  // Search suggestions should be disabled when on-focus zero-prefix suggestions
-  // are displaying in the NTP.
   return !base::FeatureList::IsEnabled(omnibox::kZeroSuggestionsOnNTP) &&
-         !base::FeatureList::IsEnabled(omnibox::kZeroSuggestionsOnNTPRealbox);
+         !base::FeatureList::IsEnabled(omnibox::kZeroSuggestionsOnNTPRealbox) &&
+         !(base::FeatureList::IsEnabled(omnibox::kOnFocusSuggestions) &&
+           (!OmniboxFieldTrial::GetZeroSuggestVariants(
+                 metrics::OmniboxEventProto::NTP_REALBOX)
+                 .empty() ||
+            !OmniboxFieldTrial::GetZeroSuggestVariants(
+                 metrics::OmniboxEventProto::
+                     INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS)
+                 .empty()));
 }
 
 SearchSuggestService::SearchSuggestService(
diff --git a/chrome/browser/search/search_suggest/search_suggest_service.h b/chrome/browser/search/search_suggest/search_suggest_service.h
index a4ecd027..386b4c1 100644
--- a/chrome/browser/search/search_suggest/search_suggest_service.h
+++ b/chrome/browser/search/search_suggest/search_suggest_service.h
@@ -28,6 +28,11 @@
 // user signs in or out, the cached value is cleared.
 class SearchSuggestService : public KeyedService {
  public:
+  // Search suggestions should be disabled when on-focus zero-prefix suggestions
+  // are displaying in the NTP. Returns false if omnibox::kZeroSuggestionsOnNTP
+  // or omnibox::kZeroSuggestionsOnNTPRealboxkNtpRealbox are enabled; or
+  // omnibox::kOnFocusSuggestions is enabled and configured to show suggestions
+  // of some type in the NTP Omnibox or Realbox.
   static bool IsEnabled();
 
   SearchSuggestService(Profile* profile,
diff --git a/chrome/browser/sessions/session_restore_browsertest.cc b/chrome/browser/sessions/session_restore_browsertest.cc
index 8ce24d1..1328428 100644
--- a/chrome/browser/sessions/session_restore_browsertest.cc
+++ b/chrome/browser/sessions/session_restore_browsertest.cc
@@ -939,9 +939,13 @@
 // to do a command reset when quitting and restoring.
 class SessionRestoreTabGroupsTest : public SessionRestoreTest,
                                     public testing::WithParamInterface<bool> {
+ public:
+  SessionRestoreTabGroupsTest() {
+    feature_override_.InitAndEnableFeature(features::kTabGroups);
+  }
+
  protected:
   void SetUpOnMainThread() override {
-    feature_override_.InitAndEnableFeature(features::kTabGroups);
     SessionRestoreTest::SetUpOnMainThread();
   }
 
@@ -1031,8 +1035,11 @@
 
 // Ensure tab groups aren't restored if |features::kTabGroups| is disabled.
 // Regression test for crbug.com/983962.
+//
+// TODO(https://crbug.com/1012605): Find a way to cover this regression without
+// relying on dynamic FeatureList overrides mid-test.
 IN_PROC_BROWSER_TEST_F(SessionRestoreTest,
-                       GroupsNotRestoredWhenFeatureDisabled) {
+                       DISABLED_GroupsNotRestoredWhenFeatureDisabled) {
   auto feature_override = std::make_unique<base::test::ScopedFeatureList>();
   feature_override->InitAndEnableFeature(features::kTabGroups);
 
@@ -1866,7 +1873,12 @@
 class SecFetchSiteSessionRestoreTest : public SessionRestoreTest {
  public:
   SecFetchSiteSessionRestoreTest()
-      : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
+      : https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
+    feature_list_.InitWithFeatures(
+        {network::features::kFetchMetadata,
+         network::features::kFetchMetadataDestination},
+        {});
+  }
 
   void SetUpOnMainThread() override {
     SessionRestoreTest::SetUpOnMainThread();
@@ -1877,11 +1889,6 @@
     https_test_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_OK);
     ASSERT_TRUE(https_test_server_.Start());
     ASSERT_TRUE(embedded_test_server()->Start());
-
-    feature_list_.InitWithFeatures(
-        {network::features::kFetchMetadata,
-         network::features::kFetchMetadataDestination},
-        {});
   }
 
   content::WebContents* GetTab(Browser* browser, int tab_index) {
diff --git a/chrome/browser/sessions/tab_restore_browsertest.cc b/chrome/browser/sessions/tab_restore_browsertest.cc
index d0d9661..fcbc65ce 100644
--- a/chrome/browser/sessions/tab_restore_browsertest.cc
+++ b/chrome/browser/sessions/tab_restore_browsertest.cc
@@ -891,10 +891,17 @@
   EXPECT_EQ(sessions::TabRestoreService::TAB, service->entries().front()->type);
 }
 
-IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTab) {
-  base::test::ScopedFeatureList feature_override;
-  feature_override.InitAndEnableFeature(features::kTabGroups);
+class TabRestoreTestWithTabGroupsEnabled : public TabRestoreTest {
+ public:
+  TabRestoreTestWithTabGroupsEnabled() {
+    feature_list_.InitAndEnableFeature(features::kTabGroups);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(TabRestoreTestWithTabGroupsEnabled, RestoreGroupedTab) {
   const int tab_count = AddSomeTabs(browser(), 1);
   ASSERT_LE(2, tab_count);
 
@@ -914,10 +921,8 @@
             browser()->tab_strip_model()->GetTabGroupForTab(grouped_tab_index));
 }
 
-IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindowWithGroupedTabs) {
-  base::test::ScopedFeatureList feature_override;
-  feature_override.InitAndEnableFeature(features::kTabGroups);
-
+IN_PROC_BROWSER_TEST_F(TabRestoreTestWithTabGroupsEnabled,
+                       RestoreWindowWithGroupedTabs) {
   ui_test_utils::NavigateToURLWithDisposition(
       browser(), GURL(chrome::kChromeUINewTabURL),
       WindowOpenDisposition::NEW_WINDOW,
@@ -962,7 +967,12 @@
 
 // Ensure tab groups aren't restored if |features::kTabGroups| is disabled.
 // Regression test for crbug.com/983962.
-IN_PROC_BROWSER_TEST_F(TabRestoreTest, GroupsNotRestoredWhenFeatureDisabled) {
+//
+// NOTE: This test is currently disabled because it fundamentally relies on
+// manipulating the FeatureList state mid-test, which is NOT safe and not
+// allowed by the FeatureList API.
+IN_PROC_BROWSER_TEST_F(TabRestoreTest,
+                       DISABLED_GroupsNotRestoredWhenFeatureDisabled) {
   auto feature_override = std::make_unique<base::test::ScopedFeatureList>();
   feature_override->InitAndEnableFeature(features::kTabGroups);
 
diff --git a/chrome/browser/sharing/click_to_call/click_to_call_browsertest.cc b/chrome/browser/sharing/click_to_call/click_to_call_browsertest.cc
index 60e68e7..9fb4cfe 100644
--- a/chrome/browser/sharing/click_to_call/click_to_call_browsertest.cc
+++ b/chrome/browser/sharing/click_to_call/click_to_call_browsertest.cc
@@ -8,6 +8,7 @@
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_feature_list.h"
 #include "chrome/app/chrome_command_ids.h"
 #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
 #include "chrome/browser/sharing/click_to_call/click_to_call_ui_controller.h"
@@ -35,11 +36,11 @@
 }  // namespace
 
 // Browser tests for the Click To Call feature.
-class ClickToCallBrowserTest : public SharingBrowserTest {
+class ClickToCallBrowserTestBase : public SharingBrowserTest {
  public:
-  ClickToCallBrowserTest() {}
+  ClickToCallBrowserTestBase() {}
 
-  ~ClickToCallBrowserTest() override {}
+  ~ClickToCallBrowserTestBase() override {}
 
   std::string GetTestPageURL() const override {
     return std::string(kTestPageURL);
@@ -59,16 +60,26 @@
               sharing_message.click_to_call_message().phone_number());
   }
 
+ protected:
+  base::test::ScopedFeatureList feature_list_;
+
  private:
-  DISALLOW_COPY_AND_ASSIGN(ClickToCallBrowserTest);
+  DISALLOW_COPY_AND_ASSIGN(ClickToCallBrowserTestBase);
+};
+
+class ClickToCallBrowserTest : public ClickToCallBrowserTestBase {
+ public:
+  ClickToCallBrowserTest() {
+    feature_list_.InitWithFeatures({kSharingDeviceRegistration, kClickToCallUI,
+                                    kClickToCallContextMenuForSelectedText},
+                                   {});
+  }
 };
 
 // TODO(himanshujaju): Add UI checks.
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest,
                        ContextMenu_TelLink_SingleDeviceAvailable) {
-  Init({kSharingDeviceRegistration, kClickToCallUI,
-        kClickToCallContextMenuForSelectedText},
-       {});
+  Init();
   SetUpDevices(/*count=*/1);
 
   auto devices = sharing_service()->GetDeviceCandidates(
@@ -93,9 +104,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest, ContextMenu_NoDevicesAvailable) {
-  Init({kSharingDeviceRegistration, kClickToCallUI,
-        kClickToCallContextMenuForSelectedText},
-       {});
+  Init();
   AwaitQuiescence();
 
   std::unique_ptr<TestRenderViewContextMenu> menu =
@@ -109,9 +118,7 @@
 
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest,
                        ContextMenu_DevicesAvailable_SyncTurnedOff) {
-  Init({kSharingDeviceRegistration, kClickToCallUI,
-        kClickToCallContextMenuForSelectedText},
-       {});
+  Init();
   SetUpDevices(/*count=*/1);
   // Disable syncing preferences which is necessary for Sharing.
   GetSyncService(0)->GetUserSettings()->SetSelectedTypes(false, {});
@@ -128,9 +135,7 @@
 
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest,
                        ContextMenu_TelLink_MultipleDevicesAvailable) {
-  Init({kSharingDeviceRegistration, kClickToCallUI,
-        kClickToCallContextMenuForSelectedText},
-       {});
+  Init();
   SetUpDevices(/*count=*/2);
 
   auto devices = sharing_service()->GetDeviceCandidates(
@@ -166,9 +171,7 @@
 
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest,
                        ContextMenu_HighlightedText_MultipleDevicesAvailable) {
-  Init({kSharingDeviceRegistration, kClickToCallUI,
-        kClickToCallContextMenuForSelectedText},
-       {});
+  Init();
   SetUpDevices(/*count=*/2);
 
   auto devices = sharing_service()->GetDeviceCandidates(
@@ -205,11 +208,19 @@
   }
 }
 
+class ClickToCallBrowserTestWithContextMenuDisabled
+    : public ClickToCallBrowserTestBase {
+ public:
+  ClickToCallBrowserTestWithContextMenuDisabled() {
+    feature_list_.InitWithFeatures({kSharingDeviceRegistration, kClickToCallUI},
+                                   {kClickToCallContextMenuForSelectedText});
+  }
+};
+
 IN_PROC_BROWSER_TEST_F(
-    ClickToCallBrowserTest,
+    ClickToCallBrowserTestWithContextMenuDisabled,
     ContextMenu_HighlightedText_DevicesAvailable_FeatureFlagOff) {
-  Init({kSharingDeviceRegistration, kClickToCallUI},
-       {kClickToCallContextMenuForSelectedText});
+  Init();
   SetUpDevices(/*count=*/2);
 
   auto devices = sharing_service()->GetDeviceCandidates(
@@ -228,9 +239,7 @@
 }
 
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest, ContextMenu_UKM) {
-  Init({kSharingDeviceRegistration, kClickToCallUI,
-        kClickToCallContextMenuForSelectedText},
-       {});
+  Init();
   SetUpDevices(/*count=*/1);
 
   ukm::TestAutoSetUkmRecorder ukm_recorder;
@@ -279,8 +288,17 @@
   // TODO(knollr): mock apps and verify |has_apps| here too.
 }
 
+class ClickToCallBrowserTestWithContextMenuFeatureDefault
+    : public ClickToCallBrowserTestBase {
+ public:
+  ClickToCallBrowserTestWithContextMenuFeatureDefault() {
+    feature_list_.InitWithFeatures({kSharingDeviceRegistration, kClickToCallUI},
+                                   {});
+  }
+};
+
 IN_PROC_BROWSER_TEST_F(ClickToCallBrowserTest, CloseTabWithBubble) {
-  Init({kSharingDeviceRegistration, kClickToCallUI}, {});
+  Init();
   SetUpDevices(/*count=*/1);
 
   base::RunLoop run_loop;
diff --git a/chrome/browser/sharing/sharing_browsertest.cc b/chrome/browser/sharing/sharing_browsertest.cc
index 483005b5..ab0fa6ce 100644
--- a/chrome/browser/sharing/sharing_browsertest.cc
+++ b/chrome/browser/sharing/sharing_browsertest.cc
@@ -32,11 +32,7 @@
   host_resolver()->AddRule("mock.http", "127.0.0.1");
 }
 
-void SharingBrowserTest::Init(
-    const std::vector<base::Feature>& enabled_features,
-    const std::vector<base::Feature>& disabled_features) {
-  scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
-
+void SharingBrowserTest::Init() {
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   ASSERT_TRUE(embedded_test_server()->Start());
diff --git a/chrome/browser/sharing/sharing_browsertest.h b/chrome/browser/sharing/sharing_browsertest.h
index 661425b..0f1d3b7a 100644
--- a/chrome/browser/sharing/sharing_browsertest.h
+++ b/chrome/browser/sharing/sharing_browsertest.h
@@ -9,7 +9,6 @@
 #include <string>
 
 #include "base/macros.h"
-#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/gcm/gcm_profile_service_factory.h"
 #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
 #include "chrome/browser/sharing/sharing_service.h"
@@ -28,8 +27,7 @@
 
   void SetUpOnMainThread() override;
 
-  void Init(const std::vector<base::Feature>& enabled_features,
-            const std::vector<base::Feature>& disabled_features);
+  void Init();
 
   virtual std::string GetTestPageURL() const = 0;
 
@@ -52,7 +50,6 @@
  private:
   gcm::GCMProfileServiceFactory::ScopedTestingFactoryInstaller
       scoped_testing_factory_installer_;
-  base::test::ScopedFeatureList scoped_feature_list_;
   gcm::FakeGCMProfileService* gcm_service_;
   content::WebContents* web_contents_;
   syncer::FakeDeviceInfoTracker fake_device_info_tracker_;
diff --git a/chrome/browser/subresource_filter/ruleset_browsertest.cc b/chrome/browser/subresource_filter/ruleset_browsertest.cc
index d84ba9a2..7e045c3 100644
--- a/chrome/browser/subresource_filter/ruleset_browsertest.cc
+++ b/chrome/browser/subresource_filter/ruleset_browsertest.cc
@@ -186,23 +186,41 @@
                                       1);
 }
 
-IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest, LazyRulesetValidation) {
+class SubresourceFilterBrowserTestWithoutAdTagging
+    : public SubresourceFilterBrowserTest {
+ public:
+  SubresourceFilterBrowserTestWithoutAdTagging() {
+    feature_list_.InitAndDisableFeature(subresource_filter::kAdTagging);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTestWithoutAdTagging,
+                       LazyRulesetValidation) {
   // The ruleset shouldn't be validated until it's used, unless ad tagging is
   // enabled.
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(subresource_filter::kAdTagging);
   SetRulesetToDisallowURLsWithPathSuffix("included_script.js");
   RulesetVerificationStatus dealer_status = GetRulesetVerification();
   EXPECT_EQ(RulesetVerificationStatus::kNotVerified, dealer_status);
 }
 
-IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
+class SubresourceFilterBrowserTestWithAdTagging
+    : public SubresourceFilterBrowserTest {
+ public:
+  SubresourceFilterBrowserTestWithAdTagging() {
+    feature_list_.InitAndEnableFeature(subresource_filter::kAdTagging);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTestWithAdTagging,
                        AdsTaggingImmediateRulesetValidation) {
   // When Ads Tagging is enabled, the ruleset should be validated as soon as
   // it's published.
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(subresource_filter::kAdTagging);
-
   SetRulesetToDisallowURLsWithPathSuffix("included_script.js");
   RulesetVerificationStatus dealer_status = GetRulesetVerification();
   EXPECT_EQ(RulesetVerificationStatus::kIntact, dealer_status);
diff --git a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
index e13250d..5b3bf3f 100644
--- a/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_browsertest.cc
@@ -724,11 +724,20 @@
       tester, true /* expect_performance_measurements */);
 }
 
-IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTest,
+class SubresourceFilterBrowserTestWithoutAdTagging
+    : public SubresourceFilterBrowserTest {
+ public:
+  SubresourceFilterBrowserTestWithoutAdTagging() {
+    feature_list_.InitAndDisableFeature(kAdTagging);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// This test only makes sense when AdTagging is disabled.
+IN_PROC_BROWSER_TEST_F(SubresourceFilterBrowserTestWithoutAdTagging,
                        ExpectHistogramsNotRecordedWhenFilteringNotActivated) {
-  // This test only makes sense when AdTagging is disabled.
-  base::test::ScopedFeatureList scoped_tagging;
-  scoped_tagging.InitAndDisableFeature(kAdTagging);
   ASSERT_NO_FATAL_FAILURE(SetRulesetToDisallowURLsWithPathSuffix(
       "suffix-that-does-not-match-anything"));
   ResetConfigurationToEnableOnPhishingSites(true /* measure_performance */);
diff --git a/chrome/browser/subresource_filter/subresource_filter_devtools_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_devtools_browsertest.cc
index c40ccc3..07e33f6 100644
--- a/chrome/browser/subresource_filter/subresource_filter_devtools_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_devtools_browsertest.cc
@@ -153,14 +153,22 @@
   console_observer.Wait();
 }
 
+class SubresourceFilterDevtoolsBrowserTestWithSitePerProcess
+    : public SubresourceFilterDevtoolsBrowserTest {
+ public:
+  SubresourceFilterDevtoolsBrowserTestWithSitePerProcess() {
+    feature_list_.InitAndEnableFeature(features::kSitePerProcess);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // See crbug.com/813197, where agent hosts from subframes could send messages to
 // disable ad blocking when they are detached (e.g. when the subframe goes
 // away).
-IN_PROC_BROWSER_TEST_F(SubresourceFilterDevtoolsBrowserTest,
+IN_PROC_BROWSER_TEST_F(SubresourceFilterDevtoolsBrowserTestWithSitePerProcess,
                        IsolatedSubframe_DoesNotSendAdBlockingMessages) {
-  base::test::ScopedFeatureList scoped_isolation;
-  scoped_isolation.InitAndEnableFeature(features::kSitePerProcess);
-
   ASSERT_NO_FATAL_FAILURE(
       SetRulesetToDisallowURLsWithPathSuffix("included_script.js"));
   ScopedDevtoolsOpener page_opener(web_contents());
diff --git a/chrome/browser/subresource_filter/subresource_filter_intercepting_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_intercepting_browsertest.cc
index ae2df5d..72f54b9 100644
--- a/chrome/browser/subresource_filter/subresource_filter_intercepting_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_intercepting_browsertest.cc
@@ -176,14 +176,24 @@
   EXPECT_GE(timer.Elapsed(), delay);
 }
 
+class SubresourceFilterInterceptingBrowserTestConsiderRedirects
+    : public SubresourceFilterInterceptingBrowserTest {
+ public:
+  SubresourceFilterInterceptingBrowserTestConsiderRedirects() {
+    feature_list_.InitAndEnableFeature(
+        kSafeBrowsingSubresourceFilterConsiderRedirects);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Verify that the correct safebrowsing result is reported when there is a
 // redirect chain. With kSafeBrowsingSubresourceFilterConsiderRedirects, the
 // result with the highest priority should be returned.
-IN_PROC_BROWSER_TEST_F(SubresourceFilterInterceptingBrowserTest,
-                       SafeBrowsingNotificationsCheckBest) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      kSafeBrowsingSubresourceFilterConsiderRedirects);
+IN_PROC_BROWSER_TEST_F(
+    SubresourceFilterInterceptingBrowserTestConsiderRedirects,
+    SafeBrowsingNotificationsCheckBest) {
   ASSERT_NO_FATAL_FAILURE(
       SetRulesetToDisallowURLsWithPathSuffix("included_script.js"));
   GURL redirect_url(embedded_test_server()->GetURL(
@@ -194,14 +204,24 @@
   EXPECT_FALSE(WasParsedScriptElementLoaded(web_contents()->GetMainFrame()));
 }
 
+class SubresourceFilterInterceptingBrowserTestDontConsiderRedirects
+    : public SubresourceFilterInterceptingBrowserTest {
+ public:
+  SubresourceFilterInterceptingBrowserTestDontConsiderRedirects() {
+    feature_list_.InitAndDisableFeature(
+        kSafeBrowsingSubresourceFilterConsiderRedirects);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Verify that the correct safebrowsing result is reported when there is a
 // redirect chain. Without kSafeBrowsingSubresourceFilterConsiderRedirects, the
 // last result should be used.
-IN_PROC_BROWSER_TEST_F(SubresourceFilterInterceptingBrowserTest,
-                       SafeBrowsingNotificationsCheckLastResult) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      kSafeBrowsingSubresourceFilterConsiderRedirects);
+IN_PROC_BROWSER_TEST_F(
+    SubresourceFilterInterceptingBrowserTestDontConsiderRedirects,
+    SafeBrowsingNotificationsCheckLastResult) {
   ASSERT_NO_FATAL_FAILURE(
       SetRulesetToDisallowURLsWithPathSuffix("included_script.js"));
   GURL redirect_url(embedded_test_server()->GetURL(
diff --git a/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc b/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc
index 16e90556..0f7433d 100644
--- a/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc
+++ b/chrome/browser/subresource_filter/subresource_filter_popup_browsertest.cc
@@ -94,9 +94,7 @@
 // Tests that subresource_filter interacts well with the abusive enforcement in
 // chrome/browser/ui/blocked_content/safe_browsing_triggered_popup_blocker.
 class SubresourceFilterPopupBrowserTest
-    : public SubresourceFilterListInsertingBrowserTest,
-      public ::testing::WithParamInterface<
-          bool /* enable_adblock_on_abusive_sites */> {
+    : public SubresourceFilterListInsertingBrowserTest {
  public:
   void SetUpOnMainThread() override {
     SubresourceFilterBrowserTest::SetUpOnMainThread();
@@ -169,13 +167,24 @@
                                embedded_test_server()->GetURL("/title1.html"));
 }
 
-IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTest,
+class SubresourceFilterPopupBrowserTestWithParam
+    : public SubresourceFilterPopupBrowserTest,
+      public ::testing::WithParamInterface<
+          bool /* enable_adblock_on_abusive_sites */> {
+ public:
+  SubresourceFilterPopupBrowserTestWithParam() {
+    const bool enable_adblock_on_abusive_sites = GetParam();
+    feature_list_.InitWithFeatureState(
+        subresource_filter::kFilterAdsOnAbusiveSites,
+        enable_adblock_on_abusive_sites);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTestWithParam,
                        BlockCreatingNewWindows) {
-  bool enable_adblock_on_abusive_sites = GetParam();
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatureState(
-      subresource_filter::kFilterAdsOnAbusiveSites,
-      enable_adblock_on_abusive_sites);
   base::HistogramTester tester;
   const char kWindowOpenPath[] = "/subresource_filter/window_open.html";
   GURL a_url(embedded_test_server()->GetURL("a.com", kWindowOpenPath));
@@ -203,6 +212,7 @@
                                                    &opened_window));
   EXPECT_FALSE(opened_window);
 
+  const bool enable_adblock_on_abusive_sites = GetParam();
   EXPECT_EQ(enable_adblock_on_abusive_sites, AreDisallowedRequestsBlocked());
 
   // Navigate to |b_url|, which should successfully open the popup.
@@ -331,12 +341,8 @@
       web_contents(), {kActivationConsoleMessage}, {kAbusiveEnforceMessage});
 }
 
-IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTest, BlockOpenURLFromTab) {
-  bool enable_adblock_on_abusive_sites = GetParam();
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatureState(
-      subresource_filter::kFilterAdsOnAbusiveSites,
-      enable_adblock_on_abusive_sites);
+IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTestWithParam,
+                       BlockOpenURLFromTab) {
   base::HistogramTester tester;
   const char kWindowOpenPath[] =
       "/subresource_filter/window_open_spoof_click.html";
@@ -356,6 +362,7 @@
 
   EXPECT_TRUE(TabSpecificContentSettings::FromWebContents(web_contents)
                   ->IsContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS));
+  const bool enable_adblock_on_abusive_sites = GetParam();
   EXPECT_EQ(enable_adblock_on_abusive_sites, AreDisallowedRequestsBlocked());
 
   // Navigate to |b_url|, which should successfully open the popup.
@@ -373,13 +380,8 @@
   EXPECT_FALSE(AreDisallowedRequestsBlocked());
 }
 
-IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTest,
+IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTestWithParam,
                        BlockOpenURLFromTabInIframe) {
-  bool enable_adblock_on_abusive_sites = GetParam();
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatureState(
-      subresource_filter::kFilterAdsOnAbusiveSites,
-      enable_adblock_on_abusive_sites);
   const char popup_path[] = "/subresource_filter/iframe_spoof_click_popup.html";
   GURL a_url(embedded_test_server()->GetURL("a.com", popup_path));
   ConfigureAsAbusiveAndBetterAds(
@@ -396,16 +398,12 @@
   EXPECT_TRUE(sent_open);
   EXPECT_TRUE(TabSpecificContentSettings::FromWebContents(web_contents)
                   ->IsContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS));
+  const bool enable_adblock_on_abusive_sites = GetParam();
   EXPECT_EQ(enable_adblock_on_abusive_sites, AreDisallowedRequestsBlocked());
 }
 
-IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTest,
+IN_PROC_BROWSER_TEST_P(SubresourceFilterPopupBrowserTestWithParam,
                        TraditionalWindowOpen_NotBlocked) {
-  bool enable_adblock_on_abusive_sites = GetParam();
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitWithFeatureState(
-      subresource_filter::kFilterAdsOnAbusiveSites,
-      enable_adblock_on_abusive_sites);
   GURL url(GetTestUrl("/title2.html"));
   ConfigureAsAbusiveAndBetterAds(
       url, SubresourceFilterLevel::ENFORCE /* abusive_level */,
@@ -422,11 +420,12 @@
       browser()->tab_strip_model()->GetActiveWebContents();
   EXPECT_FALSE(TabSpecificContentSettings::FromWebContents(web_contents)
                    ->IsContentBlocked(CONTENT_SETTINGS_TYPE_POPUPS));
+  const bool enable_adblock_on_abusive_sites = GetParam();
   EXPECT_EQ(enable_adblock_on_abusive_sites, AreDisallowedRequestsBlocked());
 }
 
 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
-                         SubresourceFilterPopupBrowserTest,
+                         SubresourceFilterPopupBrowserTestWithParam,
                          ::testing::Values(false, true));
 
 }  // namespace subresource_filter
diff --git a/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc b/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
index 9dc97528..34b0ca23 100644
--- a/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
+++ b/chrome/browser/sync/test/integration/single_client_preferences_sync_test.cc
@@ -46,29 +46,6 @@
   EXPECT_TRUE(BooleanPrefMatches(prefs::kHomePageIsNewTabPage));
 }
 
-// This test simply verifies that preferences registered after sync started
-// get properly synced.
-IN_PROC_BROWSER_TEST_F(SingleClientPreferencesSyncTest, LateRegistration) {
-  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
-  PrefRegistrySyncable* registry = GetRegistry(GetProfile(0));
-  const std::string pref_name = "testing.my-test-preference";
-  registry->WhitelistLateRegistrationPrefForSync(pref_name);
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-  registry->RegisterBooleanPref(
-      pref_name, true, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  // Verify the default is properly used.
-  EXPECT_TRUE(GetProfile(0)->GetPrefs()->GetBoolean(pref_name));
-  // Now make a change and verify it gets uploaded.
-  GetProfile(0)->GetPrefs()->SetBoolean(pref_name, false);
-  ASSERT_FALSE(GetProfile(0)->GetPrefs()->GetBoolean(pref_name));
-  EXPECT_TRUE(UpdatedProgressMarkerChecker(GetSyncService(0)).Wait());
-
-  GetRegistry(verifier())
-      ->RegisterBooleanPref(pref_name, true,
-                            user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  EXPECT_FALSE(BooleanPrefMatches(pref_name.c_str()));
-}
-
 // Flaky on Windows. https://crbug.com/930482
 #if defined(OS_WIN)
 #define MAYBE_ShouldRemoveBadDataWhenRegistering \
diff --git a/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc b/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
index ff307d4..c3f74e6a 100644
--- a/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
@@ -222,47 +222,6 @@
   DISALLOW_COPY_AND_ASSIGN(TwoClientPreferencesSyncTestWithSelfNotifications);
 };
 
-// Tests that late registered prefs are kept in sync with other clients.
-IN_PROC_BROWSER_TEST_F(TwoClientPreferencesSyncTestWithSelfNotifications,
-                       E2E_ENABLED(LateRegisteredPrefsShouldSync)) {
-  ResetSyncForPrimaryAccount();
-  // client0 has the pref registered before sync and is modifying a pref before
-  // that pref got registered with client1 (but after client1 started syncing).
-  ASSERT_TRUE(SetupClients()) << "SetupClients() failed.";
-
-  constexpr char pref_name[] = "testing.my-test-preference";
-  GetRegistry(GetProfile(0))
-      ->RegisterBooleanPref(pref_name, false,
-                            user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  GetRegistry(GetProfile(1))
-      ->WhitelistLateRegistrationPrefForSync("testing.my-test-preference");
-
-  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
-
-  ASSERT_THAT(GetPrefs(0)->GetBoolean(pref_name), Eq(false));
-  ChangeBooleanPref(0, pref_name);
-  ASSERT_THAT(GetPrefs(0)->GetBoolean(pref_name), Eq(true));
-  GetClient(0)->AwaitMutualSyncCycleCompletion(GetClient(1));
-
-  // Now register the pref and verify it's up-to-date.
-  GetRegistry(GetProfile(1))
-      ->RegisterBooleanPref(pref_name, false,
-                            user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  EXPECT_THAT(GetPrefs(1)->GetBoolean(pref_name), Eq(true));
-
-  // Make sure that subsequent changes are synced.
-  ChangeBooleanPref(0, pref_name);
-  ASSERT_THAT(GetPrefs(0)->GetBoolean(pref_name), Eq(false));
-  EXPECT_TRUE(BooleanPrefMatchChecker(pref_name).Wait());
-  EXPECT_THAT(GetPrefs(1)->GetBoolean(pref_name), Eq(false));
-
-  // Make sure that subsequent changes are synced.
-  ChangeBooleanPref(1, pref_name);
-  ASSERT_THAT(GetPrefs(1)->GetBoolean(pref_name), Eq(true));
-  EXPECT_TRUE(BooleanPrefMatchChecker(pref_name).Wait());
-  EXPECT_THAT(GetPrefs(0)->GetBoolean(pref_name), Eq(true));
-}
-
 IN_PROC_BROWSER_TEST_F(TwoClientPreferencesSyncTestWithSelfNotifications,
                        E2E_ENABLED(ShouldKeepLocalDataOnTypeMismatch)) {
   ResetSyncForPrimaryAccount();
@@ -276,8 +235,6 @@
   GetRegistry(GetProfile(0))
       ->RegisterBooleanPref(pref_name, false,
                             user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  GetRegistry(GetProfile(1))
-      ->WhitelistLateRegistrationPrefForSync("testing.my-test-preference");
   ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
 
   ChangeBooleanPref(0, pref_name);
diff --git a/chrome/browser/touch_to_fill/android/BUILD.gn b/chrome/browser/touch_to_fill/android/BUILD.gn
index 30e485b..c449988 100644
--- a/chrome/browser/touch_to_fill/android/BUILD.gn
+++ b/chrome/browser/touch_to_fill/android/BUILD.gn
@@ -15,6 +15,7 @@
 
   deps = [
     ":jni_headers",
+    "//ui/gfx",
   ]
 }
 
diff --git a/chrome/browser/touch_to_fill/android/internal/BUILD.gn b/chrome/browser/touch_to_fill/android/internal/BUILD.gn
index 7792ea5..4b27588d 100644
--- a/chrome/browser/touch_to_fill/android/internal/BUILD.gn
+++ b/chrome/browser/touch_to_fill/android/internal/BUILD.gn
@@ -17,14 +17,13 @@
   ]
 
   java_files = [
+    "java/src/org/chromium/chrome/browser/touch_to_fill/CredentialProperties.java",
     "java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java",
     "java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java",
     "java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillMediator.java",
     "java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillProperties.java",
     "java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java",
     "java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java",
-    "java/src/org/chromium/chrome/browser/touch_to_fill/helper/ListViewAdapter.java",
-    "java/src/org/chromium/chrome/browser/touch_to_fill/helper/SimpleListViewMcp.java",
   ]
 
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml
index af57e8a5..1180edf6 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/layout/touch_to_fill_credential_item.xml
@@ -15,16 +15,15 @@
 
     <ImageView
         android:id="@+id/favicon"
-        android:layout_width="24dp"
-        android:layout_height="24dp"
+        android:layout_width="@dimen/touch_to_fill_favicon_size"
+        android:layout_height="@dimen/touch_to_fill_favicon_size"
         android:layout_marginStart="12dp"
         android:importantForAccessibility="no"
         android:layout_gravity="center"/>
     <LinearLayout
         android:layout_width="0dp"
         android:layout_height="wrap_content"
-        android:layout_margin="6dp"
-        android:layout_marginStart="12dp"
+        android:layout_margin="12dp"
         android:layout_weight="1"
         android:orientation="vertical">
         <TextView
diff --git a/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml b/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml
new file mode 100644
index 0000000..d41f1d4
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/res/values/dimens.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2019 The Chromium Authors. All rights reserved.
+     Use of this source code is governed by a BSD-style license that can be
+     found in the LICENSE file. -->
+
+<resources>
+    <dimen name="touch_to_fill_favicon_size">24dp</dimen>
+</resources>
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/CredentialProperties.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/CredentialProperties.java
new file mode 100644
index 0000000..3fdcb98
--- /dev/null
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/CredentialProperties.java
@@ -0,0 +1,22 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.touch_to_fill;
+
+import android.graphics.Bitmap;
+
+import org.chromium.chrome.browser.touch_to_fill.data.Credential;
+import org.chromium.ui.modelutil.PropertyModel;
+
+/**
+ * Properties for a credential entry in TouchToFill sheet.
+ */
+class CredentialProperties {
+    static final int DEFAULT_ITEM_TYPE = 0; // Credential list has only one entry type.
+
+    static final PropertyModel.WritableObjectPropertyKey<Bitmap> FAVICON =
+            new PropertyModel.WritableObjectPropertyKey<>("favicon");
+    static final PropertyModel.WritableObjectPropertyKey<Credential> CREDENTIAL =
+            new PropertyModel.WritableObjectPropertyKey<>("credential");
+}
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java
index 3609eddc..0a9fa5a 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillBridge.java
@@ -4,6 +4,11 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
+import android.graphics.Bitmap;
+
+import androidx.annotation.Px;
+
+import org.chromium.base.Callback;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -68,9 +73,17 @@
         TouchToFillBridgeJni.get().onCredentialSelected(mNativeView, credential);
     }
 
+    @Override
+    public void fetchFavicon(String origin, @Px int desiredSize, Callback<Bitmap> callback) {
+        assert mNativeView != 0 : "Favicon was requested after the bridge was destroyed!";
+        TouchToFillBridgeJni.get().fetchFavicon(mNativeView, origin, desiredSize, callback);
+    }
+
     @NativeMethods
     interface Natives {
         void onCredentialSelected(long nativeTouchToFillViewImpl, Credential credential);
         void onDismiss(long nativeTouchToFillViewImpl);
+        void fetchFavicon(long nativeTouchToFillViewImpl, String origin, int desiredSizeInPx,
+                Callback<Bitmap> callback);
     }
 }
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java
index 9236cda..de759c5 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillCoordinator.java
@@ -4,14 +4,10 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
-import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CREDENTIAL_LIST;
-
 import android.content.Context;
 
 import org.chromium.base.VisibleForTesting;
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
-import org.chromium.chrome.browser.touch_to_fill.helper.ListViewAdapter;
-import org.chromium.chrome.browser.touch_to_fill.helper.SimpleListViewMcp;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 import org.chromium.ui.modelutil.PropertyModel;
 import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
@@ -29,7 +25,8 @@
     @Override
     public void initialize(Context context, BottomSheetController sheetController,
             TouchToFillComponent.Delegate delegate) {
-        mMediator.initialize(delegate, mModel);
+        mMediator.initialize(delegate, mModel,
+                context.getResources().getDimensionPixelSize(R.dimen.touch_to_fill_favicon_size));
         setUpModelChangeProcessors(mModel, new TouchToFillView(context, sheetController));
     }
 
@@ -46,10 +43,7 @@
      */
     @VisibleForTesting
     static void setUpModelChangeProcessors(PropertyModel model, TouchToFillView view) {
-        PropertyModelChangeProcessor.create(model, view, TouchToFillViewBinder::bind);
-        view.setCredentialListAdapter(
-                new ListViewAdapter<>(new SimpleListViewMcp<>(model.get(CREDENTIAL_LIST),
-                                              TouchToFillViewBinder::bindCredentialView),
-                        TouchToFillViewBinder::createCredentialView));
+        PropertyModelChangeProcessor.create(
+                model, view, TouchToFillViewBinder::bindTouchToFillView);
     }
 }
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillMediator.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillMediator.java
index ccc945ff..af73205 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillMediator.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillMediator.java
@@ -4,12 +4,19 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.CREDENTIAL;
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.DEFAULT_ITEM_TYPE;
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.FAVICON;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CREDENTIAL_LIST;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.FORMATTED_URL;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ORIGIN_SECURE;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
 
+import androidx.annotation.Px;
+
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
+import org.chromium.ui.modelutil.MVCListAdapter;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.List;
@@ -21,11 +28,14 @@
 class TouchToFillMediator implements TouchToFillProperties.ViewEventListener {
     private TouchToFillComponent.Delegate mDelegate;
     private PropertyModel mModel;
+    private @Px int mDesiredFaviconSize;
 
-    void initialize(TouchToFillComponent.Delegate delegate, PropertyModel model) {
+    void initialize(TouchToFillComponent.Delegate delegate, PropertyModel model,
+            @Px int desiredFaviconSize) {
         assert delegate != null;
         mDelegate = delegate;
         mModel = model;
+        mDesiredFaviconSize = desiredFaviconSize;
     }
 
     void showCredentials(
@@ -34,15 +44,26 @@
         mModel.set(FORMATTED_URL, formattedUrl);
         mModel.set(ORIGIN_SECURE, isOriginSecure);
         mModel.set(VISIBLE, true);
-        mModel.get(CREDENTIAL_LIST).clear();
-        mModel.get(CREDENTIAL_LIST).addAll(credentials);
+
+        ModelList credentialList = mModel.get(CREDENTIAL_LIST);
+        credentialList.clear();
+        for (Credential credential : credentials) {
+            PropertyModel propertyModel = new PropertyModel.Builder(FAVICON, CREDENTIAL)
+                                                  .with(FAVICON, null)
+                                                  .with(CREDENTIAL, credential)
+                                                  .build();
+            credentialList.add(new MVCListAdapter.ListItem(DEFAULT_ITEM_TYPE, propertyModel));
+            mDelegate.fetchFavicon(credential.getOriginUrl(), mDesiredFaviconSize,
+                    (bitmap) -> propertyModel.set(FAVICON, bitmap));
+        }
     }
 
     @Override
     public void onSelectItemAt(int position) {
-        assert position >= 0 && position < mModel.get(CREDENTIAL_LIST).size();
+        ModelList credentialList = mModel.get(CREDENTIAL_LIST);
+        assert position >= 0 && position < credentialList.size();
         mModel.set(VISIBLE, false);
-        mDelegate.onCredentialSelected(mModel.get(CREDENTIAL_LIST).get(position));
+        mDelegate.onCredentialSelected(credentialList.get(position).model.get(CREDENTIAL));
     }
 
     @Override
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillProperties.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillProperties.java
index 4a132dbf..a52ca84 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillProperties.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillProperties.java
@@ -4,8 +4,7 @@
 
 package org.chromium.chrome.browser.touch_to_fill;
 
-import org.chromium.chrome.browser.touch_to_fill.data.Credential;
-import org.chromium.ui.modelutil.ListModel;
+import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
 import org.chromium.ui.modelutil.PropertyModel;
 
 /**
@@ -18,7 +17,7 @@
             new PropertyModel.WritableObjectPropertyKey<>("formatted_url");
     static final PropertyModel.WritableBooleanPropertyKey ORIGIN_SECURE =
             new PropertyModel.WritableBooleanPropertyKey("origin_secure");
-    static final PropertyModel.ReadableObjectPropertyKey<ListModel<Credential>> CREDENTIAL_LIST =
+    static final PropertyModel.ReadableObjectPropertyKey<ModelList> CREDENTIAL_LIST =
             new PropertyModel.ReadableObjectPropertyKey<>("credential_list");
     static final PropertyModel.ReadableObjectPropertyKey<ViewEventListener> VIEW_EVENT_LISTENER =
             new PropertyModel.ReadableObjectPropertyKey<>("view_event_listener");
@@ -29,7 +28,7 @@
                         VISIBLE, FORMATTED_URL, ORIGIN_SECURE, CREDENTIAL_LIST, VIEW_EVENT_LISTENER)
                 .with(VISIBLE, false)
                 .with(ORIGIN_SECURE, false)
-                .with(CREDENTIAL_LIST, new ListModel<>())
+                .with(CREDENTIAL_LIST, new ModelList())
                 .with(VIEW_EVENT_LISTENER, listener)
                 .build();
     }
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java
index 22c75c4..3258329 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillView.java
@@ -39,13 +39,6 @@
             mEventListener.onDismissed();
             mBottomSheetController.getBottomSheet().removeObserver(mBottomSheetObserver);
         }
-
-        @Override
-        public void onSheetFullyPeeked() {
-            super.onSheetFullyPeeked();
-            // Since isPeekStateEnabled doesn't seem to skip the Peek state, force-expand the sheet.
-            mBottomSheetController.expandSheet();
-        }
     };
 
     /**
@@ -107,6 +100,10 @@
         mCredentialListView.setAdapter(adapter);
     }
 
+    Context getContext() {
+        return mContext;
+    }
+
     private void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
         assert adapterView == mCredentialListView : "Use this click handler only for credentials!";
         assert mEventListener != null;
@@ -150,8 +147,8 @@
     }
 
     @Override
-    public boolean isPeekStateEnabled() {
-        return true; // For some reason, false isn't working properly. Extend it explicitly!
+    public int getPeekHeight() {
+        return BottomSheet.HeightMode.DISABLED;
     }
 
     @Override
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java
index 9c4ab49..4e84d94 100644
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java
+++ b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewBinder.java
@@ -11,13 +11,16 @@
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
 import static org.chromium.chrome.browser.util.UrlUtilities.stripScheme;
 
+import android.content.Context;
 import android.text.method.PasswordTransformationMethod;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
+import org.chromium.ui.modelutil.ModelListAdapter;
 import org.chromium.ui.modelutil.PropertyKey;
 import org.chromium.ui.modelutil.PropertyModel;
 
@@ -30,31 +33,43 @@
      * Factory used to create a new View inside the ListView inside the TouchToFillView.
      * @param parent The parent {@link ViewGroup} of the new item.
      */
-    static View createCredentialView(ViewGroup parent) {
-        return LayoutInflater.from(parent.getContext())
-                .inflate(R.layout.touch_to_fill_credential_item, parent, false);
+    static View createCredentialView(Context context) {
+        return LayoutInflater.from(context).inflate(
+                R.layout.touch_to_fill_credential_item, null, false);
     }
 
     /**
      * Called whenever a credential is bound to this view holder. Please note that this method
      * might be called on the same list entry repeatedly, so make sure to always set a default for
      * unused fields.
-     * @param view The view to be bound.
-     * @param credential The {@link Credential} whose data needs to be displayed.
+     * @param model The model containing the data for the view
+     * @param view The view to be bound
+     * @param propertyKey The key of the property to be bound
      */
-    static void bindCredentialView(View view, Credential credential) {
-        TextView pslOriginText = view.findViewById(R.id.credential_origin);
-        String formattedOrigin = stripScheme(credential.getOriginUrl());
-        formattedOrigin = formattedOrigin.replaceFirst("/$", ""); // Strip possibly trailing slash.
-        pslOriginText.setText(formattedOrigin);
-        pslOriginText.setVisibility(credential.isPublicSuffixMatch() ? View.VISIBLE : View.GONE);
+    static void bindCredentialView(PropertyModel model, View view, PropertyKey propertyKey) {
+        if (propertyKey == CredentialProperties.FAVICON) {
+            ImageView imageView = view.findViewById(R.id.favicon);
+            imageView.setImageBitmap(model.get(CredentialProperties.FAVICON));
+        } else if (propertyKey == CredentialProperties.CREDENTIAL) {
+            Credential credential = model.get(CredentialProperties.CREDENTIAL);
 
-        TextView usernameText = view.findViewById(R.id.username);
-        usernameText.setText(credential.getFormattedUsername());
+            TextView pslOriginText = view.findViewById(R.id.credential_origin);
+            String formattedOrigin = stripScheme(credential.getOriginUrl());
+            formattedOrigin =
+                    formattedOrigin.replaceFirst("/$", ""); // Strip possibly trailing slash.
+            pslOriginText.setText(formattedOrigin);
+            pslOriginText.setVisibility(
+                    credential.isPublicSuffixMatch() ? View.VISIBLE : View.GONE);
 
-        TextView passwordText = view.findViewById(R.id.password);
-        passwordText.setText(credential.getPassword());
-        passwordText.setTransformationMethod(new PasswordTransformationMethod());
+            TextView usernameText = view.findViewById(R.id.username);
+            usernameText.setText(credential.getFormattedUsername());
+
+            TextView passwordText = view.findViewById(R.id.password);
+            passwordText.setText(credential.getPassword());
+            passwordText.setTransformationMethod(new PasswordTransformationMethod());
+        } else {
+            assert false : "Every possible property update needs to be handled!";
+        }
     }
 
     /**
@@ -63,7 +78,8 @@
      * @param view The {@link TouchToFillView} to update.
      * @param propertyKey The {@link PropertyKey} which changed.
      */
-    static void bind(PropertyModel model, TouchToFillView view, PropertyKey propertyKey) {
+    static void bindTouchToFillView(
+            PropertyModel model, TouchToFillView view, PropertyKey propertyKey) {
         if (propertyKey == VIEW_EVENT_LISTENER) {
             view.setEventListener(model.get(VIEW_EVENT_LISTENER));
         } else if (propertyKey == VISIBLE) {
@@ -75,7 +91,11 @@
                 view.setNonSecureSubtitle(model.get(FORMATTED_URL));
             }
         } else if (propertyKey == CREDENTIAL_LIST) {
-            // No binding required. Single items are bound via bindCredentialView.
+            ModelListAdapter adapter = new ModelListAdapter(model.get(CREDENTIAL_LIST));
+            adapter.registerType(CredentialProperties.DEFAULT_ITEM_TYPE,
+                    () -> TouchToFillViewBinder.createCredentialView(view.getContext()),
+                    TouchToFillViewBinder::bindCredentialView);
+            view.setCredentialListAdapter(adapter);
         } else {
             assert false : "Every possible property update needs to be handled!";
         }
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/helper/ListViewAdapter.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/helper/ListViewAdapter.java
deleted file mode 100644
index ae745a1c..0000000
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/helper/ListViewAdapter.java
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.touch_to_fill.helper;
-
-import android.database.DataSetObserver;
-import android.support.annotation.Nullable;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListAdapter;
-
-import org.chromium.ui.modelutil.ListObservable;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * This Adapter creates Views of type {@link V} for a {@link android.widget.ListView} and binds
- * items of type {@link T} to each of the created views. It defers most tasks to a {@link Delegate}.
- *
- * Construct with a {@link SimpleListViewMcp} to update a ListView according to a list model.
- *
- * @param <T> The item type inside the observed list. It's passed to the {@link Delegate}.
- * @param <V> The view type of a single list entry as created by the {@link ViewFactory}.
- */
-public class ListViewAdapter<T, V extends View>
-        implements ListObservable.ListObserver<Void>, ListAdapter {
-    private final Delegate<T, V> mDelegate;
-    private final ViewFactory<V> mViewFactory;
-    private final Set<DataSetObserver> mObservers = new HashSet<>();
-
-    /**
-     * This delegates decouples the ListViewAdapter from the underlying model and handles all tasks
-     * for which the model might be necessary.
-     * @param <T> The item type inside the observed list.
-     * @param <V> The view type of a single list entry as created by the {@link ViewFactory}.
-     */
-    public interface Delegate<T, V extends View> {
-        /**
-         * Returns the list item at the given position.
-         * @param pos The position inside the list to retrieve the item for.
-         * @return An item of type {@link T}.
-         */
-        T getItemAt(int pos);
-
-        /**
-         * @return The total number of items in the observed list.
-         */
-        int getItemCount();
-
-        /**
-         * Binds the data of the given item to the given View.
-         * @param view A View inside the ListView of type {@link V}.
-         * @param item An Item inside the observed list of type {@link T}.
-         */
-        void onBindView(V view, T item);
-    }
-
-    /**
-     * Subclasses of this interface create the View that represents an item in a ListView.
-     * @param <V> The type of {@link View} this factory creates.
-     */
-    public interface ViewFactory<V extends View> {
-        /**
-         * Creates a View used in a ListView and attaches it to the given parent.
-         * @param parent The parent of the newly created View.
-         * @return A {@link View} that represents an item in a ListView.
-         */
-        V create(ViewGroup parent);
-    }
-
-    /**
-     * Creates a new ListViewAdapter.
-     * @param delegate The {@link Delegate} to bind views and query for list information.
-     * @param viewFactory The {@link ViewFactory} used to create new views for the ListView.
-     */
-    public ListViewAdapter(Delegate<T, V> delegate, ViewFactory<V> viewFactory) {
-        mDelegate = delegate;
-        mViewFactory = viewFactory;
-    }
-
-    @Override
-    public void onItemMoved(ListObservable source, int curIndex, int newIndex) {
-        notifyObserversChanged();
-    }
-
-    @Override
-    public void onItemRangeChanged(
-            ListObservable source, int index, int count, @Nullable Void payload) {
-        notifyObserversChanged();
-    }
-
-    @Override
-    public void onItemRangeInserted(ListObservable source, int index, int count) {
-        notifyObserversChanged();
-    }
-
-    @Override
-    public void onItemRangeRemoved(ListObservable source, int index, int count) {
-        notifyObserversChanged();
-    }
-
-    private void notifyObserversChanged() {
-        for (DataSetObserver observer : mObservers) observer.onChanged();
-    }
-
-    @Override
-    public void registerDataSetObserver(DataSetObserver dataSetObserver) {
-        mObservers.add(dataSetObserver);
-    }
-
-    @Override
-    public void unregisterDataSetObserver(DataSetObserver dataSetObserver) {
-        mObservers.remove(dataSetObserver);
-    }
-
-    @Override
-    public int getCount() {
-        return mDelegate.getItemCount();
-    }
-
-    @Override
-    public Object getItem(int i) {
-        return mDelegate.getItemAt(i);
-    }
-
-    @Override
-    public long getItemId(int i) {
-        return i;
-    }
-
-    @Override
-    public boolean hasStableIds() {
-        return false;
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public View getView(int pos, View view, ViewGroup parent) {
-        if (view == null) view = mViewFactory.create(parent);
-        mDelegate.onBindView((V) view, mDelegate.getItemAt(pos));
-        return view;
-    }
-
-    @Override
-    public int getItemViewType(int i) {
-        return 0; // Always the same view type.
-    }
-
-    @Override
-    public int getViewTypeCount() {
-        return 1; // Always the same view type.
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return mDelegate.getItemCount() == 0;
-    }
-
-    @Override
-    public boolean areAllItemsEnabled() {
-        return true; // There are no disabled items yet.
-    }
-
-    @Override
-    public boolean isEnabled(int i) {
-        return true; // There are no disabled items yet.
-    }
-}
diff --git a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/helper/SimpleListViewMcp.java b/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/helper/SimpleListViewMcp.java
deleted file mode 100644
index 0bbfccc..0000000
--- a/chrome/browser/touch_to_fill/android/internal/java/src/org/chromium/chrome/browser/touch_to_fill/helper/SimpleListViewMcp.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-package org.chromium.chrome.browser.touch_to_fill.helper;
-
-import android.view.View;
-
-import org.chromium.ui.modelutil.ForwardingListObservable;
-import org.chromium.ui.modelutil.ListModel;
-
-// TODO(fhorschig): Ask org.chromium.ui.modelutil OWNERS whether this should be shared.
-/**
- * Observes a {@link ListModel} and forwards any changes. It implements the
- * {@link ListViewAdapter.Delegate} so it can be used to bind views as needed by a
- * {@link android.widget.ListView} that ends up displaying the observed Entries.
- *
- * @param <T> The item type inside the passed ListModel. It's passed to the {@link ViewBinder}.
- * @param <V> The view type of a single list entry.
- */
-public class SimpleListViewMcp<T, V extends View>
-        extends ForwardingListObservable<Void> implements ListViewAdapter.Delegate<T, V> {
-    private final ListModel<T> mModel;
-    private final ViewBinder<T, V> mViewBinder;
-
-    /**
-     * Subclasses of this interface bind a given item to a given view.
-     * @param <T> The item type of a list entry.
-     * @param <V> The view type of a single list entry.
-     */
-    public interface ViewBinder<T, V extends View> {
-        /**
-         * Binds a given item to a given view. That means, the item properties are mapped to view
-         * properties. No logic should happen in here.
-         * @param view The view that holds the data of a list entry.
-         * @param item The item describing a single list entry.
-         */
-        void bind(V view, T item);
-    }
-
-    /**
-     * This creates a new Model Change Processor that observes the given model.
-     * @param model The {@Link ListModel} to be observed.
-     * @param viewBinder The {@link ViewBinder} used to bind items to views inside the ListView.
-     */
-    public SimpleListViewMcp(ListModel<T> model, ViewBinder<T, V> viewBinder) {
-        mModel = model;
-        mViewBinder = viewBinder;
-        model.addObserver(this);
-    }
-
-    @Override
-    public void onBindView(V view, T item) {
-        mViewBinder.bind(view, item);
-    }
-
-    @Override
-    public T getItemAt(int pos) {
-        return mModel.get(pos);
-    }
-
-    @Override
-    public int getItemCount() {
-        return mModel.size();
-    }
-}
\ No newline at end of file
diff --git a/chrome/browser/touch_to_fill/android/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillComponent.java b/chrome/browser/touch_to_fill/android/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillComponent.java
index 3445d58..bc8d0ce 100644
--- a/chrome/browser/touch_to_fill/android/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillComponent.java
+++ b/chrome/browser/touch_to_fill/android/java/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillComponent.java
@@ -5,7 +5,11 @@
 package org.chromium.chrome.browser.touch_to_fill;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 
+import androidx.annotation.Px;
+
+import org.chromium.base.Callback;
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
 import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
 
@@ -31,6 +35,11 @@
          * selected.
          */
         void onDismissed();
+
+        /**
+         * Called to fetch a favicon for one origin to display it in the UI.
+         */
+        void fetchFavicon(String origin, @Px int desiredSize, Callback<Bitmap> callback);
     }
 
     /**
diff --git a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
index b64ab2be..9bdd577 100644
--- a/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
+++ b/chrome/browser/touch_to_fill/android/javatests/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillViewTest.java
@@ -11,6 +11,9 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.CREDENTIAL;
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.DEFAULT_ITEM_TYPE;
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.FAVICON;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CREDENTIAL_LIST;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.FORMATTED_URL;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ORIGIN_SECURE;
@@ -41,10 +44,9 @@
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestThreadUtils;
 import org.chromium.content_public.browser.test.util.TouchCommon;
+import org.chromium.ui.modelutil.MVCListAdapter;
 import org.chromium.ui.modelutil.PropertyModel;
 
-import java.util.Arrays;
-
 /**
  * View tests for the Touch To Fill component ensure that model changes are reflected in the sheet.
  */
@@ -116,11 +118,12 @@
     public void testCredentialsChangedByModel() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mTouchToFillView.setVisible(true);
-            mModel.get(CREDENTIAL_LIST)
-                    .addAll(Arrays.asList(new Credential("Ana", "S3cr3t", "Ana", "", false),
-                            new Credential("", "***", "No Username", "http://m.example.xyz/", true),
-                            new Credential(
-                                    "Bob", "***", "Bob", "http://mobile.example.xyz", true)));
+            MVCListAdapter.ModelList credentialList = mModel.get(CREDENTIAL_LIST);
+            credentialList.add(buildCredentialItem("Ana", "S3cr3t", "Ana", "", false));
+            credentialList.add(
+                    buildCredentialItem("", "***", "No Username", "http://m.example.xyz/", true));
+            credentialList.add(
+                    buildCredentialItem("Bob", "***", "Bob", "http://mobile.example.xyz", true));
         });
 
         pollUiThread(() -> getBottomSheetState() == SheetState.FULL);
@@ -149,8 +152,9 @@
     public void testCredentialsAreClickable() {
         TestThreadUtils.runOnUiThreadBlocking(() -> {
             mModel.get(CREDENTIAL_LIST)
-                    .addAll(Arrays.asList(new Credential("Carl", "G3h3!m", "Carl", "", false),
-                            new Credential("Bob", "***", "Bob", "m.example.xyz", true)));
+                    .add(buildCredentialItem("Carl", "G3h3!m", "Carl", "", false));
+            mModel.get(CREDENTIAL_LIST)
+                    .add(buildCredentialItem("Bob", "***", "Bob", "m.example.xyz", true));
             mModel.set(VISIBLE, true);
         });
         pollUiThread(() -> getBottomSheetState() == SheetState.FULL);
@@ -205,4 +209,16 @@
         return verify(mMockListener,
                 timeout(ScalableTimeout.scaleTimeout(CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL)));
     }
+
+    private static MVCListAdapter.ListItem buildCredentialItem(String username, String password,
+            String formattedUsername, String originUrl, boolean isPublicSuffixMatch) {
+        PropertyModel propertyModel =
+                new PropertyModel.Builder(FAVICON, CREDENTIAL)
+                        .with(FAVICON, null)
+                        .with(CREDENTIAL,
+                                new Credential(username, password, formattedUsername, originUrl,
+                                        isPublicSuffixMatch))
+                        .build();
+        return new MVCListAdapter.ListItem(DEFAULT_ITEM_TYPE, propertyModel);
+    }
 }
diff --git a/chrome/browser/touch_to_fill/android/junit/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillControllerTest.java b/chrome/browser/touch_to_fill/android/junit/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillControllerTest.java
index 06f91dc..7b7c382e 100644
--- a/chrome/browser/touch_to_fill/android/junit/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillControllerTest.java
+++ b/chrome/browser/touch_to_fill/android/junit/src/org/chromium/chrome/browser/touch_to_fill/TouchToFillControllerTest.java
@@ -8,22 +8,34 @@
 import static org.hamcrest.Matchers.nullValue;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import static org.chromium.chrome.browser.touch_to_fill.CredentialProperties.DEFAULT_ITEM_TYPE;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.CREDENTIAL_LIST;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.FORMATTED_URL;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.ORIGIN_SECURE;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VIEW_EVENT_LISTENER;
 import static org.chromium.chrome.browser.touch_to_fill.TouchToFillProperties.VISIBLE;
 
+import android.graphics.Bitmap;
+
+import androidx.annotation.Px;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import org.chromium.base.Callback;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.chrome.browser.touch_to_fill.data.Credential;
+import org.chromium.ui.modelutil.MVCListAdapter;
 import org.chromium.ui.modelutil.PropertyModel;
 
 import java.util.Arrays;
@@ -36,15 +48,21 @@
 @RunWith(BaseRobolectricTestRunner.class)
 public class TouchToFillControllerTest {
     private static final String TEST_URL = "www.example.xyz";
-    private static final String TEST_MOBILE_URL = "www.example.xyz";
-    private static final Credential ANA = new Credential("Ana", "S3cr3t", "Ana", "", false);
+    private static final String TEST_SUBDOMAIN_URL = "subdomain.example.xyz";
+    private static final Credential ANA = new Credential("Ana", "S3cr3t", "Ana", TEST_URL, false);
     private static final Credential BOB =
-            new Credential("Bob", "*****", "Bob", TEST_MOBILE_URL, true);
-    private static final Credential CARL = new Credential("Carl", "G3h3!m", "Carl", "", false);
+            new Credential("Bob", "*****", "Bob", TEST_SUBDOMAIN_URL, true);
+    private static final Credential CARL =
+            new Credential("Carl", "G3h3!m", "Carl", TEST_URL, false);
+    private static final @Px int DESIRED_FAVICON_SIZE = 64;
 
     @Mock
     private TouchToFillComponent.Delegate mMockDelegate;
 
+    // Can't be local, as it has to be initialized by initMocks.
+    @Captor
+    private ArgumentCaptor<Callback<Bitmap>> mCallbackArgumentCaptor;
+
     private final TouchToFillMediator mMediator = new TouchToFillMediator();
     private final PropertyModel mModel = TouchToFillProperties.createDefaultModel(mMediator);
 
@@ -54,7 +72,7 @@
 
     @Before
     public void setUp() {
-        mMediator.initialize(mMockDelegate, mModel);
+        mMediator.initialize(mMockDelegate, mModel, DESIRED_FAVICON_SIZE);
     }
 
     @Test
@@ -74,24 +92,65 @@
     }
 
     @Test
-    public void testShowCredentialsSetsCredentialList() {
+    public void testShowCredentialsSetsCredentialListAndRequestsFavicons() {
         mMediator.showCredentials(TEST_URL, true, Arrays.asList(ANA, CARL, BOB));
-        assertThat(mModel.get(CREDENTIAL_LIST).size(), is(3));
-        assertThat(mModel.get(CREDENTIAL_LIST).get(0), is(ANA));
-        assertThat(mModel.get(CREDENTIAL_LIST).get(1), is(CARL));
-        assertThat(mModel.get(CREDENTIAL_LIST).get(2), is(BOB));
+        MVCListAdapter.ModelList credentialList = mModel.get(CREDENTIAL_LIST);
+        assertThat(credentialList.size(), is(3));
+        // TODO(https://crbug.com/1013209): Simplify this after adding equals to ModelList.
+        assertThat(credentialList.get(0).type, is(DEFAULT_ITEM_TYPE));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.CREDENTIAL), is(ANA));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.FAVICON), is(nullValue()));
+        assertThat(credentialList.get(1).type, is(DEFAULT_ITEM_TYPE));
+        assertThat(credentialList.get(1).model.get(CredentialProperties.CREDENTIAL), is(CARL));
+        assertThat(credentialList.get(1).model.get(CredentialProperties.FAVICON), is(nullValue()));
+        assertThat(credentialList.get(2).type, is(DEFAULT_ITEM_TYPE));
+        assertThat(credentialList.get(2).model.get(CredentialProperties.CREDENTIAL), is(BOB));
+        assertThat(credentialList.get(2).model.get(CredentialProperties.FAVICON), is(nullValue()));
+
+        // ANA and CARL both have TEST_URL as their origin URL
+        verify(mMockDelegate, times(2)).fetchFavicon(eq(TEST_URL), eq(DESIRED_FAVICON_SIZE), any());
+        verify(mMockDelegate).fetchFavicon(eq(BOB.getOriginUrl()), eq(DESIRED_FAVICON_SIZE), any());
+    }
+
+    @Test
+    public void testFetchFaviconUpdatesModel() {
+        mMediator.showCredentials(TEST_URL, true, Collections.singletonList(CARL));
+        MVCListAdapter.ModelList credentialList = mModel.get(CREDENTIAL_LIST);
+        assertThat(credentialList.size(), is(1));
+        // TODO(https://crbug.com/1013209): Simplify this after adding equals to ModelList.
+        assertThat(credentialList.get(0).type, is(DEFAULT_ITEM_TYPE));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.CREDENTIAL), is(CARL));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.FAVICON), is(nullValue()));
+
+        // ANA and CARL both have TEST_URL as their origin URL
+        verify(mMockDelegate)
+                .fetchFavicon(
+                        eq(TEST_URL), eq(DESIRED_FAVICON_SIZE), mCallbackArgumentCaptor.capture());
+        Callback<Bitmap> callback = mCallbackArgumentCaptor.getValue();
+        Bitmap bitmap = Bitmap.createBitmap(
+                DESIRED_FAVICON_SIZE, DESIRED_FAVICON_SIZE, Bitmap.Config.ARGB_8888);
+        callback.onResult(bitmap);
+        assertThat(credentialList.get(0).model.get(CredentialProperties.FAVICON), is(bitmap));
     }
 
     @Test
     public void testClearsCredentialListWhenShowingAgain() {
         mMediator.showCredentials(TEST_URL, true, Collections.singletonList(ANA));
-        assertThat(mModel.get(CREDENTIAL_LIST).size(), is(1));
-        assertThat(mModel.get(CREDENTIAL_LIST).get(0), is(ANA));
+        MVCListAdapter.ModelList credentialList = mModel.get(CREDENTIAL_LIST);
+        // TODO(https://crbug.com/1013209): Simplify this after adding equals to ModelList.
+        assertThat(credentialList.size(), is(1));
+        assertThat(credentialList.get(0).type, is(DEFAULT_ITEM_TYPE));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.CREDENTIAL), is(ANA));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.FAVICON), is(nullValue()));
 
         // Showing the sheet a second time should replace all changed credentials.
         mMediator.showCredentials(TEST_URL, true, Collections.singletonList(BOB));
-        assertThat(mModel.get(CREDENTIAL_LIST).size(), is(1));
-        assertThat(mModel.get(CREDENTIAL_LIST).get(0), is(BOB));
+        credentialList = mModel.get(CREDENTIAL_LIST);
+        // TODO(https://crbug.com/1013209): Simplify this after adding equals to ModelList.
+        assertThat(credentialList.size(), is(1));
+        assertThat(credentialList.get(0).type, is(DEFAULT_ITEM_TYPE));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.CREDENTIAL), is(BOB));
+        assertThat(credentialList.get(0).model.get(CredentialProperties.FAVICON), is(nullValue()));
     }
 
     @Test
diff --git a/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.cc b/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.cc
index 3448382..ebe7fe2 100644
--- a/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.cc
+++ b/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "base/android/callback_android.h"
 #include "base/android/jni_android.h"
 #include "base/android/jni_string.h"
 #include "base/macros.h"
@@ -17,6 +18,8 @@
 #include "components/password_manager/core/browser/origin_credential_store.h"
 #include "ui/android/view_android.h"
 #include "ui/android/window_android.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/image/image.h"
 #include "url/gurl.h"
 
 using base::android::AttachCurrentThread;
@@ -42,6 +45,15 @@
           Java_Credential_isPublicSuffixMatch(env, credential)));
 }
 
+void OnFaviconFetched(
+    const base::android::ScopedJavaGlobalRef<jobject>& j_callback,
+    const gfx::Image& image) {
+  base::android::ScopedJavaLocalRef<jobject> bitmap;
+  if (!image.IsEmpty())
+    bitmap = gfx::ConvertToJavaBitmap(image.ToSkBitmap());
+  base::android::RunObjectCallbackAndroid(j_callback, bitmap);
+}
+
 }  // namespace
 
 TouchToFillViewImpl::TouchToFillViewImpl(TouchToFillController* controller)
@@ -89,6 +101,17 @@
   controller_->OnDismiss();
 }
 
+void TouchToFillViewImpl::FetchFavicon(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& j_origin,
+    jint desized_size_in_pixel,
+    const JavaParamRef<jobject>& j_callback) {
+  controller_->FetchFavicon(
+      ConvertJavaStringToUTF8(env, j_origin), desized_size_in_pixel,
+      base::BindOnce(&OnFaviconFetched,
+                     base::android::ScopedJavaGlobalRef<jobject>(j_callback)));
+}
+
 void TouchToFillViewImpl::OnCredentialSelected(
     JNIEnv* env,
     const JavaParamRef<jobject>& credential) {
diff --git a/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.h b/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.h
index 356770f..506b3d2 100644
--- a/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.h
+++ b/chrome/browser/touch_to_fill/android/touch_to_fill_view_impl.h
@@ -9,6 +9,10 @@
 #include "base/callback.h"
 #include "chrome/browser/touch_to_fill/touch_to_fill_view.h"
 
+namespace gfx {
+class Image;
+}
+
 class TouchToFillController;
 
 // This class provides an implementation of the TouchToFillView interface and
@@ -28,6 +32,10 @@
   void OnDismiss() override;
 
   // Called from Java via JNI:
+  void FetchFavicon(JNIEnv* env,
+                    const base::android::JavaParamRef<jstring>& j_origin,
+                    jint desized_size_in_pixel,
+                    const base::android::JavaParamRef<jobject>& j_callback);
   void OnCredentialSelected(
       JNIEnv* env,
       const base::android::JavaParamRef<jobject>& credential);
diff --git a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
index c627836..7ea0d59 100644
--- a/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
+++ b/chrome/browser/ui/app_list/app_list_client_impl_browsertest.cc
@@ -344,17 +344,24 @@
 }
 
 // Browser Test for AppListClient that observes search result changes.
-using AppListClientSearchResultsBrowserTest = extensions::ExtensionBrowserTest;
+class AppListClientSearchResultsBrowserTest
+    : public extensions::ExtensionBrowserTest {
+ public:
+  AppListClientSearchResultsBrowserTest() {
+    // Zero state changes UI behavior. This test case tests the expected UI
+    // behavior with zero state being disabled.
+    // TODO(jennyz): write new test case for zero state, crbug.com/925195.
+    feature_list_.InitAndDisableFeature(
+        app_list_features::kEnableZeroStateSuggestions);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
 
 // Test showing search results, and uninstalling one of them while displayed.
 IN_PROC_BROWSER_TEST_F(AppListClientSearchResultsBrowserTest,
                        UninstallSearchResult) {
-  // Zero state changes UI behavior. This test case tests the expected UI
-  // behavior with zero state being disabled.
-  // TODO(jennyz): write new test case for zero state, crbug.com/925195.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      app_list_features::kEnableZeroStateSuggestions);
   ASSERT_FALSE(app_list_features::IsZeroStateSuggestionsEnabled());
 
   base::FilePath test_extension_path;
diff --git a/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc b/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc
index 2db8b2c8..d251fda 100644
--- a/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc
+++ b/chrome/browser/ui/ash/chrome_new_window_client_browsertest.cc
@@ -203,11 +203,19 @@
   settings->RemoveObserver(&observer);
 }
 
-IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTest,
-                       OpenOSSettingsAppFromArc) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
+class ChromeNewWindowClientBrowserTestWithSplitSettings
+    : public ChromeNewWindowClientBrowserTest {
+ public:
+  ChromeNewWindowClientBrowserTestWithSplitSettings() {
+    feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettings);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTestWithSplitSettings,
+                       OpenOSSettingsAppFromArc) {
   // When flag is on, opening a browser setting should not open the OS setting
   // window.
   TestOpenSettingFromArc(
@@ -222,12 +230,20 @@
       /*expected_setting_window_count=*/1);
 }
 
-// TODO(crbug/950007): This should be removed when the split is complete.
-IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTest,
-                       OpenSettingsAppFromArc) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(chromeos::features::kSplitSettings);
+class ChromeNewWindowClientBrowserTestWithoutSplitSettings
+    : public ChromeNewWindowClientBrowserTest {
+ public:
+  ChromeNewWindowClientBrowserTestWithoutSplitSettings() {
+    feature_list_.InitAndDisableFeature(chromeos::features::kSplitSettings);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// TODO(crbug/950007): This should be removed when the split is complete.
+IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTestWithoutSplitSettings,
+                       OpenSettingsAppFromArc) {
   // When flag is off, opening a browser setting should open the setting window.
   TestOpenSettingFromArc(
       browser(), ChromePage::AUTOFILL,
@@ -336,11 +352,8 @@
   TestOpenChromePage(ChromePage::ABOUTBLANK, GURL(url::kAboutBlankURL));
 }
 
-IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTest,
+IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTestWithSplitSettings,
                        TestOpenChromePageWithSplitFlagOn) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
-
   // Install the Settings App.
   web_app::WebAppProvider::Get(browser()->profile())
       ->system_web_app_manager()
@@ -352,11 +365,8 @@
 }
 
 // TODO(crbug/950007): This should be removed when the split is complete.
-IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTest,
+IN_PROC_BROWSER_TEST_F(ChromeNewWindowClientBrowserTestWithoutSplitSettings,
                        TestOpenChromePageWithSplitFlagOff) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(chromeos::features::kSplitSettings);
-
   // Install the Settings App.
   web_app::WebAppProvider::Get(browser()->profile())
       ->system_web_app_manager()
diff --git a/chrome/browser/ui/ash/overview_scroll_interactive_uitest.cc b/chrome/browser/ui/ash/overview_scroll_interactive_uitest.cc
index 7bde497..0b59e2c1 100644
--- a/chrome/browser/ui/ash/overview_scroll_interactive_uitest.cc
+++ b/chrome/browser/ui/ash/overview_scroll_interactive_uitest.cc
@@ -19,14 +19,15 @@
 // Test overview scroll performance when the new overview layout is active.
 class OverviewScrollTest : public UIPerformanceTest {
  public:
-  OverviewScrollTest() = default;
+  OverviewScrollTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        ash::features::kNewOverviewLayout);
+  }
+
   ~OverviewScrollTest() override = default;
 
   // UIPerformanceTest:
   void SetUpOnMainThread() override {
-    scoped_feature_list_.InitAndEnableFeature(
-        ash::features::kNewOverviewLayout);
-
     UIPerformanceTest::SetUpOnMainThread();
 
     // Create twelve windows total, scrolling is only needed when six or more
diff --git a/chrome/browser/ui/autofill/autofill_popup_layout_model.cc b/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
index 2a112093..d2b90d9 100644
--- a/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_layout_model.cc
@@ -223,7 +223,7 @@
                                  gfx::kChromeIconGrey);
   }
   if (icon_str == "httpsInvalid") {
-    return gfx::CreateVectorIcon(omnibox::kHttpsInvalidIcon, kIconSize,
+    return gfx::CreateVectorIcon(omnibox::kNotSecureWarningIcon, kIconSize,
                                  gfx::kGoogleRed700);
   }
   if (icon_str == "keyIcon") {
diff --git a/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc b/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
index 6ff5ccd3..1950b59 100644
--- a/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
+++ b/chrome/browser/ui/blocked_content/tab_under_blocker_browsertest.cc
@@ -44,6 +44,9 @@
 class TabUnderBlockerBrowserTest : public extensions::ExtensionBrowserTest {
  public:
   TabUnderBlockerBrowserTest() {
+    scoped_feature_list_.InitAndEnableFeature(
+        TabUnderNavigationThrottle::kBlockTabUnders);
+
     EXPECT_CALL(provider_, IsInitializationComplete(testing::_))
         .WillRepeatedly(testing::Return(true));
     policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
@@ -53,8 +56,6 @@
 
   void SetUpOnMainThread() override {
     extensions::ExtensionBrowserTest::SetUpOnMainThread();
-    scoped_feature_list_.InitAndEnableFeature(
-        TabUnderNavigationThrottle::kBlockTabUnders);
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
   }
diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc
index 831f97a..44c534d2 100644
--- a/chrome/browser/ui/browser.cc
+++ b/chrome/browser/ui/browser.cc
@@ -2320,6 +2320,10 @@
   if (tab_strip_model_->GetIndexOfWebContents(source) == TabStripModel::kNoTab)
     return;
 
+  // We ignore |INVALIDATE_TYPE_AUDIO| since we subscribe to
+  // RecentlyAudibleHelper via |OnTabAudibilityChanged()|.
+  changed_flags &= ~content::INVALIDATE_TYPE_AUDIO;
+
   // Do some synchronous updates.
   if (changed_flags & content::INVALIDATE_TYPE_URL) {
     if (source == tab_strip_model_->GetActiveWebContents()) {
@@ -2409,8 +2413,8 @@
     }
 
     // Updates that don't depend upon the selected state go here.
-    if (flags & (content::INVALIDATE_TYPE_TAB | content::INVALIDATE_TYPE_TITLE |
-                 content::INVALIDATE_TYPE_AUDIO)) {
+    if (flags &
+        (content::INVALIDATE_TYPE_TAB | content::INVALIDATE_TYPE_TITLE)) {
       tab_strip_model_->UpdateWebContentsStateAt(
           tab_strip_model_->GetIndexOfWebContents(contents),
           TabChangeType::kAll);
@@ -2588,10 +2592,16 @@
     zoom::ZoomController::FromWebContents(web_contents)->AddObserver(this);
     content_translate_driver->AddObserver(this);
     BookmarkTabHelper::FromWebContents(web_contents)->AddObserver(this);
+    audibility_subscriptions_[web_contents] =
+        RecentlyAudibleHelper::FromWebContents(web_contents)
+            ->RegisterCallback(
+                base::BindRepeating(&Browser::OnTabAudibilityChanged,
+                                    base::Unretained(this), web_contents));
   } else {
     zoom::ZoomController::FromWebContents(web_contents)->RemoveObserver(this);
     content_translate_driver->RemoveObserver(this);
     BookmarkTabHelper::FromWebContents(web_contents)->RemoveObserver(this);
+    audibility_subscriptions_.erase(web_contents);
   }
 }
 
@@ -2903,3 +2913,9 @@
 
   return contents;
 }
+
+void Browser::OnTabAudibilityChanged(content::WebContents* contents,
+                                     bool recently_audible) {
+  tab_strip_model_->UpdateWebContentsStateAt(
+      tab_strip_model_->GetIndexOfWebContents(contents), TabChangeType::kAll);
+}
diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h
index cf4b6e0..d0ac5fc 100644
--- a/chrome/browser/ui/browser.h
+++ b/chrome/browser/ui/browser.h
@@ -29,6 +29,7 @@
 #include "chrome/browser/ui/chrome_web_modal_dialog_manager_delegate.h"
 #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
 #include "chrome/browser/ui/profile_chooser_constants.h"
+#include "chrome/browser/ui/recently_audible_helper.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/unload_controller.h"
 #include "components/content_settings/core/common/content_settings.h"
@@ -1007,6 +1008,10 @@
       const std::string& partition_id,
       content::SessionStorageNamespace* session_storage_namespace);
 
+  // Callback for RecentlyAudibleHelper.
+  void OnTabAudibilityChanged(content::WebContents* contents,
+                              bool recently_audible);
+
   // Data members /////////////////////////////////////////////////////////////
 
   std::vector<InterstitialObserver*> interstitial_observers_;
@@ -1148,6 +1153,13 @@
       extension_browser_window_helper_;
 #endif
 
+  // These subscriptions are returned by RecentlyAudibleHelper when registering
+  // a callback. They de-register the callback when destroyed and safely handle
+  // destruction of the associated RecentlyAudibleHelper.
+  std::map<content::WebContents*,
+           std::unique_ptr<RecentlyAudibleHelper::Subscription>>
+      audibility_subscriptions_;
+
   // The following factory is used for chrome update coalescing.
   base::WeakPtrFactory<Browser> chrome_updater_factory_{this};
 
diff --git a/chrome/browser/ui/browser_browsertest.cc b/chrome/browser/ui/browser_browsertest.cc
index d300c87..8707e58c 100644
--- a/chrome/browser/ui/browser_browsertest.cc
+++ b/chrome/browser/ui/browser_browsertest.cc
@@ -887,11 +887,19 @@
   alert->native_dialog()->AcceptAppModalDialog();
 }
 
-IN_PROC_BROWSER_TEST_F(BrowserTest, NewTabFromLinkOpensInGroup) {
-  ASSERT_TRUE(embedded_test_server()->Start());
+class BrowserTestWithTabGroupsEnabled : public BrowserTest {
+ public:
+  BrowserTestWithTabGroupsEnabled() {
+    feature_list_.InitAndEnableFeature(features::kTabGroups);
+  }
 
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kTabGroups);
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(BrowserTestWithTabGroupsEnabled,
+                       NewTabFromLinkOpensInGroup) {
+  ASSERT_TRUE(embedded_test_server()->Start());
 
   // Add a grouped tab.
   TabStripModel* const model = browser()->tab_strip_model();
diff --git a/chrome/browser/ui/browser_navigator_browsertest_chromeos.cc b/chrome/browser/ui/browser_navigator_browsertest_chromeos.cc
index 1d8d041..43a8a14 100644
--- a/chrome/browser/ui/browser_navigator_browsertest_chromeos.cc
+++ b/chrome/browser/ui/browser_navigator_browsertest_chromeos.cc
@@ -47,13 +47,21 @@
                                ui::PageTransition::PAGE_TRANSITION_TYPED);
 }
 
+class BrowserNavigatorTestChromeOSWithSplitSettings
+    : public BrowserNavigatorTestChromeOS {
+ public:
+  BrowserNavigatorTestChromeOSWithSplitSettings() {
+    feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettings);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Verifies that the OS settings page opens in a standalone surface when
 // accessed via link or url.
-IN_PROC_BROWSER_TEST_F(BrowserNavigatorTestChromeOS, NavigateToOSSettings) {
-  // Enable SplitSettings feature.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
-
+IN_PROC_BROWSER_TEST_F(BrowserNavigatorTestChromeOSWithSplitSettings,
+                       NavigateToOSSettings) {
   // Install the Settings App.
   web_app::WebAppProvider::Get(browser()->profile())
       ->system_web_app_manager()
diff --git a/chrome/browser/ui/extensions/application_launch_browsertest.cc b/chrome/browser/ui/extensions/application_launch_browsertest.cc
index 53f03db..6cfd46a 100644
--- a/chrome/browser/ui/extensions/application_launch_browsertest.cc
+++ b/chrome/browser/ui/extensions/application_launch_browsertest.cc
@@ -15,19 +15,20 @@
 
 class ApplicationLaunchBrowserTest : public InProcessBrowserTest {
  public:
-  ApplicationLaunchBrowserTest() = default;
+  ApplicationLaunchBrowserTest() {
+    feature_list_.InitAndEnableFeature(features::kFocusMode);
+  }
 
   content::WebContents* GetWebContentsForTab(Browser* browser, int index) {
     return browser->tab_strip_model()->GetWebContentsAt(index);
   }
 
- protected:
-  base::test::ScopedFeatureList feature_list;
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 IN_PROC_BROWSER_TEST_F(ApplicationLaunchBrowserTest,
                        ReparentWebContentsForFocusModeSingleTab) {
-  feature_list.InitAndEnableFeature(features::kFocusMode);
   const GURL url("http://aaa.com/empty.html");
   ui_test_utils::NavigateToURL(browser(), url);
 
@@ -51,7 +52,6 @@
 IN_PROC_BROWSER_TEST_F(ApplicationLaunchBrowserTest,
                        ReparentWebContentsForFocusModeMultipleTabs) {
   const GURL url("http://aaa.com/empty.html");
-  feature_list.InitAndEnableFeature(features::kFocusMode);
   chrome::AddTabAt(browser(), url, -1, true);
   chrome::AddTabAt(browser(), GURL(), -1, true);
   EXPECT_FALSE(browser()->is_focus_mode());
diff --git a/chrome/browser/ui/hats/hats_service_browsertest.cc b/chrome/browser/ui/hats/hats_service_browsertest.cc
index 6626716e..694e2aa 100644
--- a/chrome/browser/ui/hats/hats_service_browsertest.cc
+++ b/chrome/browser/ui/hats/hats_service_browsertest.cc
@@ -81,22 +81,15 @@
 
 class HatsServiceProbabilityZero : public HatsServiceBrowserTestBase {
  protected:
-  HatsServiceProbabilityZero() = default;
-  ~HatsServiceProbabilityZero() override = default;
-
- private:
-  void SetUpOnMainThread() override {
-    HatsServiceBrowserTestBase::SetUpOnMainThread();
-
-    // The HatsService must not be created so that feature parameters can be
-    // injected below.
-    ASSERT_FALSE(
-        HatsServiceFactory::GetForProfile(browser()->profile(), false));
+  HatsServiceProbabilityZero() {
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
         features::kHappinessTrackingSurveysForDesktop,
         {{"probability", "0.000"}});
   }
 
+  ~HatsServiceProbabilityZero() override = default;
+
+ private:
   base::test::ScopedFeatureList scoped_feature_list_;
 
   DISALLOW_COPY_AND_ASSIGN(HatsServiceProbabilityZero);
@@ -113,17 +106,7 @@
 
 class HatsServiceProbabilityOne : public HatsServiceBrowserTestBase {
  protected:
-  HatsServiceProbabilityOne() = default;
-  ~HatsServiceProbabilityOne() override = default;
-
- private:
-  void SetUpOnMainThread() override {
-    HatsServiceBrowserTestBase::SetUpOnMainThread();
-
-    // The HatsService must not be created so that feature parameters can be
-    // injected below.
-    ASSERT_FALSE(
-        HatsServiceFactory::GetForProfile(browser()->profile(), false));
+  HatsServiceProbabilityOne() {
     // TODO(weili): refactor to use constants from hats_service.cc for these
     // parameters.
     scoped_feature_list_.InitAndEnableFeatureWithParameters(
@@ -131,6 +114,14 @@
         {{"probability", "1.000"},
          {"survey", "satisfaction"},
          {"en_site_id", "test_site_id"}});
+  }
+
+  ~HatsServiceProbabilityOne() override = default;
+
+ private:
+  void SetUpOnMainThread() override {
+    HatsServiceBrowserTestBase::SetUpOnMainThread();
+
     // Set the profile creation time to be old enough to ensure triggering.
     browser()->profile()->SetCreationTimeForTesting(
         base::Time::Now() - base::TimeDelta::FromDays(45));
diff --git a/chrome/browser/ui/login/login_handler_browsertest.cc b/chrome/browser/ui/login/login_handler_browsertest.cc
index ac929c60..3a48d76 100644
--- a/chrome/browser/ui/login/login_handler_browsertest.cc
+++ b/chrome/browser/ui/login/login_handler_browsertest.cc
@@ -177,12 +177,13 @@
     auth_map_["foo"] = AuthInfo("testuser", "foopassword");
     auth_map_["bar"] = AuthInfo("testuser", "barpassword");
     auth_map_["testrealm"] = AuthInfo(username_basic_, password_);
+
+    scoped_feature_list_.InitAndEnableFeature(
+        features::kHTTPAuthCommittedInterstitials);
   }
 
   void SetUpOnMainThread() override {
     host_resolver()->AddRule("*", "127.0.0.1");
-    scoped_feature_list.InitAndEnableFeature(
-        features::kHTTPAuthCommittedInterstitials);
   }
 
  protected:
@@ -207,7 +208,9 @@
   std::string password_;
   std::string username_basic_;
   std::string username_digest_;
-  base::test::ScopedFeatureList scoped_feature_list;
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 void LoginPromptBrowserTest::SetAuthFor(LoginHandler* handler) {
@@ -1811,6 +1814,18 @@
   EXPECT_EQ(0u, observer.handlers().size());
 }
 
+class ProxyBrowserTestWithHttpAuthCommittedInterstitials
+    : public ProxyBrowserTest {
+ public:
+  ProxyBrowserTestWithHttpAuthCommittedInterstitials() {
+    feature_list_.InitAndEnableFeature(
+        features::kHTTPAuthCommittedInterstitials);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests that basic proxy auth works as expected, for HTTPS pages.
 #if defined(OS_MACOSX)
 // TODO(https://crbug.com/1000446): Re-enable this test.
@@ -1818,10 +1833,8 @@
 #else
 #define MAYBE_ProxyAuthHTTPS ProxyAuthHTTPS
 #endif
-IN_PROC_BROWSER_TEST_F(ProxyBrowserTest, MAYBE_ProxyAuthHTTPS) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kHTTPAuthCommittedInterstitials);
+IN_PROC_BROWSER_TEST_F(ProxyBrowserTestWithHttpAuthCommittedInterstitials,
+                       MAYBE_ProxyAuthHTTPS) {
   net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
   https_server.AddDefaultHandlers(GetChromeTestDataDir());
   ASSERT_TRUE(https_server.Start());
@@ -1830,10 +1843,8 @@
 }
 
 // Tests that basic proxy auth works as expected, for HTTP pages.
-IN_PROC_BROWSER_TEST_F(ProxyBrowserTest, ProxyAuthHTTP) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kHTTPAuthCommittedInterstitials);
+IN_PROC_BROWSER_TEST_F(ProxyBrowserTestWithHttpAuthCommittedInterstitials,
+                       ProxyAuthHTTP) {
   ASSERT_TRUE(embedded_test_server()->Start());
   ASSERT_NO_FATAL_FAILURE(
       TestProxyAuth(browser(), embedded_test_server()->GetURL("/simple.html")));
diff --git a/chrome/browser/ui/manifest_web_app_browsertest.cc b/chrome/browser/ui/manifest_web_app_browsertest.cc
index fed1acdb..81a9897 100644
--- a/chrome/browser/ui/manifest_web_app_browsertest.cc
+++ b/chrome/browser/ui/manifest_web_app_browsertest.cc
@@ -34,10 +34,19 @@
   std::unique_ptr<base::HistogramTester> histogram_tester_;
 };
 
+class ManifestWebAppTestWithFocusModeEnabled : public ManifestWebAppTest {
+ public:
+  ManifestWebAppTestWithFocusModeEnabled() {
+    feature_list_.InitAndEnableFeature(features::kFocusMode);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Opens a basic example site in focus mode window.
-IN_PROC_BROWSER_TEST_F(ManifestWebAppTest, OpenExampleSite) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kFocusMode);
+IN_PROC_BROWSER_TEST_F(ManifestWebAppTestWithFocusModeEnabled,
+                       OpenExampleSite) {
   const GURL url("http://example.org/");
   ui_test_utils::NavigateToURL(browser(), url);
   Browser* app_browser = web_app::ReparentWebContentsForFocusMode(
@@ -55,9 +64,7 @@
   EXPECT_EQ(controller->GetThemeColor(), SK_ColorWHITE);
 }
 
-IN_PROC_BROWSER_TEST_F(ManifestWebAppTest, MetricsTest) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(features::kFocusMode);
+IN_PROC_BROWSER_TEST_F(ManifestWebAppTestWithFocusModeEnabled, MetricsTest) {
   Browser* app_browser = web_app::ReparentWebContentsForFocusMode(
       browser()->tab_strip_model()->GetWebContentsAt(0));
 
diff --git a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
index 8c31a7b..746e5de 100644
--- a/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
+++ b/chrome/browser/ui/omnibox/omnibox_view_browsertest.cc
@@ -1609,12 +1609,19 @@
   // TODO(msw): Test that AltGr+V does not paste.
 }
 
-IN_PROC_BROWSER_TEST_F(OmniboxViewTest, EditSearchEngines) {
+class OmniboxViewTestWithoutSplitSettings : public OmniboxViewTest {
+ public:
+  OmniboxViewTestWithoutSplitSettings() {
 #if defined(OS_CHROMEOS)
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndDisableFeature(chromeos::features::kSplitSettings);
+    feature_list_.InitAndDisableFeature(chromeos::features::kSplitSettings);
 #endif
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(OmniboxViewTestWithoutSplitSettings, EditSearchEngines) {
   OmniboxView* omnibox_view = nullptr;
   ASSERT_NO_FATAL_FAILURE(GetOmniboxView(&omnibox_view));
 #if defined(OS_CHROMEOS)
diff --git a/chrome/browser/ui/page_info/page_info.cc b/chrome/browser/ui/page_info/page_info.cc
index 7480da4..77324b5 100644
--- a/chrome/browser/ui/page_info/page_info.cc
+++ b/chrome/browser/ui/page_info/page_info.cc
@@ -778,7 +778,7 @@
           security_state::SafetyTipStatus::kNone &&
       visible_security_state.safety_tip_status !=
           security_state::SafetyTipStatus::kUnknown &&
-      base::FeatureList::IsEnabled(features::kSafetyTipUI)) {
+      base::FeatureList::IsEnabled(security_state::features::kSafetyTipUI)) {
     site_details_message_ = l10n_util::GetStringUTF16(
         IDS_PAGE_INFO_SAFETY_TIP_BAD_REPUTATION_DESCRIPTION);
   }
@@ -998,7 +998,7 @@
   info.connection_status_description = UTF16ToUTF8(site_connection_details_);
   info.identity_status = site_identity_status_;
   info.safe_browsing_status = safe_browsing_status_;
-  if (base::FeatureList::IsEnabled(features::kSafetyTipUI)) {
+  if (base::FeatureList::IsEnabled(security_state::features::kSafetyTipUI)) {
     info.safety_tip_status = safety_tip_status_;
   }
   info.identity_status_description = UTF16ToUTF8(site_details_message_);
diff --git a/chrome/browser/ui/page_info/page_info_unittest.cc b/chrome/browser/ui/page_info/page_info_unittest.cc
index ec8391d..c8a7fb7 100644
--- a/chrome/browser/ui/page_info/page_info_unittest.cc
+++ b/chrome/browser/ui/page_info/page_info_unittest.cc
@@ -1162,7 +1162,8 @@
 // various Safety Tip statuses.
 TEST_F(PageInfoTest, SafetyTipMetrics) {
   base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kSafetyTipUI);
+  scoped_feature_list.InitAndEnableFeature(
+      security_state::features::kSafetyTipUI);
   struct TestCase {
     const security_state::SafetyTipStatus safety_tip_status;
     const std::string histogram_name;
diff --git a/chrome/browser/ui/passwords/google_password_manager_navigation_throttle_browsertest.cc b/chrome/browser/ui/passwords/google_password_manager_navigation_throttle_browsertest.cc
index 5eada638..7b132200 100644
--- a/chrome/browser/ui/passwords/google_password_manager_navigation_throttle_browsertest.cc
+++ b/chrome/browser/ui/passwords/google_password_manager_navigation_throttle_browsertest.cc
@@ -76,11 +76,28 @@
                   return true;
                 }))) {}
 
-  std::unique_ptr<ProfileSyncServiceHarness> EnableGooglePasswordManagerAndSync(
-      Profile* profile) {
+ protected:
+  void TearDownOnMainThread() override {
+    interceptor_.reset();
+    SyncTest::TearDownOnMainThread();
+  }
+
+ private:
+  // Instantiate a content::URLLoaderInterceptor that will fail all requests
+  // with net::ERR_FAILED. This is done, because we are interested in being
+  // redirected when a navigation fails.
+  std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
+};
+
+class GooglePasswordManagerNavigationThrottleTestWithPasswordManager
+    : public GooglePasswordManagerNavigationThrottleTest {
+ public:
+  GooglePasswordManagerNavigationThrottleTestWithPasswordManager() {
     feature_list_.InitAndEnableFeature(
         password_manager::features::kGooglePasswordManager);
+  }
 
+  std::unique_ptr<ProfileSyncServiceHarness> EnableSync(Profile* profile) {
     ProfileSyncServiceFactory::GetAsProfileSyncServiceForProfile(profile)
         ->OverrideNetworkResourcesForTest(
             std::make_unique<fake_server::FakeServerNetworkResources>(
@@ -105,18 +122,6 @@
     EXPECT_TRUE(harness->SetupSync());
     return harness;
   }
-
- protected:
-  void TearDownOnMainThread() override {
-    interceptor_.reset();
-    SyncTest::TearDownOnMainThread();
-  }
-
- private:
-  // Instantiate a content::URLLoaderInterceptor that will fail all requests
-  // with net::ERR_FAILED. This is done, because we are interested in being
-  // redirected when a navigation fails.
-  std::unique_ptr<content::URLLoaderInterceptor> interceptor_;
 };
 
 // No navigation should be redirected in case the Google Password Manager and
@@ -140,29 +145,32 @@
 // Settings Subpage when trying to access the Google Password Manager when the
 // user's profile should be considered and the user clicked a link to get to the
 // Google Password Manager page.
-IN_PROC_BROWSER_TEST_F(GooglePasswordManagerNavigationThrottleTest,
-                       ExampleWithGPMAndSync) {
+IN_PROC_BROWSER_TEST_F(
+    GooglePasswordManagerNavigationThrottleTestWithPasswordManager,
+    ExampleWithGPMAndSync) {
   std::unique_ptr<ProfileSyncServiceHarness> harness =
-      EnableGooglePasswordManagerAndSync(browser()->profile());
+      EnableSync(browser()->profile());
   EXPECT_EQ(GetExampleURL(),
             NavigateToURL(browser(), GetExampleURL(),
                           ui::PageTransition::PAGE_TRANSITION_LINK));
 }
 
-IN_PROC_BROWSER_TEST_F(GooglePasswordManagerNavigationThrottleTest,
-                       PasswordsWithGPMAndSyncUserTyped) {
+IN_PROC_BROWSER_TEST_F(
+    GooglePasswordManagerNavigationThrottleTestWithPasswordManager,
+    PasswordsWithGPMAndSyncUserTyped) {
   std::unique_ptr<ProfileSyncServiceHarness> harness =
-      EnableGooglePasswordManagerAndSync(browser()->profile());
+      EnableSync(browser()->profile());
   EXPECT_EQ(GetGooglePasswordManagerURL(),
             NavigateToURL(browser(), GetGooglePasswordManagerURL(),
                           ui::PageTransition::PAGE_TRANSITION_TYPED));
 }
 
-IN_PROC_BROWSER_TEST_F(GooglePasswordManagerNavigationThrottleTest,
-                       PasswordsWithGPMAndSyncUserClickedLink) {
+IN_PROC_BROWSER_TEST_F(
+    GooglePasswordManagerNavigationThrottleTestWithPasswordManager,
+    PasswordsWithGPMAndSyncUserClickedLink) {
   base::HistogramTester tester;
   std::unique_ptr<ProfileSyncServiceHarness> harness =
-      EnableGooglePasswordManagerAndSync(browser()->profile());
+      EnableSync(browser()->profile());
 
 #if defined(OS_CHROMEOS)
   // Install the Settings App.
diff --git a/chrome/browser/ui/settings_window_manager_browsertest_chromeos.cc b/chrome/browser/ui/settings_window_manager_browsertest_chromeos.cc
index 5eb525f..2f47eee4 100644
--- a/chrome/browser/ui/settings_window_manager_browsertest_chromeos.cc
+++ b/chrome/browser/ui/settings_window_manager_browsertest_chromeos.cc
@@ -157,29 +157,47 @@
   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
 }
 
-// TODO(crbug/950007): Remove ScopedFeatureList when kSplitSettings flag is on
-// by default.
-IN_PROC_BROWSER_TEST_P(SettingsWindowManagerTest, OpenAboutPageSplitSettings) {
+// TODO(crbug/950007): Remove when kSplitSettings flag is on by default.
+class SettingsWindowManagerTestWithSplitSettings
+    : public SettingsWindowManagerTest {
+ public:
+  SettingsWindowManagerTestWithSplitSettings() {
+    feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettings);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+// TODO(crbug/950007): Remove when kSplitSettings flag is on by default.
+class SettingsWindowManagerTestWithoutSplitSettings
+    : public SettingsWindowManagerTest {
+ public:
+  SettingsWindowManagerTestWithoutSplitSettings() {
+    feature_list_.InitAndDisableFeature(chromeos::features::kSplitSettings);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_P(SettingsWindowManagerTestWithSplitSettings,
+                       OpenAboutPageSplitSettings) {
   // About should open settings window when split settings feature flag is on.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
   chrome::ShowAboutChrome(browser());
   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
 }
 
-// TODO(crbug/950007): Remove when kSplitSettings flag is on by default.
-IN_PROC_BROWSER_TEST_P(SettingsWindowManagerTest, OpenAboutPage) {
+IN_PROC_BROWSER_TEST_P(SettingsWindowManagerTestWithoutSplitSettings,
+                       OpenAboutPage) {
   // About should open a new browser window when split settings feature flag is
   // off.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(chromeos::features::kSplitSettings);
   chrome::ShowAboutChrome(browser());
   EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
 }
 
-IN_PROC_BROWSER_TEST_P(SettingsWindowManagerTest, SplitSettings) {
-  base::test::ScopedFeatureList feature_list;
-  feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
+IN_PROC_BROWSER_TEST_P(SettingsWindowManagerTestWithSplitSettings,
+                       SplitSettings) {
   EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
 
   // Browser settings opens in the existing browser window.
diff --git a/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc b/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc
index 5f55535..f08715f 100644
--- a/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/local_card_migration_browsertest.cc
@@ -570,20 +570,18 @@
     : public LocalCardMigrationBrowserTest {
  protected:
   LocalCardMigrationBrowserTestForStatusChip()
-      : LocalCardMigrationBrowserTest() {}
-
-  ~LocalCardMigrationBrowserTestForStatusChip() override {}
-
-  void SetUp() override {
-    base::test::ScopedFeatureList scoped_feature_list;
-    scoped_feature_list.InitWithFeatures(
+      : LocalCardMigrationBrowserTest() {
+    feature_list_.InitWithFeatures(
         /*enabled_features=*/{features::kAutofillCreditCardUploadFeedback,
                               features::kAutofillEnableToolbarStatusChip,
                               features::kAutofillUpstream},
         /*disabled_features=*/{});
-
-    LocalCardMigrationBrowserTest::SetUp();
   }
+
+  ~LocalCardMigrationBrowserTestForStatusChip() override = default;
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 // Ensures that migration is not offered when user saves a new card.
@@ -904,12 +902,21 @@
   EXPECT_EQ(nullptr, personal_data_->GetCreditCardByNumber(kSecondCardNumber));
 }
 
+class LocalCardMigrationBrowserTestWithStrikeSystemV2
+    : public LocalCardMigrationBrowserTest {
+ public:
+  LocalCardMigrationBrowserTestWithStrikeSystemV2() {
+    feature_list_.InitAndEnableFeature(
+        features::kAutofillLocalCardMigrationUsesStrikeSystemV2);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Ensures that rejecting the main migration dialog adds 3 strikes.
-IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTestWithStrikeSystemV2,
                        ClosingDialogAddsLocalCardMigrationStrikes) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kAutofillLocalCardMigrationUsesStrikeSystemV2);
   base::HistogramTester histogram_tester;
 
   SaveLocalCard(kFirstCardNumber);
@@ -926,11 +933,8 @@
 }
 
 // Ensures that rejecting the migration bubble adds 2 strikes.
-IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTestWithStrikeSystemV2,
                        ClosingBubbleAddsLocalCardMigrationStrikes) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kAutofillLocalCardMigrationUsesStrikeSystemV2);
   base::HistogramTester histogram_tester;
 
   SaveLocalCard(kFirstCardNumber);
@@ -949,11 +953,8 @@
 
 // Ensures that rejecting the migration bubble repeatedly adds 2 strikes every
 // time, even for the same tab.
-IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTestWithStrikeSystemV2,
                        ClosingBubbleAgainAddsLocalCardMigrationStrikes) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kAutofillLocalCardMigrationUsesStrikeSystemV2);
   base::HistogramTester histogram_tester;
 
   SaveLocalCard(kFirstCardNumber);
@@ -976,12 +977,8 @@
 
 // Ensures that reshowing and closing bubble after previously closing it does
 // not add strikes.
-IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTest,
+IN_PROC_BROWSER_TEST_F(LocalCardMigrationBrowserTestWithStrikeSystemV2,
                        ReshowingBubbleDoesNotAddStrikes) {
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(
-      features::kAutofillLocalCardMigrationUsesStrikeSystemV2);
-
   SaveLocalCard(kFirstCardNumber);
   SaveLocalCard(kSecondCardNumber);
   UseCardAndWaitForMigrationOffer(kFirstCardNumber);
diff --git a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
index 81c8ebd..d0e126ca 100644
--- a/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
+++ b/chrome/browser/ui/views/autofill/payments/save_card_bubble_views_browsertest.cc
@@ -337,14 +337,6 @@
   }
 
   void SetUpForEditableExpirationDate() {
-    // Enable the EditableExpirationDate experiment.
-    scoped_feature_list_.InitWithFeatures(
-        // Enabled
-        {features::kAutofillUpstreamEditableExpirationDate,
-         features::kAutofillUpstream},
-        // Disabled
-        {});
-
     // Start sync.
     harness_->SetupSync();
   }
@@ -830,8 +822,6 @@
       base::CallbackList<void(content::BrowserContext*)>::Subscription>
       will_create_browser_context_services_subscription_;
 
-  base::test::ScopedFeatureList scoped_feature_list_;
-
   std::unique_ptr<ProfileSyncServiceHarness> harness_;
 
   CreditCardSaveManager* credit_card_save_manager_ = nullptr;
@@ -845,6 +835,23 @@
   DISALLOW_COPY_AND_ASSIGN(SaveCardBubbleViewsFullFormBrowserTest);
 };
 
+class SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate() {
+    // Enable the EditableExpirationDate experiment.
+    feature_list_.InitWithFeatures(
+        // Enabled
+        {features::kAutofillUpstreamEditableExpirationDate,
+         features::kAutofillUpstream},
+        // Disabled
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // TODO(crbug.com/932818): Remove this class after experiment flag is cleaned
 // up. Otherwise we need it because the toolbar is init-ed before each test is
 // set up. Thus need to enable the feature in the general browsertest SetUp().
@@ -856,8 +863,7 @@
   ~SaveCardBubbleViewsFullFormBrowserTestForStatusChip() override {}
 
   void SetUp() override {
-    base::test::ScopedFeatureList scoped_feature_list;
-    scoped_feature_list.InitWithFeatures(
+    feature_list_.InitWithFeatures(
         /*enabled_features=*/{features::kAutofillCreditCardUploadFeedback,
                               features::kAutofillEnableToolbarStatusChip,
                               features::kAutofillUpstream},
@@ -865,6 +871,9 @@
 
     SaveCardBubbleViewsFullFormBrowserTest::SetUp();
   }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 // Tests the local save bubble. Ensures that clicking the [Save] button
@@ -937,13 +946,23 @@
 }
 #endif
 
+class SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream() {
+    feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the sign in promo bubble. Ensures that the sign-in promo
 // is not shown when the user is signed-in and syncing, even if the local save
 // bubble is shown.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Local_NoSigninPromoShowsWhenUserIsSyncing) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Local_NoSigninPromoShowsWhenUserIsSyncing) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1076,16 +1095,25 @@
                                       AutofillMetrics::MANAGE_CARDS_SHOWN, 1);
 }
 
+class SaveCardBubbleViewsFullFormBrowserTestWithoutSplitSettings
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithoutSplitSettings() {
+#if defined(OS_CHROMEOS)
+    feature_list_.InitAndDisableFeature(chromeos::features::kSplitSettings);
+#endif
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // TODO(crbug/950007): Remove this test when kSplitSettings is on by default
 // Tests the manage cards bubble. Ensures that clicking the [Manage cards]
 // button redirects properly.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Local_ManageCardsButtonRedirects) {
-#if defined(OS_CHROMEOS)
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(chromeos::features::kSplitSettings);
-#endif
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithoutSplitSettings,
+    Local_ManageCardsButtonRedirects) {
   base::HistogramTester histogram_tester;
   OpenSettingsFromManageCardsPrompt();
 
@@ -1106,15 +1134,22 @@
                   Bucket(AutofillMetrics::MANAGE_CARDS_MANAGE_CARDS, 1)));
 }
 
+class SaveCardBubbleViewsFullFormBrowserTestWithSplitSettings
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithSplitSettings() {
+#if defined(OS_CHROMEOS)
+    feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettings);
+#endif
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
 // Tests the manage cards bubble. Ensures that clicking the [Manage cards]
 // button redirects properly.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTestWithSplitSettings,
                        Local_ManageCardsButtonRedirects_WithSplitSettings) {
-#if defined(OS_CHROMEOS)
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(chromeos::features::kSplitSettings);
-#endif
-
   base::HistogramTester histogram_tester;
   OpenSettingsFromManageCardsPrompt();
 
@@ -1249,13 +1284,23 @@
 }
 #endif
 
+class SaveCardBubbleViewsFullFormBrowserTestWithoutNoThanks
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithoutNoThanks() {
+    feature_list_.InitAndDisableFeature(
+        features::kAutofillSaveCardShowNoThanks);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 #if defined(OS_CHROMEOS)
 // Tests the local save bubble. Ensures that the bubble does not have a
 // [No thanks] button (it has an [X] Close button instead.)
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTestWithoutNoThanks,
                        Local_ShouldNotHaveNoThanksButton) {
-  scoped_feature_list_.InitAndDisableFeature(
-      features::kAutofillSaveCardShowNoThanks);
   FillForm();
   SubmitFormAndWaitForCardLocalSaveBubble();
 
@@ -1263,12 +1308,21 @@
   EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CANCEL_BUTTON));
 }
 
+class SaveCardBubbleViewsFullFormBrowserTestWithNoThanks
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithNoThanks() {
+    feature_list_.InitAndEnableFeature(features::kAutofillSaveCardShowNoThanks);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the local save bubble. Ensures that the bubble has a
 // [No thanks] button if |kAutofillSaveCardShowNoThanks| experiment is enabled.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
+IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTestWithNoThanks,
                        Local_ShouldHaveNoThanksButtonIfExperimentEnabled) {
-  scoped_feature_list_.InitAndEnableFeature(
-      features::kAutofillSaveCardShowNoThanks);
   FillForm();
   SubmitFormAndWaitForCardLocalSaveBubble();
 
@@ -1309,10 +1363,9 @@
 // Tests the upload save bubble. Ensures that clicking the [Save] button
 // successfully causes the bubble to go away and sends an UploadCardRequest RPC
 // to Google Payments.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ClickingSaveClosesBubble) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Upload_ClickingSaveClosesBubble) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1339,15 +1392,16 @@
 class SaveCardBubbleViewsSyncTransportFullFormBrowserTest
     : public SaveCardBubbleViewsFullFormBrowserTest {
  protected:
-  SaveCardBubbleViewsSyncTransportFullFormBrowserTest() = default;
-
-  void SetUpInProcessBrowserTestFixture() override {
+  SaveCardBubbleViewsSyncTransportFullFormBrowserTest() {
     // Set up Sync the transport mode, so that sync starts on content-area
     // signins. Also add wallet data type to the list of enabled types.
-    scoped_feature_list_.InitWithFeatures(
+    feature_list_.InitWithFeatures(
         /*enabled_features=*/{features::kAutofillUpstream,
                               features::kAutofillEnableAccountWalletStorage},
         /*disabled_features=*/{});
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
     test_signin_client_factory_ =
         secondary_account_helper::SetUpSigninClient(test_url_loader_factory());
 
@@ -1370,6 +1424,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
   secondary_account_helper::ScopedSigninClientFactory
       test_signin_client_factory_;
 
@@ -1438,20 +1493,20 @@
     SaveCardBubbleViewsSyncTransportWithEditableCardholderNameFullFormBrowserTest
     : public SaveCardBubbleViewsFullFormBrowserTest {
  protected:
-  SaveCardBubbleViewsSyncTransportWithEditableCardholderNameFullFormBrowserTest() =
-      default;
-
-  void SetUpInProcessBrowserTestFixture() override {
+  SaveCardBubbleViewsSyncTransportWithEditableCardholderNameFullFormBrowserTest() {
     // Set up Sync the transport mode, so that sync starts on content-area
     // signins. Also add wallet data type to the list of enabled types and
     // enable EditableCardholderName experiment.
-    scoped_feature_list_.InitWithFeatures(
+    feature_list_.InitWithFeatures(
         // Enabled
         {features::kAutofillUpstream,
          features::kAutofillUpstreamEditableCardholderName,
          features::kAutofillEnableAccountWalletStorage},
         // Disabled
         {});
+  }
+
+  void SetUpInProcessBrowserTestFixture() override {
     test_signin_client_factory_ =
         secondary_account_helper::SetUpSigninClient(test_url_loader_factory());
 
@@ -1459,6 +1514,7 @@
   }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
   secondary_account_helper::ScopedSigninClientFactory
       test_signin_client_factory_;
 
@@ -1503,10 +1559,9 @@
 
 // Tests the fully-syncing state. Ensures that the Butter (i) info icon does not
 // appear for fully-syncing users.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_NotTransportMode_InfoTextIconDoesNotExist) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Upload_NotTransportMode_InfoTextIconDoesNotExist) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1522,10 +1577,9 @@
     (defined(OS_LINUX) && !defined(OS_CHROMEOS))
 // Tests the upload save bubble. Ensures that clicking the [No thanks] button
 // successfully causes the bubble to go away.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ClickingNoThanksClosesBubble) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Upload_ClickingNoThanksClosesBubble) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1544,16 +1598,26 @@
 #endif
 
 #if defined(OS_CHROMEOS)
+class SaveCardBubbleViewsFullFormBrowserTestWithAutofillAndWithoutNoThanks
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithAutofillAndWithoutNoThanks() {
+    feature_list_.InitWithFeatures(
+        // Enabled
+        {features::kAutofillUpstream},
+        // Disabled
+        {features::kAutofillSaveCardShowNoThanks});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the upload save bubble. Ensures that the bubble does not have a
 // [No thanks] button (it has an [X] Close button instead.)
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ShouldNotHaveNoThanksButton) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream},
-      // Disabled
-      {features::kAutofillSaveCardShowNoThanks});
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillAndWithoutNoThanks,
+    Upload_ShouldNotHaveNoThanksButton) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1564,16 +1628,26 @@
   EXPECT_FALSE(FindViewInBubbleById(DialogViewId::CANCEL_BUTTON));
 }
 
+class SaveCardBubbleViewsFullFormBrowserTestWithAutofillAndNoThanks
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithAutofillAndNoThanks() {
+    feature_list_.InitWithFeatures(
+        // Enabled
+        {features::kAutofillUpstream, features::kAutofillSaveCardShowNoThanks},
+        // Disabled
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the upload save bubble. Ensures that the bubble has a
 // [No thanks] button if |kAutofillSaveCardShowNoThanks| experiment is enabled.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ShouldHaveNoThanksButtonIfExperimentEnabled) {
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillSaveCardShowNoThanks, features::kAutofillUpstream},
-      // Disabled
-      {});
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillAndNoThanks,
+    Upload_ShouldHaveNoThanksButtonIfExperimentEnabled) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1587,10 +1661,9 @@
 
 // Tests the upload save bubble. Ensures that clicking the top-right [X] close
 // button successfully causes the bubble to go away.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ClickingCloseClosesBubble) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Upload_ClickingCloseClosesBubble) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1601,18 +1674,28 @@
   ClickOnCloseButton();
 }
 
+class SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName() {
+    // Enable the EditableCardholderName experiment.
+    feature_list_.InitWithFeatures(
+        // Enabled
+        {features::kAutofillUpstream,
+         features::kAutofillUpstreamEditableCardholderName},
+        // Disabled
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the upload save bubble. Ensures that the bubble does not surface the
 // cardholder name textfield if it is not needed.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ShouldNotRequestCardholderNameInHappyPath) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
+    Upload_ShouldNotRequestCardholderNameInHappyPath) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1626,16 +1709,8 @@
 // Tests the upload save bubble. Ensures that the bubble surfaces a textfield
 // requesting cardholder name if cardholder name is missing.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_SubmittingFormWithMissingNamesRequestsCardholderNameIfExpOn) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
   // Start sync.
   harness_->SetupSync();
 
@@ -1648,16 +1723,8 @@
 // Tests the upload save bubble. Ensures that the bubble surfaces a textfield
 // requesting cardholder name if cardholder name is conflicting.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_SubmittingFormWithConflictingNamesRequestsCardholderNameIfExpOn) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
   // Start sync.
   harness_->SetupSync();
 
@@ -1675,16 +1742,8 @@
 // Tests the upload save bubble. Ensures that if the cardholder name textfield
 // is empty, the user is not allowed to click [Save] and close the dialog.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_SaveButtonIsDisabledIfNoCardholderNameAndCardholderNameRequested) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
   // Start sync.
   harness_->SetupSync();
 
@@ -1714,16 +1773,8 @@
 // Tests the upload save bubble. Ensures that if cardholder name is explicitly
 // requested, filling it and clicking [Save] closes the dialog.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_EnteringCardholderNameAndClickingSaveClosesBubbleIfCardholderNameRequested) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
   // Start sync.
   harness_->SetupSync();
 
@@ -1755,16 +1806,9 @@
 // Tests the upload save bubble. Ensures that if cardholder name is explicitly
 // requested, it is prefilled with the name from the user's Google Account.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_RequestedCardholderNameTextfieldIsPrefilledWithFocusName) {
   base::HistogramTester histogram_tester;
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
 
   // Start sync.
   harness_->SetupSync();
@@ -1794,16 +1838,10 @@
 // requested but the name on the user's Google Account is unable to be fetched
 // for any reason, the textfield is left blank.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_RequestedCardholderNameTextfieldIsNotPrefilledWithFocusNameIfMissing) {
   // Enable the EditableCardholderName experiment.
   base::HistogramTester histogram_tester;
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
 
   // Start sync.
   harness_->SetupSync();
@@ -1829,16 +1867,8 @@
 // requested and the user accepts the dialog without changing it, the correct
 // metric is logged.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_CardholderNameRequested_SubmittingPrefilledValueLogsUneditedMetric) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
   // Start sync.
   harness_->SetupSync();
   // Set the user's full name.
@@ -1863,16 +1893,8 @@
 // requested and the user accepts the dialog after changing it, the correct
 // metric is logged.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableCardholderName,
     Upload_CardholderNameRequested_SubmittingChangedValueLogsEditedMetric) {
-  // Enable the EditableCardholderName experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName},
-      // Disabled
-      {});
-
   // Start sync.
   harness_->SetupSync();
   // Set the user's full name.
@@ -1897,21 +1919,32 @@
       "Autofill.SaveCardCardholderNameWasEdited", true, 1);
 }
 
+class SaveCardBubbleViewsFullFormBrowserTestWithBlankEditableCardholderName
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithBlankEditableCardholderName() {
+    // Enable the EditableCardholderName and BlankCardholderNameField
+    // experiments.
+    feature_list_.InitWithFeatures(
+        {features::kAutofillUpstream,
+         features::kAutofillUpstreamEditableCardholderName,
+         features::kAutofillUpstreamBlankCardholderNameField},
+        // Disabled
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the upload save bubble. Ensures that if cardholder name is explicitly
 // requested but the AutofillUpstreamBlankCardholderNameField experiment is
 // active, the textfield is NOT prefilled even though the user's Google Account
 // name is available.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithBlankEditableCardholderName,
     Upload_CardholderNameNotPrefilledIfBlankNameExperimentEnabled) {
   base::HistogramTester histogram_tester;
-  // Enable the EditableCardholderName and BlankCardholderNameField experiments.
-  scoped_feature_list_.InitWithFeatures(
-      {features::kAutofillUpstream,
-       features::kAutofillUpstreamEditableCardholderName,
-       features::kAutofillUpstreamBlankCardholderNameField},
-      // Disabled
-      {});
 
   // Start sync.
   harness_->SetupSync();
@@ -1943,10 +1976,9 @@
 
 // Tests the upload save logic. Ensures that Chrome offers a local save when the
 // data is complete, even if Payments rejects the data.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldOfferLocalSaveIfPaymentsDeclines) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldOfferLocalSaveIfPaymentsDeclines) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1970,10 +2002,9 @@
 
 // Tests the upload save logic. Ensures that Chrome offers a local save when the
 // data is complete, even if the Payments upload fails unexpectedly.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldOfferLocalSaveIfPaymentsFails) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldOfferLocalSaveIfPaymentsFails) {
   // Start sync.
   harness_->SetupSync();
 
@@ -1998,10 +2029,8 @@
 // Tests the upload save logic. Ensures that Chrome delegates the offer-to-save
 // call to Payments, and offers to upload save the card if Payments allows it.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
     Logic_CanOfferToSaveEvenIfNothingFoundIfPaymentsAccepts) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
   // Start sync.
   harness_->SetupSync();
 
@@ -2015,10 +2044,9 @@
 
 // Tests the upload save logic. Ensures that Chrome offers a upload save for
 // dynamic change form.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_CanOfferToSaveDynamicForm) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_CanOfferToSaveDynamicForm) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2042,10 +2070,8 @@
 // call to Payments, and still does not surface the offer to upload save dialog
 // if Payments declines it.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
     Logic_ShouldNotOfferToSaveIfNothingFoundAndPaymentsDeclines) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
   // Start sync.
   harness_->SetupSync();
 
@@ -2067,10 +2093,9 @@
 
 // Tests the upload save logic. Ensures that Chrome lets Payments decide whether
 // upload save should be offered, even if CVC is not detected.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldAttemptToOfferToSaveIfCvcNotFound) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldAttemptToOfferToSaveIfCvcNotFound) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2084,10 +2109,9 @@
 
 // Tests the upload save logic. Ensures that Chrome lets Payments decide whether
 // upload save should be offered, even if the detected CVC is invalid.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldAttemptToOfferToSaveIfInvalidCvcFound) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldAttemptToOfferToSaveIfInvalidCvcFound) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2103,10 +2127,9 @@
 // Tests the upload save logic. Ensures that Chrome lets Payments decide whether
 // upload save should be offered, even if address/cardholder name is not
 // detected.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldAttemptToOfferToSaveIfNameNotFound) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldAttemptToOfferToSaveIfNameNotFound) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2122,10 +2145,9 @@
 // Tests the upload save logic. Ensures that Chrome lets Payments decide whether
 // upload save should be offered, even if multiple conflicting names are
 // detected.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldAttemptToOfferToSaveIfNamesConflict) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldAttemptToOfferToSaveIfNamesConflict) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2144,10 +2166,9 @@
 
 // Tests the upload save logic. Ensures that Chrome lets Payments decide whether
 // upload save should be offered, even if billing address is not detected.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldAttemptToOfferToSaveIfAddressNotFound) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldAttemptToOfferToSaveIfAddressNotFound) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2163,10 +2184,9 @@
 // Tests the upload save logic. Ensures that Chrome lets Payments decide whether
 // upload save should be offered, even if multiple conflicting billing address
 // postal codes are detected.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Logic_ShouldAttemptToOfferToSaveIfPostalCodesConflict) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    Logic_ShouldAttemptToOfferToSaveIfPostalCodesConflict) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2186,10 +2206,9 @@
 // Tests UMA logging for the upload save bubble. Ensures that if the user
 // declines upload, Autofill.UploadAcceptedCardOrigin is not logged.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
     Upload_DecliningUploadDoesNotLogUserAcceptedCardOriginUMA) {
   base::HistogramTester histogram_tester;
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
 
   // Start sync.
   harness_->SetupSync();
@@ -2210,7 +2229,7 @@
 // Tests the upload save bubble. Ensures that the bubble surfaces a pair of
 // dropdowns requesting expiration date if expiration date is missing.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithMissingExpirationDateRequestsExpirationDate) {
   SetUpForEditableExpirationDate();
   FillFormWithoutExpirationDate();
@@ -2221,7 +2240,7 @@
 // Tests the upload save bubble. Ensures that the bubble surfaces a pair of
 // dropdowns requesting expiration date if expiration date is expired.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithExpiredExpirationDateRequestsExpirationDate) {
   SetUpForEditableExpirationDate();
   FillFormWithSpecificExpirationDate("08", "2000");
@@ -2229,18 +2248,28 @@
   VerifyExpirationDateDropdownsAreVisible();
 }
 
+class
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstreamAndNoEditableExpirationDate
+    : public SaveCardBubbleViewsFullFormBrowserTest {
+ public:
+  SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstreamAndNoEditableExpirationDate() {
+    // Disable the EditableExpirationDate experiment.
+    feature_list_.InitWithFeatures(
+        // Enabled
+        {features::kAutofillUpstream},
+        // Disabled
+        {features::kAutofillUpstreamEditableExpirationDate});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Tests the upload save bubble. Ensures that the bubble is not shown when
 // expiration date is passed, but the flag is disabled.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstreamAndNoEditableExpirationDate,
     Logic_ShouldNotOfferToSaveIfSubmittingExpiredExpirationDateAndExpOff) {
-  // Disable the EditableExpirationDate experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream},
-      // Disabled
-      {features::kAutofillUpstreamEditableExpirationDate});
-
   // The credit card will not be imported if the expiration date is expired and
   // experiment is off.
   FillFormWithSpecificExpirationDate("08", "2000");
@@ -2251,15 +2280,8 @@
 // Tests the upload save bubble. Ensures that the bubble is not shown when
 // expiration date is missing, but the flag is disabled.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstreamAndNoEditableExpirationDate,
     Logic_ShouldNotOfferToSaveIfMissingExpirationDateAndExpOff) {
-  // Disable the EditableExpirationDate experiment.
-  scoped_feature_list_.InitWithFeatures(
-      // Enabled
-      {features::kAutofillUpstream},
-      // Disabled
-      {features::kAutofillUpstreamEditableExpirationDate});
-
   // The credit card will not be imported if there is no expiration date and
   // experiment is off.
   FillFormWithoutExpirationDate();
@@ -2269,8 +2291,9 @@
 
 // Tests the upload save bubble. Ensures that the bubble does not surface the
 // expiration date dropdowns if it is not needed.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       Upload_ShouldNotRequestExpirationDateInHappyPath) {
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
+    Upload_ShouldNotRequestExpirationDateInHappyPath) {
   SetUpForEditableExpirationDate();
   FillForm();
   SubmitFormAndWaitForCardUploadSaveBubble();
@@ -2288,7 +2311,7 @@
 // Tests the upload save bubble. Ensures that if the expiration date drop down
 // box is changing, [Save] button will change status correctly.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SaveButtonStatusResetBetweenExpirationDateSelectionChanges) {
   SetUpForEditableExpirationDate();
   FillFormWithoutExpirationDate();
@@ -2320,7 +2343,7 @@
 // Tests the upload save bubble. Ensures that if the user is selecting an
 // expired expiration date, it is not allowed to click [Save].
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SaveButtonIsDisabledIfExpiredExpirationDateAndExpirationDateRequested) {
   SetUpForEditableExpirationDate();
   FillFormWithoutExpirationDate();
@@ -2346,7 +2369,7 @@
 // dropdowns requesting expiration date with year pre-populated if year is valid
 // but month is missing.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithMissingExpirationDateMonthAndWithValidYear) {
   SetUpForEditableExpirationDate();
   // Submit the form with a year value, but not a month value.
@@ -2364,7 +2387,7 @@
 // dropdowns requesting expiration date with month pre-populated if month is
 // detected but year is missing.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithMissingExpirationDateYearAndWithMonth) {
   SetUpForEditableExpirationDate();
   // Submit the form with a month value, but not a year value.
@@ -2382,7 +2405,7 @@
 // dropdowns requesting expiration date if month is missing and year is detected
 // but out of the range of dropdown.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithExpirationDateMonthAndWithYearIsOutOfRange) {
   SetUpForEditableExpirationDate();
   // Fill form but with an expiration year ten years in the future which is out
@@ -2400,7 +2423,7 @@
 // dropdowns requesting expiration date if expiration date month is missing and
 // year is detected but passed.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithExpirationDateMonthAndYearExpired) {
   SetUpForEditableExpirationDate();
   // Fill form with a valid month but a passed year.
@@ -2418,7 +2441,7 @@
 // dropdowns requesting expiration date if expiration date is expired but is
 // current year.
 IN_PROC_BROWSER_TEST_F(
-    SaveCardBubbleViewsFullFormBrowserTest,
+    SaveCardBubbleViewsFullFormBrowserTestWithEditableExpirationDate,
     Upload_SubmittingFormWithExpirationDateMonthAndCurrentYear) {
   SetUpForEditableExpirationDate();
   const base::Time kJune2017 = base::Time::FromDoubleT(1497552271);
@@ -2475,10 +2498,9 @@
 
 // Tests StrikeDatabase interaction with the upload save bubble. Ensures that a
 // strike is added if the bubble is ignored.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       StrikeDatabase_Upload_AddStrikeIfBubbleIgnored) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    StrikeDatabase_Upload_AddStrikeIfBubbleIgnored) {
   TestAutofillClock test_clock;
   test_clock.SetNow(base::Time::Now());
 
@@ -2533,10 +2555,9 @@
     (defined(OS_LINUX) && !defined(OS_CHROMEOS))
 // Tests the upload save bubble. Ensures that clicking the [No thanks] button
 // successfully causes a strike to be added.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       StrikeDatabase_Upload_AddStrikeIfBubbleDeclined) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    StrikeDatabase_Upload_AddStrikeIfBubbleDeclined) {
   // Start sync.
   harness_->SetupSync();
 
@@ -2633,10 +2654,9 @@
 // example of declining the prompt three times and ensuring that the
 // offer-to-save bubble does not appear on the fourth try. Then, ensures that no
 // strikes are added if the card already has max strikes.
-IN_PROC_BROWSER_TEST_F(SaveCardBubbleViewsFullFormBrowserTest,
-                       StrikeDatabase_Upload_FullFlowTest) {
-  scoped_feature_list_.InitAndEnableFeature(features::kAutofillUpstream);
-
+IN_PROC_BROWSER_TEST_F(
+    SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+    StrikeDatabase_Upload_FullFlowTest) {
   bool controller_observer_set = false;
 
   // Start sync.
diff --git a/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller_dialog_browsertest.cc b/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller_dialog_browsertest.cc
index 7faaad1..3740fe1 100644
--- a/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller_dialog_browsertest.cc
+++ b/chrome/browser/ui/views/feature_promos/reopen_tab_promo_controller_dialog_browsertest.cc
@@ -18,14 +18,15 @@
 
 class ReopenTabPromoControllerDialogBrowserTest : public DialogBrowserTest {
  public:
-  ReopenTabPromoControllerDialogBrowserTest() = default;
+  ReopenTabPromoControllerDialogBrowserTest() {
+    feature_list_.InitAndEnableFeature(
+        feature_engagement::kIPHReopenTabFeature);
+  }
 
   void SetUpOnMainThread() override {
     promo_controller_ = std::make_unique<ReopenTabPromoController>(
         BrowserView::GetBrowserViewForBrowser(browser()));
     promo_controller_->disable_bubble_timeout_for_test();
-    feature_list_.InitAndEnableFeature(
-        feature_engagement::kIPHReopenTabFeature);
   }
 
   void ShowUi(const std::string& name) override {
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
index 7398f91..300c442 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.cc
@@ -131,6 +131,11 @@
     observer.OnMediaSessionMetadataUpdated();
 }
 
+const std::map<const std::string, MediaNotificationContainerImpl*>&
+MediaDialogView::GetNotificationsForTesting() const {
+  return active_sessions_view_->notifications_for_testing();
+}
+
 MediaDialogView::MediaDialogView(views::View* anchor_view,
                                  MediaToolbarButtonController* controller,
                                  service_manager::Connector* connector)
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view.h b/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
index ab5c816..689abe0 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view.h
@@ -15,6 +15,7 @@
 }  // namespace service_manager
 
 class MediaDialogViewObserver;
+class MediaNotificationContainerImpl;
 class MediaNotificationListView;
 class MediaToolbarButtonController;
 
@@ -49,6 +50,9 @@
 
   void OnMediaSessionMetadataChanged();
 
+  const std::map<const std::string, MediaNotificationContainerImpl*>&
+  GetNotificationsForTesting() const;
+
  private:
   explicit MediaDialogView(views::View* anchor_view,
                            MediaToolbarButtonController* controller,
diff --git a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
index b2ee861..878b9b0 100644
--- a/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
+++ b/chrome/browser/ui/views/global_media_controls/media_dialog_view_interactive_browsertest.cc
@@ -10,11 +10,13 @@
 #include "chrome/browser/ui/global_media_controls/media_toolbar_button_observer.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/global_media_controls/media_dialog_view_observer.h"
+#include "chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h"
 #include "chrome/browser/ui/views/global_media_controls/media_toolbar_button_view.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "components/media_message_center/media_notification_view.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/media_start_stop_observer.h"
 #include "media/base/media_switches.h"
@@ -116,24 +118,21 @@
     run_loop_->Run();
   }
 
+  // Checks the title and artist of each notification in the dialog to see if
+  // |text| is contained anywhere in the dialog.
   bool DialogContainsText(const base::string16& text) {
-    return ViewContainsText(MediaDialogView::GetDialogViewForTesting(), text);
-  }
-
-  // Recursively tries to find a views::Label containing |text| within |view|
-  // and its children.
-  bool ViewContainsText(const views::View* view, const base::string16& text) {
-    if (view->GetClassName() == views::Label::kViewClassName) {
-      const views::Label* label = static_cast<const views::Label*>(view);
-      if (label->GetText().find(text) != std::string::npos)
+    for (const auto notification_pair :
+         MediaDialogView::GetDialogViewForTesting()
+             ->GetNotificationsForTesting()) {
+      const media_message_center::MediaNotificationView* view =
+          notification_pair.second->view_for_testing();
+      if (view->title_label_for_testing()->GetText().find(text) !=
+              std::string::npos ||
+          view->artist_label_for_testing()->GetText().find(text) !=
+              std::string::npos) {
         return true;
+      }
     }
-
-    for (const views::View* child : view->children()) {
-      if (ViewContainsText(child, text))
-        return true;
-    }
-
     return false;
   }
 
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h b/chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h
index bd54b19..7565301 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_container_impl.h
@@ -49,6 +49,10 @@
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
+  media_message_center::MediaNotificationView* view_for_testing() {
+    return view_.get();
+  }
+
  private:
   class DismissButton;
 
diff --git a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h b/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h
index e094acbe..dc7c6fd6 100644
--- a/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h
+++ b/chrome/browser/ui/views/global_media_controls/media_notification_list_view.h
@@ -25,6 +25,11 @@
   void HideNotification(const std::string& id);
   bool empty() { return notifications_.empty(); }
 
+  const std::map<const std::string, MediaNotificationContainerImpl*>&
+  notifications_for_testing() const {
+    return notifications_;
+  }
+
  private:
   std::map<const std::string, MediaNotificationContainerImpl*> notifications_;
 
diff --git a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
index 24dd1d6..ac4e1f3 100644
--- a/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
+++ b/chrome/browser/ui/views/location_bar/icon_label_bubble_view.cc
@@ -436,7 +436,7 @@
   if (!label()->GetVisible()) {
     // Start animation from the current width, otherwise the icon will also be
     // included if visible.
-    grow_animation_starting_width_ = width();
+    grow_animation_starting_width_ = GetVisible() ? width() : 0;
     if (string_id) {
       base::string16 label = l10n_util::GetStringUTF16(string_id.value());
       SetLabel(label);
diff --git a/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc b/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc
index 4478480..b51bfdf 100644
--- a/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc
+++ b/chrome/browser/ui/views/location_bar/location_bar_view_browsertest.cc
@@ -188,11 +188,26 @@
 // and use specified certificate validation results. This allows tests to mock
 // Extended Validation (EV) certificate connections.
 const char kMockSecureHostname[] = "example-secure.test";
-const GURL kMockSecureURL = GURL("https://example-secure.test");
 
-class SecurityIndicatorTest : public InProcessBrowserTest {
+struct SecurityIndicatorTestParams {
+  bool is_enabled;
+  bool use_secure_url;
+  net::CertStatus cert_status;
+  security_state::SecurityLevel security_level;
+  bool should_show_text;
+  base::string16 indicator_text;
+};
+
+class SecurityIndicatorTest
+    : public InProcessBrowserTest,
+      public ::testing::WithParamInterface<SecurityIndicatorTestParams> {
  public:
-  SecurityIndicatorTest() : InProcessBrowserTest(), cert_(nullptr) {}
+  SecurityIndicatorTest() : InProcessBrowserTest(), cert_(nullptr) {
+    if (GetParam().is_enabled)
+      feature_list_.InitAndEnableFeature(omnibox::kSimplifyHttpsIndicator);
+    else
+      feature_list_.InitAndDisableFeature(omnibox::kSimplifyHttpsIndicator);
+  }
 
   void SetUpInProcessBrowserTestFixture() override {
     cert_ =
@@ -238,6 +253,8 @@
   }
 
  private:
+  base::test::ScopedFeatureList feature_list_;
+
   scoped_refptr<net::X509Certificate> cert_;
 
   std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
@@ -247,30 +264,10 @@
 
 // Check that the security indicator text is correctly set for the various
 // variations of the Security UI Study (https://crbug.com/803501).
-IN_PROC_BROWSER_TEST_F(SecurityIndicatorTest, CheckIndicatorText) {
+IN_PROC_BROWSER_TEST_P(SecurityIndicatorTest, CheckIndicatorText) {
+  const GURL kMockSecureURL = GURL("https://example-secure.test");
   const GURL kMockNonsecureURL =
       embedded_test_server()->GetURL("example.test", "/");
-  const base::string16 kEvString = base::ASCIIToUTF16("Test CA [US]");
-  const base::string16 kEmptyString = base::string16();
-
-  const struct {
-    bool is_enabled;
-    GURL url;
-    net::CertStatus cert_status;
-    security_state::SecurityLevel security_level;
-    bool should_show_text;
-    base::string16 indicator_text;
-  } cases[]{
-      // Disabled (show EV UI in omnibox)
-      {false, kMockSecureURL, net::CERT_STATUS_IS_EV, security_state::EV_SECURE,
-       true, kEvString},
-      {false, kMockSecureURL, 0, security_state::SECURE, false, kEmptyString},
-      {false, kMockNonsecureURL, 0, security_state::NONE, false, kEmptyString},
-      // Default (lock-only in omnibox)
-      {true, kMockSecureURL, net::CERT_STATUS_IS_EV, security_state::EV_SECURE,
-       false, kEmptyString},
-      {true, kMockSecureURL, 0, security_state::SECURE, false, kEmptyString},
-      {true, kMockNonsecureURL, 0, security_state::NONE, false, kEmptyString}};
 
   content::WebContents* tab =
       browser()->tab_strip_model()->GetActiveWebContents();
@@ -279,22 +276,36 @@
   ASSERT_TRUE(helper);
   LocationBarView* location_bar_view = GetLocationBarView();
 
-  for (const auto& c : cases) {
-    base::test::ScopedFeatureList scoped_feature_list;
-    if (c.is_enabled) {
-      scoped_feature_list.InitAndEnableFeature(
-          omnibox::kSimplifyHttpsIndicator);
-    } else {
-      scoped_feature_list.InitAndDisableFeature(
-          omnibox::kSimplifyHttpsIndicator);
-    }
-    SetUpInterceptor(c.cert_status);
-    ui_test_utils::NavigateToURL(browser(), c.url);
-    EXPECT_EQ(c.security_level, helper->GetSecurityLevel());
-    EXPECT_EQ(c.should_show_text,
-              location_bar_view->location_icon_view()->ShouldShowLabel());
-    EXPECT_EQ(c.indicator_text,
-              location_bar_view->location_icon_view()->GetText());
-    ResetInterceptor();
-  }
+  auto c = GetParam();
+  SetUpInterceptor(c.cert_status);
+  ui_test_utils::NavigateToURL(
+      browser(), c.use_secure_url ? kMockSecureURL : kMockNonsecureURL);
+  EXPECT_EQ(c.security_level, helper->GetSecurityLevel());
+  EXPECT_EQ(c.should_show_text,
+            location_bar_view->location_icon_view()->ShouldShowLabel());
+  EXPECT_EQ(c.indicator_text,
+            location_bar_view->location_icon_view()->GetText());
+  ResetInterceptor();
 }
+
+const base::string16 kEvString = base::ASCIIToUTF16("Test CA [US]");
+const base::string16 kEmptyString = base::string16();
+INSTANTIATE_TEST_SUITE_P(
+    /* no prefix */,
+    SecurityIndicatorTest,
+    ::testing::Values(
+        // Disabled (show EV UI in omnibox)
+        SecurityIndicatorTestParams{false, true, net::CERT_STATUS_IS_EV,
+                                    security_state::EV_SECURE, true, kEvString},
+        SecurityIndicatorTestParams{false, true, 0, security_state::SECURE,
+                                    false, kEmptyString},
+        SecurityIndicatorTestParams{false, false, 0, security_state::NONE,
+                                    false, kEmptyString},
+        // Default (lock-only in omnibox)
+        SecurityIndicatorTestParams{true, true, net::CERT_STATUS_IS_EV,
+                                    security_state::EV_SECURE, false,
+                                    kEmptyString},
+        SecurityIndicatorTestParams{true, true, 0, security_state::SECURE,
+                                    false, kEmptyString},
+        SecurityIndicatorTestParams{true, false, 0, security_state::NONE, false,
+                                    kEmptyString}));
diff --git a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
index a5532da2..21d8730 100644
--- a/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
+++ b/chrome/browser/ui/views/page_info/safety_tip_page_info_bubble_view_browsertest.cc
@@ -32,6 +32,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/safe_browsing/db/v4_protocol_manager_util.h"
+#include "components/security_state/core/features.h"
 #include "components/security_state/core/security_state.h"
 #include "components/strings/grit/components_strings.h"
 #include "content/public/browser/web_contents.h"
@@ -159,18 +160,19 @@
   void SetUp() override {
     switch (ui_status()) {
       case UIStatus::kDisabled:
-        feature_list_.InitAndDisableFeature(features::kSafetyTipUI);
+        feature_list_.InitAndDisableFeature(
+            security_state::features::kSafetyTipUI);
         break;
       case UIStatus::kEnabled:
         feature_list_.InitWithFeaturesAndParameters(
-            {{features::kSafetyTipUI, {}},
+            {{security_state::features::kSafetyTipUI, {}},
              {features::kLookalikeUrlNavigationSuggestionsUI,
               {{"topsites", "true"}}}},
             {});
         break;
       case UIStatus::kEnabledWithEditDistance:
         feature_list_.InitWithFeaturesAndParameters(
-            {{features::kSafetyTipUI,
+            {{security_state::features::kSafetyTipUI,
               {{"editdistance", "true"},
                {"editdistance_siteengagement", "true"}}},
              {features::kLookalikeUrlNavigationSuggestionsUI,
@@ -178,6 +180,7 @@
             {});
     }
 
+    InitializeSafetyTipConfig();
     InProcessBrowserTest::SetUp();
   }
 
@@ -292,6 +295,25 @@
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(browser()));
 }
 
+// Ensure explicitly-allowed sites don't get blocked when the site is otherwise
+// blocked server-side.
+IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
+                       NoShowOnAllowlist) {
+  auto kNavigatedUrl = GetURL("site1.com");
+
+  // Ensure a Safety Tip is triggered initially...
+  SetSafetyTipBadRepPatterns({"site1.com/"});
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_TRUE(IsUIShowingIfEnabled());
+  ASSERT_NO_FATAL_FAILURE(CheckPageInfoShowsSafetyTipInfo(browser()));
+
+  // ...but suppressed by the allowlist.
+  SetSafetyTipAllowlistPatterns({"site1.com/"});
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_FALSE(IsUIShowing());
+  ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
+}
+
 // After the user clicks 'leave site', the user should end up on a safe domain.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
                        LeaveSiteLeavesSite) {
@@ -435,6 +457,25 @@
   ASSERT_NO_FATAL_FAILURE(CheckPageInfoDoesNotShowSafetyTipInfo(browser()));
 }
 
+// Tests that Safety Tips don't trigger on lookalike domains that are explicitly
+// allowed by the allowlist.
+IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
+                       NoTriggersOnLookalikeAllowlist) {
+  // This domain is a lookalike of a top domain not in the top 500.
+  const GURL kNavigatedUrl = GetURL("googlé.sk");
+
+  // Ensure a Safety Tip is triggered initially...
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_TRUE(IsUIShowingIfEnabled());
+
+  // ...but suppressed by the allowlist.
+  SetSafetyTipAllowlistPatterns({"xn--googl-fsa.sk/"});
+  SetEngagementScore(browser(), kNavigatedUrl, kLowEngagement);
+  NavigateToURL(browser(), kNavigatedUrl, WindowOpenDisposition::CURRENT_TAB);
+  EXPECT_FALSE(IsUIShowing());
+}
+
 // Tests that Safety Tips trigger (or not) on lookalike domains with edit
 // distance when enabled, and not otherwise.
 IN_PROC_BROWSER_TEST_P(SafetyTipPageInfoBubbleViewBrowserTest,
diff --git a/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc b/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
index 68d5006c..0864a37 100644
--- a/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
+++ b/chrome/browser/ui/views/payments/credit_card_editor_view_controller_browsertest.cc
@@ -225,11 +225,20 @@
   EXPECT_TRUE(save_button->GetEnabled());
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestCreditCardEditorTest, EditingMaskedCard) {
-  // Masked cards are from Google Pay.
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kReturnGooglePayInBasicCard);
+class PaymentRequestCreditCardEditorTestWithGooglePayEnabled
+    : public PaymentRequestCreditCardEditorTest {
+ public:
+  PaymentRequestCreditCardEditorTestWithGooglePayEnabled() {
+    // Masked cards are from Google Pay.
+    feature_list_.InitAndEnableFeature(features::kReturnGooglePayInBasicCard);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestCreditCardEditorTestWithGooglePayEnabled,
+                       EditingMaskedCard) {
   NavigateTo("/payment_request_no_shipping_test.html");
   autofill::TestAutofillClock test_clock;
   test_clock.SetNow(kJune2017);
@@ -309,12 +318,8 @@
   EXPECT_EQ(additional_profile.guid(), selected->billing_address_id());
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestCreditCardEditorTest,
+IN_PROC_BROWSER_TEST_F(PaymentRequestCreditCardEditorTestWithGooglePayEnabled,
                        EditingMaskedCard_ClickOnPaymentsLink) {
-  // Masked cards are from Google Pay.
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kReturnGooglePayInBasicCard);
-
   NavigateTo("/payment_request_no_shipping_test.html");
   autofill::TestAutofillClock test_clock;
   test_clock.SetNow(kJune2017);
diff --git a/chrome/browser/ui/views/payments/payment_request_journey_logger_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_journey_logger_browsertest.cc
index ca498db..d928e02 100644
--- a/chrome/browser/ui/views/payments/payment_request_journey_logger_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_journey_logger_browsertest.cc
@@ -194,14 +194,24 @@
   EXPECT_FALSE(buckets[0].min & JourneyLogger::EVENT_COULD_NOT_SHOW);
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestJourneyLoggerMultipleShowTest,
-                       StartNewRequest) {
+class PaymentRequestJourneyLoggerMultipleShowTestWithPaymentHandlersEnabled
+    : public PaymentRequestJourneyLoggerMultipleShowTest {
+ public:
+  PaymentRequestJourneyLoggerMultipleShowTestWithPaymentHandlersEnabled() {
+    // Enable payment handlers, also known as service worker payment apps. The
+    // rest of the test is identical to
+    // "StartNewRequestWithoutPaymentAppsFeature" testcase above.
+    feature_list_.InitAndEnableFeature(features::kServiceWorkerPaymentApps);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    PaymentRequestJourneyLoggerMultipleShowTestWithPaymentHandlersEnabled,
+    StartNewRequest) {
   NavigateTo("/payment_request_multiple_show_test.html");
-  // Enable payment handlers, also known as service worker payment apps. The
-  // rest of the test is identical to "StartNewRequestWithoutPaymentAppsFeature"
-  // testcase above.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndEnableFeature(features::kServiceWorkerPaymentApps);
 
   base::HistogramTester histogram_tester;
 
@@ -300,14 +310,23 @@
   EXPECT_TRUE(buckets[1].min & JourneyLogger::EVENT_COULD_NOT_SHOW);
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestJourneyLoggerMultipleShowTest,
-                       StartNewRequestWithoutPaymentAppsFeature) {
+class PaymentRequestJourneyLoggerMultipleShowTestWithPaymentHandlersDisabled
+    : public PaymentRequestJourneyLoggerMultipleShowTest {
+ public:
+  PaymentRequestJourneyLoggerMultipleShowTestWithPaymentHandlersDisabled() {
+    // Disable payment handlers, also known as service worker payment apps. The
+    // rest of the test is identical to "StartNewRequest" testcase above.
+    feature_list_.InitAndDisableFeature(features::kServiceWorkerPaymentApps);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(
+    PaymentRequestJourneyLoggerMultipleShowTestWithPaymentHandlersDisabled,
+    StartNewRequestWithoutPaymentAppsFeature) {
   NavigateTo("/payment_request_multiple_show_test.html");
-  // Disable payment handlers, also known as service worker payment apps. The
-  // rest of the test is identical to "StartNewRequest" testcase above.
-  base::test::ScopedFeatureList scoped_feature_list;
-  scoped_feature_list.InitAndDisableFeature(
-      features::kServiceWorkerPaymentApps);
 
   base::HistogramTester histogram_tester;
 
diff --git a/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc b/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc
index 960bd94b6..1758dc1c 100644
--- a/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc
+++ b/chrome/browser/ui/views/payments/payment_request_payment_app_browsertest.cc
@@ -469,15 +469,25 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest, SkipUIEnabledWithBobPay) {
+class PaymentRequestPaymentAppTestWithPaymentHandlersAndUiSkip
+    : public PaymentRequestPaymentAppTest {
+ public:
+  PaymentRequestPaymentAppTestWithPaymentHandlersAndUiSkip() {
+    feature_list_.InitWithFeatures(
+        {
+            payments::features::kWebPaymentsSingleAppUiSkip,
+            ::features::kServiceWorkerPaymentApps,
+        },
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTestWithPaymentHandlersAndUiSkip,
+                       SkipUIEnabledWithBobPay) {
   base::HistogramTester histogram_tester;
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatures(
-      {
-          payments::features::kWebPaymentsSingleAppUiSkip,
-          ::features::kServiceWorkerPaymentApps,
-      },
-      {});
   InstallBobPayForMethod("https://bobpay.com");
 
   {
@@ -508,15 +518,8 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest,
+IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTestWithPaymentHandlersAndUiSkip,
                        SkipUIDisabledWithMultipleAcceptedMethods) {
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatures(
-      {
-          payments::features::kWebPaymentsSingleAppUiSkip,
-          ::features::kServiceWorkerPaymentApps,
-      },
-      {});
   InstallBobPayForMethod("https://bobpay.com");
 
   {
@@ -536,15 +539,8 @@
   }
 }
 
-IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTest,
+IN_PROC_BROWSER_TEST_F(PaymentRequestPaymentAppTestWithPaymentHandlersAndUiSkip,
                        SkipUIDisabledWithRequestedPayerEmail) {
-  base::test::ScopedFeatureList features;
-  features.InitWithFeatures(
-      {
-          payments::features::kWebPaymentsSingleAppUiSkip,
-          ::features::kServiceWorkerPaymentApps,
-      },
-      {});
   InstallBobPayForMethod("https://bobpay.com");
   autofill::AutofillProfile profile(autofill::test::GetFullProfile());
   AddAutofillProfile(profile);
diff --git a/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view_browsertest.cc b/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view_browsertest.cc
index 29264e8..2c571f6 100644
--- a/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view_browsertest.cc
+++ b/chrome/browser/ui/views/plugin_vm/plugin_vm_launcher_view_browsertest.cc
@@ -127,7 +127,6 @@
   }
 
   void AllowPluginVm() {
-    EnablePluginVmFeature();
     EnterpriseEnrollDevice();
     SetUserWithAffiliation();
     SetPluginVmDevicePolicies();
@@ -175,7 +174,6 @@
 
   chromeos::ScopedTestingCrosSettings scoped_testing_cros_settings_;
   chromeos::ScopedStubInstallAttributes scoped_stub_install_attributes_;
-  base::test::ScopedFeatureList scoped_feature_list_;
 
   std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
   PluginVmLauncherViewForTesting* view_;
@@ -184,10 +182,6 @@
   chromeos::FakeConciergeClient* fake_concierge_client_;
 
  private:
-  void EnablePluginVmFeature() {
-    scoped_feature_list_.InitAndEnableFeature(features::kPluginVm);
-  }
-
   void EnterpriseEnrollDevice() {
     scoped_stub_install_attributes_.Get()->SetCloudManaged("example.com",
                                                            "device_id");
@@ -215,12 +209,23 @@
   DISALLOW_COPY_AND_ASSIGN(PluginVmLauncherViewBrowserTest);
 };
 
+class PluginVmLauncherViewBrowserTestWithFeatureEnabled
+    : public PluginVmLauncherViewBrowserTest {
+ public:
+  PluginVmLauncherViewBrowserTestWithFeatureEnabled() {
+    feature_list_.InitAndEnableFeature(features::kPluginVm);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
 // Test the dialog is actually can be launched.
 IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTest, InvokeUi_default) {
   ShowAndVerifyUi();
 }
 
-IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTest,
+IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTestWithFeatureEnabled,
                        SetupShouldFinishSuccessfully) {
   AllowPluginVm();
   plugin_vm::SetupConciergeForSuccessfulDiskImageImport(fake_concierge_client_);
@@ -237,7 +242,7 @@
       plugin_vm::PluginVmSetupResult::kSuccess, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTest,
+IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTestWithFeatureEnabled,
                        SetupShouldFailAsHashesDoNotMatch) {
   AllowPluginVm();
   // Reset PluginVmImage hash to non-matching.
@@ -256,7 +261,7 @@
       plugin_vm::PluginVmSetupResult::kErrorDownloadingPluginVmImage, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTest,
+IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTestWithFeatureEnabled,
                        SetupShouldFailAsImportingFails) {
   AllowPluginVm();
   SetPluginVmImagePref(embedded_test_server()->GetURL(kJpgFile).spec(),
@@ -274,7 +279,7 @@
       plugin_vm::PluginVmSetupResult::kErrorImportingPluginVmImage, 1);
 }
 
-IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTest,
+IN_PROC_BROWSER_TEST_F(PluginVmLauncherViewBrowserTestWithFeatureEnabled,
                        CouldRetryAfterFailedSetup) {
   AllowPluginVm();
   // Reset PluginVmImage hash to non-matching.
diff --git a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
index 3533fc5..23073be 100644
--- a/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
+++ b/chrome/browser/ui/views/profiles/profile_menu_view_browsertest.cc
@@ -496,11 +496,20 @@
   ShowAndVerifyUi();
 }
 
+class ProfileMenuViewExtensionsParamTestWithScopedAccountConsistency
+    : public ProfileMenuViewExtensionsParamTest {
+ public:
+  ProfileMenuViewExtensionsParamTestWithScopedAccountConsistency() = default;
+
+ private:
+  ScopedAccountConsistencyDice scoped_dice_;
+};
+
 // Shows the |ProfileMenuView| during a Guest browsing session when the DICE
 // flag is enabled.
-IN_PROC_BROWSER_TEST_P(ProfileMenuViewExtensionsParamTest,
-                       InvokeUi_DiceGuest) {
-  ScopedAccountConsistencyDice scoped_dice;
+IN_PROC_BROWSER_TEST_P(
+    ProfileMenuViewExtensionsParamTestWithScopedAccountConsistency,
+    InvokeUi_DiceGuest) {
   ShowAndVerifyUi();
 }
 
@@ -525,6 +534,15 @@
   ShowAndVerifyUi();
 }
 
+class ProfileMenuViewExtensionsTestWithScopedAccountConsistency
+    : public ProfileMenuViewExtensionsTest {
+ public:
+  ProfileMenuViewExtensionsTestWithScopedAccountConsistency() = default;
+
+ private:
+  ScopedAccountConsistencyDice scoped_dice_;
+};
+
 // Open the profile chooser to increment the Dice sign-in promo show counter
 // below the threshold.
 // TODO(https://crbug.com/862573): Re-enable when no longer failing when
@@ -536,9 +554,9 @@
 #define MAYBE_IncrementDiceSigninPromoShowCounter \
   IncrementDiceSigninPromoShowCounter
 #endif
-IN_PROC_BROWSER_TEST_F(ProfileMenuViewExtensionsTest,
-                       MAYBE_IncrementDiceSigninPromoShowCounter) {
-  ScopedAccountConsistencyDice scoped_dice;
+IN_PROC_BROWSER_TEST_F(
+    ProfileMenuViewExtensionsTestWithScopedAccountConsistency,
+    MAYBE_IncrementDiceSigninPromoShowCounter) {
   browser()->profile()->GetPrefs()->SetInteger(
       prefs::kDiceSigninUserMenuPromoCount, 7);
   ASSERT_NO_FATAL_FAILURE(OpenProfileMenuView(browser()));
@@ -556,9 +574,9 @@
 #define MAYBE_DiceSigninPromoWithoutIllustration \
   DiceSigninPromoWithoutIllustration
 #endif
-IN_PROC_BROWSER_TEST_F(ProfileMenuViewExtensionsTest,
-                       MAYBE_DiceSigninPromoWithoutIllustration) {
-  ScopedAccountConsistencyDice scoped_dice;
+IN_PROC_BROWSER_TEST_F(
+    ProfileMenuViewExtensionsTestWithScopedAccountConsistency,
+    MAYBE_DiceSigninPromoWithoutIllustration) {
   browser()->profile()->GetPrefs()->SetInteger(
       prefs::kDiceSigninUserMenuPromoCount, 10);
   ASSERT_NO_FATAL_FAILURE(OpenProfileMenuView(browser()));
@@ -894,12 +912,12 @@
     ProfileMenuClickTest_WithUnconsentedPrimaryAccount::kOrderedActionableItems
         [];
 
+// TODO(crbug.com/1012167): Flaky.
 IN_PROC_BROWSER_TEST_P(ProfileMenuClickTest_WithUnconsentedPrimaryAccount,
-                       SetupAndRunTest) {
+                       DISABLED_SetupAndRunTest) {
   signin::MakeAccountAvailableWithCookies(identity_manager(),
                                           &test_url_loader_factory_,
                                           "user@example.com", "gaia_id");
-  ASSERT_TRUE(sync_harness()->AwaitSyncTransportActive());
   // Check that the setup was successful.
   ASSERT_FALSE(identity_manager()->HasPrimaryAccount());
   ASSERT_TRUE(identity_manager()->HasUnconsentedPrimaryAccount());
diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
index c61e6e5..c9b21ad 100644
--- a/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/views/tabs/tab_drag_controller_interactive_uitest.cc
@@ -648,8 +648,6 @@
 
   Browser* browser() const { return InProcessBrowserTest::browser(); }
 
-  base::test::ScopedFeatureList scoped_feature_list_;
-
  private:
 #if defined(OS_CHROMEOS)
   // The root window for the event generator.
@@ -659,6 +657,17 @@
   DISALLOW_COPY_AND_ASSIGN(DetachToBrowserTabDragControllerTest);
 };
 
+class DetachToBrowserTabDragControllerTestWithTabGroupsEnabled
+    : public DetachToBrowserTabDragControllerTest {
+ public:
+  DetachToBrowserTabDragControllerTestWithTabGroupsEnabled() {
+    scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
 // Creates a browser with two tabs, drags the second to the first.
 IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest, DragInSameWindow) {
   AddTabsAndResetBrowser(browser(), 1);
@@ -687,10 +696,8 @@
 // removal of the dragged tab from its group. Then dragging the second tab to
 // after the third tab will also result in a removal of that dragged tab from
 // its group.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragRightToUngroupTab) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -730,10 +737,8 @@
 // removal of the dragged tab from its group. Then dragging the third tab to
 // before the second tab will also result in a removal of that dragged tab from
 // its group.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragLeftToUngroupTab) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -772,9 +777,8 @@
 // Creates a browser with four tabs. The first three belong in the same Tab
 // Group. Dragging tabs in a tab group within the defined threshold does not
 // modify the group of the dragged tab.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragTabWithinGroupDoesNotModifyGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -808,10 +812,8 @@
 
 // Creates a browser with four tabs. The first tab is in a Tab Group. Dragging
 // the only tab in that group will remove the group.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragOnlyTabInGroupRemovesGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -839,10 +841,8 @@
 // tab over one to the left will result in the tab joining Tab Group 1. Then
 // dragging the fourth tab over one to the left will result in the tab joining
 // Tab Group 1 as well.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragSingleTabLeftIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -895,10 +895,8 @@
 // over one to the right will result in the tab joining Tab Group 1. Then
 // dragging the first tab over one to the right will result in the tab joining
 // Tab Group 1 as well.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragSingleTabRightIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -943,10 +941,8 @@
 // tabs joining the same group as the tab in the second position. Then dragging
 // the tabs over two to the right will result in the tabs joining the same group
 // as the last tab.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragMultipleTabsRightIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -994,10 +990,8 @@
 // Creates a browser with four tabs each in its own group. Selecting and
 // dragging the second and fourth tabs left at the fourth tab will result in the
 // tabs joining the same group as the tab in the third position.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragMultipleTabsLeftIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -1032,10 +1026,8 @@
 // Dragging the third tab over one to the left will result in the tab joining
 // Tab Group 1. While this drag is still in session, pressing escape will revert
 // group of the tab to before the drag session started.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        RevertDragSingleTabIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -1065,10 +1057,8 @@
 // will result in the tab joining Tab Group 1. While this drag is still in
 // session, pressing escape will revert group of the tab to before the drag
 // session started.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        RevertDragSingleTabGroupIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -1848,10 +1838,8 @@
 // two tabs in another group {group1}. Dragging the two tabs in the first
 // browser into the middle of the second browser will insert the two dragged
 // tabs into the {group1}} after the first tab.
-IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
+IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
                        DragWindowIntoGroup) {
-  scoped_feature_list_.InitAndEnableFeature(features::kTabGroups);
-
   TabStrip* tab_strip = GetTabStripForBrowser(browser());
   TabStripModel* model = browser()->tab_strip_model();
 
@@ -3686,6 +3674,10 @@
 INSTANTIATE_TEST_SUITE_P(TabDragging,
                          DetachToBrowserTabDragControllerTest,
                          ::testing::Values("mouse", "touch"));
+INSTANTIATE_TEST_SUITE_P(
+    TabDragging,
+    DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
+    ::testing::Values("mouse", "touch"));
 INSTANTIATE_TEST_SUITE_P(TabDragging,
                          DetachToBrowserInSeparateDisplayTabDragControllerTest,
                          ::testing::Values("mouse"));
@@ -3703,4 +3695,8 @@
 INSTANTIATE_TEST_SUITE_P(TabDragging,
                          DetachToBrowserTabDragControllerTest,
                          ::testing::Values("mouse"));
+INSTANTIATE_TEST_SUITE_P(
+    TabDragging,
+    DetachToBrowserTabDragControllerTestWithTabGroupsEnabled,
+    ::testing::Values("mouse"));
 #endif
diff --git a/chrome/browser/ui/views/translate/translate_language_browsertest.cc b/chrome/browser/ui/views/translate/translate_language_browsertest.cc
index 0590946..6de8009d 100644
--- a/chrome/browser/ui/views/translate/translate_language_browsertest.cc
+++ b/chrome/browser/ui/views/translate/translate_language_browsertest.cc
@@ -279,10 +279,19 @@
   EXPECT_EQ("fr", GetLanguageState().current_language());
 }
 
-IN_PROC_BROWSER_TEST_F(TranslateLanguageBrowserTest, RecentTargetLanguage) {
-  base::test::ScopedFeatureList enable_feature;
-  enable_feature.InitAndEnableFeature(kTranslateRecentTarget);
+class TranslateLanguageBrowserTestWithTranslateRecentTarget
+    : public TranslateLanguageBrowserTest {
+ public:
+  TranslateLanguageBrowserTestWithTranslateRecentTarget() {
+    feature_list_.InitAndEnableFeature(kTranslateRecentTarget);
+  }
 
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(TranslateLanguageBrowserTestWithTranslateRecentTarget,
+                       RecentTargetLanguage) {
   InitInIncognitoMode(false);
 
   // Before browsing: set auto translate from French to Chinese.
diff --git a/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc b/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc
index 41f5e3c..6807af9 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_manager_impl_browsertest.cc
@@ -201,8 +201,11 @@
 
 // Tests that the Settings app migrates the launcher and app list details from
 // the Settings internal app.
+//
+// TODO(https://crbug.com/1012967): Find a way to implement this that does not
+// depend on unsupported behavior of the FeatureList API.
 IN_PROC_BROWSER_TEST_F(WebAppUiManagerMigrationBrowserTest,
-                       SettingsSystemWebAppMigration) {
+                       DISABLED_SettingsSystemWebAppMigration) {
   base::test::ScopedFeatureList scoped_feature_list;
   scoped_feature_list.InitAndEnableFeature(features::kSystemWebApps);
 
diff --git a/chrome/browser/ui/webui/chromeos/system_web_dialog_browsertest.cc b/chrome/browser/ui/webui/chromeos/system_web_dialog_browsertest.cc
index 55649387..c23cac4 100644
--- a/chrome/browser/ui/webui/chromeos/system_web_dialog_browsertest.cc
+++ b/chrome/browser/ui/webui/chromeos/system_web_dialog_browsertest.cc
@@ -99,14 +99,21 @@
   // https://crbug.com/855344.
 }
 
-IN_PROC_BROWSER_TEST_F(SystemWebDialogTest, FontSize) {
+class SystemWebDialogTestWithSplitSettings : public SystemWebDialogTest {
+ public:
+  SystemWebDialogTestWithSplitSettings() {
+    feature_list_.InitAndEnableFeature(chromeos::features::kSplitSettings);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(SystemWebDialogTestWithSplitSettings, FontSize) {
   const content::WebPreferences kDefaultPrefs;
   const int kDefaultFontSize = kDefaultPrefs.default_font_size;
   const int kDefaultFixedFontSize = kDefaultPrefs.default_fixed_font_size;
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kSplitSettings);
-
   // Set the browser font sizes to non-default values.
   PrefService* profile_prefs = browser()->profile()->GetPrefs();
   profile_prefs->SetInteger(prefs::kWebKitDefaultFontSize,
@@ -127,10 +134,7 @@
   EXPECT_EQ(kDefaultFixedFontSize, dialog_prefs.default_fixed_font_size);
 }
 
-IN_PROC_BROWSER_TEST_F(SystemWebDialogTest, PageZoom) {
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kSplitSettings);
-
+IN_PROC_BROWSER_TEST_F(SystemWebDialogTestWithSplitSettings, PageZoom) {
   // Set the default browser page zoom to 150%.
   double level = blink::PageZoomFactorToZoomLevel(1.5);
   browser()->profile()->GetZoomLevelPrefs()->SetDefaultZoomLevelPref(level);
diff --git a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
index b9660fb..86da30a 100644
--- a/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/cups_printers_handler.cc
@@ -966,6 +966,7 @@
 
 void CupsPrintersHandler::HandleStartDiscovery(const base::ListValue* args) {
   PRINTER_LOG(DEBUG) << "Start printer discovery";
+  AllowJavascript();
   discovery_active_ = true;
   OnPrintersChanged(PrinterClass::kAutomatic,
                     printers_manager_->GetPrinters(PrinterClass::kAutomatic));
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc b/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc
index b4dbace..51865b80 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui.cc
@@ -11,6 +11,7 @@
 
 #include "base/base64.h"
 #include "base/bind.h"
+#include "base/strings/string_piece.h"
 #include "base/values.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/profiles/profile.h"
@@ -45,8 +46,6 @@
 
 // Writes bytes to a std::vector that can be fetched. This is used to record the
 // output of skia image encoding.
-//
-// TODO(crbug.com/991393): remove this when we no longer transcode images here.
 class BufferWStream : public SkWStream {
  public:
   BufferWStream() = default;
@@ -68,26 +67,23 @@
   std::vector<unsigned char> result_;
 };
 
-std::string EncodeImage(gfx::ImageSkia image,
-                        SkEncodedImageFormat format,
-                        float scale_factor) {
+std::string MakeDataURIForImage(base::span<const uint8_t> image_data,
+                                base::StringPiece mime_subtype) {
+  std::string result = "data:image/";
+  result.append(mime_subtype.begin(), mime_subtype.end());
+  result += ";base64,";
+  result += base::Base64Encode(image_data);
+  return result;
+}
+
+std::string EncodePNGAndMakeDataURI(gfx::ImageSkia image, float scale_factor) {
   const SkBitmap& bitmap = image.GetRepresentation(scale_factor).GetBitmap();
   BufferWStream stream;
-  const bool encoding_succeeded = SkEncodeImage(&stream, bitmap, format, 100);
+  const bool encoding_succeeded =
+      SkEncodeImage(&stream, bitmap, SkEncodedImageFormat::kPNG, 100);
   DCHECK(encoding_succeeded);
-  const std::vector<unsigned char> image_data = stream.GetBuffer();
-
-  std::string mime_subtype;
-  if (format == SkEncodedImageFormat::kJPEG) {
-    mime_subtype = "jpeg";
-  } else if (format == SkEncodedImageFormat::kPNG) {
-    mime_subtype = "png";
-  } else {
-    NOTREACHED();
-  }
-
-  return "data:image/" + mime_subtype + ";base64," +
-         base::Base64Encode(base::as_bytes(base::make_span(image_data)));
+  return MakeDataURIForImage(
+      base::as_bytes(base::make_span(stream.GetBuffer())), "png");
 }
 
 class WebUITabContextMenu : public ui::SimpleMenuModel::Delegate,
@@ -224,10 +220,9 @@
     tab_data.SetString("url", tab_renderer_data.visible_url.GetContent());
 
     if (!tab_renderer_data.favicon.isNull()) {
-      tab_data.SetString(
-          "favIconUrl",
-          EncodeImage(tab_renderer_data.favicon, SkEncodedImageFormat::kPNG,
-                      web_ui()->GetDeviceScaleFactor()));
+      tab_data.SetString("favIconUrl", EncodePNGAndMakeDataURI(
+                                           tab_renderer_data.favicon,
+                                           web_ui()->GetDeviceScaleFactor()));
     }
 
     tab_data.SetInteger("networkState",
@@ -349,24 +344,15 @@
 
   // Callback passed to |thumbnail_tracker_|. Called when a tab's thumbnail
   // changes, or when we start watching the tab.
-  void HandleThumbnailUpdate(content::WebContents* tab, gfx::ImageSkia image) {
+  void HandleThumbnailUpdate(content::WebContents* tab,
+                             ThumbnailTracker::CompressedThumbnailData image) {
     // Send base-64 encoded image to JS side.
-    //
-    // TODO(crbug.com/991393): streamline the process from tab capture to
-    // sending the image. Currently, a frame is captured, sent to
-    // ThumbnailImage, encoded to JPEG (w/ a copy), decoded to a raw bitmap
-    // (another copy), and sent to observers. Here, we then re-encode the image
-    // to a JPEG (another copy), encode to base64 (another copy), append the
-    // base64 header (another copy), and send it (another copy). This is 6
-    // copies of essentially the same image, and it is de-encoded and re-encoded
-    // to the same format. We can reduce the number of copies and avoid the
-    // redundant encoding.
-    std::string encoded_image = EncodeImage(image, SkEncodedImageFormat::kJPEG,
-                                            web_ui()->GetDeviceScaleFactor());
+    std::string data_uri =
+        MakeDataURIForImage(base::make_span(image->data), "jpeg");
 
     const int tab_id = extensions::ExtensionTabUtil::GetTabId(tab);
     FireWebUIListener("tab-thumbnail-updated", base::Value(tab_id),
-                      base::Value(encoded_image));
+                      base::Value(data_uri));
   }
 
   Browser* const browser_;
diff --git a/chrome/browser/ui/webui/tab_strip/tab_strip_ui.h b/chrome/browser/ui/webui/tab_strip/tab_strip_ui.h
index 02c375f..e7b7818 100644
--- a/chrome/browser/ui/webui/tab_strip/tab_strip_ui.h
+++ b/chrome/browser/ui/webui/tab_strip/tab_strip_ui.h
@@ -45,7 +45,8 @@
   void Initialize(Browser* browser, Embedder* embedder);
 
  private:
-  void HandleThumbnailUpdate(int extension_tab_id, gfx::ImageSkia image);
+  void HandleThumbnailUpdate(int extension_tab_id,
+                             ThumbnailTracker::CompressedThumbnailData image);
 
   DISALLOW_COPY_AND_ASSIGN(TabStripUI);
 };
diff --git a/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.cc b/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.cc
index 262144c..4ec05c2 100644
--- a/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.cc
+++ b/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.cc
@@ -10,7 +10,6 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
 #include "chrome/browser/ui/thumbnails/thumbnail_tab_helper.h"
 #include "content/public/browser/web_contents_observer.h"
 
@@ -28,7 +27,7 @@
 
   void RequestThumbnail() {
     if (thumbnail_)
-      thumbnail_->RequestThumbnailImage();
+      thumbnail_->RequestCompressedThumbnailData();
   }
 
   // content::WebContents:
@@ -45,7 +44,8 @@
   }
 
   // ThumbnailImage::Observer:
-  void OnThumbnailImageAvailable(gfx::ImageSkia thumbnail_image) override {
+  void OnCompressedThumbnailDataAvailable(
+      CompressedThumbnailData thumbnail_image) override {
     parent_->ThumbnailUpdated(web_contents(), thumbnail_image);
   }
 
@@ -79,7 +79,7 @@
 }
 
 void ThumbnailTracker::ThumbnailUpdated(content::WebContents* contents,
-                                        gfx::ImageSkia image) {
+                                        CompressedThumbnailData image) {
   callback_.Run(contents, image);
 }
 
diff --git a/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.h b/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.h
index 2d64d3d..feb174d 100644
--- a/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.h
+++ b/chrome/browser/ui/webui/tab_strip/thumbnail_tracker.h
@@ -9,19 +9,20 @@
 
 #include "base/containers/flat_map.h"
 #include "base/memory/scoped_refptr.h"
+#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
 #include "ui/gfx/image/image_skia.h"
 
 namespace content {
 class WebContents;
 }
 
-class ThumbnailImage;
-
 // Tracks the thumbnails of a set of WebContentses. This set is dynamically
 // managed, and WebContents closure is handled gracefully. The user is notified
 // of any thumbnail change via a callback specified on construction.
 class ThumbnailTracker {
  public:
+  using CompressedThumbnailData = ThumbnailImage::CompressedThumbnailData;
+
   // Should return the ThumbnailImage instance for a WebContents.
   using GetThumbnailCallback =
       base::RepeatingCallback<scoped_refptr<ThumbnailImage>(
@@ -29,7 +30,8 @@
 
   // Handles a thumbnail update for a tab.
   using ThumbnailUpdatedCallback =
-      base::RepeatingCallback<void(content::WebContents*, gfx::ImageSkia)>;
+      base::RepeatingCallback<void(content::WebContents*,
+                                   CompressedThumbnailData)>;
 
   explicit ThumbnailTracker(ThumbnailUpdatedCallback callback);
   // Specifies how to get a ThumbnailImage for a WebContents. This is intended
@@ -44,7 +46,8 @@
   void WatchTab(content::WebContents* contents);
 
  private:
-  void ThumbnailUpdated(content::WebContents* contents, gfx::ImageSkia image);
+  void ThumbnailUpdated(content::WebContents* contents,
+                        CompressedThumbnailData image);
   void ContentsClosed(content::WebContents* contents);
 
   // The default |GetThumbnailCallback| implementation.
diff --git a/chrome/browser/ui/webui/tab_strip/thumbnail_tracker_unittest.cc b/chrome/browser/ui/webui/tab_strip/thumbnail_tracker_unittest.cc
index eac65cc..71429601 100644
--- a/chrome/browser/ui/webui/tab_strip/thumbnail_tracker_unittest.cc
+++ b/chrome/browser/ui/webui/tab_strip/thumbnail_tracker_unittest.cc
@@ -99,11 +99,9 @@
   // Verify that WatchTab() gets the current image, waiting for the decoding to
   // happen.
   EXPECT_CALL(thumbnail_updated_callback_, Run(contents.get(), _)).Times(1);
-  base::RunLoop notify_loop;
   thumbnail->set_async_operation_finished_callback_for_testing(
-      notify_loop.QuitClosure());
+      base::RepeatingClosure());
   thumbnail_tracker_.WatchTab(contents.get());
-  notify_loop.Run();
 }
 
 TEST_F(ThumbnailTrackerTest, PropagatesThumbnailUpdate) {
diff --git a/chrome/browser/ui/webui/webapks_handler.cc b/chrome/browser/ui/webui/webapks_handler.cc
index 2fa4467..e7417b0 100644
--- a/chrome/browser/ui/webui/webapks_handler.cc
+++ b/chrome/browser/ui/webui/webapks_handler.cc
@@ -78,5 +78,5 @@
                    webapk_info.is_backing_browser
                        ? webapk_info.update_status
                        : "Current browser doesn't own this WebAPK.");
-  CallJavascriptFunction("returnWebApkInfo", result);
+  FireWebUIListener("web-apk-info", result);
 }
diff --git a/chrome/browser/vr/test/webxr_vr_browser_test.cc b/chrome/browser/vr/test/webxr_vr_browser_test.cc
index 80e276c..e29416f 100644
--- a/chrome/browser/vr/test/webxr_vr_browser_test.cc
+++ b/chrome/browser/vr/test/webxr_vr_browser_test.cc
@@ -16,6 +16,7 @@
 
 WebXrVrBrowserTestBase::WebXrVrBrowserTestBase() {
   enable_features_.push_back(features::kWebXr);
+  enable_features_.push_back(features::kWebXrGamepadModule);
 }
 
 WebXrVrBrowserTestBase::~WebXrVrBrowserTestBase() = default;
diff --git a/chrome/browser/vr/testapp/vr_test_context.cc b/chrome/browser/vr/testapp/vr_test_context.cc
index 6d5bc1d..921779c 100644
--- a/chrome/browser/vr/testapp/vr_test_context.cc
+++ b/chrome/browser/vr/testapp/vr_test_context.cc
@@ -720,8 +720,8 @@
        security_state::SecurityLevel::SECURE, &omnibox::kHttpsValidIcon, true,
        false},
       {GURL("https://www.domain.com/path/segment/directory/file.html"),
-       security_state::SecurityLevel::DANGEROUS, &omnibox::kHttpsInvalidIcon,
-       true, false},
+       security_state::SecurityLevel::DANGEROUS,
+       &omnibox::kNotSecureWarningIcon, true, false},
       // Do not show URL
       {GURL(), security_state::SecurityLevel::WARNING, &omnibox::kHttpIcon,
        false, false},
diff --git a/chrome/browser/wake_lock/wake_lock_browsertest.cc b/chrome/browser/wake_lock/wake_lock_browsertest.cc
index d75567a..051c1f33 100644
--- a/chrome/browser/wake_lock/wake_lock_browsertest.cc
+++ b/chrome/browser/wake_lock/wake_lock_browsertest.cc
@@ -154,13 +154,9 @@
 
   PermissionRequestObserver observer(
       browser()->tab_strip_model()->GetActiveWebContents());
-  std::string response;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "WakeLock.requestPermission('screen').then(status => "
-      "    domAutomationController.send(status));",
-      &response));
-  EXPECT_EQ(response, "granted");
+  EXPECT_EQ("granted", content::EvalJs(
+                           browser()->tab_strip_model()->GetActiveWebContents(),
+                           "WakeLock.requestPermission('screen')"));
   EXPECT_EQ(observer.request_shown(), false);
 }
 
@@ -172,13 +168,11 @@
 
   PermissionRequestObserver observer(
       browser()->tab_strip_model()->GetActiveWebContents());
-  std::string response;
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractString(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "WakeLock.requestPermission('screen').then(status => "
-      "    domAutomationController.send(status));",
-      &response));
-  EXPECT_EQ(response, "granted");
+  EXPECT_EQ(
+      "granted",
+      content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                      "WakeLock.requestPermission('screen')",
+                      content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
   EXPECT_EQ(observer.request_shown(), false);
 }
 
@@ -189,13 +183,9 @@
 
   PermissionRequestObserver observer(
       browser()->tab_strip_model()->GetActiveWebContents());
-  std::string response;
-  EXPECT_TRUE(content::ExecuteScriptAndExtractString(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "WakeLock.requestPermission('system').then(status => "
-      "    domAutomationController.send(status));",
-      &response));
-  EXPECT_EQ(response, "denied");
+  EXPECT_EQ("denied", content::EvalJs(
+                          browser()->tab_strip_model()->GetActiveWebContents(),
+                          "WakeLock.requestPermission('system')"));
   EXPECT_EQ(observer.request_shown(), false);
 }
 
@@ -207,12 +197,10 @@
 
   PermissionRequestObserver observer(
       browser()->tab_strip_model()->GetActiveWebContents());
-  std::string response;
-  EXPECT_TRUE(content::ExecuteScriptWithoutUserGestureAndExtractString(
-      browser()->tab_strip_model()->GetActiveWebContents(),
-      "WakeLock.requestPermission('system').then(status => "
-      "    domAutomationController.send(status));",
-      &response));
-  EXPECT_EQ(response, "denied");
+  EXPECT_EQ(
+      "denied",
+      content::EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
+                      "WakeLock.requestPermission('system')",
+                      content::EvalJsOptions::EXECUTE_SCRIPT_NO_USER_GESTURE));
   EXPECT_EQ(observer.request_shown(), false);
 }
diff --git a/chrome/chrome_cleaner/OWNERS b/chrome/chrome_cleaner/OWNERS
index 6459769..276db6fa 100644
--- a/chrome/chrome_cleaner/OWNERS
+++ b/chrome/chrome_cleaner/OWNERS
@@ -1,4 +1,4 @@
-joenotcharles@google.com
+joenotcharles@chromium.org
 proberge@chromium.org
 
 # TEAM: security-dev@chromium.org
diff --git a/chrome/chrome_cleaner/README.md b/chrome/chrome_cleaner/README.md
index c09c808..8fb4958 100644
--- a/chrome/chrome_cleaner/README.md
+++ b/chrome/chrome_cleaner/README.md
@@ -69,4 +69,4 @@
 
 ## Contact
 
-joenotcharles@google.com
+joenotcharles@chromium.org
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index d9a59c46..f196895 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -614,10 +614,6 @@
     "RemoveSupervisedUsersOnStartup", base::FEATURE_DISABLED_BY_DEFAULT};
 #endif
 
-// Controls whether to show Safety Tip warnings on low-reputation sites.
-const base::Feature kSafetyTipUI{"SafetyTip",
-                                 base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Controls whether the user is prompted when sites request attestation.
 const base::Feature kSecurityKeyAttestationPrompt{
     "SecurityKeyAttestationPrompt", base::FEATURE_ENABLED_BY_DEFAULT};
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index e1c88f1..3e7105a 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -391,9 +391,6 @@
 #endif
 
 COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kSafetyTipUI;
-
-COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kSecurityKeyAttestationPrompt;
 
 #if defined(OS_ANDROID)
diff --git a/chrome/installer/mac/notarize_thing.py b/chrome/installer/mac/notarize_thing.py
index e4f41c4..de893aa 100755
--- a/chrome/installer/mac/notarize_thing.py
+++ b/chrome/installer/mac/notarize_thing.py
@@ -63,7 +63,7 @@
 
         config_class = OverrideBundleIDConfig
 
-    config = config_class('notused', None, args.user, args.password,
+    config = config_class('notused', 'notused', None, args.user, args.password,
                           args.asc_provider)
 
     uuids = []
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index 43b0f39..7e1d62f 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -1709,6 +1709,7 @@
   EXPECT_FALSE(
       password_autofill_agent_->TryToShowTouchToFill(password_element_));
   EXPECT_FALSE(password_autofill_agent_->TryToShowTouchToFill(random_element));
+  EXPECT_FALSE(password_autofill_agent_->ShouldSuppressKeyboard());
 
   // This changes once fill data is simulated. |random_element| continue  to
   // have no fill data, though.
@@ -1717,6 +1718,7 @@
   EXPECT_CALL(fake_driver_, ShowTouchToFill);
   EXPECT_TRUE(
       password_autofill_agent_->TryToShowTouchToFill(username_element_));
+  EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   base::RunLoop().RunUntilIdle();
 }
 
@@ -1726,6 +1728,7 @@
   EXPECT_CALL(fake_driver_, ShowTouchToFill);
   EXPECT_TRUE(
       password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   base::RunLoop().RunUntilIdle();
 }
 
@@ -1734,18 +1737,16 @@
 
   // Touch to fill will be shown multiple times until TouchToFillDismissed()
   // gets called.
-  EXPECT_CALL(fake_driver_, ShowTouchToFill).Times(2);
-  EXPECT_TRUE(
-      password_autofill_agent_->TryToShowTouchToFill(username_element_));
+  EXPECT_CALL(fake_driver_, ShowTouchToFill);
   EXPECT_TRUE(
       password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   base::RunLoop().RunUntilIdle();
 
   password_autofill_agent_->TouchToFillDismissed();
   EXPECT_FALSE(
-      password_autofill_agent_->TryToShowTouchToFill(username_element_));
-  EXPECT_FALSE(
       password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_FALSE(password_autofill_agent_->ShouldSuppressKeyboard());
   base::RunLoop().RunUntilIdle();
 
   // Reload the page and simulate fill.
@@ -1758,6 +1759,7 @@
   EXPECT_CALL(fake_driver_, ShowTouchToFill);
   EXPECT_TRUE(
       password_autofill_agent_->TryToShowTouchToFill(password_element_));
+  EXPECT_TRUE(password_autofill_agent_->ShouldSuppressKeyboard());
   base::RunLoop().RunUntilIdle();
 }
 
diff --git a/chrome/test/chromedriver/element_commands.cc b/chrome/test/chromedriver/element_commands.cc
index 97cdcb2..a83edcf 100644
--- a/chrome/test/chromedriver/element_commands.cc
+++ b/chrome/test/chromedriver/element_commands.cc
@@ -34,6 +34,10 @@
 const int kFlickTouchEventsPerSecond = 30;
 const std::set<std::string> textControlTypes = {"text", "search", "tel", "url",
                                                 "password"};
+const std::set<std::string> inputControlTypes = {
+    "text",           "search", "url",   "tel",   "email",
+    "password",       "date",   "month", "week",  "time",
+    "datetime-local", "number", "range", "color", "file"};
 
 namespace {
 
@@ -321,6 +325,56 @@
   Status status = CheckElement(element_id);
   if (status.IsError())
     return status;
+
+  std::string tag_name;
+  status = GetElementTagName(session, web_view, element_id, &tag_name);
+  if (status.IsError())
+    return status;
+  std::string element_type;
+  bool is_input_control = false;
+
+  if (tag_name == "input") {
+    std::unique_ptr<base::Value> get_element_type;
+    status = GetElementAttribute(session, web_view, element_id, "type",
+                                 &get_element_type);
+    if (status.IsError())
+      return status;
+    if (get_element_type->GetAsString(&element_type))
+      element_type = base::ToLowerASCII(element_type);
+
+    is_input_control =
+        inputControlTypes.find(element_type) != inputControlTypes.end();
+  }
+
+  bool is_text = tag_name == "textarea";
+  bool is_content_editable = false;
+  if (!is_text && !is_input_control) {
+    std::unique_ptr<base::Value> get_content_editable;
+    base::ListValue args;
+    args.Append(CreateElement(element_id));
+    status = web_view->CallFunction(session->GetCurrentFrameId(),
+                                    "element => element.isContentEditable",
+                                    args, &get_content_editable);
+    if (status.IsError())
+      return status;
+    get_content_editable->GetAsBoolean(&is_content_editable);
+  }
+
+  std::unique_ptr<base::Value> get_readonly;
+  bool is_readonly = false;
+  base::DictionaryValue params_readOnly;
+  if (!is_content_editable) {
+    params_readOnly.SetString("name", "readOnly");
+    status = ExecuteGetElementProperty(session, web_view, element_id,
+                                       params_readOnly, &get_readonly);
+    get_readonly->GetAsBoolean(&is_readonly);
+    if (status.IsError())
+      return status;
+  }
+  bool is_editable =
+      (is_input_control || is_text || is_content_editable) && !is_readonly;
+  if (!is_editable)
+    return Status(kInvalidElementState);
   // Scrolling to element is done by webdriver::atoms::CLEAR
   bool is_displayed = false;
   base::TimeTicks start_time = base::TimeTicks::Now();
diff --git a/chrome/test/data/webui/cr_elements/cr_policy_indicator_behavior_tests.js b/chrome/test/data/webui/cr_elements/cr_policy_indicator_behavior_tests.js
index 806134f..1689b4e 100644
--- a/chrome/test/data/webui/cr_elements/cr_policy_indicator_behavior_tests.js
+++ b/chrome/test/data/webui/cr_elements/cr_policy_indicator_behavior_tests.js
@@ -35,6 +35,28 @@
             indicator.indicatorType, indicator.indicatorSourceName));
   });
 
+  test('parent-controlled indicator', function() {
+    indicator.indicatorType = CrPolicyIndicatorType.PARENT;
+
+    assertTrue(indicator.indicatorVisible);
+    assertEquals('cr20:kite', indicator.indicatorIcon);
+    assertEquals(
+        'parent',
+        indicator.getIndicatorTooltip(
+            indicator.indicatorType, indicator.indicatorSourceName));
+  });
+
+  test('child-restriction indicator', function() {
+    indicator.indicatorType = CrPolicyIndicatorType.CHILD_RESTRICTION;
+
+    assertTrue(indicator.indicatorVisible);
+    assertEquals('cr20:kite', indicator.indicatorIcon);
+    assertEquals(
+        'Restricted for child',
+        indicator.getIndicatorTooltip(
+            indicator.indicatorType, indicator.indicatorSourceName));
+  });
+
   test('recommended indicator', function() {
     indicator.indicatorType = CrPolicyIndicatorType.RECOMMENDED;
 
diff --git a/chrome/test/data/webui/cr_elements/cr_policy_indicator_tests.js b/chrome/test/data/webui/cr_elements/cr_policy_indicator_tests.js
index 5f1b092..a3f2323 100644
--- a/chrome/test/data/webui/cr_elements/cr_policy_indicator_tests.js
+++ b/chrome/test/data/webui/cr_elements/cr_policy_indicator_tests.js
@@ -40,5 +40,17 @@
       assertEquals('cr:person', icon.iconClass);
       assertEquals('owner: foo@example.com', icon.tooltipText);
     }
+
+    indicator.indicatorType = CrPolicyIndicatorType.PARENT;
+
+    assertFalse(icon.hidden);
+    assertEquals('cr20:kite', icon.iconClass);
+    assertEquals('parent', icon.tooltipText);
+
+    indicator.indicatorType = CrPolicyIndicatorType.CHILD_RESTRICTION;
+
+    assertFalse(icon.hidden);
+    assertEquals('cr20:kite', icon.iconClass);
+    assertEquals('Restricted for child', icon.tooltipText);
   });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js b/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
index f65305e..3c40a55 100644
--- a/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
+++ b/chrome/test/data/webui/settings/chromeos/cups_printer_landing_page_tests.js
@@ -544,10 +544,6 @@
       nearbyPrintersElement = page.$$('settings-cups-nearby-printers');
       assertTrue(!!nearbyPrintersElement);
 
-      // Assert that no printers have been detected.
-      let nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
-      assertEquals(0, nearbyPrinterEntries.length);
-
       // Simuluate finding nearby printers.
       cr.webUIListenerCallback(
           'on-nearby-printers-changed', automaticPrinterList,
@@ -570,10 +566,6 @@
           nearbyPrintersElement = page.$$('settings-cups-nearby-printers');
           assertTrue(!!nearbyPrintersElement);
 
-          // Assert that no printers are detected.
-          let nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
-          assertEquals(0, nearbyPrinterEntries.length);
-
           // Simuluate finding nearby printers.
           cr.webUIListenerCallback(
               'on-nearby-printers-changed', automaticPrinterList,
@@ -613,10 +605,6 @@
           nearbyPrintersElement = page.$$('settings-cups-nearby-printers');
           assertTrue(!!nearbyPrintersElement);
 
-          // Assert that there are initially no detected printers.
-          let nearbyPrinterEntries = getPrinterEntries(nearbyPrintersElement);
-          assertEquals(0, nearbyPrinterEntries.length);
-
           // Simuluate finding nearby printers.
           cr.webUIListenerCallback(
               'on-nearby-printers-changed', automaticPrinterList,
diff --git a/chromecast/browser/webview/proto/webview.proto b/chromecast/browser/webview/proto/webview.proto
index 666bffa..efba971 100644
--- a/chromecast/browser/webview/proto/webview.proto
+++ b/chromecast/browser/webview/proto/webview.proto
@@ -184,13 +184,26 @@
   string message = 2;
 }
 
+// Associate a cast application with a platform view
+message AssociateCastAppWindowRequest {
+  // This identifies the surface that the cast app will display into.
+  // It should be unique from all other platform views.
+  // This is the same platform id that is set in webview_id in
+  // WebviewCreateRequest
+  int32 platform_view_id = 1;
+  // This is the id of a cast application.
+  int32 app_window_id = 2;
+}
+
+message AssociateCastAppWindowResponse {}
+
 message WebviewRequest {
   // Unique identifier for the request. For requests that will have a response,
   // the response id will match the request id.
   // Valid ID values must be greater than 0.
   int64 id = 18;
   oneof type {
-    // This must be the first message.
+    // This must be the first message for CreateWebview.
     WebviewCreateRequest create = 1;
     // No response.
     InputEvent input = 2;
@@ -226,6 +239,8 @@
     SetAutoMediaPlaybackPolicyRequest set_auto_media_playback_policy = 17;
     // Response to a navigation request.
     NavigationDecision navigation_decision = 19;
+    // This must be the first request for CreateCastAppWindowLink
+    AssociateCastAppWindowRequest associate = 20;
   }
 }
 
@@ -247,10 +262,20 @@
     // Events sents from JS inside of Webviews to the embedder who added JS
     // channels via AddJavascriptChannelsRequest.
     JavascriptChannelMessage javascript_channel_message = 10;
+    AssociateCastAppWindowResponse associate = 20;
   }
 }
 
+// TODO(sagallea): Deprecate this after moving to new service.
 service WebviewService {
   // Creates a webview. See the comment at the top of the file.
   rpc CreateWebview(stream WebviewRequest) returns (stream WebviewResponse);
 }
+
+service PlatformViewsService {
+  // Creates a webview. See the comment at the top of the file.
+  rpc CreateWebview(stream WebviewRequest) returns (stream WebviewResponse);
+  // Only AssociateRequest and InputEvent requests are valid.
+  rpc CreateCastAppWindowLink(stream WebviewRequest)
+      returns (stream WebviewResponse);
+}
diff --git a/chromecast/media/cma/backend/cast_audio_json.cc b/chromecast/media/cma/backend/cast_audio_json.cc
index fcdc424..a3521a1 100644
--- a/chromecast/media/cma/backend/cast_audio_json.cc
+++ b/chromecast/media/cma/backend/cast_audio_json.cc
@@ -8,18 +8,37 @@
 #include <utility>
 
 #include "base/bind.h"
-#include "base/files/file_path_watcher.h"
 #include "base/files/file_util.h"
 #include "base/json/json_reader.h"
 #include "base/location.h"
 #include "base/logging.h"
-#include "base/message_loop/message_pump_type.h"
-#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
 #include "build/build_config.h"
 
 namespace chromecast {
 namespace media {
 
+namespace {
+
+void ReadFileRunCallback(CastAudioJsonProvider::TuningChangedCallback callback,
+                         const base::FilePath& path,
+                         bool error) {
+  DCHECK(callback);
+
+  std::string contents;
+  base::ReadFileToString(path, &contents);
+  std::unique_ptr<base::Value> value =
+      base::JSONReader::ReadDeprecated(contents);
+  if (value) {
+    callback.Run(std::move(value));
+    return;
+  }
+  LOG(ERROR) << "Unable to parse JSON in " << path;
+}
+
+}  // namespace
+
 #if defined(OS_FUCHSIA)
 const char kCastAudioJsonFilePath[] = "/system/data/cast_audio.json";
 #else
@@ -27,14 +46,6 @@
 #endif
 const char kCastAudioJsonFileName[] = "cast_audio.json";
 
-#define ENSURE_OWN_THREAD(method, ...)                                     \
-  if (!task_runner_->RunsTasksInCurrentSequence()) {                       \
-    task_runner_->PostTask(                                                \
-        FROM_HERE, base::BindOnce(&CastAudioJsonProviderImpl::method,      \
-                                  base::Unretained(this), ##__VA_ARGS__)); \
-    return;                                                                \
-  }
-
 // static
 base::FilePath CastAudioJson::GetFilePath() {
   base::FilePath tuning_path = CastAudioJson::GetFilePathForTuning();
@@ -56,17 +67,11 @@
 }
 
 CastAudioJsonProviderImpl::CastAudioJsonProviderImpl()
-    : thread_("cast_audio_json_provider"),
-      cast_audio_watcher_(std::make_unique<base::FilePathWatcher>()) {
-  base::Thread::Options options;
-  options.message_pump_type = base::MessagePumpType::IO;
-  thread_.StartWithOptions(options);
-  task_runner_ = thread_.task_runner();
-}
+    : cast_audio_watcher_(base::SequenceBound<FileWatcher>(
+          base::CreateSequencedTaskRunner({base::ThreadPool(), base::MayBlock(),
+                                           base::TaskPriority::LOWEST}))) {}
 
-CastAudioJsonProviderImpl::~CastAudioJsonProviderImpl() {
-  StopWatchingFileOnThread();
-}
+CastAudioJsonProviderImpl::~CastAudioJsonProviderImpl() = default;
 
 std::unique_ptr<base::Value> CastAudioJsonProviderImpl::GetCastAudioConfig() {
   std::string contents;
@@ -76,35 +81,18 @@
 
 void CastAudioJsonProviderImpl::SetTuningChangedCallback(
     TuningChangedCallback callback) {
-  ENSURE_OWN_THREAD(SetTuningChangedCallback, std::move(callback));
+  cast_audio_watcher_.Post(FROM_HERE, &FileWatcher::SetTuningChangedCallback,
+                           std::move(callback));
+}
 
-  CHECK(!callback_);
-  callback_ = callback;
-  cast_audio_watcher_->Watch(
+CastAudioJsonProviderImpl::FileWatcher::FileWatcher() = default;
+CastAudioJsonProviderImpl::FileWatcher::~FileWatcher() = default;
+
+void CastAudioJsonProviderImpl::FileWatcher::SetTuningChangedCallback(
+    TuningChangedCallback callback) {
+  watcher_.Watch(
       CastAudioJson::GetFilePathForTuning(), false /* recursive */,
-      base::BindRepeating(&CastAudioJsonProviderImpl::OnTuningFileChanged,
-                          base::Unretained(this)));
-}
-
-void CastAudioJsonProviderImpl::StopWatchingFileOnThread() {
-  ENSURE_OWN_THREAD(StopWatchingFileOnThread);
-  cast_audio_watcher_.reset();
-}
-
-void CastAudioJsonProviderImpl::OnTuningFileChanged(const base::FilePath& path,
-                                                    bool error) {
-  DCHECK(task_runner_->RunsTasksInCurrentSequence());
-  DCHECK(callback_);
-
-  std::string contents;
-  base::ReadFileToString(path, &contents);
-  std::unique_ptr<base::Value> value =
-      base::JSONReader::ReadDeprecated(contents);
-  if (value) {
-    callback_.Run(std::move(value));
-    return;
-  }
-  LOG(ERROR) << "Unable to parse JSON in " << path;
+      base::BindRepeating(&ReadFileRunCallback, std::move(callback)));
 }
 
 }  // namespace media
diff --git a/chromecast/media/cma/backend/cast_audio_json.h b/chromecast/media/cma/backend/cast_audio_json.h
index 38fc7668..99dadec 100644
--- a/chromecast/media/cma/backend/cast_audio_json.h
+++ b/chromecast/media/cma/backend/cast_audio_json.h
@@ -9,15 +9,11 @@
 
 #include "base/callback.h"
 #include "base/files/file_path.h"
+#include "base/files/file_path_watcher.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/threading/thread.h"
+#include "base/threading/sequence_bound.h"
 #include "base/values.h"
 
-namespace base {
-class FilePathWatcher;
-class SequencedTaskRunner;
-}  // namespace base
-
 namespace chromecast {
 namespace media {
 
@@ -48,9 +44,8 @@
   virtual std::unique_ptr<base::Value> GetCastAudioConfig() = 0;
 
   // |callback| will be called when a new cast_audio config is available.
-  // |callback| will always be called from the same thread, but not the same
-  // thread on which |SetTuningChangedCallback| is called.
-  // |callback| will never be called after ~CastAudioJsonProvider() is called.
+  // |callback| will always be called from the same thread, but not necessarily
+  // the same thread on which |SetTuningChangedCallback| is called.
   virtual void SetTuningChangedCallback(TuningChangedCallback callback) = 0;
 };
 
@@ -60,17 +55,22 @@
   ~CastAudioJsonProviderImpl() override;
 
  private:
+  class FileWatcher {
+   public:
+    FileWatcher();
+    ~FileWatcher();
+
+    void SetTuningChangedCallback(TuningChangedCallback callback);
+
+   private:
+    base::FilePathWatcher watcher_;
+  };
+
   // CastAudioJsonProvider implementation:
   std::unique_ptr<base::Value> GetCastAudioConfig() override;
   void SetTuningChangedCallback(TuningChangedCallback callback) override;
 
-  void StopWatchingFileOnThread();
-  void OnTuningFileChanged(const base::FilePath& path, bool error);
-
-  TuningChangedCallback callback_;
-  base::Thread thread_;
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
-  std::unique_ptr<base::FilePathWatcher> cast_audio_watcher_;
+  base::SequenceBound<FileWatcher> cast_audio_watcher_;
 
   DISALLOW_COPY_AND_ASSIGN(CastAudioJsonProviderImpl);
 };
diff --git a/chromeos/CHROMEOS_LKGM b/chromeos/CHROMEOS_LKGM
index 8e44cf9..1dd3a2e 100644
--- a/chromeos/CHROMEOS_LKGM
+++ b/chromeos/CHROMEOS_LKGM
@@ -1 +1 @@
-12585.0.0
\ No newline at end of file
+12586.0.0
\ No newline at end of file
diff --git a/components/autofill/content/renderer/autofill_agent.cc b/components/autofill/content/renderer/autofill_agent.cc
index 89be0f6..3b31ce64 100644
--- a/components/autofill/content/renderer/autofill_agent.cc
+++ b/components/autofill/content/renderer/autofill_agent.cc
@@ -850,8 +850,10 @@
   DCHECK(!node.IsNull());
   focused_node_was_last_clicked_ = node.Focused();
 
-  if (IsKeyboardAccessoryEnabled() || !focus_requires_scroll_)
+  if (IsTouchToFillEnabled() || IsKeyboardAccessoryEnabled() ||
+      !focus_requires_scroll_) {
     HandleFocusChangeComplete();
+  }
 }
 
 void AutofillAgent::SelectControlDidChange(
@@ -881,12 +883,9 @@
 
 bool AutofillAgent::ShouldSuppressKeyboard(
     const WebFormControlElement& element) {
-  // The keyboard should be suppressed if we can show the Touch To Fill UI.
-  //
   // Note: This is currently only implemented for passwords. Consider supporting
   // other autofill types in the future as well.
-  return IsTouchToFillEnabled() &&
-         password_autofill_agent_->TryToShowTouchToFill(element);
+  return password_autofill_agent_->ShouldSuppressKeyboard();
 }
 
 void AutofillAgent::SelectWasUpdated(
@@ -913,6 +912,9 @@
   if (!input_element && !form_util::IsTextAreaElement(element))
     return;
 
+  if (IsTouchToFillEnabled())
+    password_autofill_agent_->TryToShowTouchToFill(element);
+
   ShowSuggestionsOptions options;
   options.autofill_on_empty_values = true;
   // Show full suggestions when clicking on an already-focused form field.
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 6e94315..1915f4c 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -774,8 +774,16 @@
 #endif
 }
 
+bool PasswordAutofillAgent::ShouldSuppressKeyboard() {
+  // The keyboard should be suppressed if we are showing the Touch To Fill UI.
+  return touch_to_fill_state_ == TouchToFillState::kIsShowing;
+}
+
 bool PasswordAutofillAgent::TryToShowTouchToFill(
     const WebFormControlElement& control_element) {
+  if (touch_to_fill_state_ != TouchToFillState::kShouldShow)
+    return false;
+
   const WebInputElement* element = ToWebInputElement(&control_element);
   WebInputElement username_element;
   WebInputElement password_element;
@@ -786,10 +794,8 @@
     return false;
   }
 
-  if (!should_show_touch_to_fill_)
-    return false;
-
   GetPasswordManagerDriver()->ShowTouchToFill();
+  touch_to_fill_state_ = TouchToFillState::kIsShowing;
   return true;
 }
 
@@ -1197,7 +1203,7 @@
 }
 
 void PasswordAutofillAgent::TouchToFillDismissed() {
-  should_show_touch_to_fill_ = false;
+  touch_to_fill_state_ = TouchToFillState::kWasShown;
 }
 
 void PasswordAutofillAgent::AnnotateFieldsWithParsingResult(
@@ -1358,7 +1364,7 @@
   autofilled_elements_cache_.clear();
   last_updated_field_renderer_id_ = FormData::kNotSetFormRendererId;
   last_updated_form_renderer_id_ = FormData::kNotSetFormRendererId;
-  should_show_touch_to_fill_ = true;
+  touch_to_fill_state_ = TouchToFillState::kShouldShow;
 #if !defined(OS_ANDROID) && !defined(OS_IOS)
   page_passwords_analyser_.Reset();
 #endif
diff --git a/components/autofill/content/renderer/password_autofill_agent.h b/components/autofill/content/renderer/password_autofill_agent.h
index e5b5512..9e0e43c 100644
--- a/components/autofill/content/renderer/password_autofill_agent.h
+++ b/components/autofill/content/renderer/password_autofill_agent.h
@@ -179,6 +179,9 @@
   // no check request were sent from this frame load.
   void MaybeCheckSafeBrowsingReputation(const blink::WebInputElement& element);
 
+  // Returns whether the soft keyboard should be suppressed.
+  bool ShouldSuppressKeyboard();
+
   // Asks the agent to show the touch to fill UI for |control_element|. Returns
   // whether the agent was able to do so.
   bool TryToShowTouchToFill(
@@ -247,6 +250,16 @@
     RESTRICTION_NON_EMPTY_PASSWORD
   };
 
+  // Enumeration representing possible Touch To Fill states. This is used to
+  // make sure that Touch To Fill will only be shown in response to the first
+  // password form focus during a frame's life time and to suppress the soft
+  // keyboard when Touch To Fill is shown.
+  enum class TouchToFillState {
+    kShouldShow,
+    kIsShowing,
+    kWasShown,
+  };
+
   struct PasswordInfo {
     blink::WebInputElement password_field;
     PasswordFormFillData fill_data;
@@ -537,10 +550,9 @@
   // Contains renderer id of the form of the last updated input element.
   uint32_t last_updated_form_renderer_id_ = FormData::kNotSetFormRendererId;
 
-  // Flag that determines whether we instruct the browser to show the Touch To
-  // Fill sheet if applicable. This is set to false when TouchToFillDismissed()
-  // is invoked and gets reset during CleanupOnDocumentShutdown.
-  bool should_show_touch_to_fill_ = true;
+  // Current state of Touch To Fill. This is reset during
+  // CleanupOnDocumentShutdown.
+  TouchToFillState touch_to_fill_state_ = TouchToFillState::kShouldShow;
 
   DISALLOW_COPY_AND_ASSIGN(PasswordAutofillAgent);
 };
diff --git a/components/autofill/core/browser/payments/credit_card_save_manager.h b/components/autofill/core/browser/payments/credit_card_save_manager.h
index 2cdd297..941d7c0 100644
--- a/components/autofill/core/browser/payments/credit_card_save_manager.h
+++ b/components/autofill/core/browser/payments/credit_card_save_manager.h
@@ -144,10 +144,11 @@
   friend class SaveCardBubbleViewsFullFormBrowserTest;
   friend class SaveCardInfobarEGTestHelper;
   friend class ::SaveCardOfferObserver;
+  FRIEND_TEST_ALL_PREFIXES(
+      SaveCardBubbleViewsFullFormBrowserTestWithAutofillUpstream,
+      StrikeDatabase_Upload_FullFlowTest);
   FRIEND_TEST_ALL_PREFIXES(SaveCardBubbleViewsFullFormBrowserTest,
                            StrikeDatabase_Local_FullFlowTest);
-  FRIEND_TEST_ALL_PREFIXES(SaveCardBubbleViewsFullFormBrowserTest,
-                           StrikeDatabase_Upload_FullFlowTest);
   FRIEND_TEST_ALL_PREFIXES(SaveCardBubbleViewsFullFormBrowserTestForStatusChip,
                            Feedback_CardSavingAnimation);
 
diff --git a/components/dom_distiller/core/BUILD.gn b/components/dom_distiller/core/BUILD.gn
index 7d6ac95..e29b253 100644
--- a/components/dom_distiller/core/BUILD.gn
+++ b/components/dom_distiller/core/BUILD.gn
@@ -26,14 +26,10 @@
     "dom_distiller_constants.h",
     "dom_distiller_features.cc",
     "dom_distiller_features.h",
-    "dom_distiller_model.cc",
-    "dom_distiller_model.h",
     "dom_distiller_request_view_base.cc",
     "dom_distiller_request_view_base.h",
     "dom_distiller_service.cc",
     "dom_distiller_service.h",
-    "dom_distiller_store.cc",
-    "dom_distiller_store.h",
     "dom_distiller_switches.cc",
     "dom_distiller_switches.h",
     "experiments.cc",
@@ -128,7 +124,6 @@
     "distilled_page_prefs_unittests.cc",
     "distiller_unittest.cc",
     "distiller_url_fetcher_unittest.cc",
-    "dom_distiller_model_unittest.cc",
     "dom_distiller_request_view_base_unittest.cc",
     "dom_distiller_service_unittest.cc",
     "page_features_unittest.cc",
diff --git a/components/dom_distiller/core/dom_distiller_model.cc b/components/dom_distiller/core/dom_distiller_model.cc
deleted file mode 100644
index f49d625..0000000
--- a/components/dom_distiller/core/dom_distiller_model.cc
+++ /dev/null
@@ -1,103 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/dom_distiller/core/dom_distiller_model.h"
-
-#include <unordered_set>
-#include <utility>
-
-using syncer::SyncChange;
-using syncer::SyncChangeList;
-using syncer::SyncData;
-using syncer::SyncDataList;
-
-namespace dom_distiller {
-
-DomDistillerModel::DomDistillerModel() : next_key_(1) {}
-
-DomDistillerModel::DomDistillerModel(
-    const std::vector<ArticleEntry>& initial_data)
-    : next_key_(1) {
-  for (size_t i = 0; i < initial_data.size(); ++i) {
-    AddEntry(initial_data[i]);
-  }
-}
-
-DomDistillerModel::~DomDistillerModel() {}
-
-bool DomDistillerModel::GetEntryById(const std::string& entry_id,
-                                     ArticleEntry* entry) const {
-  KeyType key = 0;
-  if (!GetKeyById(entry_id, &key)) {
-    return false;
-  }
-  GetEntryByKey(key, entry);
-  return true;
-}
-
-bool DomDistillerModel::GetEntryByUrl(const GURL& url,
-                                      ArticleEntry* entry) const {
-  KeyType key = 0;
-  if (!GetKeyByUrl(url, &key)) {
-    return false;
-  }
-  GetEntryByKey(key, entry);
-  return true;
-}
-
-bool DomDistillerModel::GetKeyById(const std::string& entry_id,
-                                   KeyType* key) const {
-  auto it = entry_id_to_key_map_.find(entry_id);
-  if (it == entry_id_to_key_map_.end()) {
-    return false;
-  }
-  if (key != nullptr) {
-    *key = it->second;
-  }
-  return true;
-}
-
-bool DomDistillerModel::GetKeyByUrl(const GURL& url, KeyType* key) const {
-  auto it = url_to_key_map_.find(url.spec());
-  if (it == url_to_key_map_.end()) {
-    return false;
-  }
-  if (key != nullptr) {
-    *key = it->second;
-  }
-  return true;
-}
-
-void DomDistillerModel::GetEntryByKey(KeyType key, ArticleEntry* entry) const {
-  if (entry != nullptr) {
-    auto it = entries_.find(key);
-    DCHECK(it != entries_.end());
-    *entry = it->second;
-  }
-}
-
-size_t DomDistillerModel::GetNumEntries() const {
-  return entries_.size();
-}
-
-std::vector<ArticleEntry> DomDistillerModel::GetEntries() const {
-  std::vector<ArticleEntry> entries_list;
-  for (auto it = entries_.begin(); it != entries_.end(); ++it) {
-    entries_list.push_back(it->second);
-  }
-  return entries_list;
-}
-
-void DomDistillerModel::AddEntry(const ArticleEntry& entry) {
-  const std::string& entry_id = entry.entry_id();
-  KeyType key = next_key_++;
-  DCHECK(!GetKeyById(entry_id, nullptr));
-  entries_.insert(std::make_pair(key, entry));
-  entry_id_to_key_map_.insert(std::make_pair(entry_id, key));
-  for (int i = 0; i < entry.pages_size(); ++i) {
-    url_to_key_map_.insert(std::make_pair(entry.pages(i).url(), key));
-  }
-}
-
-}  // namespace dom_distiller
diff --git a/components/dom_distiller/core/dom_distiller_model.h b/components/dom_distiller/core/dom_distiller_model.h
deleted file mode 100644
index 4914f88..0000000
--- a/components/dom_distiller/core/dom_distiller_model.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_MODEL_H_
-#define COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_MODEL_H_
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "base/containers/id_map.h"
-#include "base/macros.h"
-#include "components/dom_distiller/core/article_entry.h"
-#include "components/sync/model/sync_change.h"
-#include "components/sync/model/sync_data.h"
-#include "url/gurl.h"
-
-namespace dom_distiller {
-
-// This stores the in-memory model of the DOM distiller list. Entries can be
-// looked up by URL or by entry_id.
-// The model assumes that an URL corresponds to at most a single entry. If this
-// assumption is broken, lookup by URL may return unexpected results.
-class DomDistillerModel {
- public:
-  DomDistillerModel();
-  explicit DomDistillerModel(const std::vector<ArticleEntry>& initial_data);
-
-  ~DomDistillerModel();
-
-  // Lookup an ArticleEntry by ID or URL. Returns whether a corresponding entry
-  // was found. On success, if |entry| is not null, it will contain the entry.
-  bool GetEntryById(const std::string& entry_id, ArticleEntry* entry) const;
-  bool GetEntryByUrl(const GURL& url, ArticleEntry* entry) const;
-
-  std::vector<ArticleEntry> GetEntries() const;
-  size_t GetNumEntries() const;
-
- private:
-  typedef int32_t KeyType;
-  typedef std::unordered_map<KeyType, ArticleEntry> EntryMap;
-  typedef std::unordered_map<std::string, KeyType> StringToKeyMap;
-
-  void AddEntry(const ArticleEntry& entry);
-
-  // Lookup an entry's key by ID or URL. Returns whether a corresponding key was
-  // found. On success, if |key| is not null, it will contain the entry.
-  bool GetKeyById(const std::string& entry_id, KeyType* key) const;
-  bool GetKeyByUrl(const GURL& url, KeyType* key) const;
-
-  // If |entry| is not null, assigns the entry for |key| to it. |key| must map
-  // to an entry in |entries_|.
-  void GetEntryByKey(KeyType key, ArticleEntry* entry) const;
-
-  KeyType next_key_;
-  EntryMap entries_;
-  StringToKeyMap url_to_key_map_;
-  StringToKeyMap entry_id_to_key_map_;
-
-  DISALLOW_COPY_AND_ASSIGN(DomDistillerModel);
-};
-
-}  // namespace dom_distiller
-
-#endif  // COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_MODEL_H_
diff --git a/components/dom_distiller/core/dom_distiller_model_unittest.cc b/components/dom_distiller/core/dom_distiller_model_unittest.cc
deleted file mode 100644
index ad8328b..0000000
--- a/components/dom_distiller/core/dom_distiller_model_unittest.cc
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/dom_distiller/core/dom_distiller_model.h"
-
-#include "testing/gtest/include/gtest/gtest.h"
-
-namespace dom_distiller {
-
-TEST(DomDistillerModelTest, TestGetByEntryId) {
-  ArticleEntry entry1;
-  entry1.set_entry_id("id1");
-  entry1.set_title("title1");
-  ArticleEntry entry2;
-  entry2.set_entry_id("id2");
-  entry2.set_title("title1");
-
-  std::vector<ArticleEntry> initial_model;
-  initial_model.push_back(entry1);
-  initial_model.push_back(entry2);
-
-  DomDistillerModel model(initial_model);
-
-  ArticleEntry found_entry;
-  EXPECT_TRUE(model.GetEntryById(entry1.entry_id(), &found_entry));
-  ASSERT_TRUE(IsEntryValid(found_entry));
-  EXPECT_TRUE(AreEntriesEqual(entry1, found_entry));
-
-  EXPECT_TRUE(model.GetEntryById(entry2.entry_id(), &found_entry));
-  ASSERT_TRUE(IsEntryValid(found_entry));
-  EXPECT_TRUE(AreEntriesEqual(entry2, found_entry));
-
-  EXPECT_FALSE(model.GetEntryById("some_other_id", nullptr));
-}
-
-TEST(DomDistillerModelTest, TestGetByUrl) {
-  ArticleEntry entry1;
-  entry1.set_entry_id("id1");
-  entry1.set_title("title1");
-  ArticleEntryPage* page1 = entry1.add_pages();
-  page1->set_url("http://example.com/1");
-  ArticleEntryPage* page2 = entry1.add_pages();
-  page2->set_url("http://example.com/2");
-
-  ArticleEntry entry2;
-  entry2.set_entry_id("id2");
-  entry2.set_title("title1");
-  ArticleEntryPage* page3 = entry2.add_pages();
-  page3->set_url("http://example.com/a1");
-
-  std::vector<ArticleEntry> initial_model;
-  initial_model.push_back(entry1);
-  initial_model.push_back(entry2);
-
-  DomDistillerModel model(initial_model);
-
-  ArticleEntry found_entry;
-  EXPECT_TRUE(model.GetEntryByUrl(GURL(page1->url()), &found_entry));
-  ASSERT_TRUE(IsEntryValid(found_entry));
-  EXPECT_TRUE(AreEntriesEqual(entry1, found_entry));
-
-  EXPECT_TRUE(model.GetEntryByUrl(GURL(page2->url()), &found_entry));
-  ASSERT_TRUE(IsEntryValid(found_entry));
-  EXPECT_TRUE(AreEntriesEqual(entry1, found_entry));
-
-  EXPECT_TRUE(model.GetEntryByUrl(GURL(page3->url()), &found_entry));
-  ASSERT_TRUE(IsEntryValid(found_entry));
-  EXPECT_TRUE(AreEntriesEqual(entry2, found_entry));
-
-  EXPECT_FALSE(model.GetEntryByUrl(GURL("http://example.com/foo"), nullptr));
-}
-
-}  // namespace dom_distiller
diff --git a/components/dom_distiller/core/dom_distiller_service.cc b/components/dom_distiller/core/dom_distiller_service.cc
index 10d6afff..82fa073 100644
--- a/components/dom_distiller/core/dom_distiller_service.cc
+++ b/components/dom_distiller/core/dom_distiller_service.cc
@@ -13,7 +13,6 @@
 #include "base/single_thread_task_runner.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/dom_distiller/core/distilled_content_store.h"
-#include "components/dom_distiller/core/dom_distiller_store.h"
 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
 #include "components/dom_distiller/core/task_tracker.h"
 #include "url/gurl.h"
@@ -35,12 +34,10 @@
 }  // namespace
 
 DomDistillerService::DomDistillerService(
-    std::unique_ptr<DomDistillerStoreInterface> store,
     std::unique_ptr<DistillerFactory> distiller_factory,
     std::unique_ptr<DistillerPageFactory> distiller_page_factory,
     std::unique_ptr<DistilledPagePrefs> distilled_page_prefs)
-    : store_(std::move(store)),
-      content_store_(new InMemoryContentStore(kDefaultMaxNumCachedEntries)),
+    : content_store_(new InMemoryContentStore(kDefaultMaxNumCachedEntries)),
       distiller_factory_(std::move(distiller_factory)),
       distiller_page_factory_(std::move(distiller_page_factory)),
       distilled_page_prefs_(std::move(distilled_page_prefs)) {}
@@ -60,14 +57,10 @@
 }
 
 bool DomDistillerService::HasEntry(const std::string& entry_id) {
-  return store_ && store_->GetEntryById(entry_id, nullptr);
+  return false;
 }
 
 std::string DomDistillerService::GetUrlForEntry(const std::string& entry_id) {
-  ArticleEntry entry;
-  if (store_ && store_->GetEntryById(entry_id, &entry)) {
-    return entry.pages().Get(0).url();
-  }
   return "";
 }
 
@@ -75,22 +68,7 @@
     ViewRequestDelegate* delegate,
     std::unique_ptr<DistillerPage> distiller_page,
     const std::string& entry_id) {
-  ArticleEntry entry;
-  if (!store_ || !store_->GetEntryById(entry_id, &entry)) {
-    return std::unique_ptr<ViewerHandle>();
-  }
-
-  TaskTracker* task_tracker = nullptr;
-  bool was_created = GetOrCreateTaskTrackerForEntry(entry, &task_tracker);
-  std::unique_ptr<ViewerHandle> viewer_handle =
-      task_tracker->AddViewer(delegate);
-  if (was_created) {
-    task_tracker->StartDistiller(distiller_factory_.get(),
-                                 std::move(distiller_page));
-    task_tracker->StartBlobFetcher();
-  }
-
-  return viewer_handle;
+  return nullptr;
 }
 
 std::unique_ptr<ViewerHandle> DomDistillerService::ViewUrl(
@@ -118,11 +96,6 @@
 bool DomDistillerService::GetOrCreateTaskTrackerForUrl(
     const GURL& url,
     TaskTracker** task_tracker) {
-  ArticleEntry entry;
-  if (store_ && store_->GetEntryByUrl(url, &entry)) {
-    return GetOrCreateTaskTrackerForEntry(entry, task_tracker);
-  }
-
   *task_tracker = GetTaskTrackerForUrl(url);
   if (*task_tracker) {
     return false;
@@ -142,28 +115,6 @@
   return nullptr;
 }
 
-TaskTracker* DomDistillerService::GetTaskTrackerForEntry(
-    const ArticleEntry& entry) const {
-  const std::string& entry_id = entry.entry_id();
-  for (auto it = tasks_.begin(); it != tasks_.end(); ++it) {
-    if ((*it)->HasEntryId(entry_id)) {
-      return (*it).get();
-    }
-  }
-  return nullptr;
-}
-
-bool DomDistillerService::GetOrCreateTaskTrackerForEntry(
-    const ArticleEntry& entry,
-    TaskTracker** task_tracker) {
-  *task_tracker = GetTaskTrackerForEntry(entry);
-  if (!*task_tracker) {
-    *task_tracker = CreateTaskTracker(entry);
-    return true;
-  }
-  return false;
-}
-
 TaskTracker* DomDistillerService::CreateTaskTracker(const ArticleEntry& entry) {
   TaskTracker::CancelCallback cancel_callback =
       base::Bind(&DomDistillerService::CancelTask, base::Unretained(this));
diff --git a/components/dom_distiller/core/dom_distiller_service.h b/components/dom_distiller/core/dom_distiller_service.h
index 8532d8a..21ad150 100644
--- a/components/dom_distiller/core/dom_distiller_service.h
+++ b/components/dom_distiller/core/dom_distiller_service.h
@@ -22,7 +22,6 @@
 class DistilledContentStore;
 class DistillerFactory;
 class DistillerPageFactory;
-class DomDistillerStoreInterface;
 class TaskTracker;
 class ViewerHandle;
 class ViewRequestDelegate;
@@ -35,22 +34,9 @@
   typedef base::Callback<void(bool)> ArticleAvailableCallback;
   virtual ~DomDistillerServiceInterface() {}
 
-  // Returns whether an article stored has the given entry id.
+  // TODO(crbug.com/1007942): Remove these methods; no entries ever exist.
   virtual bool HasEntry(const std::string& entry_id) = 0;
-
-  // Returns the source URL given an entry ID. If the entry ID article has
-  // multiple pages, this will return the URL of the first page. Returns an
-  // empty string if there is no entry associated with the given entry ID.
   virtual std::string GetUrlForEntry(const std::string& entry_id) = 0;
-
-  // Request to view an article by entry id. Returns a null pointer if no entry
-  // with |entry_id| exists. The ViewerHandle should be destroyed before the
-  // ViewRequestDelegate. The request will be cancelled when the handle is
-  // destroyed (or when this service is destroyed), which also ensures that
-  // the |delegate| is not called after that.
-  // Use CreateDefaultDistillerPage() to create a default |distiller_page|.
-  // The provided |distiller_page| is only used if there is not already a
-  // distillation task in progress for the given |entry_id|.
   virtual std::unique_ptr<ViewerHandle> ViewEntry(
       ViewRequestDelegate* delegate,
       std::unique_ptr<DistillerPage> distiller_page,
@@ -86,7 +72,6 @@
 class DomDistillerService : public DomDistillerServiceInterface {
  public:
   DomDistillerService(
-      std::unique_ptr<DomDistillerStoreInterface> store,
       std::unique_ptr<DistillerFactory> distiller_factory,
       std::unique_ptr<DistillerPageFactory> distiller_page_factory,
       std::unique_ptr<DistilledPagePrefs> distilled_page_prefs);
@@ -114,20 +99,15 @@
 
   TaskTracker* CreateTaskTracker(const ArticleEntry& entry);
 
-  TaskTracker* GetTaskTrackerForEntry(const ArticleEntry& entry) const;
   TaskTracker* GetTaskTrackerForUrl(const GURL& url) const;
 
-  // Gets the task tracker for the given |url| or |entry|. If no appropriate
+  // Gets the task tracker for the given |url|. If no appropriate
   // tracker exists, this will create one and put it in the |TaskTracker|
   // parameter passed into this function, initialize it, and add it to
-  // |tasks_|. If a |TaskTracker| needed to be created, these functions will
-  // return true.
+  // |tasks_|. Return whether a |TaskTracker| needed to be created.
   bool GetOrCreateTaskTrackerForUrl(const GURL& url,
                                     TaskTracker** task_tracker);
-  bool GetOrCreateTaskTrackerForEntry(const ArticleEntry& entry,
-                                      TaskTracker** task_tracker);
 
-  std::unique_ptr<DomDistillerStoreInterface> store_;
   std::unique_ptr<DistilledContentStore> content_store_;
   std::unique_ptr<DistillerFactory> distiller_factory_;
   std::unique_ptr<DistillerPageFactory> distiller_page_factory_;
diff --git a/components/dom_distiller/core/dom_distiller_service_unittest.cc b/components/dom_distiller/core/dom_distiller_service_unittest.cc
index bb9c859..24f8b0d 100644
--- a/components/dom_distiller/core/dom_distiller_service_unittest.cc
+++ b/components/dom_distiller/core/dom_distiller_service_unittest.cc
@@ -13,8 +13,6 @@
 #include "base/test/task_environment.h"
 #include "components/dom_distiller/core/article_entry.h"
 #include "components/dom_distiller/core/distilled_page_prefs.h"
-#include "components/dom_distiller/core/dom_distiller_model.h"
-#include "components/dom_distiller/core/dom_distiller_store.h"
 #include "components/dom_distiller/core/fake_distiller.h"
 #include "components/dom_distiller/core/fake_distiller_page.h"
 #include "components/dom_distiller/core/task_tracker.h"
@@ -22,7 +20,6 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 using testing::_;
-using testing::Invoke;
 using testing::Return;
 
 namespace dom_distiller {
@@ -30,8 +27,6 @@
 
 namespace {
 
-const char kTestEntryId[] = "id0";
-
 class FakeViewRequestDelegate : public ViewRequestDelegate {
  public:
   ~FakeViewRequestDelegate() override {}
@@ -68,15 +63,9 @@
 class DomDistillerServiceTest : public testing::Test {
  public:
   void SetUp() override {
-    // Create a test entry in the DB.
-    ArticleEntry entry;
-    entry.set_entry_id(kTestEntryId);
-    entry.add_pages()->set_url("http://www.example.com/p1");
-    store_ = new DomDistillerStore({entry});
     distiller_factory_ = new MockDistillerFactory();
     distiller_page_factory_ = new MockDistillerPageFactory();
     service_.reset(new DomDistillerService(
-        std::unique_ptr<DomDistillerStoreInterface>(store_),
         std::unique_ptr<DistillerFactory>(distiller_factory_),
         std::unique_ptr<DistillerPageFactory>(distiller_page_factory_),
         std::unique_ptr<DistilledPagePrefs>()));
@@ -84,38 +73,17 @@
 
   void TearDown() override {
     base::RunLoop().RunUntilIdle();
-    store_ = nullptr;
     distiller_factory_ = nullptr;
     service_.reset();
   }
 
  protected:
   base::test::SingleThreadTaskEnvironment task_environment_;
-  // store is owned by service_.
-  DomDistillerStoreInterface* store_;
   MockDistillerFactory* distiller_factory_;
   MockDistillerPageFactory* distiller_page_factory_;
   std::unique_ptr<DomDistillerService> service_;
 };
 
-TEST_F(DomDistillerServiceTest, TestViewEntry) {
-  FakeDistiller* distiller = new FakeDistiller(false);
-  EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
-      .WillOnce(Return(distiller));
-
-  FakeViewRequestDelegate viewer_delegate;
-  std::unique_ptr<ViewerHandle> handle = service_->ViewEntry(
-      &viewer_delegate, service_->CreateDefaultDistillerPage(gfx::Size()),
-      kTestEntryId);
-
-  ASSERT_FALSE(distiller->GetArticleCallback().is_null());
-
-  std::unique_ptr<DistilledArticleProto> proto = CreateDefaultArticle();
-  EXPECT_CALL(viewer_delegate, OnArticleReady(proto.get()));
-
-  RunDistillerCallback(distiller, std::move(proto));
-}
-
 TEST_F(DomDistillerServiceTest, TestViewUrl) {
   FakeDistiller* distiller = new FakeDistiller(false);
   EXPECT_CALL(*distiller_factory_, CreateDistillerImpl())
diff --git a/components/dom_distiller/core/dom_distiller_store.cc b/components/dom_distiller/core/dom_distiller_store.cc
deleted file mode 100644
index 4f37d22..0000000
--- a/components/dom_distiller/core/dom_distiller_store.cc
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/dom_distiller/core/dom_distiller_store.h"
-
-#include <stddef.h>
-
-#include <utility>
-
-namespace dom_distiller {
-
-DomDistillerStore::DomDistillerStore() {}
-
-DomDistillerStore::DomDistillerStore(
-    const std::vector<ArticleEntry>& initial_data)
-    : model_(initial_data) {}
-
-DomDistillerStore::~DomDistillerStore() {}
-
-bool DomDistillerStore::GetEntryById(const std::string& entry_id,
-                                     ArticleEntry* entry) {
-  return model_.GetEntryById(entry_id, entry);
-}
-
-bool DomDistillerStore::GetEntryByUrl(const GURL& url, ArticleEntry* entry) {
-  return model_.GetEntryByUrl(url, entry);
-}
-
-std::vector<ArticleEntry> DomDistillerStore::GetEntries() const {
-  return model_.GetEntries();
-}
-
-}  // namespace dom_distiller
diff --git a/components/dom_distiller/core/dom_distiller_store.h b/components/dom_distiller/core/dom_distiller_store.h
deleted file mode 100644
index a548c426..0000000
--- a/components/dom_distiller/core/dom_distiller_store.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_STORE_H_
-#define COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_STORE_H_
-
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "components/dom_distiller/core/article_entry.h"
-#include "components/dom_distiller/core/dom_distiller_model.h"
-#include "url/gurl.h"
-
-namespace dom_distiller {
-
-// Interface for accessing the stored DomDistiller entries.
-class DomDistillerStoreInterface {
- public:
-  virtual ~DomDistillerStoreInterface() {}
-
-  // Lookup an ArticleEntry by ID or URL. Returns whether a corresponding entry
-  // was found. On success, if |entry| is not null, it will contain the entry.
-  virtual bool GetEntryById(const std::string& entry_id,
-                            ArticleEntry* entry) = 0;
-  virtual bool GetEntryByUrl(const GURL& url, ArticleEntry* entry) = 0;
-
-  // Gets a copy of all the current entries.
-  virtual std::vector<ArticleEntry> GetEntries() const = 0;
-};
-
-// Implements syncing/storing of DomDistiller entries. This keeps three
-// models of the DOM distiller data in sync: the local database, sync, and the
-// user (i.e. of DomDistillerStore). No changes are accepted while the local
-// database is loading. Once the local database has loaded, changes from any of
-// the three sources (technically just two, since changes don't come from the
-// database) are handled similarly:
-// 1. convert the change to a SyncChangeList.
-// 2. apply that change to the in-memory model, calculating what changed
-// (changes_applied) and what is missing--i.e. entries missing for a full merge,
-// conflict resolution for normal changes-- (changes_missing).
-// 3. send a message (possibly handled asynchronously) containing
-// changes_missing to the source of the change.
-// 4. send messages (possibly handled asynchronously) containing changes_applied
-// to the other (i.e. non-source) two models.
-// TODO(cjhopman): Support deleting entries.
-class DomDistillerStore : public DomDistillerStoreInterface {
- public:
-  DomDistillerStore();
-
-  // Initializes the internal model to |initial_model|.
-  DomDistillerStore(const std::vector<ArticleEntry>& initial_data);
-
-  ~DomDistillerStore() override;
-
-  bool GetEntryById(const std::string& entry_id, ArticleEntry* entry) override;
-  bool GetEntryByUrl(const GURL& url, ArticleEntry* entry) override;
-  std::vector<ArticleEntry> GetEntries() const override;
-
- private:
-  DomDistillerModel model_;
-
-  DISALLOW_COPY_AND_ASSIGN(DomDistillerStore);
-};
-
-}  // namespace dom_distiller
-
-#endif  // COMPONENTS_DOM_DISTILLER_CORE_DOM_DISTILLER_STORE_H_
diff --git a/components/dom_distiller/standalone/content_extractor_browsertest.cc b/components/dom_distiller/standalone/content_extractor_browsertest.cc
index d85a15ab..b0c9284 100644
--- a/components/dom_distiller/standalone/content_extractor_browsertest.cc
+++ b/components/dom_distiller/standalone/content_extractor_browsertest.cc
@@ -28,7 +28,6 @@
 #include "components/dom_distiller/core/distilled_page_prefs.h"
 #include "components/dom_distiller/core/distiller.h"
 #include "components/dom_distiller/core/dom_distiller_service.h"
-#include "components/dom_distiller/core/dom_distiller_store.h"
 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
 #include "components/dom_distiller/core/proto/distilled_page.pb.h"
 #include "components/dom_distiller/core/task_tracker.h"
@@ -131,8 +130,6 @@
   // Setting up PrefService for DistilledPagePrefs.
   DistilledPagePrefs::RegisterProfilePrefs(pref_service->registry());
 
-  auto dom_distiller_store = std::make_unique<DomDistillerStore>();
-
   auto distiller_page_factory =
       std::make_unique<DistillerPageWebContentsFactory>(context);
   auto distiller_url_fetcher_factory =
@@ -166,8 +163,7 @@
       std::move(distiller_url_fetcher_factory), options, file_to_url_map);
 
   return std::make_unique<DomDistillerService>(
-      std::move(dom_distiller_store), std::move(distiller_factory),
-      std::move(distiller_page_factory),
+      std::move(distiller_factory), std::move(distiller_page_factory),
       std::make_unique<DistilledPagePrefs>(pref_service));
 }
 
diff --git a/components/download/internal/common/all_download_event_notifier.cc b/components/download/internal/common/all_download_event_notifier.cc
index e28d8b0..e4bed56 100644
--- a/components/download/internal/common/all_download_event_notifier.cc
+++ b/components/download/internal/common/all_download_event_notifier.cc
@@ -49,7 +49,9 @@
                                     active_downloads_only);
 }
 
-void AllDownloadEventNotifier::OnManagerGoingDown() {
+void AllDownloadEventNotifier::OnManagerGoingDown(
+    SimpleDownloadManagerCoordinator* manager) {
+  DCHECK_EQ(manager, simple_download_manager_coordinator_);
   for (auto& observer : observers_)
     observer.OnManagerGoingDown(simple_download_manager_coordinator_);
   simple_download_manager_coordinator_->RemoveObserver(this);
diff --git a/components/download/internal/common/simple_download_manager_coordinator.cc b/components/download/internal/common/simple_download_manager_coordinator.cc
index 493e99f..dfbf5ae 100644
--- a/components/download/internal/common/simple_download_manager_coordinator.cc
+++ b/components/download/internal/common/simple_download_manager_coordinator.cc
@@ -31,7 +31,7 @@
   if (simple_download_manager_)
     simple_download_manager_->RemoveObserver(this);
   for (auto& observer : observers_)
-    observer.OnManagerGoingDown();
+    observer.OnManagerGoingDown(this);
 }
 
 void SimpleDownloadManagerCoordinator::SetSimpleDownloadManager(
diff --git a/components/download/public/common/all_download_event_notifier.h b/components/download/public/common/all_download_event_notifier.h
index d9a8989..c786702 100644
--- a/components/download/public/common/all_download_event_notifier.h
+++ b/components/download/public/common/all_download_event_notifier.h
@@ -56,7 +56,7 @@
  private:
   // SimpleDownloadManagerCoordinator::Observer
   void OnDownloadsInitialized(bool active_downloads_only) override;
-  void OnManagerGoingDown() override;
+  void OnManagerGoingDown(SimpleDownloadManagerCoordinator* manager) override;
   void OnDownloadCreated(DownloadItem* item) override;
 
   // DownloadItem::Observer
diff --git a/components/download/public/common/simple_download_manager_coordinator.h b/components/download/public/common/simple_download_manager_coordinator.h
index b56adef1..c5d7a5f 100644
--- a/components/download/public/common/simple_download_manager_coordinator.h
+++ b/components/download/public/common/simple_download_manager_coordinator.h
@@ -36,7 +36,8 @@
     Observer() = default;
     virtual ~Observer() = default;
 
-    virtual void OnManagerGoingDown() {}
+    virtual void OnManagerGoingDown(
+        SimpleDownloadManagerCoordinator* coordinator) {}
     virtual void OnDownloadsInitialized(bool active_downloads_only) {}
     virtual void OnDownloadCreated(DownloadItem* item) {}
 
diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc
index 2d3fe18d..41d54b1 100644
--- a/components/exo/client_controlled_shell_surface.cc
+++ b/components/exo/client_controlled_shell_surface.cc
@@ -1024,7 +1024,7 @@
 
   if (wasPip && !window_state->IsMinimized()) {
     // Expanding PIP should end split-view. See crbug.com/941788.
-    ash::Shell::Get()->split_view_controller()->EndSplitView(
+    ash::SplitViewController::Get()->EndSplitView(
         ash::SplitViewController::EndReason::kPipExpanded);
     // As Android doesn't activate PIP tasks after they are expanded, we need
     // to do it here explicitly.
diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc
index 9dbfb039..5d72514 100644
--- a/components/exo/client_controlled_shell_surface_unittest.cc
+++ b/components/exo/client_controlled_shell_surface_unittest.cc
@@ -1003,8 +1003,7 @@
 // Test the snap functionalities in splitscreen in tablet mode.
 TEST_F(ClientControlledShellSurfaceTest, SnapWindowInSplitViewModeTest) {
   UpdateDisplay("807x607");
-  ash::Shell* shell = ash::Shell::Get();
-  shell->tablet_mode_controller()->SetEnabledForTest(true);
+  ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
 
   const gfx::Size buffer_size(800, 600);
   std::unique_ptr<Buffer> buffer1(
@@ -1026,7 +1025,7 @@
 
   // Snap window to left.
   ash::SplitViewController* split_view_controller =
-      shell->split_view_controller();
+      ash::SplitViewController::Get();
   split_view_controller->SnapWindow(window1, ash::SplitViewController::LEFT);
   state1->set_bounds_locally(true);
   window1->SetBounds(split_view_controller->GetSnappedWindowBoundsInScreen(
@@ -2017,7 +2016,7 @@
        ExpandingPipInTabletModeEndsSplitView) {
   EnableTabletMode(true);
 
-  auto* split_view_controller = ash::Shell::Get()->split_view_controller();
+  auto* split_view_controller = ash::SplitViewController::Get();
   EXPECT_FALSE(split_view_controller->InSplitViewMode());
 
   // Create a PIP window:
@@ -2053,7 +2052,7 @@
        DismissingPipInTabletModeDoesNotEndSplitView) {
   EnableTabletMode(true);
 
-  auto* split_view_controller = ash::Shell::Get()->split_view_controller();
+  auto* split_view_controller = ash::SplitViewController::Get();
   EXPECT_FALSE(split_view_controller->InSplitViewMode());
 
   // Create a PIP window:
diff --git a/components/heap_profiling/supervisor.cc b/components/heap_profiling/supervisor.cc
index f5aeda8..c0f7e51 100644
--- a/components/heap_profiling/supervisor.cc
+++ b/components/heap_profiling/supervisor.cc
@@ -125,7 +125,8 @@
   }
 
   auto finished_dump_callback = base::BindOnce(
-      [](TraceFinishedCallback callback, bool success, uint64_t dump_guid) {
+      [](TraceFinishedCallback callback, bool anonymize, bool success,
+         uint64_t dump_guid) {
         // Once the trace has stopped, run |callback| on the UI thread.
         auto finish_sink_callback = base::BindOnce(
             [](TraceFinishedCallback callback,
@@ -137,13 +138,15 @@
                              base::BindOnce(std::move(callback), true,
                                             std::move(result)));
             },
-            base::Passed(std::move(callback)));
+            std::move(callback));
         scoped_refptr<content::TracingController::TraceDataEndpoint> sink =
             content::TracingController::CreateStringEndpoint(
                 std::move(finish_sink_callback));
-        content::TracingController::GetInstance()->StopTracing(sink);
+        content::TracingController::GetInstance()->StopTracing(
+            sink,
+            /*agent_label=*/"", anonymize);
       },
-      std::move(callback));
+      std::move(callback), anonymize);
 
   auto trigger_memory_dump_callback = base::BindOnce(
       [](base::OnceCallback<void(bool success, uint64_t dump_guid)>
diff --git a/components/media_message_center/media_notification_view.h b/components/media_message_center/media_notification_view.h
index 02263b99..ab07f0d 100644
--- a/components/media_message_center/media_notification_view.h
+++ b/components/media_message_center/media_notification_view.h
@@ -90,6 +90,10 @@
       const std::set<media_session::mojom::MediaSessionAction>& actions);
   void UpdateWithMediaArtwork(const gfx::ImageSkia& image);
 
+  const views::Label* title_label_for_testing() const { return title_label_; }
+
+  const views::Label* artist_label_for_testing() const { return artist_label_; }
+
  private:
   friend class MediaNotificationViewTest;
 
diff --git a/components/omnibox/browser/BUILD.gn b/components/omnibox/browser/BUILD.gn
index 6bbed3b..aa0cfc1 100644
--- a/components/omnibox/browser/BUILD.gn
+++ b/components/omnibox/browser/BUILD.gn
@@ -46,10 +46,10 @@
     "extension_app.icon",
     "find_in_page.icon",
     "http.icon",
-    "https_invalid.icon",
     "https_valid.icon",
     "https_valid_in_chip.icon",
     "keyword_search.icon",
+    "not_secure_warning.icon",
     "offline_pin.icon",
     "page.icon",
     "pedal.icon",
diff --git a/components/omnibox/browser/location_bar_model_impl.cc b/components/omnibox/browser/location_bar_model_impl.cc
index 3ea39cb8..6d18ad3 100644
--- a/components/omnibox/browser/location_bar_model_impl.cc
+++ b/components/omnibox/browser/location_bar_model_impl.cc
@@ -195,7 +195,7 @@
     case security_state::SECURE_WITH_POLICY_INSTALLED_CERT:
       return vector_icons::kBusinessIcon;
     case security_state::DANGEROUS:
-      return omnibox::kHttpsInvalidIcon;
+      return omnibox::kNotSecureWarningIcon;
     case security_state::SECURITY_LEVEL_COUNT:
       NOTREACHED();
       return omnibox::kHttpIcon;
diff --git a/components/omnibox/browser/vector_icons/https_invalid.icon b/components/omnibox/browser/vector_icons/not_secure_warning.icon
similarity index 100%
rename from components/omnibox/browser/vector_icons/https_invalid.icon
rename to components/omnibox/browser/vector_icons/not_secure_warning.icon
diff --git a/components/pref_registry/pref_registry_syncable.cc b/components/pref_registry/pref_registry_syncable.cc
index a7b4fe9d..674e26a 100644
--- a/components/pref_registry/pref_registry_syncable.cc
+++ b/components/pref_registry/pref_registry_syncable.cc
@@ -47,14 +47,4 @@
   return registry;
 }
 
-void PrefRegistrySyncable::WhitelistLateRegistrationPrefForSync(
-    const std::string& pref_name) {
-  sync_unknown_prefs_whitelist_.insert(pref_name);
-}
-
-bool PrefRegistrySyncable::IsWhitelistedLateRegistrationPref(
-    const std::string& path) const {
-  return sync_unknown_prefs_whitelist_.count(path) != 0;
-}
-
 }  // namespace user_prefs
diff --git a/components/pref_registry/pref_registry_syncable.h b/components/pref_registry/pref_registry_syncable.h
index 9533e15..b2f630a4 100644
--- a/components/pref_registry/pref_registry_syncable.h
+++ b/components/pref_registry/pref_registry_syncable.h
@@ -7,7 +7,6 @@
 
 #include <stdint.h>
 
-#include <set>
 #include <string>
 
 #include "base/callback.h"
@@ -70,16 +69,6 @@
   // store.
   scoped_refptr<PrefRegistrySyncable> ForkForIncognito();
 
-  // Adds a the preference with name |pref_name| to the whitelist of prefs which
-  // will be synced even before they got registered. Note that it's still
-  // illegal to read or write a whitelisted preference via the PrefService
-  // before its registration.
-  void WhitelistLateRegistrationPrefForSync(const std::string& pref_name);
-
-  // Checks weather the preference with name |path| is on the whitelist of
-  // sync-supported prefs before registration.
-  bool IsWhitelistedLateRegistrationPref(const std::string& path) const;
-
  private:
   ~PrefRegistrySyncable() override;
 
@@ -88,7 +77,6 @@
                         uint32_t flags) override;
 
   SyncableRegistrationCallback callback_;
-  std::set<std::string> sync_unknown_prefs_whitelist_;
 
   DISALLOW_COPY_AND_ASSIGN(PrefRegistrySyncable);
 };
diff --git a/components/security_state/core/features.cc b/components/security_state/core/features.cc
index 961cee5..9dcd8b9 100644
--- a/components/security_state/core/features.cc
+++ b/components/security_state/core/features.cc
@@ -18,5 +18,8 @@
 const base::Feature kLegacyTLSWarnings{"LegacyTLSWarnings",
                                        base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kSafetyTipUI{"SafetyTip",
+                                 base::FEATURE_DISABLED_BY_DEFAULT};
+
 }  // namespace features
 }  // namespace security_state
diff --git a/components/security_state/core/features.h b/components/security_state/core/features.h
index 1e7f4721..8e8bd50 100644
--- a/components/security_state/core/features.h
+++ b/components/security_state/core/features.h
@@ -33,6 +33,9 @@
 // TLS version (TLS 1.0 or 1.1).
 extern const base::Feature kLegacyTLSWarnings;
 
+// This feature enables Safety Tip warnings on possibly-risky sites.
+extern const base::Feature kSafetyTipUI;
+
 }  // namespace features
 }  // namespace security_state
 
diff --git a/components/security_state/core/security_state.cc b/components/security_state/core/security_state.cc
index 88f048f5..b282c32b 100644
--- a/components/security_state/core/security_state.cc
+++ b/components/security_state/core/security_state.cc
@@ -90,6 +90,31 @@
   return std::string();
 }
 
+// Returns whether to set the security level based on the safety tip status.
+// Sets |level| to the right value if status should be set.
+bool ShouldSetSecurityLevelFromSafetyTip(security_state::SafetyTipStatus status,
+                                         SecurityLevel* level) {
+  if (!base::FeatureList::IsEnabled(security_state::features::kSafetyTipUI)) {
+    return false;
+  }
+
+  switch (status) {
+    case security_state::SafetyTipStatus::kBadReputation:
+      *level = security_state::NONE;
+      return true;
+    case security_state::SafetyTipStatus::kLookalike:
+      // PageInfo doesn't differ for lookalikes, so don't degrade the indicator.
+    case security_state::SafetyTipStatus::kBadKeyword:
+      // TODO(crbug/1012982): Decide whether to degrade the indicator once the
+      // UI lands.
+    case security_state::SafetyTipStatus::kUnknown:
+    case security_state::SafetyTipStatus::kNone:
+      return false;
+  }
+  NOTREACHED();
+  return false;
+}
+
 }  // namespace
 
 SecurityLevel GetSecurityLevel(
@@ -163,6 +188,13 @@
     return WARNING;
   }
 
+  // Downgrade the security level for pages that trigger a Safety Tip.
+  SecurityLevel safety_tip_level;
+  if (ShouldSetSecurityLevelFromSafetyTip(
+          visible_security_state.safety_tip_status, &safety_tip_level)) {
+    return safety_tip_level;
+  }
+
   // In most cases, SHA1 use is treated as a certificate error, in which case
   // DANGEROUS will have been returned above. If SHA1 was permitted by policy,
   // downgrade the security level to Neutral.
diff --git a/components/security_state/core/security_state_unittest.cc b/components/security_state/core/security_state_unittest.cc
index af0a2b3..8927fcda 100644
--- a/components/security_state/core/security_state_unittest.cc
+++ b/components/security_state/core/security_state_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/test/scoped_feature_list.h"
 #include "components/security_state/core/features.h"
 #include "components/security_state/core/insecure_input_event_data.h"
+#include "components/security_state/core/security_state.h"
 #include "net/cert/x509_certificate.h"
 #include "net/ssl/ssl_cipher_suite_names.h"
 #include "net/ssl/ssl_connection_status_flags.h"
@@ -59,7 +60,8 @@
         malicious_content_status_(MALICIOUS_CONTENT_STATUS_NONE),
         is_error_page_(false),
         is_view_source_(false),
-        has_policy_certificate_(false) {}
+        has_policy_certificate_(false),
+        safety_tip_status_(security_state::SafetyTipStatus::kUnknown) {}
   virtual ~TestSecurityStateHelper() {}
 
   void SetCertificate(scoped_refptr<net::X509Certificate> cert) {
@@ -105,6 +107,11 @@
   }
   void SetUrl(const GURL& url) { url_ = url; }
 
+  void set_safety_tip_status(
+      security_state::SafetyTipStatus safety_tip_status) {
+    safety_tip_status_ = safety_tip_status;
+  }
+
   std::unique_ptr<VisibleSecurityState> GetVisibleSecurityState() const {
     auto state = std::make_unique<VisibleSecurityState>();
     state->connection_info_initialized = true;
@@ -119,6 +126,7 @@
     state->is_error_page = is_error_page_;
     state->is_view_source = is_view_source_;
     state->insecure_input_events = insecure_input_events_;
+    state->safety_tip_status = safety_tip_status_;
     return state;
   }
 
@@ -145,6 +153,7 @@
   bool is_view_source_;
   bool has_policy_certificate_;
   InsecureInputEventData insecure_input_events_;
+  security_state::SafetyTipStatus safety_tip_status_;
 };
 
 }  // namespace
@@ -355,6 +364,36 @@
   EXPECT_EQ(DANGEROUS, helper.GetSecurityLevel());
 }
 
+// Tests that |safety_tip_status| effects security level appropriately.
+TEST(SecurityStateTest, SafetyTipSometimesRemovesSecure) {
+  using security_state::SafetyTipStatus;
+
+  struct SafetyTipCase {
+    SafetyTipStatus safety_tip_status;
+    security_state::SecurityLevel expected_level;
+  };
+
+  const SafetyTipCase kTestCases[] = {
+      {SafetyTipStatus::kUnknown, SECURE},
+      {SafetyTipStatus::kNone, SECURE},
+      {SafetyTipStatus::kBadReputation, NONE},
+      {SafetyTipStatus::kLookalike, SECURE},
+      {SafetyTipStatus::kBadKeyword, SECURE},
+  };
+
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      security_state::features::kSafetyTipUI);
+
+  for (auto testcase : kTestCases) {
+    TestSecurityStateHelper helper;
+    helper.set_cert_status(0);
+    EXPECT_EQ(SECURE, helper.GetSecurityLevel());
+    helper.set_safety_tip_status(testcase.safety_tip_status);
+    EXPECT_EQ(testcase.expected_level, helper.GetSecurityLevel());
+  }
+}
+
 // Tests IsSchemeCryptographic function.
 TEST(SecurityStateTest, CryptographicSchemeUrl) {
   // HTTPS is a cryptographic scheme.
diff --git a/components/sync_preferences/BUILD.gn b/components/sync_preferences/BUILD.gn
index e55d6b6..ed1dbe4 100644
--- a/components/sync_preferences/BUILD.gn
+++ b/components/sync_preferences/BUILD.gn
@@ -17,8 +17,6 @@
     "synced_pref_change_registrar.cc",
     "synced_pref_change_registrar.h",
     "synced_pref_observer.h",
-    "unknown_user_pref_accessor.cc",
-    "unknown_user_pref_accessor.h",
   ]
 
   deps = [
diff --git a/components/sync_preferences/pref_model_associator.cc b/components/sync_preferences/pref_model_associator.cc
index 266b7b4..6cf1f4d 100644
--- a/components/sync_preferences/pref_model_associator.cc
+++ b/components/sync_preferences/pref_model_associator.cc
@@ -22,6 +22,7 @@
 #include "base/values.h"
 #include "components/prefs/persistent_pref_store.h"
 #include "components/prefs/pref_service.h"
+#include "components/sync/base/model_type.h"
 #include "components/sync/model/sync_change.h"
 #include "components/sync/model/sync_change_processor.h"
 #include "components/sync/model/sync_error_factory.h"
@@ -29,7 +30,6 @@
 #include "components/sync/protocol/sync.pb.h"
 #include "components/sync_preferences/pref_model_associator_client.h"
 #include "components/sync_preferences/pref_service_syncable.h"
-#include "components/sync_preferences/synced_pref_observer.h"
 
 using syncer::PREFERENCES;
 using syncer::PRIORITY_PREFERENCES;
@@ -65,10 +65,11 @@
 PrefModelAssociator::PrefModelAssociator(
     const PrefModelAssociatorClient* client,
     syncer::ModelType type,
-    UnknownUserPrefAccessor* accessor)
-    : pref_accessor_(accessor), type_(type), client_(client) {
+    PersistentPrefStore* user_pref_store)
+    : type_(type), client_(client), user_pref_store_(user_pref_store) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(type_ == PREFERENCES || type_ == PRIORITY_PREFERENCES);
+  DCHECK(user_pref_store_);
 }
 
 PrefModelAssociator::~PrefModelAssociator() {
@@ -82,15 +83,8 @@
     const syncer::SyncData& sync_pref,
     const std::string& pref_name,
     syncer::SyncChangeList* sync_changes) {
-  UnknownUserPrefAccessor::PreferenceState local_pref_state =
-      pref_accessor_->GetPreferenceState(type_, pref_name);
-  if (local_pref_state.registration_state ==
-          UnknownUserPrefAccessor::RegistrationState::kUnknown ||
-      local_pref_state.registration_state ==
-          UnknownUserPrefAccessor::RegistrationState::kNotSyncable) {
-    // Only process syncable prefs and unknown prefs if whitelisted.
-    return;
-  }
+  const base::Value* user_pref_value =
+      pref_service_->GetUserPrefValue(pref_name);
   VLOG(1) << "Associating preference " << pref_name;
 
   if (sync_pref.IsValid()) {
@@ -105,20 +99,20 @@
       return;
     }
 
-    if (local_pref_state.persisted_value) {
+    if (user_pref_value) {
       DVLOG(1) << "Found user pref value for " << pref_name;
       // We have both server and local values. Merge them.
-      std::unique_ptr<base::Value> new_value(MergePreference(
-          pref_name, *local_pref_state.persisted_value, *sync_value));
+      std::unique_ptr<base::Value> new_value(
+          MergePreference(pref_name, *user_pref_value, *sync_value));
 
       // Update the local preference based on what we got from the
       // sync server. Note: this only updates the user value store, which is
       // ignored if the preference is policy controlled.
       if (new_value->is_none()) {
         LOG(WARNING) << "Sync has null value for pref " << pref_name.c_str();
-        pref_accessor_->ClearPref(pref_name, local_pref_state);
-      } else if (!local_pref_state.persisted_value->Equals(new_value.get())) {
-        pref_accessor_->SetPref(pref_name, local_pref_state, *new_value);
+        pref_service_->ClearPref(pref_name);
+      } else if (!user_pref_value->Equals(new_value.get())) {
+        SetPrefWithTypeCheck(pref_name, *new_value);
       }
 
       // If the merge resulted in an updated value, inform the syncer.
@@ -134,19 +128,16 @@
       }
     } else if (!sync_value->is_none()) {
       // Only a server value exists. Just set the local user value.
-      pref_accessor_->SetPref(pref_name, local_pref_state, *sync_value);
+      SetPrefWithTypeCheck(pref_name, *sync_value);
     } else {
       LOG(WARNING) << "Sync has null value for pref " << pref_name.c_str();
     }
     synced_preferences_.insert(preference.name());
-  } else if (local_pref_state.persisted_value) {
-    DCHECK_EQ(local_pref_state.registration_state,
-              UnknownUserPrefAccessor::RegistrationState::kSyncable);
+  } else if (user_pref_value) {
     // The server does not know about this preference and should be added
     // to the syncer's database.
     syncer::SyncData sync_data;
-    if (!CreatePrefSyncData(pref_name, *local_pref_state.persisted_value,
-                            &sync_data)) {
+    if (!CreatePrefSyncData(pref_name, *user_pref_value, &sync_data)) {
       LOG(ERROR) << "Failed to update preference.";
       return;
     }
@@ -194,6 +185,16 @@
 
     const sync_pb::PreferenceSpecifics& preference = GetSpecifics(*sync_iter);
     std::string sync_pref_name = preference.name();
+
+    if (remaining_preferences.count(sync_pref_name) == 0) {
+      // We're not syncing this preference locally, ignore the sync data.
+      // TODO(zea): Eventually we want to be able to have the syncable service
+      // reconstruct all sync data for its datatype (therefore having
+      // GetAllSyncData be a complete representation). We should store this
+      // data somewhere, even if we don't use it.
+      continue;
+    }
+
     remaining_preferences.erase(sync_pref_name);
     InitPrefAndAssociate(*sync_iter, sync_pref_name, &new_changes);
   }
@@ -204,9 +205,6 @@
     InitPrefAndAssociate(syncer::SyncData(), *pref_name_iter, &new_changes);
   }
 
-  UMA_HISTOGRAM_COUNTS_1000("Sync.Preferences.SyncingUnknownPrefs",
-                            pref_accessor_->GetNumberOfSyncingUnknownPrefs());
-
   // Push updates to sync.
   merge_result.set_error(
       sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
@@ -327,6 +325,10 @@
   return result;
 }
 
+// Note: This will build a model of all preferences registered as syncable
+// with user controlled data. We do not track any information for preferences
+// not registered locally as syncable and do not inform the syncer of
+// non-user controlled preferences.
 syncer::SyncDataList PrefModelAssociator::GetAllSyncData(
     syncer::ModelType type) const {
   DCHECK_EQ(type_, type);
@@ -334,10 +336,6 @@
   for (auto iter = synced_preferences_.begin();
        iter != synced_preferences_.end(); ++iter) {
     std::string name = *iter;
-    if (pref_accessor_->GetPreferenceState(type_, name).registration_state !=
-        UnknownUserPrefAccessor::RegistrationState::kSyncable) {
-      continue;
-    }
     const PrefService::Preference* pref = pref_service_->FindPreference(name);
     DCHECK(pref);
     if (!pref->IsUserControlled() || pref->IsDefaultValue())
@@ -367,24 +365,16 @@
     const sync_pb::PreferenceSpecifics& pref_specifics =
         GetSpecifics(iter->sync_data());
 
-    UnknownUserPrefAccessor::PreferenceState local_pref_state =
-        pref_accessor_->GetPreferenceState(type_, pref_specifics.name());
-    if (local_pref_state.registration_state ==
-        UnknownUserPrefAccessor::RegistrationState::kUnknown) {
-      // It is possible that we may receive a change to a preference we do not
-      // want to sync. For example if the user is syncing a Mac client and a
-      // Windows client, the Windows client does not support
-      // kConfirmToQuitEnabled. Ignore updates from these preferences.
-      // We only sync such prefs if they are whitelisted.
+    // It is possible that we may receive a change to a preference we do not
+    // want to sync. For example if the user is syncing a Mac client and a
+    // Windows client, the Windows client does not support
+    // kConfirmToQuitEnabled. Ignore updates from these preferences.
+    std::string pref_name = pref_specifics.name();
+    if (!IsPrefRegistered(pref_name))
       continue;
-    }
-    if (local_pref_state.registration_state ==
-        UnknownUserPrefAccessor::RegistrationState::kNotSyncable) {
-      // Don't process remote changes for prefs this client doesn't want synced.
-      continue;
-    }
+
     if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
-      pref_accessor_->ClearPref(pref_specifics.name(), local_pref_state);
+      pref_service_->ClearPref(pref_name);
       continue;
     }
 
@@ -397,17 +387,24 @@
       continue;
     }
 
+    if (!TypeMatchesUserPrefStore(pref_name, *new_value)) {
+      // Ignore updates where the server type doesn't match the local type.
+      // Don't use SetPrefWithTypeCheck() because we want to skip notifying
+      // observers and inserting into |synced_preferences_|.
+      continue;
+    }
+
     // This will only modify the user controlled value store, which takes
     // priority over the default value but is ignored if the preference is
     // policy controlled.
-    pref_accessor_->SetPref(pref_specifics.name(), local_pref_state,
-                            *new_value);
+    pref_service_->Set(pref_name, *new_value);
 
     NotifySyncedPrefObservers(pref_specifics.name(), true /*from_sync*/);
 
-    // Keep track of any newly synced preferences. This can happen if a
-    // preference was late registered or remotely added (ACTION_ADD).
-    synced_preferences_.insert(pref_specifics.name());
+    // Keep track of any newly synced preferences.
+    if (iter->change_type() == syncer::SyncChange::ACTION_ADD) {
+      synced_preferences_.insert(pref_specifics.name());
+    }
   }
   return syncer::SyncError();
 }
@@ -450,15 +447,14 @@
 }
 
 void PrefModelAssociator::RegisterPref(const std::string& name) {
-  DCHECK(!registered_preferences_.count(name));
+  DCHECK(registered_preferences_.count(name) == 0);
   registered_preferences_.insert(name);
 
-  // This pref might be registered after sync started. Make sure data in the
-  // local store matches the registered type.
+  // Make sure data in the local store matches the registered type.
   // If this results in a modification of the local pref store, we don't want
   // to tell ChromeSync about these -- it's a local anomaly,
   base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
-  pref_accessor_->EnforceRegisteredTypeInStore(name);
+  EnforceRegisteredTypeInStore(name);
 }
 
 bool PrefModelAssociator::IsPrefRegistered(const std::string& name) const {
@@ -474,9 +470,6 @@
   if (!models_associated_)
     return;
 
-  // From now on, this method does not have to deal with lazily registered
-  // prefs, as local changes can only happen after they were registered.
-
   const PrefService::Preference* preference =
       pref_service_->FindPreference(name);
   // TODO(tschumann): When can this ever happen? Should this be a DCHECK?
@@ -505,10 +498,9 @@
   NotifySyncedPrefObservers(name, false /*from_sync*/);
 
   if (synced_preferences_.count(name) == 0) {
-    // Not in synced_preferences_ means no synced data.
-    // InitPrefAndAssociate(..) will determine if we care about its data (e.g.
-    // if it has a default value and hasn't been changed yet we don't) and
-    // take care syncing any new data.
+    // Not in synced_preferences_ means no synced data. InitPrefAndAssociate(..)
+    // will determine if we care about its data (e.g. if it has a default value
+    // and hasn't been changed yet we don't) and take care syncing any new data.
     InitPrefAndAssociate(syncer::SyncData(), name, &changes);
   } else {
     // We are already syncing this preference, just update or delete its sync
@@ -518,7 +510,7 @@
       LOG(ERROR) << "Failed to update preference.";
       return;
     }
-    if (pref_accessor_->GetPreferenceState(type_, name).persisted_value) {
+    if (pref_service_->GetUserPrefValue(name)) {
       // If the pref was updated, update it.
       changes.push_back(syncer::SyncChange(
           FROM_HERE, syncer::SyncChange::ACTION_UPDATE, sync_data));
@@ -547,4 +539,49 @@
     observer.OnSyncedPrefChanged(path, from_sync);
 }
 
+void PrefModelAssociator::SetPrefWithTypeCheck(const std::string& pref_name,
+                                               const base::Value& new_value) {
+  if (TypeMatchesUserPrefStore(pref_name, new_value))
+    pref_service_->Set(pref_name, new_value);
+}
+
+bool PrefModelAssociator::TypeMatchesUserPrefStore(
+    const std::string& pref_name,
+    const base::Value& new_value) const {
+  const base::Value* local_value = nullptr;
+  user_pref_store_->GetValue(pref_name, &local_value);
+  if (!local_value || local_value->type() == new_value.type())
+    return true;
+
+  UMA_HISTOGRAM_BOOLEAN("Sync.Preferences.RemotePrefTypeMismatch", true);
+  DLOG(WARNING) << "Unexpected type mis-match for pref. "
+                << "Synced value for " << pref_name << " is of type "
+                << new_value.type() << " which doesn't match the locally "
+                << "present pref type: " << local_value->type();
+  return false;
+}
+
+void PrefModelAssociator::EnforceRegisteredTypeInStore(
+    const std::string& pref_name) {
+  const base::Value* persisted_value = nullptr;
+  if (user_pref_store_->GetValue(pref_name, &persisted_value)) {
+    // Get the registered type (typically from the default value).
+    const PrefService::Preference* pref =
+        pref_service_->FindPreference(pref_name);
+    DCHECK(pref);
+    if (pref->GetType() != persisted_value->type()) {
+      // We see conflicting type information and there's a chance the local
+      // type-conflicting data came in via sync. Remove it.
+      // TODO(tschumann): The value should get removed silently. Add a method
+      // RemoveValueSilently() to WriteablePrefStore. Note, that as of today
+      // that removal will only notify other pref stores but not sync -- that's
+      // done on a higher level.
+      user_pref_store_->RemoveValue(
+          pref_name, WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+      UMA_HISTOGRAM_BOOLEAN("Sync.Preferences.ClearedLocalPrefOnTypeMismatch",
+                            true);
+    }
+  }
+}
+
 }  // namespace sync_preferences
diff --git a/components/sync_preferences/pref_model_associator.h b/components/sync_preferences/pref_model_associator.h
index 4271095d..6b39aa1 100644
--- a/components/sync_preferences/pref_model_associator.h
+++ b/components/sync_preferences/pref_model_associator.h
@@ -17,7 +17,9 @@
 #include "base/sequence_checker.h"
 #include "components/sync/model/sync_data.h"
 #include "components/sync/model/syncable_service.h"
-#include "components/sync_preferences/unknown_user_pref_accessor.h"
+#include "components/sync_preferences/synced_pref_observer.h"
+
+class PersistentPrefStore;
 
 namespace base {
 class Value;
@@ -31,7 +33,6 @@
 
 class PrefModelAssociatorClient;
 class PrefServiceSyncable;
-class SyncedPrefObserver;
 
 // Contains all preference sync related logic.
 // TODO(sync): Merge this into PrefService once we separate the profile
@@ -39,11 +40,11 @@
 class PrefModelAssociator : public syncer::SyncableService {
  public:
   // Constructs a PrefModelAssociator initializing the |client_| and |type_|
-  // instance variable. |client| and |accessor| are not owned by this object
-  // and the caller must ensure they outlive the PrefModelAssociator.
+  // instance variable. The |client| and |user_pref_store| are not owned by this
+  // object and they must outlive the PrefModelAssociator.
   PrefModelAssociator(const PrefModelAssociatorClient* client,
                       syncer::ModelType type,
-                      UnknownUserPrefAccessor* accessor);
+                      PersistentPrefStore* user_pref_store);
   ~PrefModelAssociator() override;
 
   // See description above field for details.
@@ -66,9 +67,6 @@
   // inform the syncer of non-user controlled preferences.
   syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override;
 
-  // TODO(tschumann): Replace the RegisterPref() call with a
-  // VerifyPersistedPrefType() method. All pref registration checks are now
-  // done via the registry; no need to duplicate that concept.
   // Register a preference with the specified name for syncing. We do not care
   // about the type at registration time, but when changes arrive from the
   // syncer, we check if they can be applied and if not drop them.
@@ -80,9 +78,6 @@
   // sent to the syncer.
   void ProcessPrefChange(const std::string& name);
 
-  // TODO(tschumann): Remove the associator's dependency on PrefServiceSyncable.
-  // It's only needed for calling OnIsSyncingChanged. This logic can be moved
-  // onto the associator: PrefServiceSyncable forwards the registration calls.
   void SetPrefService(PrefServiceSyncable* pref_service);
 
   // Merges the local_value into the supplied server_value and returns
@@ -145,6 +140,22 @@
 
   void NotifySyncedPrefObservers(const std::string& path, bool from_sync) const;
 
+  // Sets |pref_name| to |new_value| if |new_value| has an appropriate type for
+  // this preference. Otherwise records metrics and logs a warning.
+  void SetPrefWithTypeCheck(const std::string& pref_name,
+                            const base::Value& new_value);
+
+  // Returns true if the |new_value| for |pref_name| has the same type as the
+  // existing value in the user's local pref store. If the types don't match,
+  // records metrics and logs a warning.
+  bool TypeMatchesUserPrefStore(const std::string& pref_name,
+                                const base::Value& new_value) const;
+
+  // Verifies that the type which preference |pref_name| was registered with
+  // matches the type of any persisted value. On mismatch, the persisted value
+  // gets removed.
+  void EnforceRegisteredTypeInStore(const std::string& pref_name);
+
   // Do we have an active association between the preferences and sync models?
   // Set when start syncing, reset in StopSyncing. While this is not set, we
   // ignore any local preference changes (when we start syncing we will look
@@ -173,9 +184,6 @@
   // The PrefService we are syncing to.
   PrefServiceSyncable* pref_service_ = nullptr;
 
-  // A pref accessor to access prefs which might not be registered.
-  UnknownUserPrefAccessor* pref_accessor_;
-
   // Sync's syncer::SyncChange handler. We push all our changes through this.
   std::unique_ptr<syncer::SyncChangeProcessor> sync_processor_;
 
@@ -193,9 +201,10 @@
       base::ObserverList<SyncedPrefObserver>::Unchecked;
   std::unordered_map<std::string, std::unique_ptr<SyncedPrefObserverList>>
       synced_pref_observers_;
-
   const PrefModelAssociatorClient* client_;  // Weak.
 
+  PersistentPrefStore* const user_pref_store_;
+
   SEQUENCE_CHECKER(sequence_checker_);
 
   DISALLOW_COPY_AND_ASSIGN(PrefModelAssociator);
diff --git a/components/sync_preferences/pref_service_syncable.cc b/components/sync_preferences/pref_service_syncable.cc
index 7cdc8ed..e9a9698d 100644
--- a/components/sync_preferences/pref_service_syncable.cc
+++ b/components/sync_preferences/pref_service_syncable.cc
@@ -42,18 +42,17 @@
     bool async)
     : PrefService(std::move(pref_notifier),
                   std::move(pref_value_store),
-                  std::move(user_prefs),
+                  user_prefs,
                   pref_registry,
                   std::move(read_error_callback),
                   async),
       pref_service_forked_(false),
-      unknown_pref_accessor_(this, pref_registry.get(), user_pref_store_.get()),
       pref_sync_associator_(pref_model_associator_client,
                             syncer::PREFERENCES,
-                            &unknown_pref_accessor_),
+                            user_prefs.get()),
       priority_pref_sync_associator_(pref_model_associator_client,
                                      syncer::PRIORITY_PREFERENCES,
-                                     &unknown_pref_accessor_),
+                                     user_prefs.get()),
       pref_registry_(std::move(pref_registry)) {
   pref_sync_associator_.SetPrefService(this);
   priority_pref_sync_associator_.SetPrefService(this);
@@ -180,12 +179,8 @@
     uint32_t flags) {
   DCHECK(FindPreference(path));
   if (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PREF) {
-    DCHECK(!pref_sync_associator_.models_associated() ||
-           pref_registry_->IsWhitelistedLateRegistrationPref(path));
     pref_sync_associator_.RegisterPref(path);
   } else if (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF) {
-    DCHECK(!priority_pref_sync_associator_.models_associated() ||
-           pref_registry_->IsWhitelistedLateRegistrationPref(path));
     priority_pref_sync_associator_.RegisterPref(path);
   }
 }
diff --git a/components/sync_preferences/pref_service_syncable.h b/components/sync_preferences/pref_service_syncable.h
index 85e3384..8013edb 100644
--- a/components/sync_preferences/pref_service_syncable.h
+++ b/components/sync_preferences/pref_service_syncable.h
@@ -18,7 +18,6 @@
 #include "components/prefs/pref_value_store.h"
 #include "components/sync_preferences/pref_model_associator.h"
 #include "components/sync_preferences/synced_pref_observer.h"
-#include "components/sync_preferences/unknown_user_pref_accessor.h"
 
 namespace syncer {
 class SyncableService;
@@ -106,7 +105,6 @@
   // "forked" PrefService.
   bool pref_service_forked_;
 
-  UnknownUserPrefAccessor unknown_pref_accessor_;
   PrefModelAssociator pref_sync_associator_;
   PrefModelAssociator priority_pref_sync_associator_;
   const scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry_;
diff --git a/components/sync_preferences/pref_service_syncable_unittest.cc b/components/sync_preferences/pref_service_syncable_unittest.cc
index 85fda24..5e2d214 100644
--- a/components/sync_preferences/pref_service_syncable_unittest.cc
+++ b/components/sync_preferences/pref_service_syncable_unittest.cc
@@ -538,47 +538,6 @@
   EXPECT_TRUE(GetPreferenceValue(kDictPrefName).Equals(&expected_dict));
 }
 
-TEST_F(PrefServiceSyncableMergeTest, InitWithUnknownPrefsValue) {
-  base::HistogramTester histogram_tester;
-  const std::string pref_name1 = "testing.whitelisted_pref1";
-  const std::string pref_name2 = "testing.whitelisted_pref2";
-  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name1);
-  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name2);
-
-  syncer::SyncDataList in;
-  AddToRemoteDataList(pref_name1, base::Value("remote_value1"), &in);
-  AddToRemoteDataList(pref_name2, base::Value("remote_value2"), &in);
-  syncer::SyncChangeList out;
-  InitWithSyncDataTakeOutput(in, &out);
-  pref_registry_->RegisterStringPref(
-      pref_name1, "default_value",
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  EXPECT_THAT(GetPreferenceValue(pref_name1).GetString(), Eq("remote_value1"));
-
-  histogram_tester.ExpectBucketCount("Sync.Preferences.SyncingUnknownPrefs", 2,
-                                     1);
-}
-
-TEST_F(PrefServiceSyncableMergeTest, ReceiveUnknownPrefsValue) {
-  base::HistogramTester histogram_tester;
-  const std::string pref_name = "testing.whitelisted_pref";
-  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name);
-
-  syncer::SyncChangeList out;
-  InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
-
-  syncer::SyncChangeList remote_changes;
-  remote_changes.push_back(MakeRemoteChange(
-      1, pref_name, base::Value("remote_value"), SyncChange::ACTION_UPDATE));
-  pref_sync_service_->ProcessSyncChanges(FROM_HERE, remote_changes);
-  EXPECT_THAT(prefs_.IsPrefSynced(pref_name), Eq(true));
-
-  pref_registry_->RegisterStringPref(
-      pref_name, "default_value",
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-  EXPECT_THAT(GetPreferenceValue(pref_name).GetString(), Eq("remote_value"));
-}
-
 TEST_F(PrefServiceSyncableMergeTest, KeepPriorityPreferencesSeparately) {
   base::HistogramTester histogram_tester;
   const std::string pref_name = "testing.priority_pref";
@@ -608,28 +567,20 @@
 
 TEST_F(PrefServiceSyncableMergeTest, RegisterShouldClearTypeMismatchingData) {
   base::HistogramTester histogram_tester;
-  const std::string pref_name = "testing.whitelisted_pref";
-  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name);
+  const std::string pref_name = "testing.pref";
+  user_prefs_->SetString(pref_name, "string_value");
+  ASSERT_TRUE(user_prefs_->GetValue(pref_name, nullptr));
+
   // Make sure no changes will be communicated to any synced pref listeners
   // (those listeners are typically only used for metrics but we still don't
   // want to inform them).
   ShouldNotBeNotifedObserver observer;
   prefs_.AddSyncedPrefObserver(pref_name, &observer);
-  syncer::SyncDataList in;
-  AddToRemoteDataList(pref_name, base::Value("remote_value"), &in);
-  syncer::SyncChangeList out;
-  InitWithSyncDataTakeOutput(in, &out);
-  ASSERT_THAT(out, IsEmpty());
-
-  EXPECT_TRUE(user_prefs_->GetValue(pref_name, nullptr));
 
   pref_registry_->RegisterListPref(
       pref_name, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
   EXPECT_TRUE(GetPreferenceValue(pref_name).GetList().empty());
   EXPECT_FALSE(user_prefs_->GetValue(pref_name, nullptr));
-  // Make sure the removal of the value was not communicated to sync via the
-  // SyncProcessor.
-  EXPECT_THAT(out, IsEmpty());
 
   histogram_tester.ExpectBucketCount(
       "Sync.Preferences.ClearedLocalPrefOnTypeMismatch", true, 1);
@@ -655,33 +606,6 @@
   EXPECT_THAT(GetPreferenceValue(pref_name).GetString(), Eq("default_value"));
 }
 
-TEST_F(PrefServiceSyncableMergeTest, GetAllSyncDataForLateRegisteredPrefs) {
-  const std::string pref_name = "testing.whitelisted_pref";
-  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name);
-
-  syncer::SyncDataList in;
-  AddToRemoteDataList(pref_name, base::Value("remote_value"), &in);
-  syncer::SyncChangeList out;
-  InitWithSyncDataTakeOutput(in, &out);
-
-  syncer::SyncDataList all_data =
-      prefs_.GetSyncableService(syncer::PREFERENCES)
-          ->GetAllSyncData(syncer::PREFERENCES);
-  EXPECT_THAT(all_data, IsEmpty());
-
-  // Make sure the preference appears in the result once it's registered.
-  pref_registry_->RegisterStringPref(
-      pref_name, "default_value",
-      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
-
-  all_data = prefs_.GetSyncableService(syncer::PREFERENCES)
-                 ->GetAllSyncData(syncer::PREFERENCES);
-  ASSERT_THAT(all_data, SizeIs(1));
-  EXPECT_THAT(all_data[0].GetSpecifics().preference().name(), Eq(pref_name));
-  EXPECT_THAT(all_data[0].GetSpecifics().preference().value(),
-              Eq("\"remote_value\""));
-}
-
 TEST_F(PrefServiceSyncableTest, FailModelAssociation) {
   syncer::SyncChangeList output;
   TestSyncProcessorStub* stub = new TestSyncProcessorStub(&output);
diff --git a/components/sync_preferences/unknown_user_pref_accessor.cc b/components/sync_preferences/unknown_user_pref_accessor.cc
deleted file mode 100644
index 063b5ae..0000000
--- a/components/sync_preferences/unknown_user_pref_accessor.cc
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/sync_preferences/unknown_user_pref_accessor.h"
-
-#include <iterator>
-#include <memory>
-
-#include "base/json/json_string_value_serializer.h"
-#include "base/logging.h"
-#include "base/memory/ptr_util.h"
-#include "base/metrics/histogram_macros.h"
-#include "base/values.h"
-#include "components/prefs/persistent_pref_store.h"
-#include "components/prefs/pref_service.h"
-#include "components/sync_preferences/pref_service_syncable.h"
-
-namespace sync_preferences {
-
-UnknownUserPrefAccessor::UnknownUserPrefAccessor(
-    PrefService* pref_service,
-    user_prefs::PrefRegistrySyncable* pref_registry,
-    PersistentPrefStore* user_prefs)
-    : pref_service_(pref_service),
-      pref_registry_(pref_registry),
-      user_prefs_(user_prefs) {}
-
-UnknownUserPrefAccessor::~UnknownUserPrefAccessor() {}
-
-UnknownUserPrefAccessor::PreferenceState
-UnknownUserPrefAccessor::GetPreferenceState(
-    syncer::ModelType type,
-    const std::string& pref_name) const {
-  PreferenceState result;
-  result.registration_state = GetRegistrationState(type, pref_name);
-  switch (result.registration_state) {
-    case RegistrationState::kUnknown:
-    case RegistrationState::kUnknownWhitelisted:
-      if (!user_prefs_->GetValue(pref_name, &result.persisted_value)) {
-        result.persisted_value = nullptr;
-      }
-      break;
-    case RegistrationState::kSyncable:
-    case RegistrationState::kNotSyncable:
-      result.persisted_value = pref_service_->GetUserPrefValue(pref_name);
-      break;
-  }
-  return result;
-}
-
-void UnknownUserPrefAccessor::ClearPref(
-    const std::string& pref_name,
-    const PreferenceState& local_pref_state) {
-  switch (local_pref_state.registration_state) {
-    case RegistrationState::kUnknown:
-      NOTREACHED() << "Sync attempted to update an unknown pref which is not "
-                      "whitelisted: "
-                   << pref_name;
-      break;
-    case RegistrationState::kUnknownWhitelisted:
-      user_prefs_->RemoveValue(pref_name,
-                               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-      break;
-    case RegistrationState::kSyncable:
-      pref_service_->ClearPref(pref_name);
-      break;
-    case RegistrationState::kNotSyncable:
-      // As this can happen if different clients disagree about which
-      // preferences should be synced, we only log a warning.
-      DLOG(WARNING)
-          << "Sync attempted to update a pref which is not registered as "
-             "syncable. Ignoring the remote change for pref: "
-          << pref_name;
-      break;
-  }
-}
-
-int UnknownUserPrefAccessor::GetNumberOfSyncingUnknownPrefs() const {
-  return synced_unknown_prefs_.size();
-}
-
-namespace {
-
-bool VerifyTypesBeforeSet(const std::string& pref_name,
-                          const base::Value* local_value,
-                          const base::Value& new_value) {
-  if (local_value == nullptr || local_value->type() == new_value.type()) {
-    return true;
-  }
-  UMA_HISTOGRAM_BOOLEAN("Sync.Preferences.RemotePrefTypeMismatch", true);
-  DLOG(WARNING) << "Unexpected type mis-match for pref. "
-                << "Synced value for " << pref_name << " is of type "
-                << new_value.type() << " which doesn't match the locally "
-                << "present pref type: " << local_value->type();
-  return false;
-}
-
-}  // namespace
-
-void UnknownUserPrefAccessor::SetPref(const std::string& pref_name,
-                                      const PreferenceState& local_pref_state,
-                                      const base::Value& value) {
-  // On type mis-match, we trust the local preference DB and ignore the remote
-  // change.
-  switch (local_pref_state.registration_state) {
-    case RegistrationState::kUnknown:
-      NOTREACHED() << "Sync attempted to update a unknown pref which is not "
-                      "whitelisted: "
-                   << pref_name;
-      break;
-    case RegistrationState::kUnknownWhitelisted:
-      if (VerifyTypesBeforeSet(pref_name, local_pref_state.persisted_value,
-                               value)) {
-        user_prefs_->SetValue(pref_name, value.CreateDeepCopy(),
-                              WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-      }
-      synced_unknown_prefs_.insert(pref_name);
-      break;
-    case RegistrationState::kSyncable:
-      if (VerifyTypesBeforeSet(pref_name, local_pref_state.persisted_value,
-                               value)) {
-        pref_service_->Set(pref_name, value);
-      }
-      break;
-    case RegistrationState::kNotSyncable:
-      // As this can happen if different clients disagree about which
-      // preferences should be synced, we only log a warning.
-      DLOG(WARNING)
-          << "Sync attempted to update a pref which is not registered as "
-             "syncable. Ignoring the remote change for pref: "
-          << pref_name;
-      break;
-  }
-}
-
-void UnknownUserPrefAccessor::EnforceRegisteredTypeInStore(
-    const std::string& pref_name) {
-  const base::Value* persisted_value = nullptr;
-  if (user_prefs_->GetValue(pref_name, &persisted_value)) {
-    // Get the registered type (typically from the default value).
-    const PrefService::Preference* pref =
-        pref_service_->FindPreference(pref_name);
-    DCHECK(pref);
-    if (pref->GetType() != persisted_value->type()) {
-      // We see conflicting type information and there's a chance the local
-      // type-conflicting data came in via sync. Remove it.
-      // TODO(tschumann): The value should get removed silently. Add a method
-      // RemoveValueSilently() to WriteablePrefStore. Note, that as of today
-      // that removal will only notify other pref stores but not sync -- that's
-      // done on a higher level.
-      user_prefs_->RemoveValue(pref_name,
-                               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
-      UMA_HISTOGRAM_BOOLEAN("Sync.Preferences.ClearedLocalPrefOnTypeMismatch",
-                            true);
-    }
-  }
-  synced_unknown_prefs_.erase(pref_name);
-}
-
-UnknownUserPrefAccessor::RegistrationState
-UnknownUserPrefAccessor::GetRegistrationState(
-    syncer::ModelType type,
-    const std::string& pref_name) const {
-  uint32_t type_flag = 0;
-  switch (type) {
-    case syncer::PRIORITY_PREFERENCES:
-      type_flag = user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF;
-      break;
-    case syncer::PREFERENCES:
-      type_flag = user_prefs::PrefRegistrySyncable::SYNCABLE_PREF;
-      break;
-    default:
-      NOTREACHED() << "unexpected model type for preferences: " << type;
-  }
-  if (pref_registry_->defaults()->GetValue(pref_name, nullptr)) {
-    uint32_t flags = pref_registry_->GetRegistrationFlags(pref_name);
-    if (flags & type_flag) {
-      return RegistrationState::kSyncable;
-    }
-    // Imagine the case where a preference has been synced as SYNCABLE_PREF
-    // first and then got changed to SYNCABLE_PRIORITY_PREF:
-    // In that situation, it could be argued for both, the preferences to be
-    // considered unknown or not synced. However, as we plan to eventually also
-    // sync unknown preferences, we cannot label them as unknown and treat them
-    // as not synced instead. (The underlying problem is that priority
-    // preferences are a concept only known to sync. The persistent stores don't
-    // distinguish between those two).
-    return RegistrationState::kNotSyncable;
-  }
-  if (pref_registry_->IsWhitelistedLateRegistrationPref(pref_name)) {
-    return RegistrationState::kUnknownWhitelisted;
-  }
-  return RegistrationState::kUnknown;
-}
-
-}  // namespace sync_preferences
diff --git a/components/sync_preferences/unknown_user_pref_accessor.h b/components/sync_preferences/unknown_user_pref_accessor.h
deleted file mode 100644
index 34336a0..0000000
--- a/components/sync_preferences/unknown_user_pref_accessor.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_SYNC_PREFERENCES_UNKNOWN_USER_PREF_ACCESSOR_H_
-#define COMPONENTS_SYNC_PREFERENCES_UNKNOWN_USER_PREF_ACCESSOR_H_
-
-#include <memory>
-#include <set>
-#include <string>
-
-#include "base/callback.h"
-#include "base/compiler_specific.h"
-#include "base/macros.h"
-#include "components/sync/base/model_type.h"
-
-class PersistentPrefStore;
-class PrefService;
-
-namespace base {
-class Value;
-}
-
-namespace user_prefs {
-class PrefRegistrySyncable;
-}
-
-namespace sync_preferences {
-
-// A class to access user prefs even before they were registered.
-// Currently, accessing not registered (unknown) prefs is limited to a
-// whitelist.
-class UnknownUserPrefAccessor {
- public:
-  enum class RegistrationState {
-    kUnknown,  // Preference is not registered (on this Chrome instance).
-    kUnknownWhitelisted,  // Preference is not registered but whitelisted to be
-                          // synced without being registered.
-    kSyncable,            // Preference is registered as being synced.
-    kNotSyncable          // Preference is registered as not being synced.
-  };
-
-  struct PreferenceState {
-    // The registration state of a preference.
-    RegistrationState registration_state = RegistrationState::kUnknown;
-
-    // The actually stored value. nullptr if no value is persisted and the pref
-    // service serves a default value for this pref.
-    // Ownership lies with the underlying pref-store.
-    const base::Value* persisted_value = nullptr;
-  };
-
-  // |pref_service|, |pref_registry|, and |user_prefs| must not be null and must
-  // outlive the lifetime of the created instance. The caller keeps ownership
-  // over these objects.
-  UnknownUserPrefAccessor(PrefService* pref_service,
-                          user_prefs::PrefRegistrySyncable* pref_registry,
-                          PersistentPrefStore* user_prefs);
-  ~UnknownUserPrefAccessor();
-
-  // Computes the state of a preference with name |pref_name| which gives
-  // information about whether it's registered and the locally persisted value.
-  PreferenceState GetPreferenceState(syncer::ModelType type,
-                                     const std::string& pref_name) const;
-
-  // Removes the value of the preference |pref_name| from the user prefstore.
-  // Must not be called for preferences having RegistrationState::kUnknown.
-  // When called for preferences registiered as not syncable
-  // (RegistrationState::kNotSyncable), no changes to the storage are made.
-  void ClearPref(const std::string& pref_name,
-                 const PreferenceState& local_pref_state);
-
-  // Changes the value of the preference |pref_name| on the user prefstore.
-  // Must not be called for preferences having RegistrationState::kUnknown.
-  // When called for preferences registiered as not syncable
-  // (RegistrationState::kNotSyncable), no changes to the storage are made.
-  void SetPref(const std::string& pref_name,
-               const PreferenceState& local_pref_state,
-               const base::Value& value);
-
-  // Verifies that the type which preference |pref_name| was registered with
-  // matches the type of any persisted value. On mismatch, the persisted value
-  // gets removed.
-  void EnforceRegisteredTypeInStore(const std::string& pref_name);
-
-  // Returns the number of synced preferences which have not been registered (so
-  // far).
-  int GetNumberOfSyncingUnknownPrefs() const;
-
- private:
-  RegistrationState GetRegistrationState(syncer::ModelType type,
-                                         const std::string& pref_name) const;
-
-  std::set<std::string> synced_unknown_prefs_;
-  PrefService* const pref_service_;
-  user_prefs::PrefRegistrySyncable* const pref_registry_;
-  PersistentPrefStore* const user_prefs_;
-
-  DISALLOW_COPY_AND_ASSIGN(UnknownUserPrefAccessor);
-};
-
-}  // namespace sync_preferences
-
-#endif  // COMPONENTS_SYNC_PREFERENCES_UNKNOWN_USER_PREF_ACCESSOR_H_
diff --git a/components/tracing/common/trace_startup_config.cc b/components/tracing/common/trace_startup_config.cc
index 8720fe0c..152c51a 100644
--- a/components/tracing/common/trace_startup_config.cc
+++ b/components/tracing/common/trace_startup_config.cc
@@ -72,8 +72,6 @@
   // First 10k events at start are sufficient to debug startup traces.
   trace_config.SetTraceBufferSizeInEvents(10000);
   trace_config.SetProcessFilterConfig(process_config);
-  // Enable argument filter since we could be background tracing.
-  trace_config.EnableArgumentFilter();
   return trace_config;
 }
 
@@ -248,6 +246,8 @@
 
   SetBackgroundStartupTracingEnabled(false);
   trace_config_ = GetDefaultBrowserStartupConfig();
+  trace_config_.EnableArgumentFilter();
+
   is_enabled_ = true;
   session_owner_ = SessionOwner::kBackgroundTracing;
   should_trace_to_result_file_ = false;
diff --git a/components/viz/common/features.cc b/components/viz/common/features.cc
index 1e9fca6..5b1edca 100644
--- a/components/viz/common/features.cc
+++ b/components/viz/common/features.cc
@@ -24,8 +24,13 @@
                                           base::FEATURE_ENABLED_BY_DEFAULT};
 #endif
 
+#if defined(OS_CHROMEOS)
 const base::Feature kEnableVizHitTestSurfaceLayer{
     "VizHitTestSurfaceLayer", base::FEATURE_DISABLED_BY_DEFAULT};
+#else
+const base::Feature kEnableVizHitTestSurfaceLayer{
+    "VizHitTestSurfaceLayer", base::FEATURE_ENABLED_BY_DEFAULT};
+#endif
 
 // Use Skia's readback API instead of GLRendererCopier.
 const base::Feature kUseSkiaForGLReadback{"UseSkiaForGLReadback",
diff --git a/components/viz/host/hit_test/hit_test_query_unittest.cc b/components/viz/host/hit_test/hit_test_query_unittest.cc
index 1b116b8..5e4a768 100644
--- a/components/viz/host/hit_test/hit_test_query_unittest.cc
+++ b/components/viz/host/hit_test/hit_test_query_unittest.cc
@@ -27,14 +27,17 @@
  protected:
   HitTestQuery& hit_test_query() { return hit_test_query_; }
   void SetUp() override {
-    if (!GetParam()) {
+    if (GetParam()) {
+      feature_list_.InitAndEnableFeature(
+          features::kEnableVizHitTestSurfaceLayer);
+    }
+
+    if (!features::IsVizHitTestingSurfaceLayerEnabled()) {
       // kHitTestIgnore has different meanings in v1 and v2. Some tests set
       // kHitTestIgnore for certain regions which works for v1 but fails v2.
       test_flags_ |= HitTestRegionFlags::kHitTestIgnore;
       return;
     }
-
-    feature_list_.InitAndEnableFeature(features::kEnableVizHitTestSurfaceLayer);
   }
 
   base::test::ScopedFeatureList feature_list_;
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index a4adc6c..63ac0dd 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -1164,6 +1164,13 @@
 
 bool BrowserAccessibilityAndroid::CanScrollForward() const {
   if (IsSlider()) {
+    // If it's not a native INPUT element, then increment and decrement
+    // won't work.
+    std::string html_tag =
+        GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
+    if (html_tag != "input")
+      return false;
+
     float value = GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange);
     float max = GetFloatAttribute(ax::mojom::FloatAttribute::kMaxValueForRange);
     return value < max;
@@ -1174,6 +1181,13 @@
 
 bool BrowserAccessibilityAndroid::CanScrollBackward() const {
   if (IsSlider()) {
+    // If it's not a native INPUT element, then increment and decrement
+    // won't work.
+    std::string html_tag =
+        GetStringAttribute(ax::mojom::StringAttribute::kHtmlTag);
+    if (html_tag != "input")
+      return false;
+
     float value = GetFloatAttribute(ax::mojom::FloatAttribute::kValueForRange);
     float min = GetFloatAttribute(ax::mojom::FloatAttribute::kMinValueForRange);
     return value > min;
diff --git a/content/browser/cache_storage/cache_storage_cache_entry_handler.cc b/content/browser/cache_storage/cache_storage_cache_entry_handler.cc
index 2def64e..6c332f6 100644
--- a/content/browser/cache_storage/cache_storage_cache_entry_handler.cc
+++ b/content/browser/cache_storage/cache_storage_cache_entry_handler.cc
@@ -294,9 +294,9 @@
       blob = std::move(response->blob->blob);
       blob_size = response->blob->size;
     }
-    if (response->side_data_blob) {
-      side_data_blob = std::move(response->side_data_blob->blob);
-      side_data_blob_size = response->side_data_blob->size;
+    if (response->side_data_blob_for_cache_put) {
+      side_data_blob = std::move(response->side_data_blob_for_cache_put->blob);
+      side_data_blob_size = response->side_data_blob_for_cache_put->size;
     }
 
     return std::make_unique<PutContext>(
@@ -307,9 +307,20 @@
   void PopulateResponseBody(scoped_refptr<DiskCacheBlobEntry> blob_entry,
                             blink::mojom::FetchAPIResponse* response) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+    // First create the blob and store it in the field for the main body
+    // loading.
     response->blob = CreateBlobWithSideData(
         std::move(blob_entry), CacheStorageCache::INDEX_RESPONSE_BODY,
         CacheStorageCache::INDEX_SIDE_DATA);
+
+    // Then clone the blob to the |side_data_blob| field for loading code_cache.
+    mojo::Remote<blink::mojom::Blob> blob_remote(
+        std::move(response->blob->blob));
+    blob_remote->Clone(response->blob->blob.InitWithNewPipeAndPassReceiver());
+    response->side_data_blob = blink::mojom::SerializedBlob::New(
+        response->blob->uuid, response->blob->content_type,
+        response->blob->size, blob_remote.Unbind());
   }
 
   void PopulateRequestBody(scoped_refptr<DiskCacheBlobEntry> blob_entry,
diff --git a/content/browser/cache_storage/cache_storage_cache_unittest.cc b/content/browser/cache_storage/cache_storage_cache_unittest.cc
index f05c5d7..2d8a059 100644
--- a/content/browser/cache_storage/cache_storage_cache_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_cache_unittest.cc
@@ -527,7 +527,9 @@
         nullptr /* blob */, blink::mojom::ServiceWorkerResponseError::kUnknown,
         response_time_, std::string() /* cache_storage_cache_name */,
         std::vector<std::string>() /* cors_exposed_header_names */,
-        nullptr /* side_data_blob */, nullptr /* content_security_policy */);
+        nullptr /* side_data_blob */,
+        nullptr /* side_data_blob_for_cache_put */,
+        nullptr /* content_security_policy */);
   }
 
   std::unique_ptr<storage::BlobDataHandle> BuildBlobHandle(
@@ -541,12 +543,16 @@
 
   void CopySideDataToResponse(storage::BlobDataHandle* side_data_blob_handle,
                               blink::mojom::FetchAPIResponse* response) {
-    response->side_data_blob = blink::mojom::SerializedBlob::New();
-    response->side_data_blob->uuid = side_data_blob_handle->uuid();
-    response->side_data_blob->size = side_data_blob_handle->size();
+    response->side_data_blob_for_cache_put =
+        blink::mojom::SerializedBlob::New();
+    response->side_data_blob_for_cache_put->uuid =
+        side_data_blob_handle->uuid();
+    response->side_data_blob_for_cache_put->size =
+        side_data_blob_handle->size();
     storage::BlobImpl::Create(
         std::make_unique<storage::BlobDataHandle>(*side_data_blob_handle),
-        response->side_data_blob->blob.InitWithNewPipeAndPassReceiver());
+        response->side_data_blob_for_cache_put->blob
+            .InitWithNewPipeAndPassReceiver());
   }
 
   blink::mojom::FetchAPIRequestPtr CopyFetchRequest(
@@ -1741,7 +1747,7 @@
   operation->operation_type = blink::mojom::OperationType::kPut;
   operation->request = BackgroundFetchSettledFetch::CloneRequest(body_request_);
   operation->response = std::move(response);
-  operation->response->side_data_blob->size =
+  operation->response->side_data_blob_for_cache_put->size =
       std::numeric_limits<uint64_t>::max();
 
   std::vector<blink::mojom::BatchOperationPtr> operations;
diff --git a/content/browser/cache_storage/cache_storage_dispatcher_host.cc b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
index 0eb5de1..7c9f18c 100644
--- a/content/browser/cache_storage/cache_storage_dispatcher_host.cc
+++ b/content/browser/cache_storage/cache_storage_dispatcher_host.cc
@@ -5,6 +5,7 @@
 #include "content/browser/cache_storage/cache_storage_dispatcher_host.h"
 
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string16.h"
 #include "base/strings/utf_string_conversions.h"
@@ -18,10 +19,12 @@
 #include "content/browser/cache_storage/cache_storage_manager.h"
 #include "content/browser/cache_storage/cache_storage_trace_utils.h"
 #include "content/common/background_fetch/background_fetch_types.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/origin_util.h"
 #include "content/public/common/referrer_type_converters.h"
 #include "mojo/public/cpp/bindings/message.h"
 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "third_party/blink/public/common/blob/blob_utils.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
 #include "url/gurl.h"
 #include "url/origin.h"
@@ -64,6 +67,43 @@
   return true;
 }
 
+blink::mojom::MatchResultPtr EagerlyReadResponseBody(
+    blink::mojom::FetchAPIResponsePtr response) {
+  if (!response->blob ||
+      !base::FeatureList::IsEnabled(features::kCacheStorageEagerReading)) {
+    return blink::mojom::MatchResult::NewResponse(std::move(response));
+  }
+
+  MojoCreateDataPipeOptions options;
+  options.struct_size = sizeof(MojoCreateDataPipeOptions);
+  options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
+  options.element_num_bytes = 1;
+  options.capacity_num_bytes =
+      blink::BlobUtils::GetDataPipeCapacity(response->blob->size);
+
+  mojo::ScopedDataPipeProducerHandle producer_handle;
+  mojo::ScopedDataPipeConsumerHandle consumer_handle;
+  MojoResult rv = CreateDataPipe(&options, &producer_handle, &consumer_handle);
+  if (rv != MOJO_RESULT_OK)
+    return blink::mojom::MatchResult::NewResponse(std::move(response));
+
+  mojo::PendingRemote<blink::mojom::BlobReaderClient> reader_client;
+  auto pending_receiver = reader_client.InitWithNewPipeAndPassReceiver();
+
+  mojo::Remote<blink::mojom::Blob> blob(std::move(response->blob->blob));
+  blob->ReadAll(std::move(producer_handle), std::move(reader_client));
+
+  // Clear the main body blob entry.  There should still be a |side_data_blob|
+  // value for reading code cache, however.
+  response->blob = nullptr;
+  DCHECK(response->side_data_blob);
+
+  return blink::mojom::MatchResult::NewEagerResponse(
+      blink::mojom::EagerResponse::New(std::move(response),
+                                       std::move(consumer_handle),
+                                       std::move(pending_receiver)));
+}
+
 }  // namespace
 
 // Implements the mojom interface CacheStorageCache. It's owned by
@@ -80,6 +120,7 @@
   // blink::mojom::CacheStorageCache implementation:
   void Match(blink::mojom::FetchAPIRequestPtr request,
              blink::mojom::CacheQueryOptionsPtr match_options,
+             bool in_related_fetch_event,
              int64_t trace_id,
              MatchCallback callback) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -98,7 +139,8 @@
 
     auto cb = base::BindOnce(
         [](base::TimeTicks start_time, bool ignore_search,
-           bool cache_initialized, int64_t trace_id,
+           bool in_related_fetch_event, bool cache_initialized,
+           int64_t trace_id,
            blink::mojom::CacheStorageCache::MatchCallback callback,
            blink::mojom::CacheStorageError error,
            blink::mojom::FetchAPIResponsePtr response) {
@@ -136,11 +178,19 @@
               TRACE_ID_GLOBAL(trace_id),
               TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "response",
               CacheStorageTracedValue(response));
-          std::move(callback).Run(
-              blink::mojom::MatchResult::NewResponse(std::move(response)));
+
+          blink::mojom::MatchResultPtr result;
+          if (in_related_fetch_event) {
+            result = EagerlyReadResponseBody(std::move(response));
+          } else {
+            result =
+                blink::mojom::MatchResult::NewResponse(std::move(response));
+          }
+          std::move(callback).Run(std::move(result));
         },
-        base::TimeTicks::Now(), match_options->ignore_search, cache_initialized,
-        trace_id, std::move(callback));
+        base::TimeTicks::Now(), match_options->ignore_search,
+        in_related_fetch_event, cache_initialized, trace_id,
+        std::move(callback));
 
     if (!cache) {
       std::move(cb).Run(CacheStorageError::kErrorNotFound, nullptr);
@@ -471,6 +521,7 @@
 
   void Match(blink::mojom::FetchAPIRequestPtr request,
              blink::mojom::MultiCacheQueryOptionsPtr match_options,
+             bool in_related_fetch_event,
              int64_t trace_id,
              blink::mojom::CacheStorage::MatchCallback callback) override {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
@@ -482,7 +533,8 @@
                            "options", CacheStorageTracedValue(match_options));
 
     auto cb = BindOnce(
-        [](base::TimeTicks start_time, bool match_all_caches, int64_t trace_id,
+        [](base::TimeTicks start_time, bool match_all_caches,
+           bool in_related_fetch_event, int64_t trace_id,
            blink::mojom::CacheStorage::MatchCallback callback,
            CacheStorageError error,
            blink::mojom::FetchAPIResponsePtr response) {
@@ -513,11 +565,18 @@
               TRACE_ID_GLOBAL(trace_id),
               TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "response",
               CacheStorageTracedValue(response));
-          std::move(callback).Run(
-              blink::mojom::MatchResult::NewResponse(std::move(response)));
+
+          blink::mojom::MatchResultPtr result;
+          if (in_related_fetch_event) {
+            result = EagerlyReadResponseBody(std::move(response));
+          } else {
+            result =
+                blink::mojom::MatchResult::NewResponse(std::move(response));
+          }
+          std::move(callback).Run(std::move(result));
         },
-        base::TimeTicks::Now(), !match_options->cache_name, trace_id,
-        std::move(callback));
+        base::TimeTicks::Now(), !match_options->cache_name,
+        in_related_fetch_event, trace_id, std::move(callback));
 
     content::CacheStorage* cache_storage = GetOrCreateCacheStorage();
     if (!cache_storage) {
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index d89bcd3..7ce4e94 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -667,7 +667,9 @@
         std::move(blob), blink::mojom::ServiceWorkerResponseError::kUnknown,
         base::Time(), std::string() /* cache_storage_cache_name */,
         std::vector<std::string>() /* cors_exposed_header_names */,
-        nullptr /* side_data_blob */, nullptr /* content_security_policy */);
+        nullptr /* side_data_blob */,
+        nullptr /* side_data_blob_for_cache_put */,
+        nullptr /* content_security_policy */);
 
     blink::mojom::BatchOperationPtr operation =
         blink::mojom::BatchOperation::New();
diff --git a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
index ce9e979..cf52c78 100644
--- a/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
+++ b/content/browser/cache_storage/legacy/legacy_cache_storage_cache.cc
@@ -356,7 +356,8 @@
       std::vector<std::string>(
           metadata.response().cors_exposed_header_names().begin(),
           metadata.response().cors_exposed_header_names().end()),
-      nullptr /* side_data_blob */, nullptr /* content_security_policy */);
+      nullptr /* side_data_blob */, nullptr /* side_data_blob_for_cache_put */,
+      nullptr /* content_security_policy */);
 }
 
 // The size of opaque (non-cors) resource responses are padded in order
@@ -642,9 +643,10 @@
   for (const auto& operation : operations) {
     if (operation->operation_type == blink::mojom::OperationType::kPut) {
       safe_space_required += CalculateRequiredSafeSpaceForPut(operation);
-      safe_side_data_size += (operation->response->side_data_blob
-                                  ? operation->response->side_data_blob->size
-                                  : 0);
+      safe_side_data_size +=
+          (operation->response->side_data_blob_for_cache_put
+               ? operation->response->side_data_blob_for_cache_put->size
+               : 0);
     }
   }
   if (!safe_space_required.IsValid() || !safe_side_data_size.IsValid()) {
@@ -755,7 +757,7 @@
     switch (operation->operation_type) {
       case blink::mojom::OperationType::kPut:
         if (skip_side_data) {
-          operation->response->side_data_blob = nullptr;
+          operation->response->side_data_blob_for_cache_put = nullptr;
           Put(std::move(operation), trace_id, completion_callback);
         } else {
           Put(std::move(operation), trace_id, completion_callback);
diff --git a/content/browser/media/session/media_session_browsertest.cc b/content/browser/media/session/media_session_browsertest.cc
index bb4fae2..d5376cb 100644
--- a/content/browser/media/session/media_session_browsertest.cc
+++ b/content/browser/media/session/media_session_browsertest.cc
@@ -75,11 +75,11 @@
 // Integration tests for content::MediaSession that do not take into
 // consideration the implementation details contrary to
 // MediaSessionImplBrowserTest.
-class MediaSessionBrowserTest : public ContentBrowserTest {
+class MediaSessionBrowserTestBase : public ContentBrowserTest {
  public:
-  MediaSessionBrowserTest() {
+  MediaSessionBrowserTestBase() {
     embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
-        &MediaSessionBrowserTest::OnServerRequest, base::Unretained(this)));
+        &MediaSessionBrowserTestBase::OnServerRequest, base::Unretained(this)));
   }
 
   void SetUp() override {
@@ -91,14 +91,6 @@
     command_line->AppendSwitchASCII(
         switches::kAutoplayPolicy,
         switches::autoplay::kNoUserGestureRequiredPolicy);
-
-    scoped_feature_list_.InitAndEnableFeature(media::kInternalMediaSession);
-  }
-
-  void DisableInternalMediaSession() {
-    disabled_feature_list_.InitWithFeatures(
-        {}, {media::kInternalMediaSession,
-             media_session::features::kMediaSessionService});
   }
 
   void StartPlaybackAndWait(Shell* shell, const std::string& id) {
@@ -173,10 +165,6 @@
     return embedded_test_server()->GetURL(kMediaSessionTestImagePath);
   }
 
- protected:
-  base::test::ScopedFeatureList scoped_feature_list_;
-  base::test::ScopedFeatureList disabled_feature_list_;
-
  private:
   void OnServerRequest(const net::test_server::HttpRequest& request) {
     // Note this method is called on the EmbeddedTestServer's background thread.
@@ -190,31 +178,57 @@
   base::Lock visited_urls_lock_;
   std::set<GURL> visited_urls_;
 
-  DISALLOW_COPY_AND_ASSIGN(MediaSessionBrowserTest);
+  DISALLOW_COPY_AND_ASSIGN(MediaSessionBrowserTestBase);
+};
+
+class MediaSessionBrowserTest : public MediaSessionBrowserTestBase {
+ public:
+  MediaSessionBrowserTest() {
+    feature_list_.InitAndEnableFeature(media::kInternalMediaSession);
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+class MediaSessionBrowserTestWithoutInternalMediaSession
+    : public MediaSessionBrowserTestBase {
+ public:
+  MediaSessionBrowserTestWithoutInternalMediaSession() {
+    disabled_feature_list_.InitWithFeatures(
+        {}, {media::kInternalMediaSession,
+             media_session::features::kMediaSessionService});
+  }
+
+ private:
+  base::test::ScopedFeatureList disabled_feature_list_;
 };
 
 // A MediaSessionBrowserTest with BackForwardCache enabled.
 class MediaSessionBrowserTestWithBackForwardCache
-    : public MediaSessionBrowserTest {
-  void SetUpOnMainThread() override {
-    host_resolver()->AddRule("*", "127.0.0.1");
-    MediaSessionBrowserTest::SetUpOnMainThread();
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    scoped_feature_list_.InitWithFeaturesAndParameters(
+    : public MediaSessionBrowserTestBase {
+ public:
+  MediaSessionBrowserTestWithBackForwardCache() {
+    feature_list_.InitWithFeaturesAndParameters(
         {{features::kBackForwardCache,
           {{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}},
          {media::kInternalMediaSession, {}}},
         /*disabled_features=*/{});
   }
+
+  void SetUpOnMainThread() override {
+    host_resolver()->AddRule("*", "127.0.0.1");
+    MediaSessionBrowserTestBase::SetUpOnMainThread();
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
 };
 
 }  // anonymous namespace
 
-IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MediaSessionNoOpWhenDisabled) {
-  DisableInternalMediaSession();
-
+IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithoutInternalMediaSession,
+                       MediaSessionNoOpWhenDisabled) {
   EXPECT_TRUE(NavigateToURL(shell(),
                             GetTestUrl("media/session", "media-session.html")));
 
diff --git a/content/browser/notifications/devtools_event_logging.cc b/content/browser/notifications/devtools_event_logging.cc
index 0dacfeb5..1e04df75e 100644
--- a/content/browser/notifications/devtools_event_logging.cc
+++ b/content/browser/notifications/devtools_event_logging.cc
@@ -7,6 +7,7 @@
 #include "base/callback.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/time/time_to_iso8601.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/devtools_background_services_context.h"
 #include "content/public/browser/notification_database_data.h"
@@ -115,8 +116,7 @@
 
   std::move(callback).Run(
       /* event_name= */ "Notification scheduled",
-      {{"Show Trigger Timestamp",
-        base::NumberToString(show_trigger_timestamp.ToJsTime())},
+      {{"Show Trigger Timestamp", base::TimeToISO8601(show_trigger_timestamp)},
        {"Title", base::UTF16ToUTF8(data.notification_data.title)},
        {"Body", base::UTF16ToUTF8(data.notification_data.body)}});
 }
diff --git a/content/browser/portal/portal_browsertest.cc b/content/browser/portal/portal_browsertest.cc
index d0fba3d..56550d4 100644
--- a/content/browser/portal/portal_browsertest.cc
+++ b/content/browser/portal/portal_browsertest.cc
@@ -12,12 +12,13 @@
 #include "content/browser/frame_host/render_frame_host_manager.h"
 #include "content/browser/frame_host/render_frame_proxy_host.h"
 #include "content/browser/portal/portal.h"
+#include "content/browser/portal/portal_created_observer.h"
+#include "content/browser/portal/portal_interceptor_for_testing.h"
 #include "content/browser/renderer_host/input/synthetic_smooth_scroll_gesture.h"
 #include "content/browser/renderer_host/input/synthetic_tap_gesture.h"
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
 #include "content/browser/renderer_host/render_widget_host_view_child_frame.h"
 #include "content/browser/web_contents/web_contents_impl.h"
-#include "content/common/frame.mojom-test-utils.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test_utils.h"
@@ -33,7 +34,6 @@
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/common/features.h"
-#include "third_party/blink/public/mojom/portal/portal.mojom-test-utils.h"
 #include "third_party/blink/public/mojom/portal/portal.mojom.h"
 #include "url/url_constants.h"
 
@@ -41,203 +41,6 @@
 
 namespace content {
 
-// The PortalInterceptorForTesting can be used in tests to inspect Portal IPCs.
-class PortalInterceptorForTesting final
-    : public blink::mojom::PortalInterceptorForTesting {
- public:
-  static PortalInterceptorForTesting* Create(
-      RenderFrameHostImpl* render_frame_host_impl,
-      mojo::PendingAssociatedReceiver<blink::mojom::Portal> receiver,
-      mojo::AssociatedRemote<blink::mojom::PortalClient> client);
-  static PortalInterceptorForTesting* Create(
-      RenderFrameHostImpl* render_frame_host_impl,
-      content::Portal* portal);
-  static PortalInterceptorForTesting* From(content::Portal* portal);
-
-  void Activate(blink::TransferableMessage data,
-                ActivateCallback callback) override {
-    portal_activated_ = true;
-
-    if (run_loop_) {
-      run_loop_->Quit();
-      run_loop_ = nullptr;
-    }
-
-    // |this| can be destroyed after Activate() is called.
-    portal_->Activate(std::move(data), std::move(callback));
-  }
-
-  void Navigate(const GURL& url,
-                blink::mojom::ReferrerPtr referrer,
-                NavigateCallback callback) override {
-    if (navigate_callback_) {
-      navigate_callback_.Run(url, std::move(referrer), std::move(callback));
-      return;
-    }
-
-    portal_->Navigate(url, std::move(referrer), std::move(callback));
-  }
-
-  void WaitForActivate() {
-    if (portal_activated_)
-      return;
-
-    base::RunLoop run_loop;
-    run_loop_ = &run_loop;
-    run_loop.Run();
-  }
-
-  // Test getters.
-  content::Portal* GetPortal() { return portal_.get(); }
-  WebContents* GetPortalContents() { return portal_->GetPortalContents(); }
-
-  // IPC callbacks
-  base::RepeatingCallback<
-      void(const GURL&, blink::mojom::ReferrerPtr, NavigateCallback)>
-      navigate_callback_;
-
- private:
-  explicit PortalInterceptorForTesting(
-      RenderFrameHostImpl* render_frame_host_impl)
-      : portal_(content::Portal::CreateForTesting(render_frame_host_impl)) {}
-  PortalInterceptorForTesting(RenderFrameHostImpl* render_frame_host_impl,
-                              std::unique_ptr<content::Portal> portal)
-      : portal_(std::move(portal)) {}
-
-  blink::mojom::Portal* GetForwardingInterface() override {
-    return portal_.get();
-  }
-
-  std::unique_ptr<content::Portal> portal_;
-  bool portal_activated_ = false;
-  base::RunLoop* run_loop_ = nullptr;
-};
-
-// static
-PortalInterceptorForTesting* PortalInterceptorForTesting::Create(
-    RenderFrameHostImpl* render_frame_host_impl,
-    mojo::PendingAssociatedReceiver<blink::mojom::Portal> receiver,
-    mojo::AssociatedRemote<blink::mojom::PortalClient> client) {
-  auto test_portal_ptr =
-      base::WrapUnique(new PortalInterceptorForTesting(render_frame_host_impl));
-  PortalInterceptorForTesting* test_portal = test_portal_ptr.get();
-  test_portal->GetPortal()->SetBindingForTesting(
-      mojo::MakeStrongAssociatedBinding<blink::mojom::Portal>(
-          std::move(test_portal_ptr), std::move(receiver)));
-  test_portal->GetPortal()->SetClientForTesting(std::move(client));
-  return test_portal;
-}
-
-PortalInterceptorForTesting* PortalInterceptorForTesting::Create(
-    RenderFrameHostImpl* render_frame_host_impl,
-    content::Portal* portal) {
-  // Take ownership of the portal.
-  std::unique_ptr<blink::mojom::Portal> mojom_portal_ptr =
-      portal->GetBindingForTesting()->SwapImplForTesting(nullptr);
-  std::unique_ptr<content::Portal> portal_ptr = base::WrapUnique(
-      static_cast<content::Portal*>(mojom_portal_ptr.release()));
-
-  // Create PortalInterceptorForTesting.
-  auto test_portal_ptr = base::WrapUnique(new PortalInterceptorForTesting(
-      render_frame_host_impl, std::move(portal_ptr)));
-  PortalInterceptorForTesting* test_portal = test_portal_ptr.get();
-
-  // Set the binding for the PortalInterceptorForTesting.
-  portal->GetBindingForTesting()->SwapImplForTesting(
-      std::move(test_portal_ptr));
-
-  return test_portal;
-}
-
-// static
-PortalInterceptorForTesting* PortalInterceptorForTesting::From(
-    content::Portal* portal) {
-  blink::mojom::Portal* impl = portal->GetBindingForTesting()->impl();
-  auto* interceptor = static_cast<PortalInterceptorForTesting*>(impl);
-  CHECK_NE(static_cast<blink::mojom::Portal*>(portal), impl);
-  CHECK_EQ(interceptor->GetPortal(), portal);
-  return interceptor;
-}
-
-// The PortalCreatedObserver observes portal creations on
-// |render_frame_host_impl|. This observer can be used to monitor for multiple
-// Portal creations on the same RenderFrameHost, by repeatedly calling
-// WaitUntilPortalCreated().
-class PortalCreatedObserver : public mojom::FrameHostInterceptorForTesting {
- public:
-  explicit PortalCreatedObserver(RenderFrameHostImpl* render_frame_host_impl)
-      : render_frame_host_impl_(render_frame_host_impl) {
-    old_impl_ = render_frame_host_impl_->frame_host_receiver_for_testing()
-                    .SwapImplForTesting(this);
-  }
-
-  ~PortalCreatedObserver() override {
-    render_frame_host_impl_->frame_host_receiver_for_testing()
-        .SwapImplForTesting(old_impl_);
-  }
-
-  FrameHost* GetForwardingInterface() override {
-    return render_frame_host_impl_;
-  }
-
-  void CreatePortal(
-      mojo::PendingAssociatedReceiver<blink::mojom::Portal> portal,
-      mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client,
-      CreatePortalCallback callback) override {
-    PortalInterceptorForTesting* portal_interceptor =
-        PortalInterceptorForTesting::Create(
-            render_frame_host_impl_, std::move(portal),
-            mojo::AssociatedRemote<blink::mojom::PortalClient>(
-                std::move(client)));
-    portal_ = portal_interceptor->GetPortal();
-    RenderFrameProxyHost* proxy_host = portal_->CreateProxyAndAttachPortal();
-    std::move(callback).Run(proxy_host->GetRoutingID(), portal_->portal_token(),
-                            portal_->GetDevToolsFrameToken());
-
-    if (run_loop_)
-      run_loop_->Quit();
-  }
-
-  void AdoptPortal(const base::UnguessableToken& portal_token,
-                   AdoptPortalCallback callback) override {
-    Portal* portal = Portal::FromToken(portal_token);
-    PortalInterceptorForTesting* portal_interceptor =
-        PortalInterceptorForTesting::Create(render_frame_host_impl_, portal);
-    portal_ = portal_interceptor->GetPortal();
-    RenderFrameProxyHost* proxy_host = portal_->CreateProxyAndAttachPortal();
-    std::move(callback).Run(
-        proxy_host->GetRoutingID(),
-        proxy_host->frame_tree_node()->current_replication_state(),
-        portal->GetDevToolsFrameToken());
-
-    if (run_loop_)
-      run_loop_->Quit();
-  }
-
-  Portal* WaitUntilPortalCreated() {
-    Portal* portal = portal_;
-    if (portal) {
-      portal_ = nullptr;
-      return portal;
-    }
-
-    base::RunLoop run_loop;
-    run_loop_ = &run_loop;
-    run_loop.Run();
-    run_loop_ = nullptr;
-
-    portal = portal_;
-    portal_ = nullptr;
-    return portal;
-  }
-
- private:
-  RenderFrameHostImpl* render_frame_host_impl_;
-  mojom::FrameHost* old_impl_;
-  base::RunLoop* run_loop_ = nullptr;
-  Portal* portal_ = nullptr;
-};
-
 class PortalBrowserTest : public ContentBrowserTest {
  protected:
   PortalBrowserTest() {}
@@ -760,13 +563,13 @@
       PortalInterceptorForTesting::From(portal);
 
   // Try to navigate to chrome://settings and wait for the process to die.
-  portal_interceptor->navigate_callback_ = base::BindRepeating(
+  portal_interceptor->SetNavigateCallback(base::BindRepeating(
       [](Portal* portal, const GURL& url, blink::mojom::ReferrerPtr referrer,
          blink::mojom::Portal::NavigateCallback callback) {
         GURL chrome_url("chrome://settings");
         portal->Navigate(chrome_url, std::move(referrer), std::move(callback));
       },
-      portal);
+      portal));
   RenderProcessHostKillWaiter kill_waiter(main_frame->GetProcess());
   GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
   ignore_result(ExecJs(main_frame, JsReplace("portal.src = $1;", a_url)));
@@ -1190,11 +993,10 @@
 }
 #endif
 
-// TODO(crbug.com/1010675): Test fails flakily.
 // Touch input transfer is only implemented in the content layer for Aura.
 #if defined(USE_AURA)
 IN_PROC_BROWSER_TEST_F(PortalBrowserTest,
-                       DISABLED_TouchInputTransferAcrossReactivation) {
+                       TouchInputTransferAcrossReactivation) {
   EXPECT_TRUE(NavigateToURL(
       shell(), embedded_test_server()->GetURL(
                    "portal.test",
diff --git a/content/browser/portal/portal_created_observer.cc b/content/browser/portal/portal_created_observer.cc
new file mode 100644
index 0000000..1a563ff
--- /dev/null
+++ b/content/browser/portal/portal_created_observer.cc
@@ -0,0 +1,86 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/portal/portal_created_observer.h"
+
+#include <utility>
+#include "base/run_loop.h"
+#include "content/browser/frame_host/frame_tree_node.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/frame_host/render_frame_proxy_host.h"
+#include "content/browser/portal/portal.h"
+#include "content/browser/portal/portal_interceptor_for_testing.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+
+namespace content {
+
+PortalCreatedObserver::PortalCreatedObserver(
+    RenderFrameHostImpl* render_frame_host_impl)
+    : render_frame_host_impl_(render_frame_host_impl) {
+  old_impl_ = render_frame_host_impl_->frame_host_receiver_for_testing()
+                  .SwapImplForTesting(this);
+}
+
+PortalCreatedObserver::~PortalCreatedObserver() {
+  render_frame_host_impl_->frame_host_receiver_for_testing().SwapImplForTesting(
+      old_impl_);
+}
+
+mojom::FrameHost* PortalCreatedObserver::GetForwardingInterface() {
+  return render_frame_host_impl_;
+}
+
+void PortalCreatedObserver::CreatePortal(
+    mojo::PendingAssociatedReceiver<blink::mojom::Portal> portal,
+    mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client,
+    CreatePortalCallback callback) {
+  PortalInterceptorForTesting* portal_interceptor =
+      PortalInterceptorForTesting::Create(
+          render_frame_host_impl_, std::move(portal),
+          mojo::AssociatedRemote<blink::mojom::PortalClient>(
+              std::move(client)));
+  portal_ = portal_interceptor->GetPortal();
+  RenderFrameProxyHost* proxy_host = portal_->CreateProxyAndAttachPortal();
+  std::move(callback).Run(proxy_host->GetRoutingID(), portal_->portal_token(),
+                          portal_->GetDevToolsFrameToken());
+
+  if (run_loop_)
+    run_loop_->Quit();
+}
+
+void PortalCreatedObserver::AdoptPortal(
+    const base::UnguessableToken& portal_token,
+    AdoptPortalCallback callback) {
+  Portal* portal = Portal::FromToken(portal_token);
+  PortalInterceptorForTesting* portal_interceptor =
+      PortalInterceptorForTesting::Create(render_frame_host_impl_, portal);
+  portal_ = portal_interceptor->GetPortal();
+  RenderFrameProxyHost* proxy_host = portal_->CreateProxyAndAttachPortal();
+  std::move(callback).Run(
+      proxy_host->GetRoutingID(),
+      proxy_host->frame_tree_node()->current_replication_state(),
+      portal->GetDevToolsFrameToken());
+
+  if (run_loop_)
+    run_loop_->Quit();
+}
+
+Portal* PortalCreatedObserver::WaitUntilPortalCreated() {
+  Portal* portal = portal_;
+  if (portal) {
+    portal_ = nullptr;
+    return portal;
+  }
+
+  base::RunLoop run_loop;
+  run_loop_ = &run_loop;
+  run_loop.Run();
+  run_loop_ = nullptr;
+
+  portal = portal_;
+  portal_ = nullptr;
+  return portal;
+}
+
+}  // namespace content
diff --git a/content/browser/portal/portal_created_observer.h b/content/browser/portal/portal_created_observer.h
new file mode 100644
index 0000000..a638cbc
--- /dev/null
+++ b/content/browser/portal/portal_created_observer.h
@@ -0,0 +1,51 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_PORTAL_PORTAL_CREATED_OBSERVER_H_
+#define CONTENT_BROWSER_PORTAL_PORTAL_CREATED_OBSERVER_H_
+
+#include "content/common/frame.mojom-test-utils.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_remote.h"
+#include "third_party/blink/public/mojom/portal/portal.mojom.h"
+
+namespace base {
+class RunLoop;
+}  // namespace base
+
+namespace content {
+
+class Portal;
+class RenderFrameHostImpl;
+
+// The PortalCreatedObserver observes portal creations on
+// |render_frame_host_impl|. This observer can be used to monitor for multiple
+// Portal creations on the same RenderFrameHost, by repeatedly calling
+// WaitUntilPortalCreated().
+class PortalCreatedObserver : public mojom::FrameHostInterceptorForTesting {
+ public:
+  explicit PortalCreatedObserver(RenderFrameHostImpl* render_frame_host_impl);
+  ~PortalCreatedObserver() override;
+
+  // mojom::FrameHostInterceptorForTesting
+  mojom::FrameHost* GetForwardingInterface() override;
+  void CreatePortal(
+      mojo::PendingAssociatedReceiver<blink::mojom::Portal> portal,
+      mojo::PendingAssociatedRemote<blink::mojom::PortalClient> client,
+      CreatePortalCallback callback) override;
+  void AdoptPortal(const base::UnguessableToken& portal_token,
+                   AdoptPortalCallback callback) override;
+
+  Portal* WaitUntilPortalCreated();
+
+ private:
+  RenderFrameHostImpl* render_frame_host_impl_;
+  mojom::FrameHost* old_impl_;
+  base::RunLoop* run_loop_ = nullptr;
+  Portal* portal_ = nullptr;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PORTAL_PORTAL_CREATED_OBSERVER_H_
diff --git a/content/browser/portal/portal_interceptor_for_testing.cc b/content/browser/portal/portal_interceptor_for_testing.cc
new file mode 100644
index 0000000..00339b0
--- /dev/null
+++ b/content/browser/portal/portal_interceptor_for_testing.cc
@@ -0,0 +1,111 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/portal/portal_interceptor_for_testing.h"
+
+#include <utility>
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "mojo/public/cpp/bindings/strong_associated_binding.h"
+
+namespace content {
+
+// static
+PortalInterceptorForTesting* PortalInterceptorForTesting::Create(
+    RenderFrameHostImpl* render_frame_host_impl,
+    mojo::PendingAssociatedReceiver<blink::mojom::Portal> receiver,
+    mojo::AssociatedRemote<blink::mojom::PortalClient> client) {
+  auto test_portal_ptr =
+      base::WrapUnique(new PortalInterceptorForTesting(render_frame_host_impl));
+  PortalInterceptorForTesting* test_portal = test_portal_ptr.get();
+  test_portal->GetPortal()->SetBindingForTesting(
+      mojo::MakeStrongAssociatedBinding<blink::mojom::Portal>(
+          std::move(test_portal_ptr), std::move(receiver)));
+  test_portal->GetPortal()->SetClientForTesting(std::move(client));
+  return test_portal;
+}
+
+PortalInterceptorForTesting* PortalInterceptorForTesting::Create(
+    RenderFrameHostImpl* render_frame_host_impl,
+    content::Portal* portal) {
+  // Take ownership of the portal.
+  std::unique_ptr<blink::mojom::Portal> mojom_portal_ptr =
+      portal->GetBindingForTesting()->SwapImplForTesting(nullptr);
+  std::unique_ptr<content::Portal> portal_ptr = base::WrapUnique(
+      static_cast<content::Portal*>(mojom_portal_ptr.release()));
+
+  // Create PortalInterceptorForTesting.
+  auto test_portal_ptr = base::WrapUnique(new PortalInterceptorForTesting(
+      render_frame_host_impl, std::move(portal_ptr)));
+  PortalInterceptorForTesting* test_portal = test_portal_ptr.get();
+
+  // Set the binding for the PortalInterceptorForTesting.
+  portal->GetBindingForTesting()->SwapImplForTesting(
+      std::move(test_portal_ptr));
+
+  return test_portal;
+}
+
+// static
+PortalInterceptorForTesting* PortalInterceptorForTesting::From(
+    content::Portal* portal) {
+  blink::mojom::Portal* impl = portal->GetBindingForTesting()->impl();
+  auto* interceptor = static_cast<PortalInterceptorForTesting*>(impl);
+  CHECK_NE(static_cast<blink::mojom::Portal*>(portal), impl);
+  CHECK_EQ(interceptor->GetPortal(), portal);
+  return interceptor;
+}
+
+PortalInterceptorForTesting::PortalInterceptorForTesting(
+    RenderFrameHostImpl* render_frame_host_impl)
+    : portal_(content::Portal::CreateForTesting(render_frame_host_impl)) {}
+
+PortalInterceptorForTesting::PortalInterceptorForTesting(
+    RenderFrameHostImpl* render_frame_host_impl,
+    std::unique_ptr<content::Portal> portal)
+    : portal_(std::move(portal)) {}
+
+PortalInterceptorForTesting::~PortalInterceptorForTesting() = default;
+
+blink::mojom::Portal* PortalInterceptorForTesting::GetForwardingInterface() {
+  return portal_.get();
+}
+
+void PortalInterceptorForTesting::Activate(blink::TransferableMessage data,
+                                           ActivateCallback callback) {
+  portal_activated_ = true;
+
+  if (run_loop_) {
+    run_loop_->Quit();
+    run_loop_ = nullptr;
+  }
+
+  // |this| can be destroyed after Activate() is called.
+  portal_->Activate(std::move(data), std::move(callback));
+}
+
+void PortalInterceptorForTesting::Navigate(
+    const GURL& url,
+    blink::mojom::ReferrerPtr referrer,
+    blink::mojom::Portal::NavigateCallback callback) {
+  if (navigate_callback_) {
+    navigate_callback_.Run(url, std::move(referrer), std::move(callback));
+    return;
+  }
+
+  portal_->Navigate(url, std::move(referrer), std::move(callback));
+}
+
+void PortalInterceptorForTesting::WaitForActivate() {
+  if (portal_activated_)
+    return;
+
+  base::RunLoop run_loop;
+  run_loop_ = &run_loop;
+  run_loop.Run();
+}
+
+}  // namespace content
diff --git a/content/browser/portal/portal_interceptor_for_testing.h b/content/browser/portal/portal_interceptor_for_testing.h
new file mode 100644
index 0000000..ff8bab4c
--- /dev/null
+++ b/content/browser/portal/portal_interceptor_for_testing.h
@@ -0,0 +1,77 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_PORTAL_PORTAL_INTERCEPTOR_FOR_TESTING_H_
+#define CONTENT_BROWSER_PORTAL_PORTAL_INTERCEPTOR_FOR_TESTING_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "content/browser/portal/portal.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
+#include "third_party/blink/public/mojom/portal/portal.mojom-test-utils.h"
+#include "third_party/blink/public/mojom/portal/portal.mojom.h"
+
+namespace base {
+class RunLoop;
+}  // namespace base
+
+namespace content {
+
+class RenderFrameHostImpl;
+
+// The PortalInterceptorForTesting can be used in tests to inspect Portal IPCs.
+class PortalInterceptorForTesting final
+    : public blink::mojom::PortalInterceptorForTesting {
+ public:
+  static PortalInterceptorForTesting* Create(
+      RenderFrameHostImpl* render_frame_host_impl,
+      mojo::PendingAssociatedReceiver<blink::mojom::Portal> receiver,
+      mojo::AssociatedRemote<blink::mojom::PortalClient> client);
+  static PortalInterceptorForTesting* Create(
+      RenderFrameHostImpl* render_frame_host_impl,
+      content::Portal* portal);
+  static PortalInterceptorForTesting* From(content::Portal* portal);
+
+  ~PortalInterceptorForTesting() override;
+
+  // blink::mojom::PortalInterceptorForTesting
+  blink::mojom::Portal* GetForwardingInterface() override;
+  void Activate(blink::TransferableMessage data,
+                ActivateCallback callback) override;
+  void Navigate(const GURL& url,
+                blink::mojom::ReferrerPtr referrer,
+                blink::mojom::Portal::NavigateCallback callback) override;
+
+  // If set, will be used to replace the implementation of Navigate.
+  using NavigateCallback =
+      base::RepeatingCallback<void(const GURL&,
+                                   blink::mojom::ReferrerPtr,
+                                   blink::mojom::Portal::NavigateCallback)>;
+  void SetNavigateCallback(NavigateCallback callback) {
+    navigate_callback_ = std::move(callback);
+  }
+
+  void WaitForActivate();
+
+  // Test getters.
+  content::Portal* GetPortal() { return portal_.get(); }
+  WebContentsImpl* GetPortalContents() { return portal_->GetPortalContents(); }
+
+ private:
+  explicit PortalInterceptorForTesting(
+      RenderFrameHostImpl* render_frame_host_impl);
+  PortalInterceptorForTesting(RenderFrameHostImpl* render_frame_host_impl,
+                              std::unique_ptr<content::Portal> portal);
+
+  std::unique_ptr<content::Portal> portal_;
+  NavigateCallback navigate_callback_;
+  bool portal_activated_ = false;
+  base::RunLoop* run_loop_ = nullptr;
+};
+
+}  // namespace content
+
+#endif  // CONTENT_BROWSER_PORTAL_PORTAL_INTERCEPTOR_FOR_TESTING_H_
diff --git a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
index dd26e35..fe3e63e23 100644
--- a/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
+++ b/content/browser/renderer_host/input/passthrough_touch_event_queue.cc
@@ -207,6 +207,9 @@
 }
 
 void PassthroughTouchEventQueue::FlushQueue() {
+  // Don't allow acks to be processed in AckCompletedEvents as that can
+  // interfere with gesture event dispatch ordering.
+  base::AutoReset<bool> process_acks(&processing_acks_, true);
   drop_remaining_touches_in_sequence_ = true;
   client_->FlushDeferredGestureQueue();
   while (!outstanding_touches_.empty()) {
diff --git a/content/browser/service_worker/service_worker_browsertest.cc b/content/browser/service_worker/service_worker_browsertest.cc
index 307a263..87414629 100644
--- a/content/browser/service_worker/service_worker_browsertest.cc
+++ b/content/browser/service_worker/service_worker_browsertest.cc
@@ -143,6 +143,20 @@
   return output;
 }
 
+size_t BlobSideDataLength(blink::mojom::Blob* actual_blob) {
+  size_t result = 0;
+  base::RunLoop run_loop;
+  actual_blob->ReadSideData(base::BindOnce(
+      [](size_t* result, base::OnceClosure continuation,
+         const base::Optional<mojo_base::BigBuffer> data) {
+        *result = data ? data->size() : 0;
+        std::move(continuation).Run();
+      },
+      &result, run_loop.QuitClosure()));
+  run_loop.Run();
+  return result;
+}
+
 struct FetchResult {
   blink::ServiceWorkerStatusCode status;
   ServiceWorkerFetchDispatcher::FetchEventResult result;
@@ -3327,9 +3341,9 @@
     }
 
     ASSERT_EQ(CacheStorageError::kSuccess, error);
-    ASSERT_TRUE(response->blob);
+    ASSERT_TRUE(response->side_data_blob);
     auto blob_handle = base::MakeRefCounted<storage::BlobHandle>(
-        std::move(response->blob->blob));
+        std::move(response->side_data_blob->blob));
     blob_handle->get()->ReadSideData(base::BindOnce(
         [](scoped_refptr<storage::BlobHandle> blob_handle, int* result,
            base::OnceClosure continuation,
@@ -3765,4 +3779,118 @@
   SetBrowserClientForTesting(old_content_browser_client);
 }
 
+class CacheStorageEagerReadingTestBase
+    : public ServiceWorkerVersionBrowserTest {
+ public:
+  explicit CacheStorageEagerReadingTestBase(bool enabled) {
+    if (enabled)
+      feature_list.InitAndEnableFeature(features::kCacheStorageEagerReading);
+    else
+      feature_list.InitAndDisableFeature(features::kCacheStorageEagerReading);
+  }
+
+  void SetupServiceWorkerAndDoFetch(
+      std::string fetch_url,
+      blink::mojom::FetchAPIResponsePtr* response_out) {
+    StartServerAndNavigateToSetup();
+    InstallTestHelper("/service_worker/cached_fetch_event.js",
+                      blink::ServiceWorkerStatusCode::kOk);
+    ActivateTestHelper(blink::ServiceWorkerStatusCode::kOk);
+
+    ServiceWorkerFetchDispatcher::FetchEventResult result;
+    FetchOnRegisteredWorker(fetch_url, &result, response_out);
+  }
+
+  void ExpectNormalCacheResponse(blink::mojom::FetchAPIResponsePtr response) {
+    EXPECT_EQ(network::mojom::FetchResponseSource::kCacheStorage,
+              response->response_source);
+
+    // A normal cache_storage response should have a blob for the body.
+    mojo::Remote<blink::mojom::Blob> blob(std::move(response->blob->blob));
+
+    // The blob should contain the expected body content.
+    EXPECT_EQ(BlobToString(blob.get()).length(), 1075u);
+
+    // Since this js response was stored in the install event it should have
+    // code cache stored in the blob side data.
+    EXPECT_GT(BlobSideDataLength(blob.get()),
+              static_cast<size_t>(kV8CacheTimeStampDataSize));
+  }
+
+  void ExpectEagerlyReadCacheResponse(
+      blink::mojom::FetchAPIResponsePtr response) {
+    EXPECT_EQ(network::mojom::FetchResponseSource::kCacheStorage,
+              response->response_source);
+
+    // An eagerly read cache_storage response should not have a blob.  Instead
+    // the body is provided out-of-band in a mojo DataPipe.  The pipe is not
+    // surfaced here in this test.
+    EXPECT_FALSE(response->blob);
+
+    // An eagerly read response should still have a side_data_blob, though.
+    // This is provided so that js resources can still load code cache.
+    mojo::Remote<blink::mojom::Blob> side_data_blob(
+        std::move(response->side_data_blob->blob));
+
+    // Since this js response was stored in the install event it should have
+    // code cache stored in the blob side data.
+    EXPECT_GT(BlobSideDataLength(side_data_blob.get()),
+              static_cast<size_t>(kV8CacheTimeStampDataSize));
+  }
+
+  // The service worker script always matches against this URL.
+  static constexpr const char* kCacheMatchURL =
+      "/service_worker/v8_cache_test.js";
+
+  // A URL that will be different from the cache.match() executed in
+  // the service worker fetch handler.
+  static constexpr const char* kOtherURL =
+      "/service_worker/non-matching-url.js";
+
+ private:
+  base::test::ScopedFeatureList feature_list;
+};
+
+class CacheStorageEagerReadingEnabledTest
+    : public CacheStorageEagerReadingTestBase {
+ public:
+  CacheStorageEagerReadingEnabledTest()
+      : CacheStorageEagerReadingTestBase(true) {}
+};
+
+class CacheStorageEagerReadingDisabledTest
+    : public CacheStorageEagerReadingTestBase {
+ public:
+  CacheStorageEagerReadingDisabledTest()
+      : CacheStorageEagerReadingTestBase(false) {}
+};
+
+IN_PROC_BROWSER_TEST_F(CacheStorageEagerReadingDisabledTest,
+                       CacheMatchInRelatedFetchEvent) {
+  blink::mojom::FetchAPIResponsePtr response;
+  SetupServiceWorkerAndDoFetch(kCacheMatchURL, &response);
+  ExpectNormalCacheResponse(std::move(response));
+}
+
+IN_PROC_BROWSER_TEST_F(CacheStorageEagerReadingDisabledTest,
+                       CacheMatchInUnrelatedFetchEvent) {
+  blink::mojom::FetchAPIResponsePtr response;
+  SetupServiceWorkerAndDoFetch(kOtherURL, &response);
+  ExpectNormalCacheResponse(std::move(response));
+}
+
+IN_PROC_BROWSER_TEST_F(CacheStorageEagerReadingEnabledTest,
+                       CacheMatchInRelatedFetchEvent) {
+  blink::mojom::FetchAPIResponsePtr response;
+  SetupServiceWorkerAndDoFetch(kCacheMatchURL, &response);
+  ExpectEagerlyReadCacheResponse(std::move(response));
+}
+
+IN_PROC_BROWSER_TEST_F(CacheStorageEagerReadingEnabledTest,
+                       CacheMatchInUnrelatedFetchEvent) {
+  blink::mojom::FetchAPIResponsePtr response;
+  SetupServiceWorkerAndDoFetch(kOtherURL, &response);
+  ExpectNormalCacheResponse(std::move(response));
+}
+
 }  // namespace content
diff --git a/content/browser/service_worker/service_worker_job_unittest.cc b/content/browser/service_worker/service_worker_job_unittest.cc
index 5e0a898..8113173 100644
--- a/content/browser/service_worker/service_worker_job_unittest.cc
+++ b/content/browser/service_worker/service_worker_job_unittest.cc
@@ -831,54 +831,55 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/one/");
 
-  auto* initial_client =
-      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
-          helper_.get());
-  scoped_refptr<ServiceWorkerRegistration> registration =
-      RunRegisterJob(script1, options);
-  // Wait until the worker becomes active.
-  base::RunLoop().RunUntilIdle();
   auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
-  registration->SetTaskRunnerForTest(runner);
+  TestServiceWorkerObserver observer(helper_->context_wrapper());
+
+  scoped_refptr<ServiceWorkerRegistration> old_registration =
+      RunRegisterJob(script1, options);
+  old_registration->SetTaskRunnerForTest(runner);
+
+  // Wait until the worker becomes active.
+  scoped_refptr<ServiceWorkerVersion> old_version =
+      old_registration->GetNewestVersion();
+  observer.RunUntilActivated(old_version.get(), runner);
 
   // Add a controllee and queue an unregister to force the uninstalling state.
   ServiceWorkerProviderHost* host = CreateControllee();
-  scoped_refptr<ServiceWorkerVersion> old_version =
-      registration->active_version();
   old_version->AddControllee(host);
+  EXPECT_TRUE(old_version->HasControllee());
   RunUnregisterJob(options.scope);
+  EXPECT_TRUE(old_registration->is_uninstalling());
 
   // Register another script.
-  EXPECT_EQ(registration, RunRegisterJob(script2, options));
-  // Wait until the worker becomes installed.
-  base::RunLoop().RunUntilIdle();
-
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_EQ(old_version, registration->active_version());
+  scoped_refptr<ServiceWorkerRegistration> new_registration =
+      RunRegisterJob(script2, options);
+  EXPECT_NE(old_registration, new_registration);
+  new_registration->SetTaskRunnerForTest(runner);
+  // Wait until the worker becomes active.
   scoped_refptr<ServiceWorkerVersion> new_version =
-      registration->waiting_version();
-
-  // Verify the new version is installed but not activated yet.
-  EXPECT_EQ(nullptr, registration->installing_version());
-  EXPECT_TRUE(new_version);
-  EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, new_version->running_status());
-  EXPECT_EQ(ServiceWorkerVersion::INSTALLED, new_version->status());
-
-  // Make the old version eligible for eviction.
-  old_version->RemoveControllee(host->client_uuid());
-  RequestTermination(&initial_client->host());
-
-  // Wait for activated.
-  TestServiceWorkerObserver observer(helper_->context_wrapper());
+      new_registration->GetNewestVersion();
+  EXPECT_NE(old_version, new_version);
   observer.RunUntilActivated(new_version.get(), runner);
 
-  // Verify state after the new version is activated.
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_FALSE(registration->is_uninstalled());
-  EXPECT_EQ(nullptr, registration->installing_version());
-  EXPECT_EQ(nullptr, registration->waiting_version());
-  EXPECT_EQ(new_version, registration->active_version());
+  EXPECT_TRUE(old_registration->is_uninstalling());
+  EXPECT_EQ(old_version, old_registration->active_version());
+  EXPECT_FALSE(old_registration->waiting_version());
+
+  // Verify the new version is installed and activated, but has no controllee.
+  EXPECT_FALSE(new_registration->installing_version());
+  EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, new_version->running_status());
   EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, new_version->status());
+  EXPECT_FALSE(new_version->HasControllee());
+
+  // Remove the controllee from the old version and verify the new version
+  // doesn't get a controllee.
+  EXPECT_TRUE(old_version->HasControllee());
+  old_version->RemoveControllee(host->client_uuid());
+  EXPECT_FALSE(old_version->HasControllee());
+  EXPECT_FALSE(new_version->HasControllee());
+
+  // Cleanup.
+  RunUnregisterJob(options.scope);
 }
 
 TEST_F(ServiceWorkerJobTest, RegisterAndUnregisterWhileUninstalling) {
@@ -887,28 +888,36 @@
   blink::mojom::ServiceWorkerRegistrationOptions options;
   options.scope = GURL("https://www.example.com/one/");
 
-  scoped_refptr<ServiceWorkerRegistration> registration =
+  auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
+  TestServiceWorkerObserver observer(helper_->context_wrapper());
+
+  scoped_refptr<ServiceWorkerRegistration> old_registration =
       RunRegisterJob(script1, options);
+  old_registration->SetTaskRunnerForTest(runner);
+
   // Wait until the worker becomes active.
-  base::RunLoop().RunUntilIdle();
+  scoped_refptr<ServiceWorkerVersion> old_version =
+      old_registration->GetNewestVersion();
+  observer.RunUntilActivated(old_version.get(), runner);
 
   // Add a controllee and queue an unregister to force the uninstalling state.
   ServiceWorkerProviderHost* host = CreateControllee();
-  scoped_refptr<ServiceWorkerVersion> old_version =
-      registration->active_version();
   old_version->AddControllee(host);
   RunUnregisterJob(options.scope);
 
-  EXPECT_EQ(registration, RunRegisterJob(script2, options));
-  // Wait until the worker becomes installed.
-  base::RunLoop().RunUntilIdle();
+  scoped_refptr<ServiceWorkerRegistration> new_registration =
+      RunRegisterJob(script2, options);
+  EXPECT_NE(old_registration, new_registration);
 
-  EXPECT_EQ(registration, FindRegistrationForScope(options.scope));
+  // Wait until the worker becomes active.
   scoped_refptr<ServiceWorkerVersion> new_version =
-      registration->waiting_version();
-  ASSERT_TRUE(new_version);
+      new_registration->GetNewestVersion();
+  ASSERT_NE(old_version, new_version);
+  observer.RunUntilActivated(new_version.get(), runner);
 
-  // Unregister the registration (but it's still live).
+  EXPECT_EQ(new_registration, FindRegistrationForScope(options.scope));
+
+  // Unregister the new registration (but the old one is still live).
   RunUnregisterJob(options.scope);
   // Now it's not found in the storage.
   RunUnregisterJob(options.scope,
@@ -916,82 +925,23 @@
 
   FindRegistrationForScope(options.scope,
                            blink::ServiceWorkerStatusCode::kErrorNotFound);
-  EXPECT_TRUE(registration->is_uninstalling());
-  EXPECT_EQ(old_version, registration->active_version());
+  EXPECT_TRUE(old_registration->is_uninstalling());
+  EXPECT_EQ(old_version, old_registration->active_version());
 
   EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, old_version->running_status());
   EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, old_version->status());
-  EXPECT_EQ(EmbeddedWorkerStatus::RUNNING, new_version->running_status());
-  EXPECT_EQ(ServiceWorkerVersion::INSTALLED, new_version->status());
 
   old_version->RemoveControllee(host->client_uuid());
+  // Wait until the worker becomes redundant.
+  observer.RunUntilStatusChange(old_version.get(),
+                                ServiceWorkerVersion::REDUNDANT);
 
   WaitForVersionRunningStatus(old_version, EmbeddedWorkerStatus::STOPPED);
   WaitForVersionRunningStatus(new_version, EmbeddedWorkerStatus::STOPPED);
 
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_TRUE(registration->is_uninstalled());
-
-  EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, old_version->running_status());
+  EXPECT_FALSE(old_registration->is_uninstalling());
+  EXPECT_TRUE(old_registration->is_uninstalled());
   EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, old_version->status());
-  EXPECT_EQ(EmbeddedWorkerStatus::STOPPED, new_version->running_status());
-  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, new_version->status());
-}
-
-TEST_F(ServiceWorkerJobTest, RegisterSameScriptMultipleTimesWhileUninstalling) {
-  GURL script1("https://www.example.com/service_worker.js");
-  GURL script2("https://www.example.com/service_worker.js?new");
-  blink::mojom::ServiceWorkerRegistrationOptions options;
-  options.scope = GURL("https://www.example.com/one/");
-
-  auto* initial_client =
-      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
-          helper_.get());
-  scoped_refptr<ServiceWorkerRegistration> registration =
-      RunRegisterJob(script1, options);
-  // Wait until the worker becomes active.
-  base::RunLoop().RunUntilIdle();
-  auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
-  registration->SetTaskRunnerForTest(runner);
-
-  // Add a controllee and queue an unregister to force the uninstalling state.
-  ServiceWorkerProviderHost* host = CreateControllee();
-  scoped_refptr<ServiceWorkerVersion> old_version =
-      registration->active_version();
-  old_version->AddControllee(host);
-  RunUnregisterJob(options.scope);
-
-  EXPECT_EQ(registration, RunRegisterJob(script2, options));
-  // Wait until the worker becomes installed.
-  base::RunLoop().RunUntilIdle();
-
-  scoped_refptr<ServiceWorkerVersion> new_version =
-      registration->waiting_version();
-  ASSERT_TRUE(new_version);
-
-  RunUnregisterJob(options.scope);
-
-  EXPECT_TRUE(registration->is_uninstalling());
-
-  EXPECT_EQ(registration, RunRegisterJob(script2, options));
-
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_EQ(new_version, registration->waiting_version());
-
-  old_version->RemoveControllee(host->client_uuid());
-  RequestTermination(&initial_client->host());
-
-  // Wait for activated.
-  TestServiceWorkerObserver observer(helper_->context_wrapper());
-  observer.RunUntilActivated(new_version.get(), runner);
-
-  // Verify state after the new version is activated.
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_FALSE(registration->is_uninstalled());
-  EXPECT_EQ(nullptr, registration->installing_version());
-  EXPECT_EQ(nullptr, registration->waiting_version());
-  EXPECT_EQ(new_version, registration->active_version());
-  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, new_version->status());
 }
 
 // A fake service worker for toggling whether a fetch event handler exists.
@@ -1880,68 +1830,6 @@
   EXPECT_EQ(nullptr, registration->installing_version());
 }
 
-TEST_P(ServiceWorkerUpdateJobTest, RegisterMultipleTimesWhileUninstalling) {
-  GURL script1("https://www.example.com/service_worker.js?first");
-  GURL script2("https://www.example.com/service_worker.js?second");
-  GURL script3("https://www.example.com/service_worker.js?third");
-  blink::mojom::ServiceWorkerRegistrationOptions options;
-  options.scope = GURL("https://www.example.com/one/");
-
-  auto* initial_client =
-      helper_->AddNewPendingInstanceClient<FakeEmbeddedWorkerInstanceClient>(
-          helper_.get());
-  scoped_refptr<ServiceWorkerRegistration> registration =
-      RunRegisterJob(script1, options);
-  // Wait until the worker becomes actvie.
-  base::RunLoop().RunUntilIdle();
-  auto runner = base::MakeRefCounted<base::TestSimpleTaskRunner>();
-  registration->SetTaskRunnerForTest(runner);
-
-  // Add a controllee and queue an unregister to force the uninstalling state.
-  ServiceWorkerProviderHost* host = CreateControllee();
-  scoped_refptr<ServiceWorkerVersion> first_version =
-      registration->active_version();
-  first_version->AddControllee(host);
-  RunUnregisterJob(options.scope);
-
-  EXPECT_EQ(registration, RunRegisterJob(script2, options));
-  // Wait until the worker becomes installed.
-  base::RunLoop().RunUntilIdle();
-
-  scoped_refptr<ServiceWorkerVersion> second_version =
-      registration->waiting_version();
-  ASSERT_TRUE(second_version);
-
-  RunUnregisterJob(options.scope);
-
-  EXPECT_TRUE(registration->is_uninstalling());
-
-  EXPECT_EQ(registration, RunRegisterJob(script3, options));
-  TestServiceWorkerObserver observer(helper_->context_wrapper());
-  observer.RunUntilStatusChange(second_version.get(),
-                                ServiceWorkerVersion::REDUNDANT);
-  scoped_refptr<ServiceWorkerVersion> third_version =
-      registration->waiting_version();
-  ASSERT_TRUE(third_version);
-
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_EQ(ServiceWorkerVersion::REDUNDANT, second_version->status());
-
-  first_version->RemoveControllee(host->client_uuid());
-  RequestTermination(&initial_client->host());
-
-  // Wait for activated.
-  observer.RunUntilActivated(third_version.get(), runner);
-
-  // Verify the new version is activated.
-  EXPECT_FALSE(registration->is_uninstalling());
-  EXPECT_FALSE(registration->is_uninstalled());
-  EXPECT_EQ(nullptr, registration->installing_version());
-  EXPECT_EQ(nullptr, registration->waiting_version());
-  EXPECT_EQ(third_version, registration->active_version());
-  EXPECT_EQ(ServiceWorkerVersion::ACTIVATED, third_version->status());
-}
-
 class CheckPauseAfterDownloadEmbeddedWorkerInstanceClient
     : public FakeEmbeddedWorkerInstanceClient {
  public:
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index 60afc9c2..719c7ff7 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -597,8 +597,15 @@
   if (!IsContextSecureForServiceWorker())
     return;
   size_t key = registration->scope().spec().size();
-  if (base::Contains(matching_registrations_, key))
-    return;
+  if (base::Contains(matching_registrations_, key)) {
+    if (registration == matching_registrations_[key]) {
+      // TODO(https://crbug.com/971571): Verify this can never happen and
+      // replace with a DCHECK.
+      return;
+    }
+    uninstalled_matching_registrations_[key].emplace(
+        std::move(matching_registrations_[key]));
+  }
   registration->AddListener(this);
   matching_registrations_[key] = registration;
   ReturnRegistrationForReadyIfNeeded();
@@ -608,12 +615,18 @@
     ServiceWorkerRegistration* registration) {
   DCHECK_NE(controller_registration_, registration);
 #if DCHECK_IS_ON()
-  DCHECK(IsMatchingRegistration(registration));
+  DCHECK(IsMatchingRegistration(registration) ||
+         IsMatchingUninstalledRegistration(registration));
 #endif  // DCHECK_IS_ON()
 
   registration->RemoveListener(this);
   size_t key = registration->scope().spec().size();
-  matching_registrations_.erase(key);
+  if (registration == matching_registrations_[key]) {
+    matching_registrations_.erase(key);
+    return;
+  }
+  DCHECK(base::Contains(uninstalled_matching_registrations_, key));
+  uninstalled_matching_registrations_.erase(key);
 }
 
 ServiceWorkerRegistration* ServiceWorkerProviderHost::MatchRegistration()
@@ -803,6 +816,15 @@
     return false;
   return true;
 }
+
+bool ServiceWorkerProviderHost::IsMatchingUninstalledRegistration(
+    ServiceWorkerRegistration* registration) {
+  size_t key = registration->scope().spec().size();
+  if (!base::Contains(uninstalled_matching_registrations_, key)) {
+    return false;
+  }
+  return base::Contains(uninstalled_matching_registrations_[key], registration);
+}
 #endif  // DCHECK_IS_ON()
 
 void ServiceWorkerProviderHost::RemoveAllMatchingRegistrations() {
@@ -812,6 +834,13 @@
     registration->RemoveListener(this);
   }
   matching_registrations_.clear();
+
+  for (const auto& it : uninstalled_matching_registrations_) {
+    for (const auto& registration : it.second) {
+      registration->RemoveListener(this);
+    }
+  }
+  uninstalled_matching_registrations_.clear();
 }
 
 void ServiceWorkerProviderHost::ReturnRegistrationForReadyIfNeeded() {
diff --git a/content/browser/service_worker/service_worker_provider_host.h b/content/browser/service_worker/service_worker_provider_host.h
index 1a74b95..be572f1 100644
--- a/content/browser/service_worker/service_worker_provider_host.h
+++ b/content/browser/service_worker/service_worker_provider_host.h
@@ -373,9 +373,17 @@
   // This is subtle: it doesn't keep all registrations (e.g., from storage) in
   // memory, but just the ones that are possibly the longest-matching one. The
   // best match from storage is added at load time. That match can't uninstall
-  // while this host is a controllee, so all the other stored registrations can
-  // be ignored. Only a newly installed registration can claim it, and new
-  // installing registrations are added as matches.
+  // while this host is a controllee, but it can be unregistered and newly
+  // installed registations can override the entry if they have the same scope
+  // as an existing match. Since it is possible that multiple service workers
+  // for the same scope exist at any given time, e.g. when a worker is
+  // registered while an unregistered worker for the same scope is controlling
+  // clients, it's the callers responsibility to remove the matching
+  // registration when they no longer need it.
+  //
+  // Note: Chrome's implementation of .ready differs from the spec s.t. the
+  // ready promise is only resolved once it has been accessed by the user.
+  // https://github.com/w3c/ServiceWorker/issues/1477
   void AddMatchingRegistration(ServiceWorkerRegistration* registration);
   void RemoveMatchingRegistration(ServiceWorkerRegistration* registration);
 
@@ -515,6 +523,8 @@
 
 #if DCHECK_IS_ON()
   bool IsMatchingRegistration(ServiceWorkerRegistration* registration) const;
+  bool IsMatchingUninstalledRegistration(
+      ServiceWorkerRegistration* registration);
 #endif  // DCHECK_IS_ON()
 
   // Discards all references to matching registrations.
@@ -674,6 +684,14 @@
   // AddMatchingRegistration().
   ServiceWorkerRegistrationMap matching_registrations_;
 
+  // Similar to ServiceWorkerRegistrationMap, but with multiple registrations.
+  using ServiceWorkerRegistrationMultiSet =
+      std::map<size_t, std::set<scoped_refptr<ServiceWorkerRegistration>>>;
+  // Contains registrations that have been uninstalled, e.g., by
+  // |registration.unregister()|, but are still alive, e.g., because they are
+  // controlling clients.
+  ServiceWorkerRegistrationMultiSet uninstalled_matching_registrations_;
+
   // Contains all ServiceWorkerRegistrationObjectHost instances corresponding to
   // the service worker registration JavaScript objects for the hosted execution
   // context (service worker global scope or service worker client) in the
diff --git a/content/browser/service_worker/service_worker_register_job.cc b/content/browser/service_worker/service_worker_register_job.cc
index 989d323..68bd487 100644
--- a/content/browser/service_worker/service_worker_register_job.cc
+++ b/content/browser/service_worker/service_worker_register_job.cc
@@ -124,15 +124,7 @@
                                weak_factory_.GetWeakPtr());
   }
 
-  scoped_refptr<ServiceWorkerRegistration> registration =
-      context_->storage()->GetUninstallingRegistration(scope_);
-  if (registration.get())
-    base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE,
-        base::BindOnce(std::move(next_step),
-                       blink::ServiceWorkerStatusCode::kOk, registration));
-  else
-    context_->storage()->FindRegistrationForScope(scope_, std::move(next_step));
+  context_->storage()->FindRegistrationForScope(scope_, std::move(next_step));
 }
 
 void ServiceWorkerRegisterJob::Abort() {
diff --git a/content/browser/tracing/background_tracing_active_scenario.cc b/content/browser/tracing/background_tracing_active_scenario.cc
index 4bba2980..b79c56f5 100644
--- a/content/browser/tracing/background_tracing_active_scenario.cc
+++ b/content/browser/tracing/background_tracing_active_scenario.cc
@@ -244,7 +244,9 @@
                 parent_scenario_->GetWeakPtr(), std::move(on_success))),
             true /* compress_with_background_priority */);
 
-    TracingControllerImpl::GetInstance()->StopTracing(trace_data_endpoint);
+    TracingControllerImpl::GetInstance()->StopTracing(
+        trace_data_endpoint, "",
+        parent_scenario_->GetConfig()->requires_anonymized_data());
   }
 
   void AbortScenario(const base::RepeatingClosure& on_abort_callback) override {
diff --git a/content/browser/tracing/background_tracing_config_impl.cc b/content/browser/tracing/background_tracing_config_impl.cc
index 93eca01..ee41a4f 100644
--- a/content/browser/tracing/background_tracing_config_impl.cc
+++ b/content/browser/tracing/background_tracing_config_impl.cc
@@ -276,10 +276,6 @@
     chrome_config.SetProcessFilterConfig(process_config);
   }
 
-  if (requires_anonymized_data_) {
-    chrome_config.EnableArgumentFilter();
-  }
-
   chrome_config.SetTraceBufferSizeInKb(GetMaximumTraceBufferSizeKb());
 
 #if defined(OS_ANDROID)
diff --git a/content/browser/tracing/background_tracing_manager_browsertest.cc b/content/browser/tracing/background_tracing_manager_browsertest.cc
index 9c682f1..b76c766 100644
--- a/content/browser/tracing/background_tracing_manager_browsertest.cc
+++ b/content/browser/tracing/background_tracing_manager_browsertest.cc
@@ -1589,7 +1589,7 @@
 
   EXPECT_TRUE(BackgroundTracingManager::GetInstance()->SetActiveScenario(
       std::move(config), trace_receiver_helper.get_receive_callback(),
-      BackgroundTracingManager::NO_DATA_FILTERING));
+      BackgroundTracingManager::ANONYMIZE_DATA));
 
   tracelog_helper.WaitForStartTracing();
   background_tracing_helper.WaitForTracingEnabled();
@@ -1598,9 +1598,6 @@
                   ->GetActiveScenarioForTesting()
                   ->GetConfig()
                   ->requires_anonymized_data());
-  EXPECT_TRUE(base::trace_event::TraceLog::GetInstance()
-                  ->GetCurrentTraceConfig()
-                  .IsArgumentFilterEnabled());
 
   // Since we specified a delay in the scenario, we should still be tracing
   // at this point.
diff --git a/content/browser/tracing/tracing_controller_browsertest.cc b/content/browser/tracing/tracing_controller_browsertest.cc
index 6064b19..fb5caa20 100644
--- a/content/browser/tracing/tracing_controller_browsertest.cc
+++ b/content/browser/tracing/tracing_controller_browsertest.cc
@@ -224,10 +224,8 @@
           base::BindOnce(&TracingControllerTest::StartTracingDoneCallbackTest,
                          base::Unretained(this), run_loop.QuitClosure());
 
-      TraceConfig config = TraceConfig();
-      config.EnableArgumentFilter();
-
-      bool result = controller->StartTracing(config, std::move(callback));
+      bool result =
+          controller->StartTracing(TraceConfig(), std::move(callback));
       ASSERT_TRUE(result);
       run_loop.Run();
       EXPECT_EQ(enable_recording_done_callback_count(), 1);
@@ -248,7 +246,9 @@
           base::Bind(&TracingControllerTest::GenerateMetadataDict,
                      base::Unretained(this)));
 
-      bool result = controller->StopTracing(trace_data_endpoint);
+      bool result =
+          controller->StopTracing(trace_data_endpoint, /*agent_label=*/"",
+                                  /*privacy_filtering_enabled=*/true);
       ASSERT_TRUE(result);
       run_loop.Run();
       EXPECT_EQ(disable_recording_done_callback_count(), 1);
diff --git a/content/browser/tracing/tracing_controller_impl.cc b/content/browser/tracing/tracing_controller_impl.cc
index 2db4e71..75139f0 100644
--- a/content/browser/tracing/tracing_controller_impl.cc
+++ b/content/browser/tracing/tracing_controller_impl.cc
@@ -40,7 +40,7 @@
 #include "net/base/network_change_notifier.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/tracing/public/cpp/perfetto/java_heap_profiler/java_heap_profiler.h"
-#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
 #include "services/tracing/public/cpp/trace_event_agent.h"
 #include "services/tracing/public/cpp/traced_process_impl.h"
 #include "services/tracing/public/cpp/tracing_features.h"
@@ -217,21 +217,17 @@
 }
 
 void TracingControllerImpl::ConnectToServiceIfNeeded() {
-  if (!coordinator_) {
+  if (!consumer_host_) {
     GetSystemConnector()->BindInterface(tracing::mojom::kServiceName,
-                                        &coordinator_);
-    coordinator_.set_connection_error_handler(base::BindOnce(
+                                        &consumer_host_);
+    consumer_host_.set_connection_error_handler(base::BindOnce(
         [](TracingControllerImpl* controller) {
-          controller->coordinator_.reset();
+          controller->consumer_host_.reset();
         },
         base::Unretained(this)));
   }
 }
 
-void TracingControllerImpl::DisconnectFromService() {
-  coordinator_ = nullptr;
-}
-
 // Can be called on any thread.
 std::unique_ptr<base::DictionaryValue>
 TracingControllerImpl::GenerateMetadataDict() {
@@ -394,15 +390,27 @@
   trace_config_ =
       std::make_unique<base::trace_event::TraceConfig>(trace_config);
 
+  DCHECK(!tracing_session_host_);
   ConnectToServiceIfNeeded();
-  coordinator_->StartTracing(
-      trace_config.ToString(),
-      base::BindOnce(
-          [](StartTracingDoneCallback callback, bool success) {
-            if (!callback.is_null())
-              std::move(callback).Run();
-          },
-          std::move(callback)));
+
+  perfetto::TraceConfig perfetto_config = tracing::GetDefaultPerfettoConfig(
+      trace_config, /*requires_anonymized_data=*/false);
+
+  mojo::PendingRemote<tracing::mojom::TracingSessionClient>
+      tracing_session_client;
+  binding_.Bind(tracing_session_client.InitWithNewPipeAndPassReceiver());
+  binding_.set_connection_error_handler(base::BindOnce(
+      &TracingControllerImpl::OnTracingFailed, base::Unretained(this)));
+
+  consumer_host_->EnableTracing(
+      mojo::MakeRequest(&tracing_session_host_),
+      std::move(tracing_session_client), std::move(perfetto_config),
+      tracing::mojom::TracingClientPriority::kUserInitiated);
+  tracing_session_host_.set_connection_error_handler(base::BindOnce(
+      &TracingControllerImpl::OnTracingFailed, base::Unretained(this)));
+
+  start_tracing_callback_ = std::move(callback);
+
   // TODO(chiniforooshan): The actual success value should be sent by the
   // callback asynchronously.
   return true;
@@ -512,8 +520,9 @@
 
 bool TracingControllerImpl::StopTracing(
     const scoped_refptr<TraceDataEndpoint>& trace_data_endpoint,
-    const std::string& agent_label) {
-  if (!IsTracing() || drainer_ || !coordinator_)
+    const std::string& agent_label,
+    bool privacy_filtering_enabled) {
+  if (!IsTracing() || drainer_ || !tracing_session_host_)
     return false;
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
@@ -521,26 +530,36 @@
   base::trace_event::TraceLog::GetInstance()->AddClockSyncMetadataEvent();
 #endif
 
+  // Setting the argument filter is no longer supported just in the TraceConfig;
+  // clients of the TracingController that need filtering need to pass that
+  // option to StopTracing directly as an argument. This is due to Perfetto-
+  // based tracing requiring this filtering to be done during serialization
+  // time and not during tracing time.
+  // TODO(oysteine): Remove the config option once the legacy IPC layer is
+  // removed.
+  CHECK(privacy_filtering_enabled || !trace_config_->IsArgumentFilterEnabled());
+
   tracing::TraceStartupConfig::GetInstance()->SetDisabled();
   trace_data_endpoint_ = std::move(trace_data_endpoint);
   is_data_complete_ = false;
-  is_metadata_available_ = false;
-  mojo::DataPipe data_pipe;
-  drainer_.reset(
-      new mojo::DataPipeDrainer(this, std::move(data_pipe.consumer_handle)));
-  if (agent_label.empty()) {
-    // Stop and flush all agents.
-    coordinator_->StopAndFlush(
-        std::move(data_pipe.producer_handle),
-        base::BindRepeating(&TracingControllerImpl::OnMetadataAvailable,
-                            base::Unretained(this)));
-  } else {
-    // Stop all and flush a particular agent.
-    coordinator_->StopAndFlushAgent(
-        std::move(data_pipe.producer_handle), agent_label,
-        base::BindRepeating(&TracingControllerImpl::OnMetadataAvailable,
-                            base::Unretained(this)));
+  read_buffers_complete_ = false;
+
+  mojo::ScopedDataPipeProducerHandle producer_handle;
+  mojo::ScopedDataPipeConsumerHandle consumer_handle;
+  MojoResult result =
+      mojo::CreateDataPipe(nullptr, &producer_handle, &consumer_handle);
+  if (result != MOJO_RESULT_OK) {
+    CompleteFlush();
+    return true;
   }
+
+  drainer_.reset(new mojo::DataPipeDrainer(this, std::move(consumer_handle)));
+
+  tracing_session_host_->DisableTracingAndEmitJson(
+      agent_label, std::move(producer_handle), privacy_filtering_enabled,
+      base::BindOnce(&TracingControllerImpl::OnReadBuffersComplete,
+                     base::Unretained(this)));
+
   // TODO(chiniforooshan): Is the return value used anywhere?
   return true;
 }
@@ -549,12 +568,14 @@
     GetTraceBufferUsageCallback callback) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
-  ConnectToServiceIfNeeded();
-  coordinator_->RequestBufferUsage(base::BindOnce(
+  if (!tracing_session_host_) {
+    std::move(callback).Run(0.0, 0);
+    return true;
+  }
+
+  tracing_session_host_->RequestBufferUsage(base::BindOnce(
       [](GetTraceBufferUsageCallback callback, bool success, float percent_full,
-         uint32_t approximate_count) {
-        std::move(callback).Run(percent_full, approximate_count);
-      },
+         bool data_loss) { std::move(callback).Run(percent_full, 0); },
       std::move(callback)));
   // TODO(chiniforooshan): The actual success value should be sent by the
   // callback asynchronously.
@@ -565,15 +586,15 @@
   return trace_config_ != nullptr;
 }
 
-void TracingControllerImpl::RegisterTracingUI(TracingUI* tracing_ui) {
-  DCHECK(tracing_uis_.find(tracing_ui) == tracing_uis_.end());
-  tracing_uis_.insert(tracing_ui);
+void TracingControllerImpl::OnTracingEnabled() {
+  if (start_tracing_callback_)
+    std::move(start_tracing_callback_).Run();
 }
 
-void TracingControllerImpl::UnregisterTracingUI(TracingUI* tracing_ui) {
-  auto it = tracing_uis_.find(tracing_ui);
-  DCHECK(it != tracing_uis_.end());
-  tracing_uis_.erase(it);
+void TracingControllerImpl::OnTracingDisabled() {}
+
+void TracingControllerImpl::OnTracingFailed() {
+  CompleteFlush();
 }
 
 void TracingControllerImpl::OnDataAvailable(const void* data,
@@ -586,42 +607,24 @@
 }
 
 void TracingControllerImpl::CompleteFlush() {
-  if (trace_data_endpoint_) {
+  if (trace_data_endpoint_)
     trace_data_endpoint_->ReceivedTraceFinalContents();
-  }
-  filtered_metadata_.reset(nullptr);
+
   trace_data_endpoint_ = nullptr;
   trace_config_ = nullptr;
   drainer_ = nullptr;
+  tracing_session_host_.reset();
+  binding_.Close();
 }
 
 void TracingControllerImpl::OnDataComplete() {
   is_data_complete_ = true;
-  if (is_metadata_available_)
+  if (read_buffers_complete_)
     CompleteFlush();
 }
 
-void TracingControllerImpl::OnMetadataAvailable(base::Value metadata) {
-  DCHECK(!filtered_metadata_);
-  is_metadata_available_ = true;
-  base::trace_event::MetadataFilterPredicate metadata_filter;
-  if (trace_config_->IsArgumentFilterEnabled()) {
-    metadata_filter = base::trace_event::TraceLog::GetInstance()
-                          ->GetMetadataFilterPredicate();
-  }
-  if (metadata_filter.is_null()) {
-    filtered_metadata_ = base::DictionaryValue::From(
-        base::Value::ToUniquePtrValue(std::move(metadata)));
-  } else {
-    filtered_metadata_ = std::make_unique<base::DictionaryValue>();
-    for (auto it : metadata.DictItems()) {
-      if (metadata_filter.Run(it.first)) {
-        filtered_metadata_->SetKey(it.first, std::move(it.second));
-      } else {
-        filtered_metadata_->SetKey(it.first, base::Value("__stripped__"));
-      }
-    }
-  }
+void TracingControllerImpl::OnReadBuffersComplete() {
+  read_buffers_complete_ = true;
   if (is_data_complete_)
     CompleteFlush();
 }
diff --git a/content/browser/tracing/tracing_controller_impl.h b/content/browser/tracing/tracing_controller_impl.h
index 34d8841..9c00a9b4 100644
--- a/content/browser/tracing/tracing_controller_impl.h
+++ b/content/browser/tracing/tracing_controller_impl.h
@@ -15,8 +15,9 @@
 #include "base/timer/timer.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/tracing_controller.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
-#include "services/tracing/public/mojom/tracing.mojom.h"
+#include "services/tracing/public/mojom/perfetto_service.mojom.h"
 
 namespace base {
 
@@ -35,14 +36,14 @@
 
 class PerfettoFileTracer;
 class TracingDelegate;
-class TracingUI;
 
 class TracingControllerImpl : public TracingController,
-                              public mojo::DataPipeDrainer::Client {
+                              public mojo::DataPipeDrainer::Client,
+                              public tracing::mojom::TracingSessionClient {
  public:
   // Create an endpoint for dumping the trace data to a callback.
   CONTENT_EXPORT static scoped_refptr<TraceDataEndpoint> CreateCallbackEndpoint(
-      base::OnceCallback<void(std::unique_ptr<std::string>)> callback);
+      CompletionCallback callback);
 
   CONTENT_EXPORT static scoped_refptr<TraceDataEndpoint>
   CreateCompressedStringEndpoint(scoped_refptr<TraceDataEndpoint> endpoint,
@@ -59,12 +60,16 @@
                     StartTracingDoneCallback callback) override;
   bool StopTracing(const scoped_refptr<TraceDataEndpoint>& endpoint) override;
   bool StopTracing(const scoped_refptr<TraceDataEndpoint>& endpoint,
-                   const std::string& agent_label) override;
+                   const std::string& agent_label,
+                   bool privacy_filtering_enabled = false) override;
   bool GetTraceBufferUsage(GetTraceBufferUsageCallback callback) override;
   bool IsTracing() override;
 
-  void RegisterTracingUI(TracingUI* tracing_ui);
-  void UnregisterTracingUI(TracingUI* tracing_ui);
+  // tracing::mojom::TracingSessionClient implementation:
+  void OnTracingEnabled() override;
+  void OnTracingDisabled() override;
+
+  void OnTracingFailed();
 
   // For unittests.
   CONTENT_EXPORT void SetTracingDelegateForTesting(
@@ -92,14 +97,13 @@
   ~TracingControllerImpl() override;
   void AddAgents();
   void ConnectToServiceIfNeeded();
-  void DisconnectFromService();
   std::unique_ptr<base::DictionaryValue> GenerateMetadataDict();
 
   // mojo::DataPipeDrainer::Client
   void OnDataAvailable(const void* data, size_t num_bytes) override;
   void OnDataComplete() override;
 
-  void OnMetadataAvailable(base::Value metadata);
+  void OnReadBuffersComplete();
 
   void CompleteFlush();
 
@@ -111,16 +115,18 @@
   base::FilePath GetStartupTraceFileName() const;
 
   std::unique_ptr<PerfettoFileTracer> perfetto_file_tracer_;
-  tracing::mojom::CoordinatorPtr coordinator_;
+  tracing::mojom::ConsumerHostPtr consumer_host_;
+  tracing::mojom::TracingSessionHostPtr tracing_session_host_;
+  mojo::Binding<tracing::mojom::TracingSessionClient> binding_{this};
+  StartTracingDoneCallback start_tracing_callback_;
+
   std::vector<std::unique_ptr<tracing::BaseAgent>> agents_;
   std::unique_ptr<TracingDelegate> delegate_;
   std::unique_ptr<base::trace_event::TraceConfig> trace_config_;
   std::unique_ptr<mojo::DataPipeDrainer> drainer_;
   scoped_refptr<TraceDataEndpoint> trace_data_endpoint_;
-  std::unique_ptr<base::DictionaryValue> filtered_metadata_;
-  std::set<TracingUI*> tracing_uis_;
   bool is_data_complete_ = false;
-  bool is_metadata_available_ = false;
+  bool read_buffers_complete_ = false;
 
   base::FilePath startup_trace_file_;
   // This timer initiates trace file saving.
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 35c3889e..82d6006 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -153,8 +153,8 @@
   if (base::FeatureList::IsEnabled(features::kPeriodicBackgroundSync))
     WebRuntimeFeatures::EnablePeriodicBackgroundSync(true);
 
-  if (base::FeatureList::IsEnabled(features::kWebXr))
-    WebRuntimeFeatures::EnableWebXR(true);
+  WebRuntimeFeatures::EnableWebXR(
+      base::FeatureList::IsEnabled(features::kWebXr));
 
   if (base::FeatureList::IsEnabled(features::kWebXrArDOMOverlay))
     WebRuntimeFeatures::EnableWebXRARDOMOverlay(true);
@@ -171,6 +171,9 @@
   if (base::FeatureList::IsEnabled(features::kWebXrPlaneDetection))
     WebRuntimeFeatures::EnableWebXRPlaneDetection(true);
 
+  if (base::FeatureList::IsEnabled(features::kWebXrGamepadModule))
+    WebRuntimeFeatures::EnableWebXrGamepadModule(true);
+
   WebRuntimeFeatures::EnableFetchMetadata(
       base::FeatureList::IsEnabled(network::features::kFetchMetadata));
   WebRuntimeFeatures::EnableFetchMetadataDestination(
diff --git a/content/child/webthemeengine_impl_default_browsertest.cc b/content/child/webthemeengine_impl_default_browsertest.cc
index 30ae37df..b50a8b78 100644
--- a/content/child/webthemeengine_impl_default_browsertest.cc
+++ b/content/child/webthemeengine_impl_default_browsertest.cc
@@ -19,6 +19,11 @@
 
 #if defined(OS_WIN)
 IN_PROC_BROWSER_TEST_F(WebThemeEngineImplDefaultBrowserTest, GetSystemColor) {
+  // The test non-deterministically fails on Windows Server 2008 builders due to
+  // a difference in the default theme. As a result, only run the test on
+  // Windows version 7 and above.
+  if (base::win::GetVersion() < base::win::Version::WIN7)
+    return;
   GURL url(
       "data:text/html,"
       "<!doctype html><html>"
diff --git a/content/common/background_fetch/background_fetch_types.cc b/content/common/background_fetch/background_fetch_types.cc
index f87695f5..35219482 100644
--- a/content/common/background_fetch/background_fetch_types.cc
+++ b/content/common/background_fetch/background_fetch_types.cc
@@ -36,6 +36,7 @@
       response->response_time, response->cache_storage_cache_name,
       response->cors_exposed_header_names,
       CloneSerializedBlob(response->side_data_blob),
+      CloneSerializedBlob(response->side_data_blob_for_cache_put),
       response->content_security_policy.Clone());
 }
 
diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
index 4edc3b9e..55ed58db 100644
--- a/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java
@@ -466,8 +466,9 @@
             }
             case ACTION_SET_TEXT: {
                 if (!WebContentsAccessibilityImplJni.get().isEditableText(
-                            mNativeObj, WebContentsAccessibilityImpl.this, virtualViewId))
+                            mNativeObj, WebContentsAccessibilityImpl.this, virtualViewId)) {
                     return false;
+                }
                 if (arguments == null) return false;
                 String newText = arguments.getString(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE);
                 if (newText == null) return false;
@@ -481,8 +482,9 @@
             }
             case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
                 if (!WebContentsAccessibilityImplJni.get().isEditableText(
-                            mNativeObj, WebContentsAccessibilityImpl.this, virtualViewId))
+                            mNativeObj, WebContentsAccessibilityImpl.this, virtualViewId)) {
                     return false;
+                }
                 int selectionStart = 0;
                 int selectionEnd = 0;
                 if (arguments != null) {
@@ -808,8 +810,9 @@
                 mNativeObj, WebContentsAccessibilityImpl.this, newAccessibilityFocusId);
 
         if (WebContentsAccessibilityImplJni.get().isAutofillPopupNode(
-                    mNativeObj, WebContentsAccessibilityImpl.this, mAccessibilityFocusId))
+                    mNativeObj, WebContentsAccessibilityImpl.this, mAccessibilityFocusId)) {
             mAutofillPopupView.requestFocus();
+        }
 
         sendAccessibilityEvent(
                 mAccessibilityFocusId, AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
@@ -1004,7 +1007,14 @@
 
     @CalledByNative
     private void handleSliderChanged(int id) {
-        sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SCROLLED);
+        // If the node has accessibility focus, fire TYPE_VIEW_SELECTED, which triggers
+        // TalkBack to announce the change. If not, fire TYPE_VIEW_SCROLLED, which
+        // does not trigger an immediate announcement but still ensures some event is fired.
+        if (mAccessibilityFocusId == id) {
+            sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SELECTED);
+        } else {
+            sendAccessibilityEvent(id, AccessibilityEvent.TYPE_VIEW_SCROLLED);
+        }
     }
 
     @CalledByNative
diff --git a/content/public/browser/tracing_controller.h b/content/public/browser/tracing_controller.h
index 7f2ba91..3927389d 100644
--- a/content/public/browser/tracing_controller.h
+++ b/content/public/browser/tracing_controller.h
@@ -114,7 +114,8 @@
       const scoped_refptr<TraceDataEndpoint>& trace_data_endpoint) = 0;
   virtual bool StopTracing(
       const scoped_refptr<TraceDataEndpoint>& trace_data_endpoint,
-      const std::string& agent_label) = 0;
+      const std::string& agent_label,
+      bool privacy_filtering_enabled = false) = 0;
 
   // Get the maximum across processes of trace buffer percent full state.
   // When the TraceBufferUsage value is determined, the callback is
diff --git a/content/public/common/content_features.cc b/content/public/common/content_features.cc
index 49770ae..ec56ae7 100644
--- a/content/public/common/content_features.cc
+++ b/content/public/common/content_features.cc
@@ -102,6 +102,12 @@
 const base::Feature kCacheStorageParallelOps{"CacheStorageParallelOps",
                                              base::FEATURE_ENABLED_BY_DEFAULT};
 
+// Enables eagerly reading the response body in cache.match() when the
+// operation was started from a FetchEvent handler with a matching request
+// URL.
+const base::Feature kCacheStorageEagerReading{
+    "CacheStorageEagerReading", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // If Canvas2D Image Chromium is allowed, this feature controls whether it is
 // enabled.
 const base::Feature kCanvas2DImageChromium {
@@ -693,7 +699,7 @@
 const base::Feature kWebUsb{"WebUSB", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Controls whether the WebXR Device API is enabled.
-const base::Feature kWebXr{"WebXR", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kWebXr{"WebXR", base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enables access to AR features via the WebXR API.
 const base::Feature kWebXrArModule{"WebXRARModule",
@@ -703,6 +709,10 @@
 const base::Feature kWebXrAnchors{"WebXRAnchors",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables access to the WebXR Device API gamepad module.
+const base::Feature kWebXrGamepadModule{"WebXrGamepadModule",
+                                        base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables access to raycasting against estimated XR scene geometry.
 const base::Feature kWebXrHitTest{"WebXRHitTest",
                                   base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/content/public/common/content_features.h b/content/public/common/content_features.h
index 2dda53e..da8a21e 100644
--- a/content/public/common/content_features.h
+++ b/content/public/common/content_features.h
@@ -32,6 +32,7 @@
 CONTENT_EXPORT extern const base::Feature kBundledHTTPExchanges;
 CONTENT_EXPORT extern const base::Feature kCacheInlineScriptCode;
 CONTENT_EXPORT extern const base::Feature kCacheStorageParallelOps;
+CONTENT_EXPORT extern const base::Feature kCacheStorageEagerReading;
 CONTENT_EXPORT extern const base::Feature kCanvas2DImageChromium;
 CONTENT_EXPORT extern const base::Feature kConsolidatedMovementXY;
 CONTENT_EXPORT extern const base::Feature kCookieDeprecationMessages;
@@ -148,6 +149,7 @@
 CONTENT_EXPORT extern const base::Feature kWebXrArModule;
 CONTENT_EXPORT extern const base::Feature kWebXrAnchors;
 CONTENT_EXPORT extern const base::Feature kWebXrArDOMOverlay;
+CONTENT_EXPORT extern const base::Feature kWebXrGamepadModule;
 CONTENT_EXPORT extern const base::Feature kWebXrHitTest;
 CONTENT_EXPORT extern const base::Feature kWebXrPlaneDetection;
 CONTENT_EXPORT extern const base::Feature kScriptStreamingOnPreload;
diff --git a/content/renderer/input/render_widget_input_handler.cc b/content/renderer/input/render_widget_input_handler.cc
index c9866111..c557b36 100644
--- a/content/renderer/input/render_widget_input_handler.cc
+++ b/content/renderer/input/render_widget_input_handler.cc
@@ -589,7 +589,7 @@
                                           granularity);
     if (injected_type == WebInputEvent::Type::kGestureScrollBegin) {
       gesture_event->data.scroll_begin.scrollable_area_element_id =
-          scrollable_area_element_id.GetInternalValue();
+          scrollable_area_element_id.GetStableId();
     }
 
     ui::LatencyInfo latency_info;
@@ -668,7 +668,7 @@
             params.scroll_delta, params.granularity);
     if (params.type == WebInputEvent::Type::kGestureScrollBegin) {
       gesture_event->data.scroll_begin.scrollable_area_element_id =
-          params.scrollable_area_element_id.GetInternalValue();
+          params.scrollable_area_element_id.GetStableId();
       last_injected_gesture_was_begin_ = true;
     } else {
       last_injected_gesture_was_begin_ = false;
diff --git a/content/renderer/loader/navigation_body_loader.cc b/content/renderer/loader/navigation_body_loader.cc
index c576e2b..09762f76 100644
--- a/content/renderer/loader/navigation_body_loader.cc
+++ b/content/renderer/loader/navigation_body_loader.cc
@@ -10,7 +10,9 @@
 #include "content/renderer/loader/code_cache_loader_impl.h"
 #include "content/renderer/loader/resource_load_stats.h"
 #include "content/renderer/loader/web_url_loader_impl.h"
+#include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/web/web_navigation_params.h"
 
 namespace content {
@@ -23,7 +25,7 @@
     mojom::CommonNavigationParamsPtr common_params,
     mojom::CommitNavigationParamsPtr commit_params,
     int request_id,
-    const network::ResourceResponseHead& response_head,
+    network::mojom::URLResponseHeadPtr response_head,
     mojo::ScopedDataPipeConsumerHandle response_body,
     network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
@@ -50,11 +52,11 @@
         navigation_params->redirects[i];
     auto& redirect_info = commit_params->redirect_infos[i];
     auto& redirect_response = commit_params->redirect_response[i];
-    NotifyResourceRedirectReceived(render_frame_id, resource_load_info.get(),
-                                   redirect_info, redirect_response);
     WebURLLoaderImpl::PopulateURLResponse(
         url, *redirect_response, &redirect.redirect_response,
-        response_head.ssl_info.has_value(), request_id);
+        response_head->ssl_info.has_value(), request_id);
+    NotifyResourceRedirectReceived(render_frame_id, resource_load_info.get(),
+                                   redirect_info, std::move(redirect_response));
     if (url.SchemeIs(url::kDataScheme))
       redirect.redirect_response.SetHttpStatusCode(200);
     redirect.new_url = redirect_info.new_url;
@@ -69,29 +71,28 @@
   }
 
   WebURLLoaderImpl::PopulateURLResponse(
-      url, *network::mojom::URLResponseHeadPtr(response_head),
-      &navigation_params->response, response_head.ssl_info.has_value(),
-      request_id);
+      url, *response_head, &navigation_params->response,
+      response_head->ssl_info.has_value(), request_id);
   if (url.SchemeIs(url::kDataScheme))
     navigation_params->response.SetHttpStatusCode(200);
 
   if (url_loader_client_endpoints) {
     navigation_params->body_loader.reset(new NavigationBodyLoader(
-        response_head, std::move(response_body),
+        std::move(response_head), std::move(response_body),
         std::move(url_loader_client_endpoints), task_runner, render_frame_id,
         std::move(resource_load_info)));
   }
 }
 
 NavigationBodyLoader::NavigationBodyLoader(
-    const network::ResourceResponseHead& response_head,
+    network::mojom::URLResponseHeadPtr response_head,
     mojo::ScopedDataPipeConsumerHandle response_body,
     network::mojom::URLLoaderClientEndpointsPtr endpoints,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner,
     int render_frame_id,
     mojom::ResourceLoadInfoPtr resource_load_info)
     : render_frame_id_(render_frame_id),
-      response_head_(response_head),
+      response_head_(std::move(response_head)),
       response_body_(std::move(response_body)),
       endpoints_(std::move(endpoints)),
       task_runner_(std::move(task_runner)),
@@ -182,24 +183,29 @@
                resource_load_info_->url.possibly_invalid_spec());
   client_ = client;
 
+  base::Time response_head_response_time = response_head_->response_time;
   NotifyResourceResponseReceived(render_frame_id_, resource_load_info_.get(),
-                                 response_head_, content::PREVIEWS_OFF);
+                                 std::move(response_head_),
+                                 content::PREVIEWS_OFF);
 
   if (use_isolated_code_cache) {
     code_cache_loader_ = std::make_unique<CodeCacheLoaderImpl>();
     code_cache_loader_->FetchFromCodeCache(
         blink::mojom::CodeCacheType::kJavascript, resource_load_info_->url,
         base::BindOnce(&NavigationBodyLoader::CodeCacheReceived,
-                       weak_factory_.GetWeakPtr()));
+                       weak_factory_.GetWeakPtr(),
+                       response_head_response_time));
     return;
   }
 
   BindURLLoaderAndStartLoadingResponseBodyIfPossible();
 }
 
-void NavigationBodyLoader::CodeCacheReceived(base::Time response_time,
-                                             mojo_base::BigBuffer data) {
-  if (response_head_.response_time == response_time && client_) {
+void NavigationBodyLoader::CodeCacheReceived(
+    base::Time response_head_response_time,
+    base::Time response_time,
+    mojo_base::BigBuffer data) {
+  if (response_head_response_time == response_time && client_) {
     base::WeakPtr<NavigationBodyLoader> weak_self = weak_factory_.GetWeakPtr();
     client_->BodyCodeCacheReceived(std::move(data));
     if (!weak_self)
diff --git a/content/renderer/loader/navigation_body_loader.h b/content/renderer/loader/navigation_body_loader.h
index 50a6c83..8780320 100644
--- a/content/renderer/loader/navigation_body_loader.h
+++ b/content/renderer/loader/navigation_body_loader.h
@@ -22,6 +22,7 @@
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/public/cpp/system/simple_watcher.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom-forward.h"
 #include "third_party/blink/public/platform/web_navigation_body_loader.h"
 
 namespace blink {
@@ -53,7 +54,7 @@
       mojom::CommonNavigationParamsPtr common_params,
       mojom::CommitNavigationParamsPtr commit_params,
       int request_id,
-      const network::ResourceResponseHead& response_head,
+      network::mojom::URLResponseHeadPtr response_head,
       mojo::ScopedDataPipeConsumerHandle response_body,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
@@ -93,7 +94,7 @@
   static constexpr uint32_t kMaxNumConsumedBytesInTask = 64 * 1024;
 
   NavigationBodyLoader(
-      const network::ResourceResponseHead& response_head,
+      network::mojom::URLResponseHeadPtr response_head,
       mojo::ScopedDataPipeConsumerHandle response_body,
       network::mojom::URLLoaderClientEndpointsPtr url_loader_client_endpoints,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner,
@@ -120,7 +121,9 @@
       mojo::ScopedDataPipeConsumerHandle handle) override;
   void OnComplete(const network::URLLoaderCompletionStatus& status) override;
 
-  void CodeCacheReceived(base::Time response_time, mojo_base::BigBuffer data);
+  void CodeCacheReceived(base::Time response_head_response_time,
+                         base::Time response_time,
+                         mojo_base::BigBuffer data);
   void BindURLLoaderAndContinue();
   void OnConnectionClosed();
   void OnReadable(MojoResult unused);
@@ -132,7 +135,7 @@
 
   // Navigation parameters.
   const int render_frame_id_;
-  const network::ResourceResponseHead response_head_;
+  network::mojom::URLResponseHeadPtr response_head_;
   mojo::ScopedDataPipeConsumerHandle response_body_;
   network::mojom::URLLoaderClientEndpointsPtr endpoints_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
diff --git a/content/renderer/loader/navigation_body_loader_unittest.cc b/content/renderer/loader/navigation_body_loader_unittest.cc
index 9a1d90c..c2a05811 100644
--- a/content/renderer/loader/navigation_body_loader_unittest.cc
+++ b/content/renderer/loader/navigation_body_loader_unittest.cc
@@ -49,7 +49,7 @@
     auto commit_params = CreateCommitNavigationParams();
     NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
         std::move(common_params), std::move(commit_params), 1 /* request_id */,
-        network::ResourceResponseHead(),
+        network::mojom::URLResponseHead::New(),
         mojo::ScopedDataPipeConsumerHandle() /* response_body */,
         std::move(endpoints),
         blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
@@ -294,8 +294,8 @@
 // Tests that FillNavigationParamsResponseAndBodyLoader populates security
 // details on the response when they are present.
 TEST_F(NavigationBodyLoaderTest, FillResponseWithSecurityDetails) {
-  network::ResourceResponseHead response;
-  response.ssl_info = net::SSLInfo();
+  auto response = network::mojom::URLResponseHead::New();
+  response->ssl_info = net::SSLInfo();
   net::CertificateList certs;
   ASSERT_TRUE(net::LoadCertificateFiles(
       {"subjectAltName_sanity_check.pem", "root_ca_cert.pem"}, &certs));
@@ -306,10 +306,10 @@
   base::StringPiece cert1_der =
       net::x509_util::CryptoBufferAsStringPiece(certs[1]->cert_buffer());
 
-  response.ssl_info->cert =
+  response->ssl_info->cert =
       net::X509Certificate::CreateFromDERCertChain({cert0_der, cert1_der});
   net::SSLConnectionStatusSetVersion(net::SSL_CONNECTION_VERSION_TLS1_2,
-                                     &response.ssl_info->connection_status);
+                                     &response->ssl_info->connection_status);
 
   auto common_params = CreateCommonNavigationParams();
   common_params->url = GURL("https://example.test");
@@ -319,7 +319,8 @@
   auto endpoints = network::mojom::URLLoaderClientEndpoints::New();
   NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
       std::move(common_params), std::move(commit_params), 1 /* request_id */,
-      response, mojo::ScopedDataPipeConsumerHandle() /* response_body */,
+      std::move(response),
+      mojo::ScopedDataPipeConsumerHandle() /* response_body */,
       std::move(endpoints),
       blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
       2 /* render_frame_id */, true /* is_main_frame */, &navigation_params);
diff --git a/content/renderer/loader/resource_dispatcher.cc b/content/renderer/loader/resource_dispatcher.cc
index 199b2a40..7def575 100644
--- a/content/renderer/loader/resource_dispatcher.cc
+++ b/content/renderer/loader/resource_dispatcher.cc
@@ -44,6 +44,7 @@
 #include "services/network/public/cpp/resource_request.h"
 #include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/loader/mime_sniffing_throttle.h"
 
 namespace content {
@@ -141,38 +142,34 @@
 
 void ResourceDispatcher::OnReceivedResponse(
     int request_id,
-    const network::ResourceResponseHead& response_head) {
+    network::mojom::URLResponseHeadPtr response_head) {
   TRACE_EVENT0("loading", "ResourceDispatcher::OnReceivedResponse");
   PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
   if (!request_info)
     return;
   DCHECK(!request_info->navigation_response_override);
   request_info->local_response_start = base::TimeTicks::Now();
-  request_info->remote_request_start = response_head.load_timing.request_start;
+  request_info->remote_request_start = response_head->load_timing.request_start;
   // Now that response_start has been set, we can properly set the TimeTicks in
-  // the ResourceResponseHead.
-  network::ResourceResponseHead renderer_response_head;
-  ToResourceResponseHead(*request_info, response_head, &renderer_response_head);
-  request_info->load_timing_info = renderer_response_head.load_timing;
+  // the URLResponseHead.
+  ToLocalURLResponseHead(*request_info, *response_head);
+  request_info->load_timing_info = response_head->load_timing;
   if (delegate_) {
     std::unique_ptr<RequestPeer> new_peer = delegate_->OnReceivedResponse(
-        std::move(request_info->peer), response_head.mime_type,
+        std::move(request_info->peer), response_head->mime_type,
         request_info->url);
     DCHECK(new_peer);
     request_info->peer = std::move(new_peer);
   }
 
-  // Unfortunately, calling OnReceivedResponse on the peer can delete
-  // pending request and/or passed |response_head| reference.
-  // Make a copy of |response_head| for later use.
-  network::ResourceResponseHead response_head_copy = renderer_response_head;
-  request_info->peer->OnReceivedResponse(renderer_response_head);
+  request_info->peer->OnReceivedResponse(
+      network::ResourceResponseHead(response_head));
   if (!GetPendingRequestInfo(request_id))
     return;
 
   NotifyResourceResponseReceived(
       request_info->render_frame_id, request_info->resource_load_info.get(),
-      response_head_copy, request_info->previews_state);
+      std::move(response_head), request_info->previews_state);
 }
 
 void ResourceDispatcher::OnReceivedCachedMetadata(int request_id,
@@ -199,7 +196,7 @@
 void ResourceDispatcher::OnReceivedRedirect(
     int request_id,
     const net::RedirectInfo& redirect_info,
-    const network::ResourceResponseHead& response_head,
+    network::mojom::URLResponseHeadPtr response_head,
     scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
   TRACE_EVENT0("loading", "ResourceDispatcher::OnReceivedRedirect");
   PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
@@ -211,22 +208,22 @@
     // URL. Handle this in a posted task, as we don't have the loader
     // pointer yet.
     base::ThreadTaskRunnerHandle::Get()->PostTask(
-        FROM_HERE, base::BindOnce(&ResourceDispatcher::OnReceivedRedirect,
-                                  weak_factory_.GetWeakPtr(), request_id,
-                                  redirect_info, response_head, task_runner));
+        FROM_HERE,
+        base::BindOnce(&ResourceDispatcher::OnReceivedRedirect,
+                       weak_factory_.GetWeakPtr(), request_id, redirect_info,
+                       std::move(response_head), task_runner));
     return;
   }
 
   request_info->local_response_start = base::TimeTicks::Now();
-  request_info->remote_request_start = response_head.load_timing.request_start;
+  request_info->remote_request_start = response_head->load_timing.request_start;
   request_info->redirect_requires_loader_restart =
       RedirectRequiresLoaderRestart(request_info->response_url,
                                     redirect_info.new_url);
 
-  network::ResourceResponseHead renderer_response_head;
-  ToResourceResponseHead(*request_info, response_head, &renderer_response_head);
-  if (request_info->peer->OnReceivedRedirect(redirect_info,
-                                             renderer_response_head)) {
+  ToLocalURLResponseHead(*request_info, *response_head);
+  if (request_info->peer->OnReceivedRedirect(
+          redirect_info, network::ResourceResponseHead(response_head))) {
     // Double-check if the request is still around. The call above could
     // potentially remove it.
     request_info = GetPendingRequestInfo(request_id);
@@ -236,7 +233,7 @@
     request_info->has_pending_redirect = true;
     NotifyResourceRedirectReceived(request_info->render_frame_id,
                                    request_info->resource_load_info.get(),
-                                   redirect_info, response_head);
+                                   redirect_info, std::move(response_head));
     if (!request_info->is_deferred)
       FollowPendingRedirect(request_info);
   } else {
@@ -571,26 +568,24 @@
   return request_id;
 }
 
-void ResourceDispatcher::ToResourceResponseHead(
+void ResourceDispatcher::ToLocalURLResponseHead(
     const PendingRequestInfo& request_info,
-    const network::ResourceResponseHead& browser_info,
-    network::ResourceResponseHead* renderer_info) const {
-  *renderer_info = browser_info;
+    network::mojom::URLResponseHead& response_head) const {
   if (base::TimeTicks::IsConsistentAcrossProcesses() ||
       request_info.local_request_start.is_null() ||
       request_info.local_response_start.is_null() ||
-      browser_info.request_start.is_null() ||
-      browser_info.response_start.is_null() ||
-      browser_info.load_timing.request_start.is_null()) {
+      response_head.request_start.is_null() ||
+      response_head.response_start.is_null() ||
+      response_head.load_timing.request_start.is_null()) {
     return;
   }
   InterProcessTimeTicksConverter converter(
       LocalTimeTicks::FromTimeTicks(request_info.local_request_start),
       LocalTimeTicks::FromTimeTicks(request_info.local_response_start),
-      RemoteTimeTicks::FromTimeTicks(browser_info.request_start),
-      RemoteTimeTicks::FromTimeTicks(browser_info.response_start));
+      RemoteTimeTicks::FromTimeTicks(response_head.request_start),
+      RemoteTimeTicks::FromTimeTicks(response_head.response_start));
 
-  net::LoadTimingInfo* load_timing = &renderer_info->load_timing;
+  net::LoadTimingInfo* load_timing = &response_head.load_timing;
   RemoteToLocalTimeTicks(converter, &load_timing->request_start);
   RemoteToLocalTimeTicks(converter, &load_timing->proxy_resolve_start);
   RemoteToLocalTimeTicks(converter, &load_timing->proxy_resolve_end);
@@ -606,8 +601,8 @@
   RemoteToLocalTimeTicks(converter, &load_timing->receive_headers_end);
   RemoteToLocalTimeTicks(converter, &load_timing->push_start);
   RemoteToLocalTimeTicks(converter, &load_timing->push_end);
-  RemoteToLocalTimeTicks(converter, &renderer_info->service_worker_start_time);
-  RemoteToLocalTimeTicks(converter, &renderer_info->service_worker_ready_time);
+  RemoteToLocalTimeTicks(converter, &response_head.service_worker_start_time);
+  RemoteToLocalTimeTicks(converter, &response_head.service_worker_ready_time);
 }
 
 // TODO(dgozman): this is not used for navigation anymore, only for worker
@@ -626,7 +621,7 @@
   request_info->should_follow_redirect = false;
 
   URLLoaderClientImpl* client_ptr = request_info->url_loader_client.get();
-  // During navigations, the ResourceResponse has already been received on the
+  // During navigations, the Response has already been received on the
   // browser side, and has been passed down to the renderer. Replay the
   // redirects that happened during navigation.
   DCHECK_EQ(response_override->redirect_responses.size(),
diff --git a/content/renderer/loader/resource_dispatcher.h b/content/renderer/loader/resource_dispatcher.h
index 38e654e..c5908b2 100644
--- a/content/renderer/loader/resource_dispatcher.h
+++ b/content/renderer/loader/resource_dispatcher.h
@@ -31,6 +31,7 @@
 #include "net/traffic_annotation/network_traffic_annotation.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom-forward.h"
 #include "third_party/blink/public/common/loader/url_loader_throttle.h"
 #include "third_party/blink/public/mojom/blob/blob_registry.mojom.h"
 #include "third_party/blink/public/platform/web_url_request.h"
@@ -46,7 +47,6 @@
 
 namespace network {
 struct ResourceRequest;
-struct ResourceResponseHead;
 struct URLLoaderCompletionStatus;
 namespace mojom {
 class URLLoaderFactory;
@@ -221,22 +221,21 @@
 
   // Message response handlers, called by the message handler for this process.
   void OnUploadProgress(int request_id, int64_t position, int64_t size);
-  void OnReceivedResponse(int request_id, const network::ResourceResponseHead&);
+  void OnReceivedResponse(int request_id, network::mojom::URLResponseHeadPtr);
   void OnReceivedCachedMetadata(int request_id, mojo_base::BigBuffer data);
   void OnReceivedRedirect(
       int request_id,
       const net::RedirectInfo& redirect_info,
-      const network::ResourceResponseHead& response_head,
+      network::mojom::URLResponseHeadPtr response_head,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner);
   void OnStartLoadingResponseBody(int request_id,
                                   mojo::ScopedDataPipeConsumerHandle body);
   void OnRequestComplete(int request_id,
                          const network::URLLoaderCompletionStatus& status);
 
-  void ToResourceResponseHead(
+  void ToLocalURLResponseHead(
       const PendingRequestInfo& request_info,
-      const network::ResourceResponseHead& browser_info,
-      network::ResourceResponseHead* renderer_info) const;
+      network::mojom::URLResponseHead& response_head) const;
 
   void ContinueForNavigation(int request_id);
 
diff --git a/content/renderer/loader/resource_load_stats.cc b/content/renderer/loader/resource_load_stats.cc
index 774cd4c..6323d50 100644
--- a/content/renderer/loader/resource_load_stats.cc
+++ b/content/renderer/loader/resource_load_stats.cc
@@ -41,21 +41,20 @@
 }
 #endif
 
-void ResourceResponseReceived(
-    int render_frame_id,
-    int request_id,
-    const GURL& response_url,
-    const network::ResourceResponseHead& response_head,
-    content::ResourceType resource_type,
-    PreviewsState previews_state) {
+void ResourceResponseReceived(int render_frame_id,
+                              int request_id,
+                              const GURL& response_url,
+                              network::mojom::URLResponseHeadPtr response_head,
+                              content::ResourceType resource_type,
+                              PreviewsState previews_state) {
   RenderFrameImpl* frame = RenderFrameImpl::FromRoutingID(render_frame_id);
   if (!frame)
     return;
   if (!IsResourceTypeFrame(resource_type)) {
     frame->GetFrameHost()->SubresourceResponseStarted(
-        response_url, response_head.cert_status);
+        response_url, response_head->cert_status);
   }
-  frame->DidStartResponse(response_url, request_id, response_head,
+  frame->DidStartResponse(response_url, request_id, std::move(response_head),
                           resource_type, previews_state);
 }
 
@@ -124,7 +123,7 @@
     int render_frame_id,
     mojom::ResourceLoadInfo* resource_load_info,
     const net::RedirectInfo& redirect_info,
-    const network::ResourceResponseHead& redirect_response) {
+    network::mojom::URLResponseHeadPtr redirect_response) {
   resource_load_info->url = redirect_info.new_url;
   resource_load_info->method = redirect_info.new_method;
   resource_load_info->referrer = GURL(redirect_info.new_referrer);
@@ -132,11 +131,11 @@
   net_redirect_info->url = redirect_info.new_url;
   net_redirect_info->network_info = mojom::CommonNetworkInfo::New();
   net_redirect_info->network_info->network_accessed =
-      redirect_response.network_accessed;
+      redirect_response->network_accessed;
   net_redirect_info->network_info->always_access_network =
-      AlwaysAccessNetwork(redirect_response.headers);
+      AlwaysAccessNetwork(redirect_response->headers);
   net_redirect_info->network_info->remote_endpoint =
-      redirect_response.remote_endpoint;
+      redirect_response->remote_endpoint;
   resource_load_info->redirect_info_chain.push_back(
       std::move(net_redirect_info));
 }
@@ -144,48 +143,49 @@
 void NotifyResourceResponseReceived(
     int render_frame_id,
     mojom::ResourceLoadInfo* resource_load_info,
-    const network::ResourceResponseHead& response_head,
+    network::mojom::URLResponseHeadPtr response_head,
     PreviewsState previews_state) {
-  if (response_head.network_accessed) {
+  if (response_head->network_accessed) {
     if (resource_load_info->resource_type == ResourceType::kMainFrame) {
       UMA_HISTOGRAM_ENUMERATION("Net.ConnectionInfo.MainFrame",
-                                response_head.connection_info,
+                                response_head->connection_info,
                                 net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS);
     } else {
       UMA_HISTOGRAM_ENUMERATION("Net.ConnectionInfo.SubResource",
-                                response_head.connection_info,
+                                response_head->connection_info,
                                 net::HttpResponseInfo::NUM_OF_CONNECTION_INFOS);
     }
   }
 
-  resource_load_info->mime_type = response_head.mime_type;
-  resource_load_info->load_timing_info = response_head.load_timing;
+  resource_load_info->mime_type = response_head->mime_type;
+  resource_load_info->load_timing_info = response_head->load_timing;
   resource_load_info->network_info->network_accessed =
-      response_head.network_accessed;
+      response_head->network_accessed;
   resource_load_info->network_info->always_access_network =
-      AlwaysAccessNetwork(response_head.headers);
+      AlwaysAccessNetwork(response_head->headers);
   resource_load_info->network_info->remote_endpoint =
-      response_head.remote_endpoint;
+      response_head->remote_endpoint;
 
   auto task_runner = RenderThreadImpl::DeprecatedGetMainTaskRunner();
   if (!task_runner)
     return;
   if (task_runner->BelongsToCurrentThread()) {
     ResourceResponseReceived(render_frame_id, resource_load_info->request_id,
-                             resource_load_info->url, response_head,
+                             resource_load_info->url, std::move(response_head),
                              resource_load_info->resource_type, previews_state);
     return;
   }
 
-  // Make a deep copy of ResourceResponseHead before passing it cross-thread.
-  auto resource_response = base::MakeRefCounted<network::ResourceResponse>();
-  resource_response->head = response_head;
-  auto deep_copied_response = resource_response->DeepCopy();
+  // Make a deep copy of URLResponseHead before passing it cross-thread.
+  if (response_head->headers) {
+    response_head->headers =
+        new net::HttpResponseHeaders(response_head->headers->raw_headers());
+  }
   task_runner->PostTask(
       FROM_HERE,
       base::BindOnce(ResourceResponseReceived, render_frame_id,
                      resource_load_info->request_id, resource_load_info->url,
-                     deep_copied_response->head,
+                     std::move(response_head),
                      resource_load_info->resource_type, previews_state));
 }
 
diff --git a/content/renderer/loader/resource_load_stats.h b/content/renderer/loader/resource_load_stats.h
index df0b175..5c99d14 100644
--- a/content/renderer/loader/resource_load_stats.h
+++ b/content/renderer/loader/resource_load_stats.h
@@ -9,6 +9,7 @@
 #include "content/public/common/previews_state.h"
 #include "content/public/common/resource_load_info.mojom.h"
 #include "content/public/common/resource_type.h"
+#include "services/network/public/mojom/url_response_head.mojom-forward.h"
 
 class GURL;
 
@@ -17,7 +18,6 @@
 }  // namespace net
 
 namespace network {
-struct ResourceResponseHead;
 struct URLLoaderCompletionStatus;
 }  // namespace network
 
@@ -47,12 +47,12 @@
     int render_frame_id,
     mojom::ResourceLoadInfo* resource_load_info,
     const net::RedirectInfo& redirect_info,
-    const network::ResourceResponseHead& redirect_response);
+    network::mojom::URLResponseHeadPtr redirect_response);
 
 void NotifyResourceResponseReceived(
     int render_frame_id,
     mojom::ResourceLoadInfo* resource_load_info,
-    const network::ResourceResponseHead& response_head,
+    network::mojom::URLResponseHeadPtr response_head,
     PreviewsState previews_state);
 
 void NotifyResourceTransferSizeUpdated(
diff --git a/content/renderer/loader/url_loader_client_impl.cc b/content/renderer/loader/url_loader_client_impl.cc
index 3bf05c3..70d6e06 100644
--- a/content/renderer/loader/url_loader_client_impl.cc
+++ b/content/renderer/loader/url_loader_client_impl.cc
@@ -15,7 +15,7 @@
 #include "content/renderer/loader/resource_dispatcher.h"
 #include "net/url_request/redirect_info.h"
 #include "services/network/public/cpp/features.h"
-#include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "third_party/blink/public/common/features.h"
 
 namespace content {
@@ -46,16 +46,16 @@
     : public DeferredMessage {
  public:
   explicit DeferredOnReceiveResponse(
-      const network::ResourceResponseHead& response_head)
-      : response_head_(response_head) {}
+      network::mojom::URLResponseHeadPtr response_head)
+      : response_head_(std::move(response_head)) {}
 
   void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
-    dispatcher->OnReceivedResponse(request_id, response_head_);
+    dispatcher->OnReceivedResponse(request_id, std::move(response_head_));
   }
   bool IsCompletionMessage() const override { return false; }
 
  private:
-  const network::ResourceResponseHead response_head_;
+  network::mojom::URLResponseHeadPtr response_head_;
 };
 
 class URLLoaderClientImpl::DeferredOnReceiveRedirect final
@@ -63,21 +63,21 @@
  public:
   DeferredOnReceiveRedirect(
       const net::RedirectInfo& redirect_info,
-      const network::ResourceResponseHead& response_head,
+      network::mojom::URLResponseHeadPtr response_head,
       scoped_refptr<base::SingleThreadTaskRunner> task_runner)
       : redirect_info_(redirect_info),
-        response_head_(response_head),
+        response_head_(std::move(response_head)),
         task_runner_(std::move(task_runner)) {}
 
   void HandleMessage(ResourceDispatcher* dispatcher, int request_id) override {
-    dispatcher->OnReceivedRedirect(request_id, redirect_info_, response_head_,
-                                   task_runner_);
+    dispatcher->OnReceivedRedirect(request_id, redirect_info_,
+                                   std::move(response_head_), task_runner_);
   }
   bool IsCompletionMessage() const override { return false; }
 
  private:
   const net::RedirectInfo redirect_info_;
-  const network::ResourceResponseHead response_head_;
+  network::mojom::URLResponseHeadPtr response_head_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 };
 
@@ -241,7 +241,7 @@
   has_received_response_head_ = true;
   if (NeedsStoringMessage()) {
     StoreAndDispatch(
-        std::make_unique<DeferredOnReceiveResponse>(response_head));
+        std::make_unique<DeferredOnReceiveResponse>(std::move(response_head)));
   } else {
     resource_dispatcher_->OnReceivedResponse(request_id_,
                                              std::move(response_head));
@@ -261,7 +261,7 @@
   last_loaded_url_ = redirect_info.new_url;
   if (NeedsStoringMessage()) {
     StoreAndDispatch(std::make_unique<DeferredOnReceiveRedirect>(
-        redirect_info, response_head, task_runner_));
+        redirect_info, std::move(response_head), task_runner_));
   } else {
     resource_dispatcher_->OnReceivedRedirect(
         request_id_, redirect_info, std::move(response_head), task_runner_);
diff --git a/content/renderer/loader/url_loader_client_impl_unittest.cc b/content/renderer/loader/url_loader_client_impl_unittest.cc
index 78a33600..7df006b 100644
--- a/content/renderer/loader/url_loader_client_impl_unittest.cc
+++ b/content/renderer/loader/url_loader_client_impl_unittest.cc
@@ -14,9 +14,9 @@
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
 #include "net/url_request/redirect_info.h"
-#include "services/network/public/cpp/resource_response.h"
 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
 #include "services/network/public/mojom/url_loader_factory.mojom.h"
+#include "services/network/public/mojom/url_response_head.mojom.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
@@ -108,9 +108,7 @@
 };
 
 TEST_F(URLLoaderClientImplTest, OnReceiveResponse) {
-  network::ResourceResponseHead response_head;
-
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
 
   EXPECT_FALSE(request_peer_context_.received_response);
   base::RunLoop().RunUntilIdle();
@@ -118,9 +116,7 @@
 }
 
 TEST_F(URLLoaderClientImplTest, ResponseBody) {
-  network::ResourceResponseHead response_head;
-
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
 
   EXPECT_FALSE(request_peer_context_.received_response);
   base::RunLoop().RunUntilIdle();
@@ -140,10 +136,10 @@
 }
 
 TEST_F(URLLoaderClientImplTest, OnReceiveRedirect) {
-  network::ResourceResponseHead response_head;
   net::RedirectInfo redirect_info;
 
-  url_loader_client_->OnReceiveRedirect(redirect_info, response_head);
+  url_loader_client_->OnReceiveRedirect(redirect_info,
+                                        network::mojom::URLResponseHead::New());
 
   EXPECT_EQ(0, request_peer_context_.seen_redirects);
   base::RunLoop().RunUntilIdle();
@@ -151,12 +147,11 @@
 }
 
 TEST_F(URLLoaderClientImplTest, OnReceiveCachedMetadata) {
-  network::ResourceResponseHead response_head;
   std::vector<uint8_t> data;
   data.push_back('a');
   mojo_base::BigBuffer metadata(data);
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   url_loader_client_->OnReceiveCachedMetadata(std::move(metadata));
 
   EXPECT_FALSE(request_peer_context_.received_response);
@@ -168,9 +163,7 @@
 }
 
 TEST_F(URLLoaderClientImplTest, OnTransferSizeUpdated) {
-  network::ResourceResponseHead response_head;
-
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   url_loader_client_->OnTransferSizeUpdated(4);
   url_loader_client_->OnTransferSizeUpdated(4);
 
@@ -182,10 +175,9 @@
 }
 
 TEST_F(URLLoaderClientImplTest, OnCompleteWithResponseBody) {
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe(DataPipeOptions());
   url_loader_client_->OnStartLoadingResponseBody(
       std::move(data_pipe.consumer_handle));
@@ -216,10 +208,9 @@
 // bytes arrives after the completion message. URLLoaderClientImpl should
 // restore the order.
 TEST_F(URLLoaderClientImplTest, OnCompleteShouldBeTheLastMessage) {
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe(DataPipeOptions());
   url_loader_client_->OnStartLoadingResponseBody(
       std::move(data_pipe.consumer_handle));
@@ -242,10 +233,9 @@
 TEST_F(URLLoaderClientImplTest, CancelOnReceiveResponse) {
   request_peer_context_.cancel_on_receive_response = true;
 
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe(DataPipeOptions());
   url_loader_client_->OnStartLoadingResponseBody(
       std::move(data_pipe.consumer_handle));
@@ -262,10 +252,9 @@
 }
 
 TEST_F(URLLoaderClientImplTest, Defer) {
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe;
   data_pipe.producer_handle.reset();  // Empty body.
   url_loader_client_->OnStartLoadingResponseBody(
@@ -291,10 +280,9 @@
 }
 
 TEST_F(URLLoaderClientImplTest, DeferWithResponseBody) {
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe(DataPipeOptions());
   uint32_t size = 5;
   MojoResult result = data_pipe.producer_handle->WriteData(
@@ -332,10 +320,9 @@
 // As "transfer size update" message is handled specially in the implementation,
 // we have a separate test.
 TEST_F(URLLoaderClientImplTest, DeferWithTransferSizeUpdated) {
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe(DataPipeOptions());
   uint32_t size = 5;
   MojoResult result = data_pipe.producer_handle->WriteData(
@@ -379,11 +366,11 @@
   request_peer_context_.defer_on_redirect = true;
 
   net::RedirectInfo redirect_info;
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveRedirect(redirect_info, response_head);
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveRedirect(redirect_info,
+                                        network::mojom::URLResponseHead::New());
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe(DataPipeOptions());
   uint32_t size = 5;
   MojoResult result = data_pipe.producer_handle->WriteData(
@@ -441,10 +428,9 @@
        SetDeferredDuringFlushingDeferredMessageOnTransferSizeUpdated) {
   request_peer_context_.defer_on_transfer_size_updated = true;
 
-  network::ResourceResponseHead response_head;
   network::URLLoaderCompletionStatus status;
 
-  url_loader_client_->OnReceiveResponse(response_head);
+  url_loader_client_->OnReceiveResponse(network::mojom::URLResponseHead::New());
   mojo::DataPipe data_pipe;
   data_pipe.producer_handle.reset();  // Empty body.
   url_loader_client_->OnStartLoadingResponseBody(
diff --git a/content/renderer/loader/web_url_loader_impl.cc b/content/renderer/loader/web_url_loader_impl.cc
index 8c6b4675..1929454 100644
--- a/content/renderer/loader/web_url_loader_impl.cc
+++ b/content/renderer/loader/web_url_loader_impl.cc
@@ -1147,6 +1147,13 @@
   }
 }
 
+void WebURLLoaderImpl::PopulateURLResponse(
+    const WebURL& url,
+    const network::ResourceResponseHead& head,
+    WebURLResponse* response,
+    bool report_security_info,
+    int request_id) {}
+
 // static
 WebURLError WebURLLoaderImpl::PopulateURLError(
     const network::URLLoaderCompletionStatus& status,
diff --git a/content/renderer/loader/web_url_loader_impl.h b/content/renderer/loader/web_url_loader_impl.h
index 27d56c26..837447e 100644
--- a/content/renderer/loader/web_url_loader_impl.h
+++ b/content/renderer/loader/web_url_loader_impl.h
@@ -20,6 +20,10 @@
 #include "third_party/blink/public/platform/web_url_loader.h"
 #include "third_party/blink/public/platform/web_url_loader_factory.h"
 
+namespace network {
+struct ResourceResponseHead;
+}
+
 namespace content {
 
 class ResourceDispatcher;
@@ -57,6 +61,11 @@
   ~WebURLLoaderImpl() override;
 
   static void PopulateURLResponse(const blink::WebURL& url,
+                                  const network::ResourceResponseHead& head,
+                                  blink::WebURLResponse* response,
+                                  bool report_security_info,
+                                  int request_id);
+  static void PopulateURLResponse(const blink::WebURL& url,
                                   const network::mojom::URLResponseHead& head,
                                   blink::WebURLResponse* response,
                                   bool report_security_info,
diff --git a/content/renderer/pepper/ppb_video_decoder_impl.cc b/content/renderer/pepper/ppb_video_decoder_impl.cc
index b3892053..735637b 100644
--- a/content/renderer/pepper/ppb_video_decoder_impl.cc
+++ b/content/renderer/pepper/ppb_video_decoder_impl.cc
@@ -166,7 +166,7 @@
   // but only after PPB_Buffer_Impl is updated to deal with that.
   media::BitstreamBuffer decode_buffer(bitstream_buffer->id,
                                        buffer->shared_memory().Duplicate(),
-                                       buffer->shared_memory().GetSize());
+                                       bitstream_buffer->size);
   if (!SetBitstreamBufferCallback(bitstream_buffer->id, callback))
     return PP_ERROR_BADARGUMENT;
 
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 2ad31d8..aa5f214 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -1628,21 +1628,21 @@
     // create the RenderWidget if the RenderFrame owned it instead of having the
     // RenderWidget live for eternity on the RenderView (after setting up the
     // WebFrameWidget since that would be part of creating the RenderWidget).
-    render_view->ReviveUndeadMainFrameRenderWidget(
-        widget_params->visual_properties.screen_info);
-
-    // The RenderViewImpl and its RenderWidget already exist by the time we
-    // get here (we get them from the RenderFrameProxy).
-    // TODO(crbug.com/419087): We probably want to create the RenderWidget
-    // here though (when we make the WebFrameWidget?).
-    RenderWidget* render_widget = render_view->GetWidget();
+    //
+    // This is equivalent to creating a new RenderWidget if it wasn't undead.
+    RenderWidget* render_widget =
+        render_view->ReviveUndeadMainFrameRenderWidget();
+    DCHECK(!render_widget->GetWebWidget());
 
     // Non-owning pointer that is self-referencing and destroyed by calling
     // Close(). The RenderViewImpl has a RenderWidget already, but not a
     // WebFrameWidget, which is now attached here.
     auto* web_frame_widget = blink::WebFrameWidget::CreateForMainFrame(
         render_view->GetWidget(), web_frame);
-    render_view->AttachWebFrameWidget(web_frame_widget);
+    // This is equivalent to calling InitForMainFrame() on a new RenderWidget
+    // if it wasn't undead.
+    render_widget->InitForRevivedMainFrame(
+        web_frame_widget, widget_params->visual_properties.screen_info);
 
     // Note that we do *not* call WebViewImpl's DidAttachLocalMainFrame() here
     // yet because this frame is provisional and not attached to the Page yet.
@@ -3468,7 +3468,7 @@
   } else {
     NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
         std::move(common_params), std::move(commit_params), request_id,
-        response_head, std::move(response_body),
+        response_head.Clone(), std::move(response_body),
         std::move(url_loader_client_endpoints),
         GetTaskRunner(blink::TaskType::kInternalLoading), GetRoutingID(),
         !frame_->Parent(), navigation_params.get());
@@ -4413,12 +4413,11 @@
   if (is_main_frame_) {
     DCHECK(!owned_render_widget_);
     // TODO(crbug.com/419087): The RenderWidget for the main frame can't be
-    // closed/destroyed since there is no way to recreate it without also
-    // fixing the lifetimes of the related browser side objects. To simulate
-    // this "swap out", the pointer is moved off to the side until it is
-    // swapped back in. The renderer is then told that the WebFrameWidget is
-    // dropped which should remove all reference to this object.
-    render_view_->DetachWebFrameWidget();
+    // closed/destroyed here, since there is no way to recreate it without also
+    // fixing the lifetimes of the related browser side objects. Closing is
+    // delegated to the RenderViewImpl which will stash the RenderWidget away
+    // as undead if needed.
+    render_view_->CloseMainFrameRenderWidget();
   } else if (render_widget_) {
     DCHECK(owned_render_widget_);
     // This closes/deletes the RenderWidget if this frame was a local root.
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index bc69d3b..db4ecc92 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1536,46 +1536,6 @@
       render_widget->input_handler().handling_input_event());
 }
 
-void RenderViewImpl::AttachWebFrameWidget(blink::WebFrameWidget* frame_widget) {
-  // The previous WebFrameWidget must already be detached by
-  // DetachWebFrameWidget().
-  DCHECK(!render_widget_->GetWebWidget());
-  render_widget_->SetWebWidgetInternal(frame_widget);
-
-  // Note that when a main frame RenderWidget is created with a main frame, then
-  // this method is not used, so other initialization should not be done here.
-}
-
-void RenderViewImpl::DetachWebFrameWidget() {
-  // There is a WebFrameWidget previously attached by AttachWebFrameWidget().
-  DCHECK(render_widget_->GetWebWidget());
-
-  if (destroying_) {
-    // We are inside RenderViewImpl::Destroy() and the main frame is being
-    // detached as part of shutdown. So we can destroy the RenderWidget.
-
-    // We pass ownership of |render_widget_| to itself. Grab a raw pointer to
-    // call the Close() method on so we don't have to be a C++ expert to know
-    // whether we will end up with a nullptr where we didn't intend due to order
-    // of execution.
-    RenderWidget* closing_widget = render_widget_.get();
-    closing_widget->CloseForFrame(std::move(render_widget_));
-  } else {
-    // We are not inside RenderViewImpl::Destroy(), the main frame is being
-    // detached and replaced with a remote frame proxy. We can't close the
-    // RenderWidget, and it is marked undead instead, but we do need to close
-    // the WebFrameWidget and remove it from the RenderWidget.
-    render_widget_->SetIsUndead();
-    // The WebWidget needs to be closed even though the RenderWidget won't be
-    // closed here (since it is marked undead instead).
-    render_widget_->GetWebWidget()->Close();
-    // This just clears the webwidget_internal_ member from RenderWidget.
-    render_widget_->SetWebWidgetInternal(nullptr);
-
-    undead_render_widget_ = std::move(render_widget_);
-  }
-}
-
 bool RenderViewImpl::SetZoomLevel(double zoom_level) {
   if (zoom_level == page_zoom_level_)
     return false;
@@ -1916,10 +1876,34 @@
   // does not change when tests override the visibility of the Page.
 }
 
-void RenderViewImpl::ReviveUndeadMainFrameRenderWidget(
-    const ScreenInfo& screen_info) {
+RenderWidget* RenderViewImpl::ReviveUndeadMainFrameRenderWidget() {
   render_widget_ = std::move(undead_render_widget_);
-  render_widget_->SetIsRevivedFromUndead(screen_info);
+  render_widget_->SetIsUndead(false);
+  return render_widget_.get();
+}
+
+void RenderViewImpl::CloseMainFrameRenderWidget() {
+  // There is a WebFrameWidget previously attached by AttachWebFrameWidget().
+  DCHECK(render_widget_->GetWebWidget());
+
+  if (destroying_) {
+    // We are inside RenderViewImpl::Destroy() and the main frame is being
+    // detached as part of shutdown. So we can destroy the RenderWidget.
+
+    // We pass ownership of |render_widget_| to itself. Grab a raw pointer to
+    // call the Close() method on so we don't have to be a C++ expert to know
+    // whether we will end up with a nullptr where we didn't intend due to order
+    // of execution.
+    RenderWidget* closing_widget = render_widget_.get();
+    closing_widget->CloseForFrame(std::move(render_widget_));
+  } else {
+    // We are not inside RenderViewImpl::Destroy(), the main frame is being
+    // detached and replaced with a remote frame proxy. We can't close the
+    // RenderWidget, and it is marked undead instead.
+    render_widget_->SetIsUndead(true);
+
+    undead_render_widget_ = std::move(render_widget_);
+  }
 }
 
 void RenderViewImpl::OnUpdateWebPreferences(const WebPreferences& prefs) {
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 2710fa61..6dd9b0b 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -60,7 +60,6 @@
 #include "ui/surface/transport_dib.h"
 
 namespace blink {
-class WebFrameWidget;
 class WebURLRequest;
 struct PluginAction;
 struct WebWindowFeatures;
@@ -186,20 +185,6 @@
   // FocusController.
   void SetFocus(bool enable);
 
-  // Attaches a WebFrameWidget that will provide a WebFrameWidget interface to
-  // the WebView. Called as part of initialization or when the main frame
-  // RenderWidget is becoming not undead, to connect it to the new local main
-  // frame.
-  void AttachWebFrameWidget(blink::WebFrameWidget* frame_widget);
-  // Detaches the current WebFrameWidget, disconnecting it from the main frame.
-  // Called when the RenderWidget is becoming undead, because the local main
-  // frame is going away.
-  void DetachWebFrameWidget();
-
-  // Called early before detaching begins for the main frame, and objects begin
-  // tearing down.
-  void PrepareForDetach();
-
   // Starts a timer to send an UpdateState message on behalf of |frame|, if the
   // timer isn't already running. This allows multiple state changing events to
   // be coalesced into one update.
@@ -324,9 +309,12 @@
   // be able to specify |initial_setting| where IPC handlers do not.
   void ApplyPageHidden(bool hidden, bool initial_setting);
 
-  // Instead of creating a new RenderWidget, this revives the undead
-  // RenderWidget for use with a new local main frame.
-  void ReviveUndeadMainFrameRenderWidget(const ScreenInfo& screen_info);
+  // Instead of creating a new RenderWidget, a RenderFrame for a main frame
+  // revives the undead RenderWidget;
+  RenderWidget* ReviveUndeadMainFrameRenderWidget();
+  // Closes the main frame RenderWidget. If not shutting down, this will close
+  // my marking it undead, to be revived later.
+  void CloseMainFrameRenderWidget();
 
  private:
   // For unit tests.
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 3897998..1dd61f7 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -499,7 +499,8 @@
                                 blink::WebPagePopup* web_page_popup,
                                 const ScreenInfo& screen_info) {
   popup_ = true;
-  Init(std::move(show_callback), web_page_popup, &screen_info);
+  UnconditionalInit(std::move(show_callback));
+  LivingInit(web_page_popup, screen_info);
 
   if (opener_widget->device_emulator_) {
     opener_widget_screen_origin_ =
@@ -514,20 +515,39 @@
                                            blink::WebWidget* web_widget,
                                            const ScreenInfo& screen_info) {
   pepper_fullscreen_ = true;
-  Init(std::move(show_callback), web_widget, &screen_info);
+  UnconditionalInit(std::move(show_callback));
+  LivingInit(web_widget, screen_info);
 }
 
 void RenderWidget::InitForMainFrame(ShowCallback show_callback,
                                     blink::WebFrameWidget* web_frame_widget,
                                     const ScreenInfo* screen_info) {
-  Init(std::move(show_callback), web_frame_widget, screen_info);
+  UnconditionalInit(std::move(show_callback));
+  // Main frame widgets can be created as undead. Then LivingInit() is deferred.
+  DCHECK_EQ(is_undead_, !web_frame_widget);
+  if (web_frame_widget)
+    LivingInit(web_frame_widget, *screen_info);
+}
+
+void RenderWidget::InitForRevivedMainFrame(
+    blink::WebFrameWidget* web_frame_widget,
+    const ScreenInfo& screen_info) {
+  DCHECK(web_frame_widget);
+  DCHECK(!is_undead_);
+
+  // UnconditionalInit() has already been done. LivingInit() may have previously
+  // occured if the RenderWidget was living previously, but we call it each time
+  // the RenderWidget transitions out of an undead state.
+  LivingInit(web_frame_widget, screen_info);
 }
 
 void RenderWidget::InitForChildLocalRoot(
     blink::WebFrameWidget* web_frame_widget,
     const ScreenInfo& screen_info) {
   for_child_local_root_frame_ = true;
-  Init(base::NullCallback(), web_frame_widget, &screen_info);
+  UnconditionalInit(base::NullCallback());
+  // Child local roots can not be undead.
+  LivingInit(web_frame_widget, screen_info);
 }
 
 void RenderWidget::CloseForFrame(std::unique_ptr<RenderWidget> widget) {
@@ -537,28 +557,11 @@
   Close(std::move(widget));
 }
 
-void RenderWidget::Init(ShowCallback show_callback,
-                        WebWidget* web_widget,
-                        const ScreenInfo* screen_info) {
-  DCHECK_EQ(is_undead_, !web_widget);  // There is a WebWidget when not undead.
-  DCHECK(!webwidget_);
+void RenderWidget::UnconditionalInit(ShowCallback show_callback) {
   DCHECK_NE(routing_id_, MSG_ROUTING_NONE);
 
   input_handler_ = std::make_unique<RenderWidgetInputHandler>(this, this);
 
-  if (!is_undead_) {
-    InitCompositing(*screen_info);
-
-    // If the widget is hidden, delay starting the compositor until the user
-    // shows it. Also if the RenderWidget is undead, we delay starting the
-    // compositor until we expect to use the widget, which will be signaled
-    // through reviving the undead RenderWidget.
-    if (!is_hidden_)
-      StartStopCompositor();
-
-    web_widget->SetAnimationHost(layer_tree_view_->animation_host());
-  }
-
   show_callback_ = std::move(show_callback);
 
 #if defined(OS_MACOSX)
@@ -566,13 +569,38 @@
       std::make_unique<TextInputClientObserver>(for_frame() ? this : nullptr);
 #endif
 
-  webwidget_ = web_widget;
   webwidget_mouse_lock_target_.reset(new WebWidgetLockTarget(this));
   mouse_lock_dispatcher_.reset(new RenderWidgetMouseLockDispatcher(this));
 
   RenderThread::Get()->AddRoute(routing_id_, this);
 }
 
+void RenderWidget::LivingInit(WebWidget* web_widget,
+                              const ScreenInfo& screen_info) {
+  DCHECK(!is_undead_);
+  DCHECK(!webwidget_);
+  DCHECK(web_widget);
+
+  if (!layer_tree_view_)
+    InitCompositing(screen_info);
+
+  const auto& command_line = *base::CommandLine::ForCurrentProcess();
+  SetShowFPSCounter(command_line.HasSwitch(cc::switches::kShowFPSCounter));
+
+  // If the widget is hidden, delay starting the compositor until the user
+  // shows it. Also if the RenderWidget is undead, we delay starting the
+  // compositor until we expect to use the widget, which will be signaled
+  // through reviving the undead RenderWidget.
+  if (!is_hidden_)
+    StartStopCompositor();
+
+  webwidget_ = web_widget;
+  web_widget->SetAnimationHost(layer_tree_view_->animation_host());
+  // Note that this calls into the WebWidget.
+  UpdateSurfaceAndScreenInfo(local_surface_id_allocation_from_parent_,
+                             CompositorViewportRect(), screen_info);
+}
+
 bool RenderWidget::OnMessageReceived(const IPC::Message& message) {
   // TODO(https://crbug.com/1000502): Don't process IPC messages on undead
   // RenderWidgets. We would like to eventually remove them altogether, so they
@@ -647,12 +675,8 @@
   CHECK(!is_undead_);
   // Provisional frames don't send IPCs until they are swapped in/committed.
   CHECK(!IsForProvisionalFrame());
-
-  // Don't send any messages after the browser has told us to close.
-  if (closing_) {
-    delete message;
-    return false;
-  }
+  // Don't send any messages during shutdown.
+  DCHECK(!closing_);
 
   // If given a messsage without a routing ID, then assign our routing ID.
   if (message->routing_id() == MSG_ROUTING_NONE)
@@ -661,11 +685,6 @@
   return RenderThread::Get()->Send(message);
 }
 
-void RenderWidget::SendOrCrash(IPC::Message* message) {
-  bool result = Send(message);
-  CHECK(closing_ || result) << "Failed to send message";
-}
-
 bool RenderWidget::ShouldHandleImeEvents() const {
   if (delegate())
     return has_focus_;
@@ -850,25 +869,20 @@
           new_compositor_viewport_pixel_rect, visual_properties.screen_info);
 
       if (for_frame()) {
-        // TODO(danakj): This should not need to go through RenderFrame to set
-        // Page-level properties.
-        blink::WebFrameWidget* frame_widget = GetFrameWidget();
-        // TODO(danakj): Stop doing SynchronizeVisualProperties() (due to
-        // emulation) while undead, and change this to a DCHECK.
-        if (frame_widget) {
-          RenderFrameImpl* render_frame =
-              RenderFrameImpl::FromWebFrame(frame_widget->LocalRoot());
-
-          // This causes compositing state to be modified which dirties the
-          // document lifecycle. Android Webview relies on the document
-          // lifecycle being clean after the RenderWidget is initialized, in
-          // order to send IPCs that query and change compositing state. So
-          // ResizeWebWidget() must come after this call, as it runs the entire
-          // document lifecycle.
-          render_frame->SetPreferCompositingToLCDTextEnabledOnRenderView(
-              ComputePreferCompositingToLCDText(
-                  compositor_deps_, page_properties_->GetDeviceScaleFactor()));
-        }
+        RenderFrameImpl* render_frame =
+            RenderFrameImpl::FromWebFrame(GetFrameWidget()->LocalRoot());
+        // This causes compositing state to be modified which dirties the
+        // document lifecycle. Android Webview relies on the document
+        // lifecycle being clean after the RenderWidget is initialized, in
+        // order to send IPCs that query and change compositing state. So
+        // ResizeWebWidget() must come after this call, as it runs the entire
+        // document lifecycle.
+        //
+        // TODO(danakj): Only the top-most RenderWidget per RenderView should
+        // be responsible for setting values onto the RenderView.
+        render_frame->SetPreferCompositingToLCDTextEnabledOnRenderView(
+            ComputePreferCompositingToLCDText(
+                compositor_deps_, page_properties_->GetDeviceScaleFactor()));
       }
 
       if (!auto_resize_mode_) {
@@ -992,17 +1006,17 @@
 }
 
 void RenderWidget::SetZoomLevel(double zoom_level) {
-  // TODO(danakj): This should not need to go through RenderFrame to set
-  // Page-level properties.
-  blink::WebFrameWidget* frame_widget = GetFrameWidget();
-  DCHECK(frame_widget);
   RenderFrameImpl* render_frame =
-      RenderFrameImpl::FromWebFrame(frame_widget->LocalRoot());
+      RenderFrameImpl::FromWebFrame(GetFrameWidget()->LocalRoot());
 
+  // TODO(danakj): Only the top-most RenderWidget per RenderView should be
+  // responsible for setting values onto the RenderView.
   bool zoom_level_changed = render_frame->SetZoomLevelOnRenderView(zoom_level);
   if (zoom_level_changed) {
     // Hide popups when the zoom changes.
-    blink::WebView* web_view = frame_widget->LocalRoot()->View();
+    // TODO(danakj): This should go through RenderFrame, and the Delegate path
+    // should be replaced.
+    blink::WebView* web_view = GetFrameWidget()->LocalRoot()->View();
     web_view->CancelPagePopup();
 
     // Propagate changes down to child local root RenderWidgets and
@@ -1098,8 +1112,6 @@
     const blink::WebCoalescedInputEvent& input_event,
     const ui::LatencyInfo& latency_info,
     HandledEventCallback callback) {
-  if (closing_)
-    return false;
   if (IsUndeadOrProvisional())
     return false;
   input_handler_->HandleInputEvent(input_event, latency_info,
@@ -1120,8 +1132,7 @@
   if (IsUndeadOrProvisional())
     return;
 
-  if (GetWebWidget())
-    GetWebWidget()->SetCursorVisibilityState(is_visible);
+  GetWebWidget()->SetCursorVisibilityState(is_visible);
 }
 
 void RenderWidget::OnFallbackCursorModeToggled(bool is_on) {
@@ -1129,8 +1140,7 @@
   if (IsUndeadOrProvisional())
     return;
 
-  if (GetWebWidget())
-    GetWebWidget()->OnFallbackCursorModeToggled(is_on);
+  GetWebWidget()->OnFallbackCursorModeToggled(is_on);
 }
 
 void RenderWidget::OnMouseCaptureLost() {
@@ -1138,8 +1148,7 @@
   if (IsUndeadOrProvisional())
     return;
 
-  if (GetWebWidget())
-    GetWebWidget()->MouseCaptureLost();
+  GetWebWidget()->MouseCaptureLost();
 }
 
 void RenderWidget::OnSetEditCommandsForNextKeyEvent(
@@ -1163,19 +1172,15 @@
 
   if (delegate())
     delegate()->DidReceiveSetFocusEventForWidget();
-  SetFocus(enable);
-}
 
-void RenderWidget::SetFocus(bool enable) {
   has_focus_ = enable;
 
-  if (GetWebWidget())
-    GetWebWidget()->SetFocus(enable);
+  GetWebWidget()->SetFocus(enable);
 
   for (auto& observer : render_frames_)
     observer.RenderWidgetSetFocus(enable);
 
-  // Notify all BrowserPlugins of the RenderView's focus state.
+  // Notify all BrowserPlugins of the RenderWidget's focus state.
   if (BrowserPluginManager::Get())
     BrowserPluginManager::Get()->UpdateFocusState();
 }
@@ -1185,46 +1190,25 @@
 
 void RenderWidget::ApplyViewportChanges(
     const cc::ApplyViewportChangesArgs& args) {
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
   GetWebWidget()->ApplyViewportChanges(args);
 }
 
 void RenderWidget::RecordManipulationTypeCounts(cc::ManipulationInfo info) {
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
   GetWebWidget()->RecordManipulationTypeCounts(info);
 }
 
 void RenderWidget::SendOverscrollEventFromImplSide(
     const gfx::Vector2dF& overscroll_delta,
     cc::ElementId scroll_latched_element_id) {
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
   GetWebWidget()->SendOverscrollEventFromImplSide(overscroll_delta,
                                                   scroll_latched_element_id);
 }
 void RenderWidget::SendScrollEndEventFromImplSide(
     cc::ElementId scroll_latched_element_id) {
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
   GetWebWidget()->SendScrollEndEventFromImplSide(scroll_latched_element_id);
 }
 
 void RenderWidget::BeginMainFrame(base::TimeTicks frame_time) {
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
-
   DCHECK(!is_undead_);
   DCHECK(!IsForProvisionalFrame());
 
@@ -1244,29 +1228,30 @@
 }
 
 void RenderWidget::OnDeferMainFrameUpdatesChanged(bool deferral_state) {
+  // LayerTreeHost::CreateThreaded() will defer main frame updates immediately
+  // until it gets a LocalSurfaceIdAllocation. That's before the
+  // |widget_input_handler_manager_| is created, so it can be null here. We
+  // detect that by seeing a null LayerTreeHost.
+  // TODO(schenney): To avoid ping-ponging between defer main frame states
+  // during initialization, and requiring null checks here, we should probably
+  // pass the LocalSurfaceIdAllocation to the compositor while it is
+  // initialized so that it doesn't have to immediately switch into deferred
+  // mode without being requested to.
+  if (!layer_tree_host_)
+    return;
+
   // The input handler wants to know about the mainframe update status to
   // enable/disable input and for metrics.
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (widget_input_handler_manager_)
-    widget_input_handler_manager_->OnDeferMainFrameUpdatesChanged(
-        deferral_state);
+  widget_input_handler_manager_->OnDeferMainFrameUpdatesChanged(deferral_state);
 }
 
 void RenderWidget::OnDeferCommitsChanged(bool deferral_state) {
   // The input handler wants to know about the commit status for metric purposes
   // and to enable/disable input.
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (widget_input_handler_manager_)
-    widget_input_handler_manager_->OnDeferCommitsChanged(deferral_state);
+  widget_input_handler_manager_->OnDeferCommitsChanged(deferral_state);
 }
 
 void RenderWidget::DidBeginMainFrame() {
-  // TODO(danakj): This should never be null now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
   GetWebWidget()->DidBeginFrame();
 }
 
@@ -1279,18 +1264,6 @@
   // widgets for provisional frames do start their compositor.
   DCHECK(!is_undead_);
 
-  // If we early out, we drop the request which means the compositor waits
-  // forever, which is fine since we're going to destroy it soon.
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (closing_)
-    return;
-
-  // TODO:(https://crbug.com/995981): If there is no WebWidget, then the
-  // RenderWidget should also be destroyed, and this DCHECK should not be
-  // necessary.
-  DCHECK(GetWebWidget());
-
   // TODO(jonross): have this generated by the LayerTreeFrameSink itself, which
   // would then handle binding.
   mojo::PendingRemote<mojom::RenderFrameMetadataObserver> observer_remote;
@@ -1332,24 +1305,19 @@
 }
 
 void RenderWidget::WillCommitCompositorFrame() {
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (GetWebWidget())
-    GetWebWidget()->BeginCommitCompositorFrame();
+  DCHECK(!is_undead_);
+  GetWebWidget()->BeginCommitCompositorFrame();
 }
 
 void RenderWidget::DidCommitCompositorFrame() {
+  DCHECK(!is_undead_);
   if (delegate())
     delegate()->DidCommitCompositorFrameForWidget();
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (GetWebWidget())
-    GetWebWidget()->EndCommitCompositorFrame();
+  GetWebWidget()->EndCommitCompositorFrame();
 }
 
 void RenderWidget::DidCompletePageScaleAnimation() {
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
+  DCHECK(!is_undead_);
   if (delegate())
     delegate()->DidCompletePageScaleAnimationForWidget();
 }
@@ -1421,9 +1389,6 @@
 }
 
 void RenderWidget::UpdateVisualState() {
-  if (!GetWebWidget())
-    return;
-
   DCHECK(!is_undead_);
   DCHECK(!IsForProvisionalFrame());
 
@@ -1467,47 +1432,34 @@
 }
 
 void RenderWidget::RecordStartOfFrameMetrics() {
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (GetWebWidget())
-    GetWebWidget()->RecordStartOfFrameMetrics();
+  DCHECK(!is_undead_);
+  GetWebWidget()->RecordStartOfFrameMetrics();
 }
 
 void RenderWidget::RecordEndOfFrameMetrics(base::TimeTicks frame_begin_time) {
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (GetWebWidget())
-    GetWebWidget()->RecordEndOfFrameMetrics(frame_begin_time);
+  DCHECK(!is_undead_);
+  GetWebWidget()->RecordEndOfFrameMetrics(frame_begin_time);
 }
 
 std::unique_ptr<cc::BeginMainFrameMetrics>
 RenderWidget::GetBeginMainFrameMetrics() {
-  if (GetWebWidget())
-    return GetWebWidget()->GetBeginMainFrameMetrics();
-  return nullptr;
+  DCHECK(!is_undead_);
+  return GetWebWidget()->GetBeginMainFrameMetrics();
 }
 
 void RenderWidget::BeginUpdateLayers() {
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (GetWebWidget())
-    GetWebWidget()->BeginUpdateLayers();
+  DCHECK(!is_undead_);
+  GetWebWidget()->BeginUpdateLayers();
 }
 
 void RenderWidget::EndUpdateLayers() {
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (GetWebWidget())
-    GetWebWidget()->EndUpdateLayers();
+  DCHECK(!is_undead_);
+  GetWebWidget()->EndUpdateLayers();
 }
 
 void RenderWidget::WillBeginCompositorFrame() {
   TRACE_EVENT0("gpu", "RenderWidget::willBeginCompositorFrame");
-
-  // TODO(danakj): This should never be true now that LayerTreeView disconnects
-  // during Close().
-  if (!GetWebWidget())
-    return;
+  DCHECK(!is_undead_);
 
   GetWebWidget()->SetSuppressFrameRequestsWorkaroundFor704763Only(true);
 
@@ -1580,10 +1532,6 @@
 }
 
 void RenderWidget::ShowVirtualKeyboard() {
-  // Blink can continue running and change input state between the Close IPC
-  // and the task that actually closes this class.
-  if (closing_)
-    return;
   UpdateTextInputStateInternal(true, false);
 }
 
@@ -1597,10 +1545,6 @@
 }
 
 void RenderWidget::UpdateTextInputState() {
-  // Blink can continue running and change input state between the Close IPC
-  // and the task that actually closes this class.
-  if (closing_)
-    return;
   UpdateTextInputStateInternal(false, false);
 }
 
@@ -1777,29 +1721,22 @@
   UpdateSurfaceAndScreenInfo(local_surface_id_allocation_from_parent_,
                              CompositorViewportRect(), screen_info);
 
+  RenderFrameImpl* render_frame =
+      RenderFrameImpl::FromWebFrame(GetFrameWidget()->LocalRoot());
   // UpdateSurfaceAndScreenInfo() changes PageProperties including the device
   // scale factor, which changes PreferCompositingToLCDText decisions.
   // TODO(danakj): Do this in UpdateSurfaceAndScreenInfo? But requires a Resize
   // to happen after (see comment on
   // SetPreferCompositingToLCDTextEnabledOnRenderView).
-  // TODO(danakj): This should not need to go through RenderFrame to set
-  // Page-level properties.
-  blink::WebFrameWidget* frame_widget = GetFrameWidget();
-  // TODO(danakj): Stop handling emulation changes while undead, and change this
-  // to a DCHECK.
-  if (frame_widget) {
-    RenderFrameImpl* render_frame =
-        RenderFrameImpl::FromWebFrame(frame_widget->LocalRoot());
-
-    // This causes compositing state to be modified which dirties the document
-    // lifecycle. Android Webview relies on the document lifecycle being clean
-    // after the RenderWidget is initialized, in order to send IPCs that query
-    // and change compositing state. So ResizeWebWidget() must come after this
-    // call, as it runs the entire document lifecycle.
-    render_frame->SetPreferCompositingToLCDTextEnabledOnRenderView(
-        ComputePreferCompositingToLCDText(
-            compositor_deps_, page_properties_->GetDeviceScaleFactor()));
-  }
+  //
+  // This causes compositing state to be modified which dirties the document
+  // lifecycle. Android Webview relies on the document lifecycle being clean
+  // after the RenderWidget is initialized, in order to send IPCs that query
+  // and change compositing state. So ResizeWebWidget() must come after this
+  // call, as it runs the entire document lifecycle.
+  render_frame->SetPreferCompositingToLCDTextEnabledOnRenderView(
+      ComputePreferCompositingToLCDText(
+          compositor_deps_, page_properties_->GetDeviceScaleFactor()));
 
   visible_viewport_size_ = visible_viewport_size;
   size_ = widget_size;
@@ -1858,9 +1795,6 @@
 }
 
 void RenderWidget::QueueMessage(std::unique_ptr<IPC::Message> msg) {
-  if (closing_)
-    return;
-
   // RenderThreadImpl::current() is NULL in some tests.
   if (!RenderThreadImpl::current()) {
     Send(msg.release());
@@ -1944,9 +1878,6 @@
       compositor_deps_->CreateUkmRecorderFactory());
   layer_tree_host_ = layer_tree_view_->layer_tree_host();
 
-  UpdateSurfaceAndScreenInfo(local_surface_id_allocation_from_parent_,
-                             CompositorViewportRect(), screen_info);
-
   blink::scheduler::WebThreadScheduler* main_thread_scheduler =
       compositor_deps_->GetWebMainThreadScheduler();
 
@@ -2003,43 +1934,24 @@
   }
 }
 
-void RenderWidget::SetIsUndead() {
-  DCHECK(!is_undead_);
-  is_undead_ = true;
-  // If hidden, then changing undead state doesn't change anything with the
-  // compositor since when hidden the compositor is always stopped.
-  if (!is_hidden_)
+void RenderWidget::SetIsUndead(bool is_undead) {
+  DCHECK_NE(is_undead, is_undead_);
+  is_undead_ = is_undead;
+
+  if (is_undead_) {
     StartStopCompositor();
 
-  // Remove undead RenderWidgets from the routing map so that they cannot be
-  // looked up with FromRoutingId().
-  g_routing_id_widget_map.Get().erase(routing_id_);
-}
-
-void RenderWidget::SetIsRevivedFromUndead(const ScreenInfo& screen_info) {
-  DCHECK(is_undead_);
-  is_undead_ = false;
-  // If started as undead, the compositor was not created yet.
-  if (!layer_tree_view_)
-    InitCompositing(screen_info);
-  // If hidden, then changing undead state doesn't change anything with the
-  // compositor since when hidden the compositor is always stopped.
-  if (!is_hidden_)
-    StartStopCompositor();
-
-  // Put revived RenderWidgets back into the routing id map so they can be
-  // looked up with FromRoutingId().
-  g_routing_id_widget_map.Get().emplace(routing_id_, this);
-
-  // Reviving from undead is like making a "new" RenderWidget, initialization
-  // that should not bleed across from the last local main frame can happen
-  // here.
-  // TODO(crbug.com/419087): This initialization can be done during
-  // InitCompositing() or when constructing the RenderWidget once there are
-  // no undead RenderWidgets.
-  const base::CommandLine& command_line =
-      *base::CommandLine::ForCurrentProcess();
-  SetShowFPSCounter(command_line.HasSwitch(cc::switches::kShowFPSCounter));
+    webwidget_->Close();
+    webwidget_ = nullptr;
+    // Remove undead RenderWidgets from the routing map so that they cannot be
+    // looked up with FromRoutingId().
+    g_routing_id_widget_map.Get().erase(routing_id_);
+  } else {
+    // When revived from undead, act like a "new RenderWidget". This method is
+    // equivalent to the constructor, and initialization comes separately
+    // through InitForRevivedMainFrame().
+    g_routing_id_widget_map.Get().emplace(routing_id_, this);
+  }
 }
 
 // static
@@ -2127,10 +2039,6 @@
   // TODO(danakj): Remove this check and don't call this method for non-frames.
   if (!for_frame())
     return nullptr;
-  // TODO(danakj): Is this needed? IPCs stop after closing, but code used to
-  // check for a null WebWidget.
-  if (closing_)
-    return nullptr;
   return static_cast<blink::WebFrameWidget*>(webwidget_);
 }
 
@@ -2421,24 +2329,20 @@
     OnOrientationChange();
 
   if (for_frame()) {
-    // TODO(danakj): This should not need to go through RenderFrame to set
-    // Page-level properties.
-    blink::WebFrameWidget* frame_widget = GetFrameWidget();
-    // TODO(danakj): Stop sending/receiving visual properties while undead, and
-    // change this to a DCHECK.
-    if (frame_widget) {
-      RenderFrameImpl* render_frame =
-          RenderFrameImpl::FromWebFrame(frame_widget->LocalRoot());
-      // TODO(danakj): RenderWidget knows the DSF and could avoid calling into
-      // blink when it hasn't changed, but it sets an initial |screen_info_|
-      // during construction, so it is hard to tell if the value is not the
-      // default value once we get to OnSynchronizeVisualProperties. Thus we
-      // call into blink unconditionally and let it early out if it's already
-      // set.
-      render_frame->SetDeviceScaleFactorOnRenderView(
-          compositor_deps_->IsUseZoomForDSFEnabled(),
-          page_properties_->GetDeviceScaleFactor());
-    }
+    RenderFrameImpl* render_frame =
+        RenderFrameImpl::FromWebFrame(GetFrameWidget()->LocalRoot());
+    // TODO(danakj): RenderWidget knows the DSF and could avoid calling into
+    // blink when it hasn't changed, but it sets an initial |screen_info_|
+    // during construction, so it is hard to tell if the value is not the
+    // default value once we get to OnSynchronizeVisualProperties. Thus we
+    // call into blink unconditionally and let it early out if it's already
+    // set.
+    //
+    // TODO(danakj): Only the top-most RenderWidget per RenderView should
+    // be responsible for setting values onto the RenderView.
+    render_frame->SetDeviceScaleFactorOnRenderView(
+        compositor_deps_->IsUseZoomForDSFEnabled(),
+        page_properties_->GetDeviceScaleFactor());
   }
 
   // Propagate changes down to child local root RenderWidgets and BrowserPlugins
@@ -2696,10 +2600,6 @@
 
 void RenderWidget::OnRequestTextInputStateUpdate() {
 #if defined(OS_ANDROID)
-  // This task may run between the Close IPC and the task that actually closes
-  // this class.
-  if (closing_)
-    return;
   DCHECK(!ime_event_guard_);
   UpdateSelectionBounds();
   UpdateTextInputStateInternal(false, true /* reply_to_request */);
@@ -2841,11 +2741,6 @@
 }
 
 void RenderWidget::DidAutoResize(const gfx::Size& new_size) {
-  // Blink can continue running and do a layout/resize between the Close IPC
-  // and the task that actually closes this class.
-  if (closing_)
-    return;
-
   WebRect new_size_in_window(0, 0, new_size.width(), new_size.height());
   ConvertViewportToWindow(&new_size_in_window);
   if (size_.width() != new_size_in_window.width ||
@@ -3858,12 +3753,6 @@
 }
 
 void RenderWidget::DidNavigate() {
-  // Blink may be navigating still between the Close IPC and the task that
-  // actually closes this class, and for a main frame that would come through
-  // this method. But since we are closing we can skip it.
-  if (closing_)
-    return;
-
   // The input handler wants to know about navigation so that it can
   // suppress input until the newly navigated page has a committed frame.
   // It also resets the state for UMA reporting of input arrival with respect
@@ -3923,11 +3812,8 @@
   if (!auto_resize_mode_)
     ResizeWebWidget();  // This picks up the new device scale factor in |info|.
 
-  // TODO(danakj): This should not need to go through RenderFrame to set
-  // Page-level properties.
-  blink::WebFrameWidget* frame_widget = GetFrameWidget();
   RenderFrameImpl* render_frame =
-      RenderFrameImpl::FromWebFrame(frame_widget->LocalRoot());
+      RenderFrameImpl::FromWebFrame(GetFrameWidget()->LocalRoot());
   render_frame->SetPreferCompositingToLCDTextEnabledOnRenderView(
       ComputePreferCompositingToLCDText(
           compositor_deps_, page_properties_->GetDeviceScaleFactor()));
@@ -4031,14 +3917,4 @@
   return weak_ptr_factory_.GetWeakPtr();
 }
 
-void RenderWidget::SetWebWidgetInternal(blink::WebWidget* webwidget) {
-  // Undead state is changed first, and the WebWidget is set accordingly. That
-  // means the compositor is always initialized before we get here.
-  DCHECK_EQ(is_undead_, !webwidget);
-
-  webwidget_ = webwidget;
-  if (webwidget_)
-    webwidget_->SetAnimationHost(layer_tree_view_->animation_host());
-}
-
 }  // namespace content
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index c98ddbf2..92e4661 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -250,6 +250,11 @@
                         blink::WebFrameWidget* web_frame_widget,
                         const ScreenInfo* screen_info);
 
+  // Initialize (or re-initialize) a main frame RenderWidget that has been
+  // revived from undead state.
+  void InitForRevivedMainFrame(blink::WebFrameWidget* web_frame_widget,
+                               const ScreenInfo& screen_info);
+
   // Initialize a new RenderWidget that will be attached to a RenderFrame (via
   // the WebFrameWidget), for a frame that is a local root, but not the main
   // frame.
@@ -308,8 +313,7 @@
   // otherwise act as if it is dead. Only whitelisted new IPC messages will be
   // sent, and it does no compositing. The process is free to exit when there
   // are no other non-undead RenderWidgets.
-  void SetIsUndead();
-  void SetIsRevivedFromUndead(const ScreenInfo& screen_info);
+  void SetIsUndead(bool is_undead);
 
   // A main frame RenderWidget is made undead instead of being deleted. Then
   // when a provisional frame is created, the RenderWidget is recycled and
@@ -650,10 +654,6 @@
                        int relative_cursor_pos);
   void OnImeFinishComposingText(bool keep_selection);
 
-  // This does the actual focus change, but is called in more situations than
-  // just as an IPC message.
-  void SetFocus(bool enable);
-
   // Called by the browser process to update text input state.
   void OnRequestTextInputStateUpdate();
 
@@ -694,12 +694,6 @@
 
   base::WeakPtr<RenderWidget> AsWeakPtr();
 
-  // TODO(https://crbug.com/995981): Eventually, the lifetime of RenderWidget
-  // should be tied to the lifetime of the WebWidget. In the short term, for
-  // main frames, the RenderView has to explicitly set/unset the WebWidget on
-  // attach/detach.
-  void SetWebWidgetInternal(blink::WebWidget* webwidget);
-
  protected:
   // Notify subclasses that we initiated the paint operation.
   virtual void DidInitiatePaint() {}
@@ -725,14 +719,16 @@
   FRIEND_TEST_ALL_PREFIXES(RenderWidgetPopupUnittest, EmulatingPopupRect);
   FRIEND_TEST_ALL_PREFIXES(RenderViewImplTest, EmulatingPopupRect);
 
-  // Called by Create() functions and subclasses to finish initialization.
-  // |show_callback| will be invoked once WebWidgetClient::Show() occurs, and
-  // should be null if Show() won't be triggered for this widget. The WebWidget
-  // and ScreenInfo are both null or both not.
-  void Init(ShowCallback show_callback,
-            blink::WebWidget* web_widget,
-            const ScreenInfo* screen_info);
+  // Called by InitFor*() methods on a new RenderWidget. This contains
+  // initialization that occurs whether the RenderWidget is created as undead or
+  // not.
+  void UnconditionalInit(ShowCallback show_callback);
 
+  // Called by InitFor*() methods when a new RenderWidget is created that is not
+  // undead, or when it is being revived from undead.
+  void LivingInit(blink::WebWidget* web_widget, const ScreenInfo& screen_info);
+  // Initializes the compositor and dependent systems, as part of the
+  // LivingInit() process.
   void InitCompositing(const ScreenInfo& screen_info);
 
   // If appropriate, initiates the compositor to set up IPC channels and begin
@@ -879,13 +875,6 @@
   // Used to force the size of a window when running web tests.
   void SetWindowRectSynchronously(const gfx::Rect& new_window_rect);
 
-  // A variant of Send but is fatal if it fails. The browser may
-  // be waiting for this IPC Message and if the send fails the browser will
-  // be left in a state waiting for something that never comes. And if it
-  // never comes then it may later determine this is a hung renderer; so
-  // instead fail right away.
-  void SendOrCrash(IPC::Message* msg);
-
   // Determines whether or not RenderWidget should process IME events from the
   // browser. It always returns true unless there is no WebFrameWidget to
   // handle the event, or there is no page focus.
diff --git a/content/renderer/render_widget_unittest.cc b/content/renderer/render_widget_unittest.cc
index 458c3ae..16a721c 100644
--- a/content/renderer/render_widget_unittest.cc
+++ b/content/renderer/render_widget_unittest.cc
@@ -174,13 +174,13 @@
                      compositor_deps,
                      page_properties,
                      blink::mojom::DisplayMode::kUndefined,
-                     false,
-                     false,
-                     false,
+                     /*is_undead=*/false,
+                     /*is_hidden=*/false,
+                     /*never_visible=*/false,
                      mojo::NullReceiver()),
         always_overscroll_(false) {
-    Init(base::NullCallback(), &mock_webwidget_,
-         &page_properties->GetScreenInfo());
+    UnconditionalInit(base::NullCallback());
+    LivingInit(&mock_webwidget_, page_properties->GetScreenInfo());
 
     mock_input_handler_host_ = std::make_unique<MockWidgetInputHandlerHost>();
 
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.cc b/content/renderer/service_worker/service_worker_subresource_loader.cc
index 9e7b54e5..a5a196f 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader.cc
@@ -137,8 +137,8 @@
   }
 
   // mojom::ServiceWorkerStreamCallback implementations:
-  void OnCompleted() override { owner_->CommitCompleted(net::OK); }
-  void OnAborted() override { owner_->CommitCompleted(net::ERR_ABORTED); }
+  void OnCompleted() override { owner_->OnBodyReadingComplete(net::OK); }
+  void OnAborted() override { owner_->OnBodyReadingComplete(net::ERR_ABORTED); }
 
  private:
   ServiceWorkerSubresourceLoader* owner_;
@@ -166,7 +166,7 @@
       body_as_blob_size_(blink::BlobUtils::kUnknownSize),
       controller_connector_(std::move(controller_connector)),
       fetch_request_restarted_(false),
-      blob_reading_complete_(false),
+      body_reading_complete_(false),
       side_data_reading_complete_(false),
       routing_id_(routing_id),
       request_id_(request_id),
@@ -477,14 +477,26 @@
   // We have a non-redirect response. Send the headers to the client.
   CommitResponseHeaders();
 
+  bool body_stream_is_valid =
+      !body_as_stream.is_null() && body_as_stream->stream.is_valid();
+
+  // Handle the case where there is no body content.
+  if (!body_stream_is_valid && !response->blob) {
+    CommitEmptyResponseAndComplete();
+    return;
+  }
+
+  mojo::ScopedDataPipeConsumerHandle data_pipe;
+
   // Handle a stream response body.
-  if (!body_as_stream.is_null() && body_as_stream->stream.is_valid()) {
+  if (body_stream_is_valid) {
     DCHECK(!response->blob);
+    if (response->side_data_blob)
+      DCHECK(base::FeatureList::IsEnabled(features::kCacheStorageEagerReading));
     DCHECK(url_loader_client_.is_bound());
     stream_waiter_ = std::make_unique<StreamWaiter>(
         this, std::move(body_as_stream->callback_receiver));
-    CommitResponseBody(std::move(body_as_stream->stream));
-    return;
+    data_pipe = std::move(body_as_stream->stream);
   }
 
   // Handle a blob response body.
@@ -497,31 +509,33 @@
 
     // Start reading the body blob immediately. This will allow the body to
     // start buffering in the pipe while the side data is read.
-    mojo::ScopedDataPipeConsumerHandle data_pipe;
     int error = StartBlobReading(&data_pipe);
     if (error != net::OK) {
       CommitCompleted(error);
       return;
     }
+  }
 
-    // Read side data if necessary.
-    auto resource_type =
-        static_cast<content::ResourceType>(resource_request_.resource_type);
-    if (resource_type == content::ResourceType::kScript) {
-      body_as_blob_->ReadSideData(base::BindOnce(
-          &ServiceWorkerSubresourceLoader::OnBlobSideDataReadingComplete,
-          weak_factory_.GetWeakPtr(), std::move(data_pipe)));
-    } else {
-      // Bypass reading side data when the request isn't for script. Currently
-      // side data only exists for scripts (as cached metadata).
-      OnBlobSideDataReadingComplete(std::move(data_pipe),
-                                    base::Optional<mojo_base::BigBuffer>());
-    }
+  DCHECK(data_pipe.is_valid());
 
+  // Read side data if necessary.  We only do this if both the
+  // |side_data_blob| is available to read and the request is destined
+  // for a script.
+  auto resource_type =
+      static_cast<content::ResourceType>(resource_request_.resource_type);
+  if (response->side_data_blob &&
+      resource_type == content::ResourceType::kScript) {
+    side_data_as_blob_.Bind(std::move(response->side_data_blob->blob));
+    side_data_as_blob_->ReadSideData(base::BindOnce(
+        &ServiceWorkerSubresourceLoader::OnSideDataReadingComplete,
+        weak_factory_.GetWeakPtr(), std::move(data_pipe)));
     return;
   }
 
-  CommitEmptyResponseAndComplete();
+  // Otherwise we can immediately complete side data reading so that the
+  // entire resource completes when the main body is read.
+  OnSideDataReadingComplete(std::move(data_pipe),
+                            base::Optional<mojo_base::BigBuffer>());
 }
 
 void ServiceWorkerSubresourceLoader::CommitResponseHeaders() {
@@ -701,35 +715,32 @@
                           TRACE_ID_LOCAL(request_id_)),
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
   DCHECK(body_pipe);
-  DCHECK(!blob_reading_complete_);
+  DCHECK(!body_reading_complete_);
 
   return ServiceWorkerLoaderHelpers::ReadBlobResponseBody(
       &body_as_blob_, body_as_blob_size_,
-      base::BindOnce(&ServiceWorkerSubresourceLoader::OnBlobReadingComplete,
+      base::BindOnce(&ServiceWorkerSubresourceLoader::OnBodyReadingComplete,
                      weak_factory_.GetWeakPtr()),
       body_pipe);
 }
 
-void ServiceWorkerSubresourceLoader::OnBlobSideDataReadingComplete(
+void ServiceWorkerSubresourceLoader::OnSideDataReadingComplete(
     mojo::ScopedDataPipeConsumerHandle data_pipe,
     base::Optional<mojo_base::BigBuffer> metadata) {
   TRACE_EVENT_WITH_FLOW1(
       "ServiceWorker",
-      "ServiceWorkerSubresourceLoader::OnBlobSideDataReadingComplete",
+      "ServiceWorkerSubresourceLoader::OnSideDataReadingComplete",
       TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
                           TRACE_ID_LOCAL(request_id_)),
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, "metadata size",
       (metadata ? metadata->size() : 0));
   DCHECK(url_loader_client_);
-  DCHECK(body_as_blob_);
   DCHECK(!side_data_reading_complete_);
   side_data_reading_complete_ = true;
 
   if (metadata.has_value())
     url_loader_client_->OnReceiveCachedMetadata(std::move(metadata.value()));
 
-  // We should have started reading the body in parallel before trying to load
-  // side data.
   DCHECK(data_pipe.is_valid());
 
   base::TimeDelta delay =
@@ -742,24 +753,23 @@
 
   // If the blob reading completed before the side data reading, then we
   // must manually finalize the blob reading now.
-  if (blob_reading_complete_) {
-    OnBlobReadingComplete(net::OK);
+  if (body_reading_complete_) {
+    OnBodyReadingComplete(net::OK);
   }
 
   // Otherwise we asyncly continue in OnBlobReadingComplete().
 }
 
-void ServiceWorkerSubresourceLoader::OnBlobReadingComplete(int net_error) {
+void ServiceWorkerSubresourceLoader::OnBodyReadingComplete(int net_error) {
   TRACE_EVENT_WITH_FLOW0(
-      "ServiceWorker", "ServiceWorkerSubresourceLoader::OnBlobReadingComplete",
+      "ServiceWorker", "ServiceWorkerSubresourceLoader::OnBodyReadingComplete",
       TRACE_ID_WITH_SCOPE(kServiceWorkerSubresourceLoaderScope,
                           TRACE_ID_LOCAL(request_id_)),
       TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT);
-  DCHECK(body_as_blob_);
-  blob_reading_complete_ = true;
+  body_reading_complete_ = true;
   // If the side data has not completed reading yet, then we need to delay
   // calling CommitCompleted.  This method will be called again from
-  // OnBlobSideDataReadingComplete().  Only delay for successful reads, though.
+  // OnSideDataReadingComplete().  Only delay for successful reads, though.
   // Abort immediately on error.
   if (!side_data_reading_complete_ && net_error == net::OK)
     return;
diff --git a/content/renderer/service_worker/service_worker_subresource_loader.h b/content/renderer/service_worker/service_worker_subresource_loader.h
index 47f27edd..f8ad0ba 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader.h
+++ b/content/renderer/service_worker/service_worker_subresource_loader.h
@@ -116,10 +116,9 @@
   void ResumeReadingBodyFromNet() override;
 
   int StartBlobReading(mojo::ScopedDataPipeConsumerHandle* body_pipe);
-  void OnBlobSideDataReadingComplete(
-      mojo::ScopedDataPipeConsumerHandle data_pipe,
-      base::Optional<mojo_base::BigBuffer> metadata);
-  void OnBlobReadingComplete(int net_error);
+  void OnSideDataReadingComplete(mojo::ScopedDataPipeConsumerHandle data_pipe,
+                                 base::Optional<mojo_base::BigBuffer> metadata);
+  void OnBodyReadingComplete(int net_error);
 
   // Calls url_loader_client_->OnReceiveResponse() with |response_head_|.
   void CommitResponseHeaders();
@@ -157,6 +156,8 @@
   // The blob needs to be held while it's read to keep it alive.
   mojo::Remote<blink::mojom::Blob> body_as_blob_;
   uint64_t body_as_blob_size_;
+  // The blob needs to be held while it's read to keep it alive.
+  mojo::Remote<blink::mojom::Blob> side_data_as_blob_;
 
   scoped_refptr<ControllerServiceWorkerConnector> controller_connector_;
 
@@ -167,7 +168,7 @@
                  ControllerServiceWorkerConnector::Observer>
       controller_connector_observer_{this};
   bool fetch_request_restarted_;
-  bool blob_reading_complete_;
+  bool body_reading_complete_;
   bool side_data_reading_complete_;
 
   // These are given by the constructor (as the params for
diff --git a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
index 2a3b80f..e34ee0e 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
@@ -53,8 +53,8 @@
 
  private:
   // Implements blink::mojom::Blob.
-  void Clone(mojo::PendingReceiver<blink::mojom::Blob>) override {
-    NOTREACHED();
+  void Clone(mojo::PendingReceiver<blink::mojom::Blob> receiver) override {
+    receivers_.Add(this, std::move(receiver));
   }
   void AsDataPipeGetter(
       mojo::PendingReceiver<network::mojom::DataPipeGetter>) override {
@@ -85,6 +85,7 @@
     NOTREACHED();
   }
 
+  mojo::ReceiverSet<blink::mojom::Blob> receivers_;
   base::Optional<std::vector<uint8_t>> side_data_;
   std::string body_;
 };
@@ -107,6 +108,14 @@
     if (response->blob) {
       response->headers.emplace("Content-Length",
                                 base::NumberToString(response->blob->size));
+
+      // Clone the blob into the side_data_blob to match cache_storage behavior.
+      mojo::Remote<blink::mojom::Blob> blob_remote(
+          std::move(response->blob->blob));
+      blob_remote->Clone(response->blob->blob.InitWithNewPipeAndPassReceiver());
+      response->side_data_blob = blink::mojom::SerializedBlob::New(
+          response->blob->uuid, response->blob->content_type,
+          response->blob->size, blob_remote.Unbind());
     }
     return response;
   }
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index beb7c4a..115c59cd 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -67,6 +67,10 @@
     "../browser/native_file_system/mock_native_file_system_permission_context.h",
     "../browser/native_file_system/mock_native_file_system_permission_grant.cc",
     "../browser/native_file_system/mock_native_file_system_permission_grant.h",
+    "../browser/portal/portal_created_observer.cc",
+    "../browser/portal/portal_created_observer.h",
+    "../browser/portal/portal_interceptor_for_testing.cc",
+    "../browser/portal/portal_interceptor_for_testing.h",
     "../browser/renderer_host/input/mock_input_router.cc",
     "../browser/renderer_host/input/mock_input_router.h",
     "../browser/renderer_host/mock_render_widget_host.cc",
diff --git a/content/test/data/accessibility/display-locking/activatable-activated.html b/content/test/data/accessibility/display-locking/activatable-activated.html
index 8682b67..6be1c7e 100644
--- a/content/test/data/accessibility/display-locking/activatable-activated.html
+++ b/content/test/data/accessibility/display-locking/activatable-activated.html
@@ -2,10 +2,10 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" rendersubtree="invisible activatable">
+  <div id="locked" rendersubtree="invisible">
     <div>child</div>
-    <div id="nested" rendersubtree="invisible activatable">nested locked element!</div>
-    <div id="nonActivatable" rendersubtree="invisible">nested non activatable locked element</div>
+    <div id="nested" rendersubtree="invisible">nested locked element!</div>
+    <div id="nonActivatable" rendersubtree="invisible skip-activation">nested non activatable locked element</div>
   </div>
 </div>
 
diff --git a/content/test/data/accessibility/display-locking/activatable.html b/content/test/data/accessibility/display-locking/activatable.html
index dca7e48..49a5d52f 100644
--- a/content/test/data/accessibility/display-locking/activatable.html
+++ b/content/test/data/accessibility/display-locking/activatable.html
@@ -2,9 +2,9 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" rendersubtree="invisible activatable">
+  <div id="locked" rendersubtree="invisible">
     <div>child</div>
-    <div id="nested" rendersubtree="invisible activatable">nested locked element!</div>
-    <div id="nonActivatable" rendersubtree="invisible">nested non activatable locked element</div>
+    <div id="nested" rendersubtree="invisible">nested locked element!</div>
+    <div id="nonActivatable" rendersubtree="invisible skip-activation">nested non activatable locked element</div>
   </div>
 </div>
diff --git a/content/test/data/accessibility/display-locking/all-committed.html b/content/test/data/accessibility/display-locking/all-committed.html
index 81eb5c1..2266d984 100644
--- a/content/test/data/accessibility/display-locking/all-committed.html
+++ b/content/test/data/accessibility/display-locking/all-committed.html
@@ -2,10 +2,10 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" rendersubtree="invisible-activatable">
+  <div id="locked" rendersubtree="invisible skip-activation">
     <div>child</div>
-    <div id="nested" rendersubtree="invisible-activatable">nested locked element!</div>
-    <div id="nonActivatable" rendersubtree="invisible">nested non activatable locked element</div>
+    <div id="nested" rendersubtree="invisible skip-activation">nested locked element!</div>
+    <div id="nonActivatable" rendersubtree="invisible skip-activation">nested non activatable locked element</div>
     <!--
       TODO(rakina): Make display:none, visibility:hidden, aria-hidden nodes
       in locked subtrees get ignored for accessibility/marked invisible.
diff --git a/content/test/data/accessibility/display-locking/all.html b/content/test/data/accessibility/display-locking/all.html
index fc60826..32bee4b 100644
--- a/content/test/data/accessibility/display-locking/all.html
+++ b/content/test/data/accessibility/display-locking/all.html
@@ -2,10 +2,10 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" rendersubtree="invisible activatable">
+  <div id="locked" rendersubtree="invisible">
     <div>child</div>
-    <div id="nested" rendersubtree="invisible activatable">nested locked element!</div>
-    <div id="nonActivatable" rendersubtree="invisible">nested non activatable locked element</div>
+    <div id="nested" rendersubtree="invisible">nested locked element!</div>
+    <div id="nonActivatable" rendersubtree="invisible skip-activation">nested non activatable locked element</div>
     <!--
       TODO(rakina): Make display:none, visibility:hidden, aria-hidden nodes
       in locked subtrees get ignored for accessibility/marked invisible.
diff --git a/content/test/data/accessibility/display-locking/non-activatable.html b/content/test/data/accessibility/display-locking/non-activatable.html
index 029fc15d..15f436e 100644
--- a/content/test/data/accessibility/display-locking/non-activatable.html
+++ b/content/test/data/accessibility/display-locking/non-activatable.html
@@ -2,9 +2,9 @@
 @BLINK-ALLOW:offscreen
 -->
 <div>
-  <div id="locked" rendersubtree="invisible">
+  <div id="locked" rendersubtree="invisible skip-activation">
     <div>child</div>
-    <div id="nested" rendersubtree="invisible">nested locked element!</div>
-    <div id="activatable" rendersubtree="invisible-activatable">non activatable locked element</div>
+    <div id="nested" rendersubtree="invisible skip-activation">nested locked element!</div>
+    <div id="activatable" rendersubtree="invisible skip-activation">non activatable locked element</div>
   </div>
 </div>
diff --git a/content/test/data/service_worker/cached_fetch_event.js b/content/test/data/service_worker/cached_fetch_event.js
new file mode 100644
index 0000000..890e51f
--- /dev/null
+++ b/content/test/data/service_worker/cached_fetch_event.js
@@ -0,0 +1,20 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const name = 'test_cache';
+const resource = '/service_worker/v8_cache_test.js';
+
+self.addEventListener('install', evt => {
+  evt.waitUntil(async function() {
+    const c = await caches.open(name);
+    await c.addAll([resource]);
+  }());
+});
+
+self.addEventListener('fetch', evt => {
+  evt.respondWith(async function() {
+    const c = await caches.open(name);
+    return c.match(resource);
+  }());
+});
diff --git a/device/vr/public/mojom/vr_service.mojom b/device/vr/public/mojom/vr_service.mojom
index 3b250a1..9edd6e04 100644
--- a/device/vr/public/mojom/vr_service.mojom
+++ b/device/vr/public/mojom/vr_service.mojom
@@ -289,6 +289,31 @@
   bool wait_for_gpu_fence;
 };
 
+// Native origins that are reference spaces are identified by their category.
+// Currently, the device needs to know about 3 reference space categories.
+enum XRReferenceSpaceCategory {
+  LOCAL,
+  LOCAL_FLOOR,
+  VIEWER,
+  BOUNDED_FLOOR,
+  UNBOUNDED
+};
+
+// Native origin represents a reference space that is known to the device and
+// whose position can be tracked over time. There are multiple different types
+// of native origins (planes, anchors, reference spaces, input sources), each of
+// them roughly corresponds to something that either is an XRSpace (for example
+// XRReferenceSpaceType, XRBoundedReferenceSpace) or returns an XRSpace (for
+// example XRAnchor, XRPlane, XRInputSource). Native origin can be identified,
+// depending on its type, by the id of the entity (this is the case for planes,
+// anchors and input sources), or by reference space category.
+union XRNativeOriginInformation {
+  uint32 input_source_id;
+  uint32 plane_id;
+  uint32 anchor_id;
+  XRReferenceSpaceCategory reference_space_category;
+};
+
 enum XRPlaneOrientation {
   UNKNOWN = 0,
   HORIZONTAL = 1,
@@ -359,6 +384,18 @@
   array<XRAnchorData> updated_anchors_data;
 };
 
+// Information about results for a single subscription for the current frame.
+struct XRHitTestSubscriptionResultData {
+  uint32 subscription_id;
+  array<XRHitResult> hit_test_results;
+};
+
+// Struct containing data about results of all hit test subscriptions for the
+// current frame.
+struct XRHitTestSubscriptionResultsData {
+  array<XRHitTestSubscriptionResultData> results;
+};
+
 // The data needed for each animation frame of an XRSession.
 struct XRFrameData {
   // General XRSession value
@@ -408,6 +445,10 @@
 
   // Tracked anchors information.
   XRAnchorsData? anchors_data;
+
+  // Hit test subscription results. Only present if the session supports
+  // environment integration.
+  XRHitTestSubscriptionResultsData? hit_test_subscription_results;
 };
 
 enum VRDisplayEventReason {
@@ -478,6 +519,11 @@
   FAILURE = 1,
 };
 
+enum SubscribeToHitTestResult {
+  SUCCESS = 0,
+  FAILURE_GENERIC = 1,
+};
+
 // Provides functionality for integrating environment information into an
 // XRSession. For example, some AR sessions would implement hit test to allow
 // developers to get the information about the world that its sensors supply.
@@ -493,6 +539,24 @@
   // there was an error.
   RequestHitTest(XRRay ray) => (array<XRHitResult>? results);
 
+  // Establishes a hit test subscription on the device. The subscription results
+  // will be computed taking into account latest state of the native origin
+  // identified by passed in |native_origin_information|. I.e., in each frame,
+  // the ray used to subscribe to hit test will be transformed to device
+  // coordinate system using the device's current knowledge of the state of the
+  // native origin. The returned |subscription_id| uniquely identifies the
+  // subscription within an immersive session. Lifetime of the subscription is
+  // tied to the lifetime of the immersive session.
+  SubscribeToHitTest(
+    XRNativeOriginInformation native_origin_information, XRRay ray) =>
+    (SubscribeToHitTestResult result, uint32 subscription_id);
+
+  // Notifies the device that subscription with the passed in |subscription_id|
+  // is no longer needed and should be removed. The |subscription_id| must be a
+  // valid id of a subscription returned by one of the SubscribeToHitTest calls,
+  // otherwise the call will be ignored.
+  UnsubscribeFromHitTest(uint32 subscription_id);
+
   // Issues a request to create an anchor attached to a session.
   // |result| will contain status code of the request. |anchor_id| will be valid
   // only if the |result| is SUCCESS.
diff --git a/fuchsia/engine/context_provider_impl.cc b/fuchsia/engine/context_provider_impl.cc
index 858805b9..a5d9a48 100644
--- a/fuchsia/engine/context_provider_impl.cc
+++ b/fuchsia/engine/context_provider_impl.cc
@@ -33,6 +33,7 @@
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
+#include "build/build_config.h"
 #include "components/viz/common/features.h"
 #include "content/public/common/content_switches.h"
 #include "fuchsia/engine/common/web_engine_content_client.h"
@@ -103,6 +104,20 @@
   return true;
 }
 
+// Returns true if DRM is supported in current configuration. Currently we
+// assume that it is supported on ARM64, but not on x64.
+//
+// TODO(crbug.com/1013412): Detect support for all features required for
+// FuchsiaCdm. Specifically we need to verify that protected memory is supported
+// and that mediacodec API provides hardware video decoders.
+bool IsFuchsiaCdmSupported() {
+#if defined(ARCH_CPU_ARM64)
+  return true;
+#else
+  return false;
+#endif
+}
+
 }  // namespace
 
 const uint32_t ContextProviderImpl::kContextRequestHandleId =
@@ -212,7 +227,17 @@
   bool enable_widevine =
       (features & fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM) ==
       fuchsia::web::ContextFeatureFlags::WIDEVINE_CDM;
+  if (enable_widevine && !IsFuchsiaCdmSupported()) {
+    LOG(WARNING) << "Widevine is not supported on this device.";
+    enable_widevine = false;
+  }
+
   bool enable_playready = params.has_playready_key_system();
+  if (enable_playready && !IsFuchsiaCdmSupported()) {
+    LOG(WARNING) << "PlayReady is not supported on this device.";
+    enable_playready = false;
+  }
+
   bool enable_protected_graphics = enable_widevine || enable_playready;
 
   if (enable_protected_graphics && !enable_vulkan) {
diff --git a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
index 141f41b..1804d217 100644
--- a/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
+++ b/fuchsia/engine/renderer/web_engine_content_renderer_client.cc
@@ -22,8 +22,8 @@
 
 // Returns true if the specified video format can be decoded on hardware.
 bool IsSupportedHardwareVideoCodec(const media::VideoType& type) {
-  // TODO(fxb/36000): Replace these hardcoded checks with a query to the
-  // fuchsia.mediacodec FIDL service.
+  // TODO(crbug.com/1013412): Replace these hardcoded checks with a query to the
+  // fuchsia.mediacodec FIDL service when fxb/36000 is resolved.
   if (type.codec == media::kCodecH264 && type.level <= 41)
     return true;
 
diff --git a/headless/test/data/protocol/helpers/virtual-time-controller.js b/headless/test/data/protocol/helpers/virtual-time-controller.js
index 1a330dcb5..4928eeb5 100644
--- a/headless/test/data/protocol/helpers/virtual-time-controller.js
+++ b/headless/test/data/protocol/helpers/virtual-time-controller.js
@@ -93,6 +93,33 @@
     }
   }
 
+  /**
+   * Capture screenshot of the entire screen and return a 2d graphics context
+   * that has the resulting screenshot painted.
+   */
+  async captureScreenshot() {
+    const frameTimeTicks = this.currentFrameTime();
+    const screenshotData =
+        (await this.dp_.HeadlessExperimental.beginFrame(
+            {frameTimeTicks, screenshot: {format: 'png'}}))
+        .result.screenshotData;
+    // Advance virtual time a bit so that next frame timestamp is greater.
+    this.virtualTimeBase_ += 0.01;
+    const image = new Image();
+    await new Promise(fulfill => {
+      image.onload = fulfill;
+      image.src = `data:image/png;base64,${screenshotData}`;
+    });
+    this.testRunner_.log(
+        `Screenshot size: ${image.naturalWidth} x ${image.naturalHeight}`);
+    const canvas = document.createElement('canvas');
+    canvas.width = image.naturalWidth;
+    canvas.height = image.naturalHeight;
+    const ctx = canvas.getContext('2d');
+    ctx.drawImage(image, 0, 0);
+    return ctx;
+  }
+
   async issueAnimationFrameAndScheduleNextChunk_() {
     if (this.totalElapsedTime_ > 0 && this.remainingBudget_ > 0) {
       const remainder = this.totalElapsedTime_ % this.animationFrameInterval_;
diff --git a/headless/test/data/protocol/sanity/renderer-canvas.js b/headless/test/data/protocol/sanity/renderer-canvas.js
index dfcea27..f438b69 100644
--- a/headless/test/data/protocol/sanity/renderer-canvas.js
+++ b/headless/test/data/protocol/sanity/renderer-canvas.js
@@ -34,41 +34,15 @@
   await virtualTimeController.grantInitialTime(500, 1000,
     null,
     async () => {
-      const frameTimeTicks = virtualTimeController.currentFrameTime();
-      const screenshotData =
-          (await dp.HeadlessExperimental.beginFrame(
-              {frameTimeTicks, screenshot: {format: 'png'}}))
-          .result.screenshotData;
-      await logScreenShotData(screenshotData);
-      testRunner.completeTest();
-    }
-  );
-
-  function logScreenShotData(pngBase64) {
-    const image = new Image();
-
-    let callback;
-    let promise = new Promise(fulfill => callback = fulfill);
-    image.onload = function() {
-      testRunner.log(`Screenshot size: `
-          + `${image.naturalWidth} x ${image.naturalHeight}`);
-      const canvas = document.createElement('canvas');
-      canvas.width = image.naturalWidth;
-      canvas.height = image.naturalHeight;
-      const ctx = canvas.getContext('2d');
-      ctx.drawImage(image, 0, 0);
-      // Expected rgba @(0,0): 255,255,255,255
+      const ctx = await virtualTimeController.captureScreenshot();
       const rgba = ctx.getImageData(0, 0, 1, 1).data;
       testRunner.log(`rgba @(0,0): ${rgba}`);
       // Expected rgba @(25,25): 255,0,0,255
       const rgba2 = ctx.getImageData(25, 25, 1, 1).data;
       testRunner.log(`rgba @(25,25): ${rgba2}`);
-      callback();
+      testRunner.completeTest();
     }
+  );
 
-    image.src = `data:image/png;base64,${pngBase64}`;
-
-    return promise;
-  }
   await frameNavigationHelper.navigate('http://example.com/');
 })
diff --git a/headless/test/data/protocol/sanity/renderer-opacity-animation-expected.txt b/headless/test/data/protocol/sanity/renderer-opacity-animation-expected.txt
new file mode 100644
index 0000000..e8348d8
--- /dev/null
+++ b/headless/test/data/protocol/sanity/renderer-opacity-animation-expected.txt
@@ -0,0 +1,6 @@
+Tests that animating layer transparency produces correct pixels
+requested url: http://example.com/
+Screenshot size: 800 x 600
+rgba @(25,25) before animaion started: 255,255,255,255
+Screenshot size: 800 x 600
+rgba @(25,25) after animation started: 1,128,1,255
\ No newline at end of file
diff --git a/headless/test/data/protocol/sanity/renderer-opacity-animation.js b/headless/test/data/protocol/sanity/renderer-opacity-animation.js
new file mode 100644
index 0000000..7cb6995
--- /dev/null
+++ b/headless/test/data/protocol/sanity/renderer-opacity-animation.js
@@ -0,0 +1,58 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function(testRunner) {
+    let {page, session, dp} = await testRunner.startWithFrameControl(
+        'Tests that animating layer transparency produces correct pixels');
+
+    await dp.Runtime.enable();
+    await dp.HeadlessExperimental.enable();
+
+    let RendererTestHelper =
+        await testRunner.loadScript('../helpers/renderer-test-helper.js');
+    let {httpInterceptor, frameNavigationHelper, virtualTimeController} =
+        await (new RendererTestHelper(testRunner, dp, page)).init();
+
+    // Animate the opacity of the div from 0 to 1 after a 1s delay.
+    httpInterceptor.addResponse(
+        `http://example.com/`,
+        `<style>
+           body { margin: 0 }
+           div {
+             animation-delay: 1s;
+             opacity: 0;
+             animation-name: animation;
+             animation-fill-mode: forwards;
+             animation-duration: 1s;
+             background-color: green;
+             width: 100vw;
+             height: 100vh;
+           }
+           @keyframes animation {
+             0% { opacity: 1; }
+             100% { opacity: 1; }
+           }
+         </style>
+         <div></div>`);
+
+    let ctx = await new Promise(async fulfill => {
+        // Give page 500ms before capturing first screenshot. The screenshot
+        // should fall into animamtion-delay interval, so no animation effects
+        // should be present and the point shoule be white.
+        await virtualTimeController.grantInitialTime(500, 100,
+            null,
+            async () => fulfill(await virtualTimeController.captureScreenshot())
+        );
+        frameNavigationHelper.navigate('http://example.com/');
+    });
+    let rgba = ctx.getImageData(25, 25, 1, 1).data;
+    testRunner.log(`rgba @(25,25) before animaion started: ${rgba}`);
+    // After additional 550ms, the animation should have started and the test
+    // point shoule be green.
+    await new Promise(fulfill => virtualTimeController.grantTime(550, fulfill));
+    ctx = await virtualTimeController.captureScreenshot();
+    rgba = ctx.getImageData(25, 25, 1, 1).data;
+    testRunner.log(`rgba @(25,25) after animation started: ${rgba}`);
+    testRunner.completeTest();
+  })
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index e984333..707d495 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.cc
@@ -422,4 +422,7 @@
                                   "sanity/renderer-css-url-filter.js")
 HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererCanvas, "sanity/renderer-canvas.js")
 
+HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererOpacityAnimation,
+                                  "sanity/renderer-opacity-animation.js")
+
 }  // namespace headless
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 9029121..0e589f1 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -2167,7 +2167,7 @@
     builders { mixins: "ios-ci" name: "ios-simulator-xcode-clang" }
     builders { mixins: "ios-ci" name: "ios-slimnav" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios-simulator-cronet" }
-    builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios12-beta-simulator" }
+    builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios-webkit-tot" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios13-beta-simulator" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios13-sdk-device" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios13-sdk-simulator" }
@@ -4669,7 +4669,6 @@
     builders { mixins: "ios-try" name: "ios-device-xcode-clang" }
     builders { mixins: "ios-try" name: "ios-simulator" }
     builders { mixins: "ios-try" name: "ios-simulator-cronet" }
-    builders { mixins: "ios-try" name: "ios12-beta-simulator" }
     builders { mixins: "ios-try" name: "ios13-beta-simulator" }
     builders { mixins: "ios-try" name: "ios13-sdk-simulator" }
     builders { mixins: "ios-try" name: "ios-simulator-full-configs" }
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index 9029121..0e589f1 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -2167,7 +2167,7 @@
     builders { mixins: "ios-ci" name: "ios-simulator-xcode-clang" }
     builders { mixins: "ios-ci" name: "ios-slimnav" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios-simulator-cronet" }
-    builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios12-beta-simulator" }
+    builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios-webkit-tot" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios13-beta-simulator" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios13-sdk-device" }
     builders { mixins: "ios-ci" mixins: "fyi-ci" name: "ios13-sdk-simulator" }
@@ -4669,7 +4669,6 @@
     builders { mixins: "ios-try" name: "ios-device-xcode-clang" }
     builders { mixins: "ios-try" name: "ios-simulator" }
     builders { mixins: "ios-try" name: "ios-simulator-cronet" }
-    builders { mixins: "ios-try" name: "ios12-beta-simulator" }
     builders { mixins: "ios-try" name: "ios13-beta-simulator" }
     builders { mixins: "ios-try" name: "ios13-sdk-simulator" }
     builders { mixins: "ios-try" name: "ios-simulator-full-configs" }
diff --git a/infra/config/generated/luci-milo.cfg b/infra/config/generated/luci-milo.cfg
index 5f23b67..47f6edc 100644
--- a/infra/config/generated/luci-milo.cfg
+++ b/infra/config/generated/luci-milo.cfg
@@ -2319,9 +2319,9 @@
     category: "goma|ios"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/ios12-beta-simulator"
-    category: "iOS|iOS12"
-    short_name: "beta"
+    name: "buildbucket/luci.chromium.ci/ios-webkit-tot"
+    category: "iOS"
+    short_name: "wk"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/ios13-sdk-device"
@@ -4559,9 +4559,6 @@
     name: "buildbucket/luci.chromium.try/ios-simulator-xcode-clang"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/ios12-beta-simulator"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/ios13-beta-simulator"
   }
   builders {
@@ -5401,11 +5398,6 @@
     short_name: "slim"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/ios12-beta-simulator"
-    category: "chromium.fyi|12"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/ios13-sdk-device"
     category: "chromium.fyi|13"
     short_name: "dev"
diff --git a/infra/config/generated/luci-scheduler.cfg b/infra/config/generated/luci-scheduler.cfg
index 52555b7..31e3e15 100644
--- a/infra/config/generated/luci-scheduler.cfg
+++ b/infra/config/generated/luci-scheduler.cfg
@@ -362,7 +362,6 @@
   triggers: "ios-simulator-noncq"
   triggers: "ios-simulator-xcode-clang"
   triggers: "ios-slimnav"
-  triggers: "ios12-beta-simulator"
   triggers: "ios13-beta-simulator"
   triggers: "ios13-sdk-device"
   triggers: "ios13-sdk-simulator"
@@ -1362,12 +1361,13 @@
 }
 
 job {
-  id: "ios12-beta-simulator"
+  id: "ios-webkit-tot"
   acl_sets: "default"
+  schedule: "0 1-23/6 * * *"
   buildbucket: {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.chromium.ci"
-    builder: "ios12-beta-simulator"
+    builder: "ios-webkit-tot"
   }
 }
 
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index 5f23b67..47f6edc 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -2319,9 +2319,9 @@
     category: "goma|ios"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/ios12-beta-simulator"
-    category: "iOS|iOS12"
-    short_name: "beta"
+    name: "buildbucket/luci.chromium.ci/ios-webkit-tot"
+    category: "iOS"
+    short_name: "wk"
   }
   builders {
     name: "buildbucket/luci.chromium.ci/ios13-sdk-device"
@@ -4559,9 +4559,6 @@
     name: "buildbucket/luci.chromium.try/ios-simulator-xcode-clang"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/ios12-beta-simulator"
-  }
-  builders {
     name: "buildbucket/luci.chromium.try/ios13-beta-simulator"
   }
   builders {
@@ -5401,11 +5398,6 @@
     short_name: "slim"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/ios12-beta-simulator"
-    category: "chromium.fyi|12"
-    short_name: "beta"
-  }
-  builders {
     name: "buildbucket/luci.chromium.ci/ios13-sdk-device"
     category: "chromium.fyi|13"
     short_name: "dev"
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index 52555b7..31e3e15 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -362,7 +362,6 @@
   triggers: "ios-simulator-noncq"
   triggers: "ios-simulator-xcode-clang"
   triggers: "ios-slimnav"
-  triggers: "ios12-beta-simulator"
   triggers: "ios13-beta-simulator"
   triggers: "ios13-sdk-device"
   triggers: "ios13-sdk-simulator"
@@ -1362,12 +1361,13 @@
 }
 
 job {
-  id: "ios12-beta-simulator"
+  id: "ios-webkit-tot"
   acl_sets: "default"
+  schedule: "0 1-23/6 * * *"
   buildbucket: {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.chromium.ci"
-    builder: "ios12-beta-simulator"
+    builder: "ios-webkit-tot"
   }
 }
 
diff --git a/ios/build/bots/chromium.fyi/ios12-beta-simulator.json b/ios/build/bots/chromium.fyi/ios-webkit-tot.json
similarity index 98%
rename from ios/build/bots/chromium.fyi/ios12-beta-simulator.json
rename to ios/build/bots/chromium.fyi/ios-webkit-tot.json
index 9ba6335..6ca32bf 100644
--- a/ios/build/bots/chromium.fyi/ios12-beta-simulator.json
+++ b/ios/build/bots/chromium.fyi/ios-webkit-tot.json
@@ -12,6 +12,7 @@
     "target_os=\"ios\"",
     "use_goma=true"
   ],
+  "configuration": "Debug",
   "tests": [
     {
       "include": "common_tests.json",
@@ -45,7 +46,6 @@
       "test args": [
         "--run-with-custom-webkit"
       ]
-
     },
     {
       "include": "eg_cq_tests.json",
@@ -90,7 +90,6 @@
       "test args": [
         "--run-with-custom-webkit"
       ]
-
     },
     {
       "include": "eg_cq_tests.json",
diff --git a/ios/build/bots/chromium.mac/ios12-beta-simulator.json b/ios/build/bots/chromium.mac/ios12-beta-simulator.json
deleted file mode 100644
index f9f6224..0000000
--- a/ios/build/bots/chromium.mac/ios12-beta-simulator.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-  "comments": [
-    "Run tests on iOS12beta track on 64-bit iOS 12 simulators.",
-    "Note: Xcode 10 requires OSX 10.13.4 hence 'host os'",
-    "Note: This file exists only to support the trybot.",
-    "It should be kept in sync with the CI configuration in ../chromium.fyi/."
-  ],
-  "xcode build version": "10b61",
-  "gn_args": [
-    "goma_dir=\"$(goma_dir)\"",
-    "is_component_build=false",
-    "is_debug=true",
-    "symbol_level=1",
-    "target_cpu=\"x64\"",
-    "target_os=\"ios\"",
-    "use_goma=true"
-  ],
-  "tests": [
-  ]
-}
diff --git a/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc b/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
index 6a43bc0..c1a404d 100644
--- a/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
+++ b/ios/chrome/browser/dom_distiller/dom_distiller_service_factory.cc
@@ -12,7 +12,6 @@
 #include "components/dom_distiller/core/article_entry.h"
 #include "components/dom_distiller/core/distiller.h"
 #include "components/dom_distiller/core/dom_distiller_service.h"
-#include "components/dom_distiller/core/dom_distiller_store.h"
 #include "components/dom_distiller/ios/distiller_page_factory_ios.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/keyed_service/ios/browser_state_dependency_manager.h"
@@ -31,13 +30,11 @@
                                  public dom_distiller::DomDistillerService {
  public:
   DomDistillerKeyedService(
-      std::unique_ptr<dom_distiller::DomDistillerStoreInterface> store,
       std::unique_ptr<dom_distiller::DistillerFactory> distiller_factory,
       std::unique_ptr<dom_distiller::DistillerPageFactory>
           distiller_page_factory,
       std::unique_ptr<dom_distiller::DistilledPagePrefs> distilled_page_prefs)
-      : DomDistillerService(std::move(store),
-                            std::move(distiller_factory),
+      : DomDistillerService(std::move(distiller_factory),
                             std::move(distiller_page_factory),
                             std::move(distilled_page_prefs)) {}
 
@@ -90,7 +87,7 @@
           ios::ChromeBrowserState::FromBrowserState(context)->GetPrefs());
 
   return std::make_unique<DomDistillerKeyedService>(
-      nullptr, std::move(distiller_factory), std::move(distiller_page_factory),
+      std::move(distiller_factory), std::move(distiller_page_factory),
       std::move(distilled_page_prefs));
 }
 
diff --git a/ios/web/navigation/wk_based_navigation_manager_impl.mm b/ios/web/navigation/wk_based_navigation_manager_impl.mm
index 1e5cb1c..23745d8 100644
--- a/ios/web/navigation/wk_based_navigation_manager_impl.mm
+++ b/ios/web/navigation/wk_based_navigation_manager_impl.mm
@@ -1107,6 +1107,16 @@
     }
   }
 
+  // TODO(crbug.com/1003680) This seems to happen if a restored navigation fails
+  // provisionally before the NavigationContext associates with the original
+  // navigation. Rather than expose the internal placeholder to the UI and to
+  // URL-sensing components outside of //ios/web layer, set virtual URL to the
+  // placeholder original URL here.
+  if (wk_navigation_util::IsPlaceholderUrl(url)) {
+    new_item->SetVirtualURL(
+        wk_navigation_util::ExtractUrlFromPlaceholderUrl(url));
+  }
+
   SetNavigationItemInWKItem(wk_item, std::move(new_item));
   return GetNavigationItemFromWKItem(wk_item);
 }
diff --git a/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm b/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
index 4b3fd23..6bbaad20 100644
--- a/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
+++ b/ios/web/navigation/wk_based_navigation_manager_impl_unittest.mm
@@ -754,6 +754,19 @@
   EXPECT_EQ(url, item->GetURL());
 }
 
+// Tests that the virtual URL of a placeholder item is updated to the original
+// URL.
+TEST_F(WKBasedNavigationManagerTest, HideInternalPlaceholderUrl) {
+  GURL original_url = GURL("http://www.1.com?query=special%26chars");
+  GURL url = wk_navigation_util::CreatePlaceholderUrlForUrl(original_url);
+  NSString* url_spec = base::SysUTF8ToNSString(url.spec());
+  [mock_wk_list_ setCurrentURL:url_spec];
+  NavigationItem* item = manager_->GetItemAtIndex(0);
+  ASSERT_TRUE(item);
+  EXPECT_EQ(original_url, item->GetVirtualURL());
+  EXPECT_EQ(url, item->GetURL());
+}
+
 // Tests that all NavigationManager APIs return reasonable values in the Empty
 // Window Open Navigation edge case. See comments in header file for details.
 TEST_F(WKBasedNavigationManagerTest, EmptyWindowOpenNavigation) {
diff --git a/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json b/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json
index e51f5bd..d5c127d 100644
--- a/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json
+++ b/testing/scripts/representative_perf_test_data/representatives_frame_times_upper_limit.json
@@ -1,212 +1,196 @@
 {
   "win": {
-    "css_value_type_shadow": {
-      "avg": 51.69078,
-      "ci_095": 8.021214076
+    "balls_css_transition_2_properties": {
+      "avg": 107.983,
+      "ci_095": 100
     },
-    "yahoo_sports_2018": {
-      "avg": 17.91754,
-      "ci_095": 1.7831799
+    "many_planets_deep": {
+      "avg": 25.015,
+        "ci_095": 100
     },
-    "mix_10k": {
-      "avg": 29.339723,
-      "ci_095": 3.462980077
+    "web_animation_value_type_transform_complex": {
+      "avg": 42.981,
+        "ci_095": 100
     },
-    "microsoft_fish_ie_tank": {
-      "avg": 17.15968,
-      "ci_095": 2.448468729
+    "nvidia_vertex_buffer_object": {
+      "avg": 25.166,
+        "ci_095": 100
     },
-    "css_animations_staggered_infinite_iterations": {
-      "avg": 17.53428,
-      "ci_095": 1.15466265
+    "runway": {
+      "avg": 74.211,
+        "ci_095": 100
     },
-    "pinterest_2018": {
-      "avg": 17.17876,
-      "ci_095": 1.990684136
+    "css_animations_simultaneous_inline_style": {
+      "avg": 25.100,
+        "ci_095": 100
     },
-    "background_color_animation_with_gradient": {
-      "avg": 18.91196,
-      "ci_095": 5.3955209
+    "aquarium_20k": {
+      "avg": 113.156,
+        "ci_095": 100
     },
-    "motion_mark_canvas_fill_shapes": {
-      "avg": 35.30084,
-      "ci_095": 3.092656437
+    "canvas_to_blob": {
+      "avg": 39.239,
+        "ci_095": 100
     },
-    "particles": {
-      "avg": 16.683,
-      "ci_095": 0.4367177926
+    "cc_poster_circle": {
+      "avg": 25.111,
+        "ci_095": 100
     },
-    "second_batch_js_light": {
-      "avg": 19.81702,
-      "ci_095": 10.392606
+    "js_poster_circle": {
+      "avg": 25.016,
+        "ci_095": 100
     },
-    "microsoft_video_city": {
-      "avg": 17.49898,
-      "ci_095": 1.506935193
-    },
-    "infinite_scroll_root_fixed_n_layers_99": {
-      "avg": 16.80068,
-      "ci_095": 0.9999470106
-    },
-    "css_value_type_length_complex": {
-      "avg": 36.60576,
-      "ci_095": 3.783401913
-    },
-    "css_opacity_plus_n_layers_99": {
-      "avg": 16.681,
-      "ci_095": 0.49490275
-    },
-    "canvas_90000_pixels_per_second": {
-      "avg": 16.68387,
-      "ci_095": 2.985124255
-    },
-    "google_web_search_2018": {
-      "avg": 16.95106,
-      "ci_095": 1.387013433
+    "web_animations_staggered_triggering_page": {
+      "avg": 25.033,
+        "ci_095": 100
     },
     "aquarium": {
-      "avg": 17.68626,
-      "ci_095": 1.080606843
+      "avg": 25.014,
+        "ci_095": 100
+    },
+    "css_value_type_shadow": {
+      "avg": 31.638,
+        "ci_095": 100
+    },
+    "filter_terrain_svg": {
+      "avg": 41.496,
+        "ci_095": 100
+    },
+    "css_transitions_inline_style": {
+      "avg": 25.312,
+        "ci_095": 100
     }
   },
   "mac": {
-    "yuv_decoding": {
-      "avg": 92.465,
-      "ci_095": 36.599
+    "mix_blend_mode_animation_screen": {
+      "avg": 148.760,
+      "ci_095": 100
     },
-    "canvas_animation_no_clear": {
-      "avg": 18.323,
-      "ci_095": 1.239
+    "twitch_2018": {
+      "avg": 28.110,
+      "ci_095": 100
     },
-    "compositor_heavy_animation": {
-      "avg": 36.002,
-      "ci_095": 3.635
+    "balls_javascript_canvas": {
+      "avg": 40.563,
+      "ci_095": 100
     },
-    "web_animation_value_type_length_3d": {
-      "avg": 40.999,
-      "ci_095": 1.897
+    "transform_transitions_js_block": {
+      "avg": 25.090,
+      "ci_095": 100
     },
-    "stroke_shapes": {
-      "avg": 60.378,
-      "ci_095": 2.860
-    },
-    "wordpress_2018": {
-      "avg": 16.980,
-      "ci_095": 0.477
-    },
-    "web_animation_value_type_path": {
-      "avg": 33.060,
-      "ci_095": 2.225
-    },
-    "web_animations_many_keyframes": {
-      "avg": 20.543,
-      "ci_095": 1.602
-    },
-    "twitter_pinch_2018": {
-      "avg": 20.395,
-      "ci_095": 5.386
-    },
-    "web_animation_value_type_length_complex": {
-      "avg": 39.018,
-      "ci_095": 2.856
-    },
-    "smash_cat": {
-      "avg": 16.748,
-      "ci_095": 0.772
-    },
-    "cc_poster_circle": {
-      "avg": 16.697,
-      "ci_095": 0.166
-    },
-    "infinite_scroll_root_fixed_n_layers_99": {
-      "avg": 16.760,
-      "ci_095": 1.100
-    },
-    "amazon_pinch_2018": {
-      "avg": 21.250,
-      "ci_095": 7.103
-    },
-    "youtube_pinch_2018": {
-      "avg": 21.467,
-      "ci_095": 7.431
-    },
-    "earth": {
-      "avg": 16.687,
-      "ci_095": 0.284
-    },
-    "css_value_type_shadow": {
-      "avg": 76.775,
-      "ci_095": 16.809
-    },
-    "js_opacity_plus_n_layers_99": {
-      "avg": 16.725,
-      "ci_095": 0.200
+    "web_animations_staggered_infinite_iterations": {
+      "avg": 25.006,
+      "ci_095": 100
     },
     "fill_shapes": {
-      "avg": 43.377,
-      "ci_095": 3.220
+      "avg": 41.667,
+      "ci_095": 100
     },
-    "raf": {
-      "avg": 16.692,
-      "ci_095": 0.218
+    "css_value_type_shadow": {
+      "avg": 69.304,
+      "ci_095": 100
     },
-    "canvas_lines": {
-      "avg": 25.519,
-      "ci_095": 0.866
+    "animometer_webgl_attrib_arrays": {
+      "avg": 27.124,
+      "ci_095": 100
     },
-    "megi_dish": {
-      "avg": 114.515,
-      "ci_095": 41.168
+    "web_animation_value_type_transform_simple": {
+      "avg": 55.002,
+      "ci_095": 100
+    },
+    "canvas_05000_pixels_per_second": {
+      "avg": 24.997,
+      "ci_095": 100
+    },
+    "bouncing_clipped_rectangles": {
+      "avg": 592.146,
+      "ci_095": 100
+    },
+    "ie_chalkboard": {
+      "avg": 68.514,
+      "ci_095": 100
+    },
+    "new_tilings": {
+      "avg": 31.733,
+      "ci_095": 100
+    },
+    "chip_tune": {
+      "avg": 25.006,
+      "ci_095": 100
+    },
+    "extra_large_texture_uploads": {
+      "avg": 77.757,
+      "ci_095": 100
+    },
+    "css_value_type_filter": {
+      "avg": 92.894,
+      "ci_095": 100
     }
   },
   "android": {
-    "motionmark_html_css_bouncing_blend_circles_25": {
-      "avg": 48.35483,
-      "ci_095": 3.21386
+    "mix_blend_mode_animation_screen": {
+      "avg": 400.414,
+      "ci_095": 100
     },
-    "yahoo_news_desktop_gpu_raster_2018": {
-      "avg": 66.0,
-      "ci_095": 7.0
+    "twitch_2018": {
+      "avg": 34.498,
+      "ci_095": 100
     },
-    "microsoft_performance": {
-      "avg": 30.8767,
-      "ci_095": 3.40595
+    "balls_javascript_canvas": {
+      "avg": 276.974,
+      "ci_095": 100
     },
-    "geo_apis": {
-      "avg": 17,
-      "ci_095": 0.5
+    "transform_transitions_js_block": {
+      "avg": 25.179,
+      "ci_095": 100
     },
-    "kevs_3d": {
-      "avg": 46.79165,
-      "ci_095": 40.90851
+    "web_animations_staggered_infinite_iterations": {
+      "avg": 48.752,
+      "ci_095": 100
     },
-    "ie_pirate_mark": {
-      "avg": 27.96598,
-      "ci_095": 6.35233
+    "text_10000_pixels_per_second": {
+      "avg": 25.470,
+      "ci_095": 100
     },
-    "linkedin_mobile_pinch": {
-      "avg": 23.64056,
-      "ci_095": 10.45832
+    "motion_mark_canvas_fill_shapes": {
+      "avg": 243.536,
+      "ci_095": 100
     },
-    "text_constant_full_page_raster_10000_pixels_per_second": {
-      "avg": 16.68138,
-      "ci_095": 0.52503
+    "css_value_type_shadow": {
+      "avg": 591.356,
+      "ci_095": 100
     },
-    "canvas_10000_pixels_per_second": {
-      "avg": 16.76975,
-      "ci_095": 0.68845
+    "animometer_webgl_attrib_arrays": {
+      "avg": 337.719,
+      "ci_095": 300
     },
-    "canvas_20000_pixels_per_second": {
-      "avg": 16.77075,
-      "ci_095": 0.63189
+    "canvas_05000_pixels_per_second": {
+      "avg": 25.322,
+      "ci_095": 100
     },
-    "canvas_40000_pixels_per_second": {
-      "avg": 16.86242,
-      "ci_095": 0.73009
+    "bouncing_clipped_rectangles": {
+      "avg": 481.911,
+      "ci_095": 100
     },
-    "canvas_75000_pixels_per_second": {
-      "avg": 16.98984,
-      "ci_095": 0.88247
+    "ie_chalkboard": {
+      "avg": 217.131,
+      "ci_095": 100
+    },
+    "new_tilings": {
+      "avg": 25.052,
+      "ci_095": 100
+    },
+    "extra_large_texture_uploads": {
+      "avg": 452.959,
+      "ci_095": 100
+    },
+    "web_animation_value_type_transform_simple": {
+      "avg": 331.928,
+      "ci_095": 100
+    },
+    "css_value_type_filter": {
+      "avg": 909.449,
+      "ci_095": 100
     }
   }
 }
\ No newline at end of file
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 276614b..6523bc4 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -766,24 +766,6 @@
             ]
         }
     ],
-    "AutofillEnableToolbarStatusChip": [
-        {
-            "platforms": [
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "AutofillCreditCardUploadFeedback",
-                        "AutofillEnableToolbarStatusChip"
-                    ]
-                }
-            ]
-        }
-    ],
     "AutofillFieldMetadata": [
         {
             "platforms": [
@@ -1974,11 +1956,11 @@
     "DefaultPassthroughCommandDecoder": [
         {
             "platforms": [
-                "windows"
+                "linux"
             ],
             "experiments": [
                 {
-                    "name": "Enabled",
+                    "name": "Enabled_20191004",
                     "enable_features": [
                         "DefaultPassthroughCommandDecoder"
                     ]
@@ -2852,24 +2834,6 @@
             ]
         }
     ],
-    "GridLayoutForNtpShortcuts": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "GridLayoutForNtpShortcuts"
-                    ]
-                }
-            ]
-        }
-    ],
     "GwpAsanAndroid": [
         {
             "platforms": [
@@ -6701,11 +6665,7 @@
     "VizHitTest": [
         {
             "platforms": [
-                "android",
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
+                "chromeos"
             ],
             "experiments": [
                 {
diff --git a/third_party/blink/public/mojom/cache_storage/cache_storage.mojom b/third_party/blink/public/mojom/cache_storage/cache_storage.mojom
index ffac72e..da1c6bd 100644
--- a/third_party/blink/public/mojom/cache_storage/cache_storage.mojom
+++ b/third_party/blink/public/mojom/cache_storage/cache_storage.mojom
@@ -5,6 +5,7 @@
 module blink.mojom;
 
 import "mojo/public/mojom/base/read_only_buffer.mojom";
+import "third_party/blink/public/mojom/blob/blob.mojom";
 import "third_party/blink/public/mojom/fetch/fetch_api_response.mojom";
 import "third_party/blink/public/mojom/fetch/fetch_api_request.mojom";
 import "mojo/public/mojom/base/string16.mojom";
@@ -76,11 +77,20 @@
   pending_associated_remote<CacheStorageCache> cache;
 };
 
+// EagerResponse: A blob response where the source has eagerly started reading
+// the body into a DataPipe.
+struct EagerResponse {
+  blink.mojom.FetchAPIResponse response;
+  handle<data_pipe_consumer> pipe;
+  pending_receiver<BlobReaderClient> client_receiver;
+};
+
 // Result of Match for both interfaces CacheStorage and CacheStorageCache
 // method. |status| is only set if there is a failure.
 union MatchResult {
   CacheStorageError status;
   blink.mojom.FetchAPIResponse response;
+  EagerResponse eager_response;
 };
 
 // Result of MatchAll method |status| is only set if there is a failure.
@@ -100,8 +110,11 @@
 // defined on spec: https://w3c.github.io/ServiceWorker/#cache-interface
 interface CacheStorageCache {
   // Returns the first cached response that matches |request| according to
-  // options specified on |query_options|.
-  Match(blink.mojom.FetchAPIRequest request, CacheQueryOptions query_options, int64 trace_id)
+  // options specified on |query_options|.  |in_related_fetch_event| will be
+  // true if the operation was initiated within a FetchEvent handler with
+  // a matching request URL.
+  Match(blink.mojom.FetchAPIRequest request, CacheQueryOptions query_options,
+        bool in_related_fetch_event, int64 trace_id)
       => (MatchResult result);
 
   // Returns all cached responses that match |request| according to options
@@ -133,8 +146,10 @@
 
   // Returns the first cached response that matches |request| and
   // |match_options|. It can search on all caches if |cache_name| isn't provided
-  // on |match_options|.
-  Match(blink.mojom.FetchAPIRequest request, MultiCacheQueryOptions match_options, int64 trace_id)
+  // on |match_options|.  |in_related_fetch_event| will be true if the operation
+  // was initiated within a FetchEvent handler with a matching request URL.
+  Match(blink.mojom.FetchAPIRequest request, MultiCacheQueryOptions match_options,
+        bool in_related_fetch_event, int64 trace_id)
       => (MatchResult result);
 
   // Opens and returns a mojo interface to a cache, it creates if doesn't exist.
diff --git a/third_party/blink/public/mojom/fetch/fetch_api_response.mojom b/third_party/blink/public/mojom/fetch/fetch_api_response.mojom
index 34ea2a4..2d9be3f 100644
--- a/third_party/blink/public/mojom/fetch/fetch_api_response.mojom
+++ b/third_party/blink/public/mojom/fetch/fetch_api_response.mojom
@@ -60,9 +60,15 @@
   // set of headers that should be exposed.
   array<string> cors_exposed_header_names;
 
-  // Side data is used to pass the metadata of the response (eg. V8 code cache).
+  // Used to pass code cache for responses produced by cache_storage.  The code
+  // cache will be stored in the side data stream of the blob.
   SerializedBlob? side_data_blob;
 
+  // Used to pass code cache for a response that is being newly stored in
+  // cache_storage.  This blob is created from a memory buffer in the renderer
+  // and the code cache is contained in the primary data stream of the blob.
+  SerializedBlob? side_data_blob_for_cache_put;
+
   // In case this response had a Content-Security-Policy header, this is the
   // parsed CSP.
   network.mojom.ContentSecurityPolicy? content_security_policy;
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index 66e8a6d..2e0a8fae 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -196,6 +196,7 @@
   BLINK_PLATFORM_EXPORT static void EnableWebXRARModule(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRARDOMOverlay(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRAnchors(bool);
+  BLINK_PLATFORM_EXPORT static void EnableWebXrGamepadModule(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRHitTest(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRPlaneDetection(bool);
   BLINK_PLATFORM_EXPORT static void EnableXSLT(bool);
diff --git a/third_party/blink/renderer/core/css/style_engine.cc b/third_party/blink/renderer/core/css/style_engine.cc
index 78e31941..2ca537f6 100644
--- a/third_party/blink/renderer/core/css/style_engine.cc
+++ b/third_party/blink/renderer/core/css/style_engine.cc
@@ -63,6 +63,7 @@
 #include "third_party/blink/renderer/core/dom/text.h"
 #include "third_party/blink/renderer/core/frame/settings.h"
 #include "third_party/blink/renderer/core/html/html_body_element.h"
+#include "third_party/blink/renderer/core/html/html_html_element.h"
 #include "third_party/blink/renderer/core/html/html_iframe_element.h"
 #include "third_party/blink/renderer/core/html/html_link_element.h"
 #include "third_party/blink/renderer/core/html/html_slot_element.h"
@@ -1753,6 +1754,7 @@
     ancestor->ClearChildNeedsStyleRecalc();
   }
   style_recalc_root_.Clear();
+  PropagateWritingModeAndDirectionToHTMLRoot();
 }
 
 void StyleEngine::RebuildLayoutTree() {
@@ -1982,6 +1984,12 @@
          IsViewportStyleDirty();
 }
 
+void StyleEngine::PropagateWritingModeAndDirectionToHTMLRoot() {
+  if (HTMLHtmlElement* root_element =
+          DynamicTo<HTMLHtmlElement>(GetDocument().documentElement()))
+    root_element->PropagateWritingModeAndDirectionFromBody();
+}
+
 void StyleEngine::Trace(blink::Visitor* visitor) {
   visitor->Trace(document_);
   visitor->Trace(injected_user_style_sheets_);
diff --git a/third_party/blink/renderer/core/css/style_engine.h b/third_party/blink/renderer/core/css/style_engine.h
index 1580b590..200011e 100644
--- a/third_party/blink/renderer/core/css/style_engine.h
+++ b/third_party/blink/renderer/core/css/style_engine.h
@@ -463,6 +463,7 @@
   bool SupportsDarkColorScheme();
 
   void ViewportDefiningElementDidChange();
+  void PropagateWritingModeAndDirectionToHTMLRoot();
 
   Member<Document> document_;
   bool is_master_;
diff --git a/third_party/blink/renderer/core/css/style_engine_test.cc b/third_party/blink/renderer/core/css/style_engine_test.cc
index 1f4f17e..737f44d 100644
--- a/third_party/blink/renderer/core/css/style_engine_test.cc
+++ b/third_party/blink/renderer/core/css/style_engine_test.cc
@@ -2277,4 +2277,24 @@
   EXPECT_EQ(outer, GetStyleRecalcRoot());
 }
 
+TEST_F(StyleEngineTest, RecalcPropagatedWritingMode) {
+  GetDocument().body()->SetInlineStyleProperty(CSSPropertyID::kWritingMode,
+                                               "vertical-lr");
+
+  UpdateAllLifecyclePhases();
+
+  // Make sure that recalculating style for the root element does not trigger a
+  // visual diff that requires layout. That is, we take the body -> root
+  // propagation of writing-mode into account before setting ComputedStyle on
+  // the root LayoutObject.
+  GetDocument().documentElement()->SetInlineStyleProperty(
+      CSSPropertyID::kWritingMode, "horizontal-tb");
+
+  GetDocument().Lifecycle().AdvanceTo(DocumentLifecycle::kInStyleRecalc);
+  GetDocument().GetStyleEngine().RecalcStyle();
+
+  EXPECT_FALSE(GetStyleEngine().NeedsLayoutTreeRebuild());
+  EXPECT_FALSE(GetDocument().View()->NeedsLayout());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.cc b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
index 077c8c7..4ef3089 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.cc
@@ -118,7 +118,9 @@
     return;
   }
 
-  bool should_observe = IsLocked() && IsActivatable() && ConnectedToView();
+  bool should_observe = IsLocked() &&
+                        IsActivatable(DisplayLockActivationReason::kViewport) &&
+                        ConnectedToView();
   if (should_observe && !is_observed_) {
     document_->RegisterDisplayLockActivationObservation(element_);
   } else if (!should_observe && is_observed_) {
@@ -127,14 +129,16 @@
   is_observed_ = should_observe;
 }
 
-void DisplayLockContext::SetActivatable(bool activatable) {
+void DisplayLockContext::SetActivatable(unsigned char activatable_mask) {
   if (IsLocked()) {
-    // If we're locked, the activatable flag might change the activation
+    // If we're locked, the activatable mask might change the activation
     // blocking lock count. If we're not locked, the activation blocking lock
     // count will be updated when we changed the state.
-    state_.UpdateActivationBlockingCount(activatable_, activatable);
+    // Note that we record this only if we're blocking all activation. That is,
+    // the lock is considered activatable if any bit is set.
+    state_.UpdateActivationBlockingCount(activatable_mask_, activatable_mask);
   }
-  activatable_ = activatable;
+  activatable_mask_ = activatable_mask;
   UpdateActivationObservationIfNeeded();
 }
 
@@ -367,8 +371,10 @@
   // This is here for symmetry, but could be removed if necessary.
 }
 
-bool DisplayLockContext::IsActivatable() const {
-  return activatable_ || !IsLocked();
+bool DisplayLockContext::IsActivatable(
+    DisplayLockActivationReason reason) const {
+  return !IsLocked() ||
+         (activatable_mask_ & static_cast<unsigned char>(reason));
 }
 
 void DisplayLockContext::CommitForActivationWithSignal(
@@ -384,7 +390,7 @@
 
   DCHECK(element_);
   DCHECK(ConnectedToView());
-  DCHECK(ShouldCommitForActivation());
+  DCHECK(ShouldCommitForActivation(DisplayLockActivationReason::kAny));
   StartCommit();
   // Since setting the attribute might trigger a commit if we are still locked,
   // we set it after we start the commit.
@@ -392,8 +398,9 @@
     element_->setAttribute(html_names::kRendersubtreeAttr, "");
 }
 
-bool DisplayLockContext::ShouldCommitForActivation() const {
-  return IsActivatable() && IsLocked();
+bool DisplayLockContext::ShouldCommitForActivation(
+    DisplayLockActivationReason reason) const {
+  return IsActivatable(reason) && IsLocked();
 }
 
 void DisplayLockContext::DidAttachLayoutTree() {
@@ -655,7 +662,7 @@
       document_->View()->RegisterForLifecycleNotifications(this);
   }
 
-  if (!IsActivatable()) {
+  if (!IsActivatable(DisplayLockActivationReason::kAny)) {
     old_document.RemoveActivationBlockingDisplayLock();
     document_->AddActivationBlockingDisplayLock();
   }
@@ -859,7 +866,8 @@
         "LockedDisplayLock", this);
   }
 
-  bool was_activatable = context_->IsActivatable();
+  bool was_activatable =
+      context_->IsActivatable(DisplayLockActivationReason::kAny);
   bool was_locked = context_->IsLocked();
 
   state_ = new_state;
@@ -872,7 +880,9 @@
   if (!context_->document_)
     return *this;
 
-  UpdateActivationBlockingCount(was_activatable, context_->IsActivatable());
+  UpdateActivationBlockingCount(
+      was_activatable,
+      context_->IsActivatable(DisplayLockActivationReason::kAny));
 
   // Adjust the total number of locked display locks.
   auto& document = *context_->document_;
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context.h b/third_party/blink/renderer/core/display_lock/display_lock_context.h
index f429d05..0484345 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context.h
@@ -21,6 +21,25 @@
 class DisplayLockScopedLogger;
 
 enum class DisplayLockLifecycleTarget { kSelf, kChildren };
+enum class DisplayLockActivationReason {
+  // This represents activations triggered by intersection observer when the
+  // element intersects the viewport.
+  kViewport = 1 << 0,
+  // This represents activations triggered by script or user actions, such as
+  // find-in-page or scrollIntoView().
+  kUser = 1 << 1,
+  // This represents any activation, and should be result of all other flags
+  // combined.
+  kAny =
+      static_cast<unsigned char>(kViewport) | static_cast<unsigned char>(kUser)
+};
+
+// Instead of specifying an underlying type, which would propagate throughout
+// forward declarations, we static assert that the activation reasons enum is
+// small.
+static_assert(static_cast<int>(DisplayLockActivationReason::kAny) <
+                  std::numeric_limits<unsigned char>::max(),
+              "DisplayLockActivationReason is too large");
 
 class CORE_EXPORT DisplayLockContext final
     : public GarbageCollected<DisplayLockContext>,
@@ -93,7 +112,8 @@
   // ContextLifecycleObserver overrides.
   void ContextDestroyed(ExecutionContext*) override;
 
-  void SetActivatable(bool activatable);
+  // Set which reasons activate, as a mask of DisplayLockActivationReason enums.
+  void SetActivatable(unsigned char activatable_mask);
 
   // Acquire the lock, should only be called when unlocked.
   void StartAcquire();
@@ -119,8 +139,10 @@
   }
 
   // Returns true if the contents of the associated element should be visible
-  // from and activatable by find-in-page, tab order, anchor links, etc.
-  bool IsActivatable() const;
+  // from and activatable by a specified reason. Note that passing
+  // kAny will return true if the lock is activatable for any
+  // reason.
+  bool IsActivatable(DisplayLockActivationReason reason) const;
 
   // Trigger commit because of activation from tab order, url fragment,
   // find-in-page, scrolling, etc.
@@ -128,7 +150,7 @@
   // activated element.
   void CommitForActivationWithSignal(Element* activated_element);
 
-  bool ShouldCommitForActivation() const;
+  bool ShouldCommitForActivation(DisplayLockActivationReason reason) const;
 
   // Returns true if this lock is locked. Note from the outside perspective, the
   // lock is locked any time the state is not kUnlocked or kCommitting.
@@ -304,7 +326,6 @@
   StateChangeHelper state_;
 
   bool update_forced_ = false;
-  bool activatable_ = false;
 
   StyleType blocked_style_traversal_type_ = kStyleUpdateNotRequired;
   // Signifies whether we've blocked a layout tree reattachment on |element_|'s
@@ -325,6 +346,9 @@
   // Tracks whether the element associated with this lock is being tracked by a
   // document level intersection observer.
   bool is_observed_ = false;
+
+  unsigned char activatable_mask_ =
+      static_cast<unsigned char>(DisplayLockActivationReason::kAny);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
index 1084802..371d8a0 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_context_test.cc
@@ -131,8 +131,8 @@
                    bool update_lifecycle = true) {
     StringBuilder value;
     value.Append("invisible");
-    if (activatable)
-      value.Append(" activatable");
+    if (!activatable)
+      value.Append(" skip-activation");
     element.setAttribute(html_names::kRendersubtreeAttr,
                          value.ToAtomicString());
     if (update_lifecycle)
@@ -706,8 +706,10 @@
   auto* host = GetDocument().getElementById("shadowHost");
   auto* slotted = GetDocument().getElementById("slotted");
 
-  ASSERT_FALSE(host->DisplayLockPreventsActivation());
-  ASSERT_FALSE(slotted->DisplayLockPreventsActivation());
+  ASSERT_FALSE(
+      host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
+  ASSERT_FALSE(slotted->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
 
   ShadowRoot& shadow_root =
       host->AttachShadowRootInternal(ShadowRootType::kOpen);
@@ -717,17 +719,23 @@
   UpdateAllLifecyclePhasesForTest();
 
   auto* container = shadow_root.getElementById("container");
-  EXPECT_FALSE(host->DisplayLockPreventsActivation());
-  EXPECT_FALSE(container->DisplayLockPreventsActivation());
-  EXPECT_FALSE(slotted->DisplayLockPreventsActivation());
+  EXPECT_FALSE(
+      host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
+  EXPECT_FALSE(container->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
+  EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
 
   LockElement(*container, false, false);
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 1);
-  EXPECT_FALSE(host->DisplayLockPreventsActivation());
-  EXPECT_TRUE(container->DisplayLockPreventsActivation());
-  EXPECT_TRUE(slotted->DisplayLockPreventsActivation());
+  EXPECT_FALSE(
+      host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
+  EXPECT_TRUE(container->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
+  EXPECT_TRUE(slotted->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
 
   // Ensure that we resolve the acquire callback, thus finishing the acquire
   // step.
@@ -737,17 +745,23 @@
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 0);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
-  EXPECT_FALSE(host->DisplayLockPreventsActivation());
-  EXPECT_FALSE(container->DisplayLockPreventsActivation());
-  EXPECT_FALSE(slotted->DisplayLockPreventsActivation());
+  EXPECT_FALSE(
+      host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
+  EXPECT_FALSE(container->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
+  EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
 
   UpdateAllLifecyclePhasesForTest();
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 0);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
-  EXPECT_FALSE(host->DisplayLockPreventsActivation());
-  EXPECT_FALSE(container->DisplayLockPreventsActivation());
-  EXPECT_FALSE(slotted->DisplayLockPreventsActivation());
+  EXPECT_FALSE(
+      host->DisplayLockPreventsActivation(DisplayLockActivationReason::kAny));
+  EXPECT_FALSE(container->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
+  EXPECT_FALSE(slotted->DisplayLockPreventsActivation(
+      DisplayLockActivationReason::kAny));
 }
 
 TEST_F(DisplayLockContextTest,
@@ -884,35 +898,41 @@
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
-  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable());
+  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
+      DisplayLockActivationReason::kAny));
 
   LockElement(*non_activatable, false);
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 2);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 1);
-  EXPECT_FALSE(non_activatable->GetDisplayLockContext()->IsActivatable());
+  EXPECT_FALSE(non_activatable->GetDisplayLockContext()->IsActivatable(
+      DisplayLockActivationReason::kAny));
 
   // Now commit the lock for |non_ctivatable|.
   CommitElement(*non_activatable);
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
-  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable());
-  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable());
+  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
+      DisplayLockActivationReason::kAny));
+  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
+      DisplayLockActivationReason::kAny));
 
   // Re-acquire the lock for |activatable|, but without the activatable flag.
   LockElement(*activatable, false);
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 1);
-  EXPECT_FALSE(activatable->GetDisplayLockContext()->IsActivatable());
+  EXPECT_FALSE(activatable->GetDisplayLockContext()->IsActivatable(
+      DisplayLockActivationReason::kAny));
 
   // Re-acquire the lock for |activatable| again with the activatable flag.
   LockElement(*activatable, true);
 
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 1);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
-  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable());
+  EXPECT_TRUE(activatable->GetDisplayLockContext()->IsActivatable(
+      DisplayLockActivationReason::kAny));
 }
 
 TEST_F(DisplayLockContextTest, ElementInTemplate) {
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
index 763e9ea..88e3fa6 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.cc
@@ -28,6 +28,33 @@
   return child->GetDocument().GetFrame()->OwnerLayoutObject()->GetNode();
 }
 
+bool UpdateStyleAndLayoutForRangeIfNeeded(const EphemeralRangeInFlatTree& range,
+                                          DisplayLockActivationReason reason) {
+  if (range.IsNull() || range.IsCollapsed())
+    return false;
+  if (!RuntimeEnabledFeatures::DisplayLockingEnabled(&range.GetDocument()) ||
+      range.GetDocument().LockedDisplayLockCount() ==
+          range.GetDocument().ActivationBlockingDisplayLockCount())
+    return false;
+  Vector<DisplayLockContext::ScopedForcedUpdate> scoped_forced_update_list_;
+  for (Node& node : range.Nodes()) {
+    for (Element* locked_activatable_ancestor :
+         DisplayLockUtilities::ActivatableLockedInclusiveAncestors(node,
+                                                                   reason)) {
+      DCHECK(locked_activatable_ancestor->GetDisplayLockContext());
+      DCHECK(locked_activatable_ancestor->GetDisplayLockContext()->IsLocked());
+      if (locked_activatable_ancestor->GetDisplayLockContext()->UpdateForced())
+        break;
+      scoped_forced_update_list_.push_back(
+          locked_activatable_ancestor->GetDisplayLockContext()
+              ->GetScopedForcedUpdate());
+    }
+  }
+  if (!scoped_forced_update_list_.IsEmpty())
+    range.GetDocument().UpdateStyleAndLayout();
+  return !scoped_forced_update_list_.IsEmpty();
+}
+
 }  // namespace
 
 bool DisplayLockUtilities::ActivateFindInPageMatchRangeIfNeeded(
@@ -49,7 +76,8 @@
   DCHECK(enclosing_block);
   DCHECK_EQ(enclosing_block,
             EnclosingBlock(range.EndPosition(), kCannotCrossEditingBoundary));
-  return enclosing_block->ActivateDisplayLockIfNeeded();
+  return enclosing_block->ActivateDisplayLockIfNeeded(
+      DisplayLockActivationReason::kUser);
 }
 
 bool DisplayLockUtilities::ActivateSelectionRangeIfNeeded(
@@ -60,7 +88,8 @@
       range.GetDocument().LockedDisplayLockCount() ==
           range.GetDocument().ActivationBlockingDisplayLockCount())
     return false;
-  UpdateStyleAndLayoutForRangeIfNeeded(range);
+  UpdateStyleAndLayoutForRangeIfNeeded(range,
+                                       DisplayLockActivationReason::kUser);
   HeapHashSet<Member<Element>> elements_to_activate;
   for (Node& node : range.Nodes()) {
     DCHECK(!node.GetDocument().NeedsLayoutTreeUpdateForNode(node));
@@ -70,39 +99,16 @@
     if (auto* nearest_locked_ancestor = NearestLockedExclusiveAncestor(node))
       elements_to_activate.insert(nearest_locked_ancestor);
   }
-  for (Element* element : elements_to_activate)
-    element->ActivateDisplayLockIfNeeded();
+  for (Element* element : elements_to_activate) {
+    element->ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kUser);
+  }
   return !elements_to_activate.IsEmpty();
 }
 
-bool DisplayLockUtilities::UpdateStyleAndLayoutForRangeIfNeeded(
-    const EphemeralRangeInFlatTree& range) {
-  if (range.IsNull() || range.IsCollapsed())
-    return false;
-  if (!RuntimeEnabledFeatures::DisplayLockingEnabled(&range.GetDocument()) ||
-      range.GetDocument().LockedDisplayLockCount() ==
-          range.GetDocument().ActivationBlockingDisplayLockCount())
-    return false;
-  Vector<DisplayLockContext::ScopedForcedUpdate> scoped_forced_update_list_;
-  for (Node& node : range.Nodes()) {
-    for (Element* locked_activatable_ancestor :
-         ActivatableLockedInclusiveAncestors(node)) {
-      DCHECK(locked_activatable_ancestor->GetDisplayLockContext());
-      DCHECK(locked_activatable_ancestor->GetDisplayLockContext()->IsLocked());
-      if (locked_activatable_ancestor->GetDisplayLockContext()->UpdateForced())
-        break;
-      scoped_forced_update_list_.push_back(
-          locked_activatable_ancestor->GetDisplayLockContext()
-              ->GetScopedForcedUpdate());
-    }
-  }
-  if (!scoped_forced_update_list_.IsEmpty())
-    range.GetDocument().UpdateStyleAndLayout();
-  return !scoped_forced_update_list_.IsEmpty();
-}
-
 const HeapVector<Member<Element>>
-DisplayLockUtilities::ActivatableLockedInclusiveAncestors(const Node& node) {
+DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+    const Node& node,
+    DisplayLockActivationReason reason) {
   HeapVector<Member<Element>> elements_to_activate;
   const_cast<Node*>(&node)->UpdateDistributionForFlatTreeTraversal();
   if (!RuntimeEnabledFeatures::DisplayLockingEnabled(
@@ -118,7 +124,7 @@
     if (auto* context = ancestor_element->GetDisplayLockContext()) {
       if (!context->IsLocked())
         continue;
-      if (!context->IsActivatable()) {
+      if (!context->IsActivatable(reason)) {
         // If we find a non-activatable locked ancestor, then we shouldn't
         // activate anything.
         elements_to_activate.clear();
@@ -300,8 +306,10 @@
 
   for (auto* element = NearestLockedExclusiveAncestor(node); element;
        element = NearestLockedExclusiveAncestor(*element)) {
-    if (!element->GetDisplayLockContext()->IsActivatable())
+    if (!element->GetDisplayLockContext()->IsActivatable(
+            DisplayLockActivationReason::kAny)) {
       return true;
+    }
   }
   return false;
 }
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
index ca23fa3f..b9b4bf33 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities.h
@@ -44,17 +44,13 @@
   static bool ActivateSelectionRangeIfNeeded(
       const EphemeralRangeInFlatTree& range);
 
-  // Updates style for all locked nodes in |range|. Returns true if there's at
-  // least one locked node encountered.
-  static bool UpdateStyleAndLayoutForRangeIfNeeded(
-      const EphemeralRangeInFlatTree& range);
-
   // Returns activatable-locked inclusive ancestors of |node|.
   // Note that this function will return an empty list if |node| is inside a
   // non-activatable locked subtree (e.g. at least one ancestor is not
   // activatable-locked).
   static const HeapVector<Member<Element>> ActivatableLockedInclusiveAncestors(
-      const Node& node);
+      const Node& node,
+      DisplayLockActivationReason reason);
 
   // Returns the nearest inclusive ancestor of |node| that is display locked.
   static const Element* NearestLockedInclusiveAncestor(const Node& node);
diff --git a/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc b/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
index d849394..154a07eb5 100644
--- a/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
+++ b/third_party/blink/renderer/core/display_lock/display_lock_utilities_test.cc
@@ -25,8 +25,8 @@
                    bool update_lifecycle = true) {
     StringBuilder value;
     value.Append("invisible");
-    if (activatable)
-      value.Append(" activatable");
+    if (!activatable)
+      value.Append(" skip-activation");
     element.setAttribute(html_names::kRendersubtreeAttr,
                          value.ToAtomicString());
     if (update_lifecycle)
@@ -73,27 +73,32 @@
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
   // Querying from every element gives |outer|.
   HeapVector<Member<Element>> result_for_outer =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(outer);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          outer, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_outer.size(), 1u);
   EXPECT_EQ(result_for_outer.at(0), outer);
 
   HeapVector<Member<Element>> result_for_inner_a =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_a);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          inner_a, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_inner_a.size(), 1u);
   EXPECT_EQ(result_for_inner_a.at(0), outer);
 
   HeapVector<Member<Element>> result_for_innermost =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(innermost);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          innermost, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_innermost.size(), 1u);
   EXPECT_EQ(result_for_innermost.at(0), outer);
 
   HeapVector<Member<Element>> result_for_inner_b =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_b);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          inner_b, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_inner_b.size(), 1u);
   EXPECT_EQ(result_for_inner_b.at(0), outer);
 
   HeapVector<Member<Element>> result_for_shadow_div =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(shadow_div);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          shadow_div, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_shadow_div.size(), 1u);
   EXPECT_EQ(result_for_shadow_div.at(0), outer);
 
@@ -102,29 +107,33 @@
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 2);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
 
-  result_for_outer =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(outer);
+  result_for_outer = DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+      outer, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_outer.size(), 1u);
   EXPECT_EQ(result_for_outer.at(0), outer);
 
   result_for_inner_a =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_a);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          inner_a, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_inner_a.size(), 1u);
   EXPECT_EQ(result_for_inner_a.at(0), outer);
 
   result_for_innermost =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(innermost);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          innermost, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_innermost.size(), 2u);
   EXPECT_EQ(result_for_innermost.at(0), innermost);
   EXPECT_EQ(result_for_innermost.at(1), outer);
 
   result_for_inner_b =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_b);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          inner_b, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_inner_b.size(), 1u);
   EXPECT_EQ(result_for_inner_b.at(0), outer);
 
   result_for_shadow_div =
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(shadow_div);
+      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+          shadow_div, DisplayLockActivationReason::kAny);
   EXPECT_EQ(result_for_shadow_div.size(), 1u);
   EXPECT_EQ(result_for_shadow_div.at(0), outer);
 
@@ -134,22 +143,26 @@
   EXPECT_EQ(GetDocument().LockedDisplayLockCount(), 0);
   EXPECT_EQ(GetDocument().ActivationBlockingDisplayLockCount(), 0);
 
-  EXPECT_EQ(
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(outer).size(),
-      0u);
-  EXPECT_EQ(
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_a).size(),
-      0u);
-  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(innermost)
+  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+                outer, DisplayLockActivationReason::kAny)
                 .size(),
             0u);
-  EXPECT_EQ(
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(inner_b).size(),
-      0u);
-  EXPECT_EQ(
-      DisplayLockUtilities::ActivatableLockedInclusiveAncestors(shadow_div)
-          .size(),
-      0u);
+  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+                inner_a, DisplayLockActivationReason::kAny)
+                .size(),
+            0u);
+  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+                innermost, DisplayLockActivationReason::kAny)
+                .size(),
+            0u);
+  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+                inner_b, DisplayLockActivationReason::kAny)
+                .size(),
+            0u);
+  EXPECT_EQ(DisplayLockUtilities::ActivatableLockedInclusiveAncestors(
+                shadow_div, DisplayLockActivationReason::kAny)
+                .size(),
+            0u);
 }
 
 TEST_F(DisplayLockUtilitiesTest, LockedSubtreeCrossingFrames) {
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 26a4f29..5a62770d 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -8523,7 +8523,8 @@
     if (entry->isIntersecting()) {
       auto* context = entry->target()->GetDisplayLockContext();
       DCHECK(context);
-      DCHECK(context->ShouldCommitForActivation());
+      DCHECK(context->ShouldCommitForActivation(
+          DisplayLockActivationReason::kViewport));
       context->CommitForActivationWithSignal(entry->target());
     }
   }
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 8e391cf..6f03eb1f 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -121,6 +121,7 @@
 #include "third_party/blink/renderer/core/html/html_element.h"
 #include "third_party/blink/renderer/core/html/html_frame_element_base.h"
 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
+#include "third_party/blink/renderer/core/html/html_html_element.h"
 #include "third_party/blink/renderer/core/html/html_plugin_element.h"
 #include "third_party/blink/renderer/core/html/html_slot_element.h"
 #include "third_party/blink/renderer/core/html/html_table_rows_collection.h"
@@ -1054,7 +1055,7 @@
 }
 
 void Element::scrollIntoViewWithOptions(const ScrollIntoViewOptions* options) {
-  ActivateDisplayLockIfNeeded();
+  ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kUser);
   GetDocument().EnsurePaintLocationDataValidForNode(this);
   ScrollIntoViewNoVisualUpdate(options);
 }
@@ -1064,7 +1065,7 @@
   if (!GetLayoutObject() || !GetDocument().GetPage())
     return;
 
-  if (DisplayLockPreventsActivation())
+  if (DisplayLockPreventsActivation(DisplayLockActivationReason::kUser))
     return;
 
   ScrollBehavior behavior = (options->behavior() == "smooth")
@@ -2362,8 +2363,18 @@
       SetNeedsStyleRecalc(kLocalStyleChange,
                           StyleChangeReasonForTracing::FromAttribute(name));
       SpaceSplitString tokens(params.new_value.LowerASCII());
-      const bool should_be_activatable = tokens.Contains("activatable");
-      EnsureDisplayLockContext().SetActivatable(should_be_activatable);
+      unsigned char activation_mask =
+          static_cast<unsigned char>(DisplayLockActivationReason::kAny);
+
+      // Figure out the activation mask.
+      if (tokens.Contains("skip-activation"))
+        activation_mask = 0;
+      if (tokens.Contains("skip-viewport-activation")) {
+        activation_mask &=
+            ~static_cast<unsigned char>(DisplayLockActivationReason::kViewport);
+      }
+
+      EnsureDisplayLockContext().SetActivatable(activation_mask);
       const bool should_be_invisible = tokens.Contains("invisible");
       if (should_be_invisible) {
         if (!GetDisplayLockContext()->IsLocked())
@@ -3206,9 +3217,14 @@
 
   if (LayoutObject* layout_object = GetLayoutObject()) {
     DCHECK(new_style);
-    auto* this_element = DynamicTo<PseudoElement>(this);
-    if (this_element && new_style->Display() == EDisplay::kContents) {
-      new_style = this_element->LayoutStyleForDisplayContents(*new_style);
+    scoped_refptr<const ComputedStyle> layout_style(std::move(new_style));
+    if (auto* pseudo_element = DynamicTo<PseudoElement>(this)) {
+      if (layout_style->Display() == EDisplay::kContents) {
+        layout_style =
+            pseudo_element->LayoutStyleForDisplayContents(*layout_style);
+      }
+    } else if (auto* html_element = DynamicTo<HTMLHtmlElement>(this)) {
+      layout_style = html_element->LayoutStyleForElement(layout_style);
     }
     // kEqual means that the computed style didn't change, but there are
     // additional flags in ComputedStyle which may have changed. For instance,
@@ -3219,7 +3235,7 @@
         diff == ComputedStyle::Difference::kEqual
             ? LayoutObject::ApplyStyleChanges::kNo
             : LayoutObject::ApplyStyleChanges::kYes;
-    layout_object->SetStyle(new_style.get(), apply_changes);
+    layout_object->SetStyle(layout_style.get(), apply_changes);
   }
   return child_change;
 }
@@ -4025,7 +4041,7 @@
       return;
     }
   }
-  ActivateDisplayLockIfNeeded();
+  ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kUser);
   DispatchActivateInvisibleEventIfNeeded();
   if (IsInsideInvisibleSubtree()) {
     // The element stays invisible because the default event action is
@@ -4179,7 +4195,7 @@
          ((SupportsFocus() && tabIndex() >= 0) ||
           (RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
            IsScrollableNode(this))) &&
-         !DisplayLockPreventsActivation();
+         !DisplayLockPreventsActivation(DisplayLockActivationReason::kUser);
 }
 
 bool Element::IsMouseFocusable() const {
@@ -4188,7 +4204,7 @@
   DCHECK(!GetDocument().IsActive() ||
          !GetDocument().NeedsLayoutTreeUpdateForNode(*this));
   return isConnected() && !IsInert() && IsFocusableStyle() && SupportsFocus() &&
-         !DisplayLockPreventsActivation();
+         !DisplayLockPreventsActivation(DisplayLockActivationReason::kUser);
 }
 
 bool Element::IsAutofocusable() const {
@@ -4198,7 +4214,7 @@
          FastHasAttribute(html_names::kAutofocusAttr);
 }
 
-bool Element::ActivateDisplayLockIfNeeded() {
+bool Element::ActivateDisplayLockIfNeeded(DisplayLockActivationReason reason) {
   if (!RuntimeEnabledFeatures::DisplayLockingEnabled(GetExecutionContext()) ||
       GetDocument().LockedDisplayLockCount() ==
           GetDocument().ActivationBlockingDisplayLockCount())
@@ -4211,8 +4227,9 @@
     if (!ancestor_element)
       continue;
     if (auto* context = ancestor_element->GetDisplayLockContext()) {
-      // If any of the ancestors is not activatable, we can't activate.
-      if (!context->IsActivatable())
+      // If any of the ancestors is not activatable for the given reason, we
+      // can't activate.
+      if (!context->IsActivatable(reason))
         return false;
       activatable_targets.push_back(std::make_pair(
           ancestor_element, &ancestor.GetTreeScope().Retarget(*this)));
@@ -4224,7 +4241,7 @@
     // Dispatch event on activatable ancestor (target.first), with
     // the retargeted element (target.second) as the |activatedElement|.
     if (auto* context = target.first->GetDisplayLockContext()) {
-      if (context->ShouldCommitForActivation()) {
+      if (context->ShouldCommitForActivation(reason)) {
         activated = true;
         context->CommitForActivationWithSignal(target.second);
       }
@@ -4233,7 +4250,8 @@
   return activated;
 }
 
-bool Element::DisplayLockPreventsActivation() const {
+bool Element::DisplayLockPreventsActivation(
+    DisplayLockActivationReason reason) const {
   if (!RuntimeEnabledFeatures::DisplayLockingEnabled(GetExecutionContext()))
     return false;
 
@@ -4252,7 +4270,7 @@
     if (!current_element)
       continue;
     if (auto* context = current_element->GetDisplayLockContext()) {
-      if (!context->IsActivatable())
+      if (!context->IsActivatable(reason))
         return true;
     }
   }
diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h
index da7c8f1..d73f2f7 100644
--- a/third_party/blink/renderer/core/dom/element.h
+++ b/third_party/blink/renderer/core/dom/element.h
@@ -95,6 +95,7 @@
 
 enum class CSSPropertyID;
 enum class CSSValueID;
+enum class DisplayLockActivationReason;
 enum class DisplayLockLifecycleTarget;
 
 using ScrollOffset = FloatSize;
@@ -935,9 +936,10 @@
 
   bool StyleRecalcBlockedByDisplayLock(DisplayLockLifecycleTarget) const;
 
-  // Activates all activatable locked ancestors for this element. Return true if
-  // we activated at least one previously locked element.
-  bool ActivateDisplayLockIfNeeded();
+  // Activates all activatable (for a given reason) locked ancestors for this
+  // element. Return true if we activated at least one previously locked
+  // element.
+  bool ActivateDisplayLockIfNeeded(DisplayLockActivationReason reason);
 
   virtual void SetActive(bool active);
   virtual void SetHovered(bool hovered);
@@ -1149,7 +1151,7 @@
   void DetachAttrNodeFromElementWithValue(Attr*, const AtomicString& value);
   void DetachAttrNodeAtIndex(Attr*, wtf_size_t index);
 
-  bool DisplayLockPreventsActivation() const;
+  bool DisplayLockPreventsActivation(DisplayLockActivationReason reason) const;
   FRIEND_TEST_ALL_PREFIXES(DisplayLockContextTest,
                            DisplayLockPreventsActivation);
 
diff --git a/third_party/blink/renderer/core/dom/node.cc b/third_party/blink/renderer/core/dom/node.cc
index a4f6e78..c7bebcbd 100644
--- a/third_party/blink/renderer/core/dom/node.cc
+++ b/third_party/blink/renderer/core/dom/node.cc
@@ -2783,7 +2783,7 @@
                                   SimulatedClickMouseEventOptions event_options,
                                   SimulatedClickCreationScope scope) {
   if (auto* element = IsElementNode() ? ToElement(this) : parentElement())
-    element->ActivateDisplayLockIfNeeded();
+    element->ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kUser);
   EventDispatcher::DispatchSimulatedClick(*this, underlying_event,
                                           event_options, scope);
 }
diff --git a/third_party/blink/renderer/core/editing/finder/find_buffer.cc b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
index a0948ff..b04f994 100644
--- a/third_party/blink/renderer/core/editing/finder/find_buffer.cc
+++ b/third_party/blink/renderer/core/editing/finder/find_buffer.cc
@@ -110,7 +110,8 @@
          IsHTMLStyleElement(*element) || IsHTMLScriptElement(*element) ||
          IsHTMLVideoElement(*element) || IsA<HTMLAudioElement>(*element) ||
          (element->GetDisplayLockContext() &&
-          !element->GetDisplayLockContext()->IsActivatable());
+          !element->GetDisplayLockContext()->IsActivatable(
+              DisplayLockActivationReason::kUser));
 }
 
 Node* GetNonSearchableAncestor(const Node& node) {
@@ -234,7 +235,7 @@
 
 bool FindBuffer::PushScopedForcedUpdateIfNeeded(const Element& element) {
   if (auto* context = element.GetDisplayLockContext()) {
-    DCHECK(context->IsActivatable());
+    DCHECK(context->IsActivatable(DisplayLockActivationReason::kUser));
     scoped_forced_update_list_.push_back(context->GetScopedForcedUpdate());
     return true;
   }
diff --git a/third_party/blink/renderer/core/editing/iterators/text_iterator.cc b/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
index 62879e2..490c9fd 100644
--- a/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
+++ b/third_party/blink/renderer/core/editing/iterators/text_iterator.cc
@@ -179,8 +179,9 @@
     return false;
 
   if (auto* element = DynamicTo<Element>(node)) {
-    if (auto* context = element->GetDisplayLockContext())
-      return context->IsActivatable();
+    if (auto* context = element->GetDisplayLockContext()) {
+      return context->IsActivatable(DisplayLockActivationReason::kUser);
+    }
   }
   return true;
 }
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 99413a1..cc15530 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -564,6 +564,13 @@
       if (!MainFrameImpl() || !MainFrameImpl()->GetFrameView())
         break;
 
+      if (event.GetType() == WebInputEvent::kGestureLongTap &&
+          !MainFrameImpl()
+               ->GetFrame()
+               ->GetEventHandler()
+               .LongTapShouldInvokeContextMenu())
+        break;
+
       AsView().page->GetContextMenuController().ClearContextMenu();
       {
         ContextMenuAllowedScope scope;
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index 84ea34bd..234e079 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -425,6 +425,8 @@
   FRIEND_TEST_ALL_PREFIXES(WebFrameTest,
                            DivScrollIntoEditableTestWithDeviceScaleFactor);
   FRIEND_TEST_ALL_PREFIXES(WebViewTest, SetBaseBackgroundColorBeforeMainFrame);
+  FRIEND_TEST_ALL_PREFIXES(WebViewTest, LongPressImage);
+  FRIEND_TEST_ALL_PREFIXES(WebViewTest, LongPressImageAndThenLongTapImage);
   friend class frame_test_helpers::WebViewHelper;
   friend class SimCompositor;
   friend class WebView;  // So WebView::Create can call our constructor
diff --git a/third_party/blink/renderer/core/exported/web_view_test.cc b/third_party/blink/renderer/core/exported/web_view_test.cc
index 1a0720bd..3359f86 100644
--- a/third_party/blink/renderer/core/exported/web_view_test.cc
+++ b/third_party/blink/renderer/core/exported/web_view_test.cc
@@ -108,6 +108,7 @@
 #include "third_party/blink/renderer/core/loader/frame_load_request.h"
 #include "third_party/blink/renderer/core/loader/interactive_detector.h"
 #include "third_party/blink/renderer/core/page/chrome_client.h"
+#include "third_party/blink/renderer/core/page/context_menu_controller.h"
 #include "third_party/blink/renderer/core/page/focus_controller.h"
 #include "third_party/blink/renderer/core/page/page.h"
 #include "third_party/blink/renderer/core/page/page_hidden_state.h"
@@ -2775,6 +2776,10 @@
   EXPECT_EQ(WebInputEventResult::kHandledSystem,
             web_view->MainFrameWidget()->HandleInputEvent(
                 WebCoalescedInputEvent(event)));
+  EXPECT_TRUE(
+      web_view->AsView()
+          .page->GetContextMenuController()
+          .ContextMenuNodeForFrame(web_view->MainFrameImpl()->GetFrame()));
 }
 
 TEST_F(WebViewTest, LongPressVideo) {
@@ -5781,4 +5786,44 @@
   EXPECT_TRUE(document->GetLayoutView()->ShouldDoFullPaintInvalidation());
 }
 
+// Regression test for https://crbug.com/1012068
+TEST_F(WebViewTest, LongPressImageAndThenLongTapImage) {
+  RegisterMockedHttpURLLoad("long_press_image.html");
+
+  WebViewImpl* web_view =
+      web_view_helper_.InitializeAndLoad(base_url_ + "long_press_image.html");
+  web_view->SettingsImpl()->SetAlwaysShowContextMenuOnTouch(false);
+  web_view->MainFrameWidget()->Resize(WebSize(500, 300));
+  UpdateAllLifecyclePhases();
+  RunPendingTasks();
+
+  WebGestureEvent event(WebInputEvent::kGestureLongPress,
+                        WebInputEvent::kNoModifiers,
+                        WebInputEvent::GetStaticTimeStampForTests(),
+                        WebGestureDevice::kTouchscreen);
+  event.SetPositionInWidget(WebFloatPoint(10, 10));
+
+  EXPECT_EQ(WebInputEventResult::kHandledSystem,
+            web_view->MainFrameWidget()->HandleInputEvent(
+                WebCoalescedInputEvent(event)));
+  EXPECT_TRUE(
+      web_view->AsView()
+          .page->GetContextMenuController()
+          .ContextMenuNodeForFrame(web_view->MainFrameImpl()->GetFrame()));
+
+  WebGestureEvent tap_event(WebInputEvent::kGestureLongTap,
+                            WebInputEvent::kNoModifiers,
+                            WebInputEvent::GetStaticTimeStampForTests(),
+                            WebGestureDevice::kTouchscreen);
+  tap_event.SetPositionInWidget(WebFloatPoint(10, 10));
+
+  EXPECT_EQ(WebInputEventResult::kNotHandled,
+            web_view->MainFrameWidget()->HandleInputEvent(
+                WebCoalescedInputEvent(tap_event)));
+  EXPECT_TRUE(
+      web_view->AsView()
+          .page->GetContextMenuController()
+          .ContextMenuNodeForFrame(web_view->MainFrameImpl()->GetFrame()));
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/fetch/fetch_response_data.cc b/third_party/blink/renderer/core/fetch/fetch_response_data.cc
index e8dfb169..18dbcd3 100644
--- a/third_party/blink/renderer/core/fetch/fetch_response_data.cc
+++ b/third_party/blink/renderer/core/fetch/fetch_response_data.cc
@@ -256,6 +256,7 @@
   response->cache_storage_cache_name = cache_storage_cache_name_;
   response->cors_exposed_header_names =
       HeaderSetToVector(cors_exposed_header_names_);
+  response->side_data_blob = side_data_blob_;
   for (const auto& header : HeaderList()->List())
     response->headers.insert(header.first, header.second);
 
diff --git a/third_party/blink/renderer/core/fetch/fetch_response_data.h b/third_party/blink/renderer/core/fetch/fetch_response_data.h
index 20910a9..67b11db 100644
--- a/third_party/blink/renderer/core/fetch/fetch_response_data.h
+++ b/third_party/blink/renderer/core/fetch/fetch_response_data.h
@@ -101,6 +101,9 @@
   void SetCorsExposedHeaderNames(const WebHTTPHeaderSet& header_names) {
     cors_exposed_header_names_ = header_names;
   }
+  void SetSideDataBlob(scoped_refptr<BlobDataHandle> blob) {
+    side_data_blob_ = std::move(blob);
+  }
 
   // If the type is Default, replaces |buffer_|.
   // If the type is Basic or CORS, replaces |buffer_| and
@@ -127,6 +130,7 @@
   base::Time response_time_;
   String cache_storage_cache_name_;
   WebHTTPHeaderSet cors_exposed_header_names_;
+  scoped_refptr<BlobDataHandle> side_data_blob_;
 
   DISALLOW_COPY_AND_ASSIGN(FetchResponseData);
 };
diff --git a/third_party/blink/renderer/core/fetch/response.cc b/third_party/blink/renderer/core/fetch/response.cc
index 900bab4..55fd745 100644
--- a/third_party/blink/renderer/core/fetch/response.cc
+++ b/third_party/blink/renderer/core/fetch/response.cc
@@ -8,7 +8,6 @@
 
 #include "base/memory/scoped_refptr.h"
 #include "base/optional.h"
-#include "services/network/public/mojom/fetch_api.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom-blink.h"
 #include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
 #include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
@@ -46,7 +45,7 @@
 namespace {
 
 template <typename CorsHeadersContainer>
-FetchResponseData* FilterResponseData(
+FetchResponseData* FilterResponseDataInternal(
     network::mojom::FetchResponseType response_type,
     FetchResponseData* response,
     CorsHeadersContainer& headers) {
@@ -81,30 +80,9 @@
 FetchResponseData* CreateFetchResponseDataFromFetchAPIResponse(
     ScriptState* script_state,
     mojom::blink::FetchAPIResponse& fetch_api_response) {
-  FetchResponseData* response = nullptr;
-  if (fetch_api_response.status_code > 0)
-    response = FetchResponseData::Create();
-  else
-    response = FetchResponseData::CreateNetworkErrorResponse();
-
-  response->SetResponseSource(fetch_api_response.response_source);
-  response->SetURLList(fetch_api_response.url_list);
-  response->SetStatus(fetch_api_response.status_code);
-  response->SetStatusMessage(WTF::AtomicString(fetch_api_response.status_text));
-  response->SetResponseTime(fetch_api_response.response_time);
-  response->SetCacheStorageCacheName(
-      fetch_api_response.cache_storage_cache_name);
-
-  for (const auto& header : fetch_api_response.headers)
-    response->HeaderList()->Append(header.key, header.value);
-
-  // TODO(wanderview): This sets the mime type of the Response based on the
-  // current headers.  This should be correct for most cases, but technically
-  // the mime type should really be frozen at the initial Response
-  // construction.  We should plumb the value through the cache_storage
-  // persistence layer and include the explicit mime type in FetchAPIResponse
-  // to set here. See: crbug.com/938939
-  response->SetMimeType(response->HeaderList()->ExtractMIMEType());
+  FetchResponseData* response =
+      Response::CreateUnfilteredFetchResponseDataWithoutBody(
+          script_state, fetch_api_response);
 
   if (fetch_api_response.blob) {
     response->ReplaceBodyStreamBuffer(MakeGarbageCollected<BodyStreamBuffer>(
@@ -115,8 +93,9 @@
   }
 
   // Filter the response according to |fetch_api_response|'s ResponseType.
-  response = FilterResponseData(fetch_api_response.response_type, response,
-                                fetch_api_response.cors_exposed_header_names);
+  response =
+      FilterResponseDataInternal(fetch_api_response.response_type, response,
+                                 fetch_api_response.cors_exposed_header_names);
 
   return response;
 }
@@ -375,6 +354,45 @@
   return r;
 }
 
+FetchResponseData* Response::CreateUnfilteredFetchResponseDataWithoutBody(
+    ScriptState* script_state,
+    mojom::blink::FetchAPIResponse& fetch_api_response) {
+  FetchResponseData* response = nullptr;
+  if (fetch_api_response.status_code > 0)
+    response = FetchResponseData::Create();
+  else
+    response = FetchResponseData::CreateNetworkErrorResponse();
+
+  response->SetResponseSource(fetch_api_response.response_source);
+  response->SetURLList(fetch_api_response.url_list);
+  response->SetStatus(fetch_api_response.status_code);
+  response->SetStatusMessage(WTF::AtomicString(fetch_api_response.status_text));
+  response->SetResponseTime(fetch_api_response.response_time);
+  response->SetCacheStorageCacheName(
+      fetch_api_response.cache_storage_cache_name);
+  response->SetSideDataBlob(fetch_api_response.side_data_blob);
+
+  for (const auto& header : fetch_api_response.headers)
+    response->HeaderList()->Append(header.key, header.value);
+
+  // TODO(wanderview): This sets the mime type of the Response based on the
+  // current headers.  This should be correct for most cases, but technically
+  // the mime type should really be frozen at the initial Response
+  // construction.  We should plumb the value through the cache_storage
+  // persistence layer and include the explicit mime type in FetchAPIResponse
+  // to set here. See: crbug.com/938939
+  response->SetMimeType(response->HeaderList()->ExtractMIMEType());
+
+  return response;
+}
+
+FetchResponseData* Response::FilterResponseData(
+    network::mojom::FetchResponseType response_type,
+    FetchResponseData* response,
+    WTF::Vector<WTF::String>& headers) {
+  return FilterResponseDataInternal(response_type, response, headers);
+}
+
 String Response::type() const {
   // "The type attribute's getter must return response's type."
   switch (response_->GetType()) {
diff --git a/third_party/blink/renderer/core/fetch/response.h b/third_party/blink/renderer/core/fetch/response.h
index 6199ca68..3d389ec 100644
--- a/third_party/blink/renderer/core/fetch/response.h
+++ b/third_party/blink/renderer/core/fetch/response.h
@@ -5,6 +5,7 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FETCH_RESPONSE_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FETCH_RESPONSE_H_
 
+#include "services/network/public/mojom/fetch_api.mojom-blink.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_response.mojom-blink-forward.h"
 #include "third_party/blink/renderer/bindings/core/v8/dictionary.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
@@ -54,6 +55,15 @@
                             uint16_t status,
                             ExceptionState&);
 
+  static FetchResponseData* CreateUnfilteredFetchResponseDataWithoutBody(
+      ScriptState*,
+      mojom::blink::FetchAPIResponse&);
+
+  static FetchResponseData* FilterResponseData(
+      network::mojom::FetchResponseType response_type,
+      FetchResponseData* response,
+      WTF::Vector<WTF::String>& headers);
+
   explicit Response(ExecutionContext*);
   Response(ExecutionContext*, FetchResponseData*);
   Response(ExecutionContext*, FetchResponseData*, Headers*);
diff --git a/third_party/blink/renderer/core/frame/window.idl b/third_party/blink/renderer/core/frame/window.idl
index bdc7ae96..0e43da8f 100644
--- a/third_party/blink/renderer/core/frame/window.idl
+++ b/third_party/blink/renderer/core/frame/window.idl
@@ -213,7 +213,7 @@
     attribute DOMMatrixConstructor WebKitCSSMatrix;
 
     // TrustedTypes API: http://github.com/wicg/trusted-types
-    [RuntimeEnabled=TrustedDOMTypes, Unforgeable] readonly attribute TrustedTypePolicyFactory TrustedTypes;
+    [RuntimeEnabled=TrustedDOMTypes, Unforgeable] readonly attribute TrustedTypePolicyFactory trustedTypes;
 };
 
 Window includes GlobalEventHandlers;
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_registry.idl b/third_party/blink/renderer/core/html/custom/custom_element_registry.idl
index 6403c3e2..986991b7 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_registry.idl
+++ b/third_party/blink/renderer/core/html/custom/custom_element_registry.idl
@@ -6,7 +6,7 @@
 
 [Exposed=Window]
 interface CustomElementRegistry {
-    [CallWith=ScriptState, CEReactions, CustomElementCallbacks, RaisesException, MeasureAs=CustomElementRegistryDefine] void define(DOMString name, CustomElementConstructor _constructor, optional ElementDefinitionOptions options);
+    [CallWith=ScriptState, CEReactions, CustomElementCallbacks, RaisesException, MeasureAs=CustomElementRegistryDefine] void define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options);
     any get(DOMString name);
     [CallWith=ScriptState,RaisesException] Promise<void> whenDefined(DOMString name);
     [CEReactions] void upgrade(Node root);
diff --git a/third_party/blink/renderer/core/html/html_html_element.cc b/third_party/blink/renderer/core/html/html_html_element.cc
index 50416b0..e66a2d4 100644
--- a/third_party/blink/renderer/core/html/html_html_element.cc
+++ b/third_party/blink/renderer/core/html/html_html_element.cc
@@ -26,13 +26,16 @@
 #include "third_party/blink/renderer/core/css/style_engine.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/document_parser.h"
+#include "third_party/blink/renderer/core/dom/node_computed_style.h"
 #include "third_party/blink/renderer/core/frame/deprecation.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/loader/appcache/application_cache_host_for_frame.h"
 #include "third_party/blink/renderer/core/loader/document_loader.h"
 #include "third_party/blink/renderer/core/loader/frame_loader.h"
+#include "third_party/blink/renderer/core/style/computed_style.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 
@@ -116,4 +119,69 @@
   return nullptr;
 }
 
+namespace {
+
+bool NeedsLayoutStylePropagation(const ComputedStyle& layout_style,
+                                 const ComputedStyle& propagated_style) {
+  return layout_style.GetWritingMode() != propagated_style.GetWritingMode() ||
+         layout_style.Direction() != propagated_style.Direction();
+}
+
+scoped_refptr<ComputedStyle> CreateLayoutStyle(
+    const ComputedStyle& style,
+    const ComputedStyle& propagated_style) {
+  scoped_refptr<ComputedStyle> layout_style = ComputedStyle::Clone(style);
+  layout_style->SetDirection(propagated_style.Direction());
+  layout_style->SetWritingMode(propagated_style.GetWritingMode());
+  return layout_style;
+}
+
+}  // namespace
+
+scoped_refptr<const ComputedStyle> HTMLHtmlElement::LayoutStyleForElement(
+    scoped_refptr<const ComputedStyle> style) {
+  DCHECK(style);
+  DCHECK(GetDocument().InStyleRecalc());
+  if (const Element* body_element = GetDocument().body()) {
+    if (const ComputedStyle* body_style = body_element->GetComputedStyle()) {
+      if (NeedsLayoutStylePropagation(*style, *body_style))
+        return CreateLayoutStyle(*style, *body_style);
+    }
+  }
+  return style;
+}
+
+void HTMLHtmlElement::PropagateWritingModeAndDirectionFromBody() {
+  // Will be propagated in HTMLHtmlElement::AttachLayoutTree().
+  if (NeedsReattachLayoutTree())
+    return;
+  LayoutObject* layout_object = GetLayoutObject();
+  if (!layout_object)
+    return;
+  const ComputedStyle* style = GetComputedStyle();
+  // If we have a layout object, and we are not marked for re-attachment, we are
+  // guaranteed to have a non-null ComputedStyle.
+  DCHECK(style);
+  const ComputedStyle* propagated_style = nullptr;
+  if (const Element* body = GetDocument().body())
+    propagated_style = body->GetComputedStyle();
+  if (!propagated_style)
+    propagated_style = style;
+  if (NeedsLayoutStylePropagation(layout_object->StyleRef(),
+                                  *propagated_style)) {
+    layout_object->SetStyle(CreateLayoutStyle(*style, *propagated_style));
+  }
+}
+
+void HTMLHtmlElement::AttachLayoutTree(AttachContext& context) {
+  scoped_refptr<const ComputedStyle> original_style = GetComputedStyle();
+  if (original_style)
+    SetComputedStyle(LayoutStyleForElement(original_style));
+
+  Element::AttachLayoutTree(context);
+
+  if (original_style)
+    SetComputedStyle(original_style);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_html_element.h b/third_party/blink/renderer/core/html/html_html_element.h
index 66abdee..a9054de 100644
--- a/third_party/blink/renderer/core/html/html_html_element.h
+++ b/third_party/blink/renderer/core/html/html_html_element.h
@@ -38,12 +38,16 @@
   void InsertedByParser();
 
   bool HasNonInBodyInsertionMode() const override { return true; }
+  void PropagateWritingModeAndDirectionFromBody();
+  scoped_refptr<const ComputedStyle> LayoutStyleForElement(
+      scoped_refptr<const ComputedStyle> style);
 
  private:
   void MaybeSetupApplicationCache();
 
   bool IsURLAttribute(const Attribute&) const override;
   const CSSPropertyValueSet* AdditionalPresentationAttributeStyle() override;
+  void AttachLayoutTree(AttachContext&) override;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/html_progress_element.cc b/third_party/blink/renderer/core/html/html_progress_element.cc
index 40dbeb8..99d1410b 100644
--- a/third_party/blink/renderer/core/html/html_progress_element.cc
+++ b/third_party/blink/renderer/core/html/html_progress_element.cc
@@ -25,6 +25,7 @@
 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
 #include "third_party/blink/renderer/core/html/shadow/progress_shadow_element.h"
 #include "third_party/blink/renderer/core/html_names.h"
+#include "third_party/blink/renderer/core/layout/layout_object_factory.h"
 #include "third_party/blink/renderer/core/layout/layout_progress.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
@@ -55,7 +56,7 @@
   }
   UseCounter::Count(GetDocument(),
                     WebFeature::kProgressElementWithProgressBarAppearance);
-  return new LayoutProgress(this);
+  return LayoutObjectFactory::CreateLayoutProgress(this, style, legacy);
 }
 
 LayoutProgress* HTMLProgressElement::GetLayoutProgress() const {
diff --git a/third_party/blink/renderer/core/html/html_progress_element.h b/third_party/blink/renderer/core/html/html_progress_element.h
index 3b7dab2f..1473489 100644
--- a/third_party/blink/renderer/core/html/html_progress_element.h
+++ b/third_party/blink/renderer/core/html/html_progress_element.h
@@ -56,7 +56,6 @@
   bool AreAuthorShadowsAllowed() const override { return false; }
   bool ShouldAppearIndeterminate() const override;
   bool IsLabelable() const override { return true; }
-  bool TypeShouldForceLegacyLayout() const final { return true; }
 
   LayoutObject* CreateLayoutObject(const ComputedStyle&, LegacyLayout) override;
   LayoutProgress* GetLayoutProgress() const;
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index 5930a3c..22d0b28 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -1427,6 +1427,10 @@
   pointer_event_manager_->RemoveLastMousePosition();
 }
 
+bool EventHandler::LongTapShouldInvokeContextMenu() {
+  return gesture_manager_->LongTapShouldInvokeContextMenu();
+}
+
 WebInputEventResult EventHandler::DispatchMousePointerEvent(
     const WebInputEvent::Type event_type,
     Element* target_element,
diff --git a/third_party/blink/renderer/core/input/event_handler.h b/third_party/blink/renderer/core/input/event_handler.h
index 69d25d0..fc5d5244 100644
--- a/third_party/blink/renderer/core/input/event_handler.h
+++ b/third_party/blink/renderer/core/input/event_handler.h
@@ -298,6 +298,8 @@
   // restart from the lock position.
   void ResetMousePositionForPointerUnlock();
 
+  bool LongTapShouldInvokeContextMenu();
+
  private:
   enum NoCursorChangeType { kNoCursorChange };
 
@@ -460,8 +462,6 @@
 
   double max_mouse_moved_duration_;
 
-  bool long_tap_should_invoke_context_menu_;
-
   TaskRunnerTimer<EventHandler> active_interval_timer_;
 
   // last_show_press_timestamp_ prevents the active state rewrited by
diff --git a/third_party/blink/renderer/core/input/event_handler_test.cc b/third_party/blink/renderer/core/input/event_handler_test.cc
index 7fdb009a..58d9032 100644
--- a/third_party/blink/renderer/core/input/event_handler_test.cc
+++ b/third_party/blink/renderer/core/input/event_handler_test.cc
@@ -1292,7 +1292,7 @@
   gsb.data.scroll_begin.delta_x_hint = -gsb_data.delta.width;
   gsb.data.scroll_begin.delta_y_hint = -gsb_data.delta.height;
   gsb.data.scroll_begin.scrollable_area_element_id =
-      gsb_data.scrollable_area_element_id.GetInternalValue();
+      gsb_data.scrollable_area_element_id.GetStableId();
   GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(gsb);
   WebGestureEvent gsu{WebInputEvent::kGestureScrollUpdate,
                       WebInputEvent::kNoModifiers,
@@ -1655,8 +1655,8 @@
   LocalFrameView* frame_view = GetDocument().View();
   constexpr float delta_y = 500;
   InjectScrollFromGestureEvents(
-      frame_view->LayoutViewport()->GetCompositorElementId().GetInternalValue(),
-      0, delta_y);
+      frame_view->LayoutViewport()->GetCompositorElementId().GetStableId(), 0,
+      delta_y);
   ASSERT_EQ(500, frame_view->LayoutViewport()->GetScrollOffset().Height());
   EXPECT_EQ("currently hovered", element1.InnerHTML().Utf8());
   EXPECT_EQ("hover over me", element2.InnerHTML().Utf8());
@@ -1730,7 +1730,7 @@
   // and mark hover state dirty in ScrollManager.
   constexpr float delta_y = 1000;
   InjectScrollFromGestureEvents(
-      iframe_scrollable_area->GetCompositorElementId().GetInternalValue(), 0,
+      iframe_scrollable_area->GetCompositorElementId().GetStableId(), 0,
       delta_y);
   LocalFrameView* frame_view = GetDocument().View();
   ASSERT_EQ(0, frame_view->LayoutViewport()->GetScrollOffset().Height());
@@ -1862,7 +1862,7 @@
       scroller->GetLayoutBox()->GetScrollableArea();
   constexpr float delta_y = 300;
   InjectScrollFromGestureEvents(
-      scrollable_area->GetCompositorElementId().GetInternalValue(), 0, delta_y);
+      scrollable_area->GetCompositorElementId().GetStableId(), 0, delta_y);
   ASSERT_EQ(300, scrollable_area->GetScrollOffset().Height());
   EXPECT_TRUE(target1->IsHovered());
   EXPECT_FALSE(target2->IsHovered());
@@ -2351,7 +2351,7 @@
   ScrollableArea* scrollable_area =
       scroller->GetLayoutBox()->GetScrollableArea();
   gesture_scroll_begin.data.scroll_begin.scrollable_area_element_id =
-      scrollable_area->GetCompositorElementId().GetInternalValue();
+      scrollable_area->GetCompositorElementId().GetStableId();
 
   GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
       gesture_scroll_begin);
@@ -2411,7 +2411,7 @@
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = -delta_y;
   gesture_scroll_begin.data.scroll_begin.scrollable_area_element_id =
-      scrollable_area->GetCompositorElementId().GetInternalValue();
+      scrollable_area->GetCompositorElementId().GetStableId();
   GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
       gesture_scroll_begin);
 
@@ -2462,7 +2462,7 @@
   gesture_scroll_begin.data.scroll_begin.delta_x_hint = 0;
   gesture_scroll_begin.data.scroll_begin.delta_y_hint = -delta_y;
   gesture_scroll_begin.data.scroll_begin.scrollable_area_element_id =
-      visual_viewport.GetCompositorElementId().GetInternalValue();
+      visual_viewport.GetCompositorElementId().GetStableId();
 
   GetDocument().GetFrame()->GetEventHandler().HandleGestureEvent(
       gesture_scroll_begin);
diff --git a/third_party/blink/renderer/core/input/gesture_manager.cc b/third_party/blink/renderer/core/input/gesture_manager.cc
index ebf43f8..76b7d7b 100644
--- a/third_party/blink/renderer/core/input/gesture_manager.cc
+++ b/third_party/blink/renderer/core/input/gesture_manager.cc
@@ -140,6 +140,10 @@
   return WebInputEventResult::kNotHandled;
 }
 
+bool GestureManager::LongTapShouldInvokeContextMenu() const {
+  return long_tap_should_invoke_context_menu_;
+}
+
 WebInputEventResult GestureManager::HandleGestureTapDown(
     const GestureEventWithHitTestResults& targeted_event) {
   suppress_mouse_events_from_gestures_ =
@@ -372,12 +376,10 @@
 
 WebInputEventResult GestureManager::HandleGestureLongTap(
     const GestureEventWithHitTestResults& targeted_event) {
-#if !defined(OS_ANDROID)
-  if (long_tap_should_invoke_context_menu_) {
+  if (LongTapShouldInvokeContextMenu()) {
     long_tap_should_invoke_context_menu_ = false;
     return SendContextMenuEventForGesture(targeted_event);
   }
-#endif
   return WebInputEventResult::kNotHandled;
 }
 
diff --git a/third_party/blink/renderer/core/input/gesture_manager.h b/third_party/blink/renderer/core/input/gesture_manager.h
index 6499ba3..69a18d78 100644
--- a/third_party/blink/renderer/core/input/gesture_manager.h
+++ b/third_party/blink/renderer/core/input/gesture_manager.h
@@ -38,6 +38,7 @@
       WebInputEvent::Type);
   WebInputEventResult HandleGestureEventInFrame(
       const GestureEventWithHitTestResults&);
+  bool LongTapShouldInvokeContextMenu() const;
 
  private:
   WebInputEventResult HandleGestureShowPress();
diff --git a/third_party/blink/renderer/core/layout/BUILD.gn b/third_party/blink/renderer/core/layout/BUILD.gn
index 0cd72b4..be29a9c 100644
--- a/third_party/blink/renderer/core/layout/BUILD.gn
+++ b/third_party/blink/renderer/core/layout/BUILD.gn
@@ -401,6 +401,8 @@
     "ng/layout_ng_flexible_box.h",
     "ng/layout_ng_mixin.cc",
     "ng/layout_ng_mixin.h",
+    "ng/layout_ng_progress.cc",
+    "ng/layout_ng_progress.h",
     "ng/layout_ng_table_caption.cc",
     "ng/layout_ng_table_caption.h",
     "ng/layout_ng_table_cell.cc",
diff --git a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc
index b052acc1..ef2d61f7 100644
--- a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.cc
@@ -69,6 +69,11 @@
   growth_limit_cap_ = growth_limit_cap;
 }
 
+void GridTrack::SetSizeDistributionWeight(double size_distribution_weight) {
+  DCHECK_GE(size_distribution_weight, 0);
+  size_distribution_weight_ = size_distribution_weight;
+}
+
 bool GridTrack::IsGrowthLimitBiggerThanBaseSize() const {
   return GrowthLimitIsInfinite() || growth_limit_ >= base_size_;
 }
@@ -521,7 +526,8 @@
 void GridTrackSizingAlgorithmStrategy::DistributeSpaceToTracks(
     Vector<GridTrack*>& tracks,
     LayoutUnit& available_logical_space) const {
-  algorithm_.DistributeSpaceToTracks<kMaximizeTracks>(tracks, nullptr,
+  algorithm_.DistributeSpaceToTracks<kNotCrossingIntrinsicFlexibleTracks,
+                                     kMaximizeTracks>(tracks, nullptr,
                                                       available_logical_space);
 }
 
@@ -962,8 +968,10 @@
     const GridTrackSize& track_size,
     LayoutUnit base_size) const {
   const GridLength& grid_length = track_size.MaxTrackBreadth();
-  if (grid_length.IsFlex())
-    return base_size;
+  if (grid_length.IsFlex()) {
+    return track_size.MinTrackBreadth().IsContentSized() ? LayoutUnit(kInfinity)
+                                                         : base_size;
+  }
 
   const Length& track_length = grid_length.length();
   if (track_length.IsSpecified()) {
@@ -1000,8 +1008,11 @@
 
     if (track_size.IsContentSized())
       content_sized_tracks_index_.push_back(i);
-    if (track_size.MaxTrackBreadth().IsFlex())
+    if (track_size.MaxTrackBreadth().IsFlex()) {
       flexible_sized_tracks_index_.push_back(i);
+      if (track_size.MinTrackBreadth().IsContentSized())
+        track.SetSizeDistributionWeight(track_size.MaxTrackBreadth().Flex());
+    }
     if (track_size.HasAutoMaxTrackBreadth() && !track_size.IsFitContent())
       auto_sized_tracks_for_stretch_index_.push_back(i);
 
@@ -1047,12 +1058,12 @@
   }
 }
 
-bool GridTrackSizingAlgorithm::SpanningItemCrossesFlexibleSizedTracks(
+bool GridTrackSizingAlgorithm::SpanningItemCrossesIntrinsicFlexibleSizedTracks(
     const GridSpan& span) const {
   for (const auto& track_position : span) {
     const GridTrackSize& track_size =
         GetGridTrackSize(direction_, track_position);
-    if (track_size.MinTrackBreadth().IsFlex() ||
+    if (track_size.HasIntrinsicMinTrackBreadth() &&
         track_size.MaxTrackBreadth().IsFlex())
       return true;
   }
@@ -1114,9 +1125,13 @@
   return track.BaseSize();
 }
 
-static bool ShouldProcessTrackForTrackSizeComputationPhase(
+static bool ShouldProcessTrackForTrackSizeComputationVariantAndPhase(
+    TrackSizeComputationVariant variant,
     TrackSizeComputationPhase phase,
     const GridTrackSize& track_size) {
+  if (variant == kCrossingIntrinsicFlexibleTracks &&
+      !track_size.MaxTrackBreadth().IsFlex())
+    return false;
   switch (phase) {
     case kResolveIntrinsicMinimums:
       return track_size.HasIntrinsicMinTrackBreadth();
@@ -1245,6 +1260,12 @@
       track2_has_infinite_growth_potential_without_cap)
     return track2_has_infinite_growth_potential_without_cap;
 
+  // We don't have to take weights into account when comparing growth potentials
+  // because they must be 0 at this point. Flexible tracks with a greater
+  // weight have already been handled due to their infinite growth limit.
+  DCHECK_EQ(track1->SizeDistributionWeight(), 0);
+  DCHECK_EQ(track2->SizeDistributionWeight(), 0);
+
   LayoutUnit track1_limit =
       track1->GrowthLimitCap().value_or(track1->GrowthLimit());
   LayoutUnit track2_limit =
@@ -1267,7 +1288,21 @@
   growth_share = std::min(growth_share, distance_to_cap);
 }
 
-template <TrackSizeComputationPhase phase>
+static Vector<double> FractionsOfRemainingSpace(
+    const Vector<GridTrack*>& tracks) {
+  size_t tracks_size = tracks.size();
+  Vector<double> fractions_of_remaining_space(tracks_size);
+  double weight_sum = 0;
+  for (size_t i = tracks_size; i-- > 0;) {
+    double weight = tracks[i]->SizeDistributionWeight();
+    weight_sum += weight;
+    fractions_of_remaining_space[i] =
+        weight_sum > 0 ? weight / weight_sum : 1.0 / (tracks_size - i);
+  }
+  return fractions_of_remaining_space;
+}
+
+template <TrackSizeComputationVariant variant, TrackSizeComputationPhase phase>
 void GridTrackSizingAlgorithm::DistributeSpaceToTracks(
     Vector<GridTrack*>& tracks,
     Vector<GridTrack*>* grow_beyond_growth_limits_tracks,
@@ -1280,13 +1315,23 @@
   }
 
   if (available_logical_space > 0) {
-    std::sort(tracks.begin(), tracks.end(), SortByGridTrackGrowthPotential);
+    // No need to sort when distributing intrinsic contributions among flexible
+    // tracks, because all of them have an infinite growth potential.
+    if (variant == kNotCrossingIntrinsicFlexibleTracks)
+      std::sort(tracks.begin(), tracks.end(), SortByGridTrackGrowthPotential);
 
+    auto fractions_of_remaining_space = FractionsOfRemainingSpace(tracks);
     size_t tracks_size = tracks.size();
     for (size_t i = 0; i < tracks_size; ++i) {
       GridTrack& track = *tracks[i];
-      LayoutUnit available_logical_space_share =
-          available_logical_space / (tracks_size - i);
+#if DCHECK_IS_ON()
+      if (variant == kCrossingIntrinsicFlexibleTracks)
+        DCHECK(track.GrowthLimitIsInfinite());
+      else
+        DCHECK_EQ(track.SizeDistributionWeight(), 0);
+#endif
+      LayoutUnit available_logical_space_share(available_logical_space *
+                                               fractions_of_remaining_space[i]);
       const LayoutUnit& track_breadth =
           TrackSizeForTrackSizeComputationPhase(phase, track, kForbidInfinity);
       LayoutUnit growth_share =
@@ -1304,6 +1349,9 @@
   }
 
   if (available_logical_space > 0 && grow_beyond_growth_limits_tracks) {
+    // We never grow flex tracks beyond growth limits, since they are infinite.
+    DCHECK_NE(variant, kCrossingIntrinsicFlexibleTracks);
+
     // We need to sort them because there might be tracks with growth limit caps
     // (like the ones with fit-content()) which cannot indefinitely grow over
     // the limits.
@@ -1313,12 +1361,15 @@
                 SortByGridTrackGrowthPotential);
     }
 
+    auto fractions_of_remaining_space =
+        FractionsOfRemainingSpace(*grow_beyond_growth_limits_tracks);
     size_t tracks_growing_above_max_breadth_size =
         grow_beyond_growth_limits_tracks->size();
     for (size_t i = 0; i < tracks_growing_above_max_breadth_size; ++i) {
       GridTrack* track = grow_beyond_growth_limits_tracks->at(i);
-      LayoutUnit growth_share =
-          available_logical_space / (tracks_growing_above_max_breadth_size - i);
+      LayoutUnit growth_share(available_logical_space *
+                              fractions_of_remaining_space[i]);
+
       ClampGrowthShareIfNeeded(phase, *track, growth_share);
       DCHECK_GE(growth_share, 0) << "We must never shrink any grid track or "
                                     "else we can't guarantee we abide by our "
@@ -1336,7 +1387,7 @@
   }
 }
 
-template <TrackSizeComputationPhase phase>
+template <TrackSizeComputationVariant variant, TrackSizeComputationPhase phase>
 void GridTrackSizingAlgorithm::IncreaseSizesToAccommodateSpanningItems(
     const GridItemsSpanGroupRange& grid_items_with_span) {
   Vector<GridTrack>& all_tracks = Tracks(direction_);
@@ -1351,8 +1402,9 @@
   for (auto* it = grid_items_with_span.range_start;
        it != grid_items_with_span.range_end; ++it) {
     GridItemWithSpan& grid_item_with_span = *it;
-    DCHECK_GT(grid_item_with_span.GetGridSpan().IntegerSpan(), 1u);
     const GridSpan& item_span = grid_item_with_span.GetGridSpan();
+    DCHECK(variant == kCrossingIntrinsicFlexibleTracks ||
+           item_span.IntegerSpan() > 1u);
 
     grow_beyond_growth_limits_tracks.Shrink(0);
     filtered_tracks.Shrink(0);
@@ -1362,7 +1414,8 @@
       GridTrack& track = Tracks(direction_)[track_position];
       spanning_tracks_size +=
           TrackSizeForTrackSizeComputationPhase(phase, track, kForbidInfinity);
-      if (!ShouldProcessTrackForTrackSizeComputationPhase(phase, track_size))
+      if (!ShouldProcessTrackForTrackSizeComputationVariantAndPhase(
+              variant, phase, track_size))
         continue;
 
       filtered_tracks.push_back(&track);
@@ -1387,7 +1440,7 @@
         grow_beyond_growth_limits_tracks.IsEmpty()
             ? filtered_tracks
             : grow_beyond_growth_limits_tracks;
-    DistributeSpaceToTracks<phase>(
+    DistributeSpaceToTracks<variant, phase>(
         filtered_tracks, &tracks_to_grow_beyond_growth_limits, extra_space);
   }
 
@@ -1398,8 +1451,25 @@
   }
 }
 
+template <TrackSizeComputationVariant variant>
+void GridTrackSizingAlgorithm::IncreaseSizesToAccommodateSpanningItems(
+    const GridItemsSpanGroupRange& grid_items_with_span) {
+  IncreaseSizesToAccommodateSpanningItems<variant, kResolveIntrinsicMinimums>(
+      grid_items_with_span);
+  IncreaseSizesToAccommodateSpanningItems<variant,
+                                          kResolveContentBasedMinimums>(
+      grid_items_with_span);
+  IncreaseSizesToAccommodateSpanningItems<variant, kResolveMaxContentMinimums>(
+      grid_items_with_span);
+  IncreaseSizesToAccommodateSpanningItems<variant, kResolveIntrinsicMaximums>(
+      grid_items_with_span);
+  IncreaseSizesToAccommodateSpanningItems<variant, kResolveMaxContentMaximums>(
+      grid_items_with_span);
+}
+
 void GridTrackSizingAlgorithm::ResolveIntrinsicTrackSizes() {
   Vector<GridItemWithSpan> items_sorted_by_increasing_span;
+  Vector<GridItemWithSpan> items_crossing_flexible_tracks;
   if (grid_.HasGridItems()) {
     HashSet<LayoutBox*> items_set;
     for (const auto& track_index : content_sized_tracks_index_) {
@@ -1408,9 +1478,12 @@
       while (auto* grid_item = iterator->NextGridItem()) {
         if (items_set.insert(grid_item).is_new_entry) {
           const GridSpan& span = grid_.GridItemSpan(*grid_item, direction_);
-          if (span.IntegerSpan() == 1) {
+          if (SpanningItemCrossesIntrinsicFlexibleSizedTracks(span)) {
+            items_crossing_flexible_tracks.push_back(
+                GridItemWithSpan(*grid_item, span));
+          } else if (span.IntegerSpan() == 1) {
             SizeTrackToFitNonSpanningItem(span, *grid_item, track);
-          } else if (!SpanningItemCrossesFlexibleSizedTracks(span)) {
+          } else {
             items_sorted_by_increasing_span.push_back(
                 GridItemWithSpan(*grid_item, span));
           }
@@ -1426,24 +1499,23 @@
   while (it != end) {
     GridItemsSpanGroupRange span_group_range = {it,
                                                 std::upper_bound(it, end, *it)};
-    IncreaseSizesToAccommodateSpanningItems<kResolveIntrinsicMinimums>(
-        span_group_range);
-    IncreaseSizesToAccommodateSpanningItems<kResolveContentBasedMinimums>(
-        span_group_range);
-    IncreaseSizesToAccommodateSpanningItems<kResolveMaxContentMinimums>(
-        span_group_range);
-    IncreaseSizesToAccommodateSpanningItems<kResolveIntrinsicMaximums>(
-        span_group_range);
-    IncreaseSizesToAccommodateSpanningItems<kResolveMaxContentMaximums>(
-        span_group_range);
+    IncreaseSizesToAccommodateSpanningItems<
+        kNotCrossingIntrinsicFlexibleTracks>(span_group_range);
     it = span_group_range.range_end;
   }
 
+  IncreaseSizesToAccommodateSpanningItems<kCrossingIntrinsicFlexibleTracks>(
+      {items_crossing_flexible_tracks.begin(),
+       items_crossing_flexible_tracks.end()});
+
+  Vector<GridTrack>& track_list = Tracks(direction_);
   for (const auto& track_index : content_sized_tracks_index_) {
-    GridTrack& track = Tracks(direction_)[track_index];
+    GridTrack& track = track_list[track_index];
     if (track.GrowthLimit() == kInfinity)
       track.SetGrowthLimit(track.BaseSize());
   }
+  for (const auto& track_index : flexible_sized_tracks_index_)
+    track_list[track_index].SetSizeDistributionWeight(0);
 }
 
 void GridTrackSizingAlgorithm::ComputeGridContainerIntrinsicSizes() {
@@ -1451,8 +1523,12 @@
 
   Vector<GridTrack>& all_tracks = Tracks(direction_);
   for (auto& track : all_tracks) {
-    DCHECK(strategy_->IsComputingSizeContainment() ||
-           !track.InfiniteGrowthPotential());
+#if DCHECK_IS_ON()
+    if (!strategy_->IsComputingSizeContainment()) {
+      DCHECK(!track.InfiniteGrowthPotential());
+      DCHECK_EQ(track.SizeDistributionWeight(), 0);
+    }
+#endif
     min_content_size_ += track.BaseSize();
     max_content_size_ +=
         track.GrowthLimitIsInfinite() ? track.BaseSize() : track.GrowthLimit();
diff --git a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h
index 6107043..2fb7574 100644
--- a/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h
+++ b/third_party/blink/renderer/core/layout/grid_track_sizing_algorithm.h
@@ -24,6 +24,11 @@
 class GridTrackSizingAlgorithmStrategy;
 class LayoutGrid;
 
+enum TrackSizeComputationVariant {
+  kNotCrossingIntrinsicFlexibleTracks,
+  kCrossingIntrinsicFlexibleTracks,
+};
+
 enum TrackSizeComputationPhase {
   kResolveIntrinsicMinimums,
   kResolveContentBasedMinimums,
@@ -66,6 +71,14 @@
   }
   void SetGrowthLimitCap(base::Optional<LayoutUnit>);
 
+  // For flexible tracks, intrinsic contributions are distributed according to
+  // the ratios of the flex fractions. At that point we will only have some
+  // GridTracks, but we won't know their index, so we won't be able to call
+  // GetGridTrackSize in order to obtain their flex fraction. Therefore we cache
+  // them instead of computing on demand.
+  double SizeDistributionWeight() const { return size_distribution_weight_; }
+  void SetSizeDistributionWeight(double);
+
  private:
   bool IsGrowthLimitBiggerThanBaseSize() const;
   void EnsureGrowthLimitIsBiggerThanBaseSize();
@@ -76,6 +89,7 @@
   LayoutUnit size_during_distribution_;
   base::Optional<LayoutUnit> growth_limit_cap_;
   bool infinitely_growable_;
+  double size_distribution_weight_{0};
 };
 
 class GridTrackSizingAlgorithm final {
@@ -150,14 +164,19 @@
   void SizeTrackToFitNonSpanningItem(const GridSpan&,
                                      LayoutBox& grid_item,
                                      GridTrack&);
-  bool SpanningItemCrossesFlexibleSizedTracks(const GridSpan&) const;
+  bool SpanningItemCrossesIntrinsicFlexibleSizedTracks(const GridSpan&) const;
   typedef struct GridItemsSpanGroupRange GridItemsSpanGroupRange;
-  template <TrackSizeComputationPhase phase>
+  template <TrackSizeComputationVariant variant,
+            TrackSizeComputationPhase phase>
+  void IncreaseSizesToAccommodateSpanningItems(
+      const GridItemsSpanGroupRange& grid_items_with_span);
+  template <TrackSizeComputationVariant variant>
   void IncreaseSizesToAccommodateSpanningItems(
       const GridItemsSpanGroupRange& grid_items_with_span);
   LayoutUnit ItemSizeForTrackSizeComputationPhase(TrackSizeComputationPhase,
                                                   LayoutBox&) const;
-  template <TrackSizeComputationPhase phase>
+  template <TrackSizeComputationVariant variant,
+            TrackSizeComputationPhase phase>
   void DistributeSpaceToTracks(
       Vector<GridTrack*>& tracks,
       Vector<GridTrack*>* grow_beyond_growth_limits_tracks,
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 098c883..11256b7 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -661,6 +661,7 @@
   bool IsLayoutNGListMarkerImage() const {
     return IsOfType(kLayoutObjectNGListMarkerImage);
   }
+  bool IsLayoutNGProgress() const { return IsOfType(kLayoutObjectNGProgress); }
   bool IsLayoutNGText() const { return IsOfType(kLayoutObjectNGText); }
   bool IsLayoutTableCol() const {
     return IsOfType(kLayoutObjectLayoutTableCol);
@@ -2498,6 +2499,7 @@
     kLayoutObjectNGListMarker,
     kLayoutObjectNGInsideListMarker,
     kLayoutObjectNGListMarkerImage,
+    kLayoutObjectNGProgress,
     kLayoutObjectNGText,
     kLayoutObjectProgress,
     kLayoutObjectQuote,
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.cc b/third_party/blink/renderer/core/layout/layout_object_factory.cc
index 9b6024f..bc47670 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.cc
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.cc
@@ -19,6 +19,7 @@
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_fieldset.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_flexible_box.h"
+#include "third_party/blink/renderer/core/layout/ng/layout_ng_progress.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_table_caption.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_table_cell.h"
 #include "third_party/blink/renderer/core/layout/ng/list/layout_ng_list_item.h"
@@ -146,4 +147,11 @@
   return layout_text_fragment;
 }
 
+LayoutProgress* LayoutObjectFactory::CreateLayoutProgress(
+    Node* node,
+    const ComputedStyle& style,
+    LegacyLayout legacy) {
+  return CreateObject<LayoutProgress, LayoutNGProgress>(*node, style, legacy);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_object_factory.h b/third_party/blink/renderer/core/layout/layout_object_factory.h
index a9596a42..9425007 100644
--- a/third_party/blink/renderer/core/layout/layout_object_factory.h
+++ b/third_party/blink/renderer/core/layout/layout_object_factory.h
@@ -15,6 +15,7 @@
 class LayoutBlock;
 class LayoutBlockFlow;
 enum class LegacyLayout;
+class LayoutProgress;
 class LayoutTableCaption;
 class LayoutTableCell;
 class LayoutText;
@@ -55,6 +56,9 @@
                                                 int start_offset,
                                                 int length,
                                                 LegacyLayout);
+  static LayoutProgress* CreateLayoutProgress(Node* node,
+                                              const ComputedStyle& style,
+                                              LegacyLayout legacy);
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_progress.cc b/third_party/blink/renderer/core/layout/layout_progress.cc
index e2cdda71..6b285eb 100644
--- a/third_party/blink/renderer/core/layout/layout_progress.cc
+++ b/third_party/blink/renderer/core/layout/layout_progress.cc
@@ -27,14 +27,16 @@
 
 namespace blink {
 
-LayoutProgress::LayoutProgress(HTMLProgressElement* element)
+LayoutProgress::LayoutProgress(Element* element)
     : LayoutBlockFlow(element),
       position_(HTMLProgressElement::kInvalidPosition),
       animating_(false),
       animation_timer_(
           element->GetDocument().GetTaskRunner(TaskType::kInternalDefault),
           this,
-          &LayoutProgress::AnimationTimerFired) {}
+          &LayoutProgress::AnimationTimerFired) {
+  DCHECK(IsHTMLProgressElement(element));
+}
 
 LayoutProgress::~LayoutProgress() = default;
 
diff --git a/third_party/blink/renderer/core/layout/layout_progress.h b/third_party/blink/renderer/core/layout/layout_progress.h
index 7430461..0c24c53 100644
--- a/third_party/blink/renderer/core/layout/layout_progress.h
+++ b/third_party/blink/renderer/core/layout/layout_progress.h
@@ -28,9 +28,9 @@
 
 class HTMLProgressElement;
 
-class CORE_EXPORT LayoutProgress final : public LayoutBlockFlow {
+class CORE_EXPORT LayoutProgress : public LayoutBlockFlow {
  public:
-  explicit LayoutProgress(HTMLProgressElement*);
+  explicit LayoutProgress(Element* element);
   ~LayoutProgress() override;
 
   double GetPosition() const { return position_; }
@@ -45,15 +45,14 @@
 
  protected:
   void WillBeDestroyed() override;
+  bool IsOfType(LayoutObjectType type) const override {
+    return type == kLayoutObjectProgress || LayoutBlockFlow::IsOfType(type);
+  }
 
   bool IsAnimating() const;
   bool IsAnimationTimerActive() const;
 
  private:
-  bool IsOfType(LayoutObjectType type) const override {
-    return type == kLayoutObjectProgress || LayoutBlockFlow::IsOfType(type);
-  }
-
   void AnimationTimerFired(TimerBase*);
   void UpdateAnimationState();
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
index b5fc291..feb0ff6 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.cc
@@ -4,14 +4,7 @@
 
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h"
 
-#include "third_party/blink/renderer/core/layout/layout_analyzer.h"
-#include "third_party/blink/renderer/core/layout/layout_view.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_block_node.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_out_of_flow_positioned_node.h"
-#include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
 
 namespace blink {
 
@@ -26,47 +19,7 @@
 }
 
 void LayoutNGBlockFlow::UpdateBlockLayout(bool relayout_children) {
-  LayoutAnalyzer::BlockScope analyzer(*this);
-
-  if (IsOutOfFlowPositioned()) {
-    UpdateOutOfFlowBlockLayout();
-    return;
-  }
-
-  NGConstraintSpace constraint_space =
-      NGConstraintSpace::CreateFromLayoutObject(
-          *this, !View()->GetLayoutState()->Next() /* is_layout_root */);
-
-  scoped_refptr<const NGLayoutResult> result =
-      NGBlockNode(this).Layout(constraint_space);
-
-  for (const auto& descendant :
-       result->PhysicalFragment().OutOfFlowPositionedDescendants())
-    descendant.node.UseLegacyOutOfFlowPositioning();
-
-  UpdateMargins(constraint_space);
-}
-
-void LayoutNGBlockFlow::UpdateMargins(const NGConstraintSpace& space) {
-  const LayoutBlock* containing_block = ContainingBlock();
-  if (!containing_block || !containing_block->IsLayoutBlockFlow())
-    return;
-
-  // In the legacy engine, for regular block container layout, children
-  // calculate and store margins on themselves, while in NG that's done by the
-  // container. Since this object is a LayoutNG entry-point, we'll have to do it
-  // on ourselves, since that's what the legacy container expects.
-  const ComputedStyle& style = StyleRef();
-  const ComputedStyle& cb_style = containing_block->StyleRef();
-  const auto writing_mode = cb_style.GetWritingMode();
-  const auto direction = cb_style.Direction();
-  LayoutUnit percentage_resolution_size =
-      space.PercentageResolutionInlineSizeForParentWritingMode();
-  NGBoxStrut margins = ComputePhysicalMargins(style, percentage_resolution_size)
-                           .ConvertToLogical(writing_mode, direction);
-  ResolveInlineMargins(style, cb_style, space.AvailableSize().inline_size,
-                       LogicalWidth(), &margins);
-  SetMargin(margins.ConvertToPhysical(writing_mode, direction));
+  UpdateNGBlockLayout();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
index 37fafae2..c17a0ca 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow.h
@@ -24,9 +24,6 @@
 
  protected:
   bool IsOfType(LayoutObjectType) const override;
-
- private:
-  void UpdateMargins(const NGConstraintSpace&);
 };
 
 DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGBlockFlow, IsLayoutNGBlockFlow());
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc
index 4f49e5f3..0939dc6 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.cc
@@ -9,6 +9,8 @@
 
 #include "third_party/blink/renderer/core/editing/position_with_affinity.h"
 #include "third_party/blink/renderer/core/layout/hit_test_location.h"
+#include "third_party/blink/renderer/core/layout/layout_analyzer.h"
+#include "third_party/blink/renderer/core/layout/layout_view.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node_data.h"
 #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_box_utils.h"
@@ -187,8 +189,9 @@
     const PhysicalOffset& additional_offset,
     NGOutlineType include_block_overflows) const {
   if (PaintFragment()) {
-    PaintFragment()->AddSelfOutlineRects(&rects, additional_offset,
-                                         include_block_overflows);
+    To<NGPhysicalBoxFragment>(PaintFragment()->PhysicalFragment())
+        .AddSelfOutlineRects(additional_offset, include_block_overflows,
+                             &rects);
   } else {
     Base::AddOutlineRects(rects, additional_offset, include_block_overflows);
   }
@@ -357,8 +360,55 @@
     NGPaintFragment::DirtyLinesFromChangedChild(child);
 }
 
+template <typename Base>
+void LayoutNGBlockFlowMixin<Base>::UpdateNGBlockLayout() {
+  LayoutAnalyzer::BlockScope analyzer(*this);
+
+  if (Base::IsOutOfFlowPositioned()) {
+    this->UpdateOutOfFlowBlockLayout();
+    return;
+  }
+
+  NGConstraintSpace constraint_space =
+      NGConstraintSpace::CreateFromLayoutObject(
+          *this, !Base::View()->GetLayoutState()->Next() /* is_layout_root */);
+
+  scoped_refptr<const NGLayoutResult> result =
+      NGBlockNode(this).Layout(constraint_space);
+
+  for (const auto& descendant :
+       result->PhysicalFragment().OutOfFlowPositionedDescendants())
+    descendant.node.UseLegacyOutOfFlowPositioning();
+  this->UpdateMargins(constraint_space);
+}
+
+template <typename Base>
+void LayoutNGBlockFlowMixin<Base>::UpdateMargins(
+    const NGConstraintSpace& space) {
+  const LayoutBlock* containing_block = Base::ContainingBlock();
+  if (!containing_block || !containing_block->IsLayoutBlockFlow())
+    return;
+
+  // In the legacy engine, for regular block container layout, children
+  // calculate and store margins on themselves, while in NG that's done by the
+  // container. Since this object is a LayoutNG entry-point, we'll have to do it
+  // on ourselves, since that's what the legacy container expects.
+  const ComputedStyle& style = Base::StyleRef();
+  const ComputedStyle& cb_style = containing_block->StyleRef();
+  const auto writing_mode = cb_style.GetWritingMode();
+  const auto direction = cb_style.Direction();
+  LayoutUnit percentage_resolution_size =
+      space.PercentageResolutionInlineSizeForParentWritingMode();
+  NGBoxStrut margins = ComputePhysicalMargins(style, percentage_resolution_size)
+                           .ConvertToLogical(writing_mode, direction);
+  ResolveInlineMargins(style, cb_style, space.AvailableSize().inline_size,
+                       Base::LogicalWidth(), &margins);
+  this->SetMargin(margins.ConvertToPhysical(writing_mode, direction));
+}
+
+template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutBlockFlow>;
+template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutProgress>;
 template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutTableCaption>;
 template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutTableCell>;
-template class CORE_TEMPLATE_EXPORT LayoutNGBlockFlowMixin<LayoutBlockFlow>;
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h
index 8e67360..6b5d874 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h
@@ -10,6 +10,7 @@
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
+#include "third_party/blink/renderer/core/layout/layout_progress.h"
 #include "third_party/blink/renderer/core/layout/layout_table_caption.h"
 #include "third_party/blink/renderer/core/layout/layout_table_cell.h"
 #include "third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h"
@@ -77,6 +78,10 @@
   void DirtyLinesFromChangedChild(LayoutObject* child,
                                   MarkingBehavior marking_behavior) final;
 
+  // Intended to be called from UpdateLayout() for subclasses that want the same
+  // behavior as LayoutNGBlockFlow.
+  void UpdateNGBlockLayout();
+
   std::unique_ptr<NGInlineNodeData> ng_inline_node_data_;
   scoped_refptr<NGPaintFragment> paint_fragment_;
 
@@ -84,6 +89,7 @@
 
  private:
   void AddScrollingOverflowFromChildren();
+  void UpdateMargins(const NGConstraintSpace& space);
 };
 
 // If you edit these export templates, also update templates in
@@ -91,6 +97,8 @@
 extern template class CORE_EXTERN_TEMPLATE_EXPORT
     LayoutNGBlockFlowMixin<LayoutBlockFlow>;
 extern template class CORE_EXTERN_TEMPLATE_EXPORT
+    LayoutNGBlockFlowMixin<LayoutProgress>;
+extern template class CORE_EXTERN_TEMPLATE_EXPORT
     LayoutNGBlockFlowMixin<LayoutTableCaption>;
 extern template class CORE_EXTERN_TEMPLATE_EXPORT
     LayoutNGBlockFlowMixin<LayoutTableCell>;
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
index 88bafe7a..bc31a16 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.cc
@@ -192,6 +192,7 @@
 
 template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutBlock>;
 template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutBlockFlow>;
+template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutProgress>;
 template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutTableCaption>;
 template class CORE_TEMPLATE_EXPORT LayoutNGMixin<LayoutTableCell>;
 
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
index 7234dcf..e75f321 100644
--- a/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_mixin.h
@@ -9,6 +9,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
+#include "third_party/blink/renderer/core/layout/layout_progress.h"
 #include "third_party/blink/renderer/core/layout/layout_table_caption.h"
 #include "third_party/blink/renderer/core/layout/layout_table_cell.h"
 
@@ -37,6 +38,7 @@
 extern template class CORE_EXTERN_TEMPLATE_EXPORT LayoutNGMixin<LayoutBlock>;
 extern template class CORE_EXTERN_TEMPLATE_EXPORT
     LayoutNGMixin<LayoutBlockFlow>;
+extern template class CORE_EXTERN_TEMPLATE_EXPORT LayoutNGMixin<LayoutProgress>;
 extern template class CORE_EXTERN_TEMPLATE_EXPORT
     LayoutNGMixin<LayoutTableCaption>;
 extern template class CORE_EXTERN_TEMPLATE_EXPORT
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_progress.cc b/third_party/blink/renderer/core/layout/ng/layout_ng_progress.cc
new file mode 100644
index 0000000..9ad9421
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_progress.cc
@@ -0,0 +1,25 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/layout/ng/layout_ng_progress.h"
+
+#include "third_party/blink/renderer/core/layout/layout_object.h"
+
+namespace blink {
+
+LayoutNGProgress::LayoutNGProgress(Element* element)
+    : LayoutNGBlockFlowMixin<LayoutProgress>(element) {}
+
+LayoutNGProgress::~LayoutNGProgress() = default;
+
+void LayoutNGProgress::UpdateBlockLayout(bool relayout_children) {
+  UpdateNGBlockLayout();
+}
+
+bool LayoutNGProgress::IsOfType(LayoutObjectType type) const {
+  return type == kLayoutObjectNGProgress ||
+         LayoutNGMixin<LayoutProgress>::IsOfType(type);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h b/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h
new file mode 100644
index 0000000..7ad4c0c1
--- /dev/null
+++ b/third_party/blink/renderer/core/layout/ng/layout_ng_progress.h
@@ -0,0 +1,32 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_PROGRESS_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_PROGRESS_H_
+
+#include "third_party/blink/renderer/core/core_export.h"
+#include "third_party/blink/renderer/core/layout/layout_progress.h"
+#include "third_party/blink/renderer/core/layout/ng/layout_ng_block_flow_mixin.h"
+
+namespace blink {
+
+class CORE_EXPORT LayoutNGProgress
+    : public LayoutNGBlockFlowMixin<LayoutProgress> {
+ public:
+  explicit LayoutNGProgress(Element*);
+  ~LayoutNGProgress() override;
+
+  void UpdateBlockLayout(bool relayout_children) override;
+
+  const char* GetName() const override { return "LayoutNGProgress"; }
+
+ protected:
+  bool IsOfType(LayoutObjectType type) const override;
+};
+
+DEFINE_LAYOUT_OBJECT_TYPE_CASTS(LayoutNGProgress, IsLayoutNGProgress());
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_LAYOUT_NG_LAYOUT_NG_PROGRESS_H_
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index 07f5dcc..ac2cc67 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -1984,6 +1984,33 @@
   return block_offset >= FragmentainerSpaceAvailable();
 }
 
+LayoutUnit NGBlockLayoutAlgorithm::OffsetFromFragmentainerStart() const {
+  DCHECK(container_builder_.BfcBlockOffset());
+  return ConstraintSpace().FragmentainerOffsetAtBfc() +
+         *container_builder_.BfcBlockOffset();
+}
+
+LayoutUnit NGBlockLayoutAlgorithm::PortionIntersectingWithFragmentainer(
+    LayoutUnit block_offset,
+    LayoutUnit block_size) const {
+  LayoutUnit offset_from_fragmentainer_start =
+      OffsetFromFragmentainerStart() + block_offset;
+  // Whatever is before the block-start of the fragmentainer isn't considered to
+  // intersect with the fragmentainer, so subtract it (by adding the negative
+  // offset).
+  if (offset_from_fragmentainer_start < LayoutUnit())
+    block_size += offset_from_fragmentainer_start;
+  return block_size;
+}
+
+void NGBlockLayoutAlgorithm::PropagateUnbreakableBlockSize(
+    LayoutUnit block_offset,
+    LayoutUnit block_size) {
+  DCHECK(ConstraintSpace().IsInitialColumnBalancingPass());
+  block_size = PortionIntersectingWithFragmentainer(block_offset, block_size);
+  container_builder_.PropagateTallestUnbreakableBlockSize(block_size);
+}
+
 bool NGBlockLayoutAlgorithm::FinalizeForFragmentation() {
   if (Node().ChildrenInline() && !early_break_) {
     if (container_builder_.DidBreak() || first_overflowing_line_) {
@@ -2105,8 +2132,7 @@
         // If this is the initial column balancing pass, attempt to make the
         // column block-size at least as large as the tallest piece of
         // monolithic content and/or block with break-inside:avoid.
-        container_builder_.PropagateTallestUnbreakableBlockSize(
-            fragment.BlockSize());
+        PropagateUnbreakableBlockSize(block_offset, fragment.BlockSize());
       }
     }
     // We only care about soft breaks if we have a fragmentainer block-size.
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
index 1fa4901..231d698 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.h
@@ -221,6 +221,21 @@
   // whether fits within the fragmentainer or not.
   bool IsFragmentainerOutOfSpace(LayoutUnit block_offset) const;
 
+  // Return the block-offset from the start of the fragmentainer, to this node.
+  LayoutUnit OffsetFromFragmentainerStart() const;
+
+  // Return the block-size of the portion that intersects with the
+  // fragmentainer. The block-offset is relative to this node.
+  LayoutUnit PortionIntersectingWithFragmentainer(LayoutUnit block_offset,
+                                                  LayoutUnit block_size) const;
+
+  // Propagate the block-size of unbreakable content. This is used to inflate
+  // the initial minimal column block-size when balancing columns. Unbreakable
+  // content will actually fragment if the columns aren't large enough, and we
+  // want to prevent that, if possible.
+  void PropagateUnbreakableBlockSize(LayoutUnit block_offset,
+                                     LayoutUnit block_size);
+
   // Final adjustments before fragment creation. We need to prevent the fragment
   // from crossing fragmentainer boundaries, and rather create a break token if
   // we're out of space. As part of finalizing we may also discover that we need
diff --git a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc
index dc6ad62f..d70d4fe27 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc
@@ -3243,6 +3243,32 @@
   EXPECT_EQ(expectation, dump);
 }
 
+TEST_F(NGColumnLayoutAlgorithmTest, ColumnBalancingUnderflow) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      #parent {
+        columns: 3;
+        column-gap: 10px;
+        width: 320px;
+      }
+    </style>
+    <div id="container">
+      <div id="parent">
+        <div style="break-inside:avoid; margin-top:-100px; width:55px; height:110px;"></div>
+      </div>
+    </div>
+  )HTML");
+
+  String dump = DumpFragmentTree(GetElementById("container"));
+  String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
+  offset:unplaced size:1000x10
+    offset:0,0 size:320x10
+      offset:0,0 size:100x10
+        offset:0,-100 size:55x110
+)DUMP";
+  EXPECT_EQ(expectation, dump);
+}
+
 TEST_F(NGColumnLayoutAlgorithmTest, ClassCBreakPointBeforeBfc) {
   SetBodyInnerHTML(R"HTML(
     <style>
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
index 898d05f4..1424071 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.cc
@@ -194,8 +194,9 @@
       Vector<PhysicalRect> outline_rects;
       // The result rects are in coordinates of this object's border box.
       AddSelfOutlineRects(
-          &outline_rects, PhysicalOffset(),
-          GetLayoutObject()->OutlineRectsShouldIncludeBlockVisualOverflow());
+          PhysicalOffset(),
+          GetLayoutObject()->OutlineRectsShouldIncludeBlockVisualOverflow(),
+          &outline_rects);
       PhysicalRect rect = UnionRectEvenIfEmpty(outline_rects);
       rect.Inflate(LayoutUnit(style.OutlineOutsetExtent()));
       ink_overflow.Unite(rect);
@@ -205,11 +206,11 @@
 }
 
 void NGPhysicalBoxFragment::AddSelfOutlineRects(
-    Vector<PhysicalRect>* outline_rects,
     const PhysicalOffset& additional_offset,
-    NGOutlineType outline_type) const {
-  // TODO(kojii): Needs inline_element_continuation logic from
-  // LayoutBlockFlow::AddOutlineRects?
+    NGOutlineType outline_type,
+    Vector<PhysicalRect>* outline_rects) const {
+  if (NGOutlineUtils::IsInlineOutlineNonpaintingFragment(*this))
+    return;
 
   const LayoutObject* layout_object = GetLayoutObject();
   DCHECK(layout_object);
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
index 999537f7..6bb45d5 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -81,9 +81,9 @@
 
   // Fragment offset is this fragment's offset from parent.
   // Needed to compensate for LayoutInline Legacy code offsets.
-  void AddSelfOutlineRects(Vector<PhysicalRect>* outline_rects,
-                           const PhysicalOffset& additional_offset,
-                           NGOutlineType include_block_overflows) const;
+  void AddSelfOutlineRects(const PhysicalOffset& additional_offset,
+                           NGOutlineType include_block_overflows,
+                           Vector<PhysicalRect>* outline_rects) const;
 
   UBiDiLevel BidiLevel() const;
 
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 4eb0b44..30d48e9 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
@@ -131,8 +131,8 @@
     // may have transforms and so we have to go through LocalToAncestorRects?
     if (descendant_box->HasLayer()) {
       Vector<PhysicalRect> layer_outline_rects;
-      descendant_box->AddSelfOutlineRects(&layer_outline_rects,
-                                          PhysicalOffset(), outline_type);
+      descendant_box->AddSelfOutlineRects(PhysicalOffset(), outline_type,
+                                          &layer_outline_rects);
 
       // Don't pass additional_offset because LocalToAncestorRects will itself
       // apply it.
@@ -145,7 +145,7 @@
 
     if (descendant_layout_object->IsBox()) {
       descendant_box->AddSelfOutlineRects(
-          outline_rects, additional_offset + descendant.Offset(), outline_type);
+          additional_offset + descendant.Offset(), outline_type, outline_rects);
       return;
     }
 
diff --git a/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc b/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc
index 6cea119..6a35ba8 100644
--- a/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc
+++ b/third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/core/page/scrolling/element_fragment_anchor.h"
 
 #include "third_party/blink/renderer/core/accessibility/ax_object_cache.h"
+#include "third_party/blink/renderer/core/display_lock/display_lock_context.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/dom/node.h"
@@ -72,7 +73,7 @@
   }
 
   if (target) {
-    target->ActivateDisplayLockIfNeeded();
+    target->ActivateDisplayLockIfNeeded(DisplayLockActivationReason::kUser);
     target->DispatchActivateInvisibleEventIfNeeded();
   }
 
diff --git a/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc b/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc
index 0e873ecf..3fcbfa6 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_fragment_painter.cc
@@ -21,23 +21,22 @@
   if (!NGOutlineUtils::HasPaintedOutline(fragment.Style(), fragment.GetNode()))
     return;
 
-  // TODO(kojii): Eliminate paint_fragment_ if this is used for block-children
-  if (!paint_fragment_)
-    return;
-
   Vector<PhysicalRect> outline_rects;
-  paint_fragment_->AddSelfOutlineRects(
-      &outline_rects, paint_offset,
+  fragment.AddSelfOutlineRects(
+      paint_offset,
       fragment.GetLayoutObject()
-          ->OutlineRectsShouldIncludeBlockVisualOverflow());
+          ->OutlineRectsShouldIncludeBlockVisualOverflow(),
+      &outline_rects);
+
   if (outline_rects.IsEmpty())
     return;
 
+  const DisplayItemClient& display_item_client = GetDisplayItemClient();
   if (DrawingRecorder::UseCachedDrawingIfPossible(
-          paint_info.context, *paint_fragment_, paint_info.phase))
+          paint_info.context, display_item_client, paint_info.phase))
     return;
 
-  DrawingRecorder recorder(paint_info.context, *paint_fragment_,
+  DrawingRecorder recorder(paint_info.context, display_item_client,
                            paint_info.phase);
   PaintOutlineRects(paint_info, outline_rects, fragment.Style());
 }
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
index a5d4187..0af714e8 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.cc
@@ -859,20 +859,6 @@
   return visual_rect;
 }
 
-void NGPaintFragment::AddSelfOutlineRects(
-    Vector<PhysicalRect>* outline_rects,
-    const PhysicalOffset& additional_offset,
-    NGOutlineType outline_type) const {
-  DCHECK(outline_rects);
-  const NGPhysicalFragment& fragment = PhysicalFragment();
-  if (auto* box_fragment = DynamicTo<NGPhysicalBoxFragment>(fragment)) {
-    if (NGOutlineUtils::IsInlineOutlineNonpaintingFragment(PhysicalFragment()))
-      return;
-    box_fragment->AddSelfOutlineRects(outline_rects, additional_offset,
-                                      outline_type);
-  }
-}
-
 const NGPaintFragment* NGPaintFragment::ContainerLineBox() const {
   DCHECK(PhysicalFragment().IsInline());
   for (const NGPaintFragment* fragment :
diff --git a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
index 254f65b..6458de1 100644
--- a/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
+++ b/third_party/blink/renderer/core/paint/ng/ng_paint_fragment.h
@@ -172,10 +172,6 @@
 
   void RecalcInlineChildrenInkOverflow() const;
 
-  void AddSelfOutlineRects(Vector<PhysicalRect>*,
-                           const PhysicalOffset& offset,
-                           NGOutlineType) const;
-
   // TODO(layout-dev): Implement when we have oveflow support.
   // TODO(eae): Switch to using NG geometry types.
   bool HasOverflowClip() const { return PhysicalFragment().HasOverflowClip(); }
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index 43a05be2..cecdf63 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -817,7 +817,7 @@
           ? CompositorElementIdNamespace::kHorizontalScrollbar
           : CompositorElementIdNamespace::kVerticalScrollbar;
   return CompositorElementIdFromUniqueObjectId(
-      scrollable_element_id.GetInternalValue(), element_id_namespace);
+      scrollable_element_id.GetStableId(), element_id_namespace);
 }
 
 void ScrollableArea::OnScrollFinished() {
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.h b/third_party/blink/renderer/core/workers/worker_global_scope.h
index f11a137e..0c6b99b 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.h
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.h
@@ -203,9 +203,7 @@
   void queueMicrotask(V8VoidFunction*);
 
   TrustedTypePolicyFactory* GetTrustedTypes() const override;
-  TrustedTypePolicyFactory* trustedTypesWorkers() const {
-    return GetTrustedTypes();
-  }
+  TrustedTypePolicyFactory* trustedTypes() const { return GetTrustedTypes(); }
 
  protected:
   WorkerGlobalScope(std::unique_ptr<GlobalScopeCreationParams>,
diff --git a/third_party/blink/renderer/core/workers/worker_global_scope.idl b/third_party/blink/renderer/core/workers/worker_global_scope.idl
index d88dadaf..a6d5607 100644
--- a/third_party/blink/renderer/core/workers/worker_global_scope.idl
+++ b/third_party/blink/renderer/core/workers/worker_global_scope.idl
@@ -74,7 +74,7 @@
     readonly attribute FontFaceSet fonts;
 
     // TrustedTypes API: http://github.com/wicg/trusted-types
-    [RuntimeEnabled=TrustedDOMTypes, Unforgeable] readonly attribute TrustedTypePolicyFactory TrustedTypesWorkers;
+    [RuntimeEnabled=TrustedDOMTypes, Unforgeable] readonly attribute TrustedTypePolicyFactory trustedTypes;
 };
 
 WorkerGlobalScope includes WindowOrWorkerGlobalScope;
diff --git a/third_party/blink/renderer/devtools/.eslintignore b/third_party/blink/renderer/devtools/.eslintignore
index 79b5ff8a..9e04d37 100644
--- a/third_party/blink/renderer/devtools/.eslintignore
+++ b/third_party/blink/renderer/devtools/.eslintignore
@@ -12,7 +12,6 @@
 front_end/cm_web_modes/
 front_end/diff/diff_match_patch.js
 front_end/formatter_worker/acorn/
-front_end/terminal/xterm.js/
 front_end/protocol_externs.js
 front_end/javascript_metadata/NativeFunctions.js
 scripts/
\ No newline at end of file
diff --git a/third_party/blink/renderer/devtools/BUILD.gn b/third_party/blink/renderer/devtools/BUILD.gn
index c2aaa57..0127da1e 100644
--- a/third_party/blink/renderer/devtools/BUILD.gn
+++ b/third_party/blink/renderer/devtools/BUILD.gn
@@ -639,12 +639,6 @@
     "front_end/sources_test_runner/SearchTestRunner.js",
     "front_end/sources_test_runner/SourcesTestRunner.js",
     "front_end/sources_test_runner/module.json",
-    "front_end/terminal/module.json",
-    "front_end/terminal/terminal.css",
-    "front_end/terminal/TerminalWidget.js",
-    "front_end/terminal/xterm.js/addons/fit/fit.js",
-    "front_end/terminal/xterm.js/build/xterm.css",
-    "front_end/terminal/xterm.js/build/xterm.js",
     "front_end/test_runner/module.json",
     "front_end/test_runner/TestRunner.js",
     "front_end/text_editor/autocompleteTooltip.css",
diff --git a/third_party/blink/renderer/devtools/front_end/langpacks/devtools_ui_strings.grd b/third_party/blink/renderer/devtools/front_end/langpacks/devtools_ui_strings.grd
index e6cb21bc..f1305d6 100644
--- a/third_party/blink/renderer/devtools/front_end/langpacks/devtools_ui_strings.grd
+++ b/third_party/blink/renderer/devtools/front_end/langpacks/devtools_ui_strings.grd
@@ -63,7 +63,6 @@
       <part file="../snippets/snippets_strings.grdp" />
       <part file="../source_frame/source_frame_strings.grdp" />
       <part file="../sources/sources_strings.grdp" />
-      <part file="../terminal/terminal_strings.grdp" />
       <part file="../text_editor/text_editor_strings.grdp" />
       <part file="../timeline/timeline_strings.grdp" />
       <part file="../timeline_model/timeline_model_strings.grdp" />
diff --git a/third_party/blink/renderer/devtools/front_end/main/Main.js b/third_party/blink/renderer/devtools/front_end/main/Main.js
index e7a5ec1b..7d62c1a 100644
--- a/third_party/blink/renderer/devtools/front_end/main/Main.js
+++ b/third_party/blink/renderer/devtools/front_end/main/Main.js
@@ -138,7 +138,6 @@
     Root.Runtime.experiments.register('sourceDiff', 'Source diff');
     Root.Runtime.experiments.register('splitInDrawer', 'Split in drawer', true);
     Root.Runtime.experiments.register('spotlight', 'Spotlight', true);
-    Root.Runtime.experiments.register('terminalInDrawer', 'Terminal in drawer', true);
 
     // Timeline
     Root.Runtime.experiments.register('timelineEventInitiators', 'Timeline: event initiators');
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js b/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js
index 9feb28cb..ef16a3c 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/CPUProfileView.js
@@ -282,6 +282,15 @@
   /**
    * @override
    * @param {number} value
+   * @return {string}
+   */
+  formatValueAccessibleText(value) {
+    return this.formatValue(value);
+  }
+
+  /**
+   * @override
+   * @param {number} value
    * @param {!Profiler.ProfileDataGridNode} node
    * @return {string}
    */
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/HeapProfileView.js b/third_party/blink/renderer/devtools/front_end/profiler/HeapProfileView.js
index b3d87f7..6cb924f 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/HeapProfileView.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/HeapProfileView.js
@@ -723,6 +723,15 @@
   /**
    * @override
    * @param {number} value
+   * @return {string}
+   */
+  formatValueAccessibleText(value) {
+    return ls`${value} bytes`;
+  }
+
+  /**
+   * @override
+   * @param {number} value
    * @param {!Profiler.ProfileDataGridNode} node
    * @return {string}
    */
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/ProfileDataGrid.js b/third_party/blink/renderer/devtools/front_end/profiler/ProfileDataGrid.js
index ca88d477..7e7ca9d6 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/ProfileDataGrid.js
+++ b/third_party/blink/renderer/devtools/front_end/profiler/ProfileDataGrid.js
@@ -205,8 +205,16 @@
   _createValueCell(value, percent) {
     const cell = createElementWithClass('td', 'numeric-column');
     const div = cell.createChild('div', 'profile-multiple-values');
-    div.createChild('span').textContent = this.tree._formatter.formatValue(value, this);
-    div.createChild('span', 'percent-column').textContent = this.tree._formatter.formatPercent(percent, this);
+    const valueSpan = div.createChild('span');
+    const valueText = this.tree._formatter.formatValue(value, this);
+    valueSpan.textContent = valueText;
+    const percentSpan = div.createChild('span', 'percent-column');
+    const percentText = this.tree._formatter.formatPercent(percent, this);
+    percentSpan.textContent = percentText;
+    UI.ARIAUtils.markAsHidden(valueSpan);
+    UI.ARIAUtils.markAsHidden(percentSpan);
+    const valueAccessibleText = this.tree._formatter.formatValueAccessibleText(value, this);
+    UI.ARIAUtils.setAccessibleName(div, ls`${valueAccessibleText}, ${percentText}`);
     return cell;
   }
 
@@ -688,6 +696,12 @@
 
   /**
    * @param {number} value
+   * @return {string}
+   */
+  formatValueAccessibleText(value) {},
+
+  /**
+   * @param {number} value
    * @param {!Profiler.ProfileDataGridNode} node
    * @return {string}
    */
diff --git a/third_party/blink/renderer/devtools/front_end/profiler/profiler_strings.grdp b/third_party/blink/renderer/devtools/front_end/profiler/profiler_strings.grdp
index 81163ae..d5a959b 100644
--- a/third_party/blink/renderer/devtools/front_end/profiler/profiler_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/profiler/profiler_strings.grdp
@@ -12,6 +12,9 @@
   <message name="IDS_DEVTOOLS_030361ea56fa177ebcbefe708a45b0f2" desc="Text in Heap Snapshot Data Grids of a profiler tool">
     # Deleted
   </message>
+  <message name="IDS_DEVTOOLS_03301bd5f0d1517cf88b2f8348b82dfc" desc="Accessible text for the value in bytes of a Memory allocation.">
+    <ph name="VALUE">$1s<ex>12345</ex></ph> bytes
+  </message>
   <message name="IDS_DEVTOOLS_04042b5589b3d4fd4e1e7e44265ad247" desc="Total trend div title in Isolate Selector of a profiler tool">
     Total page JS heap size change trend over the last <ph name="TRENDINTERVALMINUTES">$1s<ex>3</ex></ph> minutes.
   </message>
diff --git a/third_party/blink/renderer/devtools/front_end/shell.json b/third_party/blink/renderer/devtools/front_end/shell.json
index eaeb2dd..98efbd3 100644
--- a/third_party/blink/renderer/devtools/front_end/shell.json
+++ b/third_party/blink/renderer/devtools/front_end/shell.json
@@ -41,7 +41,6 @@
     { "name": "snippets" },
     { "name": "source_frame" },
     { "name": "sources" },
-    { "name": "terminal", "type": "remote" },
     { "name": "text_editor" },
     { "name": "workspace_diff" },
     { "name": "protocol_monitor"}
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/TerminalWidget.js b/third_party/blink/renderer/devtools/front_end/terminal/TerminalWidget.js
deleted file mode 100644
index 4ec72111..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/TerminalWidget.js
+++ /dev/null
@@ -1,154 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-/**
- * @unrestricted
- */
-Terminal.TerminalWidget = class extends UI.VBox {
-  constructor() {
-    super(true);
-    this.registerRequiredCSS('terminal/xterm.js/build/xterm.css');
-    this.registerRequiredCSS('terminal/terminal.css');
-    this.element.classList.add('terminal-root');
-    this._init();
-    this._linkifier = new Components.Linkifier();
-    this._config = {attributes: true, childList: true, characterData: true, subtree: true};
-  }
-
-  async _init() {
-    const backend = await Services.serviceManager.createRemoteService('Terminal');
-    this._initialized(backend);
-  }
-
-  /**
-   * @param {?Services.ServiceManager.Service} backend
-   */
-  _initialized(backend) {
-    if (!backend) {
-      if (!this._unavailableLabel) {
-        this._unavailableLabel = this.contentElement.createChild('div', 'terminal-error-message fill');
-        this._unavailableLabel.createChild('div').textContent = Common.UIString('Terminal service is not available');
-      }
-      setTimeout(this._init.bind(this), 2000);
-      return;
-    }
-
-    if (this._unavailableLabel) {
-      this._unavailableLabel.remove();
-      delete this._unavailableLabel;
-    }
-
-    this._backend = backend;
-
-    if (!this._term) {
-      this._term = new Terminal({cursorBlink: true});
-      this._term.open(this.contentElement);
-      this._mutationObserver = new MutationObserver(this._linkify.bind(this));
-      this._mutationObserver.observe(this.contentElement, this._config);
-      this._term.on('data', data => {
-        this._backend.send('write', {data: data});
-      });
-      this._term.fit();
-      this._term.on('resize', size => {
-        this._backend.send('resize', {cols: size.cols, rows: size.rows});
-      });
-    }
-
-    this._backend.send('init', {cols: this._term.cols, rows: this._term.rows});
-    this._backend.on('data', result => {
-      this._term.write(result.data);
-    });
-    this._backend.on('disposed', this._disposed.bind(this));
-  }
-
-  /**
-   * @override
-   */
-  onResize() {
-    if (this._term) {
-      this._term.fit();
-    }
-  }
-
-  _disposed() {
-    this._initialized(null);
-  }
-
-  /**
-   * @override
-   */
-  ownerViewDisposed() {
-    if (this._backend) {
-      this._backend.dispose();
-    }
-  }
-
-  _linkify() {
-    this._mutationObserver.takeRecords();
-    this._mutationObserver.disconnect();
-    this._linkifier.reset();
-    const rows = this._term['rowContainer'].children;
-    for (let i = 0; i < rows.length; i++) {
-      this._linkifyTerminalLine(rows[i]);
-    }
-    this._mutationObserver.observe(this.contentElement, this._config);
-  }
-
-  /**
-   * @param {string} string
-   */
-  _linkifyText(string) {
-    const regex1 = /([/\w\.-]*)+\:([\d]+)(?:\:([\d]+))?/;
-    const regex2 = /([/\w\.-]*)+\(([\d]+),([\d]+)\)/;
-    const container = createDocumentFragment();
-
-    while (string) {
-      const linkString = regex1.exec(string) || regex2.exec(string);
-      if (!linkString) {
-        break;
-      }
-
-      const text = linkString[0];
-      const path = linkString[1];
-      const lineNumber = parseInt(linkString[2], 10) - 1 || 0;
-      const columnNumber = parseInt(linkString[3], 10) - 1 || 0;
-
-      const uiSourceCode = Workspace.workspace.uiSourceCodes().find(uisc => uisc.url().endsWith(path));
-      const linkIndex = string.indexOf(text);
-      const nonLink = string.substring(0, linkIndex);
-      container.appendChild(createTextNode(nonLink));
-
-      if (uiSourceCode) {
-        container.appendChild(Components.Linkifier.linkifyURL(
-            uiSourceCode.url(),
-            {text, lineNumber, columnNumber, maxLengh: Number.MAX_VALUE, className: 'terminal-link'}));
-      } else {
-        container.appendChild(createTextNode(text));
-      }
-      string = string.substring(linkIndex + text.length);
-    }
-
-    if (string) {
-      container.appendChild(createTextNode(string));
-    }
-    return container;
-  }
-
-  /**
-   * @param {!Node} line
-   */
-  _linkifyTerminalLine(line) {
-    let node = line.firstChild;
-    while (node) {
-      if (node.nodeType !== Node.TEXT_NODE) {
-        node = node.nextSibling;
-        continue;
-      }
-      const nextNode = node.nextSibling;
-      node.remove();
-      const linkified = this._linkifyText(node.textContent);
-      line.insertBefore(linkified, nextNode);
-      node = nextNode;
-    }
-  }
-};
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/module.json b/third_party/blink/renderer/devtools/front_end/terminal/module.json
deleted file mode 100644
index 6802e2b4..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/module.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
-    "extensions": [
-        {
-            "type": "view",
-            "location": "drawer-sidebar",
-            "id": "drawer.xterm",
-            "title": "Terminal",
-            "order": 10,
-            "factoryName": "Terminal.TerminalWidget"
-        }
-    ],
-    "dependencies": [
-        "components",
-        "ui",
-        "services"
-    ],
-    "experiment": "terminalInDrawer",
-    "scripts": [
-        "xterm.js/build/xterm.js",
-        "xterm.js/addons/fit/fit.js",
-        "TerminalWidget.js"
-    ],
-    "skip_compilation": [
-        "xterm.js/build/xterm.js",
-        "xterm.js/addons/fit/fit.js"
-    ],
-    "resources": [
-        "terminal.css",
-        "xterm.js/build/xterm.css"
-    ]
-}
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/terminal.css b/third_party/blink/renderer/devtools/front_end/terminal/terminal.css
deleted file mode 100644
index 17fa5a9..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/terminal.css
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2016 The Chromium Authors. All rights reserved.
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-.terminal-root {
-    background-color: #111;
-    color: #fafafa;
-    padding: 2px;
-    -webkit-user-select: text;
-    white-space: nowrap;
-}
-
-.terminal-error-message {
-    display: flex;
-    align-items: center;
-    padding: 10px;
-    background-color: rgba(255, 255, 255, 0.8);
-    justify-content: center;
-    font-size: 16px;
-    color: #222;
-}
-
-.terminal-error-message div {
-    padding-right: 10px;
-}
-
-.terminal-link {
-    color: inherit;
-    text-decoration: inherit;
-}
-
-.terminal-link:hover {
-    text-decoration: underline;
-    cursor: pointer;
-}
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/terminal_strings.grdp b/third_party/blink/renderer/devtools/front_end/terminal/terminal_strings.grdp
deleted file mode 100644
index 5561a9f..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/terminal_strings.grdp
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<grit-part>
-  <message name="IDS_DEVTOOLS_514d8a494f087c0d549b9536c2ef3bd9" desc="Title of the 'Terminal' tool in the sidebar of the drawer tool">
-    Terminal
-  </message>
-  <message name="IDS_DEVTOOLS_69685b2a57646d0d0bc125b5f94f3931" desc="Text in Terminal Widget of the web version terminal">
-    Terminal service is not available
-  </message>
-</grit-part>
\ No newline at end of file
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/LICENSE b/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/LICENSE
deleted file mode 100644
index 1ed6f2a..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-Copyright (c) 2014, sourceLair Limited (https://github.com/sourcelair/)
-Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/README.chromium b/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/README.chromium
deleted file mode 100644
index 662bc38..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/README.chromium
+++ /dev/null
@@ -1,7 +0,0 @@
-Name: Xterm.js is a terminal front-end component written in JavaScript that works in the browser.
-Short Name: xterm.js
-URL: https://github.com/sourcelair/xterm.js
-License: MIT
-Security Critical: no
-
-This directory contains Chrome's version of xterm.js with tests, demo and some addons folders removed.
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/addons/fit/fit.js b/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/addons/fit/fit.js
deleted file mode 100644
index 46b79e9b..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/addons/fit/fit.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- * Fit terminal columns and rows to the dimensions of its DOM element.
- *
- * ## Approach
- * - Rows: Truncate the division of the terminal parent element height by the terminal row height.
- *
- * - Columns: Truncate the division of the terminal parent element width by the terminal character
- * width (apply display: inline at the terminal row and truncate its width with the current
- * number of columns).
- * @module xterm/addons/fit/fit
- * @license MIT
- */
-
-(function (fit) {
-  if (typeof exports === 'object' && typeof module === 'object') {
-    /*
-     * CommonJS environment
-     */
-    module.exports = fit(require('../../xterm'));
-  } else if (typeof define == 'function') {
-    /*
-     * Require.js is available
-     */
-    define(['../../xterm'], fit);
-  } else {
-    /*
-     * Plain browser environment
-     */
-    fit(window.Terminal);
-  }
-})(function (Xterm) {
-  var exports = {};
-
-  exports.proposeGeometry = function (term) {
-    if (!term.element.parentElement) {
-      return null;
-    }
-    var parentElementStyle = window.getComputedStyle(term.element.parentElement),
-        parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
-        parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
-        elementStyle = window.getComputedStyle(term.element),
-        elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
-        elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
-        availableHeight = parentElementHeight - elementPaddingVer,
-        availableWidth = parentElementWidth - elementPaddingHor,
-        container = term.rowContainer,
-        subjectRow = term.rowContainer.firstElementChild,
-        contentBuffer = subjectRow.innerHTML,
-        characterHeight,
-        rows,
-        characterWidth,
-        cols,
-        geometry;
-
-    subjectRow.style.display = 'inline';
-    subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
-    characterWidth = subjectRow.getBoundingClientRect().width;
-    subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
-    characterHeight = subjectRow.getBoundingClientRect().height;
-    subjectRow.innerHTML = contentBuffer;
-
-    rows = parseInt(availableHeight / characterHeight);
-    cols = parseInt(availableWidth / characterWidth);
-
-    geometry = {cols: cols, rows: rows};
-    return geometry;
-  };
-
-  exports.fit = function (term) {
-    var geometry = exports.proposeGeometry(term);
-
-    if (geometry) {
-      term.resize(geometry.cols, geometry.rows);
-    }
-  };
-
-  Xterm.prototype.proposeGeometry = function () {
-    return exports.proposeGeometry(this);
-  };
-
-  Xterm.prototype.fit = function () {
-    return exports.fit(this);
-  };
-
-  return exports;
-});
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/addons/fit/package.json b/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/addons/fit/package.json
deleted file mode 100644
index f7cb5bc..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/addons/fit/package.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "xterm.fit",
-  "main": "fit.js",
-  "private": true
-}
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/build/xterm.css b/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/build/xterm.css
deleted file mode 100644
index efdc016..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/build/xterm.css
+++ /dev/null
@@ -1,2248 +0,0 @@
-/**
- * xterm.js: xterm, in the browser
- * Copyright (c) 2014-2016, SourceLair Private Company (www.sourcelair.com (MIT License)
- * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
- * https://github.com/chjj/term.js
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * Originally forked from (with the author's permission):
- *   Fabrice Bellard's javascript vt100 for jslinux:
- *   http://bellard.org/jslinux/
- *   Copyright (c) 2011 Fabrice Bellard
- *   The original design remains. The terminal itself
- *   has been extended to include xterm CSI codes, among
- *   other features.
- */
-
-/*
- *  Default style for xterm.js
- */
-
-.terminal {
-    background-color: #000;
-    color: #fff;
-    font-family: courier-new, courier, monospace;
-    font-feature-settings: "liga" 0;
-    position: relative;
-}
-
-.terminal.focus,
-.terminal:focus {
-    outline: none;
-}
-
-.terminal .xterm-helpers {
-    position: absolute;
-    top: 0;
-}
-
-.terminal .xterm-helper-textarea {
-    /*
-     * HACK: to fix IE's blinking cursor
-     * Move textarea out of the screen to the far left, so that the cursor is not visible.
-     */
-    position: absolute;
-    opacity: 0;
-    left: -9999em;
-    top: 0;
-    width: 0;
-    height: 0;
-    z-index: -10;
-    /** Prevent wrapping so the IME appears against the textarea at the correct position */
-    white-space: nowrap;
-    overflow: hidden;
-    resize: none;
-}
-
-.terminal a {
-    color: inherit;
-    text-decoration: none;
-}
-
-.terminal a:hover {
-    cursor: pointer;
-    text-decoration: underline;
-}
-
-.terminal a.xterm-invalid-link:hover {
-    cursor: text;
-    text-decoration: none;
-}
-
-.terminal.focus:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar) .terminal-cursor {
-    background-color: #fff;
-    color: #000;
-}
-
-.terminal:not(.focus) .terminal-cursor {
-    outline: 1px solid #fff;
-    outline-offset: -1px;
-    background-color: transparent;
-}
-
-.terminal:not(.xterm-cursor-style-underline):not(.xterm-cursor-style-bar).focus.xterm-cursor-blink-on .terminal-cursor {
-    background-color: transparent;
-    color: inherit;
-}
-
-.terminal.xterm-cursor-style-bar .terminal-cursor,
-.terminal.xterm-cursor-style-underline .terminal-cursor {
-    position: relative;
-}
-.terminal.xterm-cursor-style-bar .terminal-cursor::before,
-.terminal.xterm-cursor-style-underline .terminal-cursor::before {
-    content: "";
-    display: block;
-    position: absolute;
-    background-color: #fff;
-}
-.terminal.xterm-cursor-style-bar .terminal-cursor::before {
-    top: 0;
-    bottom: 0;
-    left: 0;
-    width: 1px;
-}
-.terminal.xterm-cursor-style-underline .terminal-cursor::before {
-    bottom: 0;
-    left: 0;
-    right: 0;
-    height: 1px;
-}
-.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before,
-.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink.xterm-cursor-blink-on .terminal-cursor::before {
-    background-color: transparent;
-}
-.terminal.xterm-cursor-style-bar.focus.xterm-cursor-blink .terminal-cursor::before,
-.terminal.xterm-cursor-style-underline.focus.xterm-cursor-blink .terminal-cursor::before {
-    background-color: #fff;
-}
-
-.terminal .composition-view {
-    background: #000;
-    color: #FFF;
-    display: none;
-    position: absolute;
-    white-space: nowrap;
-    z-index: 1;
-}
-
-.terminal .composition-view.active {
-    display: block;
-}
-
-.terminal .xterm-viewport {
-    /* On OS X this is required in order for the scroll bar to appear fully opaque */
-    background-color: #000;
-    overflow-y: scroll;
-}
-
-.terminal .xterm-wide-char,
-.terminal .xterm-normal-char {
-    display: inline-block;
-}
-
-.terminal .xterm-rows {
-    position: absolute;
-    left: 0;
-    top: 0;
-}
-
-.terminal .xterm-rows > div {
-    /* Lines containing spans and text nodes ocassionally wrap despite being the same width (#327) */
-    white-space: nowrap;
-}
-
-.terminal .xterm-scroll-area {
-    visibility: hidden;
-}
-
-.terminal .xterm-char-measure-element {
-    display: inline-block;
-    visibility: hidden;
-    position: absolute;
-    left: -9999em;
-}
-
-/*
- *  Determine default colors for xterm.js
- */
-.terminal .xterm-bold {
-    font-weight: bold;
-}
-
-.terminal .xterm-underline {
-    text-decoration: underline;
-}
-
-.terminal .xterm-blink {
-    text-decoration: blink;
-}
-
-.terminal .xterm-hidden {
-    visibility: hidden;
-}
-
-.terminal .xterm-color-0 {
-    color: #2e3436;
-}
-
-.terminal .xterm-bg-color-0 {
-    background-color: #2e3436;
-}
-
-.terminal .xterm-color-1 {
-    color: #cc0000;
-}
-
-.terminal .xterm-bg-color-1 {
-    background-color: #cc0000;
-}
-
-.terminal .xterm-color-2 {
-    color: #4e9a06;
-}
-
-.terminal .xterm-bg-color-2 {
-    background-color: #4e9a06;
-}
-
-.terminal .xterm-color-3 {
-    color: #c4a000;
-}
-
-.terminal .xterm-bg-color-3 {
-    background-color: #c4a000;
-}
-
-.terminal .xterm-color-4 {
-    color: #3465a4;
-}
-
-.terminal .xterm-bg-color-4 {
-    background-color: #3465a4;
-}
-
-.terminal .xterm-color-5 {
-    color: #75507b;
-}
-
-.terminal .xterm-bg-color-5 {
-    background-color: #75507b;
-}
-
-.terminal .xterm-color-6 {
-    color: #06989a;
-}
-
-.terminal .xterm-bg-color-6 {
-    background-color: #06989a;
-}
-
-.terminal .xterm-color-7 {
-    color: #d3d7cf;
-}
-
-.terminal .xterm-bg-color-7 {
-    background-color: #d3d7cf;
-}
-
-.terminal .xterm-color-8 {
-    color: #555753;
-}
-
-.terminal .xterm-bg-color-8 {
-    background-color: #555753;
-}
-
-.terminal .xterm-color-9 {
-    color: #ef2929;
-}
-
-.terminal .xterm-bg-color-9 {
-    background-color: #ef2929;
-}
-
-.terminal .xterm-color-10 {
-    color: #8ae234;
-}
-
-.terminal .xterm-bg-color-10 {
-    background-color: #8ae234;
-}
-
-.terminal .xterm-color-11 {
-    color: #fce94f;
-}
-
-.terminal .xterm-bg-color-11 {
-    background-color: #fce94f;
-}
-
-.terminal .xterm-color-12 {
-    color: #729fcf;
-}
-
-.terminal .xterm-bg-color-12 {
-    background-color: #729fcf;
-}
-
-.terminal .xterm-color-13 {
-    color: #ad7fa8;
-}
-
-.terminal .xterm-bg-color-13 {
-    background-color: #ad7fa8;
-}
-
-.terminal .xterm-color-14 {
-    color: #34e2e2;
-}
-
-.terminal .xterm-bg-color-14 {
-    background-color: #34e2e2;
-}
-
-.terminal .xterm-color-15 {
-    color: #eeeeec;
-}
-
-.terminal .xterm-bg-color-15 {
-    background-color: #eeeeec;
-}
-
-.terminal .xterm-color-16 {
-    color: #000000;
-}
-
-.terminal .xterm-bg-color-16 {
-    background-color: #000000;
-}
-
-.terminal .xterm-color-17 {
-    color: #00005f;
-}
-
-.terminal .xterm-bg-color-17 {
-    background-color: #00005f;
-}
-
-.terminal .xterm-color-18 {
-    color: #000087;
-}
-
-.terminal .xterm-bg-color-18 {
-    background-color: #000087;
-}
-
-.terminal .xterm-color-19 {
-    color: #0000af;
-}
-
-.terminal .xterm-bg-color-19 {
-    background-color: #0000af;
-}
-
-.terminal .xterm-color-20 {
-    color: #0000d7;
-}
-
-.terminal .xterm-bg-color-20 {
-    background-color: #0000d7;
-}
-
-.terminal .xterm-color-21 {
-    color: #0000ff;
-}
-
-.terminal .xterm-bg-color-21 {
-    background-color: #0000ff;
-}
-
-.terminal .xterm-color-22 {
-    color: #005f00;
-}
-
-.terminal .xterm-bg-color-22 {
-    background-color: #005f00;
-}
-
-.terminal .xterm-color-23 {
-    color: #005f5f;
-}
-
-.terminal .xterm-bg-color-23 {
-    background-color: #005f5f;
-}
-
-.terminal .xterm-color-24 {
-    color: #005f87;
-}
-
-.terminal .xterm-bg-color-24 {
-    background-color: #005f87;
-}
-
-.terminal .xterm-color-25 {
-    color: #005faf;
-}
-
-.terminal .xterm-bg-color-25 {
-    background-color: #005faf;
-}
-
-.terminal .xterm-color-26 {
-    color: #005fd7;
-}
-
-.terminal .xterm-bg-color-26 {
-    background-color: #005fd7;
-}
-
-.terminal .xterm-color-27 {
-    color: #005fff;
-}
-
-.terminal .xterm-bg-color-27 {
-    background-color: #005fff;
-}
-
-.terminal .xterm-color-28 {
-    color: #008700;
-}
-
-.terminal .xterm-bg-color-28 {
-    background-color: #008700;
-}
-
-.terminal .xterm-color-29 {
-    color: #00875f;
-}
-
-.terminal .xterm-bg-color-29 {
-    background-color: #00875f;
-}
-
-.terminal .xterm-color-30 {
-    color: #008787;
-}
-
-.terminal .xterm-bg-color-30 {
-    background-color: #008787;
-}
-
-.terminal .xterm-color-31 {
-    color: #0087af;
-}
-
-.terminal .xterm-bg-color-31 {
-    background-color: #0087af;
-}
-
-.terminal .xterm-color-32 {
-    color: #0087d7;
-}
-
-.terminal .xterm-bg-color-32 {
-    background-color: #0087d7;
-}
-
-.terminal .xterm-color-33 {
-    color: #0087ff;
-}
-
-.terminal .xterm-bg-color-33 {
-    background-color: #0087ff;
-}
-
-.terminal .xterm-color-34 {
-    color: #00af00;
-}
-
-.terminal .xterm-bg-color-34 {
-    background-color: #00af00;
-}
-
-.terminal .xterm-color-35 {
-    color: #00af5f;
-}
-
-.terminal .xterm-bg-color-35 {
-    background-color: #00af5f;
-}
-
-.terminal .xterm-color-36 {
-    color: #00af87;
-}
-
-.terminal .xterm-bg-color-36 {
-    background-color: #00af87;
-}
-
-.terminal .xterm-color-37 {
-    color: #00afaf;
-}
-
-.terminal .xterm-bg-color-37 {
-    background-color: #00afaf;
-}
-
-.terminal .xterm-color-38 {
-    color: #00afd7;
-}
-
-.terminal .xterm-bg-color-38 {
-    background-color: #00afd7;
-}
-
-.terminal .xterm-color-39 {
-    color: #00afff;
-}
-
-.terminal .xterm-bg-color-39 {
-    background-color: #00afff;
-}
-
-.terminal .xterm-color-40 {
-    color: #00d700;
-}
-
-.terminal .xterm-bg-color-40 {
-    background-color: #00d700;
-}
-
-.terminal .xterm-color-41 {
-    color: #00d75f;
-}
-
-.terminal .xterm-bg-color-41 {
-    background-color: #00d75f;
-}
-
-.terminal .xterm-color-42 {
-    color: #00d787;
-}
-
-.terminal .xterm-bg-color-42 {
-    background-color: #00d787;
-}
-
-.terminal .xterm-color-43 {
-    color: #00d7af;
-}
-
-.terminal .xterm-bg-color-43 {
-    background-color: #00d7af;
-}
-
-.terminal .xterm-color-44 {
-    color: #00d7d7;
-}
-
-.terminal .xterm-bg-color-44 {
-    background-color: #00d7d7;
-}
-
-.terminal .xterm-color-45 {
-    color: #00d7ff;
-}
-
-.terminal .xterm-bg-color-45 {
-    background-color: #00d7ff;
-}
-
-.terminal .xterm-color-46 {
-    color: #00ff00;
-}
-
-.terminal .xterm-bg-color-46 {
-    background-color: #00ff00;
-}
-
-.terminal .xterm-color-47 {
-    color: #00ff5f;
-}
-
-.terminal .xterm-bg-color-47 {
-    background-color: #00ff5f;
-}
-
-.terminal .xterm-color-48 {
-    color: #00ff87;
-}
-
-.terminal .xterm-bg-color-48 {
-    background-color: #00ff87;
-}
-
-.terminal .xterm-color-49 {
-    color: #00ffaf;
-}
-
-.terminal .xterm-bg-color-49 {
-    background-color: #00ffaf;
-}
-
-.terminal .xterm-color-50 {
-    color: #00ffd7;
-}
-
-.terminal .xterm-bg-color-50 {
-    background-color: #00ffd7;
-}
-
-.terminal .xterm-color-51 {
-    color: #00ffff;
-}
-
-.terminal .xterm-bg-color-51 {
-    background-color: #00ffff;
-}
-
-.terminal .xterm-color-52 {
-    color: #5f0000;
-}
-
-.terminal .xterm-bg-color-52 {
-    background-color: #5f0000;
-}
-
-.terminal .xterm-color-53 {
-    color: #5f005f;
-}
-
-.terminal .xterm-bg-color-53 {
-    background-color: #5f005f;
-}
-
-.terminal .xterm-color-54 {
-    color: #5f0087;
-}
-
-.terminal .xterm-bg-color-54 {
-    background-color: #5f0087;
-}
-
-.terminal .xterm-color-55 {
-    color: #5f00af;
-}
-
-.terminal .xterm-bg-color-55 {
-    background-color: #5f00af;
-}
-
-.terminal .xterm-color-56 {
-    color: #5f00d7;
-}
-
-.terminal .xterm-bg-color-56 {
-    background-color: #5f00d7;
-}
-
-.terminal .xterm-color-57 {
-    color: #5f00ff;
-}
-
-.terminal .xterm-bg-color-57 {
-    background-color: #5f00ff;
-}
-
-.terminal .xterm-color-58 {
-    color: #5f5f00;
-}
-
-.terminal .xterm-bg-color-58 {
-    background-color: #5f5f00;
-}
-
-.terminal .xterm-color-59 {
-    color: #5f5f5f;
-}
-
-.terminal .xterm-bg-color-59 {
-    background-color: #5f5f5f;
-}
-
-.terminal .xterm-color-60 {
-    color: #5f5f87;
-}
-
-.terminal .xterm-bg-color-60 {
-    background-color: #5f5f87;
-}
-
-.terminal .xterm-color-61 {
-    color: #5f5faf;
-}
-
-.terminal .xterm-bg-color-61 {
-    background-color: #5f5faf;
-}
-
-.terminal .xterm-color-62 {
-    color: #5f5fd7;
-}
-
-.terminal .xterm-bg-color-62 {
-    background-color: #5f5fd7;
-}
-
-.terminal .xterm-color-63 {
-    color: #5f5fff;
-}
-
-.terminal .xterm-bg-color-63 {
-    background-color: #5f5fff;
-}
-
-.terminal .xterm-color-64 {
-    color: #5f8700;
-}
-
-.terminal .xterm-bg-color-64 {
-    background-color: #5f8700;
-}
-
-.terminal .xterm-color-65 {
-    color: #5f875f;
-}
-
-.terminal .xterm-bg-color-65 {
-    background-color: #5f875f;
-}
-
-.terminal .xterm-color-66 {
-    color: #5f8787;
-}
-
-.terminal .xterm-bg-color-66 {
-    background-color: #5f8787;
-}
-
-.terminal .xterm-color-67 {
-    color: #5f87af;
-}
-
-.terminal .xterm-bg-color-67 {
-    background-color: #5f87af;
-}
-
-.terminal .xterm-color-68 {
-    color: #5f87d7;
-}
-
-.terminal .xterm-bg-color-68 {
-    background-color: #5f87d7;
-}
-
-.terminal .xterm-color-69 {
-    color: #5f87ff;
-}
-
-.terminal .xterm-bg-color-69 {
-    background-color: #5f87ff;
-}
-
-.terminal .xterm-color-70 {
-    color: #5faf00;
-}
-
-.terminal .xterm-bg-color-70 {
-    background-color: #5faf00;
-}
-
-.terminal .xterm-color-71 {
-    color: #5faf5f;
-}
-
-.terminal .xterm-bg-color-71 {
-    background-color: #5faf5f;
-}
-
-.terminal .xterm-color-72 {
-    color: #5faf87;
-}
-
-.terminal .xterm-bg-color-72 {
-    background-color: #5faf87;
-}
-
-.terminal .xterm-color-73 {
-    color: #5fafaf;
-}
-
-.terminal .xterm-bg-color-73 {
-    background-color: #5fafaf;
-}
-
-.terminal .xterm-color-74 {
-    color: #5fafd7;
-}
-
-.terminal .xterm-bg-color-74 {
-    background-color: #5fafd7;
-}
-
-.terminal .xterm-color-75 {
-    color: #5fafff;
-}
-
-.terminal .xterm-bg-color-75 {
-    background-color: #5fafff;
-}
-
-.terminal .xterm-color-76 {
-    color: #5fd700;
-}
-
-.terminal .xterm-bg-color-76 {
-    background-color: #5fd700;
-}
-
-.terminal .xterm-color-77 {
-    color: #5fd75f;
-}
-
-.terminal .xterm-bg-color-77 {
-    background-color: #5fd75f;
-}
-
-.terminal .xterm-color-78 {
-    color: #5fd787;
-}
-
-.terminal .xterm-bg-color-78 {
-    background-color: #5fd787;
-}
-
-.terminal .xterm-color-79 {
-    color: #5fd7af;
-}
-
-.terminal .xterm-bg-color-79 {
-    background-color: #5fd7af;
-}
-
-.terminal .xterm-color-80 {
-    color: #5fd7d7;
-}
-
-.terminal .xterm-bg-color-80 {
-    background-color: #5fd7d7;
-}
-
-.terminal .xterm-color-81 {
-    color: #5fd7ff;
-}
-
-.terminal .xterm-bg-color-81 {
-    background-color: #5fd7ff;
-}
-
-.terminal .xterm-color-82 {
-    color: #5fff00;
-}
-
-.terminal .xterm-bg-color-82 {
-    background-color: #5fff00;
-}
-
-.terminal .xterm-color-83 {
-    color: #5fff5f;
-}
-
-.terminal .xterm-bg-color-83 {
-    background-color: #5fff5f;
-}
-
-.terminal .xterm-color-84 {
-    color: #5fff87;
-}
-
-.terminal .xterm-bg-color-84 {
-    background-color: #5fff87;
-}
-
-.terminal .xterm-color-85 {
-    color: #5fffaf;
-}
-
-.terminal .xterm-bg-color-85 {
-    background-color: #5fffaf;
-}
-
-.terminal .xterm-color-86 {
-    color: #5fffd7;
-}
-
-.terminal .xterm-bg-color-86 {
-    background-color: #5fffd7;
-}
-
-.terminal .xterm-color-87 {
-    color: #5fffff;
-}
-
-.terminal .xterm-bg-color-87 {
-    background-color: #5fffff;
-}
-
-.terminal .xterm-color-88 {
-    color: #870000;
-}
-
-.terminal .xterm-bg-color-88 {
-    background-color: #870000;
-}
-
-.terminal .xterm-color-89 {
-    color: #87005f;
-}
-
-.terminal .xterm-bg-color-89 {
-    background-color: #87005f;
-}
-
-.terminal .xterm-color-90 {
-    color: #870087;
-}
-
-.terminal .xterm-bg-color-90 {
-    background-color: #870087;
-}
-
-.terminal .xterm-color-91 {
-    color: #8700af;
-}
-
-.terminal .xterm-bg-color-91 {
-    background-color: #8700af;
-}
-
-.terminal .xterm-color-92 {
-    color: #8700d7;
-}
-
-.terminal .xterm-bg-color-92 {
-    background-color: #8700d7;
-}
-
-.terminal .xterm-color-93 {
-    color: #8700ff;
-}
-
-.terminal .xterm-bg-color-93 {
-    background-color: #8700ff;
-}
-
-.terminal .xterm-color-94 {
-    color: #875f00;
-}
-
-.terminal .xterm-bg-color-94 {
-    background-color: #875f00;
-}
-
-.terminal .xterm-color-95 {
-    color: #875f5f;
-}
-
-.terminal .xterm-bg-color-95 {
-    background-color: #875f5f;
-}
-
-.terminal .xterm-color-96 {
-    color: #875f87;
-}
-
-.terminal .xterm-bg-color-96 {
-    background-color: #875f87;
-}
-
-.terminal .xterm-color-97 {
-    color: #875faf;
-}
-
-.terminal .xterm-bg-color-97 {
-    background-color: #875faf;
-}
-
-.terminal .xterm-color-98 {
-    color: #875fd7;
-}
-
-.terminal .xterm-bg-color-98 {
-    background-color: #875fd7;
-}
-
-.terminal .xterm-color-99 {
-    color: #875fff;
-}
-
-.terminal .xterm-bg-color-99 {
-    background-color: #875fff;
-}
-
-.terminal .xterm-color-100 {
-    color: #878700;
-}
-
-.terminal .xterm-bg-color-100 {
-    background-color: #878700;
-}
-
-.terminal .xterm-color-101 {
-    color: #87875f;
-}
-
-.terminal .xterm-bg-color-101 {
-    background-color: #87875f;
-}
-
-.terminal .xterm-color-102 {
-    color: #878787;
-}
-
-.terminal .xterm-bg-color-102 {
-    background-color: #878787;
-}
-
-.terminal .xterm-color-103 {
-    color: #8787af;
-}
-
-.terminal .xterm-bg-color-103 {
-    background-color: #8787af;
-}
-
-.terminal .xterm-color-104 {
-    color: #8787d7;
-}
-
-.terminal .xterm-bg-color-104 {
-    background-color: #8787d7;
-}
-
-.terminal .xterm-color-105 {
-    color: #8787ff;
-}
-
-.terminal .xterm-bg-color-105 {
-    background-color: #8787ff;
-}
-
-.terminal .xterm-color-106 {
-    color: #87af00;
-}
-
-.terminal .xterm-bg-color-106 {
-    background-color: #87af00;
-}
-
-.terminal .xterm-color-107 {
-    color: #87af5f;
-}
-
-.terminal .xterm-bg-color-107 {
-    background-color: #87af5f;
-}
-
-.terminal .xterm-color-108 {
-    color: #87af87;
-}
-
-.terminal .xterm-bg-color-108 {
-    background-color: #87af87;
-}
-
-.terminal .xterm-color-109 {
-    color: #87afaf;
-}
-
-.terminal .xterm-bg-color-109 {
-    background-color: #87afaf;
-}
-
-.terminal .xterm-color-110 {
-    color: #87afd7;
-}
-
-.terminal .xterm-bg-color-110 {
-    background-color: #87afd7;
-}
-
-.terminal .xterm-color-111 {
-    color: #87afff;
-}
-
-.terminal .xterm-bg-color-111 {
-    background-color: #87afff;
-}
-
-.terminal .xterm-color-112 {
-    color: #87d700;
-}
-
-.terminal .xterm-bg-color-112 {
-    background-color: #87d700;
-}
-
-.terminal .xterm-color-113 {
-    color: #87d75f;
-}
-
-.terminal .xterm-bg-color-113 {
-    background-color: #87d75f;
-}
-
-.terminal .xterm-color-114 {
-    color: #87d787;
-}
-
-.terminal .xterm-bg-color-114 {
-    background-color: #87d787;
-}
-
-.terminal .xterm-color-115 {
-    color: #87d7af;
-}
-
-.terminal .xterm-bg-color-115 {
-    background-color: #87d7af;
-}
-
-.terminal .xterm-color-116 {
-    color: #87d7d7;
-}
-
-.terminal .xterm-bg-color-116 {
-    background-color: #87d7d7;
-}
-
-.terminal .xterm-color-117 {
-    color: #87d7ff;
-}
-
-.terminal .xterm-bg-color-117 {
-    background-color: #87d7ff;
-}
-
-.terminal .xterm-color-118 {
-    color: #87ff00;
-}
-
-.terminal .xterm-bg-color-118 {
-    background-color: #87ff00;
-}
-
-.terminal .xterm-color-119 {
-    color: #87ff5f;
-}
-
-.terminal .xterm-bg-color-119 {
-    background-color: #87ff5f;
-}
-
-.terminal .xterm-color-120 {
-    color: #87ff87;
-}
-
-.terminal .xterm-bg-color-120 {
-    background-color: #87ff87;
-}
-
-.terminal .xterm-color-121 {
-    color: #87ffaf;
-}
-
-.terminal .xterm-bg-color-121 {
-    background-color: #87ffaf;
-}
-
-.terminal .xterm-color-122 {
-    color: #87ffd7;
-}
-
-.terminal .xterm-bg-color-122 {
-    background-color: #87ffd7;
-}
-
-.terminal .xterm-color-123 {
-    color: #87ffff;
-}
-
-.terminal .xterm-bg-color-123 {
-    background-color: #87ffff;
-}
-
-.terminal .xterm-color-124 {
-    color: #af0000;
-}
-
-.terminal .xterm-bg-color-124 {
-    background-color: #af0000;
-}
-
-.terminal .xterm-color-125 {
-    color: #af005f;
-}
-
-.terminal .xterm-bg-color-125 {
-    background-color: #af005f;
-}
-
-.terminal .xterm-color-126 {
-    color: #af0087;
-}
-
-.terminal .xterm-bg-color-126 {
-    background-color: #af0087;
-}
-
-.terminal .xterm-color-127 {
-    color: #af00af;
-}
-
-.terminal .xterm-bg-color-127 {
-    background-color: #af00af;
-}
-
-.terminal .xterm-color-128 {
-    color: #af00d7;
-}
-
-.terminal .xterm-bg-color-128 {
-    background-color: #af00d7;
-}
-
-.terminal .xterm-color-129 {
-    color: #af00ff;
-}
-
-.terminal .xterm-bg-color-129 {
-    background-color: #af00ff;
-}
-
-.terminal .xterm-color-130 {
-    color: #af5f00;
-}
-
-.terminal .xterm-bg-color-130 {
-    background-color: #af5f00;
-}
-
-.terminal .xterm-color-131 {
-    color: #af5f5f;
-}
-
-.terminal .xterm-bg-color-131 {
-    background-color: #af5f5f;
-}
-
-.terminal .xterm-color-132 {
-    color: #af5f87;
-}
-
-.terminal .xterm-bg-color-132 {
-    background-color: #af5f87;
-}
-
-.terminal .xterm-color-133 {
-    color: #af5faf;
-}
-
-.terminal .xterm-bg-color-133 {
-    background-color: #af5faf;
-}
-
-.terminal .xterm-color-134 {
-    color: #af5fd7;
-}
-
-.terminal .xterm-bg-color-134 {
-    background-color: #af5fd7;
-}
-
-.terminal .xterm-color-135 {
-    color: #af5fff;
-}
-
-.terminal .xterm-bg-color-135 {
-    background-color: #af5fff;
-}
-
-.terminal .xterm-color-136 {
-    color: #af8700;
-}
-
-.terminal .xterm-bg-color-136 {
-    background-color: #af8700;
-}
-
-.terminal .xterm-color-137 {
-    color: #af875f;
-}
-
-.terminal .xterm-bg-color-137 {
-    background-color: #af875f;
-}
-
-.terminal .xterm-color-138 {
-    color: #af8787;
-}
-
-.terminal .xterm-bg-color-138 {
-    background-color: #af8787;
-}
-
-.terminal .xterm-color-139 {
-    color: #af87af;
-}
-
-.terminal .xterm-bg-color-139 {
-    background-color: #af87af;
-}
-
-.terminal .xterm-color-140 {
-    color: #af87d7;
-}
-
-.terminal .xterm-bg-color-140 {
-    background-color: #af87d7;
-}
-
-.terminal .xterm-color-141 {
-    color: #af87ff;
-}
-
-.terminal .xterm-bg-color-141 {
-    background-color: #af87ff;
-}
-
-.terminal .xterm-color-142 {
-    color: #afaf00;
-}
-
-.terminal .xterm-bg-color-142 {
-    background-color: #afaf00;
-}
-
-.terminal .xterm-color-143 {
-    color: #afaf5f;
-}
-
-.terminal .xterm-bg-color-143 {
-    background-color: #afaf5f;
-}
-
-.terminal .xterm-color-144 {
-    color: #afaf87;
-}
-
-.terminal .xterm-bg-color-144 {
-    background-color: #afaf87;
-}
-
-.terminal .xterm-color-145 {
-    color: #afafaf;
-}
-
-.terminal .xterm-bg-color-145 {
-    background-color: #afafaf;
-}
-
-.terminal .xterm-color-146 {
-    color: #afafd7;
-}
-
-.terminal .xterm-bg-color-146 {
-    background-color: #afafd7;
-}
-
-.terminal .xterm-color-147 {
-    color: #afafff;
-}
-
-.terminal .xterm-bg-color-147 {
-    background-color: #afafff;
-}
-
-.terminal .xterm-color-148 {
-    color: #afd700;
-}
-
-.terminal .xterm-bg-color-148 {
-    background-color: #afd700;
-}
-
-.terminal .xterm-color-149 {
-    color: #afd75f;
-}
-
-.terminal .xterm-bg-color-149 {
-    background-color: #afd75f;
-}
-
-.terminal .xterm-color-150 {
-    color: #afd787;
-}
-
-.terminal .xterm-bg-color-150 {
-    background-color: #afd787;
-}
-
-.terminal .xterm-color-151 {
-    color: #afd7af;
-}
-
-.terminal .xterm-bg-color-151 {
-    background-color: #afd7af;
-}
-
-.terminal .xterm-color-152 {
-    color: #afd7d7;
-}
-
-.terminal .xterm-bg-color-152 {
-    background-color: #afd7d7;
-}
-
-.terminal .xterm-color-153 {
-    color: #afd7ff;
-}
-
-.terminal .xterm-bg-color-153 {
-    background-color: #afd7ff;
-}
-
-.terminal .xterm-color-154 {
-    color: #afff00;
-}
-
-.terminal .xterm-bg-color-154 {
-    background-color: #afff00;
-}
-
-.terminal .xterm-color-155 {
-    color: #afff5f;
-}
-
-.terminal .xterm-bg-color-155 {
-    background-color: #afff5f;
-}
-
-.terminal .xterm-color-156 {
-    color: #afff87;
-}
-
-.terminal .xterm-bg-color-156 {
-    background-color: #afff87;
-}
-
-.terminal .xterm-color-157 {
-    color: #afffaf;
-}
-
-.terminal .xterm-bg-color-157 {
-    background-color: #afffaf;
-}
-
-.terminal .xterm-color-158 {
-    color: #afffd7;
-}
-
-.terminal .xterm-bg-color-158 {
-    background-color: #afffd7;
-}
-
-.terminal .xterm-color-159 {
-    color: #afffff;
-}
-
-.terminal .xterm-bg-color-159 {
-    background-color: #afffff;
-}
-
-.terminal .xterm-color-160 {
-    color: #d70000;
-}
-
-.terminal .xterm-bg-color-160 {
-    background-color: #d70000;
-}
-
-.terminal .xterm-color-161 {
-    color: #d7005f;
-}
-
-.terminal .xterm-bg-color-161 {
-    background-color: #d7005f;
-}
-
-.terminal .xterm-color-162 {
-    color: #d70087;
-}
-
-.terminal .xterm-bg-color-162 {
-    background-color: #d70087;
-}
-
-.terminal .xterm-color-163 {
-    color: #d700af;
-}
-
-.terminal .xterm-bg-color-163 {
-    background-color: #d700af;
-}
-
-.terminal .xterm-color-164 {
-    color: #d700d7;
-}
-
-.terminal .xterm-bg-color-164 {
-    background-color: #d700d7;
-}
-
-.terminal .xterm-color-165 {
-    color: #d700ff;
-}
-
-.terminal .xterm-bg-color-165 {
-    background-color: #d700ff;
-}
-
-.terminal .xterm-color-166 {
-    color: #d75f00;
-}
-
-.terminal .xterm-bg-color-166 {
-    background-color: #d75f00;
-}
-
-.terminal .xterm-color-167 {
-    color: #d75f5f;
-}
-
-.terminal .xterm-bg-color-167 {
-    background-color: #d75f5f;
-}
-
-.terminal .xterm-color-168 {
-    color: #d75f87;
-}
-
-.terminal .xterm-bg-color-168 {
-    background-color: #d75f87;
-}
-
-.terminal .xterm-color-169 {
-    color: #d75faf;
-}
-
-.terminal .xterm-bg-color-169 {
-    background-color: #d75faf;
-}
-
-.terminal .xterm-color-170 {
-    color: #d75fd7;
-}
-
-.terminal .xterm-bg-color-170 {
-    background-color: #d75fd7;
-}
-
-.terminal .xterm-color-171 {
-    color: #d75fff;
-}
-
-.terminal .xterm-bg-color-171 {
-    background-color: #d75fff;
-}
-
-.terminal .xterm-color-172 {
-    color: #d78700;
-}
-
-.terminal .xterm-bg-color-172 {
-    background-color: #d78700;
-}
-
-.terminal .xterm-color-173 {
-    color: #d7875f;
-}
-
-.terminal .xterm-bg-color-173 {
-    background-color: #d7875f;
-}
-
-.terminal .xterm-color-174 {
-    color: #d78787;
-}
-
-.terminal .xterm-bg-color-174 {
-    background-color: #d78787;
-}
-
-.terminal .xterm-color-175 {
-    color: #d787af;
-}
-
-.terminal .xterm-bg-color-175 {
-    background-color: #d787af;
-}
-
-.terminal .xterm-color-176 {
-    color: #d787d7;
-}
-
-.terminal .xterm-bg-color-176 {
-    background-color: #d787d7;
-}
-
-.terminal .xterm-color-177 {
-    color: #d787ff;
-}
-
-.terminal .xterm-bg-color-177 {
-    background-color: #d787ff;
-}
-
-.terminal .xterm-color-178 {
-    color: #d7af00;
-}
-
-.terminal .xterm-bg-color-178 {
-    background-color: #d7af00;
-}
-
-.terminal .xterm-color-179 {
-    color: #d7af5f;
-}
-
-.terminal .xterm-bg-color-179 {
-    background-color: #d7af5f;
-}
-
-.terminal .xterm-color-180 {
-    color: #d7af87;
-}
-
-.terminal .xterm-bg-color-180 {
-    background-color: #d7af87;
-}
-
-.terminal .xterm-color-181 {
-    color: #d7afaf;
-}
-
-.terminal .xterm-bg-color-181 {
-    background-color: #d7afaf;
-}
-
-.terminal .xterm-color-182 {
-    color: #d7afd7;
-}
-
-.terminal .xterm-bg-color-182 {
-    background-color: #d7afd7;
-}
-
-.terminal .xterm-color-183 {
-    color: #d7afff;
-}
-
-.terminal .xterm-bg-color-183 {
-    background-color: #d7afff;
-}
-
-.terminal .xterm-color-184 {
-    color: #d7d700;
-}
-
-.terminal .xterm-bg-color-184 {
-    background-color: #d7d700;
-}
-
-.terminal .xterm-color-185 {
-    color: #d7d75f;
-}
-
-.terminal .xterm-bg-color-185 {
-    background-color: #d7d75f;
-}
-
-.terminal .xterm-color-186 {
-    color: #d7d787;
-}
-
-.terminal .xterm-bg-color-186 {
-    background-color: #d7d787;
-}
-
-.terminal .xterm-color-187 {
-    color: #d7d7af;
-}
-
-.terminal .xterm-bg-color-187 {
-    background-color: #d7d7af;
-}
-
-.terminal .xterm-color-188 {
-    color: #d7d7d7;
-}
-
-.terminal .xterm-bg-color-188 {
-    background-color: #d7d7d7;
-}
-
-.terminal .xterm-color-189 {
-    color: #d7d7ff;
-}
-
-.terminal .xterm-bg-color-189 {
-    background-color: #d7d7ff;
-}
-
-.terminal .xterm-color-190 {
-    color: #d7ff00;
-}
-
-.terminal .xterm-bg-color-190 {
-    background-color: #d7ff00;
-}
-
-.terminal .xterm-color-191 {
-    color: #d7ff5f;
-}
-
-.terminal .xterm-bg-color-191 {
-    background-color: #d7ff5f;
-}
-
-.terminal .xterm-color-192 {
-    color: #d7ff87;
-}
-
-.terminal .xterm-bg-color-192 {
-    background-color: #d7ff87;
-}
-
-.terminal .xterm-color-193 {
-    color: #d7ffaf;
-}
-
-.terminal .xterm-bg-color-193 {
-    background-color: #d7ffaf;
-}
-
-.terminal .xterm-color-194 {
-    color: #d7ffd7;
-}
-
-.terminal .xterm-bg-color-194 {
-    background-color: #d7ffd7;
-}
-
-.terminal .xterm-color-195 {
-    color: #d7ffff;
-}
-
-.terminal .xterm-bg-color-195 {
-    background-color: #d7ffff;
-}
-
-.terminal .xterm-color-196 {
-    color: #ff0000;
-}
-
-.terminal .xterm-bg-color-196 {
-    background-color: #ff0000;
-}
-
-.terminal .xterm-color-197 {
-    color: #ff005f;
-}
-
-.terminal .xterm-bg-color-197 {
-    background-color: #ff005f;
-}
-
-.terminal .xterm-color-198 {
-    color: #ff0087;
-}
-
-.terminal .xterm-bg-color-198 {
-    background-color: #ff0087;
-}
-
-.terminal .xterm-color-199 {
-    color: #ff00af;
-}
-
-.terminal .xterm-bg-color-199 {
-    background-color: #ff00af;
-}
-
-.terminal .xterm-color-200 {
-    color: #ff00d7;
-}
-
-.terminal .xterm-bg-color-200 {
-    background-color: #ff00d7;
-}
-
-.terminal .xterm-color-201 {
-    color: #ff00ff;
-}
-
-.terminal .xterm-bg-color-201 {
-    background-color: #ff00ff;
-}
-
-.terminal .xterm-color-202 {
-    color: #ff5f00;
-}
-
-.terminal .xterm-bg-color-202 {
-    background-color: #ff5f00;
-}
-
-.terminal .xterm-color-203 {
-    color: #ff5f5f;
-}
-
-.terminal .xterm-bg-color-203 {
-    background-color: #ff5f5f;
-}
-
-.terminal .xterm-color-204 {
-    color: #ff5f87;
-}
-
-.terminal .xterm-bg-color-204 {
-    background-color: #ff5f87;
-}
-
-.terminal .xterm-color-205 {
-    color: #ff5faf;
-}
-
-.terminal .xterm-bg-color-205 {
-    background-color: #ff5faf;
-}
-
-.terminal .xterm-color-206 {
-    color: #ff5fd7;
-}
-
-.terminal .xterm-bg-color-206 {
-    background-color: #ff5fd7;
-}
-
-.terminal .xterm-color-207 {
-    color: #ff5fff;
-}
-
-.terminal .xterm-bg-color-207 {
-    background-color: #ff5fff;
-}
-
-.terminal .xterm-color-208 {
-    color: #ff8700;
-}
-
-.terminal .xterm-bg-color-208 {
-    background-color: #ff8700;
-}
-
-.terminal .xterm-color-209 {
-    color: #ff875f;
-}
-
-.terminal .xterm-bg-color-209 {
-    background-color: #ff875f;
-}
-
-.terminal .xterm-color-210 {
-    color: #ff8787;
-}
-
-.terminal .xterm-bg-color-210 {
-    background-color: #ff8787;
-}
-
-.terminal .xterm-color-211 {
-    color: #ff87af;
-}
-
-.terminal .xterm-bg-color-211 {
-    background-color: #ff87af;
-}
-
-.terminal .xterm-color-212 {
-    color: #ff87d7;
-}
-
-.terminal .xterm-bg-color-212 {
-    background-color: #ff87d7;
-}
-
-.terminal .xterm-color-213 {
-    color: #ff87ff;
-}
-
-.terminal .xterm-bg-color-213 {
-    background-color: #ff87ff;
-}
-
-.terminal .xterm-color-214 {
-    color: #ffaf00;
-}
-
-.terminal .xterm-bg-color-214 {
-    background-color: #ffaf00;
-}
-
-.terminal .xterm-color-215 {
-    color: #ffaf5f;
-}
-
-.terminal .xterm-bg-color-215 {
-    background-color: #ffaf5f;
-}
-
-.terminal .xterm-color-216 {
-    color: #ffaf87;
-}
-
-.terminal .xterm-bg-color-216 {
-    background-color: #ffaf87;
-}
-
-.terminal .xterm-color-217 {
-    color: #ffafaf;
-}
-
-.terminal .xterm-bg-color-217 {
-    background-color: #ffafaf;
-}
-
-.terminal .xterm-color-218 {
-    color: #ffafd7;
-}
-
-.terminal .xterm-bg-color-218 {
-    background-color: #ffafd7;
-}
-
-.terminal .xterm-color-219 {
-    color: #ffafff;
-}
-
-.terminal .xterm-bg-color-219 {
-    background-color: #ffafff;
-}
-
-.terminal .xterm-color-220 {
-    color: #ffd700;
-}
-
-.terminal .xterm-bg-color-220 {
-    background-color: #ffd700;
-}
-
-.terminal .xterm-color-221 {
-    color: #ffd75f;
-}
-
-.terminal .xterm-bg-color-221 {
-    background-color: #ffd75f;
-}
-
-.terminal .xterm-color-222 {
-    color: #ffd787;
-}
-
-.terminal .xterm-bg-color-222 {
-    background-color: #ffd787;
-}
-
-.terminal .xterm-color-223 {
-    color: #ffd7af;
-}
-
-.terminal .xterm-bg-color-223 {
-    background-color: #ffd7af;
-}
-
-.terminal .xterm-color-224 {
-    color: #ffd7d7;
-}
-
-.terminal .xterm-bg-color-224 {
-    background-color: #ffd7d7;
-}
-
-.terminal .xterm-color-225 {
-    color: #ffd7ff;
-}
-
-.terminal .xterm-bg-color-225 {
-    background-color: #ffd7ff;
-}
-
-.terminal .xterm-color-226 {
-    color: #ffff00;
-}
-
-.terminal .xterm-bg-color-226 {
-    background-color: #ffff00;
-}
-
-.terminal .xterm-color-227 {
-    color: #ffff5f;
-}
-
-.terminal .xterm-bg-color-227 {
-    background-color: #ffff5f;
-}
-
-.terminal .xterm-color-228 {
-    color: #ffff87;
-}
-
-.terminal .xterm-bg-color-228 {
-    background-color: #ffff87;
-}
-
-.terminal .xterm-color-229 {
-    color: #ffffaf;
-}
-
-.terminal .xterm-bg-color-229 {
-    background-color: #ffffaf;
-}
-
-.terminal .xterm-color-230 {
-    color: #ffffd7;
-}
-
-.terminal .xterm-bg-color-230 {
-    background-color: #ffffd7;
-}
-
-.terminal .xterm-color-231 {
-    color: #ffffff;
-}
-
-.terminal .xterm-bg-color-231 {
-    background-color: #ffffff;
-}
-
-.terminal .xterm-color-232 {
-    color: #080808;
-}
-
-.terminal .xterm-bg-color-232 {
-    background-color: #080808;
-}
-
-.terminal .xterm-color-233 {
-    color: #121212;
-}
-
-.terminal .xterm-bg-color-233 {
-    background-color: #121212;
-}
-
-.terminal .xterm-color-234 {
-    color: #1c1c1c;
-}
-
-.terminal .xterm-bg-color-234 {
-    background-color: #1c1c1c;
-}
-
-.terminal .xterm-color-235 {
-    color: #262626;
-}
-
-.terminal .xterm-bg-color-235 {
-    background-color: #262626;
-}
-
-.terminal .xterm-color-236 {
-    color: #303030;
-}
-
-.terminal .xterm-bg-color-236 {
-    background-color: #303030;
-}
-
-.terminal .xterm-color-237 {
-    color: #3a3a3a;
-}
-
-.terminal .xterm-bg-color-237 {
-    background-color: #3a3a3a;
-}
-
-.terminal .xterm-color-238 {
-    color: #444444;
-}
-
-.terminal .xterm-bg-color-238 {
-    background-color: #444444;
-}
-
-.terminal .xterm-color-239 {
-    color: #4e4e4e;
-}
-
-.terminal .xterm-bg-color-239 {
-    background-color: #4e4e4e;
-}
-
-.terminal .xterm-color-240 {
-    color: #585858;
-}
-
-.terminal .xterm-bg-color-240 {
-    background-color: #585858;
-}
-
-.terminal .xterm-color-241 {
-    color: #626262;
-}
-
-.terminal .xterm-bg-color-241 {
-    background-color: #626262;
-}
-
-.terminal .xterm-color-242 {
-    color: #6c6c6c;
-}
-
-.terminal .xterm-bg-color-242 {
-    background-color: #6c6c6c;
-}
-
-.terminal .xterm-color-243 {
-    color: #767676;
-}
-
-.terminal .xterm-bg-color-243 {
-    background-color: #767676;
-}
-
-.terminal .xterm-color-244 {
-    color: #808080;
-}
-
-.terminal .xterm-bg-color-244 {
-    background-color: #808080;
-}
-
-.terminal .xterm-color-245 {
-    color: #8a8a8a;
-}
-
-.terminal .xterm-bg-color-245 {
-    background-color: #8a8a8a;
-}
-
-.terminal .xterm-color-246 {
-    color: #949494;
-}
-
-.terminal .xterm-bg-color-246 {
-    background-color: #949494;
-}
-
-.terminal .xterm-color-247 {
-    color: #9e9e9e;
-}
-
-.terminal .xterm-bg-color-247 {
-    background-color: #9e9e9e;
-}
-
-.terminal .xterm-color-248 {
-    color: #a8a8a8;
-}
-
-.terminal .xterm-bg-color-248 {
-    background-color: #a8a8a8;
-}
-
-.terminal .xterm-color-249 {
-    color: #b2b2b2;
-}
-
-.terminal .xterm-bg-color-249 {
-    background-color: #b2b2b2;
-}
-
-.terminal .xterm-color-250 {
-    color: #bcbcbc;
-}
-
-.terminal .xterm-bg-color-250 {
-    background-color: #bcbcbc;
-}
-
-.terminal .xterm-color-251 {
-    color: #c6c6c6;
-}
-
-.terminal .xterm-bg-color-251 {
-    background-color: #c6c6c6;
-}
-
-.terminal .xterm-color-252 {
-    color: #d0d0d0;
-}
-
-.terminal .xterm-bg-color-252 {
-    background-color: #d0d0d0;
-}
-
-.terminal .xterm-color-253 {
-    color: #dadada;
-}
-
-.terminal .xterm-bg-color-253 {
-    background-color: #dadada;
-}
-
-.terminal .xterm-color-254 {
-    color: #e4e4e4;
-}
-
-.terminal .xterm-bg-color-254 {
-    background-color: #e4e4e4;
-}
-
-.terminal .xterm-color-255 {
-    color: #eeeeee;
-}
-
-.terminal .xterm-bg-color-255 {
-    background-color: #eeeeee;
-}
diff --git a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/build/xterm.js b/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/build/xterm.js
deleted file mode 100644
index 7dfaab5..0000000
--- a/third_party/blink/renderer/devtools/front_end/terminal/xterm.js/build/xterm.js
+++ /dev/null
@@ -1,4318 +0,0 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Terminal = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.CHARSETS = {};
-exports.DEFAULT_CHARSET = exports.CHARSETS['B'];
-exports.CHARSETS['0'] = {
-    '`': '\u25c6',
-    'a': '\u2592',
-    'b': '\u0009',
-    'c': '\u000c',
-    'd': '\u000d',
-    'e': '\u000a',
-    'f': '\u00b0',
-    'g': '\u00b1',
-    'h': '\u2424',
-    'i': '\u000b',
-    'j': '\u2518',
-    'k': '\u2510',
-    'l': '\u250c',
-    'm': '\u2514',
-    'n': '\u253c',
-    'o': '\u23ba',
-    'p': '\u23bb',
-    'q': '\u2500',
-    'r': '\u23bc',
-    's': '\u23bd',
-    't': '\u251c',
-    'u': '\u2524',
-    'v': '\u2534',
-    'w': '\u252c',
-    'x': '\u2502',
-    'y': '\u2264',
-    'z': '\u2265',
-    '{': '\u03c0',
-    '|': '\u2260',
-    '}': '\u00a3',
-    '~': '\u00b7'
-};
-exports.CHARSETS['A'] = {
-    '#': '£'
-};
-exports.CHARSETS['B'] = null;
-exports.CHARSETS['4'] = {
-    '#': '£',
-    '@': '¾',
-    '[': 'ij',
-    '\\': '½',
-    ']': '|',
-    '{': '¨',
-    '|': 'f',
-    '}': '¼',
-    '~': '´'
-};
-exports.CHARSETS['C'] =
-    exports.CHARSETS['5'] = {
-        '[': 'Ä',
-        '\\': 'Ö',
-        ']': 'Å',
-        '^': 'Ü',
-        '`': 'é',
-        '{': 'ä',
-        '|': 'ö',
-        '}': 'å',
-        '~': 'ü'
-    };
-exports.CHARSETS['R'] = {
-    '#': '£',
-    '@': 'à',
-    '[': '°',
-    '\\': 'ç',
-    ']': '§',
-    '{': 'é',
-    '|': 'ù',
-    '}': 'è',
-    '~': '¨'
-};
-exports.CHARSETS['Q'] = {
-    '@': 'à',
-    '[': 'â',
-    '\\': 'ç',
-    ']': 'ê',
-    '^': 'î',
-    '`': 'ô',
-    '{': 'é',
-    '|': 'ù',
-    '}': 'è',
-    '~': 'û'
-};
-exports.CHARSETS['K'] = {
-    '@': '§',
-    '[': 'Ä',
-    '\\': 'Ö',
-    ']': 'Ü',
-    '{': 'ä',
-    '|': 'ö',
-    '}': 'ü',
-    '~': 'ß'
-};
-exports.CHARSETS['Y'] = {
-    '#': '£',
-    '@': '§',
-    '[': '°',
-    '\\': 'ç',
-    ']': 'é',
-    '`': 'ù',
-    '{': 'à',
-    '|': 'ò',
-    '}': 'è',
-    '~': 'ì'
-};
-exports.CHARSETS['E'] =
-    exports.CHARSETS['6'] = {
-        '@': 'Ä',
-        '[': 'Æ',
-        '\\': 'Ø',
-        ']': 'Å',
-        '^': 'Ü',
-        '`': 'ä',
-        '{': 'æ',
-        '|': 'ø',
-        '}': 'å',
-        '~': 'ü'
-    };
-exports.CHARSETS['Z'] = {
-    '#': '£',
-    '@': '§',
-    '[': '¡',
-    '\\': 'Ñ',
-    ']': '¿',
-    '{': '°',
-    '|': 'ñ',
-    '}': 'ç'
-};
-exports.CHARSETS['H'] =
-    exports.CHARSETS['7'] = {
-        '@': 'É',
-        '[': 'Ä',
-        '\\': 'Ö',
-        ']': 'Å',
-        '^': 'Ü',
-        '`': 'é',
-        '{': 'ä',
-        '|': 'ö',
-        '}': 'å',
-        '~': 'ü'
-    };
-exports.CHARSETS['='] = {
-    '#': 'ù',
-    '@': 'à',
-    '[': 'é',
-    '\\': 'ç',
-    ']': 'ê',
-    '^': 'î',
-    '_': 'è',
-    '`': 'ô',
-    '{': 'ä',
-    '|': 'ö',
-    '}': 'ü',
-    '~': 'û'
-};
-
-
-
-},{}],2:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var CompositionHelper = (function () {
-    function CompositionHelper(textarea, compositionView, terminal) {
-        this.textarea = textarea;
-        this.compositionView = compositionView;
-        this.terminal = terminal;
-        this.isComposing = false;
-        this.isSendingComposition = false;
-        this.compositionPosition = { start: null, end: null };
-    }
-    CompositionHelper.prototype.compositionstart = function () {
-        this.isComposing = true;
-        this.compositionPosition.start = this.textarea.value.length;
-        this.compositionView.textContent = '';
-        this.compositionView.classList.add('active');
-    };
-    CompositionHelper.prototype.compositionupdate = function (ev) {
-        var _this = this;
-        this.compositionView.textContent = ev.data;
-        this.updateCompositionElements();
-        setTimeout(function () {
-            _this.compositionPosition.end = _this.textarea.value.length;
-        }, 0);
-    };
-    CompositionHelper.prototype.compositionend = function () {
-        this.finalizeComposition(true);
-    };
-    CompositionHelper.prototype.keydown = function (ev) {
-        if (this.isComposing || this.isSendingComposition) {
-            if (ev.keyCode === 229) {
-                return false;
-            }
-            else if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
-                return false;
-            }
-            else {
-                this.finalizeComposition(false);
-            }
-        }
-        if (ev.keyCode === 229) {
-            this.handleAnyTextareaChanges();
-            return false;
-        }
-        return true;
-    };
-    CompositionHelper.prototype.finalizeComposition = function (waitForPropogation) {
-        var _this = this;
-        this.compositionView.classList.remove('active');
-        this.isComposing = false;
-        this.clearTextareaPosition();
-        if (!waitForPropogation) {
-            this.isSendingComposition = false;
-            var input = this.textarea.value.substring(this.compositionPosition.start, this.compositionPosition.end);
-            this.terminal.handler(input);
-        }
-        else {
-            var currentCompositionPosition_1 = {
-                start: this.compositionPosition.start,
-                end: this.compositionPosition.end,
-            };
-            this.isSendingComposition = true;
-            setTimeout(function () {
-                if (_this.isSendingComposition) {
-                    _this.isSendingComposition = false;
-                    var input = void 0;
-                    if (_this.isComposing) {
-                        input = _this.textarea.value.substring(currentCompositionPosition_1.start, currentCompositionPosition_1.end);
-                    }
-                    else {
-                        input = _this.textarea.value.substring(currentCompositionPosition_1.start);
-                    }
-                    _this.terminal.handler(input);
-                }
-            }, 0);
-        }
-    };
-    CompositionHelper.prototype.handleAnyTextareaChanges = function () {
-        var _this = this;
-        var oldValue = this.textarea.value;
-        setTimeout(function () {
-            if (!_this.isComposing) {
-                var newValue = _this.textarea.value;
-                var diff = newValue.replace(oldValue, '');
-                if (diff.length > 0) {
-                    _this.terminal.handler(diff);
-                }
-            }
-        }, 0);
-    };
-    CompositionHelper.prototype.updateCompositionElements = function (dontRecurse) {
-        var _this = this;
-        if (!this.isComposing) {
-            return;
-        }
-        var cursor = this.terminal.element.querySelector('.terminal-cursor');
-        if (cursor) {
-            var xtermRows = this.terminal.element.querySelector('.xterm-rows');
-            var cursorTop = xtermRows.offsetTop + cursor.offsetTop;
-            this.compositionView.style.left = cursor.offsetLeft + 'px';
-            this.compositionView.style.top = cursorTop + 'px';
-            this.compositionView.style.height = cursor.offsetHeight + 'px';
-            this.compositionView.style.lineHeight = cursor.offsetHeight + 'px';
-            var compositionViewBounds = this.compositionView.getBoundingClientRect();
-            this.textarea.style.left = cursor.offsetLeft + 'px';
-            this.textarea.style.top = cursorTop + 'px';
-            this.textarea.style.width = compositionViewBounds.width + 'px';
-            this.textarea.style.height = compositionViewBounds.height + 'px';
-            this.textarea.style.lineHeight = compositionViewBounds.height + 'px';
-        }
-        if (!dontRecurse) {
-            setTimeout(function () { return _this.updateCompositionElements(true); }, 0);
-        }
-    };
-    ;
-    CompositionHelper.prototype.clearTextareaPosition = function () {
-        this.textarea.style.left = '';
-        this.textarea.style.top = '';
-    };
-    ;
-    return CompositionHelper;
-}());
-exports.CompositionHelper = CompositionHelper;
-
-
-
-},{}],3:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var C0;
-(function (C0) {
-    C0.NUL = '\x00';
-    C0.SOH = '\x01';
-    C0.STX = '\x02';
-    C0.ETX = '\x03';
-    C0.EOT = '\x04';
-    C0.ENQ = '\x05';
-    C0.ACK = '\x06';
-    C0.BEL = '\x07';
-    C0.BS = '\x08';
-    C0.HT = '\x09';
-    C0.LF = '\x0a';
-    C0.VT = '\x0b';
-    C0.FF = '\x0c';
-    C0.CR = '\x0d';
-    C0.SO = '\x0e';
-    C0.SI = '\x0f';
-    C0.DLE = '\x10';
-    C0.DC1 = '\x11';
-    C0.DC2 = '\x12';
-    C0.DC3 = '\x13';
-    C0.DC4 = '\x14';
-    C0.NAK = '\x15';
-    C0.SYN = '\x16';
-    C0.ETB = '\x17';
-    C0.CAN = '\x18';
-    C0.EM = '\x19';
-    C0.SUB = '\x1a';
-    C0.ESC = '\x1b';
-    C0.FS = '\x1c';
-    C0.GS = '\x1d';
-    C0.RS = '\x1e';
-    C0.US = '\x1f';
-    C0.SP = '\x20';
-    C0.DEL = '\x7f';
-})(C0 = exports.C0 || (exports.C0 = {}));
-;
-
-
-
-},{}],4:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-;
-var EventEmitter = (function () {
-    function EventEmitter() {
-        this._events = this._events || {};
-    }
-    EventEmitter.prototype.on = function (type, listener) {
-        this._events[type] = this._events[type] || [];
-        this._events[type].push(listener);
-    };
-    EventEmitter.prototype.off = function (type, listener) {
-        if (!this._events[type]) {
-            return;
-        }
-        var obj = this._events[type];
-        var i = obj.length;
-        while (i--) {
-            if (obj[i] === listener || obj[i].listener === listener) {
-                obj.splice(i, 1);
-                return;
-            }
-        }
-    };
-    EventEmitter.prototype.removeAllListeners = function (type) {
-        if (this._events[type]) {
-            delete this._events[type];
-        }
-    };
-    EventEmitter.prototype.once = function (type, listener) {
-        function on() {
-            var args = Array.prototype.slice.call(arguments);
-            this.off(type, on);
-            return listener.apply(this, args);
-        }
-        on.listener = listener;
-        return this.on(type, on);
-    };
-    EventEmitter.prototype.emit = function (type) {
-        var args = [];
-        for (var _i = 1; _i < arguments.length; _i++) {
-            args[_i - 1] = arguments[_i];
-        }
-        if (!this._events[type]) {
-            return;
-        }
-        var obj = this._events[type];
-        for (var i = 0; i < obj.length; i++) {
-            obj[i].apply(this, args);
-        }
-    };
-    EventEmitter.prototype.listeners = function (type) {
-        return this._events[type] || [];
-    };
-    return EventEmitter;
-}());
-exports.EventEmitter = EventEmitter;
-
-
-
-},{}],5:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var EscapeSequences_1 = require("./EscapeSequences");
-var Charsets_1 = require("./Charsets");
-var InputHandler = (function () {
-    function InputHandler(_terminal) {
-        this._terminal = _terminal;
-    }
-    InputHandler.prototype.addChar = function (char, code) {
-        if (char >= ' ') {
-            var ch_width = wcwidth(code);
-            if (this._terminal.charset && this._terminal.charset[char]) {
-                char = this._terminal.charset[char];
-            }
-            var row = this._terminal.y + this._terminal.ybase;
-            if (!ch_width && this._terminal.x) {
-                if (this._terminal.lines.get(row)[this._terminal.x - 1]) {
-                    if (!this._terminal.lines.get(row)[this._terminal.x - 1][2]) {
-                        if (this._terminal.lines.get(row)[this._terminal.x - 2])
-                            this._terminal.lines.get(row)[this._terminal.x - 2][1] += char;
-                    }
-                    else {
-                        this._terminal.lines.get(row)[this._terminal.x - 1][1] += char;
-                    }
-                    this._terminal.updateRange(this._terminal.y);
-                }
-                return;
-            }
-            if (this._terminal.x + ch_width - 1 >= this._terminal.cols) {
-                if (this._terminal.wraparoundMode) {
-                    this._terminal.x = 0;
-                    this._terminal.y++;
-                    if (this._terminal.y > this._terminal.scrollBottom) {
-                        this._terminal.y--;
-                        this._terminal.scroll();
-                    }
-                }
-                else {
-                    if (ch_width === 2)
-                        return;
-                }
-            }
-            row = this._terminal.y + this._terminal.ybase;
-            if (this._terminal.insertMode) {
-                for (var moves = 0; moves < ch_width; ++moves) {
-                    var removed = this._terminal.lines.get(this._terminal.y + this._terminal.ybase).pop();
-                    if (removed[2] === 0
-                        && this._terminal.lines.get(row)[this._terminal.cols - 2]
-                        && this._terminal.lines.get(row)[this._terminal.cols - 2][2] === 2)
-                        this._terminal.lines.get(row)[this._terminal.cols - 2] = [this._terminal.curAttr, ' ', 1];
-                    this._terminal.lines.get(row).splice(this._terminal.x, 0, [this._terminal.curAttr, ' ', 1]);
-                }
-            }
-            this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, char, ch_width];
-            this._terminal.x++;
-            this._terminal.updateRange(this._terminal.y);
-            if (ch_width === 2) {
-                this._terminal.lines.get(row)[this._terminal.x] = [this._terminal.curAttr, '', 0];
-                this._terminal.x++;
-            }
-        }
-    };
-    InputHandler.prototype.bell = function () {
-        var _this = this;
-        if (!this._terminal.visualBell) {
-            return;
-        }
-        this._terminal.element.style.borderColor = 'white';
-        setTimeout(function () { return _this._terminal.element.style.borderColor = ''; }, 10);
-        if (this._terminal.popOnBell) {
-            this._terminal.focus();
-        }
-    };
-    InputHandler.prototype.lineFeed = function () {
-        if (this._terminal.convertEol) {
-            this._terminal.x = 0;
-        }
-        this._terminal.y++;
-        if (this._terminal.y > this._terminal.scrollBottom) {
-            this._terminal.y--;
-            this._terminal.scroll();
-        }
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x--;
-        }
-    };
-    InputHandler.prototype.carriageReturn = function () {
-        this._terminal.x = 0;
-    };
-    InputHandler.prototype.backspace = function () {
-        if (this._terminal.x > 0) {
-            this._terminal.x--;
-        }
-    };
-    InputHandler.prototype.tab = function () {
-        this._terminal.x = this._terminal.nextStop();
-    };
-    InputHandler.prototype.shiftOut = function () {
-        this._terminal.setgLevel(1);
-    };
-    InputHandler.prototype.shiftIn = function () {
-        this._terminal.setgLevel(0);
-    };
-    InputHandler.prototype.insertChars = function (params) {
-        var param, row, j, ch;
-        param = params[0];
-        if (param < 1)
-            param = 1;
-        row = this._terminal.y + this._terminal.ybase;
-        j = this._terminal.x;
-        ch = [this._terminal.eraseAttr(), ' ', 1];
-        while (param-- && j < this._terminal.cols) {
-            this._terminal.lines.get(row).splice(j++, 0, ch);
-            this._terminal.lines.get(row).pop();
-        }
-    };
-    InputHandler.prototype.cursorUp = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.y -= param;
-        if (this._terminal.y < 0) {
-            this._terminal.y = 0;
-        }
-    };
-    InputHandler.prototype.cursorDown = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.y += param;
-        if (this._terminal.y >= this._terminal.rows) {
-            this._terminal.y = this._terminal.rows - 1;
-        }
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x--;
-        }
-    };
-    InputHandler.prototype.cursorForward = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.x += param;
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x = this._terminal.cols - 1;
-        }
-    };
-    InputHandler.prototype.cursorBackward = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x--;
-        }
-        this._terminal.x -= param;
-        if (this._terminal.x < 0) {
-            this._terminal.x = 0;
-        }
-    };
-    InputHandler.prototype.cursorNextLine = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.y += param;
-        if (this._terminal.y >= this._terminal.rows) {
-            this._terminal.y = this._terminal.rows - 1;
-        }
-        this._terminal.x = 0;
-    };
-    ;
-    InputHandler.prototype.cursorPrecedingLine = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.y -= param;
-        if (this._terminal.y < 0) {
-            this._terminal.y = 0;
-        }
-        this._terminal.x = 0;
-    };
-    ;
-    InputHandler.prototype.cursorCharAbsolute = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.x = param - 1;
-    };
-    InputHandler.prototype.cursorPosition = function (params) {
-        var row, col;
-        row = params[0] - 1;
-        if (params.length >= 2) {
-            col = params[1] - 1;
-        }
-        else {
-            col = 0;
-        }
-        if (row < 0) {
-            row = 0;
-        }
-        else if (row >= this._terminal.rows) {
-            row = this._terminal.rows - 1;
-        }
-        if (col < 0) {
-            col = 0;
-        }
-        else if (col >= this._terminal.cols) {
-            col = this._terminal.cols - 1;
-        }
-        this._terminal.x = col;
-        this._terminal.y = row;
-    };
-    InputHandler.prototype.cursorForwardTab = function (params) {
-        var param = params[0] || 1;
-        while (param--) {
-            this._terminal.x = this._terminal.nextStop();
-        }
-    };
-    InputHandler.prototype.eraseInDisplay = function (params) {
-        var j;
-        switch (params[0]) {
-            case 0:
-                this._terminal.eraseRight(this._terminal.x, this._terminal.y);
-                j = this._terminal.y + 1;
-                for (; j < this._terminal.rows; j++) {
-                    this._terminal.eraseLine(j);
-                }
-                break;
-            case 1:
-                this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
-                j = this._terminal.y;
-                while (j--) {
-                    this._terminal.eraseLine(j);
-                }
-                break;
-            case 2:
-                j = this._terminal.rows;
-                while (j--)
-                    this._terminal.eraseLine(j);
-                break;
-            case 3:
-                var scrollBackSize = this._terminal.lines.length - this._terminal.rows;
-                if (scrollBackSize > 0) {
-                    this._terminal.lines.trimStart(scrollBackSize);
-                    this._terminal.ybase = Math.max(this._terminal.ybase - scrollBackSize, 0);
-                    this._terminal.ydisp = Math.max(this._terminal.ydisp - scrollBackSize, 0);
-                }
-                break;
-        }
-    };
-    InputHandler.prototype.eraseInLine = function (params) {
-        switch (params[0]) {
-            case 0:
-                this._terminal.eraseRight(this._terminal.x, this._terminal.y);
-                break;
-            case 1:
-                this._terminal.eraseLeft(this._terminal.x, this._terminal.y);
-                break;
-            case 2:
-                this._terminal.eraseLine(this._terminal.y);
-                break;
-        }
-    };
-    InputHandler.prototype.insertLines = function (params) {
-        var param, row, j;
-        param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        row = this._terminal.y + this._terminal.ybase;
-        j = this._terminal.rows - 1 - this._terminal.scrollBottom;
-        j = this._terminal.rows - 1 + this._terminal.ybase - j + 1;
-        while (param--) {
-            if (this._terminal.lines.length === this._terminal.lines.maxLength) {
-                this._terminal.lines.trimStart(1);
-                this._terminal.ybase--;
-                this._terminal.ydisp--;
-                row--;
-                j--;
-            }
-            this._terminal.lines.splice(row, 0, this._terminal.blankLine(true));
-            this._terminal.lines.splice(j, 1);
-        }
-        this._terminal.updateRange(this._terminal.y);
-        this._terminal.updateRange(this._terminal.scrollBottom);
-    };
-    InputHandler.prototype.deleteLines = function (params) {
-        var param, row, j;
-        param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        row = this._terminal.y + this._terminal.ybase;
-        j = this._terminal.rows - 1 - this._terminal.scrollBottom;
-        j = this._terminal.rows - 1 + this._terminal.ybase - j;
-        while (param--) {
-            if (this._terminal.lines.length === this._terminal.lines.maxLength) {
-                this._terminal.lines.trimStart(1);
-                this._terminal.ybase -= 1;
-                this._terminal.ydisp -= 1;
-            }
-            this._terminal.lines.splice(j + 1, 0, this._terminal.blankLine(true));
-            this._terminal.lines.splice(row, 1);
-        }
-        this._terminal.updateRange(this._terminal.y);
-        this._terminal.updateRange(this._terminal.scrollBottom);
-    };
-    InputHandler.prototype.deleteChars = function (params) {
-        var param, row, ch;
-        param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        row = this._terminal.y + this._terminal.ybase;
-        ch = [this._terminal.eraseAttr(), ' ', 1];
-        while (param--) {
-            this._terminal.lines.get(row).splice(this._terminal.x, 1);
-            this._terminal.lines.get(row).push(ch);
-        }
-    };
-    InputHandler.prototype.scrollUp = function (params) {
-        var param = params[0] || 1;
-        while (param--) {
-            this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 1);
-            this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 0, this._terminal.blankLine());
-        }
-        this._terminal.updateRange(this._terminal.scrollTop);
-        this._terminal.updateRange(this._terminal.scrollBottom);
-    };
-    InputHandler.prototype.scrollDown = function (params) {
-        var param = params[0] || 1;
-        while (param--) {
-            this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollBottom, 1);
-            this._terminal.lines.splice(this._terminal.ybase + this._terminal.scrollTop, 0, this._terminal.blankLine());
-        }
-        this._terminal.updateRange(this._terminal.scrollTop);
-        this._terminal.updateRange(this._terminal.scrollBottom);
-    };
-    InputHandler.prototype.eraseChars = function (params) {
-        var param, row, j, ch;
-        param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        row = this._terminal.y + this._terminal.ybase;
-        j = this._terminal.x;
-        ch = [this._terminal.eraseAttr(), ' ', 1];
-        while (param-- && j < this._terminal.cols) {
-            this._terminal.lines.get(row)[j++] = ch;
-        }
-    };
-    InputHandler.prototype.cursorBackwardTab = function (params) {
-        var param = params[0] || 1;
-        while (param--) {
-            this._terminal.x = this._terminal.prevStop();
-        }
-    };
-    InputHandler.prototype.charPosAbsolute = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.x = param - 1;
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x = this._terminal.cols - 1;
-        }
-    };
-    InputHandler.prototype.HPositionRelative = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.x += param;
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x = this._terminal.cols - 1;
-        }
-    };
-    InputHandler.prototype.repeatPrecedingCharacter = function (params) {
-        var param = params[0] || 1, line = this._terminal.lines.get(this._terminal.ybase + this._terminal.y), ch = line[this._terminal.x - 1] || [this._terminal.defAttr, ' ', 1];
-        while (param--) {
-            line[this._terminal.x++] = ch;
-        }
-    };
-    InputHandler.prototype.sendDeviceAttributes = function (params) {
-        if (params[0] > 0) {
-            return;
-        }
-        if (!this._terminal.prefix) {
-            if (this._terminal.is('xterm') || this._terminal.is('rxvt-unicode') || this._terminal.is('screen')) {
-                this._terminal.send(EscapeSequences_1.C0.ESC + '[?1;2c');
-            }
-            else if (this._terminal.is('linux')) {
-                this._terminal.send(EscapeSequences_1.C0.ESC + '[?6c');
-            }
-        }
-        else if (this._terminal.prefix === '>') {
-            if (this._terminal.is('xterm')) {
-                this._terminal.send(EscapeSequences_1.C0.ESC + '[>0;276;0c');
-            }
-            else if (this._terminal.is('rxvt-unicode')) {
-                this._terminal.send(EscapeSequences_1.C0.ESC + '[>85;95;0c');
-            }
-            else if (this._terminal.is('linux')) {
-                this._terminal.send(params[0] + 'c');
-            }
-            else if (this._terminal.is('screen')) {
-                this._terminal.send(EscapeSequences_1.C0.ESC + '[>83;40003;0c');
-            }
-        }
-    };
-    InputHandler.prototype.linePosAbsolute = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.y = param - 1;
-        if (this._terminal.y >= this._terminal.rows) {
-            this._terminal.y = this._terminal.rows - 1;
-        }
-    };
-    InputHandler.prototype.VPositionRelative = function (params) {
-        var param = params[0];
-        if (param < 1) {
-            param = 1;
-        }
-        this._terminal.y += param;
-        if (this._terminal.y >= this._terminal.rows) {
-            this._terminal.y = this._terminal.rows - 1;
-        }
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x--;
-        }
-    };
-    InputHandler.prototype.HVPosition = function (params) {
-        if (params[0] < 1)
-            params[0] = 1;
-        if (params[1] < 1)
-            params[1] = 1;
-        this._terminal.y = params[0] - 1;
-        if (this._terminal.y >= this._terminal.rows) {
-            this._terminal.y = this._terminal.rows - 1;
-        }
-        this._terminal.x = params[1] - 1;
-        if (this._terminal.x >= this._terminal.cols) {
-            this._terminal.x = this._terminal.cols - 1;
-        }
-    };
-    InputHandler.prototype.tabClear = function (params) {
-        var param = params[0];
-        if (param <= 0) {
-            delete this._terminal.tabs[this._terminal.x];
-        }
-        else if (param === 3) {
-            this._terminal.tabs = {};
-        }
-    };
-    InputHandler.prototype.setMode = function (params) {
-        if (params.length > 1) {
-            for (var i = 0; i < params.length; i++) {
-                this.setMode([params[i]]);
-            }
-            return;
-        }
-        if (!this._terminal.prefix) {
-            switch (params[0]) {
-                case 4:
-                    this._terminal.insertMode = true;
-                    break;
-                case 20:
-                    break;
-            }
-        }
-        else if (this._terminal.prefix === '?') {
-            switch (params[0]) {
-                case 1:
-                    this._terminal.applicationCursor = true;
-                    break;
-                case 2:
-                    this._terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET);
-                    this._terminal.setgCharset(1, Charsets_1.DEFAULT_CHARSET);
-                    this._terminal.setgCharset(2, Charsets_1.DEFAULT_CHARSET);
-                    this._terminal.setgCharset(3, Charsets_1.DEFAULT_CHARSET);
-                    break;
-                case 3:
-                    this._terminal.savedCols = this._terminal.cols;
-                    this._terminal.resize(132, this._terminal.rows);
-                    break;
-                case 6:
-                    this._terminal.originMode = true;
-                    break;
-                case 7:
-                    this._terminal.wraparoundMode = true;
-                    break;
-                case 12:
-                    break;
-                case 66:
-                    this._terminal.log('Serial port requested application keypad.');
-                    this._terminal.applicationKeypad = true;
-                    this._terminal.viewport.syncScrollArea();
-                    break;
-                case 9:
-                case 1000:
-                case 1002:
-                case 1003:
-                    this._terminal.x10Mouse = params[0] === 9;
-                    this._terminal.vt200Mouse = params[0] === 1000;
-                    this._terminal.normalMouse = params[0] > 1000;
-                    this._terminal.mouseEvents = true;
-                    this._terminal.element.style.cursor = 'default';
-                    this._terminal.log('Binding to mouse events.');
-                    break;
-                case 1004:
-                    this._terminal.sendFocus = true;
-                    break;
-                case 1005:
-                    this._terminal.utfMouse = true;
-                    break;
-                case 1006:
-                    this._terminal.sgrMouse = true;
-                    break;
-                case 1015:
-                    this._terminal.urxvtMouse = true;
-                    break;
-                case 25:
-                    this._terminal.cursorHidden = false;
-                    break;
-                case 1049:
-                    ;
-                case 47:
-                case 1047:
-                    if (!this._terminal.normal) {
-                        var normal = {
-                            lines: this._terminal.lines,
-                            ybase: this._terminal.ybase,
-                            ydisp: this._terminal.ydisp,
-                            x: this._terminal.x,
-                            y: this._terminal.y,
-                            scrollTop: this._terminal.scrollTop,
-                            scrollBottom: this._terminal.scrollBottom,
-                            tabs: this._terminal.tabs
-                        };
-                        this._terminal.reset();
-                        this._terminal.viewport.syncScrollArea();
-                        this._terminal.normal = normal;
-                        this._terminal.showCursor();
-                    }
-                    break;
-            }
-        }
-    };
-    InputHandler.prototype.resetMode = function (params) {
-        if (params.length > 1) {
-            for (var i = 0; i < params.length; i++) {
-                this.resetMode([params[i]]);
-            }
-            return;
-        }
-        if (!this._terminal.prefix) {
-            switch (params[0]) {
-                case 4:
-                    this._terminal.insertMode = false;
-                    break;
-                case 20:
-                    break;
-            }
-        }
-        else if (this._terminal.prefix === '?') {
-            switch (params[0]) {
-                case 1:
-                    this._terminal.applicationCursor = false;
-                    break;
-                case 3:
-                    if (this._terminal.cols === 132 && this._terminal.savedCols) {
-                        this._terminal.resize(this._terminal.savedCols, this._terminal.rows);
-                    }
-                    delete this._terminal.savedCols;
-                    break;
-                case 6:
-                    this._terminal.originMode = false;
-                    break;
-                case 7:
-                    this._terminal.wraparoundMode = false;
-                    break;
-                case 12:
-                    break;
-                case 66:
-                    this._terminal.log('Switching back to normal keypad.');
-                    this._terminal.applicationKeypad = false;
-                    this._terminal.viewport.syncScrollArea();
-                    break;
-                case 9:
-                case 1000:
-                case 1002:
-                case 1003:
-                    this._terminal.x10Mouse = false;
-                    this._terminal.vt200Mouse = false;
-                    this._terminal.normalMouse = false;
-                    this._terminal.mouseEvents = false;
-                    this._terminal.element.style.cursor = '';
-                    break;
-                case 1004:
-                    this._terminal.sendFocus = false;
-                    break;
-                case 1005:
-                    this._terminal.utfMouse = false;
-                    break;
-                case 1006:
-                    this._terminal.sgrMouse = false;
-                    break;
-                case 1015:
-                    this._terminal.urxvtMouse = false;
-                    break;
-                case 25:
-                    this._terminal.cursorHidden = true;
-                    break;
-                case 1049:
-                    ;
-                case 47:
-                case 1047:
-                    if (this._terminal.normal) {
-                        this._terminal.lines = this._terminal.normal.lines;
-                        this._terminal.ybase = this._terminal.normal.ybase;
-                        this._terminal.ydisp = this._terminal.normal.ydisp;
-                        this._terminal.x = this._terminal.normal.x;
-                        this._terminal.y = this._terminal.normal.y;
-                        this._terminal.scrollTop = this._terminal.normal.scrollTop;
-                        this._terminal.scrollBottom = this._terminal.normal.scrollBottom;
-                        this._terminal.tabs = this._terminal.normal.tabs;
-                        this._terminal.normal = null;
-                        this._terminal.refresh(0, this._terminal.rows - 1);
-                        this._terminal.viewport.syncScrollArea();
-                        this._terminal.showCursor();
-                    }
-                    break;
-            }
-        }
-    };
-    InputHandler.prototype.charAttributes = function (params) {
-        if (params.length === 1 && params[0] === 0) {
-            this._terminal.curAttr = this._terminal.defAttr;
-            return;
-        }
-        var l = params.length, i = 0, flags = this._terminal.curAttr >> 18, fg = (this._terminal.curAttr >> 9) & 0x1ff, bg = this._terminal.curAttr & 0x1ff, p;
-        for (; i < l; i++) {
-            p = params[i];
-            if (p >= 30 && p <= 37) {
-                fg = p - 30;
-            }
-            else if (p >= 40 && p <= 47) {
-                bg = p - 40;
-            }
-            else if (p >= 90 && p <= 97) {
-                p += 8;
-                fg = p - 90;
-            }
-            else if (p >= 100 && p <= 107) {
-                p += 8;
-                bg = p - 100;
-            }
-            else if (p === 0) {
-                flags = this._terminal.defAttr >> 18;
-                fg = (this._terminal.defAttr >> 9) & 0x1ff;
-                bg = this._terminal.defAttr & 0x1ff;
-            }
-            else if (p === 1) {
-                flags |= 1;
-            }
-            else if (p === 4) {
-                flags |= 2;
-            }
-            else if (p === 5) {
-                flags |= 4;
-            }
-            else if (p === 7) {
-                flags |= 8;
-            }
-            else if (p === 8) {
-                flags |= 16;
-            }
-            else if (p === 22) {
-                flags &= ~1;
-            }
-            else if (p === 24) {
-                flags &= ~2;
-            }
-            else if (p === 25) {
-                flags &= ~4;
-            }
-            else if (p === 27) {
-                flags &= ~8;
-            }
-            else if (p === 28) {
-                flags &= ~16;
-            }
-            else if (p === 39) {
-                fg = (this._terminal.defAttr >> 9) & 0x1ff;
-            }
-            else if (p === 49) {
-                bg = this._terminal.defAttr & 0x1ff;
-            }
-            else if (p === 38) {
-                if (params[i + 1] === 2) {
-                    i += 2;
-                    fg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff);
-                    if (fg === -1)
-                        fg = 0x1ff;
-                    i += 2;
-                }
-                else if (params[i + 1] === 5) {
-                    i += 2;
-                    p = params[i] & 0xff;
-                    fg = p;
-                }
-            }
-            else if (p === 48) {
-                if (params[i + 1] === 2) {
-                    i += 2;
-                    bg = this._terminal.matchColor(params[i] & 0xff, params[i + 1] & 0xff, params[i + 2] & 0xff);
-                    if (bg === -1)
-                        bg = 0x1ff;
-                    i += 2;
-                }
-                else if (params[i + 1] === 5) {
-                    i += 2;
-                    p = params[i] & 0xff;
-                    bg = p;
-                }
-            }
-            else if (p === 100) {
-                fg = (this._terminal.defAttr >> 9) & 0x1ff;
-                bg = this._terminal.defAttr & 0x1ff;
-            }
-            else {
-                this._terminal.error('Unknown SGR attribute: %d.', p);
-            }
-        }
-        this._terminal.curAttr = (flags << 18) | (fg << 9) | bg;
-    };
-    InputHandler.prototype.deviceStatus = function (params) {
-        if (!this._terminal.prefix) {
-            switch (params[0]) {
-                case 5:
-                    this._terminal.send(EscapeSequences_1.C0.ESC + '[0n');
-                    break;
-                case 6:
-                    this._terminal.send(EscapeSequences_1.C0.ESC + '['
-                        + (this._terminal.y + 1)
-                        + ';'
-                        + (this._terminal.x + 1)
-                        + 'R');
-                    break;
-            }
-        }
-        else if (this._terminal.prefix === '?') {
-            switch (params[0]) {
-                case 6:
-                    this._terminal.send(EscapeSequences_1.C0.ESC + '[?'
-                        + (this._terminal.y + 1)
-                        + ';'
-                        + (this._terminal.x + 1)
-                        + 'R');
-                    break;
-                case 15:
-                    break;
-                case 25:
-                    break;
-                case 26:
-                    break;
-                case 53:
-                    break;
-            }
-        }
-    };
-    InputHandler.prototype.softReset = function (params) {
-        this._terminal.cursorHidden = false;
-        this._terminal.insertMode = false;
-        this._terminal.originMode = false;
-        this._terminal.wraparoundMode = true;
-        this._terminal.applicationKeypad = false;
-        this._terminal.viewport.syncScrollArea();
-        this._terminal.applicationCursor = false;
-        this._terminal.scrollTop = 0;
-        this._terminal.scrollBottom = this._terminal.rows - 1;
-        this._terminal.curAttr = this._terminal.defAttr;
-        this._terminal.x = this._terminal.y = 0;
-        this._terminal.charset = null;
-        this._terminal.glevel = 0;
-        this._terminal.charsets = [null];
-    };
-    InputHandler.prototype.setCursorStyle = function (params) {
-        var param = params[0] < 1 ? 1 : params[0];
-        switch (param) {
-            case 1:
-            case 2:
-                this._terminal.setOption('cursorStyle', 'block');
-                break;
-            case 3:
-            case 4:
-                this._terminal.setOption('cursorStyle', 'underline');
-                break;
-            case 5:
-            case 6:
-                this._terminal.setOption('cursorStyle', 'bar');
-                break;
-        }
-        var isBlinking = param % 2 === 1;
-        this._terminal.setOption('cursorBlink', isBlinking);
-    };
-    InputHandler.prototype.setScrollRegion = function (params) {
-        if (this._terminal.prefix)
-            return;
-        this._terminal.scrollTop = (params[0] || 1) - 1;
-        this._terminal.scrollBottom = (params[1] && params[1] <= this._terminal.rows ? params[1] : this._terminal.rows) - 1;
-        this._terminal.x = 0;
-        this._terminal.y = 0;
-    };
-    InputHandler.prototype.saveCursor = function (params) {
-        this._terminal.savedX = this._terminal.x;
-        this._terminal.savedY = this._terminal.y;
-    };
-    InputHandler.prototype.restoreCursor = function (params) {
-        this._terminal.x = this._terminal.savedX || 0;
-        this._terminal.y = this._terminal.savedY || 0;
-    };
-    return InputHandler;
-}());
-exports.InputHandler = InputHandler;
-var wcwidth = (function (opts) {
-    var COMBINING = [
-        [0x0300, 0x036F], [0x0483, 0x0486], [0x0488, 0x0489],
-        [0x0591, 0x05BD], [0x05BF, 0x05BF], [0x05C1, 0x05C2],
-        [0x05C4, 0x05C5], [0x05C7, 0x05C7], [0x0600, 0x0603],
-        [0x0610, 0x0615], [0x064B, 0x065E], [0x0670, 0x0670],
-        [0x06D6, 0x06E4], [0x06E7, 0x06E8], [0x06EA, 0x06ED],
-        [0x070F, 0x070F], [0x0711, 0x0711], [0x0730, 0x074A],
-        [0x07A6, 0x07B0], [0x07EB, 0x07F3], [0x0901, 0x0902],
-        [0x093C, 0x093C], [0x0941, 0x0948], [0x094D, 0x094D],
-        [0x0951, 0x0954], [0x0962, 0x0963], [0x0981, 0x0981],
-        [0x09BC, 0x09BC], [0x09C1, 0x09C4], [0x09CD, 0x09CD],
-        [0x09E2, 0x09E3], [0x0A01, 0x0A02], [0x0A3C, 0x0A3C],
-        [0x0A41, 0x0A42], [0x0A47, 0x0A48], [0x0A4B, 0x0A4D],
-        [0x0A70, 0x0A71], [0x0A81, 0x0A82], [0x0ABC, 0x0ABC],
-        [0x0AC1, 0x0AC5], [0x0AC7, 0x0AC8], [0x0ACD, 0x0ACD],
-        [0x0AE2, 0x0AE3], [0x0B01, 0x0B01], [0x0B3C, 0x0B3C],
-        [0x0B3F, 0x0B3F], [0x0B41, 0x0B43], [0x0B4D, 0x0B4D],
-        [0x0B56, 0x0B56], [0x0B82, 0x0B82], [0x0BC0, 0x0BC0],
-        [0x0BCD, 0x0BCD], [0x0C3E, 0x0C40], [0x0C46, 0x0C48],
-        [0x0C4A, 0x0C4D], [0x0C55, 0x0C56], [0x0CBC, 0x0CBC],
-        [0x0CBF, 0x0CBF], [0x0CC6, 0x0CC6], [0x0CCC, 0x0CCD],
-        [0x0CE2, 0x0CE3], [0x0D41, 0x0D43], [0x0D4D, 0x0D4D],
-        [0x0DCA, 0x0DCA], [0x0DD2, 0x0DD4], [0x0DD6, 0x0DD6],
-        [0x0E31, 0x0E31], [0x0E34, 0x0E3A], [0x0E47, 0x0E4E],
-        [0x0EB1, 0x0EB1], [0x0EB4, 0x0EB9], [0x0EBB, 0x0EBC],
-        [0x0EC8, 0x0ECD], [0x0F18, 0x0F19], [0x0F35, 0x0F35],
-        [0x0F37, 0x0F37], [0x0F39, 0x0F39], [0x0F71, 0x0F7E],
-        [0x0F80, 0x0F84], [0x0F86, 0x0F87], [0x0F90, 0x0F97],
-        [0x0F99, 0x0FBC], [0x0FC6, 0x0FC6], [0x102D, 0x1030],
-        [0x1032, 0x1032], [0x1036, 0x1037], [0x1039, 0x1039],
-        [0x1058, 0x1059], [0x1160, 0x11FF], [0x135F, 0x135F],
-        [0x1712, 0x1714], [0x1732, 0x1734], [0x1752, 0x1753],
-        [0x1772, 0x1773], [0x17B4, 0x17B5], [0x17B7, 0x17BD],
-        [0x17C6, 0x17C6], [0x17C9, 0x17D3], [0x17DD, 0x17DD],
-        [0x180B, 0x180D], [0x18A9, 0x18A9], [0x1920, 0x1922],
-        [0x1927, 0x1928], [0x1932, 0x1932], [0x1939, 0x193B],
-        [0x1A17, 0x1A18], [0x1B00, 0x1B03], [0x1B34, 0x1B34],
-        [0x1B36, 0x1B3A], [0x1B3C, 0x1B3C], [0x1B42, 0x1B42],
-        [0x1B6B, 0x1B73], [0x1DC0, 0x1DCA], [0x1DFE, 0x1DFF],
-        [0x200B, 0x200F], [0x202A, 0x202E], [0x2060, 0x2063],
-        [0x206A, 0x206F], [0x20D0, 0x20EF], [0x302A, 0x302F],
-        [0x3099, 0x309A], [0xA806, 0xA806], [0xA80B, 0xA80B],
-        [0xA825, 0xA826], [0xFB1E, 0xFB1E], [0xFE00, 0xFE0F],
-        [0xFE20, 0xFE23], [0xFEFF, 0xFEFF], [0xFFF9, 0xFFFB],
-        [0x10A01, 0x10A03], [0x10A05, 0x10A06], [0x10A0C, 0x10A0F],
-        [0x10A38, 0x10A3A], [0x10A3F, 0x10A3F], [0x1D167, 0x1D169],
-        [0x1D173, 0x1D182], [0x1D185, 0x1D18B], [0x1D1AA, 0x1D1AD],
-        [0x1D242, 0x1D244], [0xE0001, 0xE0001], [0xE0020, 0xE007F],
-        [0xE0100, 0xE01EF]
-    ];
-    function bisearch(ucs) {
-        var min = 0;
-        var max = COMBINING.length - 1;
-        var mid;
-        if (ucs < COMBINING[0][0] || ucs > COMBINING[max][1])
-            return false;
-        while (max >= min) {
-            mid = Math.floor((min + max) / 2);
-            if (ucs > COMBINING[mid][1])
-                min = mid + 1;
-            else if (ucs < COMBINING[mid][0])
-                max = mid - 1;
-            else
-                return true;
-        }
-        return false;
-    }
-    function wcwidth(ucs) {
-        if (ucs === 0)
-            return opts.nul;
-        if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
-            return opts.control;
-        if (bisearch(ucs))
-            return 0;
-        if (isWide(ucs)) {
-            return 2;
-        }
-        return 1;
-    }
-    function isWide(ucs) {
-        return (ucs >= 0x1100 && (ucs <= 0x115f ||
-            ucs === 0x2329 ||
-            ucs === 0x232a ||
-            (ucs >= 0x2e80 && ucs <= 0xa4cf && ucs !== 0x303f) ||
-            (ucs >= 0xac00 && ucs <= 0xd7a3) ||
-            (ucs >= 0xf900 && ucs <= 0xfaff) ||
-            (ucs >= 0xfe10 && ucs <= 0xfe19) ||
-            (ucs >= 0xfe30 && ucs <= 0xfe6f) ||
-            (ucs >= 0xff00 && ucs <= 0xff60) ||
-            (ucs >= 0xffe0 && ucs <= 0xffe6) ||
-            (ucs >= 0x20000 && ucs <= 0x2fffd) ||
-            (ucs >= 0x30000 && ucs <= 0x3fffd)));
-    }
-    return wcwidth;
-})({ nul: 0, control: 0 });
-
-
-
-},{"./Charsets":1,"./EscapeSequences":3}],6:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var INVALID_LINK_CLASS = 'xterm-invalid-link';
-var protocolClause = '(https?:\\/\\/)';
-var domainCharacterSet = '[\\da-z\\.-]+';
-var negatedDomainCharacterSet = '[^\\da-z\\.-]+';
-var domainBodyClause = '(' + domainCharacterSet + ')';
-var tldClause = '([a-z\\.]{2,6})';
-var ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
-var localHostClause = '(localhost)';
-var portClause = '(:\\d{1,5})';
-var hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
-var pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
-var queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
-var queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
-var hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
-var negatedPathCharacterSet = '[^\\/\\w\\.\\-%]+';
-var bodyClause = hostClause + pathClause + queryStringClause + hashFragmentClause;
-var start = '(?:^|' + negatedDomainCharacterSet + ')(';
-var end = ')($|' + negatedPathCharacterSet + ')';
-var strictUrlRegex = new RegExp(start + protocolClause + bodyClause + end);
-var HYPERTEXT_LINK_MATCHER_ID = 0;
-var Linkifier = (function () {
-    function Linkifier() {
-        this._nextLinkMatcherId = HYPERTEXT_LINK_MATCHER_ID;
-        this._rowTimeoutIds = [];
-        this._linkMatchers = [];
-        this.registerLinkMatcher(strictUrlRegex, null, { matchIndex: 1 });
-    }
-    Linkifier.prototype.attachToDom = function (document, rows) {
-        this._document = document;
-        this._rows = rows;
-    };
-    Linkifier.prototype.linkifyRow = function (rowIndex) {
-        if (!this._document) {
-            return;
-        }
-        var timeoutId = this._rowTimeoutIds[rowIndex];
-        if (timeoutId) {
-            clearTimeout(timeoutId);
-        }
-        this._rowTimeoutIds[rowIndex] = setTimeout(this._linkifyRow.bind(this, rowIndex), Linkifier.TIME_BEFORE_LINKIFY);
-    };
-    Linkifier.prototype.setHypertextLinkHandler = function (handler) {
-        this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].handler = handler;
-    };
-    Linkifier.prototype.setHypertextValidationCallback = function (callback) {
-        this._linkMatchers[HYPERTEXT_LINK_MATCHER_ID].validationCallback = callback;
-    };
-    Linkifier.prototype.registerLinkMatcher = function (regex, handler, options) {
-        if (options === void 0) { options = {}; }
-        if (this._nextLinkMatcherId !== HYPERTEXT_LINK_MATCHER_ID && !handler) {
-            throw new Error('handler must be defined');
-        }
-        var matcher = {
-            id: this._nextLinkMatcherId++,
-            regex: regex,
-            handler: handler,
-            matchIndex: options.matchIndex,
-            validationCallback: options.validationCallback,
-            priority: options.priority || 0
-        };
-        this._addLinkMatcherToList(matcher);
-        return matcher.id;
-    };
-    Linkifier.prototype._addLinkMatcherToList = function (matcher) {
-        if (this._linkMatchers.length === 0) {
-            this._linkMatchers.push(matcher);
-            return;
-        }
-        for (var i = this._linkMatchers.length - 1; i >= 0; i--) {
-            if (matcher.priority <= this._linkMatchers[i].priority) {
-                this._linkMatchers.splice(i + 1, 0, matcher);
-                return;
-            }
-        }
-        this._linkMatchers.splice(0, 0, matcher);
-    };
-    Linkifier.prototype.deregisterLinkMatcher = function (matcherId) {
-        for (var i = 1; i < this._linkMatchers.length; i++) {
-            if (this._linkMatchers[i].id === matcherId) {
-                this._linkMatchers.splice(i, 1);
-                return true;
-            }
-        }
-        return false;
-    };
-    Linkifier.prototype._linkifyRow = function (rowIndex) {
-        var row = this._rows[rowIndex];
-        if (!row) {
-            return;
-        }
-        var text = row.textContent;
-        for (var i = 0; i < this._linkMatchers.length; i++) {
-            var matcher = this._linkMatchers[i];
-            var linkElements = this._doLinkifyRow(row, matcher);
-            if (linkElements.length > 0) {
-                if (matcher.validationCallback) {
-                    var _loop_1 = function (j) {
-                        var element = linkElements[j];
-                        matcher.validationCallback(element.textContent, element, function (isValid) {
-                            if (!isValid) {
-                                element.classList.add(INVALID_LINK_CLASS);
-                            }
-                        });
-                    };
-                    for (var j = 0; j < linkElements.length; j++) {
-                        _loop_1(j);
-                    }
-                }
-                return;
-            }
-        }
-    };
-    Linkifier.prototype._doLinkifyRow = function (row, matcher) {
-        var result = [];
-        var isHttpLinkMatcher = matcher.id === HYPERTEXT_LINK_MATCHER_ID;
-        var nodes = row.childNodes;
-        var match = row.textContent.match(matcher.regex);
-        if (!match || match.length === 0) {
-            return result;
-        }
-        var uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
-        var rowStartIndex = match.index + uri.length;
-        for (var i = 0; i < nodes.length; i++) {
-            var node = nodes[i];
-            var searchIndex = node.textContent.indexOf(uri);
-            if (searchIndex >= 0) {
-                var linkElement = this._createAnchorElement(uri, matcher.handler, isHttpLinkMatcher);
-                if (node.textContent.length === uri.length) {
-                    if (node.nodeType === 3) {
-                        this._replaceNode(node, linkElement);
-                    }
-                    else {
-                        var element = node;
-                        if (element.nodeName === 'A') {
-                            return result;
-                        }
-                        element.innerHTML = '';
-                        element.appendChild(linkElement);
-                    }
-                }
-                else {
-                    var nodesAdded = this._replaceNodeSubstringWithNode(node, linkElement, uri, searchIndex);
-                    i += nodesAdded;
-                }
-                result.push(linkElement);
-                match = row.textContent.substring(rowStartIndex).match(matcher.regex);
-                if (!match || match.length === 0) {
-                    return result;
-                }
-                uri = match[typeof matcher.matchIndex !== 'number' ? 0 : matcher.matchIndex];
-                rowStartIndex += match.index + uri.length;
-            }
-        }
-        return result;
-    };
-    Linkifier.prototype._createAnchorElement = function (uri, handler, isHypertextLinkHandler) {
-        var element = this._document.createElement('a');
-        element.textContent = uri;
-        element.draggable = false;
-        if (isHypertextLinkHandler) {
-            element.href = uri;
-            element.target = '_blank';
-            element.addEventListener('click', function (event) {
-                if (handler) {
-                    return handler(event, uri);
-                }
-            });
-        }
-        else {
-            element.addEventListener('click', function (event) {
-                if (element.classList.contains(INVALID_LINK_CLASS)) {
-                    return;
-                }
-                return handler(event, uri);
-            });
-        }
-        return element;
-    };
-    Linkifier.prototype._replaceNode = function (oldNode) {
-        var newNodes = [];
-        for (var _i = 1; _i < arguments.length; _i++) {
-            newNodes[_i - 1] = arguments[_i];
-        }
-        var parent = oldNode.parentNode;
-        for (var i = 0; i < newNodes.length; i++) {
-            parent.insertBefore(newNodes[i], oldNode);
-        }
-        parent.removeChild(oldNode);
-    };
-    Linkifier.prototype._replaceNodeSubstringWithNode = function (targetNode, newNode, substring, substringIndex) {
-        var node = targetNode;
-        if (node.nodeType !== 3) {
-            node = node.childNodes[0];
-        }
-        if (node.childNodes.length === 0 && node.nodeType !== 3) {
-            throw new Error('targetNode must be a text node or only contain a single text node');
-        }
-        var fullText = node.textContent;
-        if (substringIndex === 0) {
-            var rightText_1 = fullText.substring(substring.length);
-            var rightTextNode_1 = this._document.createTextNode(rightText_1);
-            this._replaceNode(node, newNode, rightTextNode_1);
-            return 0;
-        }
-        if (substringIndex === targetNode.textContent.length - substring.length) {
-            var leftText_1 = fullText.substring(0, substringIndex);
-            var leftTextNode_1 = this._document.createTextNode(leftText_1);
-            this._replaceNode(node, leftTextNode_1, newNode);
-            return 0;
-        }
-        var leftText = fullText.substring(0, substringIndex);
-        var leftTextNode = this._document.createTextNode(leftText);
-        var rightText = fullText.substring(substringIndex + substring.length);
-        var rightTextNode = this._document.createTextNode(rightText);
-        this._replaceNode(node, leftTextNode, newNode, rightTextNode);
-        return 1;
-    };
-    return Linkifier;
-}());
-Linkifier.TIME_BEFORE_LINKIFY = 200;
-exports.Linkifier = Linkifier;
-
-
-
-},{}],7:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var EscapeSequences_1 = require("./EscapeSequences");
-var Charsets_1 = require("./Charsets");
-var normalStateHandler = {};
-normalStateHandler[EscapeSequences_1.C0.BEL] = function (parser, handler) { return handler.bell(); };
-normalStateHandler[EscapeSequences_1.C0.LF] = function (parser, handler) { return handler.lineFeed(); };
-normalStateHandler[EscapeSequences_1.C0.VT] = normalStateHandler[EscapeSequences_1.C0.LF];
-normalStateHandler[EscapeSequences_1.C0.FF] = normalStateHandler[EscapeSequences_1.C0.LF];
-normalStateHandler[EscapeSequences_1.C0.CR] = function (parser, handler) { return handler.carriageReturn(); };
-normalStateHandler[EscapeSequences_1.C0.BS] = function (parser, handler) { return handler.backspace(); };
-normalStateHandler[EscapeSequences_1.C0.HT] = function (parser, handler) { return handler.tab(); };
-normalStateHandler[EscapeSequences_1.C0.SO] = function (parser, handler) { return handler.shiftOut(); };
-normalStateHandler[EscapeSequences_1.C0.SI] = function (parser, handler) { return handler.shiftIn(); };
-normalStateHandler[EscapeSequences_1.C0.ESC] = function (parser, handler) { return parser.setState(ParserState.ESCAPED); };
-var escapedStateHandler = {};
-escapedStateHandler['['] = function (parser, terminal) {
-    terminal.params = [];
-    terminal.currentParam = 0;
-    parser.setState(ParserState.CSI_PARAM);
-};
-escapedStateHandler[']'] = function (parser, terminal) {
-    terminal.params = [];
-    terminal.currentParam = 0;
-    parser.setState(ParserState.OSC);
-};
-escapedStateHandler['P'] = function (parser, terminal) {
-    terminal.params = [];
-    terminal.currentParam = 0;
-    parser.setState(ParserState.DCS);
-};
-escapedStateHandler['_'] = function (parser, terminal) {
-    parser.setState(ParserState.IGNORE);
-};
-escapedStateHandler['^'] = function (parser, terminal) {
-    parser.setState(ParserState.IGNORE);
-};
-escapedStateHandler['c'] = function (parser, terminal) {
-    terminal.reset();
-};
-escapedStateHandler['E'] = function (parser, terminal) {
-    terminal.x = 0;
-    terminal.index();
-    parser.setState(ParserState.NORMAL);
-};
-escapedStateHandler['D'] = function (parser, terminal) {
-    terminal.index();
-    parser.setState(ParserState.NORMAL);
-};
-escapedStateHandler['M'] = function (parser, terminal) {
-    terminal.reverseIndex();
-    parser.setState(ParserState.NORMAL);
-};
-escapedStateHandler['%'] = function (parser, terminal) {
-    terminal.setgLevel(0);
-    terminal.setgCharset(0, Charsets_1.DEFAULT_CHARSET);
-    parser.setState(ParserState.NORMAL);
-    parser.skipNextChar();
-};
-escapedStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); };
-var csiParamStateHandler = {};
-csiParamStateHandler['?'] = function (parser) { return parser.setPrefix('?'); };
-csiParamStateHandler['>'] = function (parser) { return parser.setPrefix('>'); };
-csiParamStateHandler['!'] = function (parser) { return parser.setPrefix('!'); };
-csiParamStateHandler['0'] = function (parser) { return parser.setParam(parser.getParam() * 10); };
-csiParamStateHandler['1'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 1); };
-csiParamStateHandler['2'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 2); };
-csiParamStateHandler['3'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 3); };
-csiParamStateHandler['4'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 4); };
-csiParamStateHandler['5'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 5); };
-csiParamStateHandler['6'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 6); };
-csiParamStateHandler['7'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 7); };
-csiParamStateHandler['8'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 8); };
-csiParamStateHandler['9'] = function (parser) { return parser.setParam(parser.getParam() * 10 + 9); };
-csiParamStateHandler['$'] = function (parser) { return parser.setPostfix('$'); };
-csiParamStateHandler['"'] = function (parser) { return parser.setPostfix('"'); };
-csiParamStateHandler[' '] = function (parser) { return parser.setPostfix(' '); };
-csiParamStateHandler['\''] = function (parser) { return parser.setPostfix('\''); };
-csiParamStateHandler[';'] = function (parser) { return parser.finalizeParam(); };
-csiParamStateHandler[EscapeSequences_1.C0.CAN] = function (parser) { return parser.setState(ParserState.NORMAL); };
-var csiStateHandler = {};
-csiStateHandler['@'] = function (handler, params, prefix) { return handler.insertChars(params); };
-csiStateHandler['A'] = function (handler, params, prefix) { return handler.cursorUp(params); };
-csiStateHandler['B'] = function (handler, params, prefix) { return handler.cursorDown(params); };
-csiStateHandler['C'] = function (handler, params, prefix) { return handler.cursorForward(params); };
-csiStateHandler['D'] = function (handler, params, prefix) { return handler.cursorBackward(params); };
-csiStateHandler['E'] = function (handler, params, prefix) { return handler.cursorNextLine(params); };
-csiStateHandler['F'] = function (handler, params, prefix) { return handler.cursorPrecedingLine(params); };
-csiStateHandler['G'] = function (handler, params, prefix) { return handler.cursorCharAbsolute(params); };
-csiStateHandler['H'] = function (handler, params, prefix) { return handler.cursorPosition(params); };
-csiStateHandler['I'] = function (handler, params, prefix) { return handler.cursorForwardTab(params); };
-csiStateHandler['J'] = function (handler, params, prefix) { return handler.eraseInDisplay(params); };
-csiStateHandler['K'] = function (handler, params, prefix) { return handler.eraseInLine(params); };
-csiStateHandler['L'] = function (handler, params, prefix) { return handler.insertLines(params); };
-csiStateHandler['M'] = function (handler, params, prefix) { return handler.deleteLines(params); };
-csiStateHandler['P'] = function (handler, params, prefix) { return handler.deleteChars(params); };
-csiStateHandler['S'] = function (handler, params, prefix) { return handler.scrollUp(params); };
-csiStateHandler['T'] = function (handler, params, prefix) {
-    if (params.length < 2 && !prefix) {
-        handler.scrollDown(params);
-    }
-};
-csiStateHandler['X'] = function (handler, params, prefix) { return handler.eraseChars(params); };
-csiStateHandler['Z'] = function (handler, params, prefix) { return handler.cursorBackwardTab(params); };
-csiStateHandler['`'] = function (handler, params, prefix) { return handler.charPosAbsolute(params); };
-csiStateHandler['a'] = function (handler, params, prefix) { return handler.HPositionRelative(params); };
-csiStateHandler['b'] = function (handler, params, prefix) { return handler.repeatPrecedingCharacter(params); };
-csiStateHandler['c'] = function (handler, params, prefix) { return handler.sendDeviceAttributes(params); };
-csiStateHandler['d'] = function (handler, params, prefix) { return handler.linePosAbsolute(params); };
-csiStateHandler['e'] = function (handler, params, prefix) { return handler.VPositionRelative(params); };
-csiStateHandler['f'] = function (handler, params, prefix) { return handler.HVPosition(params); };
-csiStateHandler['g'] = function (handler, params, prefix) { return handler.tabClear(params); };
-csiStateHandler['h'] = function (handler, params, prefix) { return handler.setMode(params); };
-csiStateHandler['l'] = function (handler, params, prefix) { return handler.resetMode(params); };
-csiStateHandler['m'] = function (handler, params, prefix) { return handler.charAttributes(params); };
-csiStateHandler['n'] = function (handler, params, prefix) { return handler.deviceStatus(params); };
-csiStateHandler['p'] = function (handler, params, prefix) {
-    switch (prefix) {
-        case '!':
-            handler.softReset(params);
-            break;
-    }
-};
-csiStateHandler['q'] = function (handler, params, prefix, postfix) {
-    if (postfix === ' ') {
-        handler.setCursorStyle(params);
-    }
-};
-csiStateHandler['r'] = function (handler, params) { return handler.setScrollRegion(params); };
-csiStateHandler['s'] = function (handler, params) { return handler.saveCursor(params); };
-csiStateHandler['u'] = function (handler, params) { return handler.restoreCursor(params); };
-csiStateHandler[EscapeSequences_1.C0.CAN] = function (handler, params, prefix, postfix, parser) { return parser.setState(ParserState.NORMAL); };
-var ParserState;
-(function (ParserState) {
-    ParserState[ParserState["NORMAL"] = 0] = "NORMAL";
-    ParserState[ParserState["ESCAPED"] = 1] = "ESCAPED";
-    ParserState[ParserState["CSI_PARAM"] = 2] = "CSI_PARAM";
-    ParserState[ParserState["CSI"] = 3] = "CSI";
-    ParserState[ParserState["OSC"] = 4] = "OSC";
-    ParserState[ParserState["CHARSET"] = 5] = "CHARSET";
-    ParserState[ParserState["DCS"] = 6] = "DCS";
-    ParserState[ParserState["IGNORE"] = 7] = "IGNORE";
-})(ParserState || (ParserState = {}));
-var Parser = (function () {
-    function Parser(_inputHandler, _terminal) {
-        this._inputHandler = _inputHandler;
-        this._terminal = _terminal;
-        this._state = ParserState.NORMAL;
-    }
-    Parser.prototype.parse = function (data) {
-        var l = data.length, j, cs, ch, code, low;
-        this._position = 0;
-        if (this._terminal.surrogate_high) {
-            data = this._terminal.surrogate_high + data;
-            this._terminal.surrogate_high = '';
-        }
-        for (; this._position < l; this._position++) {
-            ch = data[this._position];
-            code = data.charCodeAt(this._position);
-            if (0xD800 <= code && code <= 0xDBFF) {
-                low = data.charCodeAt(this._position + 1);
-                if (isNaN(low)) {
-                    this._terminal.surrogate_high = ch;
-                    continue;
-                }
-                code = ((code - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000;
-                ch += data.charAt(this._position + 1);
-            }
-            if (0xDC00 <= code && code <= 0xDFFF)
-                continue;
-            switch (this._state) {
-                case ParserState.NORMAL:
-                    if (ch in normalStateHandler) {
-                        normalStateHandler[ch](this, this._inputHandler);
-                    }
-                    else {
-                        this._inputHandler.addChar(ch, code);
-                    }
-                    break;
-                case ParserState.ESCAPED:
-                    if (ch in escapedStateHandler) {
-                        escapedStateHandler[ch](this, this._terminal);
-                        break;
-                    }
-                    switch (ch) {
-                        case '(':
-                        case ')':
-                        case '*':
-                        case '+':
-                        case '-':
-                        case '.':
-                            switch (ch) {
-                                case '(':
-                                    this._terminal.gcharset = 0;
-                                    break;
-                                case ')':
-                                    this._terminal.gcharset = 1;
-                                    break;
-                                case '*':
-                                    this._terminal.gcharset = 2;
-                                    break;
-                                case '+':
-                                    this._terminal.gcharset = 3;
-                                    break;
-                                case '-':
-                                    this._terminal.gcharset = 1;
-                                    break;
-                                case '.':
-                                    this._terminal.gcharset = 2;
-                                    break;
-                            }
-                            this._state = ParserState.CHARSET;
-                            break;
-                        case '/':
-                            this._terminal.gcharset = 3;
-                            this._state = ParserState.CHARSET;
-                            this._position--;
-                            break;
-                        case 'N':
-                            break;
-                        case 'O':
-                            break;
-                        case 'n':
-                            this._terminal.setgLevel(2);
-                            break;
-                        case 'o':
-                            this._terminal.setgLevel(3);
-                            break;
-                        case '|':
-                            this._terminal.setgLevel(3);
-                            break;
-                        case '}':
-                            this._terminal.setgLevel(2);
-                            break;
-                        case '~':
-                            this._terminal.setgLevel(1);
-                            break;
-                        case '7':
-                            this._inputHandler.saveCursor();
-                            this._state = ParserState.NORMAL;
-                            break;
-                        case '8':
-                            this._inputHandler.restoreCursor();
-                            this._state = ParserState.NORMAL;
-                            break;
-                        case '#':
-                            this._state = ParserState.NORMAL;
-                            this._position++;
-                            break;
-                        case 'H':
-                            this._terminal.tabSet();
-                            this._state = ParserState.NORMAL;
-                            break;
-                        case '=':
-                            this._terminal.log('Serial port requested application keypad.');
-                            this._terminal.applicationKeypad = true;
-                            this._terminal.viewport.syncScrollArea();
-                            this._state = ParserState.NORMAL;
-                            break;
-                        case '>':
-                            this._terminal.log('Switching back to normal keypad.');
-                            this._terminal.applicationKeypad = false;
-                            this._terminal.viewport.syncScrollArea();
-                            this._state = ParserState.NORMAL;
-                            break;
-                        default:
-                            this._state = ParserState.NORMAL;
-                            this._terminal.error('Unknown ESC control: %s.', ch);
-                            break;
-                    }
-                    break;
-                case ParserState.CHARSET:
-                    if (ch in Charsets_1.CHARSETS) {
-                        cs = Charsets_1.CHARSETS[ch];
-                        if (ch === '/') {
-                            this.skipNextChar();
-                        }
-                    }
-                    else {
-                        cs = Charsets_1.DEFAULT_CHARSET;
-                    }
-                    this._terminal.setgCharset(this._terminal.gcharset, cs);
-                    this._terminal.gcharset = null;
-                    this._state = ParserState.NORMAL;
-                    break;
-                case ParserState.OSC:
-                    if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) {
-                        if (ch === EscapeSequences_1.C0.ESC)
-                            this._position++;
-                        this._terminal.params.push(this._terminal.currentParam);
-                        switch (this._terminal.params[0]) {
-                            case 0:
-                            case 1:
-                            case 2:
-                                if (this._terminal.params[1]) {
-                                    this._terminal.title = this._terminal.params[1];
-                                    this._terminal.handleTitle(this._terminal.title);
-                                }
-                                break;
-                            case 3:
-                                break;
-                            case 4:
-                            case 5:
-                                break;
-                            case 10:
-                            case 11:
-                            case 12:
-                            case 13:
-                            case 14:
-                            case 15:
-                            case 16:
-                            case 17:
-                            case 18:
-                            case 19:
-                                break;
-                            case 46:
-                                break;
-                            case 50:
-                                break;
-                            case 51:
-                                break;
-                            case 52:
-                                break;
-                            case 104:
-                            case 105:
-                            case 110:
-                            case 111:
-                            case 112:
-                            case 113:
-                            case 114:
-                            case 115:
-                            case 116:
-                            case 117:
-                            case 118:
-                                break;
-                        }
-                        this._terminal.params = [];
-                        this._terminal.currentParam = 0;
-                        this._state = ParserState.NORMAL;
-                    }
-                    else {
-                        if (!this._terminal.params.length) {
-                            if (ch >= '0' && ch <= '9') {
-                                this._terminal.currentParam =
-                                    this._terminal.currentParam * 10 + ch.charCodeAt(0) - 48;
-                            }
-                            else if (ch === ';') {
-                                this._terminal.params.push(this._terminal.currentParam);
-                                this._terminal.currentParam = '';
-                            }
-                        }
-                        else {
-                            this._terminal.currentParam += ch;
-                        }
-                    }
-                    break;
-                case ParserState.CSI_PARAM:
-                    if (ch in csiParamStateHandler) {
-                        csiParamStateHandler[ch](this);
-                        break;
-                    }
-                    this.finalizeParam();
-                    this._state = ParserState.CSI;
-                case ParserState.CSI:
-                    if (ch in csiStateHandler) {
-                        csiStateHandler[ch](this._inputHandler, this._terminal.params, this._terminal.prefix, this._terminal.postfix, this);
-                    }
-                    else {
-                        this._terminal.error('Unknown CSI code: %s.', ch);
-                    }
-                    this._state = ParserState.NORMAL;
-                    this._terminal.prefix = '';
-                    this._terminal.postfix = '';
-                    break;
-                case ParserState.DCS:
-                    if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) {
-                        if (ch === EscapeSequences_1.C0.ESC)
-                            this._position++;
-                        var pt = void 0;
-                        var valid = void 0;
-                        switch (this._terminal.prefix) {
-                            case '':
-                                break;
-                            case '$q':
-                                pt = this._terminal.currentParam;
-                                valid = false;
-                                switch (pt) {
-                                    case '"q':
-                                        pt = '0"q';
-                                        break;
-                                    case '"p':
-                                        pt = '61"p';
-                                        break;
-                                    case 'r':
-                                        pt = ''
-                                            + (this._terminal.scrollTop + 1)
-                                            + ';'
-                                            + (this._terminal.scrollBottom + 1)
-                                            + 'r';
-                                        break;
-                                    case 'm':
-                                        pt = '0m';
-                                        break;
-                                    default:
-                                        this._terminal.error('Unknown DCS Pt: %s.', pt);
-                                        pt = '';
-                                        break;
-                                }
-                                this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '$r' + pt + EscapeSequences_1.C0.ESC + '\\');
-                                break;
-                            case '+p':
-                                break;
-                            case '+q':
-                                pt = this._terminal.currentParam;
-                                valid = false;
-                                this._terminal.send(EscapeSequences_1.C0.ESC + 'P' + +valid + '+r' + pt + EscapeSequences_1.C0.ESC + '\\');
-                                break;
-                            default:
-                                this._terminal.error('Unknown DCS prefix: %s.', this._terminal.prefix);
-                                break;
-                        }
-                        this._terminal.currentParam = 0;
-                        this._terminal.prefix = '';
-                        this._state = ParserState.NORMAL;
-                    }
-                    else if (!this._terminal.currentParam) {
-                        if (!this._terminal.prefix && ch !== '$' && ch !== '+') {
-                            this._terminal.currentParam = ch;
-                        }
-                        else if (this._terminal.prefix.length === 2) {
-                            this._terminal.currentParam = ch;
-                        }
-                        else {
-                            this._terminal.prefix += ch;
-                        }
-                    }
-                    else {
-                        this._terminal.currentParam += ch;
-                    }
-                    break;
-                case ParserState.IGNORE:
-                    if (ch === EscapeSequences_1.C0.ESC || ch === EscapeSequences_1.C0.BEL) {
-                        if (ch === EscapeSequences_1.C0.ESC)
-                            this._position++;
-                        this._state = ParserState.NORMAL;
-                    }
-                    break;
-            }
-        }
-        return this._state;
-    };
-    Parser.prototype.setState = function (state) {
-        this._state = state;
-    };
-    Parser.prototype.setPrefix = function (prefix) {
-        this._terminal.prefix = prefix;
-    };
-    Parser.prototype.setPostfix = function (postfix) {
-        this._terminal.postfix = postfix;
-    };
-    Parser.prototype.setParam = function (param) {
-        this._terminal.currentParam = param;
-    };
-    Parser.prototype.getParam = function () {
-        return this._terminal.currentParam;
-    };
-    Parser.prototype.finalizeParam = function () {
-        this._terminal.params.push(this._terminal.currentParam);
-        this._terminal.currentParam = 0;
-    };
-    Parser.prototype.skipNextChar = function () {
-        this._position++;
-    };
-    return Parser;
-}());
-exports.Parser = Parser;
-
-
-
-},{"./Charsets":1,"./EscapeSequences":3}],8:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var DomElementObjectPool_1 = require("./utils/DomElementObjectPool");
-var MAX_REFRESH_FRAME_SKIP = 5;
-var FLAGS;
-(function (FLAGS) {
-    FLAGS[FLAGS["BOLD"] = 1] = "BOLD";
-    FLAGS[FLAGS["UNDERLINE"] = 2] = "UNDERLINE";
-    FLAGS[FLAGS["BLINK"] = 4] = "BLINK";
-    FLAGS[FLAGS["INVERSE"] = 8] = "INVERSE";
-    FLAGS[FLAGS["INVISIBLE"] = 16] = "INVISIBLE";
-})(FLAGS || (FLAGS = {}));
-;
-var brokenBold = null;
-var Renderer = (function () {
-    function Renderer(_terminal) {
-        this._terminal = _terminal;
-        this._refreshRowsQueue = [];
-        this._refreshFramesSkipped = 0;
-        this._refreshAnimationFrame = null;
-        this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span');
-        if (brokenBold === null) {
-            brokenBold = checkBoldBroken(this._terminal.element);
-        }
-        this._spanElementObjectPool = new DomElementObjectPool_1.DomElementObjectPool('span');
-    }
-    Renderer.prototype.queueRefresh = function (start, end) {
-        this._refreshRowsQueue.push({ start: start, end: end });
-        if (!this._refreshAnimationFrame) {
-            this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
-        }
-    };
-    Renderer.prototype._refreshLoop = function () {
-        var skipFrame = this._terminal.writeBuffer.length > 0 && this._refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;
-        if (skipFrame) {
-            this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this));
-            return;
-        }
-        this._refreshFramesSkipped = 0;
-        var start;
-        var end;
-        if (this._refreshRowsQueue.length > 4) {
-            start = 0;
-            end = this._terminal.rows - 1;
-        }
-        else {
-            start = this._refreshRowsQueue[0].start;
-            end = this._refreshRowsQueue[0].end;
-            for (var i = 1; i < this._refreshRowsQueue.length; i++) {
-                if (this._refreshRowsQueue[i].start < start) {
-                    start = this._refreshRowsQueue[i].start;
-                }
-                if (this._refreshRowsQueue[i].end > end) {
-                    end = this._refreshRowsQueue[i].end;
-                }
-            }
-        }
-        this._refreshRowsQueue = [];
-        this._refreshAnimationFrame = null;
-        this._refresh(start, end);
-    };
-    Renderer.prototype._refresh = function (start, end) {
-        var parent;
-        if (end - start >= this._terminal.rows / 2) {
-            parent = this._terminal.element.parentNode;
-            if (parent) {
-                this._terminal.element.removeChild(this._terminal.rowContainer);
-            }
-        }
-        var width = this._terminal.cols;
-        var y = start;
-        if (end >= this._terminal.rows) {
-            this._terminal.log('`end` is too large. Most likely a bad CSR.');
-            end = this._terminal.rows - 1;
-        }
-        for (; y <= end; y++) {
-            var row = y + this._terminal.ydisp;
-            var line = this._terminal.lines.get(row);
-            var x = void 0;
-            if (this._terminal.y === y - (this._terminal.ybase - this._terminal.ydisp) &&
-                this._terminal.cursorState &&
-                !this._terminal.cursorHidden) {
-                x = this._terminal.x;
-            }
-            else {
-                x = -1;
-            }
-            var attr = this._terminal.defAttr;
-            var documentFragment = document.createDocumentFragment();
-            var innerHTML = '';
-            var currentElement = void 0;
-            while (this._terminal.children[y].children.length) {
-                var child = this._terminal.children[y].children[0];
-                this._terminal.children[y].removeChild(child);
-                this._spanElementObjectPool.release(child);
-            }
-            for (var i = 0; i < width; i++) {
-                var data = line[i][0];
-                var ch = line[i][1];
-                var ch_width = line[i][2];
-                if (!ch_width) {
-                    continue;
-                }
-                if (i === x) {
-                    data = -1;
-                }
-                if (data !== attr) {
-                    if (attr !== this._terminal.defAttr) {
-                        if (innerHTML) {
-                            currentElement.innerHTML = innerHTML;
-                            innerHTML = '';
-                        }
-                        documentFragment.appendChild(currentElement);
-                        currentElement = null;
-                    }
-                    if (data !== this._terminal.defAttr) {
-                        if (innerHTML && !currentElement) {
-                            currentElement = this._spanElementObjectPool.acquire();
-                        }
-                        if (currentElement) {
-                            if (innerHTML) {
-                                currentElement.innerHTML = innerHTML;
-                                innerHTML = '';
-                            }
-                            documentFragment.appendChild(currentElement);
-                        }
-                        currentElement = this._spanElementObjectPool.acquire();
-                        if (data === -1) {
-                            currentElement.classList.add('reverse-video', 'terminal-cursor');
-                        }
-                        else {
-                            var bg = data & 0x1ff;
-                            var fg = (data >> 9) & 0x1ff;
-                            var flags = data >> 18;
-                            if (flags & FLAGS.BOLD) {
-                                if (!brokenBold) {
-                                    currentElement.classList.add('xterm-bold');
-                                }
-                                if (fg < 8) {
-                                    fg += 8;
-                                }
-                            }
-                            if (flags & FLAGS.UNDERLINE) {
-                                currentElement.classList.add('xterm-underline');
-                            }
-                            if (flags & FLAGS.BLINK) {
-                                currentElement.classList.add('xterm-blink');
-                            }
-                            if (flags & FLAGS.INVERSE) {
-                                var temp = bg;
-                                bg = fg;
-                                fg = temp;
-                                if ((flags & 1) && fg < 8) {
-                                    fg += 8;
-                                }
-                            }
-                            if (flags & FLAGS.INVISIBLE) {
-                                currentElement.classList.add('xterm-hidden');
-                            }
-                            if (flags & FLAGS.INVERSE) {
-                                if (bg === 257) {
-                                    bg = 15;
-                                }
-                                if (fg === 256) {
-                                    fg = 0;
-                                }
-                            }
-                            if (bg < 256) {
-                                currentElement.classList.add("xterm-bg-color-" + bg);
-                            }
-                            if (fg < 256) {
-                                currentElement.classList.add("xterm-color-" + fg);
-                            }
-                        }
-                    }
-                }
-                if (ch_width === 2) {
-                    innerHTML += "<span class=\"xterm-wide-char\">" + ch + "</span>";
-                }
-                else if (ch.charCodeAt(0) > 255) {
-                    innerHTML += "<span class=\"xterm-normal-char\">" + ch + "</span>";
-                }
-                else {
-                    switch (ch) {
-                        case '&':
-                            innerHTML += '&amp;';
-                            break;
-                        case '<':
-                            innerHTML += '&lt;';
-                            break;
-                        case '>':
-                            innerHTML += '&gt;';
-                            break;
-                        default:
-                            if (ch <= ' ') {
-                                innerHTML += '&nbsp;';
-                            }
-                            else {
-                                innerHTML += ch;
-                            }
-                            break;
-                    }
-                }
-                attr = data;
-            }
-            if (innerHTML && !currentElement) {
-                currentElement = this._spanElementObjectPool.acquire();
-            }
-            if (currentElement) {
-                if (innerHTML) {
-                    currentElement.innerHTML = innerHTML;
-                    innerHTML = '';
-                }
-                documentFragment.appendChild(currentElement);
-                currentElement = null;
-            }
-            this._terminal.children[y].appendChild(documentFragment);
-        }
-        if (parent) {
-            this._terminal.element.appendChild(this._terminal.rowContainer);
-        }
-        this._terminal.emit('refresh', { element: this._terminal.element, start: start, end: end });
-    };
-    ;
-    return Renderer;
-}());
-exports.Renderer = Renderer;
-function checkBoldBroken(terminal) {
-    var document = terminal.ownerDocument;
-    var el = document.createElement('span');
-    el.innerHTML = 'hello world';
-    terminal.appendChild(el);
-    var w1 = el.offsetWidth;
-    var h1 = el.offsetHeight;
-    el.style.fontWeight = 'bold';
-    var w2 = el.offsetWidth;
-    var h2 = el.offsetHeight;
-    terminal.removeChild(el);
-    return w1 !== w2 || h1 !== h2;
-}
-
-
-
-},{"./utils/DomElementObjectPool":14}],9:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var Viewport = (function () {
-    function Viewport(terminal, viewportElement, scrollArea, charMeasure) {
-        var _this = this;
-        this.terminal = terminal;
-        this.viewportElement = viewportElement;
-        this.scrollArea = scrollArea;
-        this.charMeasure = charMeasure;
-        this.currentRowHeight = 0;
-        this.lastRecordedBufferLength = 0;
-        this.lastRecordedViewportHeight = 0;
-        this.terminal.on('scroll', this.syncScrollArea.bind(this));
-        this.terminal.on('resize', this.syncScrollArea.bind(this));
-        this.viewportElement.addEventListener('scroll', this.onScroll.bind(this));
-        setTimeout(function () { return _this.syncScrollArea(); }, 0);
-    }
-    Viewport.prototype.refresh = function () {
-        if (this.charMeasure.height > 0) {
-            var rowHeightChanged = this.charMeasure.height !== this.currentRowHeight;
-            if (rowHeightChanged) {
-                this.currentRowHeight = this.charMeasure.height;
-                this.viewportElement.style.lineHeight = this.charMeasure.height + 'px';
-                this.terminal.rowContainer.style.lineHeight = this.charMeasure.height + 'px';
-            }
-            var viewportHeightChanged = this.lastRecordedViewportHeight !== this.terminal.rows;
-            if (rowHeightChanged || viewportHeightChanged) {
-                this.lastRecordedViewportHeight = this.terminal.rows;
-                this.viewportElement.style.height = this.charMeasure.height * this.terminal.rows + 'px';
-            }
-            this.scrollArea.style.height = (this.charMeasure.height * this.lastRecordedBufferLength) + 'px';
-        }
-    };
-    Viewport.prototype.syncScrollArea = function () {
-        if (this.lastRecordedBufferLength !== this.terminal.lines.length) {
-            this.lastRecordedBufferLength = this.terminal.lines.length;
-            this.refresh();
-        }
-        else if (this.lastRecordedViewportHeight !== this.terminal.rows) {
-            this.refresh();
-        }
-        else {
-            if (this.charMeasure.height !== this.currentRowHeight) {
-                this.refresh();
-            }
-        }
-        var scrollTop = this.terminal.ydisp * this.currentRowHeight;
-        if (this.viewportElement.scrollTop !== scrollTop) {
-            this.viewportElement.scrollTop = scrollTop;
-        }
-    };
-    Viewport.prototype.onScroll = function (ev) {
-        var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight);
-        var diff = newRow - this.terminal.ydisp;
-        this.terminal.scrollDisp(diff, true);
-    };
-    Viewport.prototype.onWheel = function (ev) {
-        if (ev.deltaY === 0) {
-            return;
-        }
-        var multiplier = 1;
-        if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) {
-            multiplier = this.currentRowHeight;
-        }
-        else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) {
-            multiplier = this.currentRowHeight * this.terminal.rows;
-        }
-        this.viewportElement.scrollTop += ev.deltaY * multiplier;
-        ev.preventDefault();
-    };
-    ;
-    return Viewport;
-}());
-exports.Viewport = Viewport;
-
-
-
-},{}],10:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-function prepareTextForClipboard(text) {
-    var space = String.fromCharCode(32), nonBreakingSpace = String.fromCharCode(160), allNonBreakingSpaces = new RegExp(nonBreakingSpace, 'g'), processedText = text.split('\n').map(function (line) {
-        var processedLine = line.replace(/\s+$/g, '').replace(allNonBreakingSpaces, space);
-        return processedLine;
-    }).join('\n');
-    return processedText;
-}
-exports.prepareTextForClipboard = prepareTextForClipboard;
-function prepareTextForTerminal(text, isMSWindows) {
-    if (isMSWindows) {
-        return text.replace(/\r?\n/g, '\n');
-    }
-    return text;
-}
-exports.prepareTextForTerminal = prepareTextForTerminal;
-function copyHandler(ev, term) {
-    var copiedText = window.getSelection().toString(), text = prepareTextForClipboard(copiedText);
-    if (term.browser.isMSIE) {
-        window.clipboardData.setData('Text', text);
-    }
-    else {
-        ev.clipboardData.setData('text/plain', text);
-    }
-    ev.preventDefault();
-}
-exports.copyHandler = copyHandler;
-function pasteHandler(ev, term) {
-    ev.stopPropagation();
-    var text;
-    var dispatchPaste = function (text) {
-        text = prepareTextForTerminal(text, term.browser.isMSWindows);
-        term.handler(text);
-        term.textarea.value = '';
-        term.emit('paste', text);
-        return term.cancel(ev);
-    };
-    if (term.browser.isMSIE) {
-        if (window.clipboardData) {
-            text = window.clipboardData.getData('Text');
-            dispatchPaste(text);
-        }
-    }
-    else {
-        if (ev.clipboardData) {
-            text = ev.clipboardData.getData('text/plain');
-            dispatchPaste(text);
-        }
-    }
-}
-exports.pasteHandler = pasteHandler;
-function rightClickHandler(ev, term) {
-    var s = document.getSelection(), selectedText = prepareTextForClipboard(s.toString()), clickIsOnSelection = false, x = ev.clientX, y = ev.clientY;
-    if (s.rangeCount) {
-        var r = s.getRangeAt(0), cr = r.getClientRects();
-        for (var i = 0; i < cr.length; i++) {
-            var rect = cr[i];
-            clickIsOnSelection = ((x > rect.left) && (x < rect.right) &&
-                (y > rect.top) && (y < rect.bottom));
-            if (clickIsOnSelection) {
-                break;
-            }
-        }
-        if (selectedText.match(/^\s$/) || !selectedText.length) {
-            clickIsOnSelection = false;
-        }
-    }
-    if (!clickIsOnSelection) {
-        term.textarea.style.position = 'fixed';
-        term.textarea.style.width = '20px';
-        term.textarea.style.height = '20px';
-        term.textarea.style.left = (x - 10) + 'px';
-        term.textarea.style.top = (y - 10) + 'px';
-        term.textarea.style.zIndex = '1000';
-        term.textarea.focus();
-        setTimeout(function () {
-            term.textarea.style.position = null;
-            term.textarea.style.width = null;
-            term.textarea.style.height = null;
-            term.textarea.style.left = null;
-            term.textarea.style.top = null;
-            term.textarea.style.zIndex = null;
-        }, 4);
-    }
-}
-exports.rightClickHandler = rightClickHandler;
-
-
-
-},{}],11:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var Generic_1 = require("./Generic");
-var isNode = (typeof navigator === 'undefined') ? true : false;
-var userAgent = (isNode) ? 'node' : navigator.userAgent;
-var platform = (isNode) ? 'node' : navigator.platform;
-exports.isFirefox = !!~userAgent.indexOf('Firefox');
-exports.isMSIE = !!~userAgent.indexOf('MSIE') || !!~userAgent.indexOf('Trident');
-exports.isMac = Generic_1.contains(['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'], platform);
-exports.isIpad = platform === 'iPad';
-exports.isIphone = platform === 'iPhone';
-exports.isMSWindows = Generic_1.contains(['Windows', 'Win16', 'Win32', 'WinCE'], platform);
-
-
-
-},{"./Generic":15}],12:[function(require,module,exports){
-"use strict";
-var __extends = (this && this.__extends) || (function () {
-    var extendStatics = Object.setPrototypeOf ||
-        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
-        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
-    return function (d, b) {
-        extendStatics(d, b);
-        function __() { this.constructor = d; }
-        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
-    };
-})();
-Object.defineProperty(exports, "__esModule", { value: true });
-var EventEmitter_js_1 = require("../EventEmitter.js");
-var CharMeasure = (function (_super) {
-    __extends(CharMeasure, _super);
-    function CharMeasure(document, parentElement) {
-        var _this = _super.call(this) || this;
-        _this._document = document;
-        _this._parentElement = parentElement;
-        return _this;
-    }
-    Object.defineProperty(CharMeasure.prototype, "width", {
-        get: function () {
-            return this._width;
-        },
-        enumerable: true,
-        configurable: true
-    });
-    Object.defineProperty(CharMeasure.prototype, "height", {
-        get: function () {
-            return this._height;
-        },
-        enumerable: true,
-        configurable: true
-    });
-    CharMeasure.prototype.measure = function () {
-        var _this = this;
-        if (!this._measureElement) {
-            this._measureElement = this._document.createElement('span');
-            this._measureElement.style.position = 'absolute';
-            this._measureElement.style.top = '0';
-            this._measureElement.style.left = '-9999em';
-            this._measureElement.textContent = 'W';
-            this._measureElement.setAttribute('aria-hidden', 'true');
-            this._parentElement.appendChild(this._measureElement);
-            setTimeout(function () { return _this._doMeasure(); }, 0);
-        }
-        else {
-            this._doMeasure();
-        }
-    };
-    CharMeasure.prototype._doMeasure = function () {
-        var geometry = this._measureElement.getBoundingClientRect();
-        if (geometry.width === 0 || geometry.height === 0) {
-            return;
-        }
-        if (this._width !== geometry.width || this._height !== geometry.height) {
-            this._width = geometry.width;
-            this._height = geometry.height;
-            this.emit('charsizechanged');
-        }
-    };
-    return CharMeasure;
-}(EventEmitter_js_1.EventEmitter));
-exports.CharMeasure = CharMeasure;
-
-
-
-},{"../EventEmitter.js":4}],13:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var CircularList = (function () {
-    function CircularList(maxLength) {
-        this._array = new Array(maxLength);
-        this._startIndex = 0;
-        this._length = 0;
-    }
-    Object.defineProperty(CircularList.prototype, "maxLength", {
-        get: function () {
-            return this._array.length;
-        },
-        set: function (newMaxLength) {
-            var newArray = new Array(newMaxLength);
-            for (var i = 0; i < Math.min(newMaxLength, this.length); i++) {
-                newArray[i] = this._array[this._getCyclicIndex(i)];
-            }
-            this._array = newArray;
-            this._startIndex = 0;
-        },
-        enumerable: true,
-        configurable: true
-    });
-    Object.defineProperty(CircularList.prototype, "length", {
-        get: function () {
-            return this._length;
-        },
-        set: function (newLength) {
-            if (newLength > this._length) {
-                for (var i = this._length; i < newLength; i++) {
-                    this._array[i] = undefined;
-                }
-            }
-            this._length = newLength;
-        },
-        enumerable: true,
-        configurable: true
-    });
-    Object.defineProperty(CircularList.prototype, "forEach", {
-        get: function () {
-            return this._array.forEach;
-        },
-        enumerable: true,
-        configurable: true
-    });
-    CircularList.prototype.get = function (index) {
-        return this._array[this._getCyclicIndex(index)];
-    };
-    CircularList.prototype.set = function (index, value) {
-        this._array[this._getCyclicIndex(index)] = value;
-    };
-    CircularList.prototype.push = function (value) {
-        this._array[this._getCyclicIndex(this._length)] = value;
-        if (this._length === this.maxLength) {
-            this._startIndex++;
-            if (this._startIndex === this.maxLength) {
-                this._startIndex = 0;
-            }
-        }
-        else {
-            this._length++;
-        }
-    };
-    CircularList.prototype.pop = function () {
-        return this._array[this._getCyclicIndex(this._length-- - 1)];
-    };
-    CircularList.prototype.splice = function (start, deleteCount) {
-        var items = [];
-        for (var _i = 2; _i < arguments.length; _i++) {
-            items[_i - 2] = arguments[_i];
-        }
-        if (deleteCount) {
-            for (var i = start; i < this._length - deleteCount; i++) {
-                this._array[this._getCyclicIndex(i)] = this._array[this._getCyclicIndex(i + deleteCount)];
-            }
-            this._length -= deleteCount;
-        }
-        if (items && items.length) {
-            for (var i = this._length - 1; i >= start; i--) {
-                this._array[this._getCyclicIndex(i + items.length)] = this._array[this._getCyclicIndex(i)];
-            }
-            for (var i = 0; i < items.length; i++) {
-                this._array[this._getCyclicIndex(start + i)] = items[i];
-            }
-            if (this._length + items.length > this.maxLength) {
-                this._startIndex += (this._length + items.length) - this.maxLength;
-                this._length = this.maxLength;
-            }
-            else {
-                this._length += items.length;
-            }
-        }
-    };
-    CircularList.prototype.trimStart = function (count) {
-        if (count > this._length) {
-            count = this._length;
-        }
-        this._startIndex += count;
-        this._length -= count;
-    };
-    CircularList.prototype.shiftElements = function (start, count, offset) {
-        if (count <= 0) {
-            return;
-        }
-        if (start < 0 || start >= this._length) {
-            throw new Error('start argument out of range');
-        }
-        if (start + offset < 0) {
-            throw new Error('Cannot shift elements in list beyond index 0');
-        }
-        if (offset > 0) {
-            for (var i = count - 1; i >= 0; i--) {
-                this.set(start + i + offset, this.get(start + i));
-            }
-            var expandListBy = (start + count + offset) - this._length;
-            if (expandListBy > 0) {
-                this._length += expandListBy;
-                while (this._length > this.maxLength) {
-                    this._length--;
-                    this._startIndex++;
-                }
-            }
-        }
-        else {
-            for (var i = 0; i < count; i++) {
-                this.set(start + i + offset, this.get(start + i));
-            }
-        }
-    };
-    CircularList.prototype._getCyclicIndex = function (index) {
-        return (this._startIndex + index) % this.maxLength;
-    };
-    return CircularList;
-}());
-exports.CircularList = CircularList;
-
-
-
-},{}],14:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var DomElementObjectPool = (function () {
-    function DomElementObjectPool(type) {
-        this.type = type;
-        this._type = type;
-        this._pool = [];
-        this._inUse = {};
-    }
-    DomElementObjectPool.prototype.acquire = function () {
-        var element;
-        if (this._pool.length === 0) {
-            element = this._createNew();
-        }
-        else {
-            element = this._pool.pop();
-        }
-        this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)] = element;
-        return element;
-    };
-    DomElementObjectPool.prototype.release = function (element) {
-        if (!this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)]) {
-            throw new Error('Could not release an element not yet acquired');
-        }
-        delete this._inUse[element.getAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE)];
-        this._cleanElement(element);
-        this._pool.push(element);
-    };
-    DomElementObjectPool.prototype._createNew = function () {
-        var element = document.createElement(this._type);
-        var id = DomElementObjectPool._objectCount++;
-        element.setAttribute(DomElementObjectPool.OBJECT_ID_ATTRIBUTE, id.toString(10));
-        return element;
-    };
-    DomElementObjectPool.prototype._cleanElement = function (element) {
-        element.className = '';
-        element.innerHTML = '';
-    };
-    return DomElementObjectPool;
-}());
-DomElementObjectPool.OBJECT_ID_ATTRIBUTE = 'data-obj-id';
-DomElementObjectPool._objectCount = 0;
-exports.DomElementObjectPool = DomElementObjectPool;
-
-
-
-},{}],15:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-function contains(arr, el) {
-    return arr.indexOf(el) >= 0;
-}
-exports.contains = contains;
-;
-
-
-
-},{}],16:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-function getCoords(event, rowContainer, charMeasure) {
-    if (event.pageX == null) {
-        return null;
-    }
-    var x = event.pageX;
-    var y = event.pageY;
-    var el = rowContainer;
-    while (el && el !== self.document.documentElement) {
-        x -= el.offsetLeft;
-        y -= el.offsetTop;
-        el = 'offsetParent' in el ? el.offsetParent : el.parentElement;
-    }
-    x = Math.ceil(x / charMeasure.width);
-    y = Math.ceil(y / charMeasure.height);
-    return [x, y];
-}
-exports.getCoords = getCoords;
-function getRawByteCoords(event, rowContainer, charMeasure, colCount, rowCount) {
-    var coords = getCoords(event, rowContainer, charMeasure);
-    var x = coords[0];
-    var y = coords[1];
-    x = Math.min(Math.max(x, 0), colCount);
-    y = Math.min(Math.max(y, 0), rowCount);
-    x += 32;
-    y += 32;
-    return { x: x, y: y };
-}
-exports.getRawByteCoords = getRawByteCoords;
-
-
-
-},{}],17:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-var CompositionHelper_1 = require("./CompositionHelper");
-var EventEmitter_1 = require("./EventEmitter");
-var Viewport_1 = require("./Viewport");
-var Clipboard_1 = require("./handlers/Clipboard");
-var CircularList_1 = require("./utils/CircularList");
-var EscapeSequences_1 = require("./EscapeSequences");
-var InputHandler_1 = require("./InputHandler");
-var Parser_1 = require("./Parser");
-var Renderer_1 = require("./Renderer");
-var Linkifier_1 = require("./Linkifier");
-var CharMeasure_1 = require("./utils/CharMeasure");
-var Browser = require("./utils/Browser");
-var Mouse_1 = require("./utils/Mouse");
-var document = (typeof window != 'undefined') ? window.document : null;
-var WRITE_BUFFER_PAUSE_THRESHOLD = 5;
-var WRITE_BATCH_SIZE = 300;
-var CURSOR_BLINK_INTERVAL = 600;
-function Terminal(options) {
-    var self = this;
-    if (!(this instanceof Terminal)) {
-        return new Terminal(arguments[0], arguments[1], arguments[2]);
-    }
-    self.browser = Browser;
-    self.cancel = Terminal.cancel;
-    EventEmitter_1.EventEmitter.call(this);
-    if (typeof options === 'number') {
-        options = {
-            cols: arguments[0],
-            rows: arguments[1],
-            handler: arguments[2]
-        };
-    }
-    options = options || {};
-    Object.keys(Terminal.defaults).forEach(function (key) {
-        if (options[key] == null) {
-            options[key] = Terminal.options[key];
-            if (Terminal[key] !== Terminal.defaults[key]) {
-                options[key] = Terminal[key];
-            }
-        }
-        self[key] = options[key];
-    });
-    if (options.colors.length === 8) {
-        options.colors = options.colors.concat(Terminal._colors.slice(8));
-    }
-    else if (options.colors.length === 16) {
-        options.colors = options.colors.concat(Terminal._colors.slice(16));
-    }
-    else if (options.colors.length === 10) {
-        options.colors = options.colors.slice(0, -2).concat(Terminal._colors.slice(8, -2), options.colors.slice(-2));
-    }
-    else if (options.colors.length === 18) {
-        options.colors = options.colors.concat(Terminal._colors.slice(16, -2), options.colors.slice(-2));
-    }
-    this.colors = options.colors;
-    this.options = options;
-    this.parent = options.body || options.parent || (document ? document.getElementsByTagName('body')[0] : null);
-    this.cols = options.cols || options.geometry[0];
-    this.rows = options.rows || options.geometry[1];
-    this.geometry = [this.cols, this.rows];
-    if (options.handler) {
-        this.on('data', options.handler);
-    }
-    this.ybase = 0;
-    this.ydisp = 0;
-    this.x = 0;
-    this.y = 0;
-    this.cursorState = 0;
-    this.cursorHidden = false;
-    this.convertEol;
-    this.queue = '';
-    this.scrollTop = 0;
-    this.scrollBottom = this.rows - 1;
-    this.customKeydownHandler = null;
-    this.cursorBlinkInterval = null;
-    this.applicationKeypad = false;
-    this.applicationCursor = false;
-    this.originMode = false;
-    this.insertMode = false;
-    this.wraparoundMode = true;
-    this.normal = null;
-    this.charset = null;
-    this.gcharset = null;
-    this.glevel = 0;
-    this.charsets = [null];
-    this.decLocator;
-    this.x10Mouse;
-    this.vt200Mouse;
-    this.vt300Mouse;
-    this.normalMouse;
-    this.mouseEvents;
-    this.sendFocus;
-    this.utfMouse;
-    this.sgrMouse;
-    this.urxvtMouse;
-    this.element;
-    this.children;
-    this.refreshStart;
-    this.refreshEnd;
-    this.savedX;
-    this.savedY;
-    this.savedCols;
-    this.readable = true;
-    this.writable = true;
-    this.defAttr = (0 << 18) | (257 << 9) | (256 << 0);
-    this.curAttr = this.defAttr;
-    this.params = [];
-    this.currentParam = 0;
-    this.prefix = '';
-    this.postfix = '';
-    this.inputHandler = new InputHandler_1.InputHandler(this);
-    this.parser = new Parser_1.Parser(this.inputHandler, this);
-    this.renderer = this.renderer || null;
-    this.linkifier = this.linkifier || new Linkifier_1.Linkifier();
-    this.writeBuffer = [];
-    this.writeInProgress = false;
-    this.xoffSentToCatchUp = false;
-    this.writeStopped = false;
-    this.surrogate_high = '';
-    this.lines = new CircularList_1.CircularList(this.scrollback);
-    var i = this.rows;
-    while (i--) {
-        this.lines.push(this.blankLine());
-    }
-    this.tabs;
-    this.setupStops();
-    this.userScrolling = false;
-}
-inherits(Terminal, EventEmitter_1.EventEmitter);
-Terminal.prototype.eraseAttr = function () {
-    return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
-};
-Terminal.tangoColors = [
-    '#2e3436',
-    '#cc0000',
-    '#4e9a06',
-    '#c4a000',
-    '#3465a4',
-    '#75507b',
-    '#06989a',
-    '#d3d7cf',
-    '#555753',
-    '#ef2929',
-    '#8ae234',
-    '#fce94f',
-    '#729fcf',
-    '#ad7fa8',
-    '#34e2e2',
-    '#eeeeec'
-];
-Terminal.colors = (function () {
-    var colors = Terminal.tangoColors.slice(), r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff], i;
-    i = 0;
-    for (; i < 216; i++) {
-        out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]);
-    }
-    i = 0;
-    for (; i < 24; i++) {
-        r = 8 + i * 10;
-        out(r, r, r);
-    }
-    function out(r, g, b) {
-        colors.push('#' + hex(r) + hex(g) + hex(b));
-    }
-    function hex(c) {
-        c = c.toString(16);
-        return c.length < 2 ? '0' + c : c;
-    }
-    return colors;
-})();
-Terminal._colors = Terminal.colors.slice();
-Terminal.vcolors = (function () {
-    var out = [], colors = Terminal.colors, i = 0, color;
-    for (; i < 256; i++) {
-        color = parseInt(colors[i].substring(1), 16);
-        out.push([
-            (color >> 16) & 0xff,
-            (color >> 8) & 0xff,
-            color & 0xff
-        ]);
-    }
-    return out;
-})();
-Terminal.defaults = {
-    colors: Terminal.colors,
-    theme: 'default',
-    convertEol: false,
-    termName: 'xterm',
-    geometry: [80, 24],
-    cursorBlink: false,
-    cursorStyle: 'block',
-    visualBell: false,
-    popOnBell: false,
-    scrollback: 1000,
-    screenKeys: false,
-    debug: false,
-    cancelEvents: false,
-    disableStdin: false,
-    useFlowControl: false,
-    tabStopWidth: 8
-};
-Terminal.options = {};
-Terminal.focus = null;
-each(keys(Terminal.defaults), function (key) {
-    Terminal[key] = Terminal.defaults[key];
-    Terminal.options[key] = Terminal.defaults[key];
-});
-Terminal.prototype.focus = function () {
-    return this.textarea.focus();
-};
-Terminal.prototype.getOption = function (key, value) {
-    if (!(key in Terminal.defaults)) {
-        throw new Error('No option with key "' + key + '"');
-    }
-    if (typeof this.options[key] !== 'undefined') {
-        return this.options[key];
-    }
-    return this[key];
-};
-Terminal.prototype.setOption = function (key, value) {
-    if (!(key in Terminal.defaults)) {
-        throw new Error('No option with key "' + key + '"');
-    }
-    switch (key) {
-        case 'scrollback':
-            if (this.options[key] !== value) {
-                if (this.lines.length > value) {
-                    var amountToTrim = this.lines.length - value;
-                    var needsRefresh = (this.ydisp - amountToTrim < 0);
-                    this.lines.trimStart(amountToTrim);
-                    this.ybase = Math.max(this.ybase - amountToTrim, 0);
-                    this.ydisp = Math.max(this.ydisp - amountToTrim, 0);
-                    if (needsRefresh) {
-                        this.refresh(0, this.rows - 1);
-                    }
-                }
-                this.lines.maxLength = value;
-                this.viewport.syncScrollArea();
-            }
-            break;
-    }
-    this[key] = value;
-    this.options[key] = value;
-    switch (key) {
-        case 'cursorBlink':
-            this.setCursorBlinking(value);
-            break;
-        case 'cursorStyle':
-            this.element.classList.toggle("xterm-cursor-style-underline", value === 'underline');
-            this.element.classList.toggle("xterm-cursor-style-bar", value === 'bar');
-            break;
-        case 'tabStopWidth':
-            this.setupStops();
-            break;
-    }
-};
-Terminal.prototype.restartCursorBlinking = function () {
-    this.setCursorBlinking(this.options.cursorBlink);
-};
-Terminal.prototype.setCursorBlinking = function (enabled) {
-    this.element.classList.toggle('xterm-cursor-blink', enabled);
-    this.clearCursorBlinkingInterval();
-    if (enabled) {
-        var self = this;
-        this.cursorBlinkInterval = setInterval(function () {
-            self.element.classList.toggle('xterm-cursor-blink-on');
-        }, CURSOR_BLINK_INTERVAL);
-    }
-};
-Terminal.prototype.clearCursorBlinkingInterval = function () {
-    this.element.classList.remove('xterm-cursor-blink-on');
-    if (this.cursorBlinkInterval) {
-        clearInterval(this.cursorBlinkInterval);
-        this.cursorBlinkInterval = null;
-    }
-};
-Terminal.bindFocus = function (term) {
-    on(term.textarea, 'focus', function (ev) {
-        if (term.sendFocus) {
-            term.send(EscapeSequences_1.C0.ESC + '[I');
-        }
-        term.element.classList.add('focus');
-        term.showCursor();
-        term.restartCursorBlinking.apply(term);
-        Terminal.focus = term;
-        term.emit('focus', { terminal: term });
-    });
-};
-Terminal.prototype.blur = function () {
-    return this.textarea.blur();
-};
-Terminal.bindBlur = function (term) {
-    on(term.textarea, 'blur', function (ev) {
-        term.refresh(term.y, term.y);
-        if (term.sendFocus) {
-            term.send(EscapeSequences_1.C0.ESC + '[O');
-        }
-        term.element.classList.remove('focus');
-        term.clearCursorBlinkingInterval.apply(term);
-        Terminal.focus = null;
-        term.emit('blur', { terminal: term });
-    });
-};
-Terminal.prototype.initGlobal = function () {
-    var term = this;
-    Terminal.bindKeys(this);
-    Terminal.bindFocus(this);
-    Terminal.bindBlur(this);
-    on(this.element, 'copy', function (ev) {
-        Clipboard_1.copyHandler.call(this, ev, term);
-    });
-    on(this.textarea, 'paste', function (ev) {
-        Clipboard_1.pasteHandler.call(this, ev, term);
-    });
-    on(this.element, 'paste', function (ev) {
-        Clipboard_1.pasteHandler.call(this, ev, term);
-    });
-    function rightClickHandlerWrapper(ev) {
-        Clipboard_1.rightClickHandler.call(this, ev, term);
-    }
-    if (term.browser.isFirefox) {
-        on(this.element, 'mousedown', function (ev) {
-            if (ev.button == 2) {
-                rightClickHandlerWrapper(ev);
-            }
-        });
-    }
-    else {
-        on(this.element, 'contextmenu', rightClickHandlerWrapper);
-    }
-};
-Terminal.bindKeys = function (term) {
-    on(term.element, 'keydown', function (ev) {
-        if (document.activeElement != this) {
-            return;
-        }
-        term.keyDown(ev);
-    }, true);
-    on(term.element, 'keypress', function (ev) {
-        if (document.activeElement != this) {
-            return;
-        }
-        term.keyPress(ev);
-    }, true);
-    on(term.element, 'keyup', function (ev) {
-        if (!wasMondifierKeyOnlyEvent(ev)) {
-            term.focus(term);
-        }
-    }, true);
-    on(term.textarea, 'keydown', function (ev) {
-        term.keyDown(ev);
-    }, true);
-    on(term.textarea, 'keypress', function (ev) {
-        term.keyPress(ev);
-        this.value = '';
-    }, true);
-    on(term.textarea, 'compositionstart', term.compositionHelper.compositionstart.bind(term.compositionHelper));
-    on(term.textarea, 'compositionupdate', term.compositionHelper.compositionupdate.bind(term.compositionHelper));
-    on(term.textarea, 'compositionend', term.compositionHelper.compositionend.bind(term.compositionHelper));
-    term.on('refresh', term.compositionHelper.updateCompositionElements.bind(term.compositionHelper));
-    term.on('refresh', function (data) {
-        term.queueLinkification(data.start, data.end);
-    });
-};
-Terminal.prototype.insertRow = function (row) {
-    if (typeof row != 'object') {
-        row = document.createElement('div');
-    }
-    this.rowContainer.appendChild(row);
-    this.children.push(row);
-    return row;
-};
-Terminal.prototype.open = function (parent, focus) {
-    var self = this, i = 0, div;
-    this.parent = parent || this.parent;
-    if (!this.parent) {
-        throw new Error('Terminal requires a parent element.');
-    }
-    this.context = this.parent.ownerDocument.defaultView;
-    this.document = this.parent.ownerDocument;
-    this.body = this.document.getElementsByTagName('body')[0];
-    this.element = this.document.createElement('div');
-    this.element.classList.add('terminal');
-    this.element.classList.add('xterm');
-    this.element.classList.add('xterm-theme-' + this.theme);
-    this.setCursorBlinking(this.options.cursorBlink);
-    this.element.style.height;
-    this.element.setAttribute('tabindex', 0);
-    this.viewportElement = document.createElement('div');
-    this.viewportElement.classList.add('xterm-viewport');
-    this.element.appendChild(this.viewportElement);
-    this.viewportScrollArea = document.createElement('div');
-    this.viewportScrollArea.classList.add('xterm-scroll-area');
-    this.viewportElement.appendChild(this.viewportScrollArea);
-    this.rowContainer = document.createElement('div');
-    this.rowContainer.classList.add('xterm-rows');
-    this.element.appendChild(this.rowContainer);
-    this.children = [];
-    this.linkifier.attachToDom(document, this.children);
-    this.helperContainer = document.createElement('div');
-    this.helperContainer.classList.add('xterm-helpers');
-    this.element.appendChild(this.helperContainer);
-    this.textarea = document.createElement('textarea');
-    this.textarea.classList.add('xterm-helper-textarea');
-    this.textarea.setAttribute('autocorrect', 'off');
-    this.textarea.setAttribute('autocapitalize', 'off');
-    this.textarea.setAttribute('spellcheck', 'false');
-    this.textarea.tabIndex = 0;
-    this.textarea.addEventListener('focus', function () {
-        self.emit('focus', { terminal: self });
-    });
-    this.textarea.addEventListener('blur', function () {
-        self.emit('blur', { terminal: self });
-    });
-    this.helperContainer.appendChild(this.textarea);
-    this.compositionView = document.createElement('div');
-    this.compositionView.classList.add('composition-view');
-    this.compositionHelper = new CompositionHelper_1.CompositionHelper(this.textarea, this.compositionView, this);
-    this.helperContainer.appendChild(this.compositionView);
-    this.charSizeStyleElement = document.createElement('style');
-    this.helperContainer.appendChild(this.charSizeStyleElement);
-    for (; i < this.rows; i++) {
-        this.insertRow();
-    }
-    this.parent.appendChild(this.element);
-    this.charMeasure = new CharMeasure_1.CharMeasure(document, this.helperContainer);
-    this.charMeasure.on('charsizechanged', function () {
-        self.updateCharSizeCSS();
-    });
-    this.charMeasure.measure();
-    this.viewport = new Viewport_1.Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasure);
-    this.renderer = new Renderer_1.Renderer(this);
-    this.refresh(0, this.rows - 1);
-    this.initGlobal();
-    if (typeof focus == 'undefined') {
-        var message = 'You did not pass the `focus` argument in `Terminal.prototype.open()`.\n';
-        message += 'The `focus` argument now defaults to `true` but starting with xterm.js 3.0 ';
-        message += 'it will default to `false`.';
-        console.warn(message);
-        focus = true;
-    }
-    if (focus) {
-        this.focus();
-    }
-    on(this.element, 'click', function () {
-        var selection = document.getSelection(), collapsed = selection.isCollapsed, isRange = typeof collapsed == 'boolean' ? !collapsed : selection.type == 'Range';
-        if (!isRange) {
-            self.focus();
-        }
-    });
-    this.bindMouse();
-    this.emit('open');
-};
-Terminal.loadAddon = function (addon, callback) {
-    if (typeof exports === 'object' && typeof module === 'object') {
-        return require('./addons/' + addon + '/' + addon);
-    }
-    else if (typeof define == 'function') {
-        return require(['./addons/' + addon + '/' + addon], callback);
-    }
-    else {
-        console.error('Cannot load a module without a CommonJS or RequireJS environment.');
-        return false;
-    }
-};
-Terminal.prototype.updateCharSizeCSS = function () {
-    this.charSizeStyleElement.textContent =
-        ".xterm-wide-char{width:" + this.charMeasure.width * 2 + "px;}" +
-            (".xterm-normal-char{width:" + this.charMeasure.width + "px;}");
-};
-Terminal.prototype.bindMouse = function () {
-    var el = this.element, self = this, pressed = 32;
-    function sendButton(ev) {
-        var button, pos;
-        button = getButton(ev);
-        pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
-        if (!pos)
-            return;
-        sendEvent(button, pos);
-        switch (ev.overrideType || ev.type) {
-            case 'mousedown':
-                pressed = button;
-                break;
-            case 'mouseup':
-                pressed = 32;
-                break;
-            case 'wheel':
-                break;
-        }
-    }
-    function sendMove(ev) {
-        var button = pressed, pos;
-        pos = Mouse_1.getRawByteCoords(ev, self.rowContainer, self.charMeasure, self.cols, self.rows);
-        if (!pos)
-            return;
-        button += 32;
-        sendEvent(button, pos);
-    }
-    function encode(data, ch) {
-        if (!self.utfMouse) {
-            if (ch === 255)
-                return data.push(0);
-            if (ch > 127)
-                ch = 127;
-            data.push(ch);
-        }
-        else {
-            if (ch === 2047)
-                return data.push(0);
-            if (ch < 127) {
-                data.push(ch);
-            }
-            else {
-                if (ch > 2047)
-                    ch = 2047;
-                data.push(0xC0 | (ch >> 6));
-                data.push(0x80 | (ch & 0x3F));
-            }
-        }
-    }
-    function sendEvent(button, pos) {
-        if (self.vt300Mouse) {
-            button &= 3;
-            pos.x -= 32;
-            pos.y -= 32;
-            var data = EscapeSequences_1.C0.ESC + '[24';
-            if (button === 0)
-                data += '1';
-            else if (button === 1)
-                data += '3';
-            else if (button === 2)
-                data += '5';
-            else if (button === 3)
-                return;
-            else
-                data += '0';
-            data += '~[' + pos.x + ',' + pos.y + ']\r';
-            self.send(data);
-            return;
-        }
-        if (self.decLocator) {
-            button &= 3;
-            pos.x -= 32;
-            pos.y -= 32;
-            if (button === 0)
-                button = 2;
-            else if (button === 1)
-                button = 4;
-            else if (button === 2)
-                button = 6;
-            else if (button === 3)
-                button = 3;
-            self.send(EscapeSequences_1.C0.ESC + '['
-                + button
-                + ';'
-                + (button === 3 ? 4 : 0)
-                + ';'
-                + pos.y
-                + ';'
-                + pos.x
-                + ';'
-                + (pos.page || 0)
-                + '&w');
-            return;
-        }
-        if (self.urxvtMouse) {
-            pos.x -= 32;
-            pos.y -= 32;
-            pos.x++;
-            pos.y++;
-            self.send(EscapeSequences_1.C0.ESC + '[' + button + ';' + pos.x + ';' + pos.y + 'M');
-            return;
-        }
-        if (self.sgrMouse) {
-            pos.x -= 32;
-            pos.y -= 32;
-            self.send(EscapeSequences_1.C0.ESC + '[<'
-                + (((button & 3) === 3 ? button & ~3 : button) - 32)
-                + ';'
-                + pos.x
-                + ';'
-                + pos.y
-                + ((button & 3) === 3 ? 'm' : 'M'));
-            return;
-        }
-        var data = [];
-        encode(data, button);
-        encode(data, pos.x);
-        encode(data, pos.y);
-        self.send(EscapeSequences_1.C0.ESC + '[M' + String.fromCharCode.apply(String, data));
-    }
-    function getButton(ev) {
-        var button, shift, meta, ctrl, mod;
-        switch (ev.overrideType || ev.type) {
-            case 'mousedown':
-                button = ev.button != null
-                    ? +ev.button
-                    : ev.which != null
-                        ? ev.which - 1
-                        : null;
-                if (self.browser.isMSIE) {
-                    button = button === 1 ? 0 : button === 4 ? 1 : button;
-                }
-                break;
-            case 'mouseup':
-                button = 3;
-                break;
-            case 'DOMMouseScroll':
-                button = ev.detail < 0
-                    ? 64
-                    : 65;
-                break;
-            case 'wheel':
-                button = ev.wheelDeltaY > 0
-                    ? 64
-                    : 65;
-                break;
-        }
-        shift = ev.shiftKey ? 4 : 0;
-        meta = ev.metaKey ? 8 : 0;
-        ctrl = ev.ctrlKey ? 16 : 0;
-        mod = shift | meta | ctrl;
-        if (self.vt200Mouse) {
-            mod &= ctrl;
-        }
-        else if (!self.normalMouse) {
-            mod = 0;
-        }
-        button = (32 + (mod << 2)) + button;
-        return button;
-    }
-    on(el, 'mousedown', function (ev) {
-        if (!self.mouseEvents)
-            return;
-        sendButton(ev);
-        self.focus();
-        if (self.vt200Mouse) {
-            ev.overrideType = 'mouseup';
-            sendButton(ev);
-            return self.cancel(ev);
-        }
-        if (self.normalMouse)
-            on(self.document, 'mousemove', sendMove);
-        if (!self.x10Mouse) {
-            on(self.document, 'mouseup', function up(ev) {
-                sendButton(ev);
-                if (self.normalMouse)
-                    off(self.document, 'mousemove', sendMove);
-                off(self.document, 'mouseup', up);
-                return self.cancel(ev);
-            });
-        }
-        return self.cancel(ev);
-    });
-    on(el, 'wheel', function (ev) {
-        if (!self.mouseEvents)
-            return;
-        if (self.x10Mouse
-            || self.vt300Mouse
-            || self.decLocator)
-            return;
-        sendButton(ev);
-        return self.cancel(ev);
-    });
-    on(el, 'wheel', function (ev) {
-        if (self.mouseEvents)
-            return;
-        self.viewport.onWheel(ev);
-        return self.cancel(ev);
-    });
-};
-Terminal.prototype.destroy = function () {
-    this.readable = false;
-    this.writable = false;
-    this._events = {};
-    this.handler = function () { };
-    this.write = function () { };
-    if (this.element && this.element.parentNode) {
-        this.element.parentNode.removeChild(this.element);
-    }
-};
-Terminal.prototype.refresh = function (start, end) {
-    if (this.renderer) {
-        this.renderer.queueRefresh(start, end);
-    }
-};
-Terminal.prototype.queueLinkification = function (start, end) {
-    if (this.linkifier) {
-        for (var i = start; i <= end; i++) {
-            this.linkifier.linkifyRow(i);
-        }
-    }
-};
-Terminal.prototype.showCursor = function () {
-    if (!this.cursorState) {
-        this.cursorState = 1;
-        this.refresh(this.y, this.y);
-    }
-};
-Terminal.prototype.scroll = function () {
-    var row;
-    if (this.lines.length === this.lines.maxLength) {
-        this.lines.trimStart(1);
-        this.ybase--;
-        if (this.ydisp !== 0) {
-            this.ydisp--;
-        }
-    }
-    this.ybase++;
-    if (!this.userScrolling) {
-        this.ydisp = this.ybase;
-    }
-    row = this.ybase + this.rows - 1;
-    row -= this.rows - 1 - this.scrollBottom;
-    if (row === this.lines.length) {
-        this.lines.push(this.blankLine());
-    }
-    else {
-        this.lines.splice(row, 0, this.blankLine());
-    }
-    if (this.scrollTop !== 0) {
-        if (this.ybase !== 0) {
-            this.ybase--;
-            if (!this.userScrolling) {
-                this.ydisp = this.ybase;
-            }
-        }
-        this.lines.splice(this.ybase + this.scrollTop, 1);
-    }
-    this.updateRange(this.scrollTop);
-    this.updateRange(this.scrollBottom);
-    this.emit('scroll', this.ydisp);
-};
-Terminal.prototype.scrollDisp = function (disp, suppressScrollEvent) {
-    if (disp < 0) {
-        this.userScrolling = true;
-    }
-    else if (disp + this.ydisp >= this.ybase) {
-        this.userScrolling = false;
-    }
-    this.ydisp += disp;
-    if (this.ydisp > this.ybase) {
-        this.ydisp = this.ybase;
-    }
-    else if (this.ydisp < 0) {
-        this.ydisp = 0;
-    }
-    if (!suppressScrollEvent) {
-        this.emit('scroll', this.ydisp);
-    }
-    this.refresh(0, this.rows - 1);
-};
-Terminal.prototype.scrollPages = function (pageCount) {
-    this.scrollDisp(pageCount * (this.rows - 1));
-};
-Terminal.prototype.scrollToTop = function () {
-    this.scrollDisp(-this.ydisp);
-};
-Terminal.prototype.scrollToBottom = function () {
-    this.scrollDisp(this.ybase - this.ydisp);
-};
-Terminal.prototype.write = function (data) {
-    this.writeBuffer.push(data);
-    if (this.options.useFlowControl && !this.xoffSentToCatchUp && this.writeBuffer.length >= WRITE_BUFFER_PAUSE_THRESHOLD) {
-        this.send(EscapeSequences_1.C0.DC3);
-        this.xoffSentToCatchUp = true;
-    }
-    if (!this.writeInProgress && this.writeBuffer.length > 0) {
-        this.writeInProgress = true;
-        var self = this;
-        setTimeout(function () {
-            self.innerWrite();
-        });
-    }
-};
-Terminal.prototype.innerWrite = function () {
-    var writeBatch = this.writeBuffer.splice(0, WRITE_BATCH_SIZE);
-    while (writeBatch.length > 0) {
-        var data = writeBatch.shift();
-        var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;
-        if (this.xoffSentToCatchUp && writeBatch.length === 0 && this.writeBuffer.length === 0) {
-            this.send(EscapeSequences_1.C0.DC1);
-            this.xoffSentToCatchUp = false;
-        }
-        this.refreshStart = this.y;
-        this.refreshEnd = this.y;
-        var state = this.parser.parse(data);
-        this.parser.setState(state);
-        this.updateRange(this.y);
-        this.refresh(this.refreshStart, this.refreshEnd);
-    }
-    if (this.writeBuffer.length > 0) {
-        var self = this;
-        setTimeout(function () {
-            self.innerWrite();
-        }, 0);
-    }
-    else {
-        this.writeInProgress = false;
-    }
-};
-Terminal.prototype.writeln = function (data) {
-    this.write(data + '\r\n');
-};
-Terminal.prototype.attachCustomKeydownHandler = function (customKeydownHandler) {
-    this.customKeydownHandler = customKeydownHandler;
-};
-Terminal.prototype.setHypertextLinkHandler = function (handler) {
-    if (!this.linkifier) {
-        throw new Error('Cannot attach a hypertext link handler before Terminal.open is called');
-    }
-    this.linkifier.setHypertextLinkHandler(handler);
-    this.refresh(0, this.rows - 1);
-};
-Terminal.prototype.setHypertextValidationCallback = function (handler) {
-    if (!this.linkifier) {
-        throw new Error('Cannot attach a hypertext validation callback before Terminal.open is called');
-    }
-    this.linkifier.setHypertextValidationCallback(handler);
-    this.refresh(0, this.rows - 1);
-};
-Terminal.prototype.registerLinkMatcher = function (regex, handler, options) {
-    if (this.linkifier) {
-        var matcherId = this.linkifier.registerLinkMatcher(regex, handler, options);
-        this.refresh(0, this.rows - 1);
-        return matcherId;
-    }
-};
-Terminal.prototype.deregisterLinkMatcher = function (matcherId) {
-    if (this.linkifier) {
-        if (this.linkifier.deregisterLinkMatcher(matcherId)) {
-            this.refresh(0, this.rows - 1);
-        }
-    }
-};
-Terminal.prototype.keyDown = function (ev) {
-    if (this.customKeydownHandler && this.customKeydownHandler(ev) === false) {
-        return false;
-    }
-    this.restartCursorBlinking();
-    if (!this.compositionHelper.keydown.bind(this.compositionHelper)(ev)) {
-        if (this.ybase !== this.ydisp) {
-            this.scrollToBottom();
-        }
-        return false;
-    }
-    var self = this;
-    var result = this.evaluateKeyEscapeSequence(ev);
-    if (result.key === EscapeSequences_1.C0.DC3) {
-        this.writeStopped = true;
-    }
-    else if (result.key === EscapeSequences_1.C0.DC1) {
-        this.writeStopped = false;
-    }
-    if (result.scrollDisp) {
-        this.scrollDisp(result.scrollDisp);
-        return this.cancel(ev, true);
-    }
-    if (isThirdLevelShift(this, ev)) {
-        return true;
-    }
-    if (result.cancel) {
-        this.cancel(ev, true);
-    }
-    if (!result.key) {
-        return true;
-    }
-    this.emit('keydown', ev);
-    this.emit('key', result.key, ev);
-    this.showCursor();
-    this.handler(result.key);
-    return this.cancel(ev, true);
-};
-Terminal.prototype.evaluateKeyEscapeSequence = function (ev) {
-    var result = {
-        cancel: false,
-        key: undefined,
-        scrollDisp: undefined
-    };
-    var modifiers = ev.shiftKey << 0 | ev.altKey << 1 | ev.ctrlKey << 2 | ev.metaKey << 3;
-    switch (ev.keyCode) {
-        case 8:
-            if (ev.shiftKey) {
-                result.key = EscapeSequences_1.C0.BS;
-                break;
-            }
-            result.key = EscapeSequences_1.C0.DEL;
-            break;
-        case 9:
-            if (ev.shiftKey) {
-                result.key = EscapeSequences_1.C0.ESC + '[Z';
-                break;
-            }
-            result.key = EscapeSequences_1.C0.HT;
-            result.cancel = true;
-            break;
-        case 13:
-            result.key = EscapeSequences_1.C0.CR;
-            result.cancel = true;
-            break;
-        case 27:
-            result.key = EscapeSequences_1.C0.ESC;
-            result.cancel = true;
-            break;
-        case 37:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'D';
-                if (result.key == EscapeSequences_1.C0.ESC + '[1;3D') {
-                    result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'b' : EscapeSequences_1.C0.ESC + '[1;5D';
-                }
-            }
-            else if (this.applicationCursor) {
-                result.key = EscapeSequences_1.C0.ESC + 'OD';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[D';
-            }
-            break;
-        case 39:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'C';
-                if (result.key == EscapeSequences_1.C0.ESC + '[1;3C') {
-                    result.key = (this.browser.isMac) ? EscapeSequences_1.C0.ESC + 'f' : EscapeSequences_1.C0.ESC + '[1;5C';
-                }
-            }
-            else if (this.applicationCursor) {
-                result.key = EscapeSequences_1.C0.ESC + 'OC';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[C';
-            }
-            break;
-        case 38:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'A';
-                if (result.key == EscapeSequences_1.C0.ESC + '[1;3A') {
-                    result.key = EscapeSequences_1.C0.ESC + '[1;5A';
-                }
-            }
-            else if (this.applicationCursor) {
-                result.key = EscapeSequences_1.C0.ESC + 'OA';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[A';
-            }
-            break;
-        case 40:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'B';
-                if (result.key == EscapeSequences_1.C0.ESC + '[1;3B') {
-                    result.key = EscapeSequences_1.C0.ESC + '[1;5B';
-                }
-            }
-            else if (this.applicationCursor) {
-                result.key = EscapeSequences_1.C0.ESC + 'OB';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[B';
-            }
-            break;
-        case 45:
-            if (!ev.shiftKey && !ev.ctrlKey) {
-                result.key = EscapeSequences_1.C0.ESC + '[2~';
-            }
-            break;
-        case 46:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[3;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[3~';
-            }
-            break;
-        case 36:
-            if (modifiers)
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'H';
-            else if (this.applicationCursor)
-                result.key = EscapeSequences_1.C0.ESC + 'OH';
-            else
-                result.key = EscapeSequences_1.C0.ESC + '[H';
-            break;
-        case 35:
-            if (modifiers)
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'F';
-            else if (this.applicationCursor)
-                result.key = EscapeSequences_1.C0.ESC + 'OF';
-            else
-                result.key = EscapeSequences_1.C0.ESC + '[F';
-            break;
-        case 33:
-            if (ev.shiftKey) {
-                result.scrollDisp = -(this.rows - 1);
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[5~';
-            }
-            break;
-        case 34:
-            if (ev.shiftKey) {
-                result.scrollDisp = this.rows - 1;
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[6~';
-            }
-            break;
-        case 112:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'P';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + 'OP';
-            }
-            break;
-        case 113:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'Q';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + 'OQ';
-            }
-            break;
-        case 114:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'R';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + 'OR';
-            }
-            break;
-        case 115:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[1;' + (modifiers + 1) + 'S';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + 'OS';
-            }
-            break;
-        case 116:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[15;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[15~';
-            }
-            break;
-        case 117:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[17;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[17~';
-            }
-            break;
-        case 118:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[18;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[18~';
-            }
-            break;
-        case 119:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[19;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[19~';
-            }
-            break;
-        case 120:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[20;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[20~';
-            }
-            break;
-        case 121:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[21;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[21~';
-            }
-            break;
-        case 122:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[23;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[23~';
-            }
-            break;
-        case 123:
-            if (modifiers) {
-                result.key = EscapeSequences_1.C0.ESC + '[24;' + (modifiers + 1) + '~';
-            }
-            else {
-                result.key = EscapeSequences_1.C0.ESC + '[24~';
-            }
-            break;
-        default:
-            if (ev.ctrlKey && !ev.shiftKey && !ev.altKey && !ev.metaKey) {
-                if (ev.keyCode >= 65 && ev.keyCode <= 90) {
-                    result.key = String.fromCharCode(ev.keyCode - 64);
-                }
-                else if (ev.keyCode === 32) {
-                    result.key = String.fromCharCode(0);
-                }
-                else if (ev.keyCode >= 51 && ev.keyCode <= 55) {
-                    result.key = String.fromCharCode(ev.keyCode - 51 + 27);
-                }
-                else if (ev.keyCode === 56) {
-                    result.key = String.fromCharCode(127);
-                }
-                else if (ev.keyCode === 219) {
-                    result.key = String.fromCharCode(27);
-                }
-                else if (ev.keyCode === 220) {
-                    result.key = String.fromCharCode(28);
-                }
-                else if (ev.keyCode === 221) {
-                    result.key = String.fromCharCode(29);
-                }
-            }
-            else if (!this.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) {
-                if (ev.keyCode >= 65 && ev.keyCode <= 90) {
-                    result.key = EscapeSequences_1.C0.ESC + String.fromCharCode(ev.keyCode + 32);
-                }
-                else if (ev.keyCode === 192) {
-                    result.key = EscapeSequences_1.C0.ESC + '`';
-                }
-                else if (ev.keyCode >= 48 && ev.keyCode <= 57) {
-                    result.key = EscapeSequences_1.C0.ESC + (ev.keyCode - 48);
-                }
-            }
-            break;
-    }
-    return result;
-};
-Terminal.prototype.setgLevel = function (g) {
-    this.glevel = g;
-    this.charset = this.charsets[g];
-};
-Terminal.prototype.setgCharset = function (g, charset) {
-    this.charsets[g] = charset;
-    if (this.glevel === g) {
-        this.charset = charset;
-    }
-};
-Terminal.prototype.keyPress = function (ev) {
-    var key;
-    this.cancel(ev);
-    if (ev.charCode) {
-        key = ev.charCode;
-    }
-    else if (ev.which == null) {
-        key = ev.keyCode;
-    }
-    else if (ev.which !== 0 && ev.charCode !== 0) {
-        key = ev.which;
-    }
-    else {
-        return false;
-    }
-    if (!key || ((ev.altKey || ev.ctrlKey || ev.metaKey) && !isThirdLevelShift(this, ev))) {
-        return false;
-    }
-    key = String.fromCharCode(key);
-    this.emit('keypress', key, ev);
-    this.emit('key', key, ev);
-    this.showCursor();
-    this.handler(key);
-    return false;
-};
-Terminal.prototype.send = function (data) {
-    var self = this;
-    if (!this.queue) {
-        setTimeout(function () {
-            self.handler(self.queue);
-            self.queue = '';
-        }, 1);
-    }
-    this.queue += data;
-};
-Terminal.prototype.bell = function () {
-    if (!this.visualBell)
-        return;
-    var self = this;
-    this.element.style.borderColor = 'white';
-    setTimeout(function () {
-        self.element.style.borderColor = '';
-    }, 10);
-    if (this.popOnBell)
-        this.focus();
-};
-Terminal.prototype.log = function () {
-    if (!this.debug)
-        return;
-    if (!this.context.console || !this.context.console.log)
-        return;
-    var args = Array.prototype.slice.call(arguments);
-    this.context.console.log.apply(this.context.console, args);
-};
-Terminal.prototype.error = function () {
-    if (!this.debug)
-        return;
-    if (!this.context.console || !this.context.console.error)
-        return;
-    var args = Array.prototype.slice.call(arguments);
-    this.context.console.error.apply(this.context.console, args);
-};
-Terminal.prototype.resize = function (x, y) {
-    if (isNaN(x) || isNaN(y)) {
-        return;
-    }
-    var line, el, i, j, ch, addToY;
-    if (x === this.cols && y === this.rows) {
-        return;
-    }
-    if (x < 1)
-        x = 1;
-    if (y < 1)
-        y = 1;
-    j = this.cols;
-    if (j < x) {
-        ch = [this.defAttr, ' ', 1];
-        i = this.lines.length;
-        while (i--) {
-            while (this.lines.get(i).length < x) {
-                this.lines.get(i).push(ch);
-            }
-        }
-    }
-    this.cols = x;
-    this.setupStops(this.cols);
-    j = this.rows;
-    addToY = 0;
-    if (j < y) {
-        el = this.element;
-        while (j++ < y) {
-            if (this.lines.length < y + this.ybase) {
-                if (this.ybase > 0 && this.lines.length <= this.ybase + this.y + addToY + 1) {
-                    this.ybase--;
-                    addToY++;
-                    if (this.ydisp > 0) {
-                        this.ydisp--;
-                    }
-                }
-                else {
-                    this.lines.push(this.blankLine());
-                }
-            }
-            if (this.children.length < y) {
-                this.insertRow();
-            }
-        }
-    }
-    else {
-        while (j-- > y) {
-            if (this.lines.length > y + this.ybase) {
-                if (this.lines.length > this.ybase + this.y + 1) {
-                    this.lines.pop();
-                }
-                else {
-                    this.ybase++;
-                    this.ydisp++;
-                }
-            }
-            if (this.children.length > y) {
-                el = this.children.shift();
-                if (!el)
-                    continue;
-                el.parentNode.removeChild(el);
-            }
-        }
-    }
-    this.rows = y;
-    if (this.y >= y) {
-        this.y = y - 1;
-    }
-    if (addToY) {
-        this.y += addToY;
-    }
-    if (this.x >= x) {
-        this.x = x - 1;
-    }
-    this.scrollTop = 0;
-    this.scrollBottom = y - 1;
-    this.charMeasure.measure();
-    this.refresh(0, this.rows - 1);
-    this.normal = null;
-    this.geometry = [this.cols, this.rows];
-    this.emit('resize', { terminal: this, cols: x, rows: y });
-};
-Terminal.prototype.updateRange = function (y) {
-    if (y < this.refreshStart)
-        this.refreshStart = y;
-    if (y > this.refreshEnd)
-        this.refreshEnd = y;
-};
-Terminal.prototype.maxRange = function () {
-    this.refreshStart = 0;
-    this.refreshEnd = this.rows - 1;
-};
-Terminal.prototype.setupStops = function (i) {
-    if (i != null) {
-        if (!this.tabs[i]) {
-            i = this.prevStop(i);
-        }
-    }
-    else {
-        this.tabs = {};
-        i = 0;
-    }
-    for (; i < this.cols; i += this.getOption('tabStopWidth')) {
-        this.tabs[i] = true;
-    }
-};
-Terminal.prototype.prevStop = function (x) {
-    if (x == null)
-        x = this.x;
-    while (!this.tabs[--x] && x > 0)
-        ;
-    return x >= this.cols
-        ? this.cols - 1
-        : x < 0 ? 0 : x;
-};
-Terminal.prototype.nextStop = function (x) {
-    if (x == null)
-        x = this.x;
-    while (!this.tabs[++x] && x < this.cols)
-        ;
-    return x >= this.cols
-        ? this.cols - 1
-        : x < 0 ? 0 : x;
-};
-Terminal.prototype.eraseRight = function (x, y) {
-    var line = this.lines.get(this.ybase + y);
-    if (!line) {
-        return;
-    }
-    var ch = [this.eraseAttr(), ' ', 1];
-    for (; x < this.cols; x++) {
-        line[x] = ch;
-    }
-    this.updateRange(y);
-};
-Terminal.prototype.eraseLeft = function (x, y) {
-    var line = this.lines.get(this.ybase + y);
-    if (!line) {
-        return;
-    }
-    var ch = [this.eraseAttr(), ' ', 1];
-    x++;
-    while (x--) {
-        line[x] = ch;
-    }
-    this.updateRange(y);
-};
-Terminal.prototype.clear = function () {
-    if (this.ybase === 0 && this.y === 0) {
-        return;
-    }
-    this.lines.set(0, this.lines.get(this.ybase + this.y));
-    this.lines.length = 1;
-    this.ydisp = 0;
-    this.ybase = 0;
-    this.y = 0;
-    for (var i = 1; i < this.rows; i++) {
-        this.lines.push(this.blankLine());
-    }
-    this.refresh(0, this.rows - 1);
-    this.emit('scroll', this.ydisp);
-};
-Terminal.prototype.eraseLine = function (y) {
-    this.eraseRight(0, y);
-};
-Terminal.prototype.blankLine = function (cur) {
-    var attr = cur
-        ? this.eraseAttr()
-        : this.defAttr;
-    var ch = [attr, ' ', 1], line = [], i = 0;
-    for (; i < this.cols; i++) {
-        line[i] = ch;
-    }
-    return line;
-};
-Terminal.prototype.ch = function (cur) {
-    return cur
-        ? [this.eraseAttr(), ' ', 1]
-        : [this.defAttr, ' ', 1];
-};
-Terminal.prototype.is = function (term) {
-    var name = this.termName;
-    return (name + '').indexOf(term) === 0;
-};
-Terminal.prototype.handler = function (data) {
-    if (this.options.disableStdin) {
-        return;
-    }
-    if (this.ybase !== this.ydisp) {
-        this.scrollToBottom();
-    }
-    this.emit('data', data);
-};
-Terminal.prototype.handleTitle = function (title) {
-    this.emit('title', title);
-};
-Terminal.prototype.index = function () {
-    this.y++;
-    if (this.y > this.scrollBottom) {
-        this.y--;
-        this.scroll();
-    }
-    if (this.x >= this.cols) {
-        this.x--;
-    }
-};
-Terminal.prototype.reverseIndex = function () {
-    var j;
-    if (this.y === this.scrollTop) {
-        this.lines.shiftElements(this.y + this.ybase, this.rows - 1, 1);
-        this.lines.set(this.y + this.ybase, this.blankLine(true));
-        this.updateRange(this.scrollTop);
-        this.updateRange(this.scrollBottom);
-    }
-    else {
-        this.y--;
-    }
-};
-Terminal.prototype.reset = function () {
-    this.options.rows = this.rows;
-    this.options.cols = this.cols;
-    var customKeydownHandler = this.customKeydownHandler;
-    var cursorBlinkInterval = this.cursorBlinkInterval;
-    Terminal.call(this, this.options);
-    this.customKeydownHandler = customKeydownHandler;
-    this.cursorBlinkInterval = cursorBlinkInterval;
-    this.refresh(0, this.rows - 1);
-    this.viewport.syncScrollArea();
-};
-Terminal.prototype.tabSet = function () {
-    this.tabs[this.x] = true;
-};
-function on(el, type, handler, capture) {
-    if (!Array.isArray(el)) {
-        el = [el];
-    }
-    el.forEach(function (element) {
-        element.addEventListener(type, handler, capture || false);
-    });
-}
-function off(el, type, handler, capture) {
-    el.removeEventListener(type, handler, capture || false);
-}
-function cancel(ev, force) {
-    if (!this.cancelEvents && !force) {
-        return;
-    }
-    ev.preventDefault();
-    ev.stopPropagation();
-    return false;
-}
-function inherits(child, parent) {
-    function f() {
-        this.constructor = child;
-    }
-    f.prototype = parent.prototype;
-    child.prototype = new f;
-}
-function indexOf(obj, el) {
-    var i = obj.length;
-    while (i--) {
-        if (obj[i] === el)
-            return i;
-    }
-    return -1;
-}
-function isThirdLevelShift(term, ev) {
-    var thirdLevelKey = (term.browser.isMac && ev.altKey && !ev.ctrlKey && !ev.metaKey) ||
-        (term.browser.isMSWindows && ev.altKey && ev.ctrlKey && !ev.metaKey);
-    if (ev.type == 'keypress') {
-        return thirdLevelKey;
-    }
-    return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
-}
-Terminal.prototype.matchColor = matchColor;
-function matchColor(r1, g1, b1) {
-    var hash = (r1 << 16) | (g1 << 8) | b1;
-    if (matchColor._cache[hash] != null) {
-        return matchColor._cache[hash];
-    }
-    var ldiff = Infinity, li = -1, i = 0, c, r2, g2, b2, diff;
-    for (; i < Terminal.vcolors.length; i++) {
-        c = Terminal.vcolors[i];
-        r2 = c[0];
-        g2 = c[1];
-        b2 = c[2];
-        diff = matchColor.distance(r1, g1, b1, r2, g2, b2);
-        if (diff === 0) {
-            li = i;
-            break;
-        }
-        if (diff < ldiff) {
-            ldiff = diff;
-            li = i;
-        }
-    }
-    return matchColor._cache[hash] = li;
-}
-matchColor._cache = {};
-matchColor.distance = function (r1, g1, b1, r2, g2, b2) {
-    return Math.pow(30 * (r1 - r2), 2)
-        + Math.pow(59 * (g1 - g2), 2)
-        + Math.pow(11 * (b1 - b2), 2);
-};
-function each(obj, iter, con) {
-    if (obj.forEach)
-        return obj.forEach(iter, con);
-    for (var i = 0; i < obj.length; i++) {
-        iter.call(con, obj[i], i, obj);
-    }
-}
-function wasMondifierKeyOnlyEvent(ev) {
-    return ev.keyCode === 16 ||
-        ev.keyCode === 17 ||
-        ev.keyCode === 18;
-}
-function keys(obj) {
-    if (Object.keys)
-        return Object.keys(obj);
-    var key, keys = [];
-    for (key in obj) {
-        if (Object.prototype.hasOwnProperty.call(obj, key)) {
-            keys.push(key);
-        }
-    }
-    return keys;
-}
-Terminal.EventEmitter = EventEmitter_1.EventEmitter;
-Terminal.inherits = inherits;
-Terminal.on = on;
-Terminal.off = off;
-Terminal.cancel = cancel;
-module.exports = Terminal;
-
-
-
-},{"./CompositionHelper":2,"./EscapeSequences":3,"./EventEmitter":4,"./InputHandler":5,"./Linkifier":6,"./Parser":7,"./Renderer":8,"./Viewport":9,"./handlers/Clipboard":10,"./utils/Browser":11,"./utils/CharMeasure":12,"./utils/CircularList":13,"./utils/Mouse":16}]},{},[17])(17)
-});
-//# sourceMappingURL=xterm.js.map
diff --git a/third_party/blink/renderer/devtools/services/devtools.js b/third_party/blink/renderer/devtools/services/devtools.js
deleted file mode 100644
index 831acea..0000000
--- a/third_party/blink/renderer/devtools/services/devtools.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var dispatcher = require("./dispatcher.js");
-var terminal = require("./terminal.js");
-
-var d = new dispatcher.Dispatcher();
-d.registerObject("Terminal", terminal.Terminal);
-d.start(9022);
-
-console.log("Run chrome as `chrome --devtools-flags='service-backend=ws://localhost:9022/endpoint'`");
diff --git a/third_party/blink/renderer/devtools/services/dispatcher.js b/third_party/blink/renderer/devtools/services/dispatcher.js
deleted file mode 100644
index 3735931..0000000
--- a/third_party/blink/renderer/devtools/services/dispatcher.js
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var http = require("http");
-var ws = require("ws");
-
-function Dispatcher()
-{
-    this._constructors = new Map();
-    this._connections = new Set();
-}
-
-Dispatcher.prototype = {
-    start: function(port)
-    {
-        var http_server = http.createServer();
-        http_server.listen(port);
-
-        var WebSocketServer = ws.Server;
-        var options = { server: http_server, path: "/endpoint" };
-        var wss = new WebSocketServer(options);
-        wss.on("connection", (socket) => {
-            var connection = new Connection(this, socket);
-            this._connections.add(connection);
-        });
-    },
-
-    registerObject: function(name, constructor)
-    {
-        this._constructors.set(name, constructor);
-    },
-
-    _connectionClosed: function(connection)
-    {
-        this._connections.delete(connection);
-    }
-}
-
-exports.Dispatcher = Dispatcher;
-
-function Connection(dispatcher, socket)
-{
-    this._dispatcher = dispatcher;
-    this._objects = new Map();
-    this._lastObjectId = 1;
-    this._socket = socket;
-    this._socket.on("message", this._dispatchMessageWrapped.bind(this));
-    this._socket.on("close", this._connectionClosed.bind(this));
-}
-
-Connection.prototype = {
-    _dispatchMessageWrapped: function(data)
-    {
-        try {
-            var message = JSON.parse(data);
-            this._dispatchMessage(message);
-        } catch(e) {
-            this._sendErrorResponse(message.id, e.toString());
-        }
-    },
-
-    _dispatchMessage: function(message)
-    {
-        var [objectName, method] = message.method.split(".");
-        var result = JSON.stringify({id: message.id});
-        var constructor = this._dispatcher._constructors.get(objectName);
-        if (!constructor) {
-            this._sendErrorResponse(message.id, "Could not resolve service '" + objectName + "'");
-            return;
-        }
-        if (method === "create") {
-            var id = String(this._lastObjectId++);
-            var object = new constructor(this._notify.bind(this, id, objectName));
-            this._objects.set(id, object);
-            this._sendResponse(message.id, { id: id });
-        } else if (method === "dispose") {
-            var object = this._objects.get(message.params.id);
-            if (!object) {
-                console.error("Could not look up object with id for " + JSON.stringify(message));
-                return;
-            }
-            this._objects.delete(message.params.id);
-            object.dispose().then(() => this._sendResponse(message.id));
-        } else {
-            if (!message.params) {
-                console.error("No params in the message: " + JSON.stringify(message));
-                return;
-            }
-            var object = this._objects.get(message.params.id);
-            if (!object) {
-                console.error("Could not look up object with id for " + JSON.stringify(message));
-                return;
-            }
-            var handler = object[method];
-            if (!(handler instanceof Function)) {
-                console.error("Handler for '" + method + "' is missing.");
-                return;
-            }
-            object[method](message.params).then(result => this._sendResponse(message.id, result));
-        }
-    },
-
-    _connectionClosed: function()
-    {
-        for (var object of this._objects.values())
-            object.dispose();
-        this._objects.clear();
-        this._dispatcher._connectionClosed(this);
-    },
-
-    _notify: function(objectId, objectName, method, params)
-    {
-        params["id"] = objectId;
-        var message = { method: objectName + "." + method, params: params };
-        this._socket.send(JSON.stringify(message));
-    },
-
-    _sendResponse: function(messageId, result)
-    {
-        var message = { id: messageId, result: result };
-        this._socket.send(JSON.stringify(message));
-    },
-
-    _sendErrorResponse: function(messageId, error)
-    {
-        var message = { id: messageId, error: error };
-        this._socket.send(JSON.stringify(message));
-    },
-}
diff --git a/third_party/blink/renderer/devtools/services/package.json b/third_party/blink/renderer/devtools/services/package.json
deleted file mode 100644
index 824851c..0000000
--- a/third_party/blink/renderer/devtools/services/package.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
-  "name": "devtools-backend",
-  "version": "1.0.0",
-  "ignore": [
-    ".gitignore"
-  ],
-  "dependencies": {
-    "pty.js": "0.3.1",
-    "ws": "^1.1.1"
-  }
-}
diff --git a/third_party/blink/renderer/devtools/services/terminal.js b/third_party/blink/renderer/devtools/services/terminal.js
deleted file mode 100644
index 742b553..0000000
--- a/third_party/blink/renderer/devtools/services/terminal.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-var pty = require("pty.js");
-
-function Terminal(notify)
-{
-    this._notify = notify;
-}
-
-Terminal.prototype = {
-    init: function(params)
-    {
-        this._term = pty.spawn(process.platform === "win32" ? "cmd.exe" : "bash", [], {
-            name: "xterm-color",
-            cols: params.cols || 80,
-            rows: params.rows || 24,
-            cwd: process.env.PWD,
-            env: process.env
-        });
-
-        this._term.on("data", data => {
-            if (this._notify)
-                this._notify("data", { data: data });
-        });
-        return Promise.resolve({});
-    },
-
-    resize: function(params)
-    {
-        if (this._term)
-            this._term.resize(params.cols, params.rows);
-        return Promise.resolve({});
-    },
-
-    write: function(params)
-    {
-        this._term.write(params.data);
-        return Promise.resolve({});
-    },
-
-    dispose: function(params)
-    {
-        this._notify = null;
-        if (this._term)
-            process.kill(this._term.pid);
-        return Promise.resolve({});
-    },
-}
-
-exports.Terminal = Terminal;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 1d846232..55ecaed 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -3208,8 +3208,10 @@
   if (!node)
     return false;
   if (Element* locked_ancestor =
-          DisplayLockUtilities::NearestLockedInclusiveAncestor(*node))
-    locked_ancestor->ActivateDisplayLockIfNeeded();
+          DisplayLockUtilities::NearestLockedInclusiveAncestor(*node)) {
+    locked_ancestor->ActivateDisplayLockIfNeeded(
+        DisplayLockActivationReason::kUser);
+  }
   LayoutObject* layout_object = node->GetLayoutObject();
   if (!layout_object || !node->isConnected())
     return false;
diff --git a/third_party/blink/renderer/modules/accessibility/ax_slider.cc b/third_party/blink/renderer/modules/accessibility/ax_slider.cc
index c15a9ed..a04a922 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_slider.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_slider.cc
@@ -113,6 +113,10 @@
 
   // Fire change event manually, as LayoutSlider::setValueForPosition does.
   input->DispatchFormControlChangeEvent();
+
+  // Ensure the AX node is updated.
+  AXObjectCache().MarkAXObjectDirty(this, false);
+
   return true;
 }
 
diff --git a/third_party/blink/renderer/modules/cache_storage/BUILD.gn b/third_party/blink/renderer/modules/cache_storage/BUILD.gn
index b221c15..af99e71 100644
--- a/third_party/blink/renderer/modules/cache_storage/BUILD.gn
+++ b/third_party/blink/renderer/modules/cache_storage/BUILD.gn
@@ -10,10 +10,14 @@
     "cache.h",
     "cache_storage.cc",
     "cache_storage.h",
+    "cache_storage_blob_client_list.cc",
+    "cache_storage_blob_client_list.h",
     "cache_storage_error.cc",
     "cache_storage_error.h",
     "cache_storage_trace_utils.cc",
     "cache_storage_trace_utils.h",
+    "cache_utils.cc",
+    "cache_utils.h",
     "global_cache_storage.cc",
     "global_cache_storage.h",
     "inspector_cache_storage_agent.cc",
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.cc b/third_party/blink/renderer/modules/cache_storage/cache.cc
index 2970003..43aa9b35 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache.cc
@@ -30,8 +30,10 @@
 #include "third_party/blink/renderer/core/html/parser/text_resource_decoder.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage.h"
+#include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_error.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h"
+#include "third_party/blink/renderer/modules/cache_storage/cache_utils.h"
 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h"
 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
@@ -41,6 +43,7 @@
 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
 #include "third_party/blink/renderer/platform/instrumentation/tracing/traced_value.h"
 #include "third_party/blink/renderer/platform/loader/fetch/cached_metadata.h"
+#include "third_party/blink/renderer/platform/loader/fetch/data_pipe_bytes_consumer.h"
 #include "third_party/blink/renderer/platform/network/http_names.h"
 #include "third_party/blink/renderer/platform/network/http_parsers.h"
 #include "third_party/blink/renderer/platform/network/mime/mime_type_registry.h"
@@ -316,8 +319,8 @@
         continue;
       }
       uint64_t side_data_blob_size =
-          operation->response->side_data_blob
-              ? operation->response->side_data_blob->size()
+          operation->response->side_data_blob_for_cache_put
+              ? operation->response->side_data_blob_for_cache_put->size()
               : 0;
       global_scope->CountCacheStorageInstalledScript(blob_data_handle->size(),
                                                      side_data_blob_size);
@@ -430,8 +433,9 @@
       side_data_blob_data->AppendBytes(serialized_data.data(),
                                        serialized_data.size());
 
-      batch_operation->response->side_data_blob = BlobDataHandle::Create(
-          std::move(side_data_blob_data), serialized_data.size());
+      batch_operation->response->side_data_blob_for_cache_put =
+          BlobDataHandle::Create(std::move(side_data_blob_data),
+                                 serialized_data.size());
     }
 
     barrier_callback_->OnSuccess(index_, std::move(batch_operation));
@@ -621,12 +625,14 @@
              mojo::PendingAssociatedRemote<mojom::blink::CacheStorageCache>
                  cache_pending_remote,
              scoped_refptr<base::SingleThreadTaskRunner> task_runner)
-    : scoped_fetcher_(fetcher) {
+    : scoped_fetcher_(fetcher),
+      blob_client_list_(MakeGarbageCollected<CacheStorageBlobClientList>()) {
   cache_remote_.Bind(std::move(cache_pending_remote), std::move(task_runner));
 }
 
 void Cache::Trace(blink::Visitor* visitor) {
   visitor->Trace(scoped_fetcher_);
+  visitor->Trace(blob_client_list_);
   ScriptWrappable::Trace(visitor);
 }
 
@@ -651,14 +657,20 @@
     return promise;
   }
 
+  bool in_related_fetch_event = false;
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  if (auto* global_scope = DynamicTo<ServiceWorkerGlobalScope>(context))
+    in_related_fetch_event = global_scope->HasRelatedFetchEvent(request->url());
+
   // Make sure to bind the Cache object to keep the mojo remote alive during
   // the operation. Otherwise GC might prevent the callback from ever being
   // executed.
   cache_remote_->Match(
-      std::move(mojo_request), std::move(mojo_options), trace_id,
+      std::move(mojo_request), std::move(mojo_options), in_related_fetch_event,
+      trace_id,
       WTF::Bind(
           [](ScriptPromiseResolver* resolver, base::TimeTicks start_time,
-             const CacheQueryOptions* options, int64_t trace_id, Cache* _,
+             const CacheQueryOptions* options, int64_t trace_id, Cache* self,
              mojom::blink::MatchResultPtr result) {
             base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
             UMA_HISTOGRAM_LONG_TIMES("ServiceWorkerCache.Cache.Renderer.Match",
@@ -695,8 +707,15 @@
               UMA_HISTOGRAM_LONG_TIMES(
                   "ServiceWorkerCache.Cache.Renderer.Match.Hit", elapsed);
               ScriptState::Scope scope(resolver->GetScriptState());
-              resolver->Resolve(Response::Create(resolver->GetScriptState(),
-                                                 *result->get_response()));
+              if (result->is_eager_response()) {
+                resolver->Resolve(
+                    CreateEagerResponse(resolver->GetScriptState(),
+                                        std::move(result->get_eager_response()),
+                                        self->blob_client_list_));
+              } else {
+                resolver->Resolve(Response::Create(resolver->GetScriptState(),
+                                                   *result->get_response()));
+              }
             }
           },
           WrapPersistent(resolver), base::TimeTicks::Now(),
diff --git a/third_party/blink/renderer/modules/cache_storage/cache.h b/third_party/blink/renderer/modules/cache_storage/cache.h
index 1b291d3..b386ecd 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache.h
@@ -40,6 +40,7 @@
 
 namespace blink {
 
+class CacheStorageBlobClientList;
 class ExceptionState;
 class Response;
 class Request;
@@ -116,6 +117,7 @@
                          const CacheQueryOptions*);
 
   Member<GlobalFetch::ScopedFetcher> scoped_fetcher_;
+  Member<CacheStorageBlobClientList> blob_client_list_;
 
   mojo::AssociatedRemote<mojom::blink::CacheStorageCache> cache_remote_;
 
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage.cc b/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
index c86254e..05536ddb 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage.cc
@@ -20,8 +20,10 @@
 #include "third_party/blink/renderer/core/fetch/response.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_error.h"
 #include "third_party/blink/renderer/modules/cache_storage/cache_storage_trace_utils.h"
+#include "third_party/blink/renderer/modules/cache_storage/cache_utils.h"
 #include "third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
@@ -351,6 +353,12 @@
       request->CreateFetchAPIRequest();
   mojom::blink::MultiCacheQueryOptionsPtr mojo_options =
       mojom::blink::MultiCacheQueryOptions::From(options);
+
+  ExecutionContext* context = ExecutionContext::From(script_state);
+  bool in_related_fetch_event = false;
+  if (auto* global_scope = DynamicTo<ServiceWorkerGlobalScope>(context))
+    in_related_fetch_event = global_scope->HasRelatedFetchEvent(request->url());
+
   TRACE_EVENT_WITH_FLOW2("CacheStorage", "CacheStorage::MatchImpl",
                          TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_OUT,
                          "request", CacheStorageTracedValue(mojo_request),
@@ -384,11 +392,12 @@
   // pointer alive during the operation.  Otherwise GC might prevent the
   // callback from ever being executed.
   cache_storage_remote_->Match(
-      std::move(mojo_request), std::move(mojo_options), trace_id,
+      std::move(mojo_request), std::move(mojo_options), in_related_fetch_event,
+      trace_id,
       WTF::Bind(
           [](ScriptPromiseResolver* resolver, base::TimeTicks start_time,
              const MultiCacheQueryOptions* options, int64_t trace_id,
-             mojom::blink::MatchResultPtr result) {
+             CacheStorage* self, mojom::blink::MatchResultPtr result) {
             base::TimeDelta elapsed = base::TimeTicks::Now() - start_time;
             if (!options->hasCacheName() || options->cacheName().IsEmpty()) {
               UMA_HISTOGRAM_LONG_TIMES(
@@ -424,12 +433,19 @@
                   TRACE_ID_GLOBAL(trace_id), TRACE_EVENT_FLAG_FLOW_IN,
                   "response", CacheStorageTracedValue(result->get_response()));
               ScriptState::Scope scope(resolver->GetScriptState());
-              resolver->Resolve(Response::Create(resolver->GetScriptState(),
-                                                 *result->get_response()));
+              if (result->is_eager_response()) {
+                resolver->Resolve(
+                    CreateEagerResponse(resolver->GetScriptState(),
+                                        std::move(result->get_eager_response()),
+                                        self->blob_client_list_));
+              } else {
+                resolver->Resolve(Response::Create(resolver->GetScriptState(),
+                                                   *result->get_response()));
+              }
             }
           },
           WrapPersistent(resolver), base::TimeTicks::Now(),
-          WrapPersistent(options), trace_id));
+          WrapPersistent(options), trace_id, WrapPersistent(this)));
 
   return promise;
 }
@@ -438,6 +454,7 @@
                            GlobalFetch::ScopedFetcher* fetcher)
     : ContextLifecycleObserver(context),
       scoped_fetcher_(fetcher),
+      blob_client_list_(MakeGarbageCollected<CacheStorageBlobClientList>()),
       ever_used_(false) {
   // See https://bit.ly/2S0zRAS for task types.
   scoped_refptr<base::SingleThreadTaskRunner> task_runner =
@@ -474,6 +491,7 @@
 
 void CacheStorage::Trace(blink::Visitor* visitor) {
   visitor->Trace(scoped_fetcher_);
+  visitor->Trace(blob_client_list_);
   ScriptWrappable::Trace(visitor);
   ContextLifecycleObserver::Trace(visitor);
 }
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage.h b/third_party/blink/renderer/modules/cache_storage/cache_storage.h
index f09b31e0..b545f836 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_storage.h
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage.h
@@ -23,6 +23,8 @@
 
 namespace blink {
 
+class CacheStorageBlobClientList;
+
 class CacheStorage final : public ScriptWrappable,
                            public ActiveScriptWrappable<CacheStorage>,
                            public ContextLifecycleObserver {
@@ -54,6 +56,7 @@
   bool IsAllowed(ScriptState*);
 
   Member<GlobalFetch::ScopedFetcher> scoped_fetcher_;
+  Member<CacheStorageBlobClientList> blob_client_list_;
 
   mojo::Remote<mojom::blink::CacheStorage> cache_storage_remote_;
   base::Optional<bool> allowed_;
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc
new file mode 100644
index 0000000..b46756aa
--- /dev/null
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.cc
@@ -0,0 +1,87 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
+
+namespace blink {
+
+// Class implementing the BlobReaderClient interface.  This is used to
+// propagate the completion of an eager body blob read to the
+// DataPipeBytesConsumer.
+class CacheStorageBlobClientList::Client
+    : public GarbageCollected<CacheStorageBlobClientList::Client>,
+      public mojom::blink::BlobReaderClient {
+  // We must prevent any mojo messages from coming in after this object
+  // starts getting garbage collected.
+  USING_PRE_FINALIZER(CacheStorageBlobClientList::Client, Dispose);
+
+ public:
+  Client(CacheStorageBlobClientList* owner,
+         mojo::PendingReceiver<mojom::blink::BlobReaderClient>
+             client_pending_receiver,
+         DataPipeBytesConsumer::CompletionNotifier* completion_notifier)
+      : owner_(owner),
+        client_receiver_(this, std::move(client_pending_receiver)),
+        completion_notifier_(completion_notifier) {}
+
+  void OnCalculatedSize(uint64_t total_size,
+                        uint64_t expected_content_size) override {}
+
+  void OnComplete(int32_t status, uint64_t data_length) override {
+    client_receiver_.reset();
+
+    // 0 is net::OK
+    if (status == 0)
+      completion_notifier_->SignalComplete();
+    else
+      completion_notifier_->SignalError(BytesConsumer::Error());
+
+    if (owner_)
+      owner_->RevokeClient(this);
+  }
+
+  void Trace(blink::Visitor* visitor) {
+    visitor->Trace(owner_);
+    visitor->Trace(completion_notifier_);
+  }
+
+ private:
+  void Dispose() {
+    // Use the existence of the client_receiver_ binding to see if this
+    // client has already completed.
+    if (!client_receiver_.is_bound())
+      return;
+
+    client_receiver_.reset();
+    completion_notifier_->SignalError(BytesConsumer::Error("aborted"));
+
+    // If we are already being garbage collected its not necessary to
+    // call RevokeClient() on the owner.
+  }
+
+  WeakMember<CacheStorageBlobClientList> owner_;
+  mojo::Receiver<mojom::blink::BlobReaderClient> client_receiver_;
+  Member<DataPipeBytesConsumer::CompletionNotifier> completion_notifier_;
+
+  DISALLOW_COPY_AND_ASSIGN(Client);
+};
+
+void CacheStorageBlobClientList::AddClient(
+    mojo::PendingReceiver<mojom::blink::BlobReaderClient>
+        client_pending_receiver,
+    DataPipeBytesConsumer::CompletionNotifier* completion_notifier) {
+  clients.emplace_back(MakeGarbageCollected<Client>(
+      this, std::move(client_pending_receiver), completion_notifier));
+}
+
+void CacheStorageBlobClientList::Trace(blink::Visitor* visitor) {
+  visitor->Trace(clients);
+}
+
+void CacheStorageBlobClientList::RevokeClient(Client* client) {
+  auto index = clients.Find(client);
+  clients.EraseAt(index);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h
new file mode 100644
index 0000000..23b0452
--- /dev/null
+++ b/third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h
@@ -0,0 +1,39 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_STORAGE_BLOB_CLIENT_LIST_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_STORAGE_BLOB_CLIENT_LIST_H_
+
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "third_party/blink/public/mojom/blob/blob.mojom-blink.h"
+#include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/loader/fetch/data_pipe_bytes_consumer.h"
+
+namespace blink {
+
+// This class holds a list of BlobReaderClient implementations alive until
+// they complete or the entire list is garbage collected.
+class CacheStorageBlobClientList
+    : public GarbageCollected<CacheStorageBlobClientList> {
+ public:
+  CacheStorageBlobClientList() = default;
+  void AddClient(
+      mojo::PendingReceiver<mojom::blink::BlobReaderClient>
+          client_pending_receiver,
+      DataPipeBytesConsumer::CompletionNotifier* completion_notifier);
+
+  void Trace(blink::Visitor* visitor);
+
+ private:
+  class Client;
+
+  void RevokeClient(Client* client);
+
+  HeapVector<Member<Client>> clients;
+  DISALLOW_COPY_AND_ASSIGN(CacheStorageBlobClientList);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_STORAGE_BLOB_CLIENT_LIST_H_
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_test.cc b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
index 016dbac..707c7592 100644
--- a/third_party/blink/renderer/modules/cache_storage/cache_test.cc
+++ b/third_party/blink/renderer/modules/cache_storage/cache_test.cc
@@ -141,6 +141,7 @@
 
   void Match(mojom::blink::FetchAPIRequestPtr fetch_api_request,
              mojom::blink::CacheQueryOptionsPtr query_options,
+             bool in_related_fetch_event,
              int64_t trace_id,
              MatchCallback callback) override {
     last_error_web_cache_method_called_ = "dispatchMatch";
@@ -537,6 +538,7 @@
   // From WebServiceWorkerCache:
   void Match(mojom::blink::FetchAPIRequestPtr fetch_api_request,
              mojom::blink::CacheQueryOptionsPtr query_options,
+             bool in_related_fetch_event,
              int64_t trace_id,
              MatchCallback callback) override {
     mojom::blink::MatchResultPtr result = mojom::blink::MatchResult::New();
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_utils.cc b/third_party/blink/renderer/modules/cache_storage/cache_utils.cc
new file mode 100644
index 0000000..f702022
--- /dev/null
+++ b/third_party/blink/renderer/modules/cache_storage/cache_utils.cc
@@ -0,0 +1,47 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/cache_storage/cache_utils.h"
+
+#include "third_party/blink/renderer/core/fetch/fetch_response_data.h"
+#include "third_party/blink/renderer/core/fetch/response.h"
+#include "third_party/blink/renderer/modules/cache_storage/cache_storage_blob_client_list.h"
+#include "third_party/blink/renderer/platform/loader/fetch/data_pipe_bytes_consumer.h"
+
+namespace blink {
+
+Response* CreateEagerResponse(ScriptState* script_state,
+                              mojom::blink::EagerResponsePtr eager_response,
+                              CacheStorageBlobClientList* client_list) {
+  auto& response = eager_response->response;
+  DCHECK(!response->blob);
+
+  ExecutionContext* context = ExecutionContext::From(script_state);
+
+  FetchResponseData* fetch_data =
+      Response::CreateUnfilteredFetchResponseDataWithoutBody(script_state,
+                                                             *response);
+
+  DataPipeBytesConsumer::CompletionNotifier* completion_notifier = nullptr;
+  fetch_data->ReplaceBodyStreamBuffer(MakeGarbageCollected<BodyStreamBuffer>(
+      script_state,
+      MakeGarbageCollected<DataPipeBytesConsumer>(
+          context->GetTaskRunner(TaskType::kNetworking),
+          std::move(eager_response->pipe), &completion_notifier),
+      nullptr /* AbortSignal */));
+
+  // Create a BlobReaderClient in the provided list.  This will track the
+  // completion of the eagerly read blob and propagate it to the given
+  // DataPipeBytesConsumer::CompletionNotifier.  The list will also hold
+  // the client alive.
+  client_list->AddClient(std::move(eager_response->client_receiver),
+                         std::move(completion_notifier));
+
+  fetch_data = Response::FilterResponseData(
+      response->response_type, fetch_data, response->cors_exposed_header_names);
+
+  return Response::Create(context, fetch_data);
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/cache_storage/cache_utils.h b/third_party/blink/renderer/modules/cache_storage/cache_utils.h
new file mode 100644
index 0000000..2c80bed
--- /dev/null
+++ b/third_party/blink/renderer/modules/cache_storage/cache_utils.h
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_UTILS_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_UTILS_H_
+
+#include "third_party/blink/public/mojom/cache_storage/cache_storage.mojom-blink.h"
+
+namespace blink {
+
+class CacheStorageBlobClientList;
+class Response;
+class ScriptState;
+
+// A utility function to deserialize an eagerly read cache_storage response
+// into a web-exposed fetch Response object.  The resulting Response will
+// have a DataPipeBytesConsumer body and a side_data_blob which can be used
+// to read any code cache.
+Response* CreateEagerResponse(ScriptState* script_state,
+                              mojom::blink::EagerResponsePtr eager_response,
+                              CacheStorageBlobClientList* client_list);
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_CACHE_STORAGE_CACHE_UTILS_H_
diff --git a/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc b/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc
index cc82723..ccc8cee 100644
--- a/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc
+++ b/third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.cc
@@ -266,7 +266,8 @@
           request->fetch_window_id, request->keepalive, request->is_reload,
           request->is_history_navigation);
       cache_remote_->Match(
-          std::move(request), mojom::blink::CacheQueryOptions::New(), trace_id,
+          std::move(request), mojom::blink::CacheQueryOptions::New(),
+          false /* in_related_fetch_event */, trace_id,
           WTF::Bind(
               [](scoped_refptr<ResponsesAccumulator> accumulator,
                  mojom::blink::FetchAPIRequestPtr request,
@@ -718,7 +719,8 @@
   multi_query_options->cache_name = cache_name;
 
   cache_storage->Match(
-      std::move(request), std::move(multi_query_options), trace_id,
+      std::move(request), std::move(multi_query_options),
+      false /* in_related_fetch_event */, trace_id,
       WTF::Bind(
           [](std::unique_ptr<RequestCachedResponseCallback> callback,
              mojom::blink::MatchResultPtr result) {
diff --git a/third_party/blink/renderer/modules/indexeddb/idb_key_range.idl b/third_party/blink/renderer/modules/indexeddb/idb_key_range.idl
index 49dbff0..d922a27 100644
--- a/third_party/blink/renderer/modules/indexeddb/idb_key_range.idl
+++ b/third_party/blink/renderer/modules/indexeddb/idb_key_range.idl
@@ -41,5 +41,5 @@
                                                                                 optional boolean lowerOpen = false,
                                                                                 optional boolean upperOpen = false);
 
-    [CallWith=ScriptState, RaisesException] boolean _includes(any key);
+    [CallWith=ScriptState, RaisesException] boolean includes(any key);
 };
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index 5d0860f..2fb3d77c 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -1014,8 +1014,6 @@
           "webdatabase/window_web_database.idl",
           "webgl/webgl2_rendering_context_base.idl",
           "webgl/webgl_rendering_context_base.idl",
-          "webgpu/gpu_programmable_pass_encoder.idl",
-          "webgpu/gpu_render_encoder_base.idl",
           "webgpu/navigator_gpu.idl",
           "webmidi/navigator_web_midi.idl",
           "webshare/navigator_share.idl",
diff --git a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc
index ca0dd6e..7de6a92c 100644
--- a/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc
+++ b/third_party/blink/renderer/modules/service_worker/fetch_respond_with_observer.cc
@@ -216,7 +216,7 @@
   response->status_text = "";
   response->error = error;
   To<ServiceWorkerGlobalScope>(GetExecutionContext())
-      ->RespondToFetchEvent(event_id_, std::move(response),
+      ->RespondToFetchEvent(event_id_, request_url_, std::move(response),
                             event_dispatch_time_, base::TimeTicks::Now());
 }
 
@@ -323,8 +323,8 @@
       // Handle the blob response body.
       fetch_api_response->blob = blob_data_handle;
       service_worker_global_scope->RespondToFetchEvent(
-          event_id_, std::move(fetch_api_response), event_dispatch_time_,
-          base::TimeTicks::Now());
+          event_id_, request_url_, std::move(fetch_api_response),
+          event_dispatch_time_, base::TimeTicks::Now());
       return;
     }
 
@@ -349,19 +349,20 @@
     }
 
     service_worker_global_scope->RespondToFetchEventWithResponseStream(
-        event_id_, std::move(fetch_api_response), std::move(stream_handle),
-        event_dispatch_time_, base::TimeTicks::Now());
+        event_id_, request_url_, std::move(fetch_api_response),
+        std::move(stream_handle), event_dispatch_time_, base::TimeTicks::Now());
     return;
   }
   service_worker_global_scope->RespondToFetchEvent(
-      event_id_, std::move(fetch_api_response), event_dispatch_time_,
-      base::TimeTicks::Now());
+      event_id_, request_url_, std::move(fetch_api_response),
+      event_dispatch_time_, base::TimeTicks::Now());
 }
 
 void FetchRespondWithObserver::OnNoResponse() {
   DCHECK(GetExecutionContext());
   To<ServiceWorkerGlobalScope>(GetExecutionContext())
-      ->RespondToFetchEventWithNoResponse(event_id_, event_dispatch_time_,
+      ->RespondToFetchEventWithNoResponse(event_id_, request_url_,
+                                          event_dispatch_time_,
                                           base::TimeTicks::Now());
 }
 
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
index c50950453..a5da035 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.cc
@@ -762,6 +762,12 @@
   WorkerGlobalScope::Trace(visitor);
 }
 
+bool ServiceWorkerGlobalScope::HasRelatedFetchEvent(
+    const KURL& request_url) const {
+  auto it = unresponded_fetch_event_counts_.find(request_url);
+  return it != unresponded_fetch_event_counts_.end();
+}
+
 void ServiceWorkerGlobalScope::importScripts(
     const HeapVector<StringOrTrustedScriptURL>& urls,
     ExceptionState& exception_state) {
@@ -932,6 +938,7 @@
 
 void ServiceWorkerGlobalScope::RespondToFetchEventWithNoResponse(
     int fetch_event_id,
+    const KURL& request_url,
     base::TimeTicks event_dispatch_time,
     base::TimeTicks respond_with_settled_time) {
   DCHECK(IsContextThread());
@@ -949,11 +956,14 @@
   timing->dispatch_event_time = event_dispatch_time;
   timing->respond_with_settled_time = respond_with_settled_time;
 
+  NoteRespondedToFetchEvent(request_url);
+
   response_callback->OnFallback(std::move(timing));
 }
 
 void ServiceWorkerGlobalScope::RespondToFetchEvent(
     int fetch_event_id,
+    const KURL& request_url,
     mojom::blink::FetchAPIResponsePtr response,
     base::TimeTicks event_dispatch_time,
     base::TimeTicks respond_with_settled_time) {
@@ -972,11 +982,14 @@
   timing->dispatch_event_time = event_dispatch_time;
   timing->respond_with_settled_time = respond_with_settled_time;
 
+  NoteRespondedToFetchEvent(request_url);
+
   response_callback->OnResponse(std::move(response), std::move(timing));
 }
 
 void ServiceWorkerGlobalScope::RespondToFetchEventWithResponseStream(
     int fetch_event_id,
+    const KURL& request_url,
     mojom::blink::FetchAPIResponsePtr response,
     mojom::blink::ServiceWorkerStreamHandlePtr body_as_stream,
     base::TimeTicks event_dispatch_time,
@@ -996,6 +1009,8 @@
   timing->dispatch_event_time = event_dispatch_time;
   timing->respond_with_settled_time = respond_with_settled_time;
 
+  NoteRespondedToFetchEvent(request_url);
+
   response_callback->OnResponseStream(
       std::move(response), std::move(body_as_stream), std::move(timing));
 }
@@ -1391,6 +1406,8 @@
     pending_preload_fetch_events_.insert(event_id, fetch_event);
   }
 
+  NoteNewFetchEvent(request->url());
+
   DispatchExtendableEventWithRespondWith(fetch_event, wait_until_observer,
                                          respond_with_observer);
 }
@@ -2014,4 +2031,22 @@
                               /* column_number= */ 0)));
 }
 
+void ServiceWorkerGlobalScope::NoteNewFetchEvent(const KURL& request_url) {
+  auto it = unresponded_fetch_event_counts_.find(request_url);
+  if (it == unresponded_fetch_event_counts_.end()) {
+    unresponded_fetch_event_counts_.insert(request_url, 1);
+  } else {
+    it->value += 1;
+  }
+}
+
+void ServiceWorkerGlobalScope::NoteRespondedToFetchEvent(
+    const KURL& request_url) {
+  auto it = unresponded_fetch_event_counts_.find(request_url);
+  DCHECK_GE(it->value, 1);
+  it->value -= 1;
+  if (it->value == 0)
+    unresponded_fetch_event_counts_.erase(it);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h
index e17118c..d337037 100644
--- a/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h
+++ b/third_party/blink/renderer/modules/service_worker/service_worker_global_scope.h
@@ -49,6 +49,7 @@
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 #include "third_party/blink/renderer/platform/wtf/forward.h"
+#include "third_party/blink/renderer/platform/wtf/hash_set.h"
 
 namespace blink {
 
@@ -214,10 +215,12 @@
   // native fetch.
   void RespondToFetchEventWithNoResponse(
       int fetch_event_id,
+      const KURL& request_url,
       base::TimeTicks event_dispatch_time,
       base::TimeTicks respond_with_settled_time);
   // Responds to the fetch event with |response|.
   void RespondToFetchEvent(int fetch_event_id,
+                           const KURL& request_url,
                            mojom::blink::FetchAPIResponsePtr,
                            base::TimeTicks event_dispatch_time,
                            base::TimeTicks respond_with_settled_time);
@@ -225,6 +228,7 @@
   // |body_as_stream|.
   void RespondToFetchEventWithResponseStream(
       int fetch_event_id,
+      const KURL& request_url,
       mojom::blink::FetchAPIResponsePtr,
       mojom::blink::ServiceWorkerStreamHandlePtr,
       base::TimeTicks event_dispatch_time,
@@ -283,6 +287,10 @@
 
   void Trace(blink::Visitor*) override;
 
+  // Returns true if a FetchEvent exists with the given request URL and
+  // is still waiting for a Response.
+  bool HasRelatedFetchEvent(const KURL& request_url) const;
+
  protected:
   // EventTarget
   bool AddEventListenerInternal(
@@ -450,6 +458,9 @@
   void AddMessageToConsole(mojom::blink::ConsoleMessageLevel,
                            const String& message) override;
 
+  void NoteNewFetchEvent(const KURL& request_url);
+  void NoteRespondedToFetchEvent(const KURL& request_url);
+
   Member<ServiceWorkerClients> clients_;
   Member<ServiceWorkerRegistration> registration_;
   Member<::blink::ServiceWorker> service_worker_;
@@ -537,6 +548,13 @@
 
   HeapHashMap<int, Member<FetchEvent>> pending_preload_fetch_events_;
 
+  // Track outstanding FetchEvent objects still waiting for a response by
+  // request URL.  This information can be used as a hint that cache_storage
+  // or fetch requests to the same URL is likely to be used to satisfy a
+  // FetchEvent.  This in turn can allow us to use more aggressive
+  // optimizations in these cases.
+  HashMap<KURL, int> unresponded_fetch_event_counts_;
+
   // Timer triggered when the service worker considers it should be stopped or
   // an event should be aborted.
   std::unique_ptr<ServiceWorkerTimeoutTimer> timeout_timer_;
diff --git a/third_party/blink/renderer/modules/wake_lock/wake_lock_controller.cc b/third_party/blink/renderer/modules/wake_lock/wake_lock_controller.cc
index 6c87877..d760cfe 100644
--- a/third_party/blink/renderer/modules/wake_lock/wake_lock_controller.cc
+++ b/third_party/blink/renderer/modules/wake_lock/wake_lock_controller.cc
@@ -109,7 +109,9 @@
   // abort early.
   if (type == WakeLockType::kScreen &&
       !(GetPage() && GetPage()->IsPageVisible())) {
-    ReleaseWakeLock(type, resolver);
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kNotAllowedError,
+        "The requesting page is not visible"));
     return;
   }
   // 6.2. Let success be the result of awaiting acquire a wake lock with promise
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.idl b/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.idl
index dd75cea..fc1b92f 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_compute_pass_encoder.idl
@@ -7,7 +7,16 @@
 [
     RuntimeEnabled=WebGPU
 ] interface GPUComputePassEncoder {
+    // GPUProgrammablePassEncoder methods
+    void setBindGroup(unsigned long index,
+                      GPUBindGroup bindGroup,
+                      optional sequence<GPUBufferSize> dynamicOffsets = []);
+    void pushDebugGroup(DOMString groupLabel);
+    void popDebugGroup();
+    void insertDebugMarker(DOMString markerLabel);
+
     void setPipeline(GPUComputePipeline pipeline);
+
     void dispatch(unsigned long x,
                   optional unsigned long y = 1,
                   optional unsigned long z = 1);
@@ -16,4 +25,3 @@
 
     void endPass();
 };
-GPUComputePassEncoder includes GPUProgrammablePassEncoder;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_programmable_pass_encoder.idl b/third_party/blink/renderer/modules/webgpu/gpu_programmable_pass_encoder.idl
deleted file mode 100644
index 4d57cef..0000000
--- a/third_party/blink/renderer/modules/webgpu/gpu_programmable_pass_encoder.idl
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// https://gpuweb.github.io/gpuweb/
-
-[
-    RuntimeEnabled=WebGPU
-] interface mixin GPUProgrammablePassEncoder {
-    void setBindGroup(unsigned long index, GPUBindGroup bindGroup,
-                      optional sequence<GPUBufferSize> dynamicOffsets = []);
-
-    void pushDebugGroup(DOMString groupLabel);
-    void popDebugGroup();
-    void insertDebugMarker(DOMString markerLabel);
-};
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.idl b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.idl
index 43b226a..23f97cd 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_bundle_encoder.idl
@@ -7,6 +7,32 @@
 [
     RuntimeEnabled=WebGPU
 ] interface GPURenderBundleEncoder {
+    // GPUProgrammablePassEncoder methods
+    void setBindGroup(unsigned long index,
+                      GPUBindGroup bindGroup,
+                      optional sequence<GPUBufferSize> dynamicOffsets = []);
+    void pushDebugGroup(DOMString groupLabel);
+    void popDebugGroup();
+    void insertDebugMarker(DOMString markerLabel);
+
+    // GPURenderEncoderBase methods
+    void setPipeline(GPURenderPipeline pipeline);
+    void setIndexBuffer(GPUBuffer buffer, optional GPUBufferSize offset = 0);
+    void setVertexBuffer(unsigned long slot,
+                         GPUBuffer buffer,
+                         optional GPUBufferSize offset = 0);
+    void draw(unsigned long vertexCount, unsigned long instanceCount,
+              unsigned long firstVertex,
+              unsigned long firstInstance);
+    void drawIndexed(unsigned long indexCount, unsigned long instanceCount,
+                     unsigned long firstIndex,
+                     long baseVertex,
+                     unsigned long firstInstance);
+    void drawIndirect(GPUBuffer indirectBuffer,
+                      GPUBufferSize indirectOffset);
+    void drawIndexedIndirect(GPUBuffer indirectBuffer,
+                             GPUBufferSize indirectOffset);
+
+    // GPURenderBundleEncoder methods
     GPURenderBundle finish(optional GPURenderBundleDescriptor descriptor = {});
 };
-GPURenderBundleEncoder includes GPURenderEncoderBase;
diff --git a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.idl b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.idl
index 7061c18c..e61fe3c 100644
--- a/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.idl
+++ b/third_party/blink/renderer/modules/webgpu/gpu_render_pass_encoder.idl
@@ -7,17 +7,39 @@
 [
     RuntimeEnabled=WebGPU
 ] interface GPURenderPassEncoder {
-    void setViewport(float x, float y,
-                     float width, float height,
-                     float minDepth, float maxDepth);
+    // GPUProgrammablePassEncoder methods
+    void setBindGroup(unsigned long index,
+                      GPUBindGroup bindGroup,
+                      optional sequence<GPUBufferSize> dynamicOffsets = []);
+    void pushDebugGroup(DOMString groupLabel);
+    void popDebugGroup();
+    void insertDebugMarker(DOMString markerLabel);
 
-    void setScissorRect(unsigned long x, unsigned long y,
-                        unsigned long width, unsigned long height);
+    void setPipeline(GPURenderPipeline pipeline);
 
     [RaisesException] void setBlendColor(GPUColor color);
     void setStencilReference(unsigned long reference);
+    void setViewport(float x, float y,
+                     float width, float height,
+                     float minDepth, float maxDepth);
+    void setScissorRect(unsigned long x, unsigned long y,
+                        unsigned long width, unsigned long height);
+    void setIndexBuffer(GPUBuffer buffer, optional GPUBufferSize offset = 0);
+    void setVertexBuffer(unsigned long slot,
+                         GPUBuffer buffer,
+                         optional GPUBufferSize offset = 0);
+    void draw(unsigned long vertexCount, unsigned long instanceCount,
+              unsigned long firstVertex,
+              unsigned long firstInstance);
+    void drawIndexed(unsigned long indexCount, unsigned long instanceCount,
+                     unsigned long firstIndex,
+                     long baseVertex,
+                     unsigned long firstInstance);
+    void drawIndirect(GPUBuffer indirectBuffer,
+                      GPUBufferSize indirectOffset);
+    void drawIndexedIndirect(GPUBuffer indirectBuffer,
+                             GPUBufferSize indirectOffset);
 
     void executeBundles(sequence<GPURenderBundle> bundles);
     void endPass();
 };
-GPURenderPassEncoder includes GPURenderEncoderBase;
diff --git a/third_party/blink/renderer/modules/xr/xr.cc b/third_party/blink/renderer/modules/xr/xr.cc
index 4b69014c..d904b4b 100644
--- a/third_party/blink/renderer/modules/xr/xr.cc
+++ b/third_party/blink/renderer/modules/xr/xr.cc
@@ -939,6 +939,9 @@
                                           TaskType::kMiscPlatformAPI)));
       environment_provider_.set_connection_error_handler(WTF::Bind(
           &XR::OnEnvironmentProviderDisconnect, WrapWeakPersistent(this)));
+
+      session->OnEnvironmentProviderCreated();
+
       LocalFrame* frame = GetFrame();
       DCHECK(frame);
 
diff --git a/third_party/blink/renderer/modules/xr/xr_hit_test_options.cc b/third_party/blink/renderer/modules/xr/xr_hit_test_options.cc
index 907b407..73d366ff 100644
--- a/third_party/blink/renderer/modules/xr/xr_hit_test_options.cc
+++ b/third_party/blink/renderer/modules/xr/xr_hit_test_options.cc
@@ -12,7 +12,6 @@
 
 XRHitTestOptions::XRHitTestOptions(XRHitTestOptionsInit* options_init) {
   DCHECK(options_init);
-  DCHECK(options_init->hasSpace());  // Is it enforced by generated bindings?
 
   space_ = options_init->space();
 
diff --git a/third_party/blink/renderer/modules/xr/xr_input_source.idl b/third_party/blink/renderer/modules/xr/xr_input_source.idl
index d1869d2..78ee80c 100644
--- a/third_party/blink/renderer/modules/xr/xr_input_source.idl
+++ b/third_party/blink/renderer/modules/xr/xr_input_source.idl
@@ -23,6 +23,6 @@
   readonly attribute XRTargetRayMode targetRayMode;
   [SameObject] readonly attribute XRSpace targetRaySpace;
   [SameObject] readonly attribute XRSpace? gripSpace;
-  [SameObject, Measure] readonly attribute Gamepad? gamepad;
+  [SameObject, Measure, RuntimeEnabled=WebXrGamepadModule] readonly attribute Gamepad? gamepad;
   [SameObject, SaveSameObject] readonly attribute FrozenArray<DOMString> profiles;
 };
diff --git a/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc b/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc
index 775a4c6..f25edd1 100644
--- a/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc
+++ b/third_party/blink/renderer/modules/xr/xr_native_origin_information.cc
@@ -34,11 +34,54 @@
 base::Optional<XRNativeOriginInformation> XRNativeOriginInformation::Create(
     const XRReferenceSpace* reference_space) {
   DCHECK(reference_space);
-  // TODO(https://crbug.com/997369): Implement once mojo changes land.
-  return base::nullopt;
+  switch (reference_space->GetType()) {
+    case XRReferenceSpace::Type::kTypeBoundedFloor:
+      return XRNativeOriginInformation(
+          Type::ReferenceSpace,
+          device::mojom::XRReferenceSpaceCategory::BOUNDED_FLOOR);
+    case XRReferenceSpace::Type::kTypeUnbounded:
+      return XRNativeOriginInformation(
+          Type::ReferenceSpace,
+          device::mojom::XRReferenceSpaceCategory::UNBOUNDED);
+    case XRReferenceSpace::Type::kTypeLocalFloor:
+      return XRNativeOriginInformation(
+          Type::ReferenceSpace,
+          device::mojom::XRReferenceSpaceCategory::LOCAL_FLOOR);
+    case XRReferenceSpace::Type::kTypeLocal:
+      return XRNativeOriginInformation(
+          Type::ReferenceSpace, device::mojom::XRReferenceSpaceCategory::LOCAL);
+    case XRReferenceSpace::Type::kTypeViewer:
+      return XRNativeOriginInformation(
+          Type::ReferenceSpace,
+          device::mojom::XRReferenceSpaceCategory::VIEWER);
+  }
 }
 
 XRNativeOriginInformation::XRNativeOriginInformation(Type type, uint32_t id)
-    : id_(id) {}
+    : type_(type), id_(id) {}
+
+XRNativeOriginInformation::XRNativeOriginInformation(
+    Type type,
+    device::mojom::XRReferenceSpaceCategory reference_space_category)
+    : type_(type), reference_space_category_(reference_space_category) {}
+
+device::mojom::blink::XRNativeOriginInformationPtr
+XRNativeOriginInformation::ToMojo() const {
+  switch (type_) {
+    case XRNativeOriginInformation::Type::Anchor:
+      return device::mojom::blink::XRNativeOriginInformation::NewAnchorId(id_);
+
+    case XRNativeOriginInformation::Type::InputSource:
+      return device::mojom::blink::XRNativeOriginInformation::NewInputSourceId(
+          id_);
+
+    case XRNativeOriginInformation::Type::Plane:
+      return device::mojom::blink::XRNativeOriginInformation::NewPlaneId(id_);
+
+    case XRNativeOriginInformation::Type::ReferenceSpace:
+      return device::mojom::blink::XRNativeOriginInformation::
+          NewReferenceSpaceCategory(reference_space_category_);
+  }
+}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_native_origin_information.h b/third_party/blink/renderer/modules/xr/xr_native_origin_information.h
index 899556e..e517126 100644
--- a/third_party/blink/renderer/modules/xr/xr_native_origin_information.h
+++ b/third_party/blink/renderer/modules/xr/xr_native_origin_information.h
@@ -16,10 +16,16 @@
 class XRPlane;
 class XRReferenceSpace;
 
+// XRNativeOriginInformation carries all the information that is required to
+// uniquely identify a native origin on the device side. Native origin roughly
+// represents anything that is known and tracked by the device, for example
+// anchors, planes, input sources, reference spaces.
 class XRNativeOriginInformation {
  public:
   XRNativeOriginInformation(XRNativeOriginInformation&& other) = default;
 
+  device::mojom::blink::XRNativeOriginInformationPtr ToMojo() const;
+
   static base::Optional<XRNativeOriginInformation> Create(
       const XRAnchor* anchor);
   static base::Optional<XRNativeOriginInformation> Create(
@@ -36,10 +42,16 @@
   void operator=(const XRNativeOriginInformation& other) = delete;
 
   XRNativeOriginInformation(Type type, uint32_t id);
+  XRNativeOriginInformation(
+      Type type,
+      device::mojom::XRReferenceSpaceCategory reference_space_type);
 
-  // TODO(https://crbug.com/997369): Add reference space category to the union
-  // once mojo changes land.
-  const union { uint32_t id_; };
+  const Type type_;
+
+  const union {
+    uint32_t id_;
+    device::mojom::XRReferenceSpaceCategory reference_space_category_;
+  };
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index bdcad2b..ffa613f 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -476,7 +476,6 @@
            << ", pose_ptr->position = [" << pose_ptr->position->x << ", "
            << pose_ptr->position->y << ", " << pose_ptr->position->z << "]";
 
-  EnsureEnvironmentErrorHandler();
   if (plane) {
     xr_->xrEnvironmentProviderPtr()->CreatePlaneAnchor(
         std::move(pose_ptr), plane->id(),
@@ -563,7 +562,6 @@
   auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
   ScriptPromise promise = resolver->Promise();
 
-  EnsureEnvironmentErrorHandler();
   xr_->xrEnvironmentProviderPtr()->RequestHitTest(
       std::move(ray_mojo),
       WTF::Bind(&XRSession::OnHitTestResults, WrapPersistent(this),
@@ -625,11 +623,16 @@
   auto direction = origin_from_ray.MapPoint({0, 0, -1});
   ray_mojo->direction = {direction.X(), direction.Y(), direction.Z()};
 
-  // TODO(https://crbug.com/997369): Actually issue a call to the device once
-  // mojo interfaces land.
-  exception_state.ThrowDOMException(DOMExceptionCode::kOperationError,
-                                    kHitTestSubscriptionFailed);
-  return {};
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  ScriptPromise promise = resolver->Promise();
+
+  xr_->xrEnvironmentProviderPtr()->SubscribeToHitTest(
+      maybe_native_origin->ToMojo(), std::move(ray_mojo),
+      WTF::Bind(&XRSession::OnSubscribeToHitTestResult, WrapPersistent(this),
+                WrapPersistent(resolver), WrapPersistent(options)));
+  request_hit_test_source_promises_.insert(resolver);
+
+  return promise;
 }
 
 void XRSession::OnHitTestResults(
@@ -652,6 +655,32 @@
   resolver->Resolve(hit_results);
 }
 
+void XRSession::OnSubscribeToHitTestResult(
+    ScriptPromiseResolver* resolver,
+    XRHitTestOptions* options,
+    device::mojom::SubscribeToHitTestResult result,
+    uint32_t subscription_id) {
+  DVLOG(2) << __func__ << ": result=" << result
+           << ", subscription_id=" << subscription_id;
+
+  DCHECK(request_hit_test_source_promises_.Contains(resolver));
+  request_hit_test_source_promises_.erase(resolver);
+
+  if (result != device::mojom::SubscribeToHitTestResult::SUCCESS) {
+    resolver->Reject(MakeGarbageCollected<DOMException>(
+        DOMExceptionCode::kOperationError, kHitTestSubscriptionFailed));
+    return;
+  }
+
+  XRHitTestSource* hit_test_source =
+      MakeGarbageCollected<XRHitTestSource>(subscription_id, options);
+
+  hit_test_source_ids_to_hit_test_sources_.insert(subscription_id,
+                                                  hit_test_source);
+
+  resolver->Resolve(hit_test_source);
+}
+
 void XRSession::OnCreateAnchorResult(ScriptPromiseResolver* resolver,
                                      device::mojom::CreateAnchorResult result,
                                      uint32_t id) {
@@ -665,6 +694,10 @@
   resolver->Resolve(anchor);
 }
 
+void XRSession::OnEnvironmentProviderCreated() {
+  EnsureEnvironmentErrorHandler();
+}
+
 void XRSession::EnsureEnvironmentErrorHandler() {
   // Install error handler on environment provider to ensure that we get
   // notified so that we can clean up all relevant pending promises.
@@ -758,6 +791,55 @@
   anchor_ids_to_anchors_.swap(updated_anchors);
 }
 
+void XRSession::CleanUpUnusedHitTestSources() {
+  // Gather all IDs of unused hit test sources.
+  HashSet<uint32_t> unused_hit_test_source_ids;
+  for (auto& subscription_id_and_hit_test_source :
+       hit_test_source_ids_to_hit_test_sources_) {
+    if (!subscription_id_and_hit_test_source.value) {
+      unused_hit_test_source_ids.insert(
+          subscription_id_and_hit_test_source.key);
+    }
+  }
+
+  // Remove all of the unused hit test sources.
+  hit_test_source_ids_to_hit_test_sources_.RemoveAll(
+      unused_hit_test_source_ids);
+
+  DVLOG(3) << __func__ << ": removed unused hit test sources, amount: "
+           << unused_hit_test_source_ids.size();
+}
+
+void XRSession::ProcessHitTestData(
+    const device::mojom::blink::XRHitTestSubscriptionResultsDataPtr&
+        hit_test_subscriptions_data) {
+  DVLOG(2) << __func__;
+
+  CleanUpUnusedHitTestSources();
+
+  if (hit_test_subscriptions_data) {
+    // We have received hit test results for hit test subscriptions - process
+    // each result and notify its corresponding hit test source about new
+    // results for the current frame.
+    for (auto& hit_test_subscription_data :
+         hit_test_subscriptions_data->results) {
+      auto it = hit_test_source_ids_to_hit_test_sources_.find(
+          hit_test_subscription_data->subscription_id);
+      if (it != hit_test_source_ids_to_hit_test_sources_.end()) {
+        it->value->Update(hit_test_subscription_data->hit_test_results);
+      }
+    }
+  } else {
+    // We have not received hit test results for any of the hit test
+    // subscriptions in the current frame - clean up the results on all hit test
+    // source objects.
+    for (auto& subscription_id_and_hit_test_source :
+         hit_test_source_ids_to_hit_test_sources_) {
+      subscription_id_and_hit_test_source.value->Update({});
+    }
+  }
+}
+
 ScriptPromise XRSession::end(ScriptState* script_state,
                              ExceptionState& exception_state) {
   // Don't allow a session to end twice.
@@ -1033,15 +1115,11 @@
     world_information_->ProcessPlaneInformation(
         frame_data->detected_planes_data, timestamp);
     ProcessAnchorsData(frame_data->anchors_data, timestamp);
-    // TODO(https://crbug.com/997369): Implement processing hit test data once
-    // mojo change lands.
-    // ProcessHitTestData(frame_data->hit_test_subscription_results);
+    ProcessHitTestData(frame_data->hit_test_subscription_results);
   } else {
     world_information_->ProcessPlaneInformation(nullptr, timestamp);
     ProcessAnchorsData(nullptr, timestamp);
-    // TODO(https://crbug.com/997369): Implement processing hit test data once
-    // mojo change lands.
-    // ProcessHitTestData(nullptr);
+    ProcessHitTestData(nullptr);
   }
 }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index f3af97c..2cab242 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -36,6 +36,7 @@
 class XRAnchor;
 class XRAnchorSet;
 class XRCanvasInputProvider;
+class XRHitTestOptions;
 class XRHitTestOptionsInit;
 class XRHitTestSource;
 class XRPlane;
@@ -243,6 +244,12 @@
       const device::mojom::blink::XRFrameDataPtr& frame_data,
       bool emulated_position);
 
+  // Notifies immersive session that the environment integration provider has
+  // been created by the session's XR instance, |xr_|. Gives a session an
+  // opportunity to register its own error handlers on environment integration
+  // provider endpoint.
+  void OnEnvironmentProviderCreated();
+
  private:
   class XRSessionResizeObserverDelegate;
 
@@ -272,6 +279,12 @@
       base::Optional<WTF::Vector<device::mojom::blink::XRHitResultPtr>>
           results);
 
+  void OnSubscribeToHitTestResult(
+      ScriptPromiseResolver* resolver,
+      XRHitTestOptions* options,
+      device::mojom::SubscribeToHitTestResult result,
+      uint32_t subscription_id);
+
   void OnCreateAnchorResult(ScriptPromiseResolver* resolver,
                             device::mojom::CreateAnchorResult result,
                             uint32_t id);
@@ -283,6 +296,12 @@
       const device::mojom::blink::XRAnchorsDataPtr& tracked_anchors_data,
       double timestamp);
 
+  void CleanUpUnusedHitTestSources();
+
+  void ProcessHitTestData(
+      const device::mojom::blink::XRHitTestSubscriptionResultsDataPtr&
+          hit_test_data);
+
   const Member<XR> xr_;
   const SessionMode mode_;
   const bool environment_integration_;
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index b1d8403..c5dac37 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -485,6 +485,10 @@
   RuntimeEnabledFeatures::SetWebXRAnchorsEnabled(enable);
 }
 
+void WebRuntimeFeatures::EnableWebXrGamepadModule(bool enable) {
+  RuntimeEnabledFeatures::SetWebXrGamepadModuleEnabled(enable);
+}
+
 void WebRuntimeFeatures::EnableWebXRHitTest(bool enable) {
   RuntimeEnabledFeatures::SetWebXRHitTestEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
index f2f8bdd..c98d8f2 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor_test.cc
@@ -976,7 +976,7 @@
   const cc::EffectNode& converted_effect1 = *effect_tree.Node(2);
   EXPECT_EQ(converted_root_effect.id, converted_effect1.parent_id);
   EXPECT_FLOAT_EQ(0.5, converted_effect1.opacity);
-  EXPECT_EQ(real_effect1->GetCompositorElementId().GetInternalValue(),
+  EXPECT_EQ(real_effect1->GetCompositorElementId().GetStableId(),
             converted_effect1.stable_id);
 
   const cc::EffectNode& converted_effect2 = *effect_tree.Node(3);
diff --git a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
index 94e6d59..757e737f 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/property_tree_manager.cc
@@ -290,7 +290,7 @@
   static UniqueObjectId unique_id = NewUniqueObjectId();
 
   effect_node.stable_id =
-      CompositorElementIdFromUniqueObjectId(unique_id).GetInternalValue();
+      CompositorElementIdFromUniqueObjectId(unique_id).GetStableId();
   effect_node.transform_id = kRealRootNodeId;
   effect_node.clip_id = kSecondaryRootNodeId;
   effect_node.render_surface_reason = cc::RenderSurfaceReason::kRoot;
@@ -598,14 +598,14 @@
   DCHECK_EQ(static_cast<uint64_t>(cc::EffectNode::INVALID_STABLE_ID),
             mask_isolation.stable_id);
 
-  mask_isolation.stable_id = mask_isolation_id.GetInternalValue();
+  mask_isolation.stable_id = mask_isolation_id.GetStableId();
 
   if (!needs_layer)
     return;
 
   cc::EffectNode& mask_effect = *GetEffectTree().Node(
       GetEffectTree().Insert(cc::EffectNode(), current_.effect_id));
-  mask_effect.stable_id = mask_effect_id.GetInternalValue();
+  mask_effect.stable_id = mask_effect_id.GetStableId();
   mask_effect.clip_id = clip_id;
   mask_effect.blend_mode = SkBlendMode::kDstIn;
 
@@ -906,7 +906,7 @@
     } else {
       synthetic_effect.stable_id =
           CompositorElementIdFromUniqueObjectId(NewUniqueObjectId())
-              .GetInternalValue();
+              .GetStableId();
       // The clip of the synthetic effect is the parent of the clip, so that
       // the clip itself will be applied in the render surface.
       DCHECK(pending_clip.clip->Parent());
@@ -1048,7 +1048,7 @@
     const EffectPaintPropertyNode& effect,
     int output_clip_id,
     SkBlendMode blend_mode) {
-  effect_node.stable_id = effect.GetCompositorElementId().GetInternalValue();
+  effect_node.stable_id = effect.GetCompositorElementId().GetStableId();
   effect_node.clip_id = output_clip_id;
 
   // An effect with filters or backdrop filters needs a render surface.
diff --git a/third_party/blink/renderer/platform/graphics/compositor_element_id.cc b/third_party/blink/renderer/platform/graphics/compositor_element_id.cc
index 0ea298e9..86486ca 100644
--- a/third_party/blink/renderer/platform/graphics/compositor_element_id.cc
+++ b/third_party/blink/renderer/platform/graphics/compositor_element_id.cc
@@ -47,7 +47,7 @@
 CompositorElementIdNamespace NamespaceFromCompositorElementId(
     CompositorElementId element_id) {
   return static_cast<CompositorElementIdNamespace>(
-      element_id.GetInternalValue() %
+      element_id.GetStableId() %
       static_cast<uint64_t>(CompositorElementIdNamespace::kMaxRepresentable));
 }
 
diff --git a/third_party/blink/renderer/platform/graphics/compositor_element_id_test.cc b/third_party/blink/renderer/platform/graphics/compositor_element_id_test.cc
index 225187d..7247c18 100644
--- a/third_party/blink/renderer/platform/graphics/compositor_element_id_test.cc
+++ b/third_party/blink/renderer/platform/graphics/compositor_element_id_test.cc
@@ -11,7 +11,7 @@
 class CompositorElementIdTest : public testing::Test {};
 
 uint64_t IdFromCompositorElementId(CompositorElementId element_id) {
-  return element_id.GetInternalValue() >> kCompositorNamespaceBitCount;
+  return element_id.GetStableId() >> kCompositorNamespaceBitCount;
 }
 
 TEST_F(CompositorElementIdTest, EncodeDecode) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index dc032029..05960cb 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -207,7 +207,7 @@
     },
     {
       name: "Badging",
-      origin_trial_feature_name: "Badging",
+      origin_trial_feature_name: "BadgingV2",
       status: "experimental",
     },
     {
@@ -1799,8 +1799,7 @@
     },
     {
       name: "WebXR",
-      origin_trial_feature_name: "WebXRDeviceM76",
-      status: "experimental",
+      status: "stable",
     },
     {
       name: "WebXRAnchors",
@@ -1813,6 +1812,11 @@
     },
     {
       name: "WebXRARModule",
+      depends_on: ["WebXR"],
+      status: "experimental",
+    },
+    {
+      name: "WebXrGamepadModule",
       // depends_on: ["WebXR"],  // TODO(https://crbug.com/954679): uncomment once bug is fixed
       status: "experimental",
     },
diff --git a/third_party/blink/web_tests/MSANExpectations b/third_party/blink/web_tests/MSANExpectations
index afed031..b8844a5 100644
--- a/third_party/blink/web_tests/MSANExpectations
+++ b/third_party/blink/web_tests/MSANExpectations
@@ -159,6 +159,7 @@
 crbug.com/856601 [ Linux ] virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/service-worker-servicification/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Pass Timeout ]
+crbug.com/856601 [ Linux ] virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Timeout Pass ]
 crbug.com/856601 [ Linux ] virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/interfaces-sw.https.html [ Pass Failure Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/fetch/api/idl.any.sharedworker.html [ Pass Timeout ]
 crbug.com/856601 [ Linux ] external/wpt/fetch/cors-rfc1918/idlharness.tentative.https.any.serviceworker.html [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 164c804..2a320041 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -1925,6 +1925,9 @@
 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html [ WontFix ]
 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/fetch-event-is-history-forward-navigation-manual.https.html [ WontFix ]
 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html [ WontFix ]
+virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/fetch-event-is-history-backward-navigation-manual.https.html [ WontFix ]
+virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/fetch-event-is-history-forward-navigation-manual.https.html [ WontFix ]
+virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/fetch-event-is-reload-navigation-manual.https.html [ WontFix ]
 virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-abort-manual.https.html [ WontFix ]
 virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-onerror-manual.https.html [ WontFix ]
 virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-onresult-manual.https.html [ WontFix ]
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 6018a197..1836362 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -1051,8 +1051,8 @@
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/balance-float-with-margin-top-and-line-after-break.html [ Crash Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/balance-float-with-margin-top-and-line-before-break.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/balance-line-overflow.html [ Failure ]
-crbug.com/829028 virtual/layout_ng_experimental/fast/multicol/balance-line-underflow-1.html [ Failure ]
-crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/balance-line-underflow-2.html [ Failure ]
+crbug.com/994172 virtual/layout_ng_experimental/fast/multicol/balance-line-underflow-1.html [ Pass Crash ]
+crbug.com/994172 virtual/layout_ng_experimental/fast/multicol/balance-line-underflow-2.html [ Pass Crash ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/basic-rtl.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/break-before-first-line-in-first-child.html [ Failure ]
 crbug.com/591099 virtual/layout_ng_experimental/fast/multicol/break-in-scrollable.html [ Failure ]
@@ -2016,11 +2016,8 @@
 # The spec for "Propagation to the Initial Containing Block" was changed.
 # https://drafts.csswg.org/css-writing-modes-4/#icb
 crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-001.html [ Failure ]
-crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-002.html [ Failure ]
-crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-033.html [ Failure ]
 crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-034.html [ Failure ]
 crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-035.html [ Failure ]
-crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-037.html [ Failure ]
 crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-038.html [ Failure ]
 crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-039.html [ Failure ]
 crbug.com/988585 external/wpt/css/css-writing-modes/wm-propagation-body-042.html [ Failure ]
@@ -2555,6 +2552,7 @@
 crbug.com/832071 virtual/navigation-mojo-response/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 crbug.com/832071 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 crbug.com/832071 virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
+crbug.com/832071 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/worker-client-id.https.html [ Failure ]
 
 # failures in external/wpt/css/css-animations/ and web-animations/ from Mozilla tests
 crbug.com/849859 external/wpt/css/css-animations/CSSAnimation-pausing.tentative.html [ Failure ]
@@ -2878,10 +2876,6 @@
 crbug.com/626703 virtual/audio-service/external/wpt/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html [ Timeout ]
 crbug.com/626703 virtual/feature-policy-permissions/external/wpt/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html [ Timeout ]
 crbug.com/699040 external/wpt/svg/text/reftests/text-xml-space-001.svg [ Failure ]
-crbug.com/626703 external/wpt/service-workers/service-worker/ready.https.html [ Timeout ]
-crbug.com/626703 virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/ready.https.html [ Timeout ]
-crbug.com/626703 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/ready.https.html [ Timeout ]
-crbug.com/626703 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/ready.https.html [ Timeout ]
 crbug.com/626703 [ Linux ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-002.tentative.html [ Failure ]
 crbug.com/626703 [ Mac ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-002.tentative.html [ Failure ]
 crbug.com/626703 [ Win ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-002.tentative.html [ Failure ]
@@ -4040,12 +4034,14 @@
 crbug.com/691944 virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
 crbug.com/691944 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
 crbug.com/691944 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
+crbug.com/691944 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/update-after-oneday.https.html [ Skip ]
 
 # These tests (erroneously) see a platform-specific User-Agent header
 crbug.com/595993 external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
 crbug.com/595993 virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
 crbug.com/595993 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
 crbug.com/595993 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
+crbug.com/595993 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/fetch-header-visibility.https.html [ Failure ]
 
 crbug.com/619427 [ Mac ] fast/overflow/overflow-height-float-not-removed-crash3.html [ Pass Failure ]
 
@@ -4306,6 +4302,7 @@
 crbug.com/889798 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
 crbug.com/889798 virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
 crbug.com/889798 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
+crbug.com/889798 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/import-scripts-redirect.https.html [ Skip ]
 
 # Sheriff failures 2017-07-03
 crbug.com/708994 http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
@@ -4873,6 +4870,8 @@
 crbug.com/873873 external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ]
 crbug.com/873873 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video.https.html [ Timeout Pass ]
 crbug.com/873873 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ]
+crbug.com/873873 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video.https.html [ Timeout Pass ]
+crbug.com/873873 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/fetch-canvas-tainting-video-cache.https.html [ Timeout Pass ]
 
 crbug.com/875884 [ Linux ] lifecycle/background-change-lifecycle-count.html [ Pass Failure ]
 crbug.com/875884 [ Win ] lifecycle/background-change-lifecycle-count.html [ Pass Failure ]
@@ -5192,6 +5191,7 @@
 crbug.com/933880 virtual/omt-service-worker-startup/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
 crbug.com/933880 virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
 crbug.com/933880 virtual/cache-storage-sequence/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
+crbug.com/933880 virtual/cache-storage-eager-reading/external/wpt/service-workers/service-worker/request-end-to-end.https.html [ Failure ]
 crbug.com/933880 http/tests/inspector-protocol/network/interception-take-stream.js [ Failure ]
 crbug.com/933880 http/tests/inspector-protocol/network/xhr-interception-auth-fail.js [ Failure ]
 # This passes in content_shell but not in chrome with network service disabled,
@@ -5422,9 +5422,6 @@
 crbug.com/963141 [ Linux ] media/video-object-fit.html [ Pass Failure ]
 crbug.com/963141 [ Linux ] virtual/audio-service/media/video-object-fit.html [ Pass Failure ]
 
-# Workaround for bug; should be removed soon.
-crbug.com/959693 external/wpt/trusted-types/WorkerGlobalScope-importScripts.https.html [ Timeout ]
-
 # Allow failure until its appearance gets stable.
 crbug.com/972476 std-switch/switch-appearance.html [ Failure ]
 crbug.com/972476 std-switch/switch-appearance-customization.html [ Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 82ae233..a66c757b 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1090,6 +1090,11 @@
     "args": ["--disable-features=CacheStorageSequence"]
   },
   {
+    "prefix": "cache-storage-eager-reading",
+    "base": "external/wpt/service-workers",
+    "args": ["--enable-features=CacheStorageEagerReading"]
+  },
+  {
     "prefix": "conditional-appcache-delay",
     "base": "http/tests/loading/appcache-delay",
     "args": ["--enable-features=VerifyHTMLFetchedFromAppCacheBeforeDelay"]
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
index 5fef2d24..59a52c2 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -461973,7 +461973,7 @@
    "support"
   ],
   "interfaces/cssom-view.idl": [
-   "5d30ede1e40debc56ab71ba58a7ddec0ba5b40cf",
+   "34036484195e28b3068c9ef09f44acd0bed6bda9",
    "support"
   ],
   "interfaces/cssom.idl": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-001.html b/third_party/blink/web_tests/external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-001.html
new file mode 100644
index 0000000..a478f9a
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-001.html
@@ -0,0 +1,81 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Intrinsic contribution of an item with flex tracks</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-spanning-items" title="11.5.3 Increase sizes to accommodate spanning items crossing content-sized tracks">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-spanning-flex-items" title="11.5.4 Increase sizes to accommodate spanning items crossing flexible tracks">
+<meta name="assert" content="This test checks that the intrinsic contribution of a single grid item is distributed correctly among the tracks it spans when flexible tracks are involved.">
+<style>
+#grid {
+  display: grid;
+  width: 50px;
+  height: 50px;
+  border: solid;
+}
+#item {
+  width: 100px;
+  height: 100px;
+  background: blue;
+}
+</style>
+
+<div id="log"></div>
+
+<div id="grid">
+  <div id="item"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../grid-definition/support/testing-utils.js"></script>
+<script>
+const item = document.getElementById("item");
+function checkTrackSizes(span, trackList, expected) {
+  item.style.gridColumn = item.style.gridRow = `span ${span}`;
+  TestingUtils.testGridTemplateColumnsRows("grid", trackList, trackList, expected, expected);
+}
+
+// Item spanning an intrinsic flexible track
+checkTrackSizes(1, "0fr", "100px");
+checkTrackSizes(1, "1fr", "100px");
+checkTrackSizes(1, "2fr", "100px");
+
+// Item spanning a fixed flexible track
+checkTrackSizes(1, "minmax(0, 0fr)", "0px");
+checkTrackSizes(1, "minmax(0, .5fr)", "25px");
+checkTrackSizes(1, "minmax(0, 1fr)", "50px");
+checkTrackSizes(1, "minmax(0, 2fr)", "50px");
+checkTrackSizes(1, "minmax(75px, 1fr)", "75px");
+
+// Item spanning 2 intrinsic flexible tracks
+checkTrackSizes(2, "0fr 0fr", "50px 50px");
+checkTrackSizes(2, "0fr 1fr", "0px 100px");
+checkTrackSizes(2, "1fr 0fr", "100px 0px");
+checkTrackSizes(2, "1fr 1fr", "50px 50px");
+checkTrackSizes(2, "1fr 3fr", "25px 75px");
+checkTrackSizes(2, "0fr 0fr 1fr", "50px 50px 0px");
+
+// Item spanning 2 fixed flexible tracks
+checkTrackSizes(2, "minmax(0, 0fr) minmax(0, 0fr)", "0px 0px");
+checkTrackSizes(2, "minmax(0, 0fr) minmax(0, 1fr)", "0px 50px");
+checkTrackSizes(2, "minmax(15px, 0fr) minmax(0, 1fr)", "15px 35px");
+checkTrackSizes(2, "minmax(20px, 1fr) minmax(0, 1fr)", "25px 25px");
+checkTrackSizes(2, "minmax(30px, 1fr) minmax(0, 1fr)", "30px 20px");
+
+// Item spanning an intrinsic flexible track and a fixed flexible track
+checkTrackSizes(2, "0fr minmax(0, 0fr)", "100px 0px");
+checkTrackSizes(2, "0fr minmax(0, 1fr)", "100px 0px");
+checkTrackSizes(2, "1fr minmax(0, 1fr)", "100px 0px");
+checkTrackSizes(2, "1fr minmax(25px, 1fr)", "75px 25px");
+
+// Item spanning an intrinsic flexible track and an intrinsic non-flexible track
+checkTrackSizes(2, "0fr auto", "100px 0px");
+checkTrackSizes(2, "1fr auto", "100px 0px");
+checkTrackSizes(2, "1fr max-content", "100px 0px");
+
+// Item spanning a fixed flexible track and an intrinsic non-flexible track
+checkTrackSizes(2, "minmax(0, 0fr) auto", "0px 100px");
+checkTrackSizes(2, "minmax(0, 1fr) auto", "0px 100px");
+checkTrackSizes(2, "minmax(25px, 0fr) auto", "25px 75px");
+checkTrackSizes(2, "minmax(25px, 1fr) auto", "25px 75px");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-002.html b/third_party/blink/web_tests/external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-002.html
new file mode 100644
index 0000000..ef5f1ae
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/layout-algorithm/grid-flex-track-intrinsic-sizes-002.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Grid Layout Test: Intrinsic contributions of 2 items with flex tracks</title>
+<link rel="author" title="Oriol Brufau" href="mailto:obrufau@igalia.com">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-spanning-items" title="11.5.3 Increase sizes to accommodate spanning items crossing content-sized tracks">
+<link rel="help" href="https://drafts.csswg.org/css-grid/#algo-spanning-flex-items" title="11.5.4 Increase sizes to accommodate spanning items crossing flexible tracks">
+<meta name="assert" content="This test checks that the intrinsic contributions of 2 items are distributed in the right order when flexible tracks are involved.">
+<style>
+#grid {
+  display: grid;
+  grid-template-areas: ". . . ."
+                       ". a . ."
+                       ". . . ."
+                       ". . . b";
+  width: 50px;
+  height: 50px;
+  border: solid;
+}
+#item1 {
+  grid-column: 1 / a;
+  grid-row: 1 / a;
+  width: 60px;
+  height: 60px;
+  background: blue;
+}
+#item2 {
+  grid-column: a / b;
+  grid-row: a / b;
+  width: 150px;
+  height: 150px;
+  background: yellow;
+}
+</style>
+
+<div id="log"></div>
+
+<div id="grid">
+  <div id="item1"></div>
+  <div id="item2"></div>
+</div>
+
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../grid-definition/support/testing-utils.js"></script>
+<script>
+function checkTrackSizes(trackList, expected) {
+  TestingUtils.testGridTemplateColumnsRows("grid", trackList, trackList, expected, expected);
+}
+
+// We have a symmetric grid with 2 items and 4 tracks, as follows:
+// ╔═╤═╗─┬─┐
+// ╟─╔═╬═╪═╗
+// ╚═╬═╝─┼─╢
+// ├─╫─┼─┼─╢
+// └─╚═╧═╧═╝
+
+// The 1st item has spans less tracks (2) than the 2nd item (3),
+// therefore its contribution (60px) is distributed first.
+// All the 60px go to the 2nd track, since the 1st track is not intrinsic.
+// Then the 2nd item only needs to distribute 150px-60px=90px
+// among the 3rd and 4th tracks.
+checkTrackSizes("minmax(0, 1fr) auto auto auto", "0px 60px 45px 45px");
+
+// The 1st item now spans a flexible track with an intrinsic minimum,
+// therefore its contribution (60px) is distributed last.
+// The 2nd item distributes its contribution (150px) among the 2nd, 3rd and 4th tracks.
+// Then the 1st item only needs to distribute 60px-50px=10px to the 1st track.
+checkTrackSizes("1fr auto auto auto", "10px 50px 50px 50px");
+
+// Now both items span a flexible track with an intrinsic minimum,
+// so their contributions are handled simultaneously,
+// even if the 1st item still spans less tracks than the 2nd one.
+// Therefore the distribution is as follows:
+//  - 1st track: 60px/2 = 30px
+//  - 2nd track: max(60px/2, 150px/3) = 50px
+//  - 3rd track: 150px/3 = 50px
+//  - 4th track: 150px/3 = 50px
+checkTrackSizes("1fr 1fr 1fr 1fr", "30px 50px 50px 50px");
+
+// Like the previous case, but with different flex ratios:
+//  - 1st track: 60px/2 = 30px
+//  - 2nd track: max(60px/2, 150px/6) = 30px
+//  - 3rd track: 150px/6 = 25px
+//  - 4th track: 150px*4/6 = 100px
+checkTrackSizes("1fr 1fr 1fr 4fr", "30px 30px 25px 100px");
+
+// Change the grid as follows:
+// ╔═╦═╤═╗
+// ╠═╝─┼─╢
+// ╟─┼─┼─╢
+// ╚═╧═╧═╝
+document.getElementById("grid").style.gridTemplateAreas = `
+  "a . ."
+  ". . ."
+  ". . b"`;
+
+// Now the 1st item has a span of 1, so usually we would handle its contribution
+// at the very beginning, before items that span multiple tracks.
+// But not if its track is flexible, then it's still handled at the end,
+// simultaneously with other items that span some intrinsic flexible track.
+//  - 1nd track: max(60px, 150px/3) = 60px
+//  - 2nd track: 150px/3 = 50px
+//  - 3rd track: 150px/3 = 50px
+checkTrackSizes("1fr 1fr 1fr", "60px 50px 50px");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vlr-005.xht b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vlr-005.xht
index 2754e24..a443db4e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vlr-005.xht
+++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vlr-005.xht
@@ -36,10 +36,6 @@
   html
     {
       writing-mode: vertical-lr;
-    }
-
-  body#containing-block
-    {
       background-image: url("support/bg-red-3col-2row-320x320.png");
       background-position: 198px 8px;
       /* first value represents the horizontal position and the second represents the vertical position */
@@ -58,9 +54,12 @@
         198px
       */
       background-repeat: no-repeat;
+    }
+
+  #containing-block
+    {
       direction: rtl;
       height: 320px;
-      margin: 8px;
     }
 
   p
@@ -70,7 +69,7 @@
       margin-right: 16px;
     }
 
-  div
+  #test
     {
       background-color: green;
       margin-top: 160px;
@@ -139,16 +138,16 @@
 
  </head>
 
- <body id="containing-block">
+ <body>
+  <div id="containing-block">
+   <p><img src="support/pass-cdts-abs-pos-non-replaced.png" width="246" height="36" alt="Image download support must be enabled" /></p>
+   <!--
+   The image says:
+   Test passes if there is a filled
+   green square and <strong>no red</strong>.
+   -->
 
-  <p><img src="support/pass-cdts-abs-pos-non-replaced.png" width="246" height="36" alt="Image download support must be enabled" /></p>
-  <!--
-  The image says:
-  Test passes if there is a filled
-  green square and <strong>no red</strong>.
-  -->
-
-  <div></div>
-
+   <div id="test"></div>
+  </div>
  </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vrl-004.xht b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vrl-004.xht
index 472405d..d084c2c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vrl-004.xht
+++ b/third_party/blink/web_tests/external/wpt/css/css-writing-modes/normal-flow-overconstrained-vrl-004.xht
@@ -36,10 +36,6 @@
   html
     {
       writing-mode: vertical-rl;
-    }
-
-  body#containing-block
-    {
       background-image: url("support/bg-red-3col-2row-320x320.png");
       background-position: -152px 8px;
       /* first value represents the horizontal position and the second represents the vertical position */
@@ -54,9 +50,11 @@
       -152px
       */
       background-repeat: no-repeat;
+    }
+  #containing-block
+    {
       direction: rtl;
       height: 320px;
-      margin: 8px;
     }
 
   p
@@ -66,7 +64,7 @@
       margin-right: 16px;
     }
 
-  div
+  #test
     {
       background-color: green;
       margin-top: 160px;
@@ -135,16 +133,16 @@
 
  </head>
 
- <body id="containing-block">
+ <body>
+  <div id="containing-block">
+   <p><img src="support/pass-cdts-abs-pos-non-replaced.png" width="246" height="36" alt="Image download support must be enabled" /></p>
+   <!--
+   The image says:
+   Test passes if there is a filled
+   green square and <strong>no red</strong>.
+   -->
 
-  <p><img src="support/pass-cdts-abs-pos-non-replaced.png" width="246" height="36" alt="Image download support must be enabled" /></p>
-  <!--
-  The image says:
-  Test passes if there is a filled
-  green square and <strong>no red</strong>.
-  -->
-
-  <div></div>
-
+   <div id="test"></div>
+  </div>
  </body>
 </html>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/cssom-view.idl b/third_party/blink/web_tests/external/wpt/interfaces/cssom-view.idl
index 5d30ede..34036484 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/cssom-view.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/cssom-view.idl
@@ -53,8 +53,8 @@
 interface MediaQueryList : EventTarget {
   readonly attribute CSSOMString media;
   readonly attribute boolean matches;
-  void addListener(EventListener? listener);
-  void removeListener(EventListener? listener);
+  void addListener(EventListener? callback);
+  void removeListener(EventListener? callback);
            attribute EventHandler onchange;
 };
 
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/clients-matchall-client-types.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/clients-matchall-client-types.https-expected.txt
index 2b0c2f8..096b59d 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/clients-matchall-client-types.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/clients-matchall-client-types.https-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
 FAIL Verify matchAll() with window client type assert_array_equals: property 2, expected "https://web-platform.test:8444/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html" but got "https://web-platform.test:8444/service-workers/service-worker/resources/url-modified-via-pushstate.html"
-FAIL Verify matchAll() with {window, sharedworker, worker} client types promise_test: Unhandled rejection with value: object "Error: wait_for_state must be passed a ServiceWorker"
+FAIL Verify matchAll() with {window, sharedworker, worker} client types assert_array_equals: property 2, expected "https://web-platform.test:8444/service-workers/service-worker/resources/clients-matchall-client-types-iframe.html" but got "https://web-platform.test:8444/service-workers/service-worker/resources/url-modified-via-pushstate.html"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/ready.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/ready.https-expected.txt
index d29a2b4e..10f1cc7 100644
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/ready.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/ready.https-expected.txt
@@ -7,6 +7,7 @@
 PASS ready on an iframe that installs a new service worker
 PASS ready after a longer matched registration registered
 PASS access ready after it has been resolved
-FAIL access ready on uninstalling registration that is resurrected assert_not_equals: ready promise should resolve before timeout got disallowed value null
+PASS resolve ready after unregistering and reregistering
+FAIL resolve ready before unregistering and reregistering assert_equals: Resolves with the first registration expected "https://web-platform.test:8444/service-workers/service-worker/resources/empty-worker.js" but got "https://web-platform.test:8444/service-workers/service-worker/resources/empty-worker.js?2"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-then-register-new-script.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-then-register-new-script.https-expected.txt
deleted file mode 100644
index 20a0fa54..0000000
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-then-register-new-script.https-expected.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-This is a testharness.js-based test.
-FAIL Registering a new script URL while an unregistered registration is in use assert_equals: before activated registration.installing expected null but got object "[object ServiceWorker]"
-PASS Registering a new script URL that 404s does not resurrect unregistered registration
-FAIL Registering a new script URL that fails to install does not resurrect unregistered registration assert_not_equals: New registration is different got disallowed value object "[object ServiceWorkerRegistration]"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-then-register.https-expected.txt b/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-then-register.https-expected.txt
deleted file mode 100644
index 30d5ef1..0000000
--- a/third_party/blink/web_tests/external/wpt/service-workers/service-worker/unregister-then-register.https-expected.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-This is a testharness.js-based test.
-PASS Unregister then register resolves to a new value
-FAIL Unregister then register does not resolve to the original value even if the registration is in use. assert_not_equals: Unregister and register should always create a new registration got disallowed value object "[object ServiceWorkerRegistration]"
-PASS Unregister then register does not affect existing controllee
-FAIL Unregister then register does not resurrect the registration assert_equals: Registration is new expected null but got object "[object ServiceWorker]"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/Node-multiple-arguments.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/Node-multiple-arguments.tentative.html
index 062c26d..e3e4a263 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/Node-multiple-arguments.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/Node-multiple-arguments.tentative.html
@@ -9,7 +9,7 @@
 <div id="container"></div>
 <script>
   const container = document.querySelector("#container");
-  const policy = window.TrustedTypes.createPolicy("policy", {
+  const policy = window.trustedTypes.createPolicy("policy", {
     createScript: t => t,
   });
   function stringify(arg) {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-no-name.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-no-name.tentative.html
index 2dfec26..18819b20 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-no-name.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-no-name.tentative.html
@@ -9,7 +9,7 @@
   // No name given test
   test(t => {
     assert_throws(new TypeError(),
-      () => window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } ),
+      () => window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } ),
       "createPolicy with an empty trusted-types CSP directive");
   }, "No name list given - policy creation fails.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-wildcard.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-wildcard.tentative.html
index 22b8796..0e97a4a2 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-wildcard.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP-wildcard.tentative.html
@@ -7,7 +7,7 @@
 <body>
 <script>
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } );
     assert_equals(policy.name, 'SomeName');
   }, "CSP supports wildcards.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP.tentative.html
index 1293493..0b57c3a 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-CSP.tentative.html
@@ -8,20 +8,20 @@
 <script>
   // Whitelisted name test
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } );
     assert_equals(policy.name, 'SomeName');
   }, "Whitelisted policy creation works.");
 
   // Another whitelisted name test
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('JustOneMoreName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('JustOneMoreName', { createHTML: s => s } );
     assert_equals(policy.name, 'JustOneMoreName');
   }, "Another whitelisted policy creation works.");
 
   // Non-whitelisted names test
   test(t => {
     assert_throws(new TypeError(), _ => {
-     window.TrustedTypes.createPolicy('SomeOtherName', { createURL: s => s } );
+     window.trustedTypes.createPolicy('SomeOtherName', { createURL: s => s } );
     });
   }, "Non-whitelisted policy creation throws.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-createXXX.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-createXXX.tentative.html
index a078af4b..73ed8c7 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-createXXX.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-createXXX.tentative.html
@@ -7,14 +7,14 @@
 <body>
 <script>
   test(t => {
-    const p1 = TrustedTypes.createPolicy("policyHTMLAndScript", {
+    const p1 = trustedTypes.createPolicy("policyHTMLAndScript", {
       createHTML: s => s,
       createScript: s => s
     });
     assert_throws(new TypeError(), _ => { p1.createScriptURL("foo"); });
     assert_throws(new TypeError(), _ => { p1.createURL("foo"); });
 
-    const p2 = TrustedTypes.createPolicy("policyURLAndScriptURL", {
+    const p2 = trustedTypes.createPolicy("policyURLAndScriptURL", {
       createURL: s => s,
       createScriptURL: s => s
     });
@@ -29,7 +29,7 @@
       createURL: (s) => s,
       createScript: (s) => s,
     };
-    policy = TrustedTypes.createPolicy(Math.random(), noopPolicy, true);
+    policy = trustedTypes.createPolicy(Math.random(), noopPolicy, true);
     let el = document.createElement("div");
 
     el.title = policy.createHTML(INPUTS.URL);
@@ -40,12 +40,12 @@
   }, "Attributes without type constraints will work as before.");
 
   test(t => {
-    const policy = TrustedTypes.createPolicy("nullpolicy", null);
+    const policy = trustedTypes.createPolicy("nullpolicy", null);
     assert_throws(new TypeError(), _ => { policy.createScriptURL("foo"); });
     assert_throws(new TypeError(), _ => { policy.createURL("foo"); });
     assert_throws(new TypeError(), _ => { policy.createHTML("foo"); });
     assert_throws(new TypeError(), _ => { policy.createScript("foo"); });
-  }, "TrustedTypes.createPolicy(.., null) creates empty policy.");
+  }, "trustedTypes.createPolicy(.., null) creates empty policy.");
 
 
   // testCases contains a list of policy functions and expected results (when
@@ -85,7 +85,7 @@
     return function(name, fn) {
       let options = {};
       options[trustedMethodName] = fn;
-      let policy = window.TrustedTypes.createPolicy(name, options);
+      let policy = window.trustedTypes.createPolicy(name, options);
       let result = policy[trustedMethodName](defaultArg);
       assert_true(result instanceof trustedType);
       return result;
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-name.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-name.tentative.html
index c121fe4..4b7c30c 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-name.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicy-name.tentative.html
@@ -8,7 +8,7 @@
 <script>
   // Policy name test
   test(t => {
-    let policy = TrustedTypes.createPolicy('hidden', { createHTML: s => s }, false );
+    let policy = trustedTypes.createPolicy('hidden', { createHTML: s => s }, false );
     assert_true(policy instanceof TrustedTypePolicy);
     assert_equals(policy.name, 'hidden');
   }, "policy.name = name");
@@ -16,14 +16,14 @@
   // Duplicate names test
   test(t => {
     assert_throws(new TypeError(), _ => {
-      TrustedTypes.createPolicy('hidden', { createURL: s => s } );
+      trustedTypes.createPolicy('hidden', { createURL: s => s } );
     });
   }, "duplicate policy name attempt throws");
 
   // Retrieve policy names tests
   test(t => {
-    let policy = TrustedTypes.createPolicy('exposed', { createURL: s => s }, true );
-    let names = TrustedTypes.getPolicyNames();
+    let policy = trustedTypes.createPolicy('exposed', { createURL: s => s }, true );
+    let names = trustedTypes.getPolicyNames();
     assert_equals(names.length, 2);
     assert_true(names.includes('hidden'));
     assert_true(names.includes('exposed'));
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-constants.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-constants.tentative.html
index f164e79..001a6ce 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-constants.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-constants.tentative.html
@@ -7,20 +7,20 @@
 <body>
 <script>
   test(t => {
-    const empty = TrustedTypes.emptyHTML;
-    assert_true(TrustedTypes.isHTML(empty));
+    const empty = trustedTypes.emptyHTML;
+    assert_true(trustedTypes.isHTML(empty));
     assert_equals(empty.toString(), "");
-  }, 'TrustedTypes.emptyHTML returns the intended value.');
+  }, 'trustedTypes.emptyHTML returns the intended value.');
 
   test(t => {
-    try { TrustedTypes.emptyHTML = 'fake'; } catch { }
-    assert_true(TrustedTypes.isHTML(TrustedTypes.emptyHTML));
-    assert_equals(TrustedTypes.emptyHTML.toString(), "");
-  }, 'TrustedTypes.emptyHTML cannot be redefined.');
+    try { trustedTypes.emptyHTML = 'fake'; } catch { }
+    assert_true(trustedTypes.isHTML(trustedTypes.emptyHTML));
+    assert_equals(trustedTypes.emptyHTML.toString(), "");
+  }, 'trustedTypes.emptyHTML cannot be redefined.');
 
   test(t => {
     try { Object.defineProperty(TrustedTypes, 'emptyHTML', 'fake'); } catch { }
-    assert_true(TrustedTypes.isHTML(TrustedTypes.emptyHTML));
-    assert_equals(TrustedTypes.emptyHTML.toString(), "");
-  }, 'TrustedTypes.emptyHTML cannot be redefined via defineProperty.');
+    assert_true(trustedTypes.isHTML(trustedTypes.emptyHTML));
+    assert_equals(trustedTypes.emptyHTML.toString(), "");
+  }, 'trustedTypes.emptyHTML cannot be redefined via defineProperty.');
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-createXYZTests.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-createXYZTests.tentative.html
index 8cdc837..05c7301 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-createXYZTests.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-createXYZTests.tentative.html
@@ -6,10 +6,10 @@
 <script>
   //HTML tests
   function createHTMLTest(policyName, policy, expectedHTML, t) {
-    let p = window.TrustedTypes.createPolicy(policyName, policy);
+    let p = window.trustedTypes.createPolicy(policyName, policy);
     let html = p.createHTML('whatever');
     assert_true(html instanceof TrustedHTML);
-    assert_true(TrustedTypes.isHTML(html));
+    assert_true(trustedTypes.isHTML(html));
     assert_equals(html + "", expectedHTML);
   }
 
@@ -33,7 +33,7 @@
   }, "html = identity function, global string changed");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyHTML5', { createHTML: s => { throw new Error(); }});
+    let p = window.trustedTypes.createPolicy('TestPolicyHTML5', { createHTML: s => { throw new Error(); }});
     assert_throws(new Error(), _ => {
       p.createHTML('whatever');
     });
@@ -57,14 +57,14 @@
   }, "html = this without bind");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyHTML8', null);
+    let p = window.trustedTypes.createPolicy('TestPolicyHTML8', null);
     assert_throws(new TypeError(), _ => {
       p.createHTML('whatever');
     });
   }, "html - calling undefined callback throws");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyHTML9', { createHTML: createHTMLJS });
+    let p = window.trustedTypes.createPolicy('TestPolicyHTML9', { createHTML: createHTMLJS });
     assert_throws(new TypeError(), _ => {
       p.createScript(INPUTS.SCRIPT);
     });
@@ -78,10 +78,10 @@
 
   //Script tests
   function createScriptTest(policyName, policy, expectedScript, t) {
-    let p = window.TrustedTypes.createPolicy(policyName, policy);
+    let p = window.trustedTypes.createPolicy(policyName, policy);
     let script = p.createScript('whatever');
     assert_true(script instanceof TrustedScript);
-    assert_true(TrustedTypes.isScript(script));
+    assert_true(trustedTypes.isScript(script));
     assert_equals(script + "", expectedScript);
   }
 
@@ -105,7 +105,7 @@
   }, "script = identity function, global string changed");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyScript5', {
+    let p = window.trustedTypes.createPolicy('TestPolicyScript5', {
       createScript: s => { throw new Error(); }
     });
     assert_throws(new Error(), _ => {
@@ -131,14 +131,14 @@
   }, "script = this without bind");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyScript8', null);
+    let p = window.trustedTypes.createPolicy('TestPolicyScript8', null);
     assert_throws(new TypeError(), _ => {
       p.createScript('whatever');
     });
   }, "script - calling undefined callback throws");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyScript9', { createScript: createScriptJS });
+    let p = window.trustedTypes.createPolicy('TestPolicyScript9', { createScript: createScriptJS });
     assert_throws(new TypeError(), _ => {
       p.createHTML(INPUTS.HTML);
     });
@@ -153,10 +153,10 @@
 
   //ScriptURL tests
   function createScriptURLTest(policyName, policy, expectedScriptURL, t) {
-    let p = window.TrustedTypes.createPolicy(policyName, policy);
+    let p = window.trustedTypes.createPolicy(policyName, policy);
     let scriptUrl = p.createScriptURL(INPUTS.SCRIPTURL);
     assert_true(scriptUrl instanceof TrustedScriptURL);
-    assert_true(TrustedTypes.isScriptURL(scriptUrl));
+    assert_true(trustedTypes.isScriptURL(scriptUrl));
     assert_equals(scriptUrl + "", expectedScriptURL);
   }
 
@@ -180,7 +180,7 @@
   }, "script_url = identity function, global string changed");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyScriptURL5', {
+    let p = window.trustedTypes.createPolicy('TestPolicyScriptURL5', {
       createScriptURL: s => { throw new Error(); }
     });
     assert_throws(new Error(), _ => {
@@ -206,14 +206,14 @@
   }, "script_url = this without bind");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyScriptURL8', null);
+    let p = window.trustedTypes.createPolicy('TestPolicyScriptURL8', null);
     assert_throws(new TypeError(), _ => {
       p.createScriptURL(INPUTS.SCRIPTURL);
     });
   }, "script_url - calling undefined callback throws");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyScriptURL9', { createScriptURL: createScriptURLJS });
+    let p = window.trustedTypes.createPolicy('TestPolicyScriptURL9', { createScriptURL: createScriptURLJS });
     assert_throws(new TypeError(), _ => {
       p.createHTML(INPUTS.HTML);
     });
@@ -228,10 +228,10 @@
 
   //URL tests
   function createURLTest(policyName, policy, expectedURL, t) {
-    let p = window.TrustedTypes.createPolicy(policyName, policy);
+    let p = window.trustedTypes.createPolicy(policyName, policy);
     let url = p.createURL(INPUTS.URL);
     assert_true(url instanceof TrustedURL);
-    assert_true(TrustedTypes.isURL(url));
+    assert_true(trustedTypes.isURL(url));
     assert_equals(url + "", expectedURL);
   }
 
@@ -255,7 +255,7 @@
   }, "url = identity function, global string changed");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyURL5', {
+    let p = window.trustedTypes.createPolicy('TestPolicyURL5', {
       createURL: s => { throw new Error(); }
     });
     assert_throws(new Error(), _ => {
@@ -281,14 +281,14 @@
   }, "url = this without bind");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyURL8', null);
+    let p = window.trustedTypes.createPolicy('TestPolicyURL8', null);
     assert_throws(new TypeError(), _ => {
       p.createURL(INPUTS.URL);
     });
   }, "url - calling undefined callback throws");
 
   test(t => {
-    let p = window.TrustedTypes.createPolicy('TestPolicyURL9', { createURL: createURLJS });
+    let p = window.trustedTypes.createPolicy('TestPolicyURL9', { createURL: createURLJS });
     assert_throws(new TypeError(), _ => {
       p.createHTML(INPUTS.HTML);
     });
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-noNamesGiven.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-noNamesGiven.tentative.html
index 2fc8f01..cec1bfb6 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-noNamesGiven.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-noNamesGiven.tentative.html
@@ -8,7 +8,7 @@
   //No name given test
   test(t => {
     assert_throws(new TypeError(), _ => {
-      window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } );
+      window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } );
     });
   }, "No name list given - policy creation throws");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-wildcard.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-wildcard.tentative.html
index 7edc64b..f1b5f27 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-wildcard.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests-wildcard.tentative.html
@@ -7,7 +7,7 @@
 <script>
   //No name given test
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } );
     assert_equals(policy.name, 'SomeName');
   }, "Wildcard given - policy creation works");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.tentative.html
index 1293493..0b57c3a 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-cspTests.tentative.html
@@ -8,20 +8,20 @@
 <script>
   // Whitelisted name test
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } );
     assert_equals(policy.name, 'SomeName');
   }, "Whitelisted policy creation works.");
 
   // Another whitelisted name test
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('JustOneMoreName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('JustOneMoreName', { createHTML: s => s } );
     assert_equals(policy.name, 'JustOneMoreName');
   }, "Another whitelisted policy creation works.");
 
   // Non-whitelisted names test
   test(t => {
     assert_throws(new TypeError(), _ => {
-     window.TrustedTypes.createPolicy('SomeOtherName', { createURL: s => s } );
+     window.trustedTypes.createPolicy('SomeOtherName', { createURL: s => s } );
     });
   }, "Non-whitelisted policy creation throws.");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-nameTests.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-nameTests.tentative.html
index 6d43e0b..e0aa537 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-nameTests.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-createPolicy-nameTests.tentative.html
@@ -6,7 +6,7 @@
 <script>
   //Policy name test
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('SomeName', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('SomeName', { createHTML: s => s } );
     assert_true(policy instanceof TrustedTypePolicy);
     assert_equals(policy.name, 'SomeName');
   }, "policy.name = name");
@@ -14,14 +14,14 @@
   //Duplicate names test
   test(t => {
     assert_throws(new TypeError(), _ => {
-     window.TrustedTypes.createPolicy('SomeName', { createURL: s => s } );
+     window.trustedTypes.createPolicy('SomeName', { createURL: s => s } );
     });
   }, "duplicate policy name attempt throws");
 
   //Retrieve policy names tests
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('SomeOtherName', { createURL: s => s } );
-    let names = window.TrustedTypes.getPolicyNames();
+    let policy = window.trustedTypes.createPolicy('SomeOtherName', { createURL: s => s } );
+    let names = window.trustedTypes.getPolicyNames();
     assert_true(names.includes('SomeName'));
     assert_true(names.includes('SomeOtherName'));
   }, "Retrieving policy names");
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-defaultPolicy.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-defaultPolicy.tentative.html
index ea00566..7ac09d8b 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-defaultPolicy.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-defaultPolicy.tentative.html
@@ -5,20 +5,20 @@
 <body>
 <script>
   test(t => {
-    assert_equals(window.TrustedTypes.defaultPolicy, null);
+    assert_equals(window.trustedTypes.defaultPolicy, null);
   }, "defaultPolicy with no default created is not an error");
 
   test(t => {
-    let policy = window.TrustedTypes.createPolicy('default', { createHTML: s => s } );
+    let policy = window.trustedTypes.createPolicy('default', { createHTML: s => s } );
     assert_true(policy instanceof TrustedTypePolicy);
-    assert_equals(policy, window.TrustedTypes.defaultPolicy);
+    assert_equals(policy, window.trustedTypes.defaultPolicy);
   }, "defaultPolicy returns the correct default policy");
 
   test(t => {
-    let foo_policy = window.TrustedTypes.createPolicy('foo', { createHTML: s => s } );
-    let default_policy = window.TrustedTypes.defaultPolicy;
-    window.TrustedTypes.defaultPolicy = foo_policy;
-    assert_equals(window.TrustedTypes.defaultPolicy, default_policy);
-    assert_not_equals(window.TrustedTypes.defaultPolicy, foo_policy);
+    let foo_policy = window.trustedTypes.createPolicy('foo', { createHTML: s => s } );
+    let default_policy = window.trustedTypes.defaultPolicy;
+    window.trustedTypes.defaultPolicy = foo_policy;
+    assert_equals(window.trustedTypes.defaultPolicy, default_policy);
+    assert_not_equals(window.trustedTypes.defaultPolicy, foo_policy);
   }, "defaultPolicy is a read-only property");
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-getPropertyType.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-getPropertyType.tentative.html
index 90fc7d556..f9ba8f27 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-getPropertyType.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-getPropertyType.tentative.html
@@ -8,77 +8,77 @@
 <div id="target"></div>
 <script>
   test(t => {
-    assert_equals(TrustedTypes.getPropertyType("a", "href"), "TrustedURL");
-    assert_equals(TrustedTypes.getPropertyType("a", "id"), null);
-    assert_equals(TrustedTypes.getPropertyType("a", "b"), null);
-  }, "sanity check TrustedTypes.getPropertyType for the HTML a element.");
+    assert_equals(trustedTypes.getPropertyType("a", "href"), "TrustedURL");
+    assert_equals(trustedTypes.getPropertyType("a", "id"), null);
+    assert_equals(trustedTypes.getPropertyType("a", "b"), null);
+  }, "sanity check trustedTypes.getPropertyType for the HTML a element.");
 
   test(t => {
-    assert_equals(TrustedTypes.getAttributeType("img", "onerror"), "TrustedScript");
-    assert_equals(TrustedTypes.getAttributeType("img", "width"), null);
-    assert_equals(TrustedTypes.getAttributeType("img", "madeup"), null);
-  }, "sanity check TrustedTypes.getAttributeType.");
+    assert_equals(trustedTypes.getAttributeType("img", "onerror"), "TrustedScript");
+    assert_equals(trustedTypes.getAttributeType("img", "width"), null);
+    assert_equals(trustedTypes.getAttributeType("img", "madeup"), null);
+  }, "sanity check trustedTypes.getAttributeType.");
 
   test(t => {
-    assert_true(!!TrustedTypes.getTypeMapping());
-  }, "sanity check TrustedTypes.getTypeMapping");
+    assert_true(!!trustedTypes.getTypeMapping());
+  }, "sanity check trustedTypes.getTypeMapping");
 
 
   // getPropertyType tests adapted from WICG/trusted-types polyfill:
   test(t => {
     // returns the proper type for attribute-related properties
-    assert_equals(TrustedTypes.getPropertyType("script", "src"), "TrustedScriptURL");
-    assert_equals(TrustedTypes.getPropertyType("img", "src"), "TrustedURL");
+    assert_equals(trustedTypes.getPropertyType("script", "src"), "TrustedScriptURL");
+    assert_equals(trustedTypes.getPropertyType("img", "src"), "TrustedURL");
 
     // is case insensitive for tag names
-    assert_equals(TrustedTypes.getPropertyType("SCRIPT", "src"), "TrustedScriptURL");
-    assert_equals(TrustedTypes.getPropertyType("ImG", "src"), "TrustedURL");
+    assert_equals(trustedTypes.getPropertyType("SCRIPT", "src"), "TrustedScriptURL");
+    assert_equals(trustedTypes.getPropertyType("ImG", "src"), "TrustedURL");
 
     // is case sensitive for property names
-    assert_equals(TrustedTypes.getPropertyType("script", "sRc"), null);
-    assert_equals(TrustedTypes.getPropertyType("div", "innerhtml"), null);
+    assert_equals(trustedTypes.getPropertyType("script", "sRc"), null);
+    assert_equals(trustedTypes.getPropertyType("div", "innerhtml"), null);
 
     // returns the proper type for innerHTML
-    assert_equals(TrustedTypes.getPropertyType("div", "innerHTML"), "TrustedHTML");
+    assert_equals(trustedTypes.getPropertyType("div", "innerHTML"), "TrustedHTML");
 
     // returns the proper type for outerHTML
-    assert_equals(TrustedTypes.getPropertyType("div", "outerHTML"), "TrustedHTML");
+    assert_equals(trustedTypes.getPropertyType("div", "outerHTML"), "TrustedHTML");
 
     // returns the proper type for script.prop
     ["text", "innerText", "textContent"].forEach((prop) => {
-      assert_equals(TrustedTypes.getPropertyType("script", prop), "TrustedScript");
+      assert_equals(trustedTypes.getPropertyType("script", prop), "TrustedScript");
     });
   }, "getPropertyType tests adapted from WICG/trusted-types polyfill");
 
   test(t => {
     // returns the proper type
-    assert_equals(TrustedTypes.getAttributeType('script', 'src'), 'TrustedScriptURL');
-    assert_equals(TrustedTypes.getAttributeType('img', 'src'), 'TrustedURL');
+    assert_equals(trustedTypes.getAttributeType('script', 'src'), 'TrustedScriptURL');
+    assert_equals(trustedTypes.getAttributeType('img', 'src'), 'TrustedURL');
 
     // ignores attributes from unknown namespaces
-    assert_equals(TrustedTypes.getAttributeType(
+    assert_equals(trustedTypes.getAttributeType(
           'a', 'href', '', 'http://foo.bar'), null);
 
     // is case insensitive for element names
-    assert_equals(TrustedTypes.getAttributeType('SCRIPT', 'src'), 'TrustedScriptURL');
-    assert_equals(TrustedTypes.getAttributeType('imG', 'src'), 'TrustedURL');
+    assert_equals(trustedTypes.getAttributeType('SCRIPT', 'src'), 'TrustedScriptURL');
+    assert_equals(trustedTypes.getAttributeType('imG', 'src'), 'TrustedURL');
 
     // is case insensitive for the attribute names
-    assert_equals(TrustedTypes.getAttributeType('script', 'SRC'), 'TrustedScriptURL');
-    assert_equals(TrustedTypes.getAttributeType('imG', 'srC'), 'TrustedURL');
+    assert_equals(trustedTypes.getAttributeType('script', 'SRC'), 'TrustedScriptURL');
+    assert_equals(trustedTypes.getAttributeType('imG', 'srC'), 'TrustedURL');
 
     // supports the inline event handlers
-    assert_equals(TrustedTypes.getAttributeType('img', 'onerror'), 'TrustedScript');
-    assert_equals(TrustedTypes.getAttributeType('unknown', 'onerror'), 'TrustedScript');
+    assert_equals(trustedTypes.getAttributeType('img', 'onerror'), 'TrustedScript');
+    assert_equals(trustedTypes.getAttributeType('unknown', 'onerror'), 'TrustedScript');
 
     // defaults to undefined
-    assert_equals(TrustedTypes.getAttributeType('unknown', 'src'), null);
-    assert_equals(TrustedTypes.getAttributeType('img', 'bar'), null);
+    assert_equals(trustedTypes.getAttributeType('unknown', 'src'), null);
+    assert_equals(trustedTypes.getAttributeType('img', 'bar'), null);
   }, "getAttributeType tests adapted from WICG/trusted-types polyfill");
 
 
   test(t=> {
-    const map = TrustedTypes.getTypeMapping();
+    const map = trustedTypes.getTypeMapping();
 
     // Spot testing some values.
     assert_equals(map["script"].attributes.src, "TrustedScriptURL");
@@ -89,10 +89,10 @@
     // getTypeMapping returns a 'clean' object, in case the return value has
     // been modified.
     map["*"].attributes["foo"] = "bar";
-    assert_equals(TrustedTypes.getTypeMapping()["*"].attributes["foo"], undefined);
+    assert_equals(trustedTypes.getTypeMapping()["*"].attributes["foo"], undefined);
 ;
     // Unknown namespaces:
-    assert_equals(TrustedTypes.getTypeMapping("http://foo/bar"), undefined);
+    assert_equals(trustedTypes.getTypeMapping("http://foo/bar"), undefined);
   }, "getTypeMapping tests adapted from WICG/trusted-types polyfill");
 
   // Test case handling for both attributes and properties.
@@ -100,12 +100,12 @@
     for (let elem of ["object", "OBJECT", "oBjEcT"]) {
       test(t => {
         // attributes are case-insensitive, so all variants should be defined.
-        assert_true(TrustedTypes.getAttributeType(elem, attr) != undefined);
+        assert_true(trustedTypes.getAttributeType(elem, attr) != undefined);
       }, `${elem}[${attr}] is defined`);
       test(t => {
         // properties are case-sensitive, so only the "correct" spelling
         // should be defined.
-        assert_equals(TrustedTypes.getPropertyType(elem, attr) != undefined,
+        assert_equals(trustedTypes.getPropertyType(elem, attr) != undefined,
                       attr == "codeBase");
       }, `${elem}.${attr} is maybe defined`);
     }
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-isXXX.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-isXXX.tentative.html
index 854f69ed..efaac5d 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-isXXX.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-isXXX.tentative.html
@@ -16,94 +16,94 @@
 
   // isHTML tests
   test(t => {
-    const p = TrustedTypes.createPolicy('html', noopPolicy);
+    const p = trustedTypes.createPolicy('html', noopPolicy);
     let html = p.createHTML(INPUTS.HTML);
 
-    assert_true(TrustedTypes.isHTML(html));
+    assert_true(trustedTypes.isHTML(html));
     let html2 = Object.create(html);
 
     // instanceof can pass, but we rely on isHTML
     assert_true(html2 instanceof TrustedHTML);
-    assert_false(TrustedTypes.isHTML(html2));
+    assert_false(trustedTypes.isHTML(html2));
 
     let html3 = Object.assign({}, html, {toString: () => 'fake'});
 
-    assert_false(TrustedTypes.isHTML(html3));
+    assert_false(trustedTypes.isHTML(html3));
   }, 'TrustedTypePolicyFactory.isHTML requires the object to be created via policy.');
 
   // isScript tests
   test(t => {
-    const p = TrustedTypes.createPolicy('script', noopPolicy);
+    const p = trustedTypes.createPolicy('script', noopPolicy);
     let script = p.createScript(INPUTS.SCRIPT);
 
-    assert_true(TrustedTypes.isScript(script));
+    assert_true(trustedTypes.isScript(script));
     let script2 = Object.create(script);
 
     // instanceof can pass, but we rely on isScript
     assert_true(script2 instanceof TrustedScript);
-    assert_false(TrustedTypes.isScript(script2));
+    assert_false(trustedTypes.isScript(script2));
 
     let script3 = Object.assign({}, script, {toString: () => 'fake'});
 
-    assert_false(TrustedTypes.isScript(script3));
+    assert_false(trustedTypes.isScript(script3));
   }, 'TrustedTypePolicyFactory.isScript requires the object to be created via policy.');
 
   // isScriptURL tests
   test(t => {
-    const p = TrustedTypes.createPolicy('script_url', noopPolicy);
+    const p = trustedTypes.createPolicy('script_url', noopPolicy);
     let script = p.createScriptURL(INPUTS.SCRIPTURL);
 
-    assert_true(TrustedTypes.isScriptURL(script));
+    assert_true(trustedTypes.isScriptURL(script));
     let script2 = Object.create(script);
 
     // instanceof can pass, but we rely on isScript
     assert_true(script2 instanceof TrustedScriptURL);
-    assert_false(TrustedTypes.isScriptURL(script2));
+    assert_false(trustedTypes.isScriptURL(script2));
 
     let script3 = Object.assign({}, script, {toString: () => 'fake'});
 
-    assert_false(TrustedTypes.isScriptURL(script3));
+    assert_false(trustedTypes.isScriptURL(script3));
   }, 'TrustedTypePolicyFactory.isScriptURL requires the object to be created via policy.');
 
   // isURL tests
   test(t => {
-    const p = TrustedTypes.createPolicy('url', noopPolicy);
+    const p = trustedTypes.createPolicy('url', noopPolicy);
     let url = p.createURL(INPUTS.URL);
 
-    assert_true(TrustedTypes.isURL(url));
+    assert_true(trustedTypes.isURL(url));
     let url2 = Object.create(url);
 
     // instanceof can pass, but we rely on isScript
     assert_true(url2 instanceof TrustedURL);
-    assert_false(TrustedTypes.isURL(url2));
+    assert_false(trustedTypes.isURL(url2));
 
     let url3 = Object.assign({}, url, {toString: () => 'fake'});
 
-    assert_false(TrustedTypes.isURL(url3));
+    assert_false(trustedTypes.isURL(url3));
   }, 'TrustedTypePolicyFactory.isURL requires the object to be created via policy.');
 
   // Test non-object parameters.
   test(t => {
-    assert_false(TrustedTypes.isHTML(null));
-    assert_false(TrustedTypes.isHTML(123));
-    assert_false(TrustedTypes.isHTML(0.5));
-    assert_false(TrustedTypes.isHTML('test'));
-    assert_false(TrustedTypes.isHTML({}));
-    assert_false(TrustedTypes.isScript(null));
-    assert_false(TrustedTypes.isScript(123));
-    assert_false(TrustedTypes.isScript(0.5));
-    assert_false(TrustedTypes.isScript('test'));
-    assert_false(TrustedTypes.isScript({}));
-    assert_false(TrustedTypes.isURL(null));
-    assert_false(TrustedTypes.isURL(123));
-    assert_false(TrustedTypes.isURL(0.5));
-    assert_false(TrustedTypes.isURL('test'));
-    assert_false(TrustedTypes.isURL({}));
-    assert_false(TrustedTypes.isScriptURL(null));
-    assert_false(TrustedTypes.isScriptURL(123));
-    assert_false(TrustedTypes.isScriptURL(0.5));
-    assert_false(TrustedTypes.isScriptURL('test'));
-    assert_false(TrustedTypes.isScriptURL({}));
+    assert_false(trustedTypes.isHTML(null));
+    assert_false(trustedTypes.isHTML(123));
+    assert_false(trustedTypes.isHTML(0.5));
+    assert_false(trustedTypes.isHTML('test'));
+    assert_false(trustedTypes.isHTML({}));
+    assert_false(trustedTypes.isScript(null));
+    assert_false(trustedTypes.isScript(123));
+    assert_false(trustedTypes.isScript(0.5));
+    assert_false(trustedTypes.isScript('test'));
+    assert_false(trustedTypes.isScript({}));
+    assert_false(trustedTypes.isURL(null));
+    assert_false(trustedTypes.isURL(123));
+    assert_false(trustedTypes.isURL(0.5));
+    assert_false(trustedTypes.isURL('test'));
+    assert_false(trustedTypes.isURL({}));
+    assert_false(trustedTypes.isScriptURL(null));
+    assert_false(trustedTypes.isScriptURL(123));
+    assert_false(trustedTypes.isScriptURL(0.5));
+    assert_false(trustedTypes.isScriptURL('test'));
+    assert_false(trustedTypes.isScriptURL({}));
   }, 'TrustedTypePolicyFactory.isXXX should accept anything without throwing.');
 
   // Redefinition tests, assign to property.
@@ -112,44 +112,44 @@
   //  what [Unforgeable] does. Hence, the tests use try {..} catch {} to cover
   //  both situationsm rather than expect_throws(...).)
   test(t => {
-    try { TrustedTypes.isHTML = () => 'fake'; } catch { }
-    assert_false(TrustedTypes.isHTML({}));
+    try { trustedTypes.isHTML = () => 'fake'; } catch { }
+    assert_false(trustedTypes.isHTML({}));
   }, 'TrustedTypePolicyFactory.IsHTML cannot be redefined.');
 
   test(t => {
-    try { TrustedTypes.isScript = () => 'fake'; } catch { }
-    assert_false(TrustedTypes.isScript({}));
+    try { trustedTypes.isScript = () => 'fake'; } catch { }
+    assert_false(trustedTypes.isScript({}));
   }, 'TrustedTypePolicyFactory.isScript cannot be redefined.');
 
   test(t => {
-    try { TrustedTypes.isScriptURL = () => 'fake'; } catch { }
-    assert_false(TrustedTypes.isScriptURL({}));
+    try { trustedTypes.isScriptURL = () => 'fake'; } catch { }
+    assert_false(trustedTypes.isScriptURL({}));
   }, 'TrustedTypePolicyFactory.isScriptURL cannot be redefined.');
 
   test(t => {
-    try { TrustedTypes.isURL = () => 'fake'; } catch { }
-    assert_false(TrustedTypes.isURL({}));
+    try { trustedTypes.isURL = () => 'fake'; } catch { }
+    assert_false(trustedTypes.isURL({}));
   }, 'TrustedTypePolicyFactory.isURL cannot be redefined.');
 
   // Redefinition tests, via Object.defineProperty.
   test(t => {
     try { Object.defineProperty(TrustedTypes, 'isHTML', () => 'fake'); } catch { }
-    assert_false(TrustedTypes.isHTML({}));
+    assert_false(trustedTypes.isHTML({}));
   }, 'TrustedTypePolicyFactory.IsHTML cannot be redefined via defineProperty.');
 
   test(t => {
     try { Object.defineProperty(TrustedTypes, 'isScript', () => 'fake'); } catch { }
-    assert_false(TrustedTypes.isScript({}));
+    assert_false(trustedTypes.isScript({}));
   }, 'TrustedTypePolicyFactory.isScript cannot be redefined via definePropert.');
 
   test(t => {
     try { Object.defineProperty(TrustedTypes, 'isScriptURL', () => 'fake'); } catch { }
-    assert_false(TrustedTypes.isScriptURL({}));
+    assert_false(trustedTypes.isScriptURL({}));
   }, 'TrustedTypePolicyFactory.isScriptURL cannot be redefined via definePropert.');
 
   test(t => {
     try { Object.defineProperty(TrustedTypes, 'isURL', () => 'fake'); } catch { }
-    assert_false(TrustedTypes.isURL({}));
+    assert_false(trustedTypes.isURL({}));
   }, 'TrustedTypePolicyFactory.isURL cannot be redefined via definePropert.');
 
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-metadata.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-metadata.tentative.html
index 694e4d2..70f77b1b 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-metadata.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/TrustedTypePolicyFactory-metadata.tentative.html
@@ -9,7 +9,7 @@
 <div id="target"></div>
 <script>
 
-  const policy = TrustedTypes.createPolicy("anythinggoes", {
+  const policy = trustedTypes.createPolicy("anythinggoes", {
     "createHTML": x => x,
     "createScript": x => x,
     "createURL": x => x,
@@ -41,7 +41,7 @@
   // so we'll get decent error messages when it might fail.
   test(t => {
     // Collect all element and property names from getTypeMapping().
-    const map = TrustedTypes.getTypeMapping();
+    const map = trustedTypes.getTypeMapping();
     for (let elem in map) {
       elements.push(elem);
       properties = properties.concat(Object.keys(map[elem].properties));
@@ -91,7 +91,7 @@
         test(t => {
           const element = target.appendChild(document.createElement(elem));
           t.add_cleanup(_ => element.remove());
-          const expected_type = TrustedTypes.getPropertyType(elem, property);
+          const expected_type = trustedTypes.getPropertyType(elem, property);
           const value = create_value[type];
           const test_fn = _ => { element[property] = value; };
           if (type == expected_type || !expected_type) {
@@ -129,7 +129,7 @@
         test(t => {
           const element = target.appendChild(document.createElement(elem));
           t.add_cleanup(_ => element.remove());
-          const expected_type = TrustedTypes.getAttributeType(elem, property);
+          const expected_type = trustedTypes.getAttributeType(elem, property);
           const value = create_value[type];
           const test_fn = _ => { element.setAttribute(property, value); };
           if (type == expected_type || !expected_type) {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/Window-TrustedTypes.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/Window-TrustedTypes.tentative.html
index 5bbb435..c61d9207 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/Window-TrustedTypes.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/Window-TrustedTypes.tentative.html
@@ -5,9 +5,9 @@
 <body>
 <script>
   test(t => {
-    let factory = window.TrustedTypes;
+    let factory = window.trustedTypes;
     assert_true(factory instanceof TrustedTypePolicyFactory);
-  }, "factory = window.TrustedTypes");
+  }, "factory = window.trustedTypes");
 
   test(t => {
     assert_throws(new TypeError(), _ => {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-importScripts.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-importScripts.https.html
index 6ae5263..9dbfd7b 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-importScripts.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/WorkerGlobalScope-importScripts.https.html
@@ -18,7 +18,7 @@
 // For the same reason we cannot use the otherwise preferred 'META: workers'
 // tag, since that test setup would be blocked as soon as trusted types
 // enforcement is enabled.
-const test_setup_policy = TrustedTypes.createPolicy("hurrayanythinggoes", {
+const test_setup_policy = trustedTypes.createPolicy("hurrayanythinggoes", {
   createScriptURL: x => x});
 const test_url =
   test_setup_policy.createScriptURL("support/WorkerGlobalScope-importScripts.https.js");
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-Node-multiple-arguments.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-Node-multiple-arguments.tentative.html
index f84998d4..5552e13 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-Node-multiple-arguments.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-Node-multiple-arguments.tentative.html
@@ -10,7 +10,7 @@
 <div id="container"></div>
 <script>
   const container = document.querySelector("#container");
-  const policy = window.TrustedTypes.createPolicy("policy", {
+  const policy = window.trustedTypes.createPolicy("policy", {
     createScript: t => t,
   });
   function stringify(arg) {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html
index 4446a58..82e3120 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMParser-parseFromString.tentative.html
@@ -33,7 +33,7 @@
 
   // After default policy creation string assignment implicitly calls createHTML.
   test(t => {
-    let p = window.TrustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
+    let p = window.trustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
     let parser = new DOMParser();
     let doc = parser.parseFromString(INPUTS.HTML, "text/html");
     assert_equals(doc.body.innerText, RESULTS.HTML);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html
index 2554ce6..468ed7b 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-DOMWindowTimers-setTimeout-setInterval.tentative.html
@@ -56,7 +56,7 @@
 
   // After default policy creation string assignment implicitly calls createScript.
   test(t => {
-    let policy = window.TrustedTypes.createPolicy("default", { createScript: createScriptJS }, true);
+    let policy = window.trustedTypes.createPolicy("default", { createScript: createScriptJS }, true);
     setTimeout(INPUTS.SCRIPT);
     setInterval(INPUTS.SCRIPT);
   }, "`setTimeout(string)`, `setInterval(string)` via default policy (successful Script transformation).");
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Document-write.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Document-write.tentative.html
index 845df47..4defb56 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Document-write.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Document-write.tentative.html
@@ -62,7 +62,7 @@
     assert_equals(document.body.innerText, old);
   }, "`document.writeln(null)` throws");
 
-  let default_policy = TrustedTypes.createPolicy('default',
+  let default_policy = trustedTypes.createPolicy('default',
       { createHTML: createHTMLJS }, true );
 
   // Default policy works.
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.tentative.html
index ae1ace69..37a73f08 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-insertAdjacentHTML.tentative.html
@@ -93,7 +93,7 @@
 
   // After default policy creation string assignment implicitly calls createHTML.
   test(t => {
-    let p = window.TrustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
+    let p = window.trustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
 
     var d = document.createElement('div');
     container.appendChild(d);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-outerHTML.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-outerHTML.tentative.html
index 945e3dd..8f314a26 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-outerHTML.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-outerHTML.tentative.html
@@ -52,7 +52,7 @@
 
   // After default policy creation string assignment implicitly calls createHTML.
   test(t => {
-    let p = window.TrustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
+    let p = window.trustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
 
     var d = document.createElement('div');
     document.querySelector('#container').appendChild(d);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttribute.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttribute.tentative.html
index d77d9da..3cae5d29 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttribute.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Element-setAttribute.tentative.html
@@ -9,7 +9,7 @@
 </head>
 <body>
 <script>
-  const nullPolicy = TrustedTypes.createPolicy('NullPolicy', {createScript: s => s});
+  const nullPolicy = trustedTypes.createPolicy('NullPolicy', {createScript: s => s});
 
   // TrustedURL Assignments
   const URLTestCases = [
@@ -93,7 +93,7 @@
   }, "`Element.prototype.setAttribute.SrC = string` throws.");
 
   // After default policy creation string and null assignments implicitly call createXYZ
-  let p = window.TrustedTypes.createPolicy("default", { createURL: createURLJS, createScriptURL: createScriptURLJS, createHTML: createHTMLJS, createScript: createScriptJS }, true);
+  let p = window.trustedTypes.createPolicy("default", { createURL: createURLJS, createScriptURL: createScriptURLJS, createHTML: createHTMLJS, createScript: createScriptJS }, true);
   URLTestCases.forEach(c => {
     test(t => {
       assert_element_accepts_trusted_type(c[0], c[1], INPUTS.URL, RESULTS.URL);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLElement-generic.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLElement-generic.tentative.html
index fe27d45..89d1216 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLElement-generic.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-HTMLElement-generic.tentative.html
@@ -69,7 +69,7 @@
   });
 
   // After default policy creation string and null assignments implicitly call createHTML
-  let p = window.TrustedTypes.createPolicy("default", { createURL: createURLJS, createScriptURL: createScriptURLJS, createHTML: createHTMLJS }, true);
+  let p = window.trustedTypes.createPolicy("default", { createURL: createURLJS, createScriptURL: createScriptURLJS, createHTML: createHTMLJS }, true);
 
   URLTestCases.forEach(c => {
     test(t => {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-assign.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-assign.tentative.html
index 4c295ff..8e89d0d 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-assign.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-assign.tentative.html
@@ -36,7 +36,7 @@
   }, "`location.assign = null` throws");
 
   // Create default policy. Applies to all subsequent tests.
-  let p = window.TrustedTypes.createPolicy("default",
+  let p = window.trustedTypes.createPolicy("default",
       { createURL: createLocationURLJS }, true);
 
   // After default policy creation string assignment implicitly calls createURL.
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-href.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-href.tentative.html
index 86bce799..998ee21 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-href.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-href.tentative.html
@@ -36,7 +36,7 @@
   }, "`location.href = null` throws");
 
   // Create default policy. Applies to all subsequent tests.
-  let p = window.TrustedTypes.createPolicy("default",
+  let p = window.trustedTypes.createPolicy("default",
       { createURL: createLocationURLJS }, true);
 
   // After default policy creation string assignment implicitly calls createURL.
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-replace.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-replace.tentative.html
index aa3af64..e85bb646 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-replace.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Location-replace.tentative.html
@@ -36,7 +36,7 @@
   }, "`location.replace = null` throws");
 
   // Create default policy. Applies to all subsequent tests.
-  let p = window.TrustedTypes.createPolicy("default",
+  let p = window.trustedTypes.createPolicy("default",
       { createURL: createLocationURLJS }, true);
 
   // After default policy creation string assignment implicitly calls createURL.
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Range-createContextualFragment.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Range-createContextualFragment.tentative.html
index 4919b7f..61553eb 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Range-createContextualFragment.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Range-createContextualFragment.tentative.html
@@ -36,7 +36,7 @@
 
   // After default policy creation string assignment implicitly calls createHTML
   test(t => {
-    let p = window.TrustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
+    let p = window.trustedTypes.createPolicy("default", { createHTML: createHTMLJS }, true);
     var range = document.createRange();
     range.selectNodeContents(document.documentElement);
     var result = range.createContextualFragment(INPUTS.HTML);
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Window-open.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Window-open.tentative.html
index c66a16d..e9c1c79 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Window-open.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/block-string-assignment-to-Window-open.tentative.html
@@ -64,7 +64,7 @@
   }, "`document.open(null)` throws.");
 
   // After default policy creation string assignment implicitly calls createURL.
-  let p = window.TrustedTypes.createPolicy("default", { createURL: createURLJS }, true);
+  let p = window.trustedTypes.createPolicy("default", { createURL: createURLJS }, true);
   test(t => {
     testWindowDoesntThrow(t, INPUTS.URL, RESULTS.URL, window);
   }, "'window.open(string)' assigned via default policy (successful URL transformation).");
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/default-policy-report-only.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/default-policy-report-only.tentative.html
index 1170655..1a54fd6 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/default-policy-report-only.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/default-policy-report-only.tentative.html
@@ -70,7 +70,7 @@
     return "sanitized: " + str;
 }
 
-TrustedTypes.createPolicy("default", {
+trustedTypes.createPolicy("default", {
   createURL: policy,
   createScriptURL: policy,
   createHTML: policy,
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/default-policy.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/default-policy.tentative.html
index 68e05c130..672eccf 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/default-policy.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/default-policy.tentative.html
@@ -70,7 +70,7 @@
     return "sanitized: " + str;
 }
 
-TrustedTypes.createPolicy("default", {
+trustedTypes.createPolicy("default", {
   createURL: policy,
   createScriptURL: policy,
   createHTML: policy,
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/eval-with-permissive-csp.tentative.html b/third_party/blink/web_tests/external/wpt/trusted-types/eval-with-permissive-csp.tentative.html
index 25b4948c..074fe79d 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/eval-with-permissive-csp.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/eval-with-permissive-csp.tentative.html
@@ -25,7 +25,7 @@
     assert_equals("" + s, "Hello a cat string");
   }, "eval with TrustedScript and permissive CSP works.");
 
-  TrustedTypes.createPolicy("default", { createScript: createScriptJS }, true);
+  trustedTypes.createPolicy("default", { createScript: createScriptJS }, true);
   test(t => {
     let s = eval('"Hello transformed untrusted string"');
     assert_equals(s, "Hello a cat untrusted string");
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/idlharness.window.js b/third_party/blink/web_tests/external/wpt/trusted-types/idlharness.window.js
index de136977..4c1ee6e0 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/idlharness.window.js
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/idlharness.window.js
@@ -6,12 +6,12 @@
     ['dom', 'html'],
     idl_array => {
       idl_array.add_objects({
-        TrustedTypePolicyFactory: ['window.TrustedTypes'],
-        TrustedTypePolicy: ['window.TrustedTypes.createPolicy("SomeName", { createHTML: s => s })'],
-        TrustedHTML: ['window.TrustedTypes.createPolicy("SomeName1", { createHTML: s => s }).createHTML("A string")'],
-        TrustedScript: ['window.TrustedTypes.createPolicy("SomeName2", { createScript: s => s }).createScript("A string")'],
-        TrustedScriptURL: ['window.TrustedTypes.createPolicy("SomeName3", { createScriptURL: s => s }).createScriptURL("A string")'],
-        TrustedURL: ['window.TrustedTypes.createPolicy("SomeName4", { createURL: s => s }).createURL("A string")']
+        TrustedTypePolicyFactory: ['window.trustedTypes'],
+        TrustedTypePolicy: ['window.trustedTypes.createPolicy("SomeName", { createHTML: s => s })'],
+        TrustedHTML: ['window.trustedTypes.createPolicy("SomeName1", { createHTML: s => s }).createHTML("A string")'],
+        TrustedScript: ['window.trustedTypes.createPolicy("SomeName2", { createScript: s => s }).createScript("A string")'],
+        TrustedScriptURL: ['window.trustedTypes.createPolicy("SomeName3", { createScriptURL: s => s }).createScriptURL("A string")'],
+        TrustedURL: ['window.trustedTypes.createPolicy("SomeName4", { createURL: s => s }).createURL("A string")']
       });
     },
     'Trusted Types'
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js
index 8665c69..fa63c8ba 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/WorkerGlobalScope-importScripts.https.js
@@ -1,4 +1,4 @@
-let test_setup_policy = TrustedTypes.createPolicy("hurrayanythinggoes", {
+let test_setup_policy = trustedTypes.createPolicy("hurrayanythinggoes", {
   createScriptURL: x => x
 });
 importScripts(test_setup_policy.createScriptURL("/resources/testharness.js"));
@@ -13,14 +13,14 @@
   worker_type = "service worker";
 }
 
-let test_policy = TrustedTypes.createPolicy("xxx", {
+let test_policy = trustedTypes.createPolicy("xxx", {
   createScriptURL: url => url.replace("play", "work")
 });
 
 test(t => {
   self.result = "Fail";
   let trusted_url = test_policy.createScriptURL("player.js");
-  assert_true(this.TrustedTypes.isScriptURL(trusted_url));
+  assert_true(this.trustedTypes.isScriptURL(trusted_url));
   importScripts(trusted_url);  // worker.js modifies self.result.
   assert_equals(self.result, "Pass");
 }, "importScripts with TrustedScriptURL works in " + worker_type);
@@ -63,7 +63,7 @@
 }, "importScripts with two URLs, one trusted, in " + worker_type);
 
 // Test default policy application:
-TrustedTypes.createPolicy("default", {
+trustedTypes.createPolicy("default", {
   createScriptURL: url => url.replace("play", "work")
 }, true);
 test(t => {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/support/helper.sub.js b/third_party/blink/web_tests/external/wpt/trusted-types/support/helper.sub.js
index 36ee240..d63ff54 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/support/helper.sub.js
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/support/helper.sub.js
@@ -40,19 +40,19 @@
 }
 
 function createHTML_policy(win, c) {
-  return win.TrustedTypes.createPolicy('SomeHTMLPolicyName' + c, { createHTML: createHTMLJS });
+  return win.trustedTypes.createPolicy('SomeHTMLPolicyName' + c, { createHTML: createHTMLJS });
 }
 
 function createScript_policy(win, c) {
-  return win.TrustedTypes.createPolicy('SomeScriptPolicyName' + c, { createScript: createScriptJS });
+  return win.trustedTypes.createPolicy('SomeScriptPolicyName' + c, { createScript: createScriptJS });
 }
 
 function createScriptURL_policy(win, c) {
-  return win.TrustedTypes.createPolicy('SomeScriptURLPolicyName' + c, { createScriptURL: createScriptURLJS });
+  return win.trustedTypes.createPolicy('SomeScriptURLPolicyName' + c, { createScriptURL: createScriptURLJS });
 }
 
 function createURL_policy(win, c) {
-  return win.TrustedTypes.createPolicy('SomeURLPolicyName' + c, { createURL: createURLJS });
+  return win.trustedTypes.createPolicy('SomeURLPolicyName' + c, { createURL: createURLJS });
 }
 
 function assert_element_accepts_trusted_html(win, c, t, tag, attribute, expected) {
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-no-unsafe-eval.tentative.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-no-unsafe-eval.tentative.https.html
index dc86536..e15ecfa 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-no-unsafe-eval.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-no-unsafe-eval.tentative.https.html
@@ -48,7 +48,7 @@
     try { fn(); assert_unreached(); } catch (err) { /* ignore */ }
   }
 
-  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  // A sample policy we use to test trustedTypes.createPolicy behaviour.
   const id = x => x;
   const a_policy = {
     createHTML: id,
@@ -57,7 +57,7 @@
     createScript: id,
   };
 
-  const scriptyPolicy = TrustedTypes.createPolicy('allowEval', a_policy);
+  const scriptyPolicy = trustedTypes.createPolicy('allowEval', a_policy);
 
   // Provoke/wait for a CSP violation, in order to be sure that all previous
   // CSP violations have been delivered.
@@ -96,7 +96,7 @@
   }, "Trusted Type violation report: evaluating a Trusted Script violates script-src.");
 
   promise_test(t => {
-    TrustedTypes.createPolicy('default', {
+    trustedTypes.createPolicy('default', {
       createScript: s => s.replace('payload', 'default policy'),
     }, true);
     let p = Promise.resolve()
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-report-only.tentative.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-report-only.tentative.https.html
index 1fb65459..bd8933a4 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-report-only.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting-report-only.tentative.https.html
@@ -48,7 +48,7 @@
     try { fn(); assert_unreached(); } catch (err) { /* ignore */ }
   }
 
-  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  // A sample policy we use to test trustedTypes.createPolicy behaviour.
   const id = x => x;
   const a_policy = {
     createHTML: id,
@@ -57,7 +57,7 @@
     createScript: id,
   };
 
-  const scriptyPolicy = TrustedTypes.createPolicy('allowEval', a_policy);
+  const scriptyPolicy = trustedTypes.createPolicy('allowEval', a_policy);
 
   // Provoke/wait for a CSP violation, in order to be sure that all previous
   // CSP violations have been delivered.
@@ -93,7 +93,7 @@
   }, "Trusted Type violation report: evaluating a Trusted Script.");
 
   promise_test(t => {
-    TrustedTypes.createPolicy('default', {
+    trustedTypes.createPolicy('default', {
       createScript: s => s.replace('payload', 'default policy'),
     }, true);
     let p = promise_flush()();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting.tentative.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting.tentative.https.html
index 4ec5db1..c751ae1 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-eval-reporting.tentative.https.html
@@ -42,7 +42,7 @@
     try { fn(); assert_unreached(); } catch (err) { /* ignore */ }
   }
 
-  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  // A sample policy we use to test trustedTypes.createPolicy behaviour.
   const id = x => x;
   const a_policy = {
     createHTML: id,
@@ -50,7 +50,7 @@
     createURL: id,
     createScript: id,
   };
-  const scriptyPolicy = TrustedTypes.createPolicy('allowEval', a_policy);
+  const scriptyPolicy = trustedTypes.createPolicy('allowEval', a_policy);
 
   // Provoke/wait for a CSP violation, in order to be sure that all previous
   // CSP violations have been delivered.
@@ -88,7 +88,7 @@
 
   promise_test(t => {
     let beacon = 'never_overwritten';
-    TrustedTypes.createPolicy('default', {
+    trustedTypes.createPolicy('default', {
       createScript: s => s.replace('payload', 'default policy'),
     }, true);
     let p = promise_flush()();
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-report-only.tentative.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-report-only.tentative.https.html
index f33183b..1a17d529 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-report-only.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-report-only.tentative.https.html
@@ -34,9 +34,9 @@
     });
   }
 
-  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  // A sample policy we use to test trustedTypes.createPolicy behaviour.
   const id = x => x;
-  const policy = TrustedTypes.createPolicy("two", {
+  const policy = trustedTypes.createPolicy("two", {
     createHTML: id,
     createScriptURL: id,
     createURL: id,
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting-check-report.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting-check-report.https.html
index 1119077..fc98f5c 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting-check-report.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting-check-report.https.html
@@ -15,7 +15,7 @@
 </head>
 <body>
   <script>
-    TrustedTypes.createPolicy("three", {});
+    trustedTypes.createPolicy("three", {});
   </script>
   <script async defer src='../content-security-policy/support/checkReport.sub.js?reportField=violated-directive&reportValue=trusted-types'></script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting.tentative.https.html b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting.tentative.https.html
index 0104ba3..6a79fec 100644
--- a/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting.tentative.https.html
+++ b/third_party/blink/web_tests/external/wpt/trusted-types/trusted-types-reporting.tentative.https.html
@@ -72,7 +72,7 @@
     return e;
   } }
 
-  // A sample policy we use to test TrustedTypes.createPolicy behaviour.
+  // A sample policy we use to test trustedTypes.createPolicy behaviour.
   const id = x => x;
   const a_policy = {
     createHTML: id,
@@ -101,14 +101,14 @@
         .then(expect_sample("three"))
         .then(expect_blocked_uri("trusted-types-policy"))
         .then(promise_flush());
-    expect_throws(_ => TrustedTypes.createPolicy("three", a_policy));
+    expect_throws(_ => trustedTypes.createPolicy("three", a_policy));
     flush();
     return p;
   }, "Trusted Type violation report: creating a forbidden policy.");
 
   promise_test(t => {
     let p = promise_flush()();
-    expect_throws(_ => TrustedTypes.createPolicy("two", a_policy));
+    expect_throws(_ => trustedTypes.createPolicy("two", a_policy));
     flush();
     return p;
   }, "Trusted Type violation report: creating a report-only-forbidden policy.");
@@ -120,7 +120,7 @@
     let p = Promise.resolve()
         .then(promise_violation("trusted-types two"))
         .then(promise_flush());
-    policy_one = TrustedTypes.createPolicy("one", a_policy);
+    policy_one = trustedTypes.createPolicy("one", a_policy);
     flush();
     return p;
   }, "Trusted Type violation report: creating a forbidden-but-not-reported policy.");
diff --git a/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns-expected.txt b/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns-expected.txt
index 0074a83..c60be850 100644
--- a/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns-expected.txt
+++ b/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns-expected.txt
@@ -1,10 +1,10 @@
 PASS window.getComputedStyle(gridFixedAndMinContentAndFlex, '').getPropertyValue('grid-template-columns') is "20px 30px 50px"
 PASS window.getComputedStyle(gridFixedAndMinContentAndFlexMultipleOverlap, '').getPropertyValue('grid-template-columns') is "20px 10px 70px"
-PASS window.getComputedStyle(gridMinMaxFixedFlexAndMaxContentAndAuto, '').getPropertyValue('grid-template-columns') is "60px 20px 20px"
-PASS window.getComputedStyle(gridMinMaxFixedFlexAndMaxContentAndAutoNoFlexSpanningItems, '').getPropertyValue('grid-template-columns') is "100px 0px 0px"
+PASS window.getComputedStyle(gridMinMaxFixedFlexAndMaxContentAndAuto, '').getPropertyValue('grid-template-columns') is "30px 50px 20px"
+PASS window.getComputedStyle(gridMinMaxFixedFlexAndMaxContentAndAutoNoFlexSpanningItems, '').getPropertyValue('grid-template-columns') is "30px 70px 0px"
 PASS window.getComputedStyle(gridMinMaxAutoFixedAndMinContentAndFixed, '').getPropertyValue('grid-template-columns') is "35px 20px 25px"
 PASS window.getComputedStyle(gridMinContentAndMinMaxFixedMinContentAndFlex, '').getPropertyValue('grid-template-columns') is "20px 20px 60px"
-PASS window.getComputedStyle(gridMaxContentAndMinMaxFixedMaxContentAndFlex, '').getPropertyValue('grid-template-columns') is "70px 20px 10px"
+PASS window.getComputedStyle(gridMaxContentAndMinMaxFixedMaxContentAndFlex, '').getPropertyValue('grid-template-columns') is "70px 20px 30px"
 PASS successfullyParsed is true
 
 TEST COMPLETE
diff --git a/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns.html b/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns.html
index 113a142..0b68649 100644
--- a/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns.html
+++ b/third_party/blink/web_tests/fast/css-grid-layout/flex-and-content-sized-resolution-columns.html
@@ -98,11 +98,11 @@
 
 checkColumns("gridFixedAndMinContentAndFlex", "20px 30px 50px");
 checkColumns("gridFixedAndMinContentAndFlexMultipleOverlap", "20px 10px 70px");
-checkColumns("gridMinMaxFixedFlexAndMaxContentAndAuto", "60px 20px 20px");
-checkColumns("gridMinMaxFixedFlexAndMaxContentAndAutoNoFlexSpanningItems", "100px 0px 0px");
+checkColumns("gridMinMaxFixedFlexAndMaxContentAndAuto", "30px 50px 20px");
+checkColumns("gridMinMaxFixedFlexAndMaxContentAndAutoNoFlexSpanningItems", "30px 70px 0px");
 checkColumns("gridMinMaxAutoFixedAndMinContentAndFixed", "35px 20px 25px");
 checkColumns("gridMinContentAndMinMaxFixedMinContentAndFlex", "20px 20px 60px");
-checkColumns("gridMaxContentAndMinMaxFixedMaxContentAndFlex", "70px 20px 10px");
+checkColumns("gridMaxContentAndMinMaxFixedMaxContentAndFlex", "70px 20px 30px");
 
 </script>
 </html>
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
index dcb6f0e..ef68b1d 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-navigated-expected.txt
@@ -3,7 +3,6 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS window.cached_TrustedTypes.defaultPolicy is null
 PASS window.cached_applicationCache.oncached is null
 PASS window.cached_applicationCache.onchecking is null
 PASS window.cached_applicationCache.ondownloading is null
@@ -108,6 +107,7 @@
 PASS window.cached_statusbar.visible is false
 PASS window.cached_styleMedia.type is ''
 PASS window.cached_toolbar.visible is false
+PASS window.cached_trustedTypes.defaultPolicy is null
 PASS window.cached_visualViewport.height is 0
 PASS window.cached_visualViewport.offsetLeft is 0
 PASS window.cached_visualViewport.offsetTop is 0
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
index ed75ee3..40852a9 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-and-gced-expected.txt
@@ -3,7 +3,6 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS window.cached_TrustedTypes.defaultPolicy is null
 PASS window.cached_applicationCache.oncached is null
 PASS window.cached_applicationCache.onchecking is null
 PASS window.cached_applicationCache.ondownloading is null
@@ -108,6 +107,7 @@
 PASS window.cached_statusbar.visible is false
 PASS window.cached_styleMedia.type is ''
 PASS window.cached_toolbar.visible is false
+PASS window.cached_trustedTypes.defaultPolicy is null
 PASS window.cached_visualViewport.height is 0
 PASS window.cached_visualViewport.offsetLeft is 0
 PASS window.cached_visualViewport.offsetTop is 0
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
index e54c2b0..eedea18a 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-properties-after-frame-removed-expected.txt
@@ -3,7 +3,6 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS window.cached_TrustedTypes.defaultPolicy is null
 PASS window.cached_applicationCache.oncached is null
 PASS window.cached_applicationCache.onchecking is null
 PASS window.cached_applicationCache.ondownloading is null
@@ -108,6 +107,7 @@
 PASS window.cached_statusbar.visible is false
 PASS window.cached_styleMedia.type is ''
 PASS window.cached_toolbar.visible is false
+PASS window.cached_trustedTypes.defaultPolicy is null
 PASS window.cached_visualViewport.height is 0
 PASS window.cached_visualViewport.offsetLeft is 0
 PASS window.cached_visualViewport.offsetTop is 0
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
index 26a3eae2..e6bd51193 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-navigated-expected.txt
@@ -3,7 +3,6 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS oldChildWindow.TrustedTypes.defaultPolicy is newChildWindow.TrustedTypes.defaultPolicy
 PASS oldChildWindow.applicationCache.oncached is newChildWindow.applicationCache.oncached
 PASS oldChildWindow.applicationCache.onchecking is newChildWindow.applicationCache.onchecking
 PASS oldChildWindow.applicationCache.ondownloading is newChildWindow.applicationCache.ondownloading
@@ -244,6 +243,7 @@
 PASS oldChildWindow.statusbar.visible is newChildWindow.statusbar.visible
 PASS oldChildWindow.styleMedia.type is newChildWindow.styleMedia.type
 PASS oldChildWindow.toolbar.visible is newChildWindow.toolbar.visible
+PASS oldChildWindow.trustedTypes.defaultPolicy is newChildWindow.trustedTypes.defaultPolicy
 PASS oldChildWindow.visualViewport.height is newChildWindow.visualViewport.height
 PASS oldChildWindow.visualViewport.offsetLeft is newChildWindow.visualViewport.offsetLeft
 PASS oldChildWindow.visualViewport.offsetTop is newChildWindow.visualViewport.offsetTop
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
index 0780dd8..c94a582 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-and-gced-expected.txt
@@ -3,7 +3,6 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS childWindow.TrustedTypes.defaultPolicy is null
 PASS childWindow.closed is true
 PASS childWindow.defaultStatus is ''
 PASS childWindow.defaultstatus is ''
@@ -179,6 +178,7 @@
 PASS childWindow.statusbar.visible is false
 PASS childWindow.styleMedia.type is ''
 PASS childWindow.toolbar.visible is false
+PASS childWindow.trustedTypes.defaultPolicy is null
 PASS childWindow.visualViewport.height is 0
 PASS childWindow.visualViewport.offsetLeft is 0
 PASS childWindow.visualViewport.offsetTop is 0
diff --git a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
index aa0c57a..636dcaca 100644
--- a/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/Window/property-access-on-cached-window-after-frame-removed-expected.txt
@@ -3,7 +3,6 @@
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
 
-PASS childWindow.TrustedTypes.defaultPolicy is null
 PASS childWindow.closed is true
 PASS childWindow.defaultStatus is ''
 PASS childWindow.defaultstatus is ''
@@ -179,6 +178,7 @@
 PASS childWindow.statusbar.visible is false
 PASS childWindow.styleMedia.type is ''
 PASS childWindow.toolbar.visible is false
+PASS childWindow.trustedTypes.defaultPolicy is null
 PASS childWindow.visualViewport.height is 0
 PASS childWindow.visualViewport.offsetLeft is 0
 PASS childWindow.visualViewport.offsetTop is 0
diff --git a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-display-locked.js b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-display-locked.js
index b33e6365..40ec9369 100644
--- a/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-display-locked.js
+++ b/third_party/blink/web_tests/http/tests/devtools/elements/highlight/highlight-display-locked.js
@@ -8,16 +8,10 @@
   await TestRunner.loadModule('console_test_runner');
   await TestRunner.showPanel('elements');
   await TestRunner.loadHTML(`
-      <div id="container" renderSubtree="invisible" style="content-size: 10px;">
+      <div id="container" renderSubtree="invisible skip-activation" style="content-size: 10px;">
         <div id="child" style="width: 50px; height: 50px;"></div>
       </div>
     `);
-  //await TestRunner.evaluateInPagePromise(`
-  //  container.renderSubtree = "invisible";
-  //  await requestAnimationFrame(() => {});
-  //  //// force layout so that acquire finishes.
-  //  //container.offsetTop;
-  //`);
 
   function dumpChild() {
     ElementsTestRunner.dumpInspectorHighlightJSON('child', TestRunner.completeTest.bind(TestRunner));
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/badging-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/badging-origin-trial-interfaces.html
index c0ac874..be17e92 100644
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/badging-origin-trial-interfaces.html
+++ b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/badging-origin-trial-interfaces.html
@@ -1,10 +1,10 @@
 <!DOCTYPE html>
 <meta charset="utf-8">
 <!-- Generate token with the command:
-generate_token.py http://127.0.0.1:8000 Badging --expire-timestamp=2000000000
+generate_token.py http://127.0.0.1:8000 BadgingV2 --expire-timestamp=2000000000
 -->
 
-<meta http-equiv="origin-trial" content="AjMyKQIep8aE/3H2rfLKB0UEt0aGtfdVGult8LN5tjpYZnkhbaGEm8KirzODRym/5KnVfv33DaXex0tUZoDQFQ0AAABPeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiQmFkZ2luZyIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==" />
+<meta http-equiv="origin-trial" content="AqzH1yAjqt/6grJkR3r1584FLOYa+kkfoenZBdnmBOShEN/eGrOF7OoxdPXg5e2b+KeB+ysH8qp/F9eyimHZygIAAABReyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiQmFkZ2luZ1YyIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9" />
 <title>Badging API - interfaces exposed by origin trial</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
@@ -19,4 +19,4 @@
   assert_function_on(navigator, 'setExperimentalAppBadge', 'setExperimentalAppBadge is not defined on navigator');
   assert_function_on(navigator, 'clearExperimentalAppBadge', 'clearExperimentalAppBadge is not defined on navigator');
 }, 'Badge API interfaces and properties in Origin-Trial enabled document.');
-</script>
\ No newline at end of file
+</script>
diff --git a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html b/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html
deleted file mode 100644
index 4aab4c44..0000000
--- a/third_party/blink/web_tests/http/tests/origin_trials/webexposed/xr/webxr-origin-trial-interfaces.html
+++ /dev/null
@@ -1,156 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>WebXR - interfaces exposed by origin trial</title>
-<script src="../../../resources/testharness.js"></script>
-<script src="../../../resources/testharnessreport.js"></script>
-<script src="../../../resources/origin-trials-helper.js"></script>
-<script>
-let properties_to_check = {
-  'Navigator': ['xr']
-};
-
-// The WebXR APIs should not be present without the token.
-test(t => {
-  OriginTrialsHelper.check_properties_missing_unless_runtime_flag(
-    this, properties_to_check, 'webXREnabled');
-}, "WebXR's entrypoint properties are not available without a token.");
-
-test(() => {
-  if (OriginTrialsHelper.is_runtime_flag_enabled('webXREnabled')) {
-    assert_in_array('xr-spatial-tracking', document.featurePolicy.features());
-  } else {
-    assert_equals(document.featurePolicy.features().indexOf("xr-spatial-tracking"), -1);
-  }
-}, 'document.featurePolicy.features does not advertise xr-spatial-tracking without a token or flag.');
-
-// Add the token, which was generated with the following command:
-// tools/origin_trials/generate_token.py http://127.0.0.1:8000 WebXRDeviceM76 --expire-timestamp=2000000000
-let token = "AkyVxpb70lHShWfDAoWZVaVN2iZ9BxVFtnErcD8gysTmnF+SNXlduVTCLBpcr1V2q/MFrjeqPb2BlFALolT1bAwAAABWeyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiV2ViWFJEZXZpY2VNNzYiLCAiZXhwaXJ5IjogMjAwMDAwMDAwMH0=";
-OriginTrialsHelper.add_token(token);
-
-// The WebXR APIs should be available now.
-test(t => {
-  OriginTrialsHelper.check_properties_exist(this, properties_to_check);
-}, "WebXR's entrypoint properties are available with origin trial token.");
-
-test(() => {
-  assert_in_array('xr-spatial-tracking', document.featurePolicy.features());
-}, 'document.featurePolicy.features advertises xr-spatial-tracking after token added.');
-
-// Ensure Gamepad Extensions are NOT enabled by the WebXR origin trial token.
-test(t => {
-  webvr_gamepad_properties = {
-    'Gamepad': ['pose', 'hand', 'displayId'],
-  };
-  OriginTrialsHelper.check_properties_missing_unless_runtime_flag(
-    this, webvr_gamepad_properties, 'webVREnabled');
-}, "WebVR-specific Gamepad properties are not available with a WebXR token.");
-test(t => {
-  webvr_gamepad_interfaces = [
-    'GamepadPose'
-  ];
-  OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag(
-    this, webvr_gamepad_interfaces, 'webVREnabled');
-}, "WebVR-specific Gamepad interfaces are not available with a WebXR token.");
-
-
-// Ensure that APIs that are not part of the core WebXR set are NOT enabled by
-// the WebXR origin trial token.
-
-test(t => {
-  let ar_properties = {
-    'XRSession': ['environmentBlendMode']
-  };
-  // TODO: Update AR support to use it's own runtime flag
-  OriginTrialsHelper.check_properties_missing_unless_runtime_flag(
-    this, ar_properties, 'webXRARModuleEnabled');
-}, "AR properties are not available with a WebXR token.");
-
-test(t => {
-  let hittest_properties = {
-    'XRSession': ['requestHitTest']
-  };
-  OriginTrialsHelper.check_properties_missing_unless_runtime_flag(
-    this, hittest_properties, 'webXRHitTestEnabled');
-}, "Hit-test properties are not available with a WebXR token.");
-test(t => {
-  let hittest_interfaces = [
-    'XRHitResult',
-    'XRRay'
-  ];
-  OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag(
-    this, hittest_interfaces, 'webXRHitTestEnabled');
-}, "Hit-test interfaces are not available with a WebXR token.");
-
-test(t => {
-  let planes_properties = {
-    'XRFrame': ['worldInformation'],
-    'XRSession': ['worldTrackingState', 'updateWorldTrackingState']
-  };
-  OriginTrialsHelper.check_properties_missing_unless_runtime_flag(
-    this, planes_properties, 'webXRPlaneDetectionEnabled');
-}, "Plane properties are not available with a WebXR token.");
-test(t => {
-  let planes_interfaces = [
-    'XRPlane',
-    'XRPlaneDetectionState',
-    'XRWorldInformation',
-    'XRWorldTrackingState'
-  ];
-  OriginTrialsHelper.check_interfaces_missing_unless_runtime_flag(
-    this, planes_interfaces, 'webXRPlaneDetectionEnabled');
-}, "Plane interfaces are not available with a WebXR token.");
-
-
-// Ensure that AR sessions are not exposed as part of the WebXR origin trial.
-// The webXREnabled check is effectively checking whether experimental features
-// are enabled. This works as long as at least one AR feature is experimental.
-promise_test(t => {
-  let promise = navigator.xr.supportsSession('immersive-ar');
-  if (OriginTrialsHelper.is_runtime_flag_enabled('webXREnabled')) {
-    return promise_rejects(t, "NotSupportedError", promise);
-  } else {
-    return promise_rejects(t, new TypeError(), promise);
-  }
-}, "immersive-ar is not recognized by supportsSession() with a WebXR token.");
-promise_test(t => {
-  let promise = navigator.xr.requestSession('immersive-ar');
-  if (OriginTrialsHelper.is_runtime_flag_enabled('webXREnabled')) {
-    return promise_rejects(t, "SecurityError", promise);
-  } else {
-    return promise_rejects(t, new TypeError(), promise);
-  }
-}, "immersive-ar is not recognized by requestSession() with a WebXR token.");
-// Verify the rejection reason matches that for other invalid enum values.
-// It only makes sense to run these when the failure occurs.
-if (!OriginTrialsHelper.is_runtime_flag_enabled('webXREnabled')) {
-  promise_test(t => {
-    return navigator.xr.supportsSession('invalid').then(function() {
-        assert_unreached("Promise should be rejected.")
-    }).catch(function(invalidReason) {
-      return navigator.xr.supportsSession('immersive-ar').then(function() {
-        assert_unreached("Promise should be rejected.")
-      }).catch(function(arReason) {
-        // Replace the enum value in the expected message. That is the only
-        // thing that should be different.
-        invalidReason.message = invalidReason.message.replace('invalid', 'immersive-ar');
-        assert_object_equals(invalidReason, arReason);
-      });
-    });
-  }, "supportsSession('immersive-ar') result matches result for other invalid values.");
-  promise_test(t => {
-    return navigator.xr.requestSession('invalid').then(function() {
-        assert_unreached("Promise should be rejected.")
-    }).catch(function(invalidReason) {
-      return navigator.xr.requestSession('immersive-ar').then(function() {
-        assert_unreached("Promise should be rejected.")
-      }).catch(function(arReason) {
-        // Replace the enum value in the expected message. That is the only
-        // thing that should be different.
-        invalidReason.message = invalidReason.message.replace('invalid', 'immersive-ar');
-        assert_object_equals(invalidReason, arReason);
-      });
-    });
-  }, "requestSession('immersive-ar') result matches result for other invalid values.");
-}
-</script>
diff --git a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index 82d14229..93fd88d 100644
--- a/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -3765,7 +3765,6 @@
     attribute console
     attribute globalThis
     attribute internals
-    getter TrustedTypesWorkers
     getter clients
     getter cookieStore
     getter onabortpayment
@@ -3789,6 +3788,7 @@
     getter onsync
     getter registration
     getter serviceWorker
+    getter trustedTypes
     method gc
     method skipWaiting
     setter cookieStore
diff --git a/third_party/blink/web_tests/inspector-protocol/css/css-get-platform-fonts-display-locked.js b/third_party/blink/web_tests/inspector-protocol/css/css-get-platform-fonts-display-locked.js
index 3773117..03d1a520 100644
--- a/third_party/blink/web_tests/inspector-protocol/css/css-get-platform-fonts-display-locked.js
+++ b/third_party/blink/web_tests/inspector-protocol/css/css-get-platform-fonts-display-locked.js
@@ -29,7 +29,7 @@
 `, 'Test css.getPlatformFontsForNode method with display locking.');
 
   await session.evaluateAsync(async () => {
-    await requestAnimationFrame(() => { document.getElementById("parent").renderSubtree = "invisible"; });
+    await requestAnimationFrame(() => { document.getElementById("parent").renderSubtree = "invisible skip-activation"; });
   });
 
   var CSSHelper = await testRunner.loadScript('../resources/css-helper.js');
diff --git a/third_party/blink/web_tests/virtual/cache-storage-eager-reading/external/wpt/service-workers/README.txt b/third_party/blink/web_tests/virtual/cache-storage-eager-reading/external/wpt/service-workers/README.txt
new file mode 100644
index 0000000..5f43f63
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/cache-storage-eager-reading/external/wpt/service-workers/README.txt
@@ -0,0 +1,4 @@
+This suite runs the ServiceWorker and CacheStorage tests with the
+CacheStorageEagerReading feature enabled.  This makes CacheStorage immediately
+read response bodies when cache.match() called within a FetchEvent handler.
+See crbug.com/1010624.
diff --git a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
index e0ec92c9..e9c65a5a 100644
--- a/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/http/tests/serviceworker/webexposed/global-interface-listing-service-worker-expected.txt
@@ -1951,6 +1951,7 @@
     method isVertexArray
     method lineWidth
     method linkProgram
+    method makeXRCompatible
     method pauseTransformFeedback
     method pixelStorei
     method polygonOffset
@@ -2443,6 +2444,7 @@
     method isTexture
     method lineWidth
     method linkProgram
+    method makeXRCompatible
     method pixelStorei
     method polygonOffset
     method readPixels
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt
index aee6621..324728f0 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/feature-policy-features-expected.txt
@@ -18,4 +18,5 @@
 sync-xhr
 usb
 vr
+xr-spatial-tracking
 
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 5af578b..c2efd05 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -1973,6 +1973,7 @@
 [Worker]     method isVertexArray
 [Worker]     method lineWidth
 [Worker]     method linkProgram
+[Worker]     method makeXRCompatible
 [Worker]     method pauseTransformFeedback
 [Worker]     method pixelStorei
 [Worker]     method polygonOffset
@@ -2465,6 +2466,7 @@
 [Worker]     method isTexture
 [Worker]     method lineWidth
 [Worker]     method linkProgram
+[Worker]     method makeXRCompatible
 [Worker]     method pixelStorei
 [Worker]     method polygonOffset
 [Worker]     method readPixels
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
index 2aae6d3..5f8b634a 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-expected.txt
@@ -4324,6 +4324,7 @@
     getter vendorSub
     getter webkitPersistentStorage
     getter webkitTemporaryStorage
+    getter xr
     method constructor
     method getBattery
     method getGamepads
@@ -7953,6 +7954,7 @@
     method isVertexArray
     method lineWidth
     method linkProgram
+    method makeXRCompatible
     method pauseTransformFeedback
     method pixelStorei
     method polygonOffset
@@ -8449,6 +8451,7 @@
     method isTexture
     method lineWidth
     method linkProgram
+    method makeXRCompatible
     method pixelStorei
     method polygonOffset
     method readPixels
@@ -8750,6 +8753,139 @@
     method constructor
     method iterateNext
     method snapshotItem
+interface XR : EventTarget
+    attribute @@toStringTag
+    getter ondevicechange
+    method constructor
+    method isSessionSupported
+    method requestSession
+    method supportsSession
+    setter ondevicechange
+interface XRBoundedReferenceSpace : XRReferenceSpace
+    attribute @@toStringTag
+    getter boundsGeometry
+    method constructor
+interface XRFrame
+    attribute @@toStringTag
+    getter session
+    method constructor
+    method getPose
+    method getViewerPose
+interface XRInputSource
+    attribute @@toStringTag
+    getter gripSpace
+    getter handedness
+    getter profiles
+    getter targetRayMode
+    getter targetRaySpace
+    method constructor
+interface XRInputSourceArray
+    attribute @@toStringTag
+    getter length
+    method @@iterator
+    method constructor
+    method entries
+    method forEach
+    method keys
+    method values
+interface XRInputSourceEvent : Event
+    attribute @@toStringTag
+    getter frame
+    getter inputSource
+    method constructor
+interface XRInputSourcesChangeEvent : Event
+    attribute @@toStringTag
+    getter added
+    getter removed
+    getter session
+    method constructor
+interface XRPose
+    attribute @@toStringTag
+    getter emulatedPosition
+    getter transform
+    method constructor
+interface XRReferenceSpace : XRSpace
+    attribute @@toStringTag
+    getter onreset
+    method constructor
+    method getOffsetReferenceSpace
+    setter onreset
+interface XRReferenceSpaceEvent : Event
+    attribute @@toStringTag
+    getter referenceSpace
+    getter transform
+    method constructor
+interface XRRenderState
+    attribute @@toStringTag
+    getter baseLayer
+    getter depthFar
+    getter depthNear
+    getter inlineVerticalFieldOfView
+    method constructor
+interface XRRigidTransform
+    attribute @@toStringTag
+    getter inverse
+    getter matrix
+    getter orientation
+    getter position
+    method constructor
+interface XRSession : EventTarget
+    attribute @@toStringTag
+    getter inputSources
+    getter onend
+    getter oninputsourceschange
+    getter onselect
+    getter onselectend
+    getter onselectstart
+    getter onvisibilitychange
+    getter renderState
+    getter visibilityState
+    method cancelAnimationFrame
+    method constructor
+    method end
+    method requestAnimationFrame
+    method requestReferenceSpace
+    method updateRenderState
+    setter onend
+    setter oninputsourceschange
+    setter onselect
+    setter onselectend
+    setter onselectstart
+    setter onvisibilitychange
+interface XRSessionEvent : Event
+    attribute @@toStringTag
+    getter session
+    method constructor
+interface XRSpace : EventTarget
+    attribute @@toStringTag
+    method constructor
+interface XRView
+    attribute @@toStringTag
+    getter eye
+    getter projectionMatrix
+    getter transform
+    method constructor
+interface XRViewerPose : XRPose
+    attribute @@toStringTag
+    getter views
+    method constructor
+interface XRViewport
+    attribute @@toStringTag
+    getter height
+    getter width
+    getter x
+    getter y
+    method constructor
+interface XRWebGLLayer
+    static method getNativeFramebufferScaleFactor
+    attribute @@toStringTag
+    getter antialias
+    getter framebuffer
+    getter framebufferHeight
+    getter framebufferWidth
+    getter ignoreDepthValues
+    method constructor
+    method getViewport
 interface XSLTProcessor
     attribute @@toStringTag
     method clearParameters
diff --git a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
index 9a25a1e2..84ada2d 100644
--- a/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/virtual/stable/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -1869,6 +1869,7 @@
 [Worker]     method isVertexArray
 [Worker]     method lineWidth
 [Worker]     method linkProgram
+[Worker]     method makeXRCompatible
 [Worker]     method pauseTransformFeedback
 [Worker]     method pixelStorei
 [Worker]     method polygonOffset
@@ -2361,6 +2362,7 @@
 [Worker]     method isTexture
 [Worker]     method lineWidth
 [Worker]     method linkProgram
+[Worker]     method makeXRCompatible
 [Worker]     method pixelStorei
 [Worker]     method polygonOffset
 [Worker]     method readPixels
diff --git a/third_party/blink/web_tests/wake-lock/wakelock-document-hidden.https.html b/third_party/blink/web_tests/wake-lock/wakelock-document-hidden.https.html
index 10cdcad..e0fc6b3 100644
--- a/third_party/blink/web_tests/wake-lock/wakelock-document-hidden.https.html
+++ b/third_party/blink/web_tests/wake-lock/wakelock-document-hidden.https.html
@@ -20,7 +20,7 @@
   const screenLock = WakeLock.request('screen');
   window.testRunner.setPageVisibility('hidden');
   assert_true(document.hidden);
-  return promise_rejects(t, "AbortError", screenLock);
+  return promise_rejects(t, "NotAllowedError", screenLock);
 }, "WakeLock.request('screen') aborts when the page is hidden");
 
 promise_test(async t => {
@@ -30,7 +30,7 @@
   const screenLock = WakeLock.request('screen', { signal: controller.signal });
   window.testRunner.setPageVisibility('hidden');
   assert_true(document.hidden);
-  await promise_rejects(t, "AbortError", screenLock);
+  await promise_rejects(t, "NotAllowedError", screenLock);
   controller.abort();
 }, "Aborting a rejected wake lock does not crash");
 </script>
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
index 5e5121a..e6fff60 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-dedicated-worker-expected.txt
@@ -3866,10 +3866,10 @@
 [Worker]     attribute console
 [Worker]     attribute globalThis
 [Worker]     attribute internals
-[Worker]     getter TrustedTypesWorkers
 [Worker]     getter name
 [Worker]     getter onmessage
 [Worker]     getter onmessageerror
+[Worker]     getter trustedTypes
 [Worker]     method cancelAnimationFrame
 [Worker]     method close
 [Worker]     method gc
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index ac21954..94d36cd 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
@@ -11267,7 +11267,6 @@
     attribute textInputController
     attribute top
     attribute window
-    getter TrustedTypes
     getter applicationCache
     getter caches
     getter clientInformation
@@ -11428,6 +11427,7 @@
     getter statusbar
     getter styleMedia
     getter toolbar
+    getter trustedTypes
     getter visualViewport
     getter webkitStorageInfo
     method NodeFilter
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
index ccf0496..1b87eaa 100644
--- a/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
+++ b/third_party/blink/web_tests/webexposed/global-interface-listing-shared-worker-expected.txt
@@ -3715,9 +3715,9 @@
 [Worker]     attribute console
 [Worker]     attribute globalThis
 [Worker]     attribute internals
-[Worker]     getter TrustedTypesWorkers
 [Worker]     getter name
 [Worker]     getter onconnect
+[Worker]     getter trustedTypes
 [Worker]     method close
 [Worker]     method gc
 [Worker]     method webkitRequestFileSystem
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/accessibility-non-activatable.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/accessibility-non-activatable.html
index 30118b7..3dc49994d 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/accessibility-non-activatable.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/accessibility-non-activatable.html
@@ -8,7 +8,7 @@
 <body>
 
 <div id="container">
-  <div id="locked" rendersubtree="invisible">
+  <div id="locked" rendersubtree="invisible skip-activation">
     locked
     <div id="child">
       child
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/acquire-on-display-contents.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/acquire-on-display-contents.html
index df7de7a..c1c9ee1 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/acquire-on-display-contents.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/acquire-on-display-contents.html
@@ -18,7 +18,7 @@
   slot.style = "display: block;";
   document.body.appendChild(slot);
   await setInvisible(slot).then(() => {
-    t.step(() => assert_equals(slot.renderSubtree, "invisible"));
+    t.step(() => assert_equals(slot.renderSubtree, INVISIBLE_NOT_ACTIVATABLE));
     t.done();
   });
 }, "<slot> with changed display type can be locked");
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-focus.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-focus.html
index bde2f4a..1f10a76 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-focus.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-focus.html
@@ -3,10 +3,11 @@
 <head>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
 </head>
 <body>
 
-<div id="locked" rendersubtree="invisible activatable">
+<div id="locked" rendersubtree="invisible">
   foo
   <div id="child" tabindex="0">
     bar
@@ -20,7 +21,7 @@
 
 async_test(async(t) => {
   const lockedEl = document.getElementById("locked");
-  t.step(() => { assert_equals(lockedEl.renderSubtree, "invisible activatable") });
+  t.step(() => { assert_equals(lockedEl.renderSubtree, INVISIBLE_ACTIVATABLE) });
   let axLocked = axElementById("locked");
   t.step(() => { assert_equals(axLocked.childrenCount, 3, "Child count after acquire"); });
   axElementById("child").takeFocus();
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-press.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-press.html
index 0ae0de2f..2fb80a7 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-press.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-press.html
@@ -3,10 +3,11 @@
 <head>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
 </head>
 <body>
 
-<div id="locked" rendersubtree="invisible activatable">
+<div id="locked" rendersubtree="invisible">
   foo
   <div id="child" tabindex="0">
     bar
@@ -20,7 +21,7 @@
 
 async_test(async(t) => {
   const lockedEl = document.getElementById("locked");
-  t.step(() => { assert_equals(lockedEl.renderSubtree, "invisible activatable") });
+  t.step(() => { assert_equals(lockedEl.renderSubtree, INVISIBLE_ACTIVATABLE) });
   let axLocked = axElementById("locked");
   t.step(() => { assert_equals(axLocked.childrenCount, 3, "Child count after acquire"); });
 
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-scroll.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-scroll.html
index bee65918..0cedebf 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-scroll.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/accessibility-activatable-scroll.html
@@ -3,10 +3,11 @@
 <head>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
 </head>
 <body>
 
-<div id="locked" rendersubtree="invisible activatable">
+<div id="locked" rendersubtree="invisible">
   foo
   <div id="child" tabindex="0">
     bar
@@ -20,7 +21,7 @@
 
 async_test(async(t) => {
   const lockedEl = document.getElementById("locked");
-  t.step(() => { assert_equals(lockedEl.renderSubtree, "invisible activatable") });
+  t.step(() => { assert_equals(lockedEl.renderSubtree, INVISIBLE_ACTIVATABLE) });
   let axLocked = axElementById("locked");
   t.step(() => { assert_equals(axLocked.childrenCount, 3, "Child count after acquire"); });
 
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links-ancestor.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links-ancestor.html
index 169016e4..bc9ffb2 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links-ancestor.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links-ancestor.html
@@ -35,8 +35,8 @@
   return new Promise((resolve, reject) => {
     prepareTest().then(() => {
       assert_equals(outermost.renderSubtree, "");
-      assert_equals(outer.renderSubtree, "invisible activatable");
-      assert_equals(inner.renderSubtree, "invisible activatable");
+      assert_equals(outer.renderSubtree, INVISIBLE_ACTIVATABLE);
+      assert_equals(inner.renderSubtree, INVISIBLE_ACTIVATABLE);
       assert_equals(innermost.renderSubtree, "");
 
       let innerPromise = new Promise((resolve, reject) => {
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links.html
index b6d40ac1..4e6852d 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/anchor-links.html
@@ -31,7 +31,7 @@
 promise_test(() => {
   return new Promise((resolve, reject) => {
     prepareTest().then(() => {
-      assert_equals(inner.renderSubtree, "invisible activatable");
+      assert_equals(inner.renderSubtree, INVISIBLE_ACTIVATABLE);
       inner.onbeforeactivate = (e) => {
         assert_equals(e.activatedElement, inner);
         resolve();
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/commit-in-beforeactivate.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/commit-in-beforeactivate.html
index cd282fa..9e71dfe 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/commit-in-beforeactivate.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/commit-in-beforeactivate.html
@@ -24,7 +24,7 @@
   async function runTest() {
     const target = document.getElementById("target");
     await setInvisibleActivatable(target);
-    t.step(() => assert_equals(target.renderSubtree, "invisible activatable"));
+    t.step(() => assert_equals(target.renderSubtree, INVISIBLE_ACTIVATABLE));
 
     target.addEventListener("beforeactivate", () => commit(target));
     target.scrollIntoView();
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll-into-view-beforeactivate.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll-into-view-beforeactivate.html
index 5655cdb5..35cf768 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll-into-view-beforeactivate.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll-into-view-beforeactivate.html
@@ -34,8 +34,8 @@
   return new Promise((resolve, reject) => {
     prepareTest().then(() => {
       assert_equals(outermost.renderSubtree, "");
-      assert_equals(outer.renderSubtree, "invisible activatable");
-      assert_equals(inner.renderSubtree, "invisible activatable");
+      assert_equals(outer.renderSubtree, INVISIBLE_ACTIVATABLE);
+      assert_equals(inner.renderSubtree, INVISIBLE_ACTIVATABLE);
       assert_equals(innermost.renderSubtree, "");
 
       let innerPromise = new Promise((resolve, reject) => {
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll.html
index 9108983b..0170d33 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/scroll.html
@@ -8,9 +8,9 @@
 <script src="/resources/testharnessreport.js"></script>
 <script src="../resources/utils.js"></script>
 
-<div id=visible rendersubtree="invisible activatable">text</div>
+<div id=visible rendersubtree="invisible">text</div>
 <div id=spacer style="height: 3000px">text</div>
-<div id=target rendersubtree="invisible activatable">text</div>
+<div id=target rendersubtree="invisible">text</div>
 
 <script>
 promise_test(() => {
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/selection.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/selection.html
index 8e823944..5336162 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/selection.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/activation/selection.html
@@ -30,7 +30,7 @@
       window.getSelection().selectAllChildren(container);
       assert_equals(window.getSelection().toString(), "foo\nbar");
       assert_equals(container.renderSubtree, "");
-      assert_equals(nonActivatable.renderSubtree, "invisible");
+      assert_equals(nonActivatable.renderSubtree, INVISIBLE_NOT_ACTIVATABLE);
       assert_equals(nested.renderSubtree, "");
       resolve();
     });
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/attribute/values.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/attribute/values.html
index a22bd69e..559c926 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/attribute/values.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/attribute/values.html
@@ -3,18 +3,19 @@
 <title>Tests rendersubtree attribute values</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<script src="../resources/utils.js"></script>
 
 <script>
 test(() => {
   const element = document.createElement("div");
 
-  element.setAttribute("rendersubtree", "invisible");
-  assert_equals(element.getAttribute("rendersubtree"), "invisible");
-  assert_equals(element.renderSubtree, "invisible");
+  element.setAttribute("rendersubtree", INVISIBLE_ACTIVATABLE);
+  assert_equals(element.getAttribute("rendersubtree"), INVISIBLE_ACTIVATABLE);
+  assert_equals(element.renderSubtree, INVISIBLE_ACTIVATABLE);
 
-  element.setAttribute("rendersubtree", "invisible activatable");
-  assert_equals(element.getAttribute("rendersubtree"), "invisible activatable");
-  assert_equals(element.renderSubtree, "invisible activatable");
+  element.setAttribute("rendersubtree", INVISIBLE_NOT_ACTIVATABLE);
+  assert_equals(element.getAttribute("rendersubtree"), INVISIBLE_NOT_ACTIVATABLE);
+  assert_equals(element.renderSubtree, INVISIBLE_NOT_ACTIVATABLE);
 
   element.setAttribute("rendersubtree", "not-visible");
   assert_equals(element.getAttribute("rendersubtree"), "not-visible");
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/containment/rendersubtree-adds-containment.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/containment/rendersubtree-adds-containment.html
index 2bae9bf1..e2f09e4 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/containment/rendersubtree-adds-containment.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/containment/rendersubtree-adds-containment.html
@@ -9,7 +9,7 @@
 
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="resources/utils.js"></script>
+<script src="../resources/utils.js"></script>
 
 <script>
 function setUp() {
@@ -20,15 +20,15 @@
 
 test(() => {
   setUp();
-  container.setAttribute("rendersubtree", "invisible");
+  container.setAttribute("rendersubtree", INVISIBLE_NOT_ACTIVATABLE);
   assert_equals(getComputedStyle(container).contain, "size layout style");
-}, "rendersubtree=invisible adds contain: size layout style;");
+}, "rendersubtree='invisible skip-activation' adds contain: size layout style;");
 
 test(() => {
   setUp();
-  container.setAttribute("rendersubtree", "invisible activatable");
+  container.setAttribute("rendersubtree", INVISIBLE_ACTIVATABLE);
   assert_equals(getComputedStyle(container).contain, "size layout style");
-}, "rendersubtree='invisible activatable' adds contain: size layout style;");
+}, "rendersubtree='invisible' adds contain: size layout style;");
 
 test(() => {
   setUp();
@@ -48,7 +48,7 @@
 
 test(() => {
   setUp();
-  container.setAttribute("rendersubtree", "invisible");
+  container.setAttribute("rendersubtree", INVISIBLE_ACTIVATABLE);
   container.style = "contain: style;";
   assert_equals(getComputedStyle(container).contain, "size layout style");
   container.style = "contain: style layout;";
@@ -59,7 +59,7 @@
 
 test(() => {
   setUp();
-  container.setAttribute("rendersubtree", "invisible");
+  container.setAttribute("rendersubtree", INVISIBLE_ACTIVATABLE);
   container.style = "contain: paint;";
   assert_equals(getComputedStyle(container).contain, "size layout style paint");
   container.style = "contain: strict;";
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/element-in-template.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/element-in-template.html
index 879e1d3d..e6cad57 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/element-in-template.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/element-in-template.html
@@ -21,20 +21,20 @@
 
     const acquirePromise = setInvisible(templateChild);
     await acquirePromise;
-    t.step(() => assert_equals(templateChild.renderSubtree, "invisible", "Can lock element in template"));
+    t.step(() => assert_equals(templateChild.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "Can lock element in template"));
 
     const adoptedNode = document.adoptNode(templateChild);
-    t.step(() => assert_equals(adoptedNode.renderSubtree, "invisible", "Adopted element is still locked"));
+    t.step(() => assert_equals(adoptedNode.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "Adopted element is still locked"));
 
     container.appendChild(adoptedNode);
-    t.step(() => assert_equals(adoptedNode.renderSubtree, "invisible", "Still locked after appended"));
+    t.step(() => assert_equals(adoptedNode.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "Still locked after appended"));
 
     await setVisible(adoptedNode);
     t.step(() => assert_equals(adoptedNode.renderSubtree, "", "Can commit"));
 
     await setInvisible(adoptedNode);
 
-    t.step(() => assert_equals(adoptedNode.renderSubtree, "invisible", "Can re-lock element"));
+    t.step(() => assert_equals(adoptedNode.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "Can re-lock element"));
 
     await setVisible(adoptedNode);
     t.step(() => assert_equals(adoptedNode.renderSubtree, "", "Can re-commit element"));
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/locked-attribute.html b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/locked-attribute.html
index d521599..4cc9acc 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/locked-attribute.html
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/locked-attribute.html
@@ -16,16 +16,16 @@
   async function runTest() {
     const container = document.getElementById("container");
     const acquire_promise = setInvisible(container);
-    t.step(() => assert_equals(container.renderSubtree, "invisible", "context before acquire finishes is locked"));
+    t.step(() => assert_equals(container.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "context before acquire finishes is locked"));
 
     await acquire_promise;
-    t.step(() => assert_equals(container.renderSubtree, "invisible", "context after acquire finishes is locked"));
+    t.step(() => assert_equals(container.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "context after acquire finishes is locked"));
 
     const update_promise = container.updateRendering();
-    t.step(() => assert_equals(container.renderSubtree, "invisible", "context during update is locked"));
+    t.step(() => assert_equals(container.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "context during update is locked"));
 
     await update_promise;
-    t.step(() => assert_equals(container.renderSubtree, "invisible", "context after update is locked"));
+    t.step(() => assert_equals(container.renderSubtree, INVISIBLE_NOT_ACTIVATABLE, "context after update is locked"));
 
     const commit_promise = setVisible(container);
     t.step(() => assert_equals(container.renderSubtree, "", "context during commit is unlocked"));
diff --git a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/resources/utils.js b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/resources/utils.js
index 3e6d56e..aa3ac0f 100644
--- a/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/resources/utils.js
+++ b/third_party/blink/web_tests/wpt_internal/display-lock/rendersubtree/resources/utils.js
@@ -1,10 +1,12 @@
+const INVISIBLE_ACTIVATABLE = "invisible";
+const INVISIBLE_NOT_ACTIVATABLE = "invisible skip-activation";
 
 function setInvisible(element) {
-  return setRenderSubtree(element, "invisible");
+  return setRenderSubtree(element, INVISIBLE_NOT_ACTIVATABLE);
 }
 
 function setInvisibleActivatable(element) {
-  return setRenderSubtree(element, "invisible activatable");
+  return setRenderSubtree(element, INVISIBLE_ACTIVATABLE);
 }
 
 function setVisible(element) {
diff --git a/third_party/inspector_protocol/README.chromium b/third_party/inspector_protocol/README.chromium
index 39092bf..a73a85c 100644
--- a/third_party/inspector_protocol/README.chromium
+++ b/third_party/inspector_protocol/README.chromium
@@ -2,7 +2,7 @@
 Short Name: inspector_protocol
 URL: https://chromium.googlesource.com/deps/inspector_protocol/
 Version: 0
-Revision: ed2845d5409a04d3c87655f0b55e87a66e806de2
+Revision: a14dad30f0e5b0fc05911856d5a20b1ffe89fd9b
 License: BSD
 License File: LICENSE
 Security Critical: yes
diff --git a/third_party/inspector_protocol/bindings/bindings.h b/third_party/inspector_protocol/bindings/bindings.h
index 6ae15ac5..50c83f0 100644
--- a/third_party/inspector_protocol/bindings/bindings.h
+++ b/third_party/inspector_protocol/bindings/bindings.h
@@ -58,10 +58,9 @@
     return is_just_ ? value_ : default_value;
   }
   bool isJust() const { return is_just_; }
-  // TODO(johannes): |is_just_| isn't reset by this operation -
-  // introduce && to ensure avoiding continued usage of |this|?
   T takeJust() {
     assert(is_just_);
+    is_just_ = false;
     return std::move(value_);
   }
 
diff --git a/third_party/inspector_protocol/code_generator.py b/third_party/inspector_protocol/code_generator.py
index 7c72cc7..5cf5a308 100755
--- a/third_party/inspector_protocol/code_generator.py
+++ b/third_party/inspector_protocol/code_generator.py
@@ -43,6 +43,9 @@
       items = [(k, os.path.join(output_base, v) if k == "output" else v)
                for (k, v) in items]
       keys, values = list(zip(*items))
+      # 'async' is a keyword since Python 3.7.
+      # Avoid namedtuple(rename=True) for compatibility with Python 2.X.
+      keys = tuple('async_' if k == 'async' else k for k in keys)
       return collections.namedtuple('X', keys)(*values)
     return json.loads(data, object_hook=json_object_hook)
 
@@ -555,7 +558,7 @@
     if not self.config.protocol.options:
       return False
     return self.check_options(self.config.protocol.options, domain, command,
-                              "async", None, False)
+                              "async_", None, False)
 
   def is_exported(self, domain, name):
     if not self.config.protocol.options:
diff --git a/third_party/inspector_protocol/templates/TypeBuilder_cpp.template b/third_party/inspector_protocol/templates/TypeBuilder_cpp.template
index 982e2c61..b1c3ab7 100644
--- a/third_party/inspector_protocol/templates/TypeBuilder_cpp.template
+++ b/third_party/inspector_protocol/templates/TypeBuilder_cpp.template
@@ -385,7 +385,6 @@
       {% endif %}
     return;
     {% else %}
-    std::unique_ptr<DispatcherBase::WeakPtr> weak = weakPtr();
     std::unique_ptr<{{command_name_title}}CallbackImpl> callback(new {{command.name | to_title_case}}CallbackImpl(weakPtr(), callId, method, message));
     m_backend->{{command.name | to_method_case}}(
       {%- for property in command.parameters -%}
diff --git a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
index e32cb9d..ae5646a 100644
--- a/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
+++ b/third_party/webxr_test_pages/webxr-samples/proposals/phone-ar-hit-test.html
@@ -62,6 +62,7 @@
       import {QueryArgs} from '../js/cottontail/src/util/query-args.js';
       import {FallbackHelper} from '../js/cottontail/src/util/fallback-helper.js';
       import {Node} from '../js/cottontail/src/core/node.js';
+      import {RayNode} from '../js/cottontail/src/nodes/ray-node.js';
       import {DropShadowNode} from '../js/cottontail/src/nodes/drop-shadow.js';
       import {vec3} from '../js/cottontail/src/math/gl-matrix.js';
 
@@ -75,6 +76,10 @@
       // XR globals.
       let xrButton = null;
       let xrRefSpace = null;
+      let xrLocalFloor = null;
+      let xrViewerSpace = null;
+      let xrOffsetSpace = null;
+      let xrViewerSpaceHitTestSource = null;
 
       // WebGL scene globals.
       let gl = null;
@@ -82,6 +87,35 @@
       let scene = new Scene();
       scene.enableStats(false);
 
+      // Visualise the origin.
+      {
+        let xRay = new RayNode({direction : [5, 0, 0], baseColor : [1, 0, 0, 1]});
+        let yRay = new RayNode({direction : [0, 5, 0], baseColor : [0, 1, 0, 1]});
+        let zRay = new RayNode({direction : [0, 0, 5], baseColor : [0, 0, 1, 1]});
+
+        scene.addNode(xRay);
+        scene.addNode(yRay);
+        scene.addNode(zRay);
+      }
+
+      // Visualise the offset space.
+      let offsetSpaceNode = new Node();
+      offsetSpaceNode.visible = false;
+
+      {
+        let xRay = new RayNode({direction : [5, 0, 0], baseColor : [1, 0, 0, 1]});
+        let yRay = new RayNode({direction : [0, 5, 0], baseColor : [0, 1, 0, 1]});
+        let zRay = new RayNode({direction : [0, 0, 5], baseColor : [0, 0, 1, 1]});
+
+        offsetSpaceNode.addNode(xRay);
+        offsetSpaceNode.addNode(yRay);
+        offsetSpaceNode.addNode(zRay);
+      }
+
+      scene.addNode(offsetSpaceNode);
+
+      // -----------------------------------------------------------------------
+
       let arObject = new Node();
       arObject.visible = false;
       scene.addNode(arObject);
@@ -114,7 +148,10 @@
       }
 
       function onRequestSession() {
-        navigator.xr.requestSession('immersive-ar').then((session) => {
+        let options = {
+          requiredFeatures: ['local-floor'],
+        };
+        navigator.xr.requestSession('immersive-ar', options).then((session) => {
               session.mode = 'immersive-ar';
               xrButton.setSession(session);
               onSessionStarted(session);
@@ -124,6 +161,7 @@
       function onSessionStarted(session) {
         session.addEventListener('end', onSessionEnded);
         session.addEventListener('select', onSelect);
+        session.addEventListener('inputsourceschange', onInputSourcesChange);
 
         if (!gl) {
           gl = createWebGLContext({
@@ -139,7 +177,36 @@
 
         session.requestReferenceSpace('local').then((refSpace) => {
           xrRefSpace = refSpace;
-          session.requestAnimationFrame(onXRFrame);
+
+          session.requestReferenceSpace('local-floor').then((localFloor) => {
+            xrLocalFloor = localFloor;
+
+            session.requestReferenceSpace('viewer').then((viewerSpace) => {
+              xrViewerSpace = viewerSpace;
+
+              xrOffsetSpace = xrViewerSpace.getOffsetReferenceSpace(
+                new XRRigidTransform(new DOMPointReadOnly(0.1, 0, 0.5))
+              );
+
+              if (useReticle.checked) {
+                console.debug("Requesting hit test source.");
+                session.requestHitTestSource({
+                    space : xrViewerSpace,
+                    //space : xrLocalFloor, // WIP: change back to viewer
+                    //space : xrOffsetSpace, // WIP: change back to viewer
+                    offsetRay : new XRRay()
+                    //offsetRay : new XRRay(new DOMPointReadOnly(0,.5,-.5), new DOMPointReadOnly(0, -0.5, -1)) // WIP: change back to default
+                  }).then((hitTestSource) => {
+                  console.debug("Hit test source created.");
+                  xrViewerSpaceHitTestSource = hitTestSource;
+                }).catch(error => {
+                  console.error("Error when requesting hit test source", error);
+                });
+              }
+
+              session.requestAnimationFrame(onXRFrame);
+            });
+          });
         });
       }
 
@@ -176,10 +243,11 @@
 
       let rayOrigin = vec3.create();
       let rayDirection = vec3.create();
+
       function onSelect(event) {
         if (useReticle.checked && arObject.visible) {
           // If we're using the reticle then we've already got a mesh positioned
-          // at the latest hit point and we should just use it's matrix to save
+          // at the latest hit point and we should just use its matrix to save
           // an unnecessary requestHitTest call.
           addARObjectAt(arObject.matrix);
         } else {
@@ -207,34 +275,36 @@
         }
       }
 
+      function onInputSourcesChange(inputSourcesChangeEvent) {
+        console.debug("Input sources changed!", inputSourcesChangeEvent);
+      }
+
       // Called every time a XRSession requests that a new frame be drawn.
       function onXRFrame(t, frame) {
         let session = frame.session;
         let pose = frame.getViewerPose(xrRefSpace);
+        let viewerSpacePose = frame.getPose(xrViewerSpace, xrRefSpace);
+        let offsetSpacePose = frame.getPose(xrOffsetSpace, xrRefSpace);
 
-        // If requested, use the pose to cast a reticle into the scene using a
-        // continuous hit test. For the moment we're just using the flower
-        // as the "reticle".
-        if (useReticle.checked && pose && pose.transform.matrix) {
-          vec3.set(rayOrigin, 0, 0, 0);
-          vec3.transformMat4(rayOrigin, rayOrigin, pose.transform.matrix);
+        if(offsetSpacePose) {
+          offsetSpaceNode.matrix = offsetSpacePose.transform.matrix;
+          offsetSpaceNode.visible = true;
+        } else {
+          offsetSpaceNode.visible = false;
+        }
 
-          vec3.set(rayDirection, 0, 0, -1);
-          vec3.transformMat4(rayDirection, rayDirection, pose.transform.matrix);
-          vec3.sub(rayDirection, rayDirection, rayOrigin);
-          vec3.normalize(rayDirection, rayDirection);
+        if (useReticle.checked && xrViewerSpaceHitTestSource) {
+          let results = frame.getHitTestResults(xrViewerSpaceHitTestSource);
 
-          let ray = new XRRay(DOMPointFromVec3(rayOrigin), DOMPointFromVec3(rayDirection));
-          session.requestHitTest(ray, xrRefSpace).then((results) => {
-            // When the hit test returns use it to place our proxy object.
-            if (results.length) {
-              let hitResult = results[0];
-              arObject.visible = true;
-              arObject.matrix = hitResult.hitMatrix;
-            } else {
-              arObject.visible = false;
-            }
-          });
+          // Use the results to place our proxy object.
+          if (results.length) {
+            let hitResult = results[0];
+            arObject.visible = true;
+            arObject.matrix = hitResult.getPose(xrRefSpace).transform.matrix;
+          } else {
+            arObject.visible = false;
+          }
+
         } else {
           arObject.visible = false;
         }
diff --git a/tools/idl_parser/idl_parser.py b/tools/idl_parser/idl_parser.py
index ceedb91..e6c76497 100755
--- a/tools/idl_parser/idl_parser.py
+++ b/tools/idl_parser/idl_parser.py
@@ -274,14 +274,20 @@
                            | ATTRIBUTE
                            | CALLBACK
                            | CONST
+                           | CONSTRUCTOR
                            | DELETER
                            | DICTIONARY
                            | ENUM
                            | GETTER
                            | INCLUDES
                            | INHERIT
+                           | INTERFACE
+                           | ITERABLE
+                           | MAPLIKE
                            | NAMESPACE
                            | PARTIAL
+                           | REQUIRED
+                           | SETLIKE
                            | SETTER
                            | STATIC
                            | STRINGIFIER
@@ -555,13 +561,22 @@
     p[0] = self.BuildNamed('Operation', p, 1, arguments)
 
   def p_OptionalOperationName(self, p):
-    """OptionalOperationName : identifier
+    """OptionalOperationName : OperationName
                              |"""
     if len(p) > 1:
       p[0] = p[1]
     else:
       p[0] = ''
 
+  def p_OperationName(self, p):
+    """OperationName : OperationNameKeyword
+                     | identifier"""
+    p[0] = p[1]
+
+  def p_OperationNameKeyword(self, p):
+    """OperationNameKeyword : INCLUDES"""
+    p[0] = p[1]
+
   def p_ArgumentList(self, p):
     """ArgumentList : Argument Arguments
                     |"""
diff --git a/tools/idl_parser/test_parser/interface_web.idl b/tools/idl_parser/test_parser/interface_web.idl
index 10ac458..97dc116 100644
--- a/tools/idl_parser/test_parser/interface_web.idl
+++ b/tools/idl_parser/test_parser/interface_web.idl
@@ -152,6 +152,33 @@
 };
 
 /** TREE
+ *Interface(IFaceAllowedKeywords)
+ *  Attribute(async)
+ *    Type()
+ *      PrimitiveType(long)
+ *  Operation(includes)
+ *    Arguments()
+ *      Argument(async)
+ *        Type()
+ *          PrimitiveType(long)
+ *      Argument(constructor)
+ *        Type()
+ *          PrimitiveType(long)
+ *    Type()
+ *      PrimitiveType(void)
+ *  Attribute(constructor)
+ *    Type()
+ *      PrimitiveType(long)
+ *  Error(Unexpected constructor.)
+ */
+interface IFaceAllowedKeywords {
+  attribute long async;
+  void includes(long async, long constructor);
+  attribute long _constructor;
+  attribute long constructor;
+};
+
+/** TREE
  *Interface(MyIfaceDefalutValue)
  *  Operation(foo)
  *    Arguments()
@@ -667,19 +694,3 @@
  *  REFERENCE: Bar
  */
 Foo includes Bar;
-
-/** TREE
- *Interface(IFaceWithKeyword)
- *  Operation(includes)
- *    Arguments()
- *      Argument(val)
- *        Type()
- *          StringType(DOMString)
- *    Type()
- *      PrimitiveType(void)
- */
-interface IFaceWithKeyword {
-  // '_' prefix needs to be removed in tokenization.
-  // https://heycam.github.io/webidl/#idl-names
-  void _includes(DOMString val);
-};
diff --git a/tools/metrics/actions/actions.xml b/tools/metrics/actions/actions.xml
index 4ab8cc8..4f55d66 100644
--- a/tools/metrics/actions/actions.xml
+++ b/tools/metrics/actions/actions.xml
@@ -1863,6 +1863,9 @@
 </action>
 
 <action name="Android.DownloadManager.List.Selection.Menu.Images.Action">
+  <obsolete>
+    Deprecated October 2019. Feature dropped during implementation review.
+  </obsolete>
   <owner>dtrainor@chromium.org</owner>
   <description>
     User triggered a menu action on the images section header of download home.
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index fd88c0e..9a236d8e 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1241,6 +1241,10 @@
 </enum>
 
 <enum name="Android.DownloadManager.List.Section.Menu.Actions">
+  <obsolete>
+    Deprecated 10/2019. Section menu button was removed during Download Home V2
+    implementation review.
+  </obsolete>
   <int value="0" label="START_SELECTING"/>
   <int value="1" label="SHARE_ALL"/>
   <int value="2" label="DELETE_ALL"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index a76bc737..84399ac 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -2346,6 +2346,10 @@
 
 <histogram name="Android.DownloadManager.List.Section.Menu.Images.Action"
     enum="Android.DownloadManager.List.Section.Menu.Actions">
+  <obsolete>
+    Deprecated October 2019. The feature was dropped during implementation
+    review.
+  </obsolete>
   <owner>dtrainor@chromium.org</owner>
   <owner>shaktisahu@chromium.org</owner>
   <owner>clank-downloads@google.com</owner>
@@ -144395,6 +144399,9 @@
 </histogram>
 
 <histogram name="Sync.Preferences.SyncingUnknownPrefs" units="prefs">
+  <obsolete>
+    Deprecated in M79 because support for lazy pref registration was removed.
+  </obsolete>
   <owner>tschumann@chromium.org</owner>
   <owner>treib@chromium.org</owner>
   <summary>
diff --git a/tools/perf/core/results_processor/processor.py b/tools/perf/core/results_processor/processor.py
index 4e22cd5f..d16a0c7 100644
--- a/tools/perf/core/results_processor/processor.py
+++ b/tools/perf/core/results_processor/processor.py
@@ -20,7 +20,6 @@
 from core.results_processor import formatters
 from core.results_processor import util
 
-from tracing.trace_data import trace_data
 from tracing.value.diagnostics import generic_set
 from tracing.value.diagnostics import reserved_infos
 from tracing.value import histogram_set
@@ -47,7 +46,7 @@
   intermediate_results = _LoadIntermediateResults(
       os.path.join(options.intermediate_dir, TELEMETRY_RESULTS))
 
-  AggregateTraces(intermediate_results)
+  _AggregateTraces(intermediate_results)
 
   UploadArtifacts(
       intermediate_results, options.upload_bucket, options.results_label)
@@ -100,7 +99,7 @@
   return results
 
 
-def AggregateTraces(intermediate_results):
+def _AggregateTraces(intermediate_results):
   """Replace individual traces with an aggregate one for each test result.
 
   For each test run with traces, generates an aggregate HTML trace. Removes
@@ -110,16 +109,10 @@
     artifacts = result.get('outputArtifacts', {})
     traces = [name for name in artifacts if name.startswith('trace/')]
     if len(traces) > 0:
-      if compute_metrics.HTML_TRACE_NAME not in artifacts:
-        trace_files = [artifacts[name]['filePath'] for name in traces]
-        html_path = os.path.join(
-            os.path.dirname(os.path.commonprefix(trace_files)),
-            compute_metrics.HTML_TRACE_NAME)
-        trace_data.SerializeAsHtml(trace_files, html_path)
-        artifacts[compute_metrics.HTML_TRACE_NAME] = {
-          'filePath': html_path,
-          'contentType': 'text/html',
-        }
+      # For now, the html trace is generated by Telemetry, so it should be there
+      # already. All we need to do is remove individual traces from the dict.
+      # TODO(crbug.com/981349): replace this with actual aggregation code.
+      assert compute_metrics.HTML_TRACE_NAME in artifacts
       for trace in traces:
         del artifacts[trace]
 
diff --git a/tools/perf/core/results_processor/processor_test.py b/tools/perf/core/results_processor/processor_test.py
index b25893d4..a755845 100644
--- a/tools/perf/core/results_processor/processor_test.py
+++ b/tools/perf/core/results_processor/processor_test.py
@@ -24,7 +24,6 @@
 from core.results_processor import processor
 from core.results_processor import testing
 
-from tracing.value.diagnostics import generic_set
 from tracing.value import histogram
 from tracing.value import histogram_set
 from tracing_build import render_histograms_viewer
@@ -269,45 +268,12 @@
 
     out_histograms = histogram_set.HistogramSet()
     out_histograms.ImportDicts(results)
+    self.assertEqual(len(out_histograms), 4)
+    self.assertIsNotNone(out_histograms.GetHistogramNamed('foo'))
 
-    # sampleMetric records a histogram with the name 'foo'.
-    hist = out_histograms.GetHistogramNamed('foo')
-    self.assertIsNotNone(hist)
-    self.assertEqual(hist.diagnostics['traceUrls'],
-                     generic_set.GenericSet(['gs://trace.html']))
-
-  def testHistogramsOutputNoAggregatedTrace(self):
-    json_trace = os.path.join(self.output_dir, 'trace.json')
-    with open(json_trace, 'w') as f:
-      json.dump({'traceEvents': []}, f)
-
-    self.SerializeIntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story',
-                output_artifacts={'trace/json': testing.Artifact(json_trace)},
-                tags=['tbmv2:sampleMetric'],
-            ),
-        ],
-    )
-
-    processor.main([
-        '--output-format', 'histograms',
-        '--output-dir', self.output_dir,
-        '--intermediate-dir', self.intermediate_dir,
-    ])
-
-    with open(os.path.join(
-        self.output_dir, histograms_output.OUTPUT_FILENAME)) as f:
-      results = json.load(f)
-
-    out_histograms = histogram_set.HistogramSet()
-    out_histograms.ImportDicts(results)
-
-    # sampleMetric records a histogram with the name 'foo'.
-    hist = out_histograms.GetHistogramNamed('foo')
-    self.assertIsNotNone(hist)
-    self.assertIn('traceUrls', hist.diagnostics)
+    diag_values = [list(v) for v in  out_histograms.shared_diagnostics]
+    self.assertEqual(len(diag_values), 1)
+    self.assertIn(['gs://trace.html'], diag_values)
 
   def testHtmlOutput(self):
     hist_file = os.path.join(self.output_dir,
diff --git a/tools/perf/core/results_processor/processor_unittest.py b/tools/perf/core/results_processor/processor_unittest.py
index d2de1e5..192f37e 100644
--- a/tools/perf/core/results_processor/processor_unittest.py
+++ b/tools/perf/core/results_processor/processor_unittest.py
@@ -94,55 +94,3 @@
             'src_abc_123_20191001T120000_54321/benchmark/story/trace.html',
             '/trace.html'
         )
-
-  def testAggregateTraces(self):
-    in_results = testing.IntermediateResults(
-        test_results=[
-            testing.TestResult(
-                'benchmark/story1',
-                output_artifacts={
-                    'trace/1.json': testing.Artifact(
-                        '/artifacts/test_run/story1/trace/1.json'),
-                },
-            ),
-            testing.TestResult(
-                'benchmark/story2',
-                output_artifacts={
-                    'trace/1.json': testing.Artifact(
-                        '/artifacts/test_run/story2/trace/1.json'),
-                    'trace/2.json': testing.Artifact(
-                        '/artifacts/test_run/story2/trace/2.json'),
-                },
-            ),
-        ],
-    )
-
-    with mock.patch('tracing.trace_data.trace_data.SerializeAsHtml') as patch:
-      processor.AggregateTraces(in_results)
-
-    call_list = [list(call[0]) for call in patch.call_args_list]
-    self.assertEqual(len(call_list), 2)
-    for call in call_list:
-      call[0] = set(call[0])
-    self.assertIn(
-        [
-            set(['/artifacts/test_run/story1/trace/1.json']),
-            '/artifacts/test_run/story1/trace/trace.html',
-        ],
-        call_list
-    )
-    self.assertIn(
-        [
-            set([
-                '/artifacts/test_run/story2/trace/1.json',
-                '/artifacts/test_run/story2/trace/2.json',
-            ]),
-            '/artifacts/test_run/story2/trace/trace.html',
-        ],
-        call_list
-    )
-
-    for result in in_results['testResults']:
-      artifacts = result['outputArtifacts']
-      self.assertEqual(len(artifacts), 1)
-      self.assertEqual(artifacts.keys()[0], 'trace.html')
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 6ba2b78..4490ef1 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -58,6 +58,8 @@
 crbug.com/978159 [ android-nexus-5x android-webview ] blink_perf.canvas/* [ Skip ]
 crbug.com/994912 [ android-webview ] blink_perf.canvas/transferFromImageBitmap_RAF.html?RAF [ Skip ]
 crbug.com/994912 [ android-webview ] blink_perf.canvas/transferFromImageBitmap.html [ Skip ]
+crbug.com/1013618 [ android-webview ] blink_perf.canvas/putImageData.html [ Skip ]
+crbug.com/1013618 [ android-webview ] blink_perf.canvas/putImageData_RAF.html?RAF [ Skip ]
 
 # Benchmark: blink_perf.css
 crbug.com/891878 [ android-nexus-5x android-webview ] blink_perf.css/CustomPropertiesVarAlias.html [ Skip ]
diff --git a/tools/perf/page_sets/rendering/maps.py b/tools/perf/page_sets/rendering/maps.py
index 4e04c09..932e538 100644
--- a/tools/perf/page_sets/rendering/maps.py
+++ b/tools/perf/page_sets/rendering/maps.py
@@ -25,8 +25,7 @@
 """
   BASE_NAME = 'maps_perf_test'
   URL = 'file://performance.html'
-  TAGS = [story_tags.REQUIRED_WEBGL, story_tags.MAPS,
-    story_tags.REPRESENTATIVE_MOBILE]
+  TAGS = [story_tags.REQUIRED_WEBGL, story_tags.MAPS]
 
   def __init__(self,
                page_set,
diff --git a/tools/perf/page_sets/rendering/motionmark.py b/tools/perf/page_sets/rendering/motionmark.py
index 3af6dcf..ac39b626 100644
--- a/tools/perf/page_sets/rendering/motionmark.py
+++ b/tools/perf/page_sets/rendering/motionmark.py
@@ -122,7 +122,6 @@
 class MotionmarkHTMLCSSBouncingBlendCircles25(MotionMarkPage):
   BASE_NAME = 'motionmark_html_css_bouncing_blend_circles_25'
   URL = MotionMarkPage.GetUrl('HTML suite', 'CSS bouncing blend circles', 25)
-  TAGS = MotionMarkPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 # Why: MotionMark HTML case """
diff --git a/tools/perf/page_sets/rendering/top_real_world_desktop.py b/tools/perf/page_sets/rendering/top_real_world_desktop.py
index 0667a276..a6909a55 100644
--- a/tools/perf/page_sets/rendering/top_real_world_desktop.py
+++ b/tools/perf/page_sets/rendering/top_real_world_desktop.py
@@ -40,8 +40,6 @@
   BASE_NAME = 'google_web_search'
   YEAR = '2018'
   URL = 'https://www.google.com/#hl=en&q=barack+obama'
-  TAGS = TopRealWorldDesktopPage.TAGS + [
-      story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
   def __init__(self,
                page_set,
@@ -153,7 +151,6 @@
   YEAR = '2018'
   # pylint: disable=line-too-long
   URL = 'http://en.blog.wordpress.com/2012/09/04/freshly-pressed-editors-picks-for-august-2012/'
-  TAGS = TopRealWorldDesktopPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
   def __init__(self,
                page_set,
@@ -264,7 +261,6 @@
   BASE_NAME = 'pinterest'
   YEAR = '2018'
   URL = 'https://www.pinterest.com/search/pins/?q=flowers&rs=typed'
-  TAGS = TopRealWorldDesktopPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
   def __init__(self,
                page_set,
@@ -301,6 +297,10 @@
   BASE_NAME = 'twitch'
   YEAR = '2018'
   URL = 'https://www.twitch.tv'
+  TAGS = TopRealWorldDesktopPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
   def __init__(self,
                page_set,
@@ -428,7 +428,6 @@
   BASE_NAME = 'yahoo_news'
   YEAR = '2018'
   URL = 'http://news.yahoo.com'
-  TAGS = TopRealWorldDesktopPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class CNNNews2018Page(TopRealWorldDesktopPage):
@@ -472,7 +471,6 @@
   BASE_NAME = 'yahoo_sports'
   YEAR = '2018'
   URL = 'http://sports.yahoo.com/'
-  TAGS = TopRealWorldDesktopPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class TechCrunch2018Page(TopRealWorldDesktopPage):
diff --git a/tools/perf/page_sets/rendering/tough_animation_cases.py b/tools/perf/page_sets/rendering/tough_animation_cases.py
index ad66356..abade9f 100644
--- a/tools/perf/page_sets/rendering/tough_animation_cases.py
+++ b/tools/perf/page_sets/rendering/tough_animation_cases.py
@@ -44,6 +44,10 @@
   """Why: Tests the balls animation implemented with Javascript and canvas."""
   BASE_NAME = 'balls_javascript_canvas'
   URL = 'file://../tough_animation_cases/balls_javascript_canvas.html'
+  TAGS = ToughAnimationPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class BallsJavascriptCssPage(ToughAnimationPage):
@@ -73,6 +77,7 @@
   """
   BASE_NAME = 'balls_css_transition_2_properties'
   URL = 'file://../tough_animation_cases/balls_css_transition_2_properties.html'
+  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class BallsCssTransition40PropertiesPage(ToughAnimationPage):
@@ -133,6 +138,7 @@
   BASE_NAME = 'css_transitions_inline_style'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_transitions_simultaneous_by_updating_inline_style.html?N=0316'
+  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class CssTransitionsStaggeredNewElementPage(ToughAnimationPage):
@@ -250,6 +256,7 @@
   BASE_NAME = 'css_animations_simultaneous_inline_style'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_animations_simultaneous_by_updating_inline_style.html?N=0316'
+  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class CssAnimationsStaggeredNewElementPage(ToughAnimationPage):
@@ -304,7 +311,6 @@
   BASE_NAME = 'css_animations_staggered_infinite_iterations'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_animations_staggered_infinite_iterations.html?N=0316'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class CssAnimationsTriggeredStyleElementPage(ToughAnimationPage):
@@ -341,7 +347,6 @@
   BASE_NAME = 'web_animations_many_keyframes'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/web_animations_many_keyframes.html?N=0316'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class WebAnimationsSetCurrentTimePage(ToughAnimationPage):
@@ -376,6 +381,10 @@
   BASE_NAME = 'web_animations_staggered_infinite_iterations'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/web_animations_staggered_infinite_iterations.html?N=0316'
+  TAGS = ToughAnimationPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class WebAnimationsStaggeredTriggeringPage(ToughAnimationPage):
@@ -383,6 +392,7 @@
   BASE_NAME = 'web_animations_staggered_triggering_page'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/web_animations_staggered_triggering.html?N=0316'
+  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class CssValueTypeColorPage(ToughAnimationPage):
@@ -397,6 +407,10 @@
   BASE_NAME = 'css_value_type_filter'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_filter.html?api=css_animations&N=0316'
+  TAGS = ToughAnimationPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class CssValueTypeLengthPage(ToughAnimationPage):
@@ -411,7 +425,6 @@
   BASE_NAME = 'css_value_type_length_complex'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_length_complex.html?api=css_animations&N=0316'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class CssValueTypeLengthSimplePage(ToughAnimationPage):
@@ -434,8 +447,9 @@
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_shadow.html?api=css_animations&N=0316'
   TAGS = ToughAnimationPage.TAGS + [
-      story_tags.REPRESENTATIVE_WIN_DESKTOP,
-      story_tags.REPRESENTATIVE_MAC_DESKTOP,
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_WIN_DESKTOP,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
   ]
 
 
@@ -465,7 +479,6 @@
   BASE_NAME = 'web_animation_value_type_length_3d'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_length_3d.html?api=web_animations&N=0316'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class WebAnimationValueTypeLengthComplexPage(ToughAnimationPage):
@@ -473,7 +486,6 @@
   BASE_NAME = 'web_animation_value_type_length_complex'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_length_complex.html?api=web_animations&N=0316'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class WebAnimationValueTypeLengthSimplePage(ToughAnimationPage):
@@ -488,7 +500,6 @@
   BASE_NAME = 'web_animation_value_type_path'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_path.html?api=web_animations&N=0316'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class WebAnimationValueTypeShadowPage(ToughAnimationPage):
@@ -503,6 +514,7 @@
   BASE_NAME = 'web_animation_value_type_transform_complex'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_transform_complex.html?api=web_animations&N=0316'
+  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class WebAnimationValueTypeTransformSimplePage(ToughAnimationPage):
@@ -510,6 +522,10 @@
   BASE_NAME = 'web_animation_value_type_transform_simple'
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/css_value_type_transform_simple.html?api=web_animations&N=0316'
+  TAGS = ToughAnimationPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class CompositorHeavyAnimationPage(ToughAnimationPage):
@@ -518,7 +534,6 @@
   """
   BASE_NAME = 'compositor_heavy_animation'
   URL = 'file://../tough_animation_cases/compositor_heavy_animation.html?N=0200'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class KeyframedAnimationsPage(ToughAnimationPage):
@@ -540,6 +555,10 @@
   BASE_NAME = 'transform_transitions_js_block'
   URL = 'file://../tough_animation_cases/transform_transition_js_block.html'
   NEED_MEASUREMENT_READY = False
+  TAGS = ToughAnimationPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class MixBlendModeAnimationDifferencePage(ToughAnimationPage):
@@ -568,6 +587,10 @@
   # pylint: disable=line-too-long
   URL = 'file://../tough_animation_cases/mix_blend_mode_animation_screen.html'
   NEED_MEASUREMENT_READY = False
+  TAGS = ToughAnimationPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class MixAnimationPropagatingIsolationPage(ToughAnimationPage):
@@ -584,7 +607,6 @@
   """Why: Login page is slow because of ineffecient transform operations."""
   BASE_NAME = 'microsoft_performance'
   URL = 'http://ie.microsoft.com/testdrive/performance/robohornetpro/'
-  TAGS = ToughAnimationPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
   NEED_MEASUREMENT_READY = False
 
 
diff --git a/tools/perf/page_sets/rendering/tough_canvas_cases.py b/tools/perf/page_sets/rendering/tough_canvas_cases.py
index 19e8cd0..edc5867 100644
--- a/tools/perf/page_sets/rendering/tough_canvas_cases.py
+++ b/tools/perf/page_sets/rendering/tough_canvas_cases.py
@@ -41,19 +41,18 @@
 class GeoAPIsPage(ToughCanvasPage):
   BASE_NAME = 'geo_apis'
   URL = 'http://geoapis.appspot.com/agdnZW9hcGlzchMLEgtFeGFtcGxlQ29kZRjh1wIM'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class RunwayPage(ToughCanvasPage):
   BASE_NAME = 'runway'
   URL = 'http://runway.countlessprojects.com/prototype/performance_test.html'
+  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class MicrosoftFishIETankPage(ToughCanvasPage):
   BASE_NAME = 'microsoft_fish_ie_tank'
   # pylint: disable=line-too-long
   URL = 'http://ie.microsoft.com/testdrive/Performance/FishIETank/Default.html'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class MicrosoftSpeedReadingPage(ToughCanvasPage):
@@ -65,13 +64,11 @@
 class Kevs3DPage(ToughCanvasPage):
   BASE_NAME = 'kevs_3d'
   URL = 'http://www.kevs3d.co.uk/dev/canvask3d/k3d_test.html'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class MegiDishPage(ToughCanvasPage):
   BASE_NAME = 'megi_dish'
   URL = 'http://www.megidish.net/awjs/'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class ManInBluePage(ToughCanvasPage):
@@ -82,7 +79,6 @@
 class Mix10KPage(ToughCanvasPage):
   BASE_NAME = 'mix_10k'
   URL = 'http://mix10k.visitmix.com/Entry/Details/169'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class CraftyMindPage(ToughCanvasPage):
@@ -93,6 +89,7 @@
 class ChipTunePage(ToughCanvasPage):
   BASE_NAME = 'chip_tune'
   URL = 'http://www.chiptune.com/starfield/starfield.html'
+  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class JarroDoversonPage(ToughCanvasPage):
@@ -134,7 +131,6 @@
 class MicrosoftVideoCityPage(ToughCanvasPage):
   BASE_NAME = 'microsoft_video_city'
   URL = 'http://ie.microsoft.com/testdrive/Graphics/VideoCity/Default.html'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class MicrosoftAsteroidBeltPage(ToughCanvasPage):
@@ -146,15 +142,12 @@
 class SmashCatPage(ToughCanvasPage):
   BASE_NAME = 'smash_cat'
   URL = 'http://www.smashcat.org/av/canvas_test/'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP,
-    story_tags.REPRESENTATIVE_MOBILE]
 
 
 class BouncingBallsShadowPage(ToughCanvasPage):
   BASE_NAME = 'bouncing_balls_shadow'
   # pylint: disable=line-too-long
   URL = 'file://../tough_canvas_cases/canvas2d_balls_common/bouncing_balls.html?ball=image_with_shadow&back=image'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class BouncingBalls15Page(ToughCanvasPage):
@@ -171,12 +164,12 @@
 class CanvasAnimationNoClearPage(ToughCanvasPage):
   BASE_NAME = 'canvas_animation_no_clear'
   URL = 'file://../tough_canvas_cases/canvas-animation-no-clear.html'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class CanvasToBlobPage(ToughCanvasPage):
   BASE_NAME = 'canvas_to_blob'
   URL = 'file://../tough_canvas_cases/canvas_toBlob.html'
+  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class ManyImagesPage(ToughCanvasPage):
@@ -192,7 +185,6 @@
 class CanvasLinesPage(ToughCanvasPage):
   BASE_NAME = 'canvas_lines'
   URL = 'file://../tough_canvas_cases/rendering_throughput/canvas_lines.html'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class PutGetImageDataPage(ToughCanvasPage):
@@ -209,13 +201,16 @@
 class StrokeShapesPage(ToughCanvasPage):
   BASE_NAME = 'stroke_shapes'
   URL = 'file://../tough_canvas_cases/rendering_throughput/stroke_shapes.html'
-  TAGS = ToughCanvasPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class BouncingClippedRectanglesPage(ToughCanvasPage):
   BASE_NAME = 'bouncing_clipped_rectangles'
   # pylint: disable=line-too-long
   URL = 'file://../tough_canvas_cases/rendering_throughput/bouncing_clipped_rectangles.html'
+  TAGS = ToughCanvasPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class BouncingGradientCirclesPage(ToughCanvasPage):
diff --git a/tools/perf/page_sets/rendering/tough_compositor_cases.py b/tools/perf/page_sets/rendering/tough_compositor_cases.py
index 964588b..31bbcb4 100644
--- a/tools/perf/page_sets/rendering/tough_compositor_cases.py
+++ b/tools/perf/page_sets/rendering/tough_compositor_cases.py
@@ -63,13 +63,14 @@
 class CCPosterCirclePage(ToughCompositorWaitPage):
   BASE_NAME = 'cc_poster_circle'
   URL = 'http://jsbin.com/falefice/1/quiet?CC_POSTER_CIRCLE'
-  TAGS = ToughCompositorWaitPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
+  TAGS = ToughCompositorWaitPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 # Why: JS poster circle animates/commits many layers """
 class JSPosterCirclePage(ToughCompositorWaitPage):
   BASE_NAME = 'js_poster_circle'
   URL = 'http://jsbin.com/giqafofe/1/quiet?JS_POSTER_CIRCLE'
+  TAGS = ToughCompositorWaitPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 # Why: JS invalidation does lots of uploads """
@@ -82,6 +83,10 @@
 class NewTilingsPage(ToughCompositorWaitPage):
   BASE_NAME = 'new_tilings'
   URL = 'http://jsbin.com/covoqi/1/quiet?NEW_TILINGS'
+  TAGS = ToughCompositorWaitPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 # Why: CSS property update baseline """
@@ -121,7 +126,6 @@
   SUPPORTED_PLATFORMS = platforms.ALL_PLATFORMS
   URL = ('file://../../../../chrome/test/data/perf/tough_compositor_cases/'
          'css_opacity_plus_n_layers.html?layer_count=306&visible_layers=46')
-  TAGS = ToughCompositorWaitPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 # Why: JS driven CSS property change on 1 layer baseline """
@@ -162,7 +166,6 @@
   SUPPORTED_PLATFORMS = platforms.ALL_PLATFORMS
   URL = ('file://../../../../chrome/test/data/perf/tough_compositor_cases/'
          'js_opacity_plus_n_layers.html?layer_count=306&visible_layers=46')
-  TAGS = ToughCompositorWaitPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 # Why: Painting 1 layer baseline """
@@ -339,7 +342,3 @@
   SUPPORTED_PLATFORMS = platforms.ALL_PLATFORMS
   URL = ('file://../../../../chrome/test/data/perf/tough_compositor_cases/'
          'infinite_scroll_root_fixed_n_layers.html?layer_count=306')
-  TAGS = InfiniteScrollRootNLayersPage.TAGS + [
-      story_tags.REPRESENTATIVE_WIN_DESKTOP,
-      story_tags.REPRESENTATIVE_MAC_DESKTOP,
-  ]
diff --git a/tools/perf/page_sets/rendering/tough_filters_cases.py b/tools/perf/page_sets/rendering/tough_filters_cases.py
index be33a82..a6a73457 100644
--- a/tools/perf/page_sets/rendering/tough_filters_cases.py
+++ b/tools/perf/page_sets/rendering/tough_filters_cases.py
@@ -30,6 +30,7 @@
 class FilterTerrainSVGPage(ToughFiltersCasesPage):
   BASE_NAME = 'filter_terrain_svg'
   URL = 'http://letmespellitoutforyou.com/samples/svg/filter_terrain.svg'
+  TAGS = ToughFiltersCasesPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class AnalogClockSVGPage(ToughFiltersCasesPage):
@@ -42,8 +43,6 @@
   URL = ('http://web.archive.org/web/20150502135732/'
          'http://ie.microsoft.com/testdrive/Performance/'
          'Pirates/Default.html')
-  TAGS = rendering_story.RenderingStory.TAGS + [
-    story_tags.REPRESENTATIVE_MOBILE]
 
   def __init__(self,
                page_set,
diff --git a/tools/perf/page_sets/rendering/tough_path_rendering_cases.py b/tools/perf/page_sets/rendering/tough_path_rendering_cases.py
index cb6790d..d4f1516 100644
--- a/tools/perf/page_sets/rendering/tough_path_rendering_cases.py
+++ b/tools/perf/page_sets/rendering/tough_path_rendering_cases.py
@@ -24,7 +24,7 @@
   BASE_NAME = 'motion_mark_canvas_fill_shapes'
   # pylint: disable=line-too-long
   URL = 'http://rawgit.com/WebKit/webkit/master/PerformanceTests/MotionMark/developer.html?test-name=Fillshapes&test-interval=20&display=minimal&tiles=big&controller=fixed&frame-rate=50&kalman-process-error=1&kalman-measurement-error=4&time-measurement=performance&suite-name=Canvassuite&complexity=1000'
-  TAGS = ToughPathRenderingPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
+  TAGS = ToughPathRenderingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class MotionMarkCanvasStrokeShapesPage(ToughPathRenderingPage):
@@ -36,7 +36,11 @@
 class ChalkboardPage(rendering_story.RenderingStory):
   BASE_NAME = 'ie_chalkboard'
   URL = 'https://testdrive-archive.azurewebsites.net/performance/chalkboard/'
-  TAGS = [story_tags.TOUGH_PATH_RENDERING]
+  TAGS = [
+    story_tags.TOUGH_PATH_RENDERING,
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
   def RunPageInteractions(self, action_runner):
     with action_runner.CreateInteraction('ClickStart'):
diff --git a/tools/perf/page_sets/rendering/tough_pinch_zoom_cases.py b/tools/perf/page_sets/rendering/tough_pinch_zoom_cases.py
index 5de2bed..4beeeb9 100644
--- a/tools/perf/page_sets/rendering/tough_pinch_zoom_cases.py
+++ b/tools/perf/page_sets/rendering/tough_pinch_zoom_cases.py
@@ -113,7 +113,6 @@
   BASE_NAME = 'youtube_pinch'
   YEAR = '2018'
   URL = 'http://www.youtube.com'
-  TAGS = ToughPinchZoomPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
   def RunNavigateSteps(self, action_runner):
     super(YoutubePinchZoom2018Page, self).RunNavigateSteps(action_runner)
@@ -170,7 +169,6 @@
   BASE_NAME = 'twitter_pinch'
   YEAR = '2018'
   URL = 'https://twitter.com/katyperry'
-  TAGS = ToughPinchZoomPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
   def RunNavigateSteps(self, action_runner):
@@ -229,7 +227,6 @@
   BASE_NAME = 'amazon_pinch'
   YEAR = '2018'
   URL = 'http://www.amazon.com'
-  TAGS = ToughPinchZoomPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class EBayPinchZoom2018Page(ToughPinchZoomPage):
diff --git a/tools/perf/page_sets/rendering/tough_pinch_zoom_mobile_cases.py b/tools/perf/page_sets/rendering/tough_pinch_zoom_mobile_cases.py
index ea4c8a8e..c9056f4e 100644
--- a/tools/perf/page_sets/rendering/tough_pinch_zoom_mobile_cases.py
+++ b/tools/perf/page_sets/rendering/tough_pinch_zoom_mobile_cases.py
@@ -58,7 +58,6 @@
   BASE_NAME = 'linkedin_mobile_pinch'
   YEAR = '2018'
   URL = 'http://www.linkedin.com/in/linustorvalds'
-  TAGS = ToughPinchZoomMobilePage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
   # Linkedin has expensive shader compilation so it can benefit from shader
   # cache from reload.
diff --git a/tools/perf/page_sets/rendering/tough_scheduling_cases.py b/tools/perf/page_sets/rendering/tough_scheduling_cases.py
index f5f39ac8..37307c3 100644
--- a/tools/perf/page_sets/rendering/tough_scheduling_cases.py
+++ b/tools/perf/page_sets/rendering/tough_scheduling_cases.py
@@ -66,7 +66,6 @@
 
   BASE_NAME = 'raf'
   URL = 'file://../tough_scheduling_cases/raf.html'
-  TAGS = ToughSchedulingPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class RafCanvasScrollingPage(ToughSchedulingPage):
@@ -133,7 +132,6 @@
 class SecondBatchLightJsPage(SecondBatchJsPage):
   BASE_NAME = 'second_batch_js_light'
   URL = 'file://../tough_scheduling_cases/second_batch_js.html?light'
-  TAGS = SecondBatchJsPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class SecondBatchJsMediumPage(SecondBatchJsPage):
diff --git a/tools/perf/page_sets/rendering/tough_scrolling_cases.py b/tools/perf/page_sets/rendering/tough_scrolling_cases.py
index 451f743..95b2c7a 100644
--- a/tools/perf/page_sets/rendering/tough_scrolling_cases.py
+++ b/tools/perf/page_sets/rendering/tough_scrolling_cases.py
@@ -43,6 +43,7 @@
   BASE_NAME = 'text_10000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text.html'
   SPEED_IN_PIXELS_PER_SECOND = 10000
+  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class ScrollingText20000Page(ToughFastScrollingPage):
@@ -134,7 +135,6 @@
   BASE_NAME = 'text_constant_full_page_raster_10000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/text_constant_full_page_raster.html'
   SPEED_IN_PIXELS_PER_SECOND = 10000
-  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class ScrollingTextRaster20000Page(ToughFastScrollingPage):
@@ -171,27 +171,28 @@
   BASE_NAME = 'canvas_05000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 5000
+  TAGS = ToughFastScrollingPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 
 class ScrollingCanvas10000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_10000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 10000
-  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class ScrollingCanvas20000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_20000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 20000
-  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class ScrollingCanvas40000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_40000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 40000
-  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class ScrollingCanvas60000Page(ToughFastScrollingPage):
@@ -204,11 +205,9 @@
   BASE_NAME = 'canvas_75000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 75000
-  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_MOBILE]
 
 
 class ScrollingCanvas90000Page(ToughFastScrollingPage):
   BASE_NAME = 'canvas_90000_pixels_per_second'
   URL = 'file://../tough_scrolling_cases/canvas.html'
   SPEED_IN_PIXELS_PER_SECOND = 90000
-  TAGS = ToughFastScrollingPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
diff --git a/tools/perf/page_sets/rendering/tough_texture_upload_cases.py b/tools/perf/page_sets/rendering/tough_texture_upload_cases.py
index 2b350b2..eedea04 100644
--- a/tools/perf/page_sets/rendering/tough_texture_upload_cases.py
+++ b/tools/perf/page_sets/rendering/tough_texture_upload_cases.py
@@ -36,7 +36,6 @@
   BASE_NAME = 'background_color_animation_with_gradient'
   # pylint: disable=line-too-long
   URL = 'file://../tough_texture_upload_cases/background_color_animation_with_gradient.html'
-  TAGS = ToughTextureUploadPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class SmallTextureUploadsPage(ToughTextureUploadPage):
@@ -57,3 +56,7 @@
 class ExtraLargeTextureUploadsPage(ToughTextureUploadPage):
   BASE_NAME = 'extra_large_texture_uploads'
   URL = 'file://../tough_texture_upload_cases/extra_large_texture_uploads.html'
+  TAGS = ToughTextureUploadPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
diff --git a/tools/perf/page_sets/rendering/tough_webgl_cases.py b/tools/perf/page_sets/rendering/tough_webgl_cases.py
index 567cf86..844b2593 100644
--- a/tools/perf/page_sets/rendering/tough_webgl_cases.py
+++ b/tools/perf/page_sets/rendering/tough_webgl_cases.py
@@ -45,6 +45,7 @@
   BASE_NAME = 'nvidia_vertex_buffer_object'
   # pylint: disable=line-too-long
   URL = 'http://www.khronos.org/registry/webgl/sdk/demos/google/nvidia-vertex-buffer-object/index.html'
+  TAGS = ToughWebglPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class SansAngelesPage(ToughWebglPage):
@@ -57,19 +58,18 @@
   BASE_NAME = 'particles'
   # pylint: disable=line-too-long
   URL = 'http://www.khronos.org/registry/webgl/sdk/demos/google/particles/index.html'
-  TAGS = ToughWebglPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class EarthPage(ToughWebglPage):
   BASE_NAME = 'earth'
   URL = 'http://www.khronos.org/registry/webgl/sdk/demos/webkit/Earth.html'
-  TAGS = ToughWebglPage.TAGS + [story_tags.REPRESENTATIVE_MAC_DESKTOP]
 
 
 class ManyPlanetsDeepPage(ToughWebglPage):
   BASE_NAME = 'many_planets_deep'
   # pylint: disable=line-too-long
   URL = 'http://www.khronos.org/registry/webgl/sdk/demos/webkit/ManyPlanetsDeep.html'
+  TAGS = ToughWebglPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class AquariumPage(ToughWebglPage):
@@ -81,6 +81,7 @@
 class Aquarium20KFishPage(ToughWebglPage):
   BASE_NAME = 'aquarium_20k'
   URL = 'http://webglsamples.org/aquarium/aquarium.html?numFish=20000'
+  TAGS = ToughWebglPage.TAGS + [story_tags.REPRESENTATIVE_WIN_DESKTOP]
 
 
 class BlobPage(ToughWebglPage):
@@ -107,6 +108,10 @@
   BASE_NAME = 'animometer_webgl_attrib_arrays'
   # pylint: disable=line-too-long
   URL = 'http://kenrussell.github.io/webgl-animometer/Animometer/tests/3d/webgl.html?use_attributes=1'
+  TAGS = ToughWebglPage.TAGS + [
+    story_tags.REPRESENTATIVE_MOBILE,
+    story_tags.REPRESENTATIVE_MAC_DESKTOP
+  ]
 
 class CameraToWebGLPage(ToughWebglPage):
   TAGS = ToughWebglPage.TAGS + [story_tags.USE_FAKE_CAMERA_DEVICE]
diff --git a/ui/accessibility/ax_table_info.cc b/ui/accessibility/ax_table_info.cc
index 05a1325..89453af 100644
--- a/ui/accessibility/ax_table_info.cc
+++ b/ui/accessibility/ax_table_info.cc
@@ -342,23 +342,18 @@
   // The table header container is just a node with all of the headers in the
   // table as indirect children.
 
-  // One node for each column, and one more for the table header container.
-  size_t extra_node_count = col_count + 1;
+  // Delete old extra nodes.
+  ClearExtraMacNodes();
 
-  if (extra_mac_nodes.size() != extra_node_count) {
-    // Delete old extra nodes.
-    ClearExtraMacNodes();
+  // Resize.
+  extra_mac_nodes.resize(col_count + 1);
 
-    // Resize.
-    extra_mac_nodes.resize(col_count + 1);
+  // Create column nodes.
+  for (size_t i = 0; i < col_count; i++)
+    extra_mac_nodes[i] = CreateExtraMacColumnNode(i);
 
-    // Create column nodes.
-    for (size_t i = 0; i < col_count; i++)
-      extra_mac_nodes[i] = CreateExtraMacColumnNode(i);
-
-    // Create table header container node.
-    extra_mac_nodes[col_count] = CreateExtraMacTableHeaderNode();
-  }
+  // Create table header container node.
+  extra_mac_nodes[col_count] = CreateExtraMacTableHeaderNode();
 
   // Update the columns to reflect current state of the table.
   for (size_t i = 0; i < col_count; i++)
diff --git a/ui/accessibility/platform/ax_fragment_root_win_unittest.cc b/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
index 87f2774..7c0447a 100644
--- a/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
+++ b/ui/accessibility/platform/ax_fragment_root_win_unittest.cc
@@ -52,12 +52,25 @@
   element2_data.relative_bounds.bounds = gfx::RectF(0, 50, 30, 30);
   root_data.child_ids.push_back(element2_data.id);
 
-  Init(root_data, element1_data, element2_data);
+  AXNodeData element3_data;
+  element3_data.id = 4;
+  element3_data.relative_bounds.bounds = gfx::RectF(50, 0, 20, 20);
+  root_data.child_ids.push_back(element3_data.id);
+
+  // Overlapping child view.
+  AXNodeData element4_data;
+  element4_data.id = 5;
+  element4_data.relative_bounds.bounds = gfx::RectF(50, 0, 10, 10);
+  element3_data.child_ids.push_back(element4_data.id);
+
+  Init(root_data, element1_data, element2_data, element3_data, element4_data);
   InitFragmentRoot();
 
   AXNode* root_node = GetRootNode();
   AXNode* element1_node = root_node->children()[0];
   AXNode* element2_node = root_node->children()[1];
+  AXNode* element3_node = root_node->children()[2];
+  AXNode* element4_node = root_node->children()[2]->children()[0];
 
   ComPtr<IRawElementProviderFragmentRoot> fragment_root_prov(GetFragmentRoot());
   ComPtr<IRawElementProviderFragment> root_provider(
@@ -66,6 +79,10 @@
       QueryInterfaceFromNode<IRawElementProviderFragment>(element1_node);
   ComPtr<IRawElementProviderFragment> element2_provider =
       QueryInterfaceFromNode<IRawElementProviderFragment>(element2_node);
+  ComPtr<IRawElementProviderFragment> element3_provider =
+      QueryInterfaceFromNode<IRawElementProviderFragment>(element3_node);
+  ComPtr<IRawElementProviderFragment> element4_provider =
+      QueryInterfaceFromNode<IRawElementProviderFragment>(element4_node);
 
   ComPtr<IRawElementProviderFragment> provider_from_point;
   EXPECT_HRESULT_SUCCEEDED(fragment_root_prov->ElementProviderFromPoint(
@@ -80,6 +97,11 @@
       47, 67, &provider_from_point));
   EXPECT_EQ(root_provider.Get(), provider_from_point.Get());
 
+  // This is on node 3 and 4.
+  EXPECT_HRESULT_SUCCEEDED(fragment_root_prov->ElementProviderFromPoint(
+      55, 5, &provider_from_point));
+  EXPECT_EQ(element4_provider.Get(), provider_from_point.Get());
+
   // This is on node 1 with scale factor of 1.5.
   std::unique_ptr<base::AutoReset<float>> scale_factor_reset =
       TestAXNodeWrapper::SetScaleFactor(1.5);
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux.cc b/ui/accessibility/platform/ax_platform_node_auralinux.cc
index bcfd818..1c3cac2 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux.cc
@@ -3361,6 +3361,14 @@
 }
 
 void AXPlatformNodeAuraLinux::OnValueChanged() {
+  // If this is a non-web-content text entry, then we need to trigger text
+  // change signals when the value changes. This is handled by browser
+  // accessibility for web content.
+  if (IsPlainTextField() || !GetDelegate()->IsWebContent()) {
+    UpdateHypertext();
+    return;
+  }
+
   if (!IsRangeValueSupported(GetData()))
     return;
 
diff --git a/ui/accessibility/platform/ax_platform_node_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
index d607880..92a7e4bf 100644
--- a/ui/accessibility/platform/ax_platform_node_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_win_unittest.cc
@@ -460,7 +460,20 @@
   node2.SetName("Name2");
   root.child_ids.push_back(node2.id);
 
-  Init(root, node1, node2);
+  AXNodeData node3;
+  node3.id = 4;
+  node3.relative_bounds.bounds = gfx::RectF(10, 10, 10, 10);
+  node3.SetName("Name3");
+  root.child_ids.push_back(node3.id);
+
+  // Overlapping child view.
+  AXNodeData node4;
+  node4.id = 5;
+  node4.relative_bounds.bounds = gfx::RectF(10, 10, 5, 5);
+  node4.SetName("Name4");
+  node3.child_ids.push_back(node4.id);
+
+  Init(root, node1, node2, node3, node4);
 
   ComPtr<IAccessible> root_obj(GetRootIAccessible());
 
@@ -474,13 +487,19 @@
   ASSERT_NE(nullptr, obj_1.ptr());
   CheckVariantHasName(obj_1, L"Name1");
 
-  // This is directly on node 2 with a scale factor of 1.5.
+  // This is directly on node 3 and 4.
   ScopedVariant obj_2;
+  EXPECT_EQ(S_OK, root_obj->accHitTest(12, 12, obj_2.Receive()));
+  ASSERT_NE(nullptr, obj_2.ptr());
+  CheckVariantHasName(obj_2, L"Name4");
+
+  // This is directly on node 2 with a scale factor of 1.5.
+  ScopedVariant obj_3;
   std::unique_ptr<base::AutoReset<float>> scale_factor_reset =
       TestAXNodeWrapper::SetScaleFactor(1.5);
-  EXPECT_EQ(S_OK, root_obj->accHitTest(38, 38, obj_2.Receive()));
-  ASSERT_NE(nullptr, obj_2.ptr());
-  CheckVariantHasName(obj_2, L"Name2");
+  EXPECT_EQ(S_OK, root_obj->accHitTest(38, 38, obj_3.Receive()));
+  ASSERT_NE(nullptr, obj_3.ptr());
+  CheckVariantHasName(obj_3, L"Name2");
 }
 
 TEST_F(AXPlatformNodeWinTest, TestIAccessibleName) {
diff --git a/ui/events/blink/input_handler_proxy.cc b/ui/events/blink/input_handler_proxy.cc
index 5f260ead..c4a0a8e 100644
--- a/ui/events/blink/input_handler_proxy.cc
+++ b/ui/events/blink/input_handler_proxy.cc
@@ -396,7 +396,7 @@
   // provided element_id.
   if (type == WebInputEvent::Type::kGestureScrollBegin)
     synthetic_gesture_event->data.scroll_begin.scrollable_area_element_id =
-        pointer_result.target_scroller.GetInternalValue();
+        pointer_result.target_scroller.GetStableId();
 
   WebScopedInputEvent web_scoped_gesture_event(
       synthetic_gesture_event.release());
diff --git a/ui/message_center/BUILD.gn b/ui/message_center/BUILD.gn
index 2d6d7ee5..9ca2f4b 100644
--- a/ui/message_center/BUILD.gn
+++ b/ui/message_center/BUILD.gn
@@ -119,8 +119,6 @@
         "views/proportional_image_view.h",
         "views/relative_time_formatter.cc",
         "views/relative_time_formatter.h",
-        "views/slide_out_controller.cc",
-        "views/slide_out_controller.h",
       ]
       deps += [
         "//ui/compositor",
@@ -207,7 +205,6 @@
         "views/notification_header_view_unittest.cc",
         "views/notification_view_md_unittest.cc",
         "views/relative_time_formatter_unittest.cc",
-        "views/slide_out_controller_unittest.cc",
       ]
       deps += [
         "//ui/display",
diff --git a/ui/message_center/public/cpp/message_center_constants.h b/ui/message_center/public/cpp/message_center_constants.h
index c87bb97..f4939b4 100644
--- a/ui/message_center/public/cpp/message_center_constants.h
+++ b/ui/message_center/public/cpp/message_center_constants.h
@@ -141,10 +141,6 @@
 // The corners are only rounded in Chrome OS.
 constexpr int kNotificationCornerRadius = 2;
 
-// Close if notification is slided more than this amount in addition to the
-// width of the buttons and their margins.
-constexpr int kSwipeCloseMargin = 64;
-
 }  // namespace message_center
 
 #endif  // UI_MESSAGE_CENTER_PUBLIC_CPP_MESSAGE_CENTER_CONSTANTS_H_
diff --git a/ui/message_center/views/message_view.cc b/ui/message_center/views/message_view.cc
index 468a330..8e33233 100644
--- a/ui/message_center/views/message_view.cc
+++ b/ui/message_center/views/message_view.cc
@@ -359,21 +359,21 @@
   }
 }
 
-SlideOutController::SlideMode MessageView::CalculateSlideMode() const {
+views::SlideOutController::SlideMode MessageView::CalculateSlideMode() const {
   if (disable_slide_)
-    return SlideOutController::SlideMode::NO_SLIDE;
+    return views::SlideOutController::SlideMode::kNone;
 
   switch (GetMode()) {
     case Mode::SETTING:
-      return SlideOutController::SlideMode::NO_SLIDE;
+      return views::SlideOutController::SlideMode::kNone;
     case Mode::PINNED:
-      return SlideOutController::SlideMode::PARTIALLY;
+      return views::SlideOutController::SlideMode::kPartial;
     case Mode::NORMAL:
-      return SlideOutController::SlideMode::FULL;
+      return views::SlideOutController::SlideMode::kFull;
   }
 
   NOTREACHED();
-  return SlideOutController::SlideMode::FULL;
+  return views::SlideOutController::SlideMode::kFull;
 }
 
 MessageView::Mode MessageView::GetMode() const {
diff --git a/ui/message_center/views/message_view.h b/ui/message_center/views/message_view.h
index 59a20ec9..e3c3181 100644
--- a/ui/message_center/views/message_view.h
+++ b/ui/message_center/views/message_view.h
@@ -18,8 +18,9 @@
 #include "ui/message_center/message_center_export.h"
 #include "ui/message_center/public/cpp/notification.h"
 #include "ui/message_center/public/cpp/notification_delegate.h"
-#include "ui/message_center/views/slide_out_controller.h"
 #include "ui/views/animation/ink_drop_host_view.h"
+#include "ui/views/animation/slide_out_controller.h"
+#include "ui/views/animation/slide_out_controller_delegate.h"
 #include "ui/views/controls/focus_ring.h"
 #include "ui/views/focus/focus_manager.h"
 #include "ui/views/view.h"
@@ -43,9 +44,10 @@
 // NotificationViewMD subclass needs ink drop functionality.  Rework ink drops
 // to not need to be the base class of views which use them, and move the
 // functionality to the subclass that uses these.
-class MESSAGE_CENTER_EXPORT MessageView : public views::InkDropHostView,
-                                          public SlideOutController::Delegate,
-                                          public views::FocusChangeListener {
+class MESSAGE_CENTER_EXPORT MessageView
+    : public views::InkDropHostView,
+      public views::SlideOutControllerDelegate,
+      public views::FocusChangeListener {
  public:
   static const char kViewClassName[];
 
@@ -126,7 +128,7 @@
   void AddedToWidget() override;
   const char* GetClassName() const final;
 
-  // message_center::SlideOutController::Delegate:
+  // views::SlideOutControllerDelegate:
   ui::Layer* GetSlideOutLayer() override;
   void OnSlideStarted() override;
   void OnSlideChanged(bool in_progress) override;
@@ -179,7 +181,7 @@
   friend class test::MessagePopupCollectionTest;
 
   // Returns the ideal slide mode by calculating the current status.
-  SlideOutController::SlideMode CalculateSlideMode() const;
+  views::SlideOutController::SlideMode CalculateSlideMode() const;
 
   // Returns if the control buttons should be shown.
   bool ShouldShowControlButtons() const;
@@ -196,7 +198,7 @@
   // "fixed" mode flag. See the comment in MessageView::Mode for detail.
   bool setting_mode_ = false;
 
-  SlideOutController slide_out_controller_;
+  views::SlideOutController slide_out_controller_;
   base::ObserverList<SlideObserver>::Unchecked slide_observers_;
 
   // True if |this| is embedded in another view. Equivalent to |!top_level| in
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
index 1ade466..4e5403b 100644
--- a/ui/views/BUILD.gn
+++ b/ui/views/BUILD.gn
@@ -88,6 +88,8 @@
     "animation/installable_ink_drop_config.h",
     "animation/installable_ink_drop_painter.h",
     "animation/scroll_animator.h",
+    "animation/slide_out_controller.h",
+    "animation/slide_out_controller_delegate.h",
     "animation/square_ink_drop_ripple.h",
     "background.h",
     "border.h",
@@ -311,6 +313,7 @@
     "animation/installable_ink_drop_animator.cc",
     "animation/installable_ink_drop_painter.cc",
     "animation/scroll_animator.cc",
+    "animation/slide_out_controller.cc",
     "animation/square_ink_drop_ripple.cc",
     "background.cc",
     "border.cc",
@@ -994,6 +997,7 @@
     "animation/ink_drop_unittest.cc",
     "animation/installable_ink_drop_animator_unittest.cc",
     "animation/installable_ink_drop_unittest.cc",
+    "animation/slide_out_controller_unittest.cc",
     "animation/square_ink_drop_ripple_unittest.cc",
     "border_unittest.cc",
     "bubble/bubble_border_unittest.cc",
@@ -1244,6 +1248,12 @@
     data_deps +=
         [ "//testing/buildbot/filters:linux_ozone_views_unittests_filters" ]
   }
+
+  if (use_atk) {
+    sources +=
+        [ "accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc" ]
+    configs += [ "//build/config/linux/atk" ]
+  }
 }
 
 # This target is added as a dependency of browser interactive_ui_tests. It must
diff --git a/ui/views/accessibility/view_ax_platform_node_delegate.cc b/ui/views/accessibility/view_ax_platform_node_delegate.cc
index ffd1d2a..82b7498 100644
--- a/ui/views/accessibility/view_ax_platform_node_delegate.cc
+++ b/ui/views/accessibility/view_ax_platform_node_delegate.cc
@@ -349,10 +349,15 @@
   if (!view()->HitTestPoint(point))
     return nullptr;
 
+  // GetEventHandlerForPoint correctly handles overlapping views but it
+  // only returns views that handle events. For accessibility, we want to
+  // return the deepest child view. This is why we need to continue
+  // searching from here.
+  View* v = view()->GetEventHandlerForPoint(point);
+
   // Check if the point is within any of the immediate children of this
   // view. We don't have to search further because AXPlatformNode will
   // do a recursive hit test if we return anything other than |this| or NULL.
-  View* v = view();
   const auto is_point_in_child = [point, v](View* child) {
     if (!child->GetVisible())
       return false;
@@ -363,7 +368,7 @@
   const auto i = std::find_if(v->children().rbegin(), v->children().rend(),
                               is_point_in_child);
   // If it's not inside any of our children, it's inside this view.
-  return (i == v->children().rend()) ? GetNativeObject()
+  return (i == v->children().rend()) ? v->GetNativeViewAccessible()
                                      : (*i)->GetNativeViewAccessible();
 }
 
diff --git a/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc b/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
new file mode 100644
index 0000000..22f38a6
--- /dev/null
+++ b/ui/views/accessibility/view_ax_platform_node_delegate_auralinux_unittest.cc
@@ -0,0 +1,101 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/views/accessibility/view_ax_platform_node_delegate.h"
+
+#include <atk/atk.h>
+
+#include "ui/views/controls/textfield/textfield.h"
+#include "ui/views/test/views_test_base.h"
+
+namespace views {
+namespace test {
+
+class ViewAXPlatformNodeDelegateAuraLinuxTest : public ViewsTestBase {
+ public:
+  ViewAXPlatformNodeDelegateAuraLinuxTest() = default;
+  ~ViewAXPlatformNodeDelegateAuraLinuxTest() override = default;
+};
+
+TEST_F(ViewAXPlatformNodeDelegateAuraLinuxTest, TextfieldAccessibility) {
+  Widget widget;
+  Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
+  init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  widget.Init(std::move(init_params));
+
+  View* content = new View;
+  widget.SetContentsView(content);
+
+  Textfield* textfield = new Textfield;
+  textfield->SetAccessibleName(base::UTF8ToUTF16("Name"));
+  textfield->SetText(base::UTF8ToUTF16("Value"));
+  content->AddChildView(textfield);
+
+  AtkText* atk_text = ATK_TEXT(textfield->GetNativeViewAccessible());
+  ASSERT_NE(nullptr, atk_text);
+
+  struct TextChangeData {
+    int position;
+    int length;
+    std::string text;
+  };
+
+  std::vector<TextChangeData> text_remove_events;
+  std::vector<TextChangeData> text_insert_events;
+  GCallback callback = G_CALLBACK(
+      +[](AtkText*, int position, int length, char* text, gpointer data) {
+        auto* events = static_cast<std::vector<TextChangeData>*>(data);
+        events->push_back(TextChangeData{position, length, text});
+      });
+  g_signal_connect(atk_text, "text-insert", callback, &text_insert_events);
+  g_signal_connect(atk_text, "text-remove", callback, &text_remove_events);
+
+  textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+  ASSERT_EQ(text_remove_events.size(), 0ul);
+  ASSERT_EQ(text_insert_events.size(), 1ul);
+  ASSERT_EQ(text_insert_events[0].position, 0);
+  ASSERT_EQ(text_insert_events[0].length, 5);
+  ASSERT_EQ(text_insert_events[0].text, "Value");
+  text_insert_events.clear();
+
+  textfield->SetText(base::UTF8ToUTF16("Value A"));
+  textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+
+  ASSERT_EQ(text_remove_events.size(), 0ul);
+  ASSERT_EQ(text_insert_events.size(), 1ul);
+  ASSERT_EQ(text_insert_events[0].position, 5);
+  ASSERT_EQ(text_insert_events[0].length, 2);
+  ASSERT_EQ(text_insert_events[0].text, " A");
+  text_insert_events.clear();
+
+  textfield->SetText(base::UTF8ToUTF16("Value"));
+  textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+  ASSERT_EQ(text_remove_events.size(), 1ul);
+  ASSERT_EQ(text_insert_events.size(), 0ul);
+  ASSERT_EQ(text_remove_events[0].position, 5);
+  ASSERT_EQ(text_remove_events[0].length, 2);
+  ASSERT_EQ(text_remove_events[0].text, " A");
+  text_remove_events.clear();
+
+  textfield->SetText(base::UTF8ToUTF16("Prefix Value"));
+  textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+  ASSERT_EQ(text_remove_events.size(), 0ul);
+  ASSERT_EQ(text_insert_events.size(), 1ul);
+  ASSERT_EQ(text_insert_events[0].position, 0);
+  ASSERT_EQ(text_insert_events[0].length, 7);
+  ASSERT_EQ(text_insert_events[0].text, "Prefix ");
+  text_insert_events.clear();
+
+  textfield->SetText(base::UTF8ToUTF16("Value"));
+  textfield->NotifyAccessibilityEvent(ax::mojom::Event::kValueChanged, true);
+  ASSERT_EQ(text_remove_events.size(), 1ul);
+  ASSERT_EQ(text_insert_events.size(), 0ul);
+  ASSERT_EQ(text_remove_events[0].position, 0);
+  ASSERT_EQ(text_remove_events[0].length, 7);
+  ASSERT_EQ(text_remove_events[0].text, "Prefix ");
+  text_insert_events.clear();
+}
+
+}  // namespace test
+}  // namespace views
diff --git a/ui/message_center/views/slide_out_controller.cc b/ui/views/animation/slide_out_controller.cc
similarity index 83%
rename from ui/message_center/views/slide_out_controller.cc
rename to ui/views/animation/slide_out_controller.cc
index fad2063..8e63abc 100644
--- a/ui/message_center/views/slide_out_controller.cc
+++ b/ui/views/animation/slide_out_controller.cc
@@ -2,25 +2,32 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/message_center/views/slide_out_controller.h"
+#include "ui/views/animation/slide_out_controller.h"
 
 #include "base/bind.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "ui/compositor/layer.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/gfx/transform.h"
-#include "ui/message_center/public/cpp/message_center_constants.h"
+#include "ui/views/animation/slide_out_controller_delegate.h"
 
-namespace message_center {
+namespace views {
 
 namespace {
-constexpr int kSwipeRestoreDurationMs = 150;
+
+constexpr base::TimeDelta kSwipeRestoreDuration =
+    base::TimeDelta::FromMilliseconds(150);
 constexpr int kSwipeOutTotalDurationMs = 150;
 gfx::Tween::Type kSwipeTweenType = gfx::Tween::EASE_IN;
+
+// When we have a swipe control, we will close the target if it is slid more
+// than this amount plus the width of the swipe control.
+constexpr int kSwipeCloseMargin = 64;
+
 }  // anonymous namespace
 
 SlideOutController::SlideOutController(ui::EventTarget* target,
-                                       Delegate* delegate)
+                                       SlideOutControllerDelegate* delegate)
     : target_handling_(target, this), delegate_(delegate) {}
 
 SlideOutController::~SlideOutController() {}
@@ -28,13 +35,13 @@
 void SlideOutController::CaptureControlOpenState() {
   if (!has_swipe_control_)
     return;
-  if (mode_ == SlideMode::FULL &&
+  if (mode_ == SlideMode::kFull &&
       fabs(gesture_amount_) >= swipe_control_width_) {
     control_open_state_ = gesture_amount_ < 0
-                              ? SwipeControlOpenState::OPEN_ON_RIGHT
-                              : SwipeControlOpenState::OPEN_ON_LEFT;
+                              ? SwipeControlOpenState::kOpenOnRight
+                              : SwipeControlOpenState::kOpenOnLeft;
   } else {
-    control_open_state_ = SwipeControlOpenState::CLOSED;
+    control_open_state_ = SwipeControlOpenState::kClosed;
   }
 }
 
@@ -49,7 +56,7 @@
     // The threshold for the fling velocity is computed empirically.
     // The unit is in pixels/second.
     const float kFlingThresholdForClose = 800.f;
-    if (mode_ == SlideMode::FULL &&
+    if (mode_ == SlideMode::kFull &&
         fabsf(event->details().velocity_x()) > kFlingThresholdForClose) {
       SlideOutAndClose(event->details().velocity_x());
       event->StopPropagation();
@@ -65,13 +72,13 @@
 
   if (event->type() == ui::ET_GESTURE_SCROLL_BEGIN) {
     switch (control_open_state_) {
-      case SwipeControlOpenState::CLOSED:
+      case SwipeControlOpenState::kClosed:
         gesture_amount_ = 0.f;
         break;
-      case SwipeControlOpenState::OPEN_ON_RIGHT:
+      case SwipeControlOpenState::kOpenOnRight:
         gesture_amount_ = -swipe_control_width_;
         break;
-      case SwipeControlOpenState::OPEN_ON_LEFT:
+      case SwipeControlOpenState::kOpenOnLeft:
         gesture_amount_ = swipe_control_width_;
         break;
       default:
@@ -85,15 +92,15 @@
     float scroll_amount;
     float opacity;
     switch (mode_) {
-      case SlideMode::FULL:
+      case SlideMode::kFull:
         scroll_amount = gesture_amount_;
         opacity = 1.f - std::min(fabsf(scroll_amount) / width, 1.f);
         break;
-      case SlideMode::NO_SLIDE:
+      case SlideMode::kNone:
         scroll_amount = 0.f;
         opacity = 1.f;
         break;
-      case SlideMode::PARTIALLY:
+      case SlideMode::kPartial:
         if (gesture_amount_ >= 0) {
           scroll_amount = std::min(0.5f * gesture_amount_,
                                    scroll_amount_for_closing_notification);
@@ -113,7 +120,7 @@
     delegate_->OnSlideChanged(true);
   } else if (event->type() == ui::ET_GESTURE_SCROLL_END) {
     float scrolled_ratio = fabsf(gesture_amount_) / width;
-    if (mode_ == SlideMode::FULL &&
+    if (mode_ == SlideMode::kFull &&
         scrolled_ratio >= scroll_amount_for_closing_notification / width) {
       SlideOutAndClose(gesture_amount_);
       event->StopPropagation();
@@ -130,22 +137,21 @@
   // Restore the layer state.
   gfx::Transform transform;
   switch (control_open_state_) {
-    case SwipeControlOpenState::CLOSED:
+    case SwipeControlOpenState::kClosed:
       gesture_amount_ = 0.f;
       break;
-    case SwipeControlOpenState::OPEN_ON_RIGHT:
+    case SwipeControlOpenState::kOpenOnRight:
       gesture_amount_ = -swipe_control_width_;
       transform.Translate(-swipe_control_width_, 0);
       break;
-    case SwipeControlOpenState::OPEN_ON_LEFT:
+    case SwipeControlOpenState::kOpenOnLeft:
       gesture_amount_ = swipe_control_width_;
       transform.Translate(swipe_control_width_, 0);
       break;
   }
 
   SetOpacityIfNecessary(1.f);
-  SetTransformWithAnimationIfNecessary(
-      transform, base::TimeDelta::FromMilliseconds(kSwipeRestoreDurationMs));
+  SetTransformWithAnimationIfNecessary(transform, kSwipeRestoreDuration);
 }
 
 void SlideOutController::SlideOutAndClose(int direction) {
@@ -210,7 +216,8 @@
   if (!is_completely_slid_out)
     return;
 
-  // Call Delegate::OnSlideOut() if this animation came from SlideOutAndClose().
+  // Call SlideOutControllerDelegate::OnSlideOut() if this animation came from
+  // SlideOutAndClose().
 
   // OnImplicitAnimationsCompleted is called from BeginMainFrame, so we should
   // delay operation that might result in deletion of LayerTreeHost.
@@ -237,4 +244,4 @@
   RestoreVisualState();
 }
 
-}  // namespace message_center
+}  // namespace views
diff --git a/ui/message_center/views/slide_out_controller.h b/ui/views/animation/slide_out_controller.h
similarity index 71%
rename from ui/message_center/views/slide_out_controller.h
rename to ui/views/animation/slide_out_controller.h
index 2c750e9..61be0f2 100644
--- a/ui/message_center/views/slide_out_controller.h
+++ b/ui/views/animation/slide_out_controller.h
@@ -2,47 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef UI_MESSAGE_CENTER_VIEWS_SLIDE_OUT_CONTROLLER_H_
-#define UI_MESSAGE_CENTER_VIEWS_SLIDE_OUT_CONTROLLER_H_
+#ifndef UI_VIEWS_ANIMATION_SLIDE_OUT_CONTROLLER_H_
+#define UI_VIEWS_ANIMATION_SLIDE_OUT_CONTROLLER_H_
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "ui/compositor/layer_animation_observer.h"
 #include "ui/events/scoped_target_handler.h"
-#include "ui/message_center/message_center_export.h"
 #include "ui/views/view.h"
+#include "ui/views/views_export.h"
 
-namespace message_center {
+namespace views {
+
+class SlideOutControllerDelegate;
 
 // This class contains logic to control sliding out of a layer in response to
 // swipes, i.e. gesture scroll events.
-class MESSAGE_CENTER_EXPORT SlideOutController
-    : public ui::EventHandler,
-      public ui::ImplicitAnimationObserver {
+class VIEWS_EXPORT SlideOutController : public ui::EventHandler,
+                                        public ui::ImplicitAnimationObserver {
  public:
+  // Indicates how much the target layer is allowed to slide.
   enum class SlideMode {
-    FULL,
-    PARTIALLY,
-    NO_SLIDE,
+    kFull,
+    kPartial,
+    kNone,
   };
 
-  class Delegate {
-   public:
-    // Returns the layer for slide operations.
-    virtual ui::Layer* GetSlideOutLayer() = 0;
-
-    // Called when a manual slide starts.
-    virtual void OnSlideStarted() {}
-
-    // Called when a manual slide updates or ends. The argument is true if the
-    // slide starts or in progress, false if it ends.
-    virtual void OnSlideChanged(bool in_progress) = 0;
-
-    // Called when user intends to close the View by sliding it out.
-    virtual void OnSlideOut() = 0;
-  };
-
-  SlideOutController(ui::EventTarget* target, Delegate* delegate);
+  SlideOutController(ui::EventTarget* target,
+                     SlideOutControllerDelegate* delegate);
   ~SlideOutController() override;
 
   void set_update_opacity(bool update_opacity) {
@@ -68,7 +55,7 @@
   float GetGestureAmount() const { return gesture_amount_; }
 
   // Moves slide back to the center position to closes the swipe control.
-  // Effective only when swipe control is enabled by EnableSwipeControl().
+  // Effective only when swipe control is enabled by |SetSwipeControlWidth()|.
   void CloseSwipeControl();
 
   // Slides the view out and closes it after the animation. The sign of
@@ -77,7 +64,7 @@
 
  private:
   // Positions where the slided view stays after the touch released.
-  enum class SwipeControlOpenState { CLOSED, OPEN_ON_LEFT, OPEN_ON_RIGHT };
+  enum class SwipeControlOpenState { kClosed, kOpenOnLeft, kOpenOnRight };
 
   // Restores the transform and opacity of the view.
   void RestoreVisualState();
@@ -99,26 +86,26 @@
   ui::ScopedTargetHandler target_handling_;
 
   // Unowned and outlives this object.
-  Delegate* delegate_;
+  SlideOutControllerDelegate* delegate_;
 
   // Cumulative scroll amount since the beginning of current slide gesture.
   // Includes the initial shift when swipe control was open at gesture start.
   float gesture_amount_ = 0.f;
 
   // Whether or not this view can be slided and/or swiped out.
-  SlideMode mode_ = SlideMode::FULL;
+  SlideMode mode_ = SlideMode::kFull;
 
-  // Whether the swipe control is enabled. See EnableSwipeControl().
+  // Whether the swipe control is enabled. See |SetSwipeControlWidth()|.
   // Effective only when |mode_| is FULL.
   bool has_swipe_control_ = false;
 
   // The horizontal position offset to for swipe control.
-  // See |EnableSwipeControl|.
+  // See |SetSwipeControlWidth()|.
   int swipe_control_width_ = 0;
 
   // The position where the slided view stays after the touch released.
   // Changed only when |mode_| is FULL and |has_swipe_control_| is true.
-  SwipeControlOpenState control_open_state_ = SwipeControlOpenState::CLOSED;
+  SwipeControlOpenState control_open_state_ = SwipeControlOpenState::kClosed;
 
   // If false, it doesn't update the opacity.
   bool update_opacity_ = true;
@@ -131,6 +118,6 @@
   DISALLOW_COPY_AND_ASSIGN(SlideOutController);
 };
 
-}  // namespace message_center
+}  // namespace views
 
-#endif  // UI_MESSAGE_CENTER_VIEWS_SLIDE_OUT_CONTROLLER_H_
+#endif  // UI_VIEWS_ANIMATION_SLIDE_OUT_CONTROLLER_H_
diff --git a/ui/views/animation/slide_out_controller_delegate.h b/ui/views/animation/slide_out_controller_delegate.h
new file mode 100644
index 0000000..99c5bff8
--- /dev/null
+++ b/ui/views/animation/slide_out_controller_delegate.h
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_VIEWS_ANIMATION_SLIDE_OUT_CONTROLLER_DELEGATE_H_
+#define UI_VIEWS_ANIMATION_SLIDE_OUT_CONTROLLER_DELEGATE_H_
+
+#include "ui/views/views_export.h"
+
+namespace ui {
+class Layer;
+}  // namespace ui
+
+namespace views {
+
+class VIEWS_EXPORT SlideOutControllerDelegate {
+ public:
+  // Returns the layer for slide operations.
+  virtual ui::Layer* GetSlideOutLayer() = 0;
+
+  // Called when a manual slide starts.
+  virtual void OnSlideStarted() {}
+
+  // Called when a manual slide updates or ends. The argument is true if the
+  // slide starts or in progress, false if it ends.
+  virtual void OnSlideChanged(bool in_progress) = 0;
+
+  // Called when user intends to close the View by sliding it out.
+  virtual void OnSlideOut() = 0;
+
+ protected:
+  virtual ~SlideOutControllerDelegate() = default;
+};
+
+}  // namespace views
+
+#endif  // UI_VIEWS_ANIMATION_SLIDE_OUT_CONTROLLER_DELEGATE_H_
diff --git a/ui/message_center/views/slide_out_controller_unittest.cc b/ui/views/animation/slide_out_controller_unittest.cc
similarity index 93%
rename from ui/message_center/views/slide_out_controller_unittest.cc
rename to ui/views/animation/slide_out_controller_unittest.cc
index 16ea607..5abfefb 100644
--- a/ui/message_center/views/slide_out_controller_unittest.cc
+++ b/ui/views/animation/slide_out_controller_unittest.cc
@@ -2,22 +2,23 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/message_center/views/slide_out_controller.h"
+#include "ui/views/animation/slide_out_controller.h"
 
+#include "ui/views/animation/slide_out_controller_delegate.h"
 #include "ui/views/test/views_test_base.h"
 #include "ui/views/view.h"
 
-namespace message_center {
+namespace views {
 
 namespace {
 constexpr int kSwipeControlWidth = 30;  // px
 constexpr int kTargetWidth = 200;       // px
 }  // namespace
 
-class SlideOutControllerDelegate : public SlideOutController::Delegate {
+class TestSlideOutControllerDelegate : public SlideOutControllerDelegate {
  public:
-  explicit SlideOutControllerDelegate(views::View* target) : target_(target) {}
-  virtual ~SlideOutControllerDelegate() = default;
+  explicit TestSlideOutControllerDelegate(View* target) : target_(target) {}
+  ~TestSlideOutControllerDelegate() override = default;
 
   ui::Layer* GetSlideOutLayer() override { return target_->layer(); }
 
@@ -45,34 +46,33 @@
   int slide_out_count_ = 0;
 
  private:
-  views::View* const target_;
+  View* const target_;
 };
 
-class SlideOutControllerTest : public views::ViewsTestBase {
+class SlideOutControllerTest : public ViewsTestBase {
  public:
   SlideOutControllerTest() = default;
   ~SlideOutControllerTest() override = default;
 
   void SetUp() override {
-    views::ViewsTestBase::SetUp();
+    ViewsTestBase::SetUp();
 
-    widget_ = std::make_unique<views::Widget>();
+    widget_ = std::make_unique<Widget>();
 
-    views::Widget::InitParams params =
-        CreateParams(views::Widget::InitParams::TYPE_POPUP);
-    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+    Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP);
+    params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
     params.bounds = gfx::Rect(50, 50, 650, 650);
     widget_->Init(std::move(params));
-    views::View* root = widget_->GetRootView();
+    View* root = widget_->GetRootView();
 
-    views::View* target_ = new views::View();
+    View* target_ = new View();
     target_->SetPaintToLayer(ui::LAYER_TEXTURED);
     target_->SetSize(gfx::Size(kTargetWidth, 50));
 
     root->AddChildView(target_);
     widget_->Show();
 
-    delegate_ = std::make_unique<SlideOutControllerDelegate>(target_);
+    delegate_ = std::make_unique<TestSlideOutControllerDelegate>(target_);
     slide_out_controller_ =
         std::make_unique<SlideOutController>(target_, delegate_.get());
   }
@@ -82,7 +82,7 @@
     delegate_.reset();
     widget_.reset();
 
-    views::ViewsTestBase::TearDown();
+    ViewsTestBase::TearDown();
   }
 
  protected:
@@ -90,7 +90,7 @@
     return slide_out_controller_.get();
   }
 
-  SlideOutControllerDelegate* delegate() { return delegate_.get(); }
+  TestSlideOutControllerDelegate* delegate() { return delegate_.get(); }
 
   void PostSequentialGestureEvent(const ui::GestureEventDetails& details) {
     // Set the timestamp ahead one microsecond.
@@ -112,9 +112,9 @@
   }
 
  private:
-  std::unique_ptr<views::Widget> widget_;
+  std::unique_ptr<Widget> widget_;
   std::unique_ptr<SlideOutController> slide_out_controller_;
-  std::unique_ptr<SlideOutControllerDelegate> delegate_;
+  std::unique_ptr<TestSlideOutControllerDelegate> delegate_;
   base::TimeDelta sequential_event_timestamp_;
 };
 
@@ -507,4 +507,4 @@
   EXPECT_EQ(0, delegate()->slide_out_count_);
 }
 
-}  // namespace message_center
+}  // namespace views