diff --git a/DEPS b/DEPS
index 90c20c7..ac72a5c 100644
--- a/DEPS
+++ b/DEPS
@@ -142,11 +142,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': '4d03689edb5dfdce22f11414d2351ce2b221a410',
+  'skia_revision': '38ae3f42fec129839767e96b05ada677c2f9b22d',
   # 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': 'da069071ec861916b268f20bcc21d2d440299a74',
+  'v8_revision': '7a88817255bf8557ebdcff2dacaf4cf6f1ff8ecd',
   # 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.
@@ -154,7 +154,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': 'da904484bfc696193c15d89c9c6e847afeb978cc',
+  'angle_revision': '6b5830333673c370d9ee086af3e14a9c5e46b928',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -205,7 +205,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling catapult
   # and whatever else without interference from each other.
-  'catapult_revision': '63f7fcc05ac1011f52d81e926f8638e7aea91133',
+  'catapult_revision': 'e9399f9f929fbb6cc36a9f8fcae866e75512d487',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -261,7 +261,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'spv_tools_revision': 'e6e3e2ccc6a2a9bf43730d8edccf0b1a212d660a',
+  'spv_tools_revision': '9702d47c6fe4cefbc55f905b0e9966452124b6c2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -277,7 +277,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'dawn_revision': '4886403b610c39e2fa92463503b5b6d872b82736',
+  'dawn_revision': '72508d6d0634bdb28353c2ec26bbe79307ab916a',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -806,7 +806,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '104b067af6a2eea63e010a4d2a3038c59f178436',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '5e4b1bc773108632898be413caa4f9dbf8592f89',
       'condition': 'checkout_linux',
   },
 
@@ -831,7 +831,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'dc37feb99d46bdf499715ceb467d341d24524e0b',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'ffd96e6df09708e09e5e3e467a2ffd322e8cb143',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -900,7 +900,7 @@
   },
 
   'src/third_party/glslang/src':
-    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '22683b409e6df419da940df561b24b4b5d8ab90a',
+    Var('chromium_git') + '/external/github.com/KhronosGroup/glslang.git' + '@' + '4b4b41a63499d34c527ee4f714dde8072f60c900',
 
   'src/third_party/google_toolbox_for_mac/src': {
       'url': Var('chromium_git') + '/external/github.com/google/google-toolbox-for-mac.git' + '@' + Var('google_toolbox_for_mac_revision'),
@@ -1356,7 +1356,7 @@
     Var('chromium_git') + '/external/github.com/SeleniumHQ/selenium/py.git' + '@' + 'd0045ec570c1a77612db35d1e92f05e1d27b4d53',
 
   'src/third_party/webgl/src':
-    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '6f0b34abee8dba611c253738d955c59f703c147a',
+    Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + '91350f8ecf9ab2922ee062c114e4a759f24bd8d0',
 
   'src/third_party/webrtc':
     Var('webrtc_git') + '/src.git' + '@' + '02d7d353a90bccc6fdd0e517af31fab50944d31c',
@@ -1400,7 +1400,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@dbcafbd04544f460eadeccb3a7f8f5b6149937cc',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@097e6a0deb3091ac1c592c18ab61b4e2162467cd',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 5ac749f..e03180a 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -431,6 +431,8 @@
     "login/ui/login_user_menu_view.h",
     "login/ui/login_user_view.cc",
     "login/ui/login_user_view.h",
+    "login/ui/media_controls_header_view.cc",
+    "login/ui/media_controls_header_view.h",
     "login/ui/non_accessible_view.cc",
     "login/ui/non_accessible_view.h",
     "login/ui/note_action_launch_button.cc",
@@ -1658,6 +1660,7 @@
     "login/ui/fake_login_detachable_base_model.cc",
     "login/ui/fake_login_detachable_base_model.h",
     "login/ui/lock_contents_view_unittest.cc",
+    "login/ui/lock_screen_media_controls_view_unittest.cc",
     "login/ui/lock_screen_sanity_unittest.cc",
     "login/ui/lock_window_unittest.cc",
     "login/ui/login_auth_user_view_unittest.cc",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 6a9c319c..07c5f7f3 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -1970,6 +1970,11 @@
       <message name="IDS_ASH_MEDIA_NOTIFICATION_ACCESSIBLE_NAME" desc="The accessible name for the media notification to control playback">
         Media Controls
       </message>
+
+        <!-- Lock Screen Media Controls -->
+      <message name="IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACCESSIBLE_NAME" desc="The accessible name for the lock screen media controls view.">
+        Media Controls
+      </message>
     </messages>
   </release>
 </grit>
diff --git a/ash/login/ui/lock_screen_media_controls_view.cc b/ash/login/ui/lock_screen_media_controls_view.cc
index cee939e..e502ea8a 100644
--- a/ash/login/ui/lock_screen_media_controls_view.cc
+++ b/ash/login/ui/lock_screen_media_controls_view.cc
@@ -3,22 +3,44 @@
 // found in the LICENSE file.
 
 #include "ash/login/ui/lock_screen_media_controls_view.h"
+
 #include "ash/login/ui/lock_contents_view.h"
+#include "ash/login/ui/media_controls_header_view.h"
 #include "ash/media/media_controller_impl.h"
 #include "ash/shell.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "components/media_message_center/media_notification_util.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/vector_icons.h"
 #include "ui/views/background.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/layout/box_layout.h"
 
 namespace ash {
 
 namespace {
-// Total width of the media controls view.
-const int kMediaControlsTotalWidthDp = 320;
 
-// Total height of the media controls view.
-const int kMediaControlsTotalHeightDp = 400;
+constexpr SkColor kMediaControlsBackground = SkColorSetA(SK_ColorDKGRAY, 100);
+
+// Dimensions.
+constexpr int kMediaControlsTotalWidthDp = 320;
+constexpr int kMediaControlsTotalHeightDp = 400;
+constexpr int kMediaControlsCornerRadius = 8;
+constexpr gfx::Insets kMediaControlsInsets = gfx::Insets(25, 25, 50, 25);
+constexpr int kMediaControlsChildSpacing = 50;
+constexpr int kMinimumIconSize = 16;
+constexpr int kDesiredIconSize = 20;
+constexpr int kIconSize = 20;
+constexpr int kMinimumArtworkSize = 200;
+constexpr int kDesiredArtworkSize = 300;
+constexpr int kArtworkViewWidth = 270;
+constexpr int kArtworkViewHeight = 200;
 
 // How long to wait (in milliseconds) for a new media session to begin.
 constexpr base::TimeDelta kNextMediaDelay =
@@ -26,6 +48,25 @@
 
 constexpr const char kLockScreenMediaControlsViewName[] =
     "LockScreenMediaControlsView";
+
+// Scales |size| to fit |view_size| while preserving proportions.
+gfx::Size ScaleSizeToFitView(const gfx::Size& size,
+                             const gfx::Size& view_size) {
+  // If |size| is too big in either dimension or two small in both
+  // dimensions, scale it appropriately.
+  if ((size.width() > view_size.width() ||
+       size.height() > view_size.height()) ||
+      (size.width() < view_size.width() &&
+       size.height() < view_size.height())) {
+    const float scale =
+        std::min(view_size.width() / static_cast<float>(size.width()),
+                 view_size.height() / static_cast<float>(size.height()));
+    return gfx::ScaleToFlooredSize(size, scale);
+  }
+
+  return size;
+}
+
 }  // namespace
 
 LockScreenMediaControlsView::LockScreenMediaControlsView(
@@ -34,13 +75,32 @@
     : view_(view),
       connector_(connector),
       hide_controls_timer_(new base::OneShotTimer()) {
-  SetBackground(views::CreateSolidBackground(SK_ColorBLACK));
+  SetBackground(views::CreateRoundedRectBackground(kMediaControlsBackground,
+                                                   kMediaControlsCornerRadius));
   middle_spacing_ = std::make_unique<NonAccessibleView>();
   middle_spacing_->set_owned_by_client();
 
   // Media controls have not been dismissed initially.
   Shell::Get()->media_controller()->SetMediaControlsDismissed(false);
 
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kVertical, kMediaControlsInsets,
+      kMediaControlsChildSpacing));
+
+  header_row_ = AddChildView(std::make_unique<MediaControlsHeaderView>());
+
+  auto session_artwork = std::make_unique<views::ImageView>();
+  session_artwork->SetPreferredSize(
+      gfx::Size(kArtworkViewWidth, kArtworkViewHeight));
+  session_artwork_ = AddChildView(std::move(session_artwork));
+
+  // Set child view data to default values initially, until the media controller
+  // observers are triggered by a change in media session state..
+  MediaSessionMetadataChanged(base::nullopt);
+  MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kSourceIcon, SkBitmap());
+  SetArtwork(base::nullopt);
+
   // |connector_| can be null in tests.
   if (!connector_)
     return;
@@ -57,17 +117,40 @@
   media_session::mojom::MediaControllerObserverPtr media_controller_observer;
   observer_binding_.Bind(mojo::MakeRequest(&media_controller_observer));
   media_controller_ptr_->AddObserver(std::move(media_controller_observer));
+
+  media_session::mojom::MediaControllerImageObserverPtr artwork_observer;
+  artwork_observer_binding_.Bind(mojo::MakeRequest(&artwork_observer));
+  media_controller_ptr_->ObserveImages(
+      media_session::mojom::MediaSessionImageType::kArtwork,
+      kMinimumArtworkSize, kDesiredArtworkSize, std::move(artwork_observer));
+
+  media_session::mojom::MediaControllerImageObserverPtr icon_observer;
+  icon_observer_binding_.Bind(mojo::MakeRequest(&icon_observer));
+  media_controller_ptr_->ObserveImages(
+      media_session::mojom::MediaSessionImageType::kSourceIcon,
+      kMinimumIconSize, kDesiredIconSize, std::move(icon_observer));
 }
 
 LockScreenMediaControlsView::~LockScreenMediaControlsView() = default;
 
-// views::View:
+const char* LockScreenMediaControlsView::GetClassName() const {
+  return kLockScreenMediaControlsViewName;
+}
+
 gfx::Size LockScreenMediaControlsView::CalculatePreferredSize() const {
   return gfx::Size(kMediaControlsTotalWidthDp, kMediaControlsTotalHeightDp);
 }
 
-const char* LockScreenMediaControlsView::GetClassName() const {
-  return kLockScreenMediaControlsViewName;
+void LockScreenMediaControlsView::GetAccessibleNodeData(
+    ui::AXNodeData* node_data) {
+  node_data->role = ax::mojom::Role::kListItem;
+  node_data->AddStringAttribute(
+      ax::mojom::StringAttribute::kRoleDescription,
+      l10n_util::GetStringUTF8(
+          IDS_ASH_LOCK_SCREEN_MEDIA_CONTROLS_ACCESSIBLE_NAME));
+
+  if (!accessible_name_.empty())
+    node_data->SetName(accessible_name_);
 }
 
 views::View* LockScreenMediaControlsView::GetMiddleSpacingView() {
@@ -103,4 +186,51 @@
   media_session_info_ = std::move(session_info);
 }
 
+void LockScreenMediaControlsView::MediaSessionMetadataChanged(
+    const base::Optional<media_session::MediaMetadata>& metadata) {
+  media_session::MediaMetadata session_metadata =
+      metadata.value_or(media_session::MediaMetadata());
+  base::string16 source_title =
+      session_metadata.source_title.empty()
+          ? message_center::MessageCenter::Get()->GetSystemNotificationAppName()
+          : session_metadata.source_title;
+  header_row_->SetAppName(source_title);
+
+  accessible_name_ =
+      media_message_center::GetAccessibleNameFromMetadata(session_metadata);
+}
+
+void LockScreenMediaControlsView::MediaControllerImageChanged(
+    media_session::mojom::MediaSessionImageType type,
+    const SkBitmap& bitmap) {
+  switch (type) {
+    case media_session::mojom::MediaSessionImageType::kArtwork: {
+      base::Optional<gfx::ImageSkia> session_artwork =
+          gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+      SetArtwork(session_artwork);
+      break;
+    }
+    case media_session::mojom::MediaSessionImageType::kSourceIcon: {
+      gfx::ImageSkia session_icon = gfx::ImageSkia::CreateFrom1xBitmap(bitmap);
+      if (session_icon.isNull()) {
+        session_icon = gfx::CreateVectorIcon(message_center::kProductIcon,
+                                             kIconSize, gfx::kChromeIconGrey);
+      }
+      header_row_->SetAppIcon(session_icon);
+    }
+  }
+}
+
+void LockScreenMediaControlsView::SetArtwork(
+    base::Optional<gfx::ImageSkia> img) {
+  if (!img.has_value()) {
+    session_artwork_->SetImage(nullptr);
+    return;
+  }
+
+  session_artwork_->SetImageSize(ScaleSizeToFitView(
+      img->size(), gfx::Size(kArtworkViewWidth, kArtworkViewHeight)));
+  session_artwork_->SetImage(*img);
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/lock_screen_media_controls_view.h b/ash/login/ui/lock_screen_media_controls_view.h
index 2f971bf..78498fd7 100644
--- a/ash/login/ui/lock_screen_media_controls_view.h
+++ b/ash/login/ui/lock_screen_media_controls_view.h
@@ -13,23 +13,30 @@
 
 namespace service_manager {
 class Connector;
-}  // namespace service_manager
+}
+
+namespace views {
+class ImageView;
+}
 
 namespace ash {
 
 class LockContentsView;
+class MediaControlsHeaderView;
 
-class LockScreenMediaControlsView
+class ASH_EXPORT LockScreenMediaControlsView
     : public views::View,
-      public media_session::mojom::MediaControllerObserver {
+      public media_session::mojom::MediaControllerObserver,
+      public media_session::mojom::MediaControllerImageObserver {
  public:
   LockScreenMediaControlsView(service_manager::Connector* connector,
                               LockContentsView* view);
   ~LockScreenMediaControlsView() override;
 
   // views::View:
-  gfx::Size CalculatePreferredSize() const override;
   const char* GetClassName() const override;
+  gfx::Size CalculatePreferredSize() const override;
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
   views::View* GetMiddleSpacingView();
 
@@ -37,18 +44,29 @@
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr session_info) override;
   void MediaSessionMetadataChanged(
-      const base::Optional<media_session::MediaMetadata>& metadata) override {}
+      const base::Optional<media_session::MediaMetadata>& metadata) override;
   void MediaSessionActionsChanged(
       const std::vector<media_session::mojom::MediaSessionAction>& actions)
       override {}
   void MediaSessionChanged(
       const base::Optional<base::UnguessableToken>& request_id) override {}
 
+  // media_session::mojom::MediaControllerImageObserver:
+  void MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType type,
+      const SkBitmap& bitmap) override;
+
   void set_timer_for_testing(std::unique_ptr<base::OneShotTimer> test_timer) {
     hide_controls_timer_ = std::move(test_timer);
   }
 
  private:
+  friend class LockScreenMediaControlsViewTest;
+
+  // Sets the media artwork to |img|. If |img| is nullopt, the default artwork
+  // is set instead.
+  void SetArtwork(base::Optional<gfx::ImageSkia> img);
+
   // Lock screen view which this view belongs to.
   LockContentsView* const view_;
 
@@ -62,6 +80,14 @@
   mojo::Binding<media_session::mojom::MediaControllerObserver>
       observer_binding_{this};
 
+  // Used to receive updates to the active media's icon.
+  mojo::Binding<media_session::mojom::MediaControllerImageObserver>
+      icon_observer_binding_{this};
+
+  // Used to receive updates to the active media's artwork.
+  mojo::Binding<media_session::mojom::MediaControllerImageObserver>
+      artwork_observer_binding_{this};
+
   // The info about the current media session. It will be null if there is not
   // a current session.
   media_session::mojom::MediaSessionInfoPtr media_session_info_;
@@ -72,6 +98,14 @@
   // Automatically hides the controls a few seconds if no media playing.
   std::unique_ptr<base::OneShotTimer> hide_controls_timer_;
 
+  // Caches the text to be read by screen readers describing the media controls
+  // view.
+  base::string16 accessible_name_;
+
+  // Container views directly attached to this view.
+  MediaControlsHeaderView* header_row_ = nullptr;
+  views::ImageView* session_artwork_ = nullptr;
+
   DISALLOW_COPY_AND_ASSIGN(LockScreenMediaControlsView);
 };
 
diff --git a/ash/login/ui/lock_screen_media_controls_view_unittest.cc b/ash/login/ui/lock_screen_media_controls_view_unittest.cc
new file mode 100644
index 0000000..4381981a
--- /dev/null
+++ b/ash/login/ui/lock_screen_media_controls_view_unittest.cc
@@ -0,0 +1,186 @@
+// 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 "ash/login/ui/lock_screen_media_controls_view.h"
+
+#include "ash/login/ui/fake_login_detachable_base_model.h"
+#include "ash/login/ui/lock_contents_view.h"
+#include "ash/login/ui/login_test_base.h"
+#include "ash/login/ui/media_controls_header_view.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/message_center/message_center.h"
+#include "ui/message_center/vector_icons.h"
+#include "ui/views/controls/image_view.h"
+
+namespace ash {
+
+namespace {
+
+const int kAppIconSize = 20;
+constexpr int kArtworkViewWidth = 270;
+constexpr int kArtworkViewHeight = 200;
+const base::string16 kTestAppName = base::ASCIIToUTF16("Test app");
+
+}  // namespace
+
+class LockScreenMediaControlsViewTest : public LoginTestBase {
+ public:
+  LockScreenMediaControlsViewTest() = default;
+  ~LockScreenMediaControlsViewTest() override = default;
+
+  void SetUp() override {
+    LoginTestBase::SetUp();
+
+    lock_contents_view_ = new LockContentsView(
+        mojom::TrayActionState::kAvailable, LockScreen::ScreenType::kLock,
+        DataDispatcher(),
+        std::make_unique<FakeLoginDetachableBaseModel>(DataDispatcher()));
+    LockContentsView::TestApi lock_contents(lock_contents_view_);
+
+    std::unique_ptr<views::Widget> widget =
+        CreateWidgetWithContent(lock_contents_view_);
+    SetWidget(std::move(widget));
+
+    SetUserCount(1);
+
+    media_controls_view_ = lock_contents.media_controls_view();
+  }
+
+  MediaControlsHeaderView* header_row() const {
+    return media_controls_view_->header_row_;
+  }
+
+  views::ImageView* artwork_view() const {
+    return media_controls_view_->session_artwork_;
+  }
+
+  const gfx::ImageSkia& GetAppIcon() const {
+    return header_row()->app_icon_for_testing();
+  }
+
+  const base::string16& GetAppName() const {
+    return header_row()->app_name_for_testing();
+  }
+
+  LockScreenMediaControlsView* media_controls_view_ = nullptr;
+
+ private:
+  LockContentsView* lock_contents_view_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(LockScreenMediaControlsViewTest);
+};
+
+TEST_F(LockScreenMediaControlsViewTest, UpdateAppIcon) {
+  gfx::ImageSkia default_icon = gfx::CreateVectorIcon(
+      message_center::kProductIcon, kAppIconSize, gfx::kChromeIconGrey);
+
+  // Verify that the icon is initialized to the default.
+  EXPECT_TRUE(GetAppIcon().BackedBySameObjectAs(default_icon));
+  EXPECT_EQ(kAppIconSize, GetAppIcon().width());
+  EXPECT_EQ(kAppIconSize, GetAppIcon().height());
+
+  media_controls_view_->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kSourceIcon, SkBitmap());
+
+  // Verify that the default icon is used if no icon is provided.
+  EXPECT_TRUE(GetAppIcon().BackedBySameObjectAs(default_icon));
+  EXPECT_EQ(kAppIconSize, GetAppIcon().width());
+  EXPECT_EQ(kAppIconSize, GetAppIcon().height());
+
+  SkBitmap bitmap;
+  bitmap.allocN32Pixels(kAppIconSize, kAppIconSize);
+  media_controls_view_->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kSourceIcon, bitmap);
+
+  // Verify that the provided icon is used.
+  EXPECT_FALSE(GetAppIcon().BackedBySameObjectAs(default_icon));
+  EXPECT_EQ(kAppIconSize, GetAppIcon().width());
+  EXPECT_EQ(kAppIconSize, GetAppIcon().height());
+}
+
+TEST_F(LockScreenMediaControlsViewTest, UpdateAppName) {
+  // Verify that the app name is initialized to the default.
+  EXPECT_EQ(
+      message_center::MessageCenter::Get()->GetSystemNotificationAppName(),
+      GetAppName());
+
+  media_session::MediaMetadata metadata;
+  media_controls_view_->MediaSessionMetadataChanged(metadata);
+
+  // Verify that default name is used if no name is provided.
+  EXPECT_EQ(
+      message_center::MessageCenter::Get()->GetSystemNotificationAppName(),
+      GetAppName());
+
+  metadata.source_title = kTestAppName;
+  media_controls_view_->MediaSessionMetadataChanged(metadata);
+
+  // Verify that the provided app name is used.
+  EXPECT_EQ(kTestAppName, GetAppName());
+}
+
+TEST_F(LockScreenMediaControlsViewTest, UpdateArtwork) {
+  // Verify that the artwork is initially empty.
+  EXPECT_TRUE(artwork_view()->GetImage().isNull());
+
+  // Create artwork that must be scaled down to fit the view.
+  SkBitmap artwork;
+  artwork.allocN32Pixels(540, 300);
+
+  media_controls_view_->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kArtwork, artwork);
+
+  gfx::Rect artwork_bounds = artwork_view()->GetImageBounds();
+
+  // Verify that the provided artwork is correctly scaled down.
+  EXPECT_EQ(kArtworkViewWidth, artwork_bounds.width());
+  EXPECT_EQ(150, artwork_bounds.height());
+
+  // Create artwork that must be scaled up to fit the view.
+  artwork.allocN32Pixels(200, 190);
+
+  media_controls_view_->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kArtwork, artwork);
+
+  artwork_bounds = artwork_view()->GetImageBounds();
+
+  // Verify that the provided artwork is correctly scaled up.
+  EXPECT_EQ(210, artwork_bounds.width());
+  EXPECT_EQ(kArtworkViewHeight, artwork_bounds.height());
+
+  // Create artwork that already fits the view size.
+  artwork.allocN32Pixels(250, kArtworkViewHeight);
+
+  media_controls_view_->MediaControllerImageChanged(
+      media_session::mojom::MediaSessionImageType::kArtwork, artwork);
+
+  artwork_bounds = artwork_view()->GetImageBounds();
+
+  // Verify that the provided artwork size doesn't change.
+  EXPECT_EQ(250, artwork_bounds.width());
+  EXPECT_EQ(kArtworkViewHeight, artwork_bounds.height());
+}
+
+TEST_F(LockScreenMediaControlsViewTest, AccessibleNodeData) {
+  ui::AXNodeData data;
+  media_controls_view_->GetAccessibleNodeData(&data);
+
+  // Verify that the accessible name is initially empty.
+  EXPECT_FALSE(data.HasStringAttribute(ax::mojom::StringAttribute::kName));
+
+  // Update the metadata.
+  media_session::MediaMetadata metadata;
+  metadata.title = base::ASCIIToUTF16("title");
+  metadata.artist = base::ASCIIToUTF16("artist");
+  media_controls_view_->MediaSessionMetadataChanged(metadata);
+  media_controls_view_->GetAccessibleNodeData(&data);
+
+  // Verify that the accessible name updates with the metadata.
+  EXPECT_TRUE(
+      data.HasStringAttribute(ax::mojom::StringAttribute::kRoleDescription));
+  EXPECT_EQ(base::ASCIIToUTF16("title - artist"),
+            data.GetString16Attribute(ax::mojom::StringAttribute::kName));
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/login/ui/media_controls_header_view.cc b/ash/login/ui/media_controls_header_view.cc
new file mode 100644
index 0000000..103dd42738
--- /dev/null
+++ b/ash/login/ui/media_controls_header_view.cc
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/login/ui/media_controls_header_view.h"
+
+#include "ui/accessibility/ax_node_data.h"
+#include "ui/gfx/font_list.h"
+#include "ui/views/background.h"
+#include "ui/views/border.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/layout/box_layout.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kIconSize = 20;
+constexpr int kHeaderTextFontSize = 14;
+constexpr int kMediaControlsHeaderChildSpacing = 10;
+constexpr gfx::Insets kIconPadding = gfx::Insets(1, 1, 1, 1);
+constexpr int kIconCornerRadius = 2;
+
+}  // namespace
+
+MediaControlsHeaderView::MediaControlsHeaderView() {
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal, gfx::Insets(),
+      kMediaControlsHeaderChildSpacing));
+
+  auto app_icon_view = std::make_unique<views::ImageView>();
+  app_icon_view->SetImageSize(gfx::Size(kIconSize, kIconSize));
+  app_icon_view->SetVerticalAlignment(views::ImageView::Alignment::kLeading);
+  app_icon_view->SetHorizontalAlignment(views::ImageView::Alignment::kLeading);
+  app_icon_view->SetBorder(views::CreateEmptyBorder(kIconPadding));
+  app_icon_view->SetBackground(
+      views::CreateRoundedRectBackground(SK_ColorWHITE, kIconCornerRadius));
+  app_icon_view_ = AddChildView(std::move(app_icon_view));
+
+  // Font list for text views.
+  gfx::Font default_font;
+  int font_size_delta = kHeaderTextFontSize - default_font.GetFontSize();
+  gfx::Font font = default_font.Derive(font_size_delta, gfx::Font::NORMAL,
+                                       gfx::Font::Weight::NORMAL);
+  gfx::FontList font_list(font);
+
+  auto app_name_view = std::make_unique<views::Label>();
+  app_name_view->SetFontList(font_list);
+  app_name_view->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+  app_name_view->SetEnabledColor(SK_ColorWHITE);
+  app_name_view->SetAutoColorReadabilityEnabled(false);
+  app_name_view_ = AddChildView(std::move(app_name_view));
+}
+
+MediaControlsHeaderView::~MediaControlsHeaderView() = default;
+
+void MediaControlsHeaderView::SetAppIcon(const gfx::ImageSkia& img) {
+  app_icon_view_->SetImage(img);
+}
+
+void MediaControlsHeaderView::SetAppName(const base::string16& name) {
+  app_name_view_->SetText(name);
+}
+
+void MediaControlsHeaderView::GetAccessibleNodeData(ui::AXNodeData* node_data) {
+  node_data->SetName(app_name_view_->GetText());
+}
+
+const base::string16& MediaControlsHeaderView::app_name_for_testing() const {
+  return app_name_view_->GetText();
+}
+
+const gfx::ImageSkia& MediaControlsHeaderView::app_icon_for_testing() const {
+  return app_icon_view_->GetImage();
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/login/ui/media_controls_header_view.h b/ash/login/ui/media_controls_header_view.h
new file mode 100644
index 0000000..3d6154b
--- /dev/null
+++ b/ash/login/ui/media_controls_header_view.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_LOGIN_UI_MEDIA_CONTROLS_HEADER_VIEW_H_
+#define ASH_LOGIN_UI_MEDIA_CONTROLS_HEADER_VIEW_H_
+
+#include "ash/ash_export.h"
+#include "ui/views/view.h"
+
+namespace views {
+class ImageView;
+class Label;
+}  // namespace views
+
+namespace ash {
+
+class ASH_EXPORT MediaControlsHeaderView : public views::View {
+ public:
+  MediaControlsHeaderView();
+  ~MediaControlsHeaderView() override;
+
+  void SetAppIcon(const gfx::ImageSkia& img);
+  void SetAppName(const base::string16& name);
+
+  // views::View:
+  void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
+
+  const base::string16& app_name_for_testing() const;
+  const gfx::ImageSkia& app_icon_for_testing() const;
+
+ private:
+  views::ImageView* app_icon_view_;
+  views::Label* app_name_view_;
+
+  DISALLOW_COPY_AND_ASSIGN(MediaControlsHeaderView);
+};
+
+}  // namespace ash
+
+#endif  // ASH_LOGIN_UI_MEDIA_CONTROLS_HEADER_VIEW_H_
\ No newline at end of file
diff --git a/ash/system/audio/unified_volume_view.cc b/ash/system/audio/unified_volume_view.cc
index 90196b74a..62b30c3 100644
--- a/ash/system/audio/unified_volume_view.cc
+++ b/ash/system/audio/unified_volume_view.cc
@@ -9,6 +9,7 @@
 #include "ash/system/audio/unified_volume_slider_controller.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_popup_utils.h"
+#include "base/i18n/rtl.h"
 #include "base/stl_util.h"
 #include "components/vector_icons/vector_icons.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -73,9 +74,12 @@
 
     auto* more = new views::ImageView();
     more->set_can_process_events_within_subtree(false);
+    auto icon_rotation = base::i18n::IsRTL()
+                             ? SkBitmapOperations::ROTATION_270_CW
+                             : SkBitmapOperations::ROTATION_90_CW;
     more->SetImage(gfx::ImageSkiaOperations::CreateRotatedImage(
         CreateVectorIcon(kUnifiedMenuExpandIcon, kUnifiedMenuIconColor),
-        SkBitmapOperations::ROTATION_90_CW));
+        icon_rotation));
     AddChildView(more);
 
     SetTooltipText(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_AUDIO));
diff --git a/ash/wm/overview/scoped_overview_transform_window.cc b/ash/wm/overview/scoped_overview_transform_window.cc
index 31965c33..8f8c992 100644
--- a/ash/wm/overview/scoped_overview_transform_window.cc
+++ b/ash/wm/overview/scoped_overview_transform_window.cc
@@ -379,6 +379,8 @@
   overview_bounds_.reset();
 }
 
+// TODO(sammiequon): Investigate if waiting till the end of the animation to set
+// these properties is still required.
 void ScopedOverviewTransformWindow::UpdateMask(bool show) {
   // Minimized windows have their corners rounded in CaptionContainerView.
   if (IsMinimized())
@@ -392,11 +394,13 @@
                                         : 0.0f);
   layer->SetRoundedCornerRadius(radii);
   layer->SetIsFastRoundedCorner(true);
-  int top_inset = GetTopInset();
-  if (top_inset > 0) {
-    gfx::Rect clip_rect(window_->bounds().size());
-    clip_rect.Inset(0, top_inset, 0, 0);
-    layer->SetClipRect(clip_rect);
+  if (!layer->GetAnimator()->is_animating()) {
+    int top_inset = GetTopInset();
+    if (top_inset > 0) {
+      gfx::Rect clip_rect(window_->bounds().size());
+      clip_rect.Inset(0, top_inset, 0, 0);
+      layer->SetClipRect(clip_rect);
+    }
   }
 }
 
diff --git a/build/fuchsia/device_target.py b/build/fuchsia/device_target.py
index 0d7b4473..6a51396 100644
--- a/build/fuchsia/device_target.py
+++ b/build/fuchsia/device_target.py
@@ -160,7 +160,7 @@
 
     if self._node_name:
       # Handle the result of "dev_finder resolve".
-      self._host = output[0].strip()
+      self._host = output.pop().strip()
 
     else:
       name_host_pairs = [x.strip().split(' ') for x in output]
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index db96dbf..a0e1d62 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-8909016706543512784
\ No newline at end of file
+8908988868010627552
\ No newline at end of file
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index d5d8184..0145f776 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-8909064474621102512
\ No newline at end of file
+8908988579903450896
\ No newline at end of file
diff --git a/cc/animation/animation_host.cc b/cc/animation/animation_host.cc
index 5168e82..f2d85b5 100644
--- a/cc/animation/animation_host.cc
+++ b/cc/animation/animation_host.cc
@@ -653,10 +653,12 @@
     ElementId element_id,
     const gfx::ScrollOffset& target_offset,
     const gfx::ScrollOffset& current_offset,
-    float autoscroll_velocity) {
+    float autoscroll_velocity,
+    base::TimeDelta animation_start_offset) {
   DCHECK(scroll_offset_animations_impl_);
   scroll_offset_animations_impl_->AutoScrollAnimationCreate(
-      element_id, target_offset, current_offset, autoscroll_velocity);
+      element_id, target_offset, current_offset, autoscroll_velocity,
+      animation_start_offset);
 }
 
 void AnimationHost::ImplOnlyScrollAnimationCreate(
diff --git a/cc/animation/animation_host.h b/cc/animation/animation_host.h
index 84cba1b..0e770e06 100644
--- a/cc/animation/animation_host.h
+++ b/cc/animation/animation_host.h
@@ -168,7 +168,8 @@
       ElementId element_id,
       const gfx::ScrollOffset& target_offset,
       const gfx::ScrollOffset& current_offset,
-      float autoscroll_velocity) override;
+      float autoscroll_velocity,
+      base::TimeDelta animation_start_offset) override;
 
   void ImplOnlyScrollAnimationCreate(
       ElementId element_id,
diff --git a/cc/animation/scroll_offset_animation_curve.cc b/cc/animation/scroll_offset_animation_curve.cc
index d713fa375..0bc523f 100644
--- a/cc/animation/scroll_offset_animation_curve.cc
+++ b/cc/animation/scroll_offset_animation_curve.cc
@@ -103,7 +103,8 @@
             kInverseDeltaMaxDuration);
         break;
       case DurationBehavior::CONSTANT_VELOCITY:
-        duration = MaximumDimension(delta) / velocity * kDurationDivisor;
+        duration =
+            std::abs(MaximumDimension(delta) / velocity * kDurationDivisor);
         break;
       default:
         NOTREACHED();
diff --git a/cc/animation/scroll_offset_animations_impl.cc b/cc/animation/scroll_offset_animations_impl.cc
index a54fa300..5c7ff936 100644
--- a/cc/animation/scroll_offset_animations_impl.cc
+++ b/cc/animation/scroll_offset_animations_impl.cc
@@ -38,7 +38,8 @@
     ElementId element_id,
     const gfx::ScrollOffset& target_offset,
     const gfx::ScrollOffset& current_offset,
-    float autoscroll_velocity) {
+    float autoscroll_velocity,
+    base::TimeDelta animation_start_offset) {
   std::unique_ptr<ScrollOffsetAnimationCurve> curve =
       ScrollOffsetAnimationCurve::Create(
           target_offset, LinearTimingFunction::Create(),
@@ -46,7 +47,7 @@
   curve->SetInitialValue(current_offset, base::TimeDelta(),
                          autoscroll_velocity);
   ScrollAnimationCreateInternal(element_id, std::move(curve),
-                                base::TimeDelta());
+                                animation_start_offset);
 }
 
 void ScrollOffsetAnimationsImpl::ScrollAnimationCreate(
diff --git a/cc/animation/scroll_offset_animations_impl.h b/cc/animation/scroll_offset_animations_impl.h
index 3f959faa..4371deed 100644
--- a/cc/animation/scroll_offset_animations_impl.h
+++ b/cc/animation/scroll_offset_animations_impl.h
@@ -35,7 +35,8 @@
   void AutoScrollAnimationCreate(ElementId element_id,
                                  const gfx::ScrollOffset& target_offset,
                                  const gfx::ScrollOffset& current_offset,
-                                 float autoscroll_velocity);
+                                 float autoscroll_velocity,
+                                 base::TimeDelta animation_start_offset);
 
   // |delayed_by| shrinks the duration of the
   // animation. |animation_start_offset| causes us to start the animation
diff --git a/cc/input/scrollbar.h b/cc/input/scrollbar.h
index ec23fef..c63c800 100644
--- a/cc/input/scrollbar.h
+++ b/cc/input/scrollbar.h
@@ -10,8 +10,15 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 
-static const int kPixelsPerLineStep = 40;
-static const float kMinFractionToStepWhenPaging = 0.875f;
+static constexpr int kPixelsPerLineStep = 40;
+static constexpr float kMinFractionToStepWhenPaging = 0.875f;
+
+// Autoscrolling (on the main thread) happens by applying a delta every 50ms.
+// Hence, pixels per second for a autoscroll cc animation can be calculated as:
+// autoscroll velocity = delta / 0.05 sec = delta x 20
+static constexpr float kAutoscrollMultiplier = 20.f;
+static constexpr base::TimeDelta kInitialAutoscrollTimerDelay =
+    base::TimeDelta::FromMilliseconds(250);
 
 namespace cc {
 
diff --git a/cc/input/scrollbar_controller.cc b/cc/input/scrollbar_controller.cc
index d29a257..d95235f 100644
--- a/cc/input/scrollbar_controller.cc
+++ b/cc/input/scrollbar_controller.cc
@@ -6,19 +6,30 @@
 
 #include <algorithm>
 
+#include "base/cancelable_callback.h"
 #include "cc/base/math_util.h"
 #include "cc/input/scrollbar.h"
 #include "cc/input/scrollbar_controller.h"
 #include "cc/trees/layer_tree_impl.h"
+#include "cc/trees/scroll_node.h"
 
 namespace cc {
+ScrollbarController::~ScrollbarController() {
+  if (cancelable_autoscroll_task_) {
+    cancelable_autoscroll_task_->Cancel();
+    cancelable_autoscroll_task_.reset();
+  }
+}
+
 ScrollbarController::ScrollbarController(
     LayerTreeHostImpl* layer_tree_host_impl)
     : layer_tree_host_impl_(layer_tree_host_impl),
       scrollbar_scroll_is_active_(false),
       thumb_drag_in_progress_(false),
+      autoscroll_in_progress_(false),
       currently_captured_scrollbar_(nullptr),
-      previous_pointer_position_(gfx::PointF(0, 0)) {}
+      previous_pointer_position_(gfx::PointF(0, 0)),
+      cancelable_autoscroll_task_(nullptr) {}
 
 // Performs hit test and prepares scroll deltas that will be used by GSB and
 // GSU.
@@ -49,6 +60,19 @@
         ui::input_types::ScrollGranularity::kScrollByPixel;
   }
 
+  // Thumb drag is the only scrollbar manipulation that cannot produce an
+  // autoscroll. All other interactions like clicking on arrows/trackparts have
+  // the potential of initiating an autoscroll (if held down long enough).
+  if (!scroll_result.scroll_offset.IsZero() && !thumb_drag_in_progress_) {
+    cancelable_autoscroll_task_ = std::make_unique<base::CancelableClosure>(
+        base::Bind(&ScrollbarController::StartAutoScrollAnimation,
+                   base::Unretained(this), scroll_result.scroll_offset,
+                   currently_captured_scrollbar_->scroll_element_id()));
+    layer_tree_host_impl_->task_runner_provider()
+        ->ImplThreadTaskRunner()
+        ->PostDelayedTask(FROM_HERE, cancelable_autoscroll_task_->callback(),
+                          kInitialAutoscrollTimerDelay);
+  }
   return scroll_result;
 }
 
@@ -138,6 +162,52 @@
   return scroll_result;
 }
 
+void ScrollbarController::StartAutoScrollAnimation(
+    gfx::ScrollOffset scroll_offset,
+    ElementId element_id) {
+  // scroll_node is set up while handling GSB. If there's no node to scroll, we
+  // don't need to create any animation for it.
+  ScrollTree& scroll_tree =
+      layer_tree_host_impl_->active_tree()->property_trees()->scroll_tree;
+  ScrollNode* scroll_node = scroll_tree.FindNodeFromElementId(element_id);
+
+  if (!(scroll_node && scrollbar_scroll_is_active_))
+    return;
+
+  layer_tree_host_impl_->active_tree()->UpdateScrollbarGeometries();
+  ScrollbarOrientation orientation =
+      currently_captured_scrollbar_->orientation();
+  // TODO(arakeri): The animation needs to be readjusted if the scroller length
+  // changes. Tracked here: crbug.com/972485
+  float scroll_layer_length =
+      currently_captured_scrollbar_->scroll_layer_length();
+
+  gfx::ScrollOffset current_offset =
+      scroll_tree.current_scroll_offset(scroll_node->element_id);
+  gfx::Vector2dF target_offset;
+
+  // Determine the max offset for the scroll based on the scrolling direction.
+  // Negative scroll_delta indicates backwards scrolling whereas a positive
+  // scroll_delta indicates forwards scrolling.
+  float scroll_delta = 0;
+  if (orientation == ScrollbarOrientation::VERTICAL) {
+    DCHECK_NE(scroll_offset.y(), 0);
+    scroll_delta = scroll_offset.y();
+    float final_offset = scroll_delta < 0 ? 0 : scroll_layer_length;
+    target_offset = gfx::Vector2dF(current_offset.x(), final_offset);
+  } else {
+    DCHECK_NE(scroll_offset.x(), 0);
+    scroll_delta = scroll_offset.x();
+    float final_offset = scroll_delta < 0 ? 0 : scroll_layer_length;
+    target_offset = gfx::Vector2dF(final_offset, current_offset.y());
+  }
+
+  float autoscroll_velocity = std::abs(scroll_delta) * kAutoscrollMultiplier;
+  autoscroll_in_progress_ = true;
+  layer_tree_host_impl_->AutoScrollAnimationCreate(scroll_node, target_offset,
+                                                   autoscroll_velocity);
+}
+
 // Performs hit test and prepares scroll deltas that will be used by GSE.
 InputHandlerPointerResult ScrollbarController::HandleMouseUp(
     const gfx::PointF position_in_widget) {
@@ -146,7 +216,20 @@
     scrollbar_scroll_is_active_ = false;
     scroll_result.type = PointerResultType::kScrollbarScroll;
   }
+
+  // TODO(arakeri): This needs to be moved to ScrollOffsetAnimationsImpl as it
+  // has knowledge about what type of animation is running. crbug.com/976353
+  // Only abort the animation if it is an "autoscroll" animation.
+  if (autoscroll_in_progress_)
+    layer_tree_host_impl_->mutator_host()->ScrollAnimationAbort();
+
+  if (cancelable_autoscroll_task_) {
+    cancelable_autoscroll_task_->Cancel();
+    cancelable_autoscroll_task_.reset();
+  }
+
   thumb_drag_in_progress_ = false;
+  autoscroll_in_progress_ = false;
   return scroll_result;
 }
 
diff --git a/cc/input/scrollbar_controller.h b/cc/input/scrollbar_controller.h
index 3ce46246..517fa9f51 100644
--- a/cc/input/scrollbar_controller.h
+++ b/cc/input/scrollbar_controller.h
@@ -18,7 +18,7 @@
 class CC_EXPORT ScrollbarController {
  public:
   explicit ScrollbarController(LayerTreeHostImpl*);
-  virtual ~ScrollbarController() = default;
+  virtual ~ScrollbarController();
 
   InputHandlerPointerResult HandleMouseDown(
       const gfx::PointF position_in_widget);
@@ -26,6 +26,16 @@
       const gfx::PointF position_in_widget);
   InputHandlerPointerResult HandleMouseUp(const gfx::PointF position_in_widget);
 
+  // scroll_offset is the delta from the initial click. This is needed to
+  // determine whether we should set up the autoscrolling in the forwards or the
+  // backwards direction and the speed of the animation.
+  void StartAutoScrollAnimation(gfx::ScrollOffset scroll_offset,
+                                ElementId element_id);
+  bool ScrollbarScrollIsActive() { return scrollbar_scroll_is_active_; }
+  ScrollbarOrientation orientation() {
+    return currently_captured_scrollbar_->orientation();
+  }
+
  private:
   // Returns a gfx::ScrollOffset object which contains scroll deltas for the
   // synthetic Gesture events.
@@ -43,10 +53,17 @@
 
   // Used to tell if the scrollbar thumb is getting dragged.
   bool thumb_drag_in_progress_;
+
+  // "Autoscroll" here means the continuous scrolling that occurs when the
+  // pointer is held down on a hit-testable area of the scrollbar such as an
+  // arrows of the track itself.
+  bool autoscroll_in_progress_;
   const ScrollbarLayerImplBase* currently_captured_scrollbar_;
 
   // This is relative to the RenderWidget's origin.
   gfx::PointF previous_pointer_position_;
+
+  std::unique_ptr<base::CancelableClosure> cancelable_autoscroll_task_;
 };
 
 }  // namespace cc
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 9465fb7..d645e1d2 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -38,6 +38,7 @@
 #include "cc/input/page_scale_animation.h"
 #include "cc/input/scroll_elasticity_helper.h"
 #include "cc/input/scroll_state.h"
+#include "cc/input/scrollbar.h"
 #include "cc/input/scrollbar_animation_controller.h"
 #include "cc/input/scroller_size_metrics.h"
 #include "cc/input/snap_selection_strategy.h"
@@ -3939,14 +3940,31 @@
   return gfx::Vector2dF(scrolled.x(), scrolled.y());
 }
 
+bool LayerTreeHostImpl::AutoScrollAnimationCreate(ScrollNode* scroll_node,
+                                                  const gfx::Vector2dF& delta,
+                                                  float autoscroll_velocity) {
+  return ScrollAnimationCreateInternal(scroll_node, delta, base::TimeDelta(),
+                                       autoscroll_velocity);
+}
+
 bool LayerTreeHostImpl::ScrollAnimationCreate(ScrollNode* scroll_node,
                                               const gfx::Vector2dF& delta,
                                               base::TimeDelta delayed_by) {
+  return ScrollAnimationCreateInternal(scroll_node, delta, delayed_by,
+                                       base::nullopt);
+}
+
+bool LayerTreeHostImpl::ScrollAnimationCreateInternal(
+    ScrollNode* scroll_node,
+    const gfx::Vector2dF& delta,
+    base::TimeDelta delayed_by,
+    base::Optional<float> autoscroll_velocity) {
   ScrollTree& scroll_tree = active_tree_->property_trees()->scroll_tree;
 
   const float kEpsilon = 0.1f;
   bool scroll_animated =
-      (std::abs(delta.x()) > kEpsilon || std::abs(delta.y()) > kEpsilon);
+      (std::abs(delta.x()) > kEpsilon || std::abs(delta.y()) > kEpsilon) ||
+      autoscroll_velocity;
   if (!scroll_animated) {
     scroll_tree.ScrollBy(scroll_node, delta, active_tree());
     TRACE_EVENT_INSTANT0("cc", "no scroll animation due to small delta",
@@ -3966,9 +3984,15 @@
   // input latency tracking architecture from working.
   base::TimeDelta animation_start_offset = CurrentBeginFrameArgs().interval;
 
-  mutator_host_->ImplOnlyScrollAnimationCreate(
-      scroll_node->element_id, target_offset, current_offset, delayed_by,
-      animation_start_offset);
+  if (autoscroll_velocity) {
+    mutator_host_->ImplOnlyAutoScrollAnimationCreate(
+        scroll_node->element_id, gfx::ScrollOffset(delta), current_offset,
+        autoscroll_velocity.value(), animation_start_offset);
+  } else {
+    mutator_host_->ImplOnlyScrollAnimationCreate(
+        scroll_node->element_id, target_offset, current_offset, delayed_by,
+        animation_start_offset);
+  }
 
   SetNeedsOneBeginImplFrame();
 
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index a348b86..4115da9 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -726,6 +726,9 @@
   bool ScrollAnimationCreate(ScrollNode* scroll_node,
                              const gfx::Vector2dF& scroll_amount,
                              base::TimeDelta delayed_by);
+  bool AutoScrollAnimationCreate(ScrollNode* scroll_node,
+                                 const gfx::Vector2dF& scroll_amount,
+                                 float autoscroll_velocity);
 
   void SetLayerTreeMutator(std::unique_ptr<LayerTreeMutator> mutator);
   void SetPaintWorkletLayerPainter(
@@ -817,6 +820,10 @@
       const gfx::PointF& viewport_point,
       const gfx::Vector2dF& viewport_delta,
       ScrollTree* scroll_tree);
+  bool ScrollAnimationCreateInternal(ScrollNode* scroll_node,
+                                     const gfx::Vector2dF& delta,
+                                     base::TimeDelta delayed_by,
+                                     base::Optional<float> autoscroll_velocity);
 
   void CleanUpTileManagerResources();
   void CreateTileManagerResources();
diff --git a/cc/trees/mutator_host.h b/cc/trees/mutator_host.h
index 5496c42..49673bd6 100644
--- a/cc/trees/mutator_host.h
+++ b/cc/trees/mutator_host.h
@@ -135,7 +135,8 @@
       ElementId element_id,
       const gfx::ScrollOffset& target_offset,
       const gfx::ScrollOffset& current_offset,
-      float autoscroll_velocity) = 0;
+      float autoscroll_velocity,
+      base::TimeDelta animation_start_offset) = 0;
 
   virtual void ImplOnlyScrollAnimationCreate(
       ElementId element_id,
diff --git a/chrome/VERSION b/chrome/VERSION
index f358afb7..4fa6b80 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
 MAJOR=77
 MINOR=0
-BUILD=3842
+BUILD=3843
 PATCH=0
diff --git a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
index e061d311..739d1a180 100644
--- a/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
+++ b/chrome/android/features/keyboard_accessory/internal/java/src/org/chromium/chrome/browser/keyboard_accessory/ManualFillingMediator.java
@@ -255,7 +255,7 @@
     }
 
     void showWhenKeyboardIsVisible() {
-        if (!isInitialized() || mKeyboardAccessory.empty()) return;
+        if (!isInitialized()) return;
         mModel.set(SHOW_WHEN_VISIBLE, true);
         if (is(HIDDEN)) mModel.set(KEYBOARD_EXTENSION_STATE, FLOATING_BAR);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index eacc712..1d997d3a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -256,6 +256,7 @@
     public static final String SEARCH_ENGINE_PROMO_EXISTING_DEVICE =
             "SearchEnginePromo.ExistingDevice";
     public static final String SEARCH_ENGINE_PROMO_NEW_DEVICE = "SearchEnginePromo.NewDevice";
+    // TODO(crbug.com/980849) Remove ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY
     public static final String MOBILE_IDENTITY_CONSISTENCY = "MobileIdentityConsistency";
     public static final String MODAL_PERMISSION_PROMPTS = "ModalPermissionPrompts";
     public static final String MODAL_PERMISSION_DIALOG_VIEW = "ModalPermissionDialogView";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
index 53c0bdf4..d2f2c2c2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
@@ -27,7 +27,6 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.PostTask;
-import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.components.signin.AccountIdProvider;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.AccountTrackerService;
@@ -308,7 +307,7 @@
     public boolean isSigninSupported() {
         return !ApiCompatibilityUtils.isDemoUser(mContext)
                 && mDelegate.isGooglePlayServicesPresent(mContext)
-                && !ChromeFeatureList.isEnabled(ChromeFeatureList.MOBILE_IDENTITY_CONSISTENCY);
+                && !SigninManagerJni.get().isMobileIdentityConsistencyEnabled();
     }
 
     /**
@@ -732,5 +731,7 @@
         boolean isSignedInOnNative(@JCaller SigninManager self, long nativeSigninManagerAndroid);
 
         String extractDomainName(String email);
+
+        boolean isMobileIdentityConsistencyEnabled();
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerDelegate.java
index 3ad643d0..3ee437d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManagerDelegate.java
@@ -19,7 +19,7 @@
     /**
      * Perform destruction of the object, including destructing the associated native object.
      */
-    public void destroy();
+    void destroy();
 
     /**
      * If there is no Google Play Services available, ask the user to fix by showing either a
@@ -27,17 +27,17 @@
      * @param activity The activity used to open the dialog, or null to use notifications
      * @param cancelable Whether the dialog can be canceled
      */
-    public void handleGooglePlayServicesUnavailability(Activity activity, boolean cancelable);
+    void handleGooglePlayServicesUnavailability(Activity activity, boolean cancelable);
 
     /**
      * @return the management domain if the signed in account is managed, otherwise null.
      */
-    public String getManagementDomain();
+    String getManagementDomain();
 
     /**
      * @return Whether the device has Google Play Services.
      */
-    public boolean isGooglePlayServicesPresent(Context context);
+    boolean isGooglePlayServicesPresent(Context context);
 
     /**
      * Verifies if the account is managed. Callback may be called either synchronously or
@@ -46,25 +46,25 @@
      * @param callback The callback that will receive true if the account is managed, false
      *                 otherwise.
      */
-    public void isAccountManaged(String email, final Callback<Boolean> callback);
+    void isAccountManaged(String email, final Callback<Boolean> callback);
 
     /**
      * Interact with the UserPolicySigninService to retrieve the user policy.
      * @param username (email) of the user signing in.
      * @param callback The callback called once the policy is retrieved and applied
      */
-    public void fetchAndApplyCloudPolicy(String username, Runnable callback);
+    void fetchAndApplyCloudPolicy(String username, Runnable callback);
 
     /**
      * Perform the required cloud policy cleanup when a signin is aborted.
      */
-    public void stopApplyingCloudPolicy();
+    void stopApplyingCloudPolicy();
 
     /**
      * Called AFTER native sign-in is complete, enabling Sync.
      * @param account to be used by sync
      */
-    public void enableSync(Account account);
+    void enableSync(Account account);
 
     /**
      * Called AFTER native sign-out is complete, this method clears various
@@ -73,5 +73,5 @@
      *                            different cleanup flow
      * @param wipeDataCallback to be called once profile data cleanup is complete
      */
-    public void disableSyncAndWipeData(boolean isManagedOrForceWipe, Runnable wipeDataCallback);
+    void disableSyncAndWipeData(boolean isManagedOrForceWipe, Runnable wipeDataCallback);
 }
diff --git a/chrome/android/profiles/newest.txt b/chrome/android/profiles/newest.txt
index 7fc3f33..b8a80341 100644
--- a/chrome/android/profiles/newest.txt
+++ b/chrome/android/profiles/newest.txt
@@ -1 +1 @@
-chromeos-chrome-amd64-77.0.3841.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
+chromeos-chrome-amd64-77.0.3842.0_rc-r1-merged.afdo.bz2
\ No newline at end of file
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 0984de9..15ca8c9 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -3632,12 +3632,6 @@
      flag_descriptions::kEnableGpuServiceLoggingDescription, kOsAll,
      SINGLE_VALUE_TYPE(switches::kEnableGPUServiceLogging)},
 
-#if defined(OS_CHROMEOS)
-    {"crostini-app-search", flag_descriptions::kCrostiniAppSearchName,
-     flag_descriptions::kCrostiniAppSearchDescription, kOsCrOS,
-     FEATURE_VALUE_TYPE(features::kCrostiniAppSearch)},
-#endif  // OS_CHROMEOS
-
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX)
     {"autofill-settings-split-by-card-type",
      flag_descriptions::kAutofillSettingsSplitByCardTypeName,
diff --git a/chrome/browser/android/signin/signin_manager_android.cc b/chrome/browser/android/signin/signin_manager_android.cc
index 4db70d6..d573bd5f 100644
--- a/chrome/browser/android/signin/signin_manager_android.cc
+++ b/chrome/browser/android/signin/signin_manager_android.cc
@@ -9,12 +9,14 @@
 
 #include "base/android/jni_string.h"
 #include "base/bind.h"
+#include "base/feature_list.h"
 #include "chrome/android/chrome_jni_headers/SigninManager_jni.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
 #include "chrome/common/pref_names.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/core/browser/account_consistency_method.h"
 #include "components/signin/core/browser/primary_account_manager.h"
 #include "components/signin/core/browser/signin_pref_names.h"
 #include "google_apis/gaia/gaia_auth_util.h"
@@ -142,3 +144,7 @@
   std::string domain = gaia::ExtractDomainName(email);
   return base::android::ConvertUTF8ToJavaString(env, domain);
 }
+
+jboolean JNI_SigninManager_IsMobileIdentityConsistencyEnabled(JNIEnv* env) {
+  return base::FeatureList::IsEnabled(signin::kMiceFeature);
+}
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 9446e6a2..dca38b7 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -1205,23 +1205,6 @@
                      weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
 }
 
-void CrostiniManager::GetLinuxPackageInfoFromApt(
-    const std::string& vm_name,
-    const std::string& container_name,
-    const std::string& package_name,
-    GetLinuxPackageInfoCallback callback) {
-  vm_tools::cicerone::LinuxPackageInfoRequest request;
-  request.set_owner_id(owner_id_);
-  request.set_vm_name(vm_name);
-  request.set_container_name(container_name);
-  request.set_package_name(package_name);
-
-  GetCiceroneClient()->GetLinuxPackageInfo(
-      std::move(request),
-      base::BindOnce(&CrostiniManager::OnGetLinuxPackageInfo,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
 void CrostiniManager::InstallLinuxPackage(
     std::string vm_name,
     std::string container_name,
@@ -1456,23 +1439,6 @@
   std::move(callback).Run(/*success=*/true, std::move(mount_points));
 }
 
-void CrostiniManager::SearchApp(const std::string& vm_name,
-                                const std::string& container_name,
-                                const std::string& query,
-                                SearchAppCallback callback) {
-  vm_tools::cicerone::AppSearchRequest request;
-
-  request.set_owner_id(owner_id_);
-  request.set_vm_name(std::move(vm_name));
-  request.set_container_name(std::move(container_name));
-  request.set_query(query);
-
-  GetCiceroneClient()->SearchApp(
-      std::move(request),
-      base::BindOnce(&CrostiniManager::OnSearchApp,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
 CrostiniManager::RestartId CrostiniManager::RestartCrostini(
     std::string vm_name,
     std::string container_name,
@@ -2334,20 +2300,6 @@
   }
 }
 
-void CrostiniManager::OnSearchApp(
-    SearchAppCallback callback,
-    base::Optional<vm_tools::cicerone::AppSearchResponse> response) {
-  std::vector<std::string> package_names;
-  if (!response) {
-    LOG(ERROR) << "Failed to SearchApp. Empty response.";
-    std::move(callback).Run(package_names);
-    return;
-  }
-  for (auto& package : response->packages())
-    package_names.push_back(package.package_name());
-  std::move(callback).Run(package_names);
-}
-
 void CrostiniManager::OnExportLxdContainer(
     std::string vm_name,
     std::string container_name,
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.h b/chrome/browser/chromeos/crostini/crostini_manager.h
index 7d2f1d65..83711de 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.h
+++ b/chrome/browser/chromeos/crostini/crostini_manager.h
@@ -400,13 +400,6 @@
                            std::string package_path,
                            GetLinuxPackageInfoCallback callback);
 
-  // Asynchronously retrieve information about a Linux Package in the APT
-  // repository. This uses a package_name to identify a package.
-  void GetLinuxPackageInfoFromApt(const std::string& vm_name,
-                                  const std::string& container_name,
-                                  const std::string& package_name,
-                                  GetLinuxPackageInfoCallback callback);
-
   // Begin installation of a Linux Package inside the container. If the
   // installation is successfully started, further updates will be sent to
   // added LinuxPackageOperationProgressObservers.
@@ -472,15 +465,6 @@
   void ListUsbDevices(const std::string& vm_name,
                       ListUsbDevicesCallback callback);
 
-  // Searches for not installed packages that have names matching the passed
-  // plaintext search query and returns a vector containing their names.
-  using SearchAppCallback =
-      base::OnceCallback<void(const std::vector<std::string>& package_names)>;
-  void SearchApp(const std::string& vm_name,
-                 const std::string& container_name,
-                 const std::string& query,
-                 SearchAppCallback callback);
-
   using RestartId = int;
   static const RestartId kUninitializedRestartId = -1;
   // Runs all the steps required to restart the given crostini vm and container.
@@ -718,8 +702,7 @@
       GetContainerAppIconsCallback callback,
       base::Optional<vm_tools::cicerone::ContainerAppIconResponse> response);
 
-  // Callback for CrostiniManager::GetLinuxPackageInfo and
-  // CrostiniManager::GetLinuxPackageInfoFromApt.
+  // Callback for CrostiniManager::GetLinuxPackageInfo.
   void OnGetLinuxPackageInfo(
       GetLinuxPackageInfoCallback callback,
       base::Optional<vm_tools::cicerone::LinuxPackageInfoResponse> response);
@@ -762,11 +745,6 @@
       ListUsbDevicesCallback callback,
       base::Optional<vm_tools::concierge::ListUsbDeviceResponse> response);
 
-  // Callback for CrostiniManager::SearchApp.
-  void OnSearchApp(
-      SearchAppCallback callback,
-      base::Optional<vm_tools::cicerone::AppSearchResponse> response);
-
   // Helper for CrostiniManager::MaybeUpgradeCrostini. Makes blocking calls to
   // check for file paths and registered components.
   static void CheckPathsAndComponents();
diff --git a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
index 9cb7eee..50329b89 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager_unittest.cc
@@ -124,13 +124,6 @@
     std::move(closure).Run();
   }
 
-  void SearchAppCallback(base::OnceClosure closure,
-                         const std::vector<std::string>& expected_result,
-                         const std::vector<std::string>& result) {
-    EXPECT_THAT(result, testing::ContainerEq(expected_result));
-    std::move(closure).Run();
-  }
-
   void GetLinuxPackageInfoFromAptCallback(
       base::OnceClosure closure,
       const LinuxPackageInfo& expected_result,
@@ -1200,92 +1193,6 @@
   run_loop()->Run();
 }
 
-TEST_F(CrostiniManagerTest, SearchAppSuccess) {
-  vm_tools::cicerone::AppSearchResponse response;
-  vm_tools::cicerone::AppSearchResponse::AppSearchResult* app =
-      response.add_packages();
-  app->set_package_name("fake app");
-  app = response.add_packages();
-  app->set_package_name("fake app1");
-  app = response.add_packages();
-  app->set_package_name("fake app2");
-  fake_cicerone_client_->set_search_app_response(response);
-  std::vector<std::string> expected = {"fake app", "fake app1", "fake app2"};
-  crostini_manager()->SearchApp(
-      kVmName, kContainerName, "fake ap",
-      base::BindOnce(&CrostiniManagerTest::SearchAppCallback,
-                     base::Unretained(this), run_loop()->QuitClosure(),
-                     expected));
-  run_loop()->Run();
-}
-
-TEST_F(CrostiniManagerTest, SearchAppNoResults) {
-  vm_tools::cicerone::AppSearchResponse response;
-  fake_cicerone_client_->set_search_app_response(response);
-  std::vector<std::string> expected = {};
-  crostini_manager()->SearchApp(
-      kVmName, kContainerName, "fake ap",
-      base::BindOnce(&CrostiniManagerTest::SearchAppCallback,
-                     base::Unretained(this), run_loop()->QuitClosure(),
-                     expected));
-  run_loop()->Run();
-}
-
-TEST_F(CrostiniManagerTest, GetLinuxPackageInfoFromAptFailedToGetInfo) {
-  const char kFailMessage[] = "Failed to get package info.";
-  vm_tools::cicerone::LinuxPackageInfoResponse response;
-  response.set_success(false);
-  response.set_failure_reason(kFailMessage);
-  fake_cicerone_client_->set_linux_package_info_response(response);
-  LinuxPackageInfo expected;
-  expected.success = false;
-  expected.failure_reason = kFailMessage;
-  crostini_manager()->GetLinuxPackageInfoFromApt(
-      kVmName, kContainerName, "fake app",
-      base::BindOnce(&CrostiniManagerTest::GetLinuxPackageInfoFromAptCallback,
-                     base::Unretained(this), run_loop()->QuitClosure(),
-                     expected));
-  run_loop()->Run();
-}
-
-TEST_F(CrostiniManagerTest, GetLinuxPackageInfoFromAptInvalidID) {
-  vm_tools::cicerone::LinuxPackageInfoResponse response;
-  response.set_success(true);
-  response.set_package_id("Bad;;id;");
-  fake_cicerone_client_->set_linux_package_info_response(response);
-  LinuxPackageInfo expected;
-  expected.success = false;
-  expected.failure_reason = "Linux package info contained invalid package id.";
-  crostini_manager()->GetLinuxPackageInfoFromApt(
-      kVmName, kContainerName, "fake app",
-      base::BindOnce(&CrostiniManagerTest::GetLinuxPackageInfoFromAptCallback,
-                     base::Unretained(this), run_loop()->QuitClosure(),
-                     expected));
-  run_loop()->Run();
-}
-
-TEST_F(CrostiniManagerTest, GetLinuxPackageInfoFromAptSuccess) {
-  vm_tools::cicerone::LinuxPackageInfoResponse response;
-  response.set_success(true);
-  response.set_package_id("good;1.1;id;");
-  response.set_summary("A summary");
-  response.set_description("A description");
-  fake_cicerone_client_->set_linux_package_info_response(response);
-  LinuxPackageInfo expected;
-  expected.success = true;
-  expected.package_id = "good;1.1;id;";
-  expected.name = "good";
-  expected.version = "1.1";
-  expected.summary = "A summary";
-  expected.description = "A description";
-  crostini_manager()->GetLinuxPackageInfoFromApt(
-      kVmName, kContainerName, "fake app",
-      base::BindOnce(&CrostiniManagerTest::GetLinuxPackageInfoFromAptCallback,
-                     base::Unretained(this), run_loop()->QuitClosure(),
-                     expected));
-  run_loop()->Run();
-}
-
 TEST_F(CrostiniManagerTest, InstallLinuxPackageFromAptSignalNotConnectedError) {
   fake_cicerone_client_->set_install_linux_package_progress_signal_connected(
       false);
diff --git a/chrome/browser/chromeos/crostini/crostini_package_service.cc b/chrome/browser/chromeos/crostini/crostini_package_service.cc
index e3833c9..de126ac 100644
--- a/chrome/browser/chromeos/crostini/crostini_package_service.cc
+++ b/chrome/browser/chromeos/crostini/crostini_package_service.cc
@@ -173,18 +173,6 @@
                      std::move(callback)));
 }
 
-void CrostiniPackageService::InstallLinuxPackageFromApt(
-    const std::string& vm_name,
-    const std::string& container_name,
-    const std::string& package_id,
-    CrostiniManager::InstallLinuxPackageCallback callback) {
-  CrostiniManager::GetForProfile(profile_)->InstallLinuxPackageFromApt(
-      vm_name, container_name, package_id,
-      base::BindOnce(&CrostiniPackageService::OnInstallLinuxPackage,
-                     weak_ptr_factory_.GetWeakPtr(), vm_name, container_name,
-                     std::move(callback)));
-}
-
 void CrostiniPackageService::OnInstallLinuxPackageProgress(
     const std::string& vm_name,
     const std::string& container_name,
diff --git a/chrome/browser/chromeos/crostini/crostini_package_service.h b/chrome/browser/chromeos/crostini/crostini_package_service.h
index ad13ee9..a82cb14 100644
--- a/chrome/browser/chromeos/crostini/crostini_package_service.h
+++ b/chrome/browser/chromeos/crostini/crostini_package_service.h
@@ -64,14 +64,6 @@
       const std::string& package_path,
       CrostiniManager::InstallLinuxPackageCallback callback);
 
-  // Install a Linux package via a package_id. If successfully started, a
-  // system notification will be used to display further updates.
-  void InstallLinuxPackageFromApt(
-      const std::string& vm_name,
-      const std::string& container_name,
-      const std::string& package_id,
-      CrostiniManager::InstallLinuxPackageCallback callback);
-
   // LinuxPackageOperationProgressObserver:
   void OnInstallLinuxPackageProgress(const std::string& vm_name,
                                      const std::string& container_name,
diff --git a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
index ce0772a6..661c64d9 100644
--- a/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
+++ b/chrome/browser/chromeos/file_manager/file_manager_browsertest_base.cc
@@ -744,91 +744,6 @@
   DISALLOW_COPY_AND_ASSIGN(LocalTestVolume);
 };
 
-// Removable TestVolume: local test volume for external media devices.
-class RemovableTestVolume : public LocalTestVolume {
- public:
-  RemovableTestVolume(const std::string& name,
-                      VolumeType volume_type,
-                      chromeos::DeviceType device_type,
-                      const base::FilePath& device_path,
-                      const std::string& drive_label)
-      : LocalTestVolume(name),
-        volume_type_(volume_type),
-        device_type_(device_type),
-        device_path_(device_path),
-        drive_label_(drive_label) {}
-  ~RemovableTestVolume() override = default;
-
-  bool PreparePartitionTestEntries(Profile* profile) {
-    if (!CreateRootDirectory(profile))
-      return false;
-
-    // Create fake file on the removable volume.
-    CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::FILE,
-                                                 "text.txt", "hello.txt")
-                    .SetMimeType("text/plain"));
-
-    CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::DIRECTORY,
-                                                 "", "Folder"));
-    base::RunLoop().RunUntilIdle();
-    return true;
-  }
-
-  bool PrepareUsbTestEntries(Profile* profile) {
-    if (!CreateRootDirectory(profile))
-      return false;
-
-    // Create fake file on the removable volume.
-    CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::FILE,
-                                                 "text.txt", "hello.txt")
-                    .SetMimeType("text/plain"));
-    CreateEntry(AddEntriesMessage::TestEntryInfo(AddEntriesMessage::DIRECTORY,
-                                                 "", "Folder"));
-
-    base::RunLoop().RunUntilIdle();
-    return true;
-  }
-
-  bool Mount(Profile* profile) override {
-    if (!CreateRootDirectory(profile))
-      return false;
-
-    // Revoke name() mount point first, then re-add its mount point.
-    GetMountPoints()->RevokeFileSystem(name());
-    const bool added = GetMountPoints()->RegisterFileSystem(
-        name(), storage::kFileSystemTypeNativeLocal,
-        storage::FileSystemMountOption(), root_path());
-    if (!added)
-      return false;
-
-    // Expose the mount point with the given volume and device type.
-    VolumeManager::Get(profile)->AddVolumeForTesting(
-        root_path(), volume_type_, device_type_, read_only_, device_path_,
-        drive_label_);
-    base::RunLoop().RunUntilIdle();
-    return true;
-  }
-
-  void Unmount(Profile* profile) {
-    VolumeManager::Get(profile)->RemoveVolumeForTesting(
-        root_path(), volume_type_, device_type_, read_only_, device_path_,
-        drive_label_);
-  }
-
- private:
-  storage::ExternalMountPoints* GetMountPoints() {
-    return storage::ExternalMountPoints::GetSystemInstance();
-  }
-
-  const VolumeType volume_type_;
-  const chromeos::DeviceType device_type_;
-  const base::FilePath device_path_;
-  const bool read_only_ = false;
-  const std::string drive_label_;
-
-  DISALLOW_COPY_AND_ASSIGN(RemovableTestVolume);
-};
-
 // DownloadsTestVolume: local test volume for the "Downloads" directory.
 class DownloadsTestVolume : public LocalTestVolume {
  public:
@@ -973,15 +888,7 @@
   }
 
   bool Mount(Profile* profile) override {
-    if (!CreateRootDirectory(profile))
-      return false;
-
-    // Revoke name() mount point first, then re-add its mount point.
-    GetMountPoints()->RevokeFileSystem(name());
-    const bool added = GetMountPoints()->RegisterFileSystem(
-        name(), storage::kFileSystemTypeNativeLocal,
-        storage::FileSystemMountOption(), root_path());
-    if (!added)
+    if (!MountSetup(profile))
       return false;
 
     // Expose the mount point with the given volume and device type.
@@ -996,18 +903,75 @@
         root_path(), volume_type_, device_type_, read_only_);
   }
 
- private:
+ protected:
   storage::ExternalMountPoints* GetMountPoints() {
     return storage::ExternalMountPoints::GetSystemInstance();
   }
 
+  bool MountSetup(Profile* profile) {
+    if (!CreateRootDirectory(profile))
+      return false;
+
+    // Revoke name() mount point first, then re-add its mount point.
+    GetMountPoints()->RevokeFileSystem(name());
+    const bool added = GetMountPoints()->RegisterFileSystem(
+        name(), storage::kFileSystemTypeNativeLocal,
+        storage::FileSystemMountOption(), root_path());
+    if (!added)
+      return false;
+
+    return true;
+  }
+
   const VolumeType volume_type_;
   const chromeos::DeviceType device_type_;
   const bool read_only_ = false;
 
+ private:
   DISALLOW_COPY_AND_ASSIGN(FakeTestVolume);
 };
 
+// Removable TestVolume: local test volume for external media devices.
+class RemovableTestVolume : public FakeTestVolume {
+ public:
+  RemovableTestVolume(const std::string& name,
+                      VolumeType volume_type,
+                      chromeos::DeviceType device_type,
+                      const base::FilePath& device_path,
+                      const std::string& drive_label,
+                      const std::string& file_system_type)
+      : FakeTestVolume(name, volume_type, device_type),
+        device_path_(device_path),
+        drive_label_(drive_label),
+        file_system_type_(file_system_type) {}
+  ~RemovableTestVolume() override = default;
+
+  bool Mount(Profile* profile) override {
+    if (!MountSetup(profile))
+      return false;
+
+    // Expose the mount point with the given volume and device type.
+    VolumeManager::Get(profile)->AddVolumeForTesting(
+        root_path(), volume_type_, device_type_, read_only_, device_path_,
+        drive_label_, file_system_type_);
+    base::RunLoop().RunUntilIdle();
+    return true;
+  }
+
+  void Unmount(Profile* profile) {
+    VolumeManager::Get(profile)->RemoveVolumeForTesting(
+        root_path(), volume_type_, device_type_, read_only_, device_path_,
+        drive_label_, file_system_type_);
+  }
+
+ private:
+  const base::FilePath device_path_;
+  const std::string drive_label_;
+  const std::string file_system_type_;
+
+  DISALLOW_COPY_AND_ASSIGN(RemovableTestVolume);
+};
+
 // DriveTestVolume: test volume for Google Drive.
 class DriveTestVolume : public TestVolume {
  public:
@@ -2000,9 +1964,14 @@
 
   if (name == "mountFakeUsb" || name == "mountFakeUsbEmpty" ||
       name == "mountFakeUsbDcim") {
-    usb_volume_ = std::make_unique<FakeTestVolume>(
+    std::string file_system = "ext4";
+    const std::string* file_system_param = value.FindStringKey("filesystem");
+    if (file_system_param) {
+      file_system = *file_system_param;
+    }
+    usb_volume_ = std::make_unique<RemovableTestVolume>(
         "fake-usb", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
-        chromeos::DEVICE_TYPE_USB);
+        chromeos::DEVICE_TYPE_USB, base::FilePath(), "FAKEUSB", file_system);
 
     if (name == "mountFakeUsb")
       ASSERT_TRUE(usb_volume_->PrepareTestEntries(profile()));
@@ -2016,6 +1985,7 @@
   if (name == "unmountUsb") {
     DCHECK(usb_volume_);
     usb_volume_->Unmount(profile());
+    return;
   }
 
   if (name == "mountUsbWithPartitions") {
@@ -2028,14 +1998,14 @@
     // Create partition volumes with the same device path and drive label.
     partition_1_ = std::make_unique<RemovableTestVolume>(
         "partition-1", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
-        chromeos::DEVICE_TYPE_USB, device_path, "Drive Label");
+        chromeos::DEVICE_TYPE_USB, device_path, "Drive Label", "ext4");
     partition_2_ = std::make_unique<RemovableTestVolume>(
         "partition-2", VOLUME_TYPE_REMOVABLE_DISK_PARTITION,
-        chromeos::DEVICE_TYPE_USB, device_path, "Drive Label");
+        chromeos::DEVICE_TYPE_USB, device_path, "Drive Label", "ext4");
 
     // Create fake entries on partitions.
-    ASSERT_TRUE(partition_1_->PreparePartitionTestEntries(profile()));
-    ASSERT_TRUE(partition_2_->PreparePartitionTestEntries(profile()));
+    ASSERT_TRUE(partition_1_->PrepareTestEntries(profile()));
+    ASSERT_TRUE(partition_2_->PrepareTestEntries(profile()));
 
     ASSERT_TRUE(partition_1_->Mount(profile()));
     ASSERT_TRUE(partition_2_->Mount(profile()));
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.cc b/chrome/browser/chromeos/file_manager/volume_manager.cc
index 98fe917..0da12c4d 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager.cc
@@ -442,7 +442,8 @@
     chromeos::DeviceType device_type,
     bool read_only,
     const base::FilePath& device_path,
-    const std::string& drive_label) {
+    const std::string& drive_label,
+    const std::string& file_system_type) {
   std::unique_ptr<Volume> volume(new Volume());
   volume->type_ = volume_type;
   volume->device_type_ = device_type;
@@ -455,7 +456,7 @@
   volume->volume_id_ = GenerateVolumeId(*volume);
   volume->drive_label_ = drive_label;
   if (volume_type == VOLUME_TYPE_REMOVABLE_DISK_PARTITION) {
-    volume->file_system_type_ = "ext4";
+    volume->file_system_type_ = file_system_type;
   }
   return volume;
 }
@@ -750,11 +751,13 @@
                                         chromeos::DeviceType device_type,
                                         bool read_only,
                                         const base::FilePath& device_path,
-                                        const std::string& drive_label) {
+                                        const std::string& drive_label,
+                                        const std::string& file_system_type) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
-  DoMountEvent(chromeos::MOUNT_ERROR_NONE,
-               Volume::CreateForTesting(path, volume_type, device_type,
-                                        read_only, device_path, drive_label));
+  DoMountEvent(
+      chromeos::MOUNT_ERROR_NONE,
+      Volume::CreateForTesting(path, volume_type, device_type, read_only,
+                               device_path, drive_label, file_system_type));
 }
 
 void VolumeManager::AddVolumeForTesting(std::unique_ptr<Volume> volume) {
@@ -762,17 +765,19 @@
   DoMountEvent(chromeos::MOUNT_ERROR_NONE, std::move(volume));
 }
 
-void VolumeManager::RemoveVolumeForTesting(const base::FilePath& path,
-                                           VolumeType volume_type,
-                                           chromeos::DeviceType device_type,
-                                           bool read_only,
-                                           const base::FilePath& device_path,
-                                           const std::string& drive_label) {
+void VolumeManager::RemoveVolumeForTesting(
+    const base::FilePath& path,
+    VolumeType volume_type,
+    chromeos::DeviceType device_type,
+    bool read_only,
+    const base::FilePath& device_path,
+    const std::string& drive_label,
+    const std::string& file_system_type) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DoUnmountEvent(
       chromeos::MOUNT_ERROR_NONE,
       *Volume::CreateForTesting(path, volume_type, device_type, read_only,
-                                device_path, drive_label));
+                                device_path, drive_label, file_system_type));
 }
 
 void VolumeManager::OnFileSystemMounted() {
diff --git a/chrome/browser/chromeos/file_manager/volume_manager.h b/chrome/browser/chromeos/file_manager/volume_manager.h
index 3588c1a5..6ce0775 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager.h
+++ b/chrome/browser/chromeos/file_manager/volume_manager.h
@@ -127,7 +127,8 @@
       chromeos::DeviceType device_type,
       bool read_only,
       const base::FilePath& device_path,
-      const std::string& drive_label);
+      const std::string& drive_label,
+      const std::string& file_system_type = "");
   static std::unique_ptr<Volume> CreateForTesting(
       const base::FilePath& device_path,
       const base::FilePath& mount_path);
@@ -348,7 +349,8 @@
                            chromeos::DeviceType device_type,
                            bool read_only,
                            const base::FilePath& device_path = base::FilePath(),
-                           const std::string& drive_label = "");
+                           const std::string& drive_label = "",
+                           const std::string& file_system_type = "");
 
   // For testing purposes, adds the volume info to the volume manager.
   void AddVolumeForTesting(std::unique_ptr<Volume> volume);
@@ -359,7 +361,8 @@
       chromeos::DeviceType device_type,
       bool read_only,
       const base::FilePath& device_path = base::FilePath(),
-      const std::string& drive_label = "");
+      const std::string& drive_label = "",
+      const std::string& file_system_type = "");
 
   // drive::DriveIntegrationServiceObserver overrides.
   void OnFileSystemMounted() override;
diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index 21671708..3f23bc5 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -848,7 +848,14 @@
 }
 
 #if defined(OS_ANDROID)
-TEST_F(ChromeDownloadManagerDelegateTest, InterceptDownloadByOfflinePages) {
+#if defined(DISABLE_OFFLINE_PAGES_TOUCHLESS)
+#define MAYBE_InterceptDownloadByOfflinePages \
+  DISABLED_InterceptDownloadByOfflinePages
+#else
+#define MAYBE_InterceptDownloadByOfflinePages InterceptDownloadByOfflinePages
+#endif
+TEST_F(ChromeDownloadManagerDelegateTest,
+       MAYBE_InterceptDownloadByOfflinePages) {
   const GURL kUrl("http://example.com/foo");
   std::string mime_type = "text/html";
   bool should_intercept = delegate()->InterceptDownloadIfApplicable(
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 67e2cb7..16c40758 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -1017,6 +1017,8 @@
       "api/image_writer_private/removable_storage_provider_linux.cc",
       "api/messaging/native_message_process_host.cc",
       "api/messaging/native_message_process_host.h",
+      "api/messaging/native_messaging_launch_from_native.cc",
+      "api/messaging/native_messaging_launch_from_native.h",
       "api/messaging/native_process_launcher.cc",
       "api/messaging/native_process_launcher.h",
       "api/messaging/native_process_launcher_win.cc",
diff --git a/chrome/browser/extensions/api/messaging/native_message_process_host.cc b/chrome/browser/extensions/api/messaging/native_message_process_host.cc
index 94b83a3e..c7dfc4d 100644
--- a/chrome/browser/extensions/api/messaging/native_message_process_host.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_process_host.cc
@@ -16,6 +16,7 @@
 #include "base/task/post_task.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/api/messaging/native_messaging_host_manifest.h"
+#include "chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h"
 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
 #include "chrome/browser/profiles/profile.h"
 #include "content/public/browser/browser_context.h"
@@ -44,9 +45,10 @@
 base::FilePath GetProfilePathIfEnabled(Profile* profile,
                                        const std::string& extension_id,
                                        const std::string& host_id) {
-  // TODO(crbug.com/967262): Return an empty path if the extension would not
-  // accept an inbound native messaging connection.
-  return profile->GetPath();
+  return extensions::ExtensionSupportsConnectionFromNativeApp(
+             extension_id, host_id, profile, /* log_errors = */ false)
+             ? profile->GetPath()
+             : base::FilePath();
 }
 
 }  // namespace
@@ -104,7 +106,8 @@
       NativeProcessLauncher::CreateDefault(
           allow_user_level, native_view,
           GetProfilePathIfEnabled(Profile::FromBrowserContext(browser_context),
-                                  source_extension_id, native_host_name)));
+                                  source_extension_id, native_host_name),
+          /* require_native_initiated_connections = */ false));
 }
 
 // static
diff --git a/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc b/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc
index 702ac7c..ddab3fb 100644
--- a/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc
+++ b/chrome/browser/extensions/api/messaging/native_message_process_host_unittest.cc
@@ -31,6 +31,7 @@
 #include "base/threading/platform_thread.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
+#include "chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h"
 #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
 #include "chrome/browser/profiles/profile.h"
@@ -321,6 +322,7 @@
 TEST_F(NativeMessagingTest, ReconnectArgs) {
   base::test::ScopedFeatureList feature_list;
   feature_list.InitAndEnableFeature(features::kOnConnectNative);
+  ScopedAllowNativeAppConnectionForTest allow_native_app_connection(true);
   ScopedTestNativeMessagingHost test_host;
   ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
   std::string error_message;
@@ -397,6 +399,34 @@
   EXPECT_TRUE(args->is_none());
 }
 
+// Test that reconnect args are not sent if the extension is not permitted to
+// receive natively-established connections.
+TEST_F(NativeMessagingTest, ReconnectArgsIfNativeConnectionDisallowed) {
+  base::test::ScopedFeatureList feature_list;
+  feature_list.InitAndEnableFeature(features::kOnConnectNative);
+  ScopedAllowNativeAppConnectionForTest disallow_native_app_connection(false);
+  ScopedTestNativeMessagingHost test_host;
+  ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
+  std::string error_message;
+  native_message_host_ = NativeMessageProcessHost::Create(
+      &profile_, NULL, ScopedTestNativeMessagingHost::kExtensionId,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      false, &error_message);
+  native_message_host_->Start(this);
+  ASSERT_TRUE(native_message_host_);
+
+  native_message_host_->OnMessage("{\"text\": \"Hello.\"}");
+  run_loop_.reset(new base::RunLoop());
+  run_loop_->Run();
+  ASSERT_FALSE(last_message_.empty());
+  ASSERT_TRUE(last_message_parsed_);
+
+  const base::Value* args_value = nullptr;
+  ASSERT_TRUE(last_message_parsed_->Get("args", &args_value));
+  EXPECT_TRUE(args_value->is_none());
+}
+
 TEST_F(NativeMessagingTest, UserLevel) {
   ScopedTestNativeMessagingHost test_host;
   ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(true));
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
index 0983cc2..d577d53 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_apitest.cc
@@ -2,10 +2,20 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "base/feature_list.h"
+#include "base/test/scoped_feature_list.h"
+#include "build/build_config.h"
 #include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
 #include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/startup/startup_browser_creator.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_switches.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/test/result_catcher.h"
 
 namespace extensions {
+namespace {
 
 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, NativeMessagingBasic) {
   extensions::ScopedTestNativeMessagingHost test_host;
@@ -19,4 +29,118 @@
   ASSERT_TRUE(RunExtensionTest("native_messaging")) << message_;
 }
 
+#if !defined(OS_CHROMEOS)
+
+class TestProcessManagerObserver : public ProcessManagerObserver {
+ public:
+  TestProcessManagerObserver() : observer_(this) {}
+
+  void WaitForProcessShutdown(ProcessManager* process_manager,
+                              const std::string& extension_id) {
+    DCHECK(!quit_);
+    extension_id_ = extension_id;
+    base::RunLoop run_loop;
+    quit_ = run_loop.QuitClosure();
+
+    observer_.Add(process_manager);
+    run_loop.Run();
+  }
+
+ private:
+  void OnBackgroundHostClose(const std::string& extension_id) override {
+    if (extension_id != extension_id_) {
+      return;
+    }
+    observer_.RemoveAll();
+    extension_id_.clear();
+    std::move(quit_).Run();
+  }
+
+  std::string extension_id_;
+  ScopedObserver<ProcessManager, TestProcessManagerObserver> observer_;
+  base::OnceClosure quit_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestProcessManagerObserver);
+};
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, NativeMessagingLaunch) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(features::kOnConnectNative);
+  ProcessManager::SetEventPageIdleTimeForTesting(1);
+  ProcessManager::SetEventPageSuspendingTimeForTesting(1);
+  extensions::ScopedTestNativeMessagingHost test_host;
+  ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
+
+  auto* extension =
+      LoadExtension(test_data_dir_.AppendASCII("native_messaging_launch"));
+  TestProcessManagerObserver observer;
+  observer.WaitForProcessShutdown(ProcessManager::Get(profile()),
+                                  extension->id());
+
+  ResultCatcher catcher;
+
+  base::CommandLine command_line(*base::CommandLine::ForCurrentProcess());
+  command_line.AppendSwitchASCII(switches::kNativeMessagingConnectExtension,
+                                 extension->id());
+  command_line.AppendSwitchASCII(
+      switches::kNativeMessagingConnectHost,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName);
+
+  StartupBrowserCreator::ProcessCommandLineAlreadyRunning(command_line, {},
+                                                          profile()->GetPath());
+
+  if (!catcher.GetNextResult()) {
+    FAIL() << catcher.message();
+  }
+  size_t tabs = 0;
+  for (auto* browser : *BrowserList::GetInstance()) {
+    tabs += browser->tab_strip_model()->count();
+  }
+  EXPECT_EQ(1u, tabs);
+}
+
+// Test that a natively-initiated connection from a host not supporting
+// 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,
+    NativeMessagingLaunch_LaunchFromNativeUnsupportedByNativeHost) {
+  base::test::ScopedFeatureList features;
+  features.InitAndEnableFeature(features::kOnConnectNative);
+  ProcessManager::SetEventPageIdleTimeForTesting(1);
+  ProcessManager::SetEventPageSuspendingTimeForTesting(1);
+  extensions::ScopedTestNativeMessagingHost test_host;
+  ASSERT_NO_FATAL_FAILURE(test_host.RegisterTestHost(false));
+
+  auto* extension = LoadExtension(
+      test_data_dir_.AppendASCII("native_messaging_launch_unsupported"));
+  TestProcessManagerObserver observer;
+  observer.WaitForProcessShutdown(ProcessManager::Get(profile()),
+                                  extension->id());
+
+  ResultCatcher catcher;
+
+  base::CommandLine command_line(*base::CommandLine::ForCurrentProcess());
+  command_line.AppendSwitchASCII(switches::kNativeMessagingConnectExtension,
+                                 extension->id());
+  command_line.AppendSwitchASCII(switches::kNativeMessagingConnectHost,
+                                 ScopedTestNativeMessagingHost::kHostName);
+
+  StartupBrowserCreator::ProcessCommandLineAlreadyRunning(command_line, {},
+                                                          profile()->GetPath());
+
+  if (!catcher.GetNextResult()) {
+    FAIL() << catcher.message();
+  }
+  size_t tabs = 0;
+  for (auto* browser : *BrowserList::GetInstance()) {
+    tabs += browser->tab_strip_model()->count();
+  }
+  EXPECT_EQ(1u, tabs);
+}
+
+#endif  // !defined(OS_CHROMEOS)
+
+}  // namespace
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc
new file mode 100644
index 0000000..eabfae99
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.cc
@@ -0,0 +1,126 @@
+// 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/extensions/api/messaging/native_messaging_launch_from_native.h"
+
+#include <memory>
+#include <utility>
+
+#include "chrome/browser/extensions/api/messaging/native_message_port.h"
+#include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
+#include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/extensions/manifest_handlers/natively_connectable_handler.h"
+#include "extensions/browser/api/messaging/channel_endpoint.h"
+#include "extensions/browser/api/messaging/message_service.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/api/messaging/messaging_endpoint.h"
+#include "extensions/common/permissions/permission_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+
+namespace extensions {
+namespace {
+
+ScopedAllowNativeAppConnectionForTest* g_allow_native_app_connection_for_test =
+    nullptr;
+
+}  // namespace
+
+bool ExtensionSupportsConnectionFromNativeApp(const std::string& extension_id,
+                                              const std::string& host_id,
+                                              Profile* profile,
+                                              bool log_errors) {
+  if (g_allow_native_app_connection_for_test) {
+    return g_allow_native_app_connection_for_test->allow();
+  }
+  if (profile->IsOffTheRecord()) {
+    return false;
+  }
+  auto* extension =
+      ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
+          extension_id);
+  if (!extension) {
+    LOG_IF(ERROR, log_errors)
+        << "Failed to launch native messaging connection: Unknown extension ID "
+        << extension_id;
+    return false;
+  }
+  const auto* natively_connectable_hosts =
+      NativelyConnectableHosts::GetConnectableNativeMessageHosts(*extension);
+  if (!natively_connectable_hosts ||
+      !natively_connectable_hosts->count(host_id)) {
+    LOG_IF(ERROR, log_errors)
+        << "Extension \"" << extension_id << "\" does not list \"" << host_id
+        << "\" in its natively_connectable manifest field";
+    return false;
+  }
+  if (!extension->permissions_data()->active_permissions().HasAPIPermission(
+          "nativeMessaging")) {
+    LOG_IF(ERROR, log_errors)
+        << "Extension \"" << extension_id
+        << "\" does not have the \"nativeMessaging\" permission";
+    return false;
+  }
+  if (!extension->permissions_data()->active_permissions().HasAPIPermission(
+          "transientBackground")) {
+    LOG_IF(ERROR, log_errors)
+        << "Extension \"" << extension_id
+        << "\" does not have the \"transientBackground\" permission";
+    return false;
+  }
+  if (!EventRouter::Get(profile)->ExtensionHasEventListener(
+          extension_id, "runtime.onConnectNative")) {
+    LOG_IF(ERROR, log_errors)
+        << "Failed to launch native messaging connection: Extension \""
+        << extension_id << "\" is not listening for runtime.onConnectNative";
+    return false;
+  }
+
+  return true;
+}
+
+ScopedAllowNativeAppConnectionForTest::ScopedAllowNativeAppConnectionForTest(
+    bool allow)
+    : allow_(allow) {
+  DCHECK(!g_allow_native_app_connection_for_test);
+  g_allow_native_app_connection_for_test = this;
+}
+
+ScopedAllowNativeAppConnectionForTest::
+    ~ScopedAllowNativeAppConnectionForTest() {
+  DCHECK_EQ(this, g_allow_native_app_connection_for_test);
+  g_allow_native_app_connection_for_test = nullptr;
+}
+
+void LaunchNativeMessageHostFromNativeApp(const std::string& extension_id,
+                                          const std::string& host_id,
+                                          Profile* profile) {
+  if (!ExtensionSupportsConnectionFromNativeApp(extension_id, host_id, profile,
+                                                /* log_errors = */ true)) {
+    // TODO(crbug.com/967262): Report errors to the native messaging host.
+    return;
+  }
+  const extensions::PortId port_id(base::UnguessableToken::Create(),
+                                   1 /* port_number */, true /* is_opener */);
+  extensions::MessageService* const message_service =
+      extensions::MessageService::Get(profile);
+  // TODO(crbug.com/967262): Apply policy for allow_user_level.
+  auto native_message_host = NativeMessageProcessHost::CreateWithLauncher(
+      extension_id, host_id,
+      NativeProcessLauncher::CreateDefault(
+          /* allow_user_level = */ true, /* native_view = */ nullptr,
+          profile->GetPath(),
+          /* require_native_initiated_connections = */ true));
+  auto native_message_port = std::make_unique<extensions::NativeMessagePort>(
+      message_service->GetChannelDelegate(), port_id,
+      std::move(native_message_host));
+  message_service->OpenChannelToExtension(
+      extensions::ChannelEndpoint(profile), port_id,
+      extensions::MessagingEndpoint::ForNativeApp(host_id),
+      std::move(native_message_port), extension_id, GURL(),
+      std::string() /* channel_name */);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h
new file mode 100644
index 0000000..d730422c
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h
@@ -0,0 +1,44 @@
+// 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_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGING_LAUNCH_FROM_NATIVE_H_
+#define CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGING_LAUNCH_FROM_NATIVE_H_
+
+#include <string>
+#include "base/macros.h"
+
+class Profile;
+
+namespace extensions {
+
+// Returns whether |extension_id| running in |profile| is allowed to accept
+// connections from native host named |host_id|.
+bool ExtensionSupportsConnectionFromNativeApp(const std::string& extension_id,
+                                              const std::string& host_id,
+                                              Profile* profile,
+                                              bool log_errors);
+
+// Creates a native messaging connection between the extension with ID
+// |extension_id| with |profile| and the native messaging host with name
+// |host_id|.
+void LaunchNativeMessageHostFromNativeApp(const std::string& extension_id,
+                                          const std::string& host_id,
+                                          Profile* profile);
+
+class ScopedAllowNativeAppConnectionForTest {
+ public:
+  explicit ScopedAllowNativeAppConnectionForTest(bool allow);
+  ~ScopedAllowNativeAppConnectionForTest();
+
+  bool allow() const { return allow_; }
+
+ private:
+  const bool allow_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedAllowNativeAppConnectionForTest);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_MESSAGING_NATIVE_MESSAGING_LAUNCH_FROM_NATIVE_H_
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc
new file mode 100644
index 0000000..c80afec
--- /dev/null
+++ b/chrome/browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc
@@ -0,0 +1,230 @@
+// 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/extensions/api/messaging/native_messaging_launch_from_native.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/path_service.h"
+#include "base/test/values_test_util.h"
+#include "chrome/browser/extensions/api/messaging/native_messaging_test_util.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/event_router_factory.h"
+#include "extensions/browser/extension_prefs.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/common/extension_paths.h"
+#include "extensions/common/manifest_constants.h"
+#include "extensions/common/value_builder.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+namespace {
+
+class MockEventRouter : public EventRouter {
+ public:
+  MockEventRouter(content::BrowserContext* browser_context,
+                  ExtensionPrefs* extension_prefs,
+                  const bool* has_listener_result)
+      : EventRouter(browser_context, extension_prefs),
+        has_listener_result_(has_listener_result) {
+    DCHECK(has_listener_result_);
+  }
+
+  bool ExtensionHasEventListener(const std::string& extension_id,
+                                 const std::string& event_name) const override {
+    return *has_listener_result_;
+  }
+
+ private:
+  const bool* has_listener_result_;
+};
+
+std::unique_ptr<KeyedService> BuildMockEventRouter(
+    const bool* has_listener_result,
+    content::BrowserContext* context) {
+  return std::make_unique<MockEventRouter>(
+      context, ExtensionPrefs::Get(context), has_listener_result);
+}
+
+class ExtensionSupportsConnectionFromNativeAppTest : public ::testing::Test {
+ public:
+  void SetUp() override {
+    EventRouterFactory::GetInstance()->SetTestingFactory(
+        &profile_,
+        base::BindRepeating(&BuildMockEventRouter, &has_listener_result_));
+  }
+
+ protected:
+  void RegisterExtension(bool natively_connectable,
+                         bool transient_background_permission,
+                         bool native_messaging_permission) {
+    DictionaryBuilder manifest_builder(
+        static_cast<base::DictionaryValue&&>(base::test::ParseJson(R"(
+            {
+              "version": "1.0.0.0",
+              "manifest_version": 2,
+              "name": "native messaging test",
+              "description": "native messaging test",
+              "background": {
+                "scripts": ["test.js"],
+                "persistent": false
+              }
+            }
+    )")));
+
+    if (natively_connectable) {
+      ListBuilder natively_connectable_hosts;
+      natively_connectable_hosts.Append(
+          ScopedTestNativeMessagingHost::kHostName);
+
+      natively_connectable_hosts.Append(
+          ScopedTestNativeMessagingHost::
+              kSupportsNativeInitiatedConnectionsHostName);
+      manifest_builder.Set(manifest_keys::kNativelyConnectable,
+                           natively_connectable_hosts.Build());
+    }
+
+    ListBuilder permissions;
+    if (transient_background_permission) {
+      permissions.Append("transientBackground");
+    }
+    if (native_messaging_permission) {
+      permissions.Append("nativeMessaging");
+    }
+    manifest_builder.Set(manifest_keys::kPermissions, permissions.Build());
+
+    base::FilePath path;
+    EXPECT_TRUE(base::PathService::Get(DIR_TEST_DATA, &path));
+
+    std::string error;
+    scoped_refptr<Extension> extension(
+        Extension::Create(path, Manifest::INTERNAL, *manifest_builder.Build(),
+                          Extension::NO_FLAGS, &error));
+    ASSERT_TRUE(extension.get()) << error;
+    ExtensionRegistry::Get(&profile_)->AddEnabled(extension);
+    extension_id_ = extension->id();
+  }
+
+  content::TestBrowserThreadBundle thread_bundle_;
+  bool has_listener_result_ = true;
+  TestingProfile profile_;
+  std::string extension_id_;
+};
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, Success) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(true, true, true));
+
+  EXPECT_TRUE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NoOnConnectNative) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(true, true, true));
+  has_listener_result_ = false;
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, OffTheRecordProfile) {
+  auto* off_the_record_profile = profile_.GetOffTheRecordProfile();
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(true, true, true));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      off_the_record_profile, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NotNativelyConnectable) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(false, true, true));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NotTransientBackground) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(true, false, true));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NotNativeMessaging) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(true, true, false));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NativeMessagingOnly) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(false, false, true));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, TransientBackgroundOnly) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(false, true, false));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NativelyConnectableOnly) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(true, false, false));
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, NoPermissions) {
+  ASSERT_NO_FATAL_FAILURE(RegisterExtension(false, false, false));
+  has_listener_result_ = false;
+
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      extension_id_,
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+TEST_F(ExtensionSupportsConnectionFromNativeAppTest, UnknownExtension) {
+  EXPECT_FALSE(ExtensionSupportsConnectionFromNativeApp(
+      "fake extension id",
+      ScopedTestNativeMessagingHost::
+          kSupportsNativeInitiatedConnectionsHostName,
+      &profile_, false));
+}
+
+}  // namespace
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_process_launcher.cc b/chrome/browser/extensions/api/messaging/native_process_launcher.cc
index a00f9a8..fc731d68 100644
--- a/chrome/browser/extensions/api/messaging/native_process_launcher.cc
+++ b/chrome/browser/extensions/api/messaging/native_process_launcher.cc
@@ -46,7 +46,8 @@
  public:
   NativeProcessLauncherImpl(bool allow_user_level_hosts,
                             intptr_t native_window,
-                            const base::FilePath& profile_directory);
+                            const base::FilePath& profile_directory,
+                            bool require_native_initiated_connections);
   ~NativeProcessLauncherImpl() override;
 
   void Launch(const GURL& origin,
@@ -58,7 +59,8 @@
    public:
     Core(bool allow_user_level_hosts,
          intptr_t native_window,
-         const base::FilePath& profile_directory);
+         const base::FilePath& profile_directory,
+         bool require_native_initiated_connections);
     void Launch(const GURL& origin,
                 const std::string& native_host_name,
                 const LaunchedCallback& callback);
@@ -84,10 +86,11 @@
 
     bool detached_;
 
-    bool allow_user_level_hosts_;
+    const bool allow_user_level_hosts_;
 
-    base::FilePath profile_directory_;
+    const base::FilePath profile_directory_;
 
+    const bool require_native_initiated_connections_;
 #if defined(OS_WIN)
     // Handle of the native window corresponding to the extension.
     intptr_t window_handle_;
@@ -103,10 +106,13 @@
 
 NativeProcessLauncherImpl::Core::Core(bool allow_user_level_hosts,
                                       intptr_t window_handle,
-                                      const base::FilePath& profile_directory)
+                                      const base::FilePath& profile_directory,
+                                      bool require_native_initiated_connections)
     : detached_(false),
       allow_user_level_hosts_(allow_user_level_hosts),
-      profile_directory_(profile_directory)
+      profile_directory_(profile_directory),
+      require_native_initiated_connections_(
+          require_native_initiated_connections)
 #if defined(OS_WIN)
       ,
       window_handle_(window_handle)
@@ -176,6 +182,12 @@
     return;
   }
 
+  if (require_native_initiated_connections_ &&
+      !manifest->supports_native_initiated_connections()) {
+    PostErrorResult(callback, RESULT_FORBIDDEN);
+    return;
+  }
+
   base::FilePath host_path = manifest->path();
   if (!host_path.IsAbsolute()) {
     // On Windows host path is allowed to be relative to the location of the
@@ -300,9 +312,12 @@
 NativeProcessLauncherImpl::NativeProcessLauncherImpl(
     bool allow_user_level_hosts,
     intptr_t window_handle,
-    const base::FilePath& profile_directory)
-    : core_(
-          new Core(allow_user_level_hosts, window_handle, profile_directory)) {}
+    const base::FilePath& profile_directory,
+    bool require_native_initiated_connections)
+    : core_(new Core(allow_user_level_hosts,
+                     window_handle,
+                     profile_directory,
+                     require_native_initiated_connections)) {}
 
 NativeProcessLauncherImpl::~NativeProcessLauncherImpl() {
   core_->Detach();
@@ -320,14 +335,16 @@
 std::unique_ptr<NativeProcessLauncher> NativeProcessLauncher::CreateDefault(
     bool allow_user_level_hosts,
     gfx::NativeView native_view,
-    const base::FilePath& profile_directory) {
+    const base::FilePath& profile_directory,
+    bool require_native_initiated_connections) {
   intptr_t window_handle = 0;
 #if defined(OS_WIN)
   window_handle = reinterpret_cast<intptr_t>(
       views::HWNDForNativeView(native_view));
 #endif
   return std::make_unique<NativeProcessLauncherImpl>(
-      allow_user_level_hosts, window_handle, profile_directory);
+      allow_user_level_hosts, window_handle, profile_directory,
+      require_native_initiated_connections);
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/messaging/native_process_launcher.h b/chrome/browser/extensions/api/messaging/native_process_launcher.h
index 68032648..51102bc 100644
--- a/chrome/browser/extensions/api/messaging/native_process_launcher.h
+++ b/chrome/browser/extensions/api/messaging/native_process_launcher.h
@@ -44,11 +44,14 @@
   // window that contains calling page. Can be nullptr, e.g. for background
   // pages. If |profile_directory| is non-empty and the host supports
   // native-initiated connections, additional reconnect args will be passed to
-  // the host.
+  // the host. If |require_native_initiated_connections| is true, the connection
+  // will be allowed only if the native messaging host sets
+  // "supports_native_initiated_connections" to true in its manifest.
   static std::unique_ptr<NativeProcessLauncher> CreateDefault(
       bool allow_user_level_hosts,
       gfx::NativeView native_view,
-      const base::FilePath& profile_directory);
+      const base::FilePath& profile_directory,
+      bool require_native_initiated_connections);
 
   NativeProcessLauncher() {}
   virtual ~NativeProcessLauncher() {}
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index 414c88d..94481067 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -501,11 +501,6 @@
     "expiry_milestone": 80
   },
   {
-    "name": "crostini-app-search",
-    "owners": [ "crostini-ui" ],
-    "expiry_milestone": 76
-  },
-  {
     "name": "crostini-backup",
     "owners": [ "joelhockey", "nverne", "benwells" ],
     "expiry_milestone": 77
diff --git a/chrome/browser/net/proxy_browsertest.cc b/chrome/browser/net/proxy_browsertest.cc
index f3e2f3ba8..944a4fc 100644
--- a/chrome/browser/net/proxy_browsertest.cc
+++ b/chrome/browser/net/proxy_browsertest.cc
@@ -13,6 +13,7 @@
 #include "base/test/bind_test_util.h"
 #include "build/build_config.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/net/proxy_test_utils.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/login/login_handler.h"
@@ -86,53 +87,6 @@
   DISALLOW_COPY_AND_ASSIGN(LoginPromptObserver);
 };
 
-class ProxyBrowserTest : public InProcessBrowserTest {
- public:
-  ProxyBrowserTest()
-      : proxy_server_(net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
-                      base::FilePath()) {
-  }
-
-  void SetUp() override {
-    ASSERT_TRUE(proxy_server_.Start());
-    // Block the GaiaAuthFetcher related requests, they somehow interfere with
-    // the test when the network service is running.
-    url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>(
-        base::BindLambdaForTesting(
-            [&](content::URLLoaderInterceptor::RequestParams* params) -> bool {
-              if (params->url_request.url.host() ==
-                  GaiaUrls::GetInstance()->gaia_url().host()) {
-                return true;
-              }
-              return false;
-            }));
-    InProcessBrowserTest::SetUp();
-  }
-
-  void PostRunTestOnMainThread() override {
-    url_loader_interceptor_.reset();
-    InProcessBrowserTest::PostRunTestOnMainThread();
-  }
-
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitchASCII(switches::kProxyServer,
-                                    proxy_server_.host_port_pair().ToString());
-
-    // TODO(https://crbug.com/901896): Don't rely on proxying localhost (Relied
-    // on by BasicAuthWSConnect)
-    command_line->AppendSwitchASCII(
-        switches::kProxyBypassList,
-        net::ProxyBypassRules::GetRulesToSubtractImplicit());
-  }
-
- protected:
-  net::SpawnedTestServer proxy_server_;
-
- private:
-  std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
-  DISALLOW_COPY_AND_ASSIGN(ProxyBrowserTest);
-};
-
 // Test that the browser can establish a WebSocket connection via a proxy
 // that requires basic authentication. This test also checks the headers
 // arrive at WebSocket server.
diff --git a/chrome/browser/net/proxy_test_utils.cc b/chrome/browser/net/proxy_test_utils.cc
new file mode 100644
index 0000000..e26cec4
--- /dev/null
+++ b/chrome/browser/net/proxy_test_utils.cc
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/proxy_test_utils.h"
+
+#include "base/test/bind_test_util.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "google_apis/gaia/gaia_urls.h"
+
+ProxyBrowserTest::ProxyBrowserTest()
+    : proxy_server_(net::SpawnedTestServer::TYPE_BASIC_AUTH_PROXY,
+                    base::FilePath()) {}
+
+ProxyBrowserTest::~ProxyBrowserTest() {}
+
+void ProxyBrowserTest::SetUp() {
+  ASSERT_TRUE(proxy_server_.Start());
+  // Block the GaiaAuthFetcher related requests, they somehow interfere with
+  // the test when the network service is running.
+  url_loader_interceptor_ = std::make_unique<content::URLLoaderInterceptor>(
+      base::BindLambdaForTesting(
+          [&](content::URLLoaderInterceptor::RequestParams* params) -> bool {
+            if (params->url_request.url.host() ==
+                GaiaUrls::GetInstance()->gaia_url().host()) {
+              return true;
+            }
+            return false;
+          }));
+  InProcessBrowserTest::SetUp();
+}
+
+void ProxyBrowserTest::PostRunTestOnMainThread() {
+  url_loader_interceptor_.reset();
+  InProcessBrowserTest::PostRunTestOnMainThread();
+}
+
+void ProxyBrowserTest::SetUpCommandLine(base::CommandLine* command_line) {
+  command_line->AppendSwitchASCII(switches::kProxyServer,
+                                  proxy_server_.host_port_pair().ToString());
+
+  // TODO(https://crbug.com/901896): Don't rely on proxying localhost (Relied
+  // on by BasicAuthWSConnect)
+  command_line->AppendSwitchASCII(
+      switches::kProxyBypassList,
+      net::ProxyBypassRules::GetRulesToSubtractImplicit());
+}
diff --git a/chrome/browser/net/proxy_test_utils.h b/chrome/browser/net/proxy_test_utils.h
new file mode 100644
index 0000000..22ac7754
--- /dev/null
+++ b/chrome/browser/net/proxy_test_utils.h
@@ -0,0 +1,33 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_NET_PROXY_TEST_UTILS_H_
+#define CHROME_BROWSER_NET_PROXY_TEST_UTILS_H_
+
+#include "chrome/test/base/in_process_browser_test.h"
+#include "net/test/spawned_test_server/spawned_test_server.h"
+
+namespace content {
+class URLLoaderInterceptor;
+}
+
+class ProxyBrowserTest : public InProcessBrowserTest {
+ public:
+  ProxyBrowserTest();
+  ~ProxyBrowserTest() override;
+
+  // InProcessBrowserTest::
+  void SetUp() override;
+  void PostRunTestOnMainThread() override;
+  void SetUpCommandLine(base::CommandLine* command_line) override;
+
+ protected:
+  net::SpawnedTestServer proxy_server_;
+
+ private:
+  std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+  DISALLOW_COPY_AND_ASSIGN(ProxyBrowserTest);
+};
+
+#endif  // CHROME_BROWSER_NET_PROXY_TEST_UTILS_H_
diff --git a/chrome/browser/notifications/scheduler/internal/background_task_coordinator.cc b/chrome/browser/notifications/scheduler/internal/background_task_coordinator.cc
index 95bfa6c..3011246 100644
--- a/chrome/browser/notifications/scheduler/internal/background_task_coordinator.cc
+++ b/chrome/browser/notifications/scheduler/internal/background_task_coordinator.cc
@@ -40,7 +40,7 @@
     int shown_total = 0;
     SchedulerClientType last_shown_type = SchedulerClientType::kUnknown;
     NotificationsShownToday(client_states, &shown_per_type, &shown_total,
-                            &last_shown_type);
+                            &last_shown_type, clock_);
     bool reach_max_today_all_type =
         shown_total >= config_->max_daily_shown_all_type;
     base::Time next_morning = NextMorning();
diff --git a/chrome/browser/notifications/scheduler/internal/scheduler_utils.cc b/chrome/browser/notifications/scheduler/internal/scheduler_utils.cc
index 6cf4b82..2731948 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduler_utils.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduler_utils.cc
@@ -10,6 +10,32 @@
 #include "chrome/browser/notifications/scheduler/internal/scheduler_config.h"
 
 namespace notifications {
+namespace {
+using FirstAndLastIters = std::pair<std::deque<Impression>::const_iterator,
+                                    std::deque<Impression>::const_iterator>;
+
+base::Optional<FirstAndLastIters> FindFirstAndLastNotificationShownToday(
+    const std::deque<Impression>& impressions,
+    const base::Time& now,
+    const base::Time& beginning_of_today) {
+  if (impressions.empty() || impressions.cbegin()->create_time > now ||
+      impressions.crbegin()->create_time < beginning_of_today)
+    return base::nullopt;
+
+  auto last =
+      std::upper_bound(impressions.cbegin(), impressions.cend(), now,
+                       [](const base::Time& lhs, const Impression& rhs) {
+                         return lhs < rhs.create_time;
+                       });
+
+  auto first =
+      std::lower_bound(impressions.cbegin(), last, beginning_of_today,
+                       [](const Impression& lhs, const base::Time& rhs) {
+                         return lhs.create_time < rhs;
+                       });
+  return base::make_optional<FirstAndLastIters>(first, last - 1);
+}
+}  // namespace
 
 bool ToLocalHour(int hour,
                  const base::Time& today,
@@ -32,62 +58,50 @@
   return base::Time::FromLocalExploded(another_day_exploded, out);
 }
 
+int NotificationsShownToday(const ClientState* state, base::Clock* clock) {
+  std::map<SchedulerClientType, const ClientState*> client_states;
+  std::map<SchedulerClientType, int> shown_per_type;
+  client_states.emplace(state->type, state);
+  int shown_total = 0;
+  SchedulerClientType last_shown_type = SchedulerClientType::kUnknown;
+  NotificationsShownToday(client_states, &shown_per_type, &shown_total,
+                          &last_shown_type, clock);
+  return shown_per_type[state->type];
+}
+
 void NotificationsShownToday(
     const std::map<SchedulerClientType, const ClientState*>& client_states,
     std::map<SchedulerClientType, int>* shown_per_type,
     int* shown_total,
-    SchedulerClientType* last_shown_type) {
+    SchedulerClientType* last_shown_type,
+    base::Clock* clock) {
   base::Time last_shown_time;
-  base::Time now(base::Time::Now());
+  base::Time now = clock->Now();
   base::Time beginning_of_today;
   bool success = ToLocalHour(0, now, 0, &beginning_of_today);
   DCHECK(success);
-
   for (const auto& state : client_states) {
-    const auto* client_state = state.second;
-    for (const auto& impression : client_state->impressions) {
-      // Tracks last notification shown to the user.
-      if (impression.create_time > last_shown_time) {
-        last_shown_time = impression.create_time;
-        *last_shown_type = client_state->type;
-      }
+    auto* client_state = state.second;
 
-      // Count notification shown today.
-      if (impression.create_time >= beginning_of_today) {
-        (*shown_per_type)[client_state->type]++;
-        ++(*shown_total);
-      }
+    auto iter_pair = FindFirstAndLastNotificationShownToday(
+        client_state->impressions, now, beginning_of_today);
+
+    if (!iter_pair)
+      continue;
+
+    if (iter_pair->second != client_state->impressions.cend())
+      DLOG(ERROR) << "Wrong format: time stamped to the future! "
+                  << iter_pair->second->create_time;
+
+    if (iter_pair->second->create_time > last_shown_time) {
+      last_shown_time = iter_pair->second->create_time;
+      *last_shown_type = client_state->type;
     }
-  }
-}
 
-int NotificationsShownToday(ClientState* state) {
-  int count = 0;
-  auto impressions = state->impressions;
-  base::Time now(base::Time::Now());
-  base::Time beginning_of_today, beginning_of_tomorrow;
-  bool success = ToLocalHour(0, now, 0, &beginning_of_today);
-  beginning_of_tomorrow += base::TimeDelta::FromDays(1);
-  DCHECK(success);
-  if (impressions.rbegin()->create_time < beginning_of_today) {
-    count = 0;
-  } else if (impressions.begin()->create_time > beginning_of_tomorrow) {
-    count = impressions.size();
-  } else {
-    auto right = lower_bound(impressions.rbegin(), impressions.rend(),
-                             beginning_of_today,
-                             [](const Impression& lhs, const base::Time& rhs) {
-                               return lhs.create_time < rhs;
-                             });
-    auto left = upper_bound(impressions.rbegin(), impressions.rend(),
-                            beginning_of_tomorrow,
-                            [](const base::Time& lhs, const Impression& rhs) {
-                              return lhs < rhs.create_time;
-                            });
-    count = right - left + 1;
+    int count = std::distance(iter_pair->first, iter_pair->second) + 1;
+    (*shown_per_type)[client_state->type] = count;
+    (*shown_total) += count;
   }
-  DCHECK_LE(count, state->current_max_daily_show);
-  return count;
 }
 
 std::unique_ptr<ClientState> CreateNewClientState(
diff --git a/chrome/browser/notifications/scheduler/internal/scheduler_utils.h b/chrome/browser/notifications/scheduler/internal/scheduler_utils.h
index 05e944d..28733ac 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduler_utils.h
+++ b/chrome/browser/notifications/scheduler/internal/scheduler_utils.h
@@ -5,9 +5,12 @@
 #ifndef CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_SCHEDULER_UTILS_H_
 #define CHROME_BROWSER_NOTIFICATIONS_SCHEDULER_INTERNAL_SCHEDULER_UTILS_H_
 
+#include <deque>
 #include <map>
 #include <memory>
 
+#include "base/time/clock.h"
+#include "base/time/default_clock.h"
 #include "base/time/time.h"
 #include "chrome/browser/notifications/scheduler/public/notification_scheduler_types.h"
 
@@ -32,10 +35,13 @@
     const std::map<SchedulerClientType, const ClientState*>& client_states,
     std::map<SchedulerClientType, int>* shown_per_type,
     int* shown_total,
-    SchedulerClientType* last_shown_type);
+    SchedulerClientType* last_shown_type,
+    base::Clock* clock = base::DefaultClock::GetInstance());
 
 // Counts the number of notifications shown today of a given |state|.
-int NotificationsShownToday(ClientState* state);
+int NotificationsShownToday(
+    const ClientState* state,
+    base::Clock* clock = base::DefaultClock::GetInstance());
 
 // Creates client state data for new registered client.
 std::unique_ptr<ClientState> CreateNewClientState(
diff --git a/chrome/browser/notifications/scheduler/internal/scheduler_utils_unittest.cc b/chrome/browser/notifications/scheduler/internal/scheduler_utils_unittest.cc
index 3c28e3a..c476d787 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduler_utils_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduler_utils_unittest.cc
@@ -4,14 +4,60 @@
 
 #include "chrome/browser/notifications/scheduler/internal/scheduler_utils.h"
 
-#include "base/time/time.h"
+#include <algorithm>
+
+#include "base/guid.h"
+#include "base/test/scoped_task_environment.h"
+#include "chrome/browser/notifications/scheduler/internal/impression_types.h"
+#include "chrome/browser/notifications/scheduler/test/fake_clock.h"
+#include "chrome/browser/notifications/scheduler/test/test_utils.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace notifications {
 namespace {
 
+const char kFakeNow[] = "01/01/18 01:23:45 AM";
+
+class SchedulerUtilsTest : public testing::Test {
+ public:
+  SchedulerUtilsTest() {}
+  ~SchedulerUtilsTest() override = default;
+
+  void SetUp() override { config_.initial_daily_shown_per_type = 100; }
+
+  void InitFakeDay() {
+    clock_.SetNow(kFakeNow);
+    ToLocalHour(0, clock_.Now(), 0, &beginning_of_today_);
+  }
+
+  void CreateFakeImpressions(std::vector<base::Time> times,
+                             std::deque<Impression>& impressions) {
+    impressions.clear();
+    for (auto time : times) {
+      impressions.emplace_back(SchedulerClientType::kTest1,
+                               base::GenerateGUID(), time);
+    }
+    std::sort(impressions.begin(), impressions.end(),
+              [](const Impression& lhs, const Impression& rhs) {
+                return lhs.create_time < rhs.create_time;
+              });
+  }
+
+ protected:
+  SchedulerConfig& config() { return config_; }
+  test::FakeClock* clock() { return &clock_; }
+  base::Time& beginning_of_today() { return beginning_of_today_; }
+
+ private:
+  base::test::ScopedTaskEnvironment scoped_task_environment_;
+  test::FakeClock clock_;
+  SchedulerConfig config_;
+  base::Time beginning_of_today_;
+  DISALLOW_COPY_AND_ASSIGN(SchedulerUtilsTest);
+};
+
 // Verifies we can get the correct time stamp at certain hour in yesterday.
-TEST(SchedulerUtilsTest, ToLocalHour) {
+TEST_F(SchedulerUtilsTest, ToLocalHour) {
   base::Time today, another_day, expected;
 
   // Timestamp of another day in the past.
@@ -38,5 +84,44 @@
   EXPECT_EQ(expected, another_day);
 }
 
+TEST_F(SchedulerUtilsTest, NotificationsShownToday) {
+  // Create fake client.
+  auto new_client = CreateNewClientState(SchedulerClientType::kTest1, config());
+  InitFakeDay();
+  base::Time now = clock()->Now();
+  // Test case 1:
+  int count = NotificationsShownToday(new_client.get(), clock());
+  EXPECT_EQ(count, 0);
+
+  // Test case 2:
+  std::vector<base::Time> create_times = {
+      now /*today*/,
+      beginning_of_today() + base::TimeDelta::FromDays(1) /*tomorrow*/,
+      beginning_of_today() - base::TimeDelta::FromSeconds(1) /*yesterday*/,
+      beginning_of_today() + base::TimeDelta::FromSeconds(1) /*today*/,
+      beginning_of_today() /*today*/};
+
+  CreateFakeImpressions(create_times, new_client->impressions);
+  count = NotificationsShownToday(new_client.get(), clock());
+  EXPECT_EQ(count, 3);
+
+  // Test case 3:
+  create_times = {
+      beginning_of_today() - base::TimeDelta::FromSeconds(2), /*yesterday*/
+      beginning_of_today() - base::TimeDelta::FromDays(2),    /*tomorrow*/
+  };
+  CreateFakeImpressions(create_times, new_client->impressions);
+  count = NotificationsShownToday(new_client.get(), clock());
+  EXPECT_EQ(count, 0);
+
+  // Test case 4:
+  create_times = {
+      now /*today*/, now - base::TimeDelta::FromSeconds(1) /*today*/,
+      beginning_of_today() - base::TimeDelta::FromSeconds(1) /*yesterday*/,
+      beginning_of_today() + base::TimeDelta::FromDays(1) /*tomorrow*/};
+  CreateFakeImpressions(create_times, new_client->impressions);
+  count = NotificationsShownToday(new_client.get(), clock());
+  EXPECT_EQ(count, 2);
+}
 }  // namespace
 }  // namespace notifications
diff --git a/chrome/browser/offline_pages/offline_page_utils_unittest.cc b/chrome/browser/offline_pages/offline_page_utils_unittest.cc
index fdc4165..2bd76419 100644
--- a/chrome/browser/offline_pages/offline_page_utils_unittest.cc
+++ b/chrome/browser/offline_pages/offline_page_utils_unittest.cc
@@ -394,7 +394,14 @@
 }
 #endif
 
-TEST_F(OfflinePageUtilsTest, TestGetCachedOfflinePageSizeBetween) {
+#if defined(DISABLE_OFFLINE_PAGES_TOUCHLESS)
+#define MAYBE_TestGetCachedOfflinePageSizeBetween \
+  DISABLED_TestGetCachedOfflinePageSizeBetween
+#else
+#define MAYBE_TestGetCachedOfflinePageSizeBetween \
+  TestGetCachedOfflinePageSizeBetween
+#endif
+TEST_F(OfflinePageUtilsTest, MAYBE_TestGetCachedOfflinePageSizeBetween) {
   // The clock will be at 03:00:00 after adding pages.
   CreateCachedOfflinePages();
 
diff --git a/chrome/browser/resources/app_management/browser_proxy.js b/chrome/browser/resources/app_management/browser_proxy.js
index cb6dc812..a82975b 100644
--- a/chrome/browser/resources/app_management/browser_proxy.js
+++ b/chrome/browser/resources/app_management/browser_proxy.js
@@ -16,7 +16,7 @@
 
       if (useFake) {
         this.handler = new app_management.FakePageHandler(
-            this.callbackRouter.createProxy());
+            this.callbackRouter.$.createProxy());
 
         const permissionOptions = {};
         permissionOptions[PwaPermissionType.CONTENT_SETTINGS_TYPE_GEOLOCATION] =
@@ -98,7 +98,8 @@
         this.handler = new appManagement.mojom.PageHandlerProxy();
         const factory = appManagement.mojom.PageHandlerFactory.getProxy();
         factory.createPageHandler(
-            this.callbackRouter.createProxy(), this.handler.$.createRequest());
+            this.callbackRouter.$.createProxy(),
+            this.handler.$.createRequest());
       }
     }
   }
diff --git a/chrome/browser/resources/bluetooth_internals/adapter_broker.js b/chrome/browser/resources/bluetooth_internals/adapter_broker.js
index 0167d09..918c700 100644
--- a/chrome/browser/resources/bluetooth_internals/adapter_broker.js
+++ b/chrome/browser/resources/bluetooth_internals/adapter_broker.js
@@ -40,7 +40,7 @@
       super();
       this.adapterClient_ = new bluetooth.mojom.AdapterClient(this);
       this.adapter_ = adapter;
-      this.adapter_.setClient(this.adapterClient_.createProxy());
+      this.adapter_.setClient(this.adapterClient_.$.createProxy());
     }
 
     presentChanged(present) {
diff --git a/chrome/browser/resources/chromeos/camera/.eslintrc.js b/chrome/browser/resources/chromeos/camera/.eslintrc.js
index be7e5f6..875b7ac6 100644
--- a/chrome/browser/resources/chromeos/camera/.eslintrc.js
+++ b/chrome/browser/resources/chromeos/camera/.eslintrc.js
@@ -147,6 +147,9 @@
   },
   'extends': 'eslint:recommended',
   'globals': {
+    // Adds BigInt64Array here since current version of eslint does not treat
+    // BigInt64Array as a defined type.
+    'BigInt64Array': 'readable',
     'cros': 'readable',
     'ImageCapture': 'readable',
     'webkitRequestFileSystem': 'readable',
diff --git a/chrome/browser/resources/chromeos/camera/BUILD.gn b/chrome/browser/resources/chromeos/camera/BUILD.gn
index 09e7daa..daaf9c4d 100644
--- a/chrome/browser/resources/chromeos/camera/BUILD.gn
+++ b/chrome/browser/resources/chromeos/camera/BUILD.gn
@@ -55,6 +55,8 @@
     "src/images/browser_button_print.svg",
     "src/images/camera_app_icons_128.png",
     "src/images/camera_app_icons_48.png",
+    "src/images/camera_button_fps_30.svg",
+    "src/images/camera_button_fps_60.svg",
     "src/images/camera_button_grid_off.svg",
     "src/images/camera_button_grid_on.svg",
     "src/images/camera_button_mic_off.svg",
@@ -161,12 +163,12 @@
 
 copy("chrome_camera_app_js_views_camera") {
   sources = [
+    "src/js/views/camera/constraints_preferrer.js",
     "src/js/views/camera/layout.js",
     "src/js/views/camera/modes.js",
     "src/js/views/camera/options.js",
     "src/js/views/camera/preview.js",
     "src/js/views/camera/recordtime.js",
-    "src/js/views/camera/resolution_preference.js",
     "src/js/views/camera/timertick.js",
   ]
 
diff --git a/chrome/browser/resources/chromeos/camera/Makefile b/chrome/browser/resources/chromeos/camera/Makefile
index 1770fc8..0dcb67a 100644
--- a/chrome/browser/resources/chromeos/camera/Makefile
+++ b/chrome/browser/resources/chromeos/camera/Makefile
@@ -77,6 +77,8 @@
 	src/images/browser_button_delete.svg \
 	src/images/browser_button_export.svg \
 	src/images/browser_button_print.svg \
+	src/images/camera_button_fps_30.svg \
+	src/images/camera_button_fps_60.svg \
 	src/images/camera_button_grid_off.svg \
 	src/images/camera_button_grid_on.svg \
 	src/images/camera_button_mic_off.svg \
@@ -133,12 +135,12 @@
 	src/js/util.js \
 	src/js/views/browser.js \
 	src/js/views/camera.js \
+	src/js/views/camera/constraints_preferrer.js \
 	src/js/views/camera/layout.js \
 	src/js/views/camera/modes.js \
 	src/js/views/camera/options.js \
 	src/js/views/camera/preview.js \
 	src/js/views/camera/recordtime.js \
-	src/js/views/camera/resolution_preference.js \
 	src/js/views/camera/timertick.js \
 	src/js/views/dialog.js \
 	src/js/views/gallery_base.js \
diff --git a/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json b/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json
index e4bf00c5..5a9f048 100644
--- a/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json
+++ b/chrome/browser/resources/chromeos/camera/src/_locales/en/messages.json
@@ -91,6 +91,10 @@
     "message": "Grid type",
     "description": "Label for the button of grid-type options."
   },
+  "TOGGLE_60FPS_BUTTON": {
+    "message": "60 FPS",
+    "description": "Label for the checkbox to toggle 60 FPS recording."
+  },
   "TIMER_DURATION_BUTTON": {
     "message": "Timer duration",
     "description": "Label for the button of timer-duration options."
diff --git a/chrome/browser/resources/chromeos/camera/src/css/main.css b/chrome/browser/resources/chromeos/camera/src/css/main.css
index 90a274e..894695b 100644
--- a/chrome/browser/resources/chromeos/camera/src/css/main.css
+++ b/chrome/browser/resources/chromeos/camera/src/css/main.css
@@ -167,7 +167,7 @@
 }
 
 .bottom-stripe {
-  bottom: 56px;
+  bottom: var(--bottom-line);
   position: absolute;
   transform: translateY(50%);
 }
@@ -405,16 +405,17 @@
 }
 
 #options-group {
-  bottom: calc((var(--bottom-line) + (var(--big-icon) / 2)) + 30px);
+  --option-item-vpadding: 12px;
+  bottom: calc(var(--bottom-line) + (var(--big-icon) / 2) + 32px - var(--option-item-vpadding));
   flex-direction: column-reverse;
 }
 
 body:not(.multi-camera) #options-group {
-  bottom: calc((var(--bottom-line) - 18px) - (var(--small-icon) / 2));
+  bottom: calc(var(--bottom-line) - var(--option-item-vpadding) - (var(--small-icon) / 2));
 }
 
 #options-group input {
-  margin: 18px 0;
+  margin: var(--option-item-vpadding) 0;
 }
 
 body._3sec #toggle-timer:checked {
@@ -453,6 +454,19 @@
   background-image: url(../images/camera_button_mic_off.svg);
 }
 
+body:not(.multi-fps) #toggle-fps,
+body:not(.video-mode) #toggle-fps {
+  display: none;
+}
+
+body._30fps #toggle-fps {
+  background-image: url(../images/camera_button_fps_30.svg);
+}
+
+body._60fps #toggle-fps {
+  background-image: url(../images/camera_button_fps_60.svg);
+}
+
 #open-settings {
   background-image: url(../images/camera_button_settings.svg);
 }
@@ -528,7 +542,7 @@
 
 #camera {
   --big-icon: 48px;
-  --bottom-line: 56px;
+  --bottom-line: 40px;
   --mode-item-height: 48px;
   --modes-bottom: calc((var(--bottom-line) + (var(--big-icon) / 2)) + 24px);
   --modes-gradient-padding: 16px;
diff --git a/chrome/browser/resources/chromeos/camera/src/images/camera_button_fps_30.svg b/chrome/browser/resources/chromeos/camera/src/images/camera_button_fps_30.svg
new file mode 100644
index 0000000..6f859d39
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/images/camera_button_fps_30.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-80.8%" y="-80.8%" width="261.5%" height="261.5%" filterUnits="objectBoundingBox" id="a"><feOffset dy="2" in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.125490196 0 0 0 0 0.129411765 0 0 0 0 0.141176471 0 0 0 0.3 0" in="shadowBlurOuter1" result="shadowMatrixOuter1"/><feMerge><feMergeNode in="shadowMatrixOuter1"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g filter="url(#a)" transform="translate(5.5 5.5)" fill="#FFF" fill-rule="nonzero"><path d="M.722 0v1.444h3.611V2.89H1.444v1.444h2.89v1.445H.721v1.444h3.611c.795 0 1.445-.65 1.445-1.444V4.694c0-.599-.123-1.083-.722-1.083.599 0 .722-.484.722-1.083V1.444C5.778.65 5.128 0 4.333 0H.723zm10.111 0c.795 0 1.445.65 1.445 1.444v4.334c0 .794-.65 1.444-1.445 1.444H8.667c-.795 0-1.445-.65-1.445-1.444V1.444C7.222.65 7.872 0 8.667 0h2.166zm0 1.444H8.667v4.334h2.166V1.444zM1.444 13H0V9.389h1.444V13zm2.89 0H2.888V9.389h1.444V13zm2.888 0H5.778V9.389h1.444V13zM13 13H8.667V9.389H13V13z"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/camera/src/images/camera_button_fps_60.svg b/chrome/browser/resources/chromeos/camera/src/images/camera_button_fps_60.svg
new file mode 100644
index 0000000..087c5d6
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/images/camera_button_fps_60.svg
@@ -0,0 +1 @@
+<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-50%" y="-50%" width="200%" height="200%" filterUnits="objectBoundingBox" id="a"><feOffset in="SourceAlpha" result="shadowOffsetOuter1"/><feGaussianBlur stdDeviation="1.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"/><feColorMatrix values="0 0 0 0 0.125490196 0 0 0 0 0.129411765 0 0 0 0 0.141176471 0 0 0 0.3 0" in="shadowBlurOuter1" result="shadowMatrixOuter1"/><feMerge><feMergeNode in="shadowMatrixOuter1"/><feMergeNode in="SourceGraphic"/></feMerge></filter></defs><g filter="url(#a)" transform="translate(5.5 5.5)" fill="#FFF" fill-rule="nonzero"><path d="M10.833 1.444v4.334H8.667V1.444h2.166zm0-1.444H8.667c-.795 0-1.445.65-1.445 1.444v4.334c0 .794.65 1.444 1.445 1.444h2.166c.795 0 1.445-.65 1.445-1.444V1.444C12.278.65 11.628 0 10.833 0zM5.778 1.444V0H2.167C1.372 0 .722.65.722 1.444v4.334c0 .794.65 1.444 1.445 1.444h2.166c.795 0 1.445-.65 1.445-1.444V4.333c0-.794-.65-1.444-1.445-1.444H2.167V1.444h3.61zm-1.445 2.89v1.444H2.167V4.333h2.166zM1.444 13H0V9.389h1.444V13zm2.89 0H2.888V9.389h1.444V13zm2.888 0H5.778V9.389h1.444V13zM13 13H8.667V9.389H13V13z"/></g></svg>
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/camera/src/js/metrics.js b/chrome/browser/resources/chromeos/camera/src/js/metrics.js
index 4f956f2..40dab84 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/metrics.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/metrics.js
@@ -124,6 +124,7 @@
       .dimen(8, condState(['max-wnd']))
       .dimen(9, condState(['tall']))
       .dimen(10, `${width}x${height}`)
+      .dimen(11, condState(['_30fps', '_60fps'], 'video-mode', true))
       .value(length || 0);
 };
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/mojo/imagecapture.js b/chrome/browser/resources/chromeos/camera/src/js/mojo/imagecapture.js
index 2fad22c..03a23395 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/mojo/imagecapture.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/mojo/imagecapture.js
@@ -85,50 +85,33 @@
  * @private
  */
 cca.mojo.getMetadataData_ = function(metadata, tag) {
-  const size4Bytes = 4;
-  const size8Bytes = 8;
-
   for (let i = 0; i < metadata.entryCount; i++) {
     let entry = metadata.entries[i];
     if (entry.tag !== tag) {
       continue;
     }
-    let uint8Array = Uint8Array.from(entry.data);
-    let dataView = new DataView(uint8Array.buffer);
-    let results = [];
-    let mostSignificantInt32;
-    let leastSignificantInt32;
-    for (let j = 0; j < entry.count; j++) {
-      switch (entry.type) {
-        case cros.mojom.EntryType.TYPE_BYTE:
-          results.push(dataView.getUint8(j, true));
-          break;
-        case cros.mojom.EntryType.TYPE_INT32:
-          results.push(dataView.getInt32(j * size4Bytes, true));
-          break;
-        case cros.mojom.EntryType.TYPE_FLOAT:
-          results.push(dataView.getFloat32(j * size4Bytes, true));
-          break;
-        case cros.mojom.EntryType.TYPE_DOUBLE:
-          results.push(dataView.getFloat64(j * size8Bytes, true));
-          break;
-        case cros.mojom.EntryType.TYPE_INT64:
-          // TODO(wtlee): Currently int64 value will fallback to int32 by
-          // picking only the least significant 32 bits. Need to find a way
-          // to better handle int64 values.
-          leastSignificantInt32 = dataView.getInt32(j * size8Bytes, true);
-          mostSignificantInt32 = dataView.getInt32(j * size8Bytes + 4, true);
-          if (mostSignificantInt32 !== 0) {
-            console.warn('Truncate non-zero most significant bytes');
+    const {buffer} = Uint8Array.from(entry.data);
+    switch (entry.type) {
+      case cros.mojom.EntryType.TYPE_BYTE:
+        return Array.from(new Uint8Array(buffer));
+      case cros.mojom.EntryType.TYPE_INT32:
+        return Array.from(new Int32Array(buffer));
+      case cros.mojom.EntryType.TYPE_FLOAT:
+        return Array.from(new Float32Array(buffer));
+      case cros.mojom.EntryType.TYPE_DOUBLE:
+        return Array.from(new Float64Array(buffer));
+      case cros.mojom.EntryType.TYPE_INT64:
+        return Array.from(new BigInt64Array(buffer), (bigIntVal) => {
+          let numVal = Number(bigIntVal);
+          if (!Number.isSafeInteger(numVal)) {
+            console.warn('The int64 value is not a safe integer');
           }
-          results.push(leastSignificantInt32);
-          break;
-        default:
-          // TODO(wtlee): Supports rational type.
-          throw new Error('Unsupported type: ' + entry.type);
-      }
+          return numVal;
+        });
+      default:
+        // TODO(wtlee): Supports rational type.
+        throw new Error('Unsupported type: ' + entry.type);
     }
-    return results;
   }
   return [];
 };
@@ -296,8 +279,8 @@
  * Gets supported fps ranges for specific camera.
  * @param {string} deviceId The renderer-facing device Id of the target camera
  *     which could be retrieved from MediaDeviceInfo.deviceId.
- * @return {Promise<Array<Object>>} Promise of supported fps ranges. Each range
- *     is represented as [min, max].
+ * @return {Promise<Array<[number, number]>>} Promise of supported fps ranges.
+ *     Each range is represented as [min, max].
  */
 cca.mojo.getSupportedFpsRanges = function(deviceId) {
   const numElementPerEntry = 2;
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
index d3a9f96..921699fdb 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
@@ -38,10 +38,10 @@
       resolBroker, this.stop_.bind(this));
 
   /**
-   * @type {cca.views.camera.VideoResolPreferrer}
+   * @type {cca.views.camera.VideoConstraintsPreferrer}
    * @private
    */
-  this.videoResolPreferrer_ = new cca.views.camera.VideoResolPreferrer(
+  this.videoPreferrer_ = new cca.views.camera.VideoConstraintsPreferrer(
       resolBroker, this.stop_.bind(this));
 
   /**
@@ -64,8 +64,7 @@
    * @private
    */
   this.options_ = new cca.views.camera.Options(
-      this.photoResolPreferrer_, this.videoResolPreferrer_,
-      this.stop_.bind(this));
+      this.photoResolPreferrer_, this.videoPreferrer_, this.stop_.bind(this));
 
   /**
    * @type {HTMLElement}
@@ -83,8 +82,8 @@
    * @private
    */
   this.modes_ = new cca.views.camera.Modes(
-      this.photoResolPreferrer_, this.videoResolPreferrer_,
-      this.stop_.bind(this), async (blob, isMotionPicture, filename) => {
+      this.photoResolPreferrer_, this.videoPreferrer_, this.stop_.bind(this),
+      async (blob, isMotionPicture, filename) => {
         if (blob) {
           cca.metrics.log(
               cca.metrics.Type.CAPTURE, this.facingMode_, blob.mins,
@@ -273,7 +272,7 @@
     try {
       if (!deviceId) {
         // Null for requesting default camera on HALv1.
-        throw new Error;
+        throw new Error('HALv1-api');
       }
       const previewRs = (await this.options_.getDeviceResolutions(deviceId))[1];
       var resolCandidates =
@@ -281,7 +280,11 @@
     } catch (e) {
       // Assume the exception here is thrown from error of HALv1 not support
       // resolution query, fallback to use v1 constraints-candidates.
-      resolCandidates = this.modes_.getResolutionCandidatesV1(mode, deviceId);
+      if (e.message == 'HALv1-api') {
+        resolCandidates = this.modes_.getResolutionCandidatesV1(mode, deviceId);
+      } else {
+        throw e;
+      }
     }
     for (const [captureResolution, previewCandidates] of resolCandidates) {
       if (supportedModes && !supportedModes.includes(mode)) {
@@ -289,7 +292,7 @@
       }
       for (const constraints of previewCandidates) {
         try {
-          const stream = await navigator.mediaDevices.getUserMedia(constraints);
+          const stream = await cca.mojo.getUserMedia(deviceId, constraints);
           if (!supportedModes) {
             supportedModes = await this.modes_.getSupportedModes(stream);
             if (!supportedModes.includes(mode)) {
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/constraints_preferrer.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/constraints_preferrer.js
new file mode 100644
index 0000000..0d22ee0e
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/constraints_preferrer.js
@@ -0,0 +1,525 @@
+// 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.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for views.
+ */
+cca.views = cca.views || {};
+
+/**
+ * Namespace for Camera view.
+ */
+cca.views.camera = cca.views.camera || {};
+
+/**
+ * Controller for managing preference of capture settings and generating a list
+ * of stream constraints-candidates sorted by user preference.
+ * @param {cca.ResolutionEventBroker} resolBroker
+ * @param {function()} doReconfigureStream Trigger stream reconfiguration to
+ *     reflect changes in user preferred settings.
+ * @constructor
+ */
+cca.views.camera.ConstraintsPreferrer = function(
+    resolBroker, doReconfigureStream) {
+  /**
+   * @type {cca.ResolutionEventBroker}
+   * @protected
+   */
+  this.resolBroker_ = resolBroker;
+
+  /**
+   * @type {function()}
+   * @protected
+   */
+  this.doReconfigureStream_ = doReconfigureStream;
+
+  /**
+   * Object saving resolution preference that each of its key as device id and
+   * value to be preferred width, height of resolution of that video device.
+   * @type {?Object<string, {width: number, height: number}>}
+   * @protected
+   */
+  this.prefResolution_ = null;
+
+  /**
+   * Device id of currently working video device.
+   * @type {?string}
+   * @protected
+   */
+  this.deviceId_ = null;
+
+  /**
+   * Object of device id as its key and all of available capture resolutions
+   * supported by that video device as its value.
+   * @type {Object<string, ResolList>}
+   * @protected
+   */
+  this.deviceResolutions_ = null;
+};
+
+/**
+ * Updates resolution preference based on newly updated available resolutions.
+ * @param {?[string, ResolList]} frontResolutions Device id and available
+ *     resolutions of front camera.
+ * @param {?[string, ResolList]} backResolutions Device id and available
+ *     resolutions of back camera.
+ * @param {Array<[string, ResolList]>} externalResolutions Device ids and
+ *     available resolutions of all external cameras.
+ * @public
+ */
+cca.views.camera.ConstraintsPreferrer.prototype.updateResolutions = function(
+    frontResolutions, backResolutions, externalResolutions) {};
+
+/**
+ * Updates values according to currently working video device and capture
+ * settings.
+ * @param {string} deviceId Device id of video device to be updated.
+ * @param {MediaStream} stream Currently active preview stream.
+ * @param {number} width Width of resolution to be updated to.
+ * @param {number} height Height of resolution to be updated to.
+ * @public
+ */
+cca.views.camera.ConstraintsPreferrer.prototype.updateValues = function(
+    deviceId, stream, width, height) {};
+
+/**
+ * Gets all available candidates for capturing under this controller and its
+ * corresponding preview constraints for the specified video device. Returned
+ * resolutions and constraints candidates are both sorted in desired trying
+ * order.
+ * @param {string} deviceId Device id of video device.
+ * @param {ResolList} previewResolutions Available preview resolutions for the
+ *     video device.
+ * @return {Array<[[number, number], Array<Object>]>} Result capture resolution
+ *     width, height and constraints-candidates for its preview.
+ * @public
+ */
+cca.views.camera.ConstraintsPreferrer.prototype.getSortedCandidates = function(
+    deviceId, previewResolutions) {
+  return null;
+};
+
+/**
+ * Controller for handling video resolution preference.
+ * @param {cca.ResolutionEventBroker} resolBroker
+ * @param {function()} doReconfigureStream
+ * @constructor
+ */
+cca.views.camera.VideoConstraintsPreferrer = function(
+    resolBroker, doReconfigureStream) {
+  cca.views.camera.ConstraintsPreferrer.call(
+      this, resolBroker, doReconfigureStream);
+
+  /**
+   * Object saving information of supported constant fps. Each of its key as
+   * device id and value as an object mapping from resolution to all constant
+   * fps options supported by that resolution.
+   * @type {Object<string, Object<[number, number], Array<number>>>}
+   * @private
+   */
+  this.constFpsInfo_ = {};
+
+  /**
+   * Object saving fps preference that each of its key as device id and value as
+   * an object mapping from resolution to preferred constant fps for that
+   * resolution.
+   * @type {Object<string, Object<[number, number], number>>}
+   */
+  this.prefFpses_ = {};
+
+  /**
+   * @type {HTMLButtonElement}
+   */
+  this.toggleFps_ = document.querySelector('#toggle-fps');
+
+  /**
+   * Currently in used recording resolution.
+   * @type {?[number, number]}
+   * @protected
+   */
+  this.resolution_ = null;
+
+  // End of properties, seal the object.
+  Object.seal(this);
+
+  // Restore saved preferred recording fps per video device per resolution.
+  chrome.storage.local.get(
+      {deviceVideoFps: {}},
+      (values) => this.prefFpses_ = values.deviceVideoFps);
+
+  // Restore saved preferred video resolution per video device.
+  chrome.storage.local.get(
+      {deviceVideoResolution: {}},
+      (values) => this.prefResolution_ = values.deviceVideoResolution);
+
+  this.resolBroker_.registerChangeVideoPrefResolHandler(
+      (deviceId, width, height) => {
+        this.prefResolution_[deviceId] = {width, height};
+        chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
+        if (cca.state.get('video-mode') && deviceId == this.deviceId_) {
+          this.doReconfigureStream_();
+        } else {
+          this.resolBroker_.notifyVideoPrefResolChange(deviceId, width, height);
+        }
+      });
+
+  this.toggleFps_.addEventListener('click', (event) => {
+    if (!cca.state.get('streaming') || cca.state.get('taking')) {
+      event.preventDefault();
+    }
+  });
+  this.toggleFps_.addEventListener('change', (event) => {
+    this.setPreferredConstFps_(
+        this.deviceId_, ...this.resolution_, this.toggleFps_.checked ? 60 : 30);
+    this.doReconfigureStream_();
+  });
+};
+
+cca.views.camera.VideoConstraintsPreferrer.prototype = {
+  __proto__: cca.views.camera.ConstraintsPreferrer.prototype,
+};
+
+/**
+ * All supported constant fps options of video recording.
+ * @type {Array<number>}
+ * @const
+ */
+cca.views.camera.SUPPORTED_CONSTANT_FPS = [30, 60];
+
+/**
+ * Sets the preferred fps used in video recording for particular video device
+ * and particular resolution.
+ * @param {string} deviceId Device id of video device to be set with.
+ * @param {number} width Resolution width to be set with.
+ * @param {number} height Resolution height to be set with.
+ * @param {number} prefFps Preferred fps to be set with.
+ * @private
+ */
+cca.views.camera.VideoConstraintsPreferrer.prototype.setPreferredConstFps_ =
+    function(deviceId, width, height, prefFps) {
+  if (!cca.views.camera.SUPPORTED_CONSTANT_FPS.includes(prefFps)) {
+    return;
+  }
+  this.toggleFps_.checked = prefFps === 60;
+  cca.views.camera.SUPPORTED_CONSTANT_FPS.forEach(
+      (fps) => cca.state.set(`_${fps}fps`, fps == prefFps));
+  this.prefFpses_[deviceId] = this.prefFpses_[deviceId] || {};
+  this.prefFpses_[deviceId][[width, height]] = prefFps;
+  chrome.storage.local.set({deviceVideoFps: this.prefFpses_});
+};
+
+/**
+ * @param {?[string, ResolList]} frontResolutions
+ * @param {?[string, ResolList]} backResolutions
+ * @param {Array<[string, ResolList]>} externalResolutions
+ * @override
+ * @public
+ */
+cca.views.camera.VideoConstraintsPreferrer.prototype.updateResolutions =
+    function(frontResolutions, backResolutions, externalResolutions) {
+  this.deviceResolutions_ = {};
+
+  const toDeviceIdResols = (deviceId, resolutions) => {
+    this.deviceResolutions_[deviceId] = resolutions;
+    let {width = -1, height = -1} = this.prefResolution_[deviceId] || {};
+    if (!resolutions.find(([w, h]) => w == width && h == height)) {
+      [width, height] = resolutions.reduce(
+          (maxR, R) => (maxR[0] * maxR[1] < R[0] * R[1] ? R : maxR), [0, 0]);
+    }
+    this.prefResolution_[deviceId] = {width, height};
+    return [deviceId, width, height, resolutions];
+  };
+
+  this.resolBroker_.notifyVideoResolChange(
+      frontResolutions && toDeviceIdResols(...frontResolutions),
+      backResolutions && toDeviceIdResols(...backResolutions),
+      externalResolutions.map((ext) => toDeviceIdResols(...ext)));
+  chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
+};
+
+/**
+ * Updates information of supported fps for all video devices.
+ * @param {Array<[string, MaxFpsInfo, FpsRangeInfo]>} deviceFpsInfo Video device
+ *     id and max fps for all of its supported video resolutions and supported
+ *     fps ranges of that device.
+ * @public
+ */
+cca.views.camera.VideoConstraintsPreferrer.prototype.updateFpses = function(
+    deviceFpsInfo) {
+  this.constFpsInfo_ = {};
+  for (const [deviceId, maxFpses, fpsRanges] of deviceFpsInfo) {
+    const constFpses = cca.views.camera.SUPPORTED_CONSTANT_FPS.filter((fps) => {
+      return fpsRanges.find(
+                 ([minFps, maxFps]) => minFps === fps && maxFps === fps) !==
+          undefined;
+    });
+    const fpsInfo = {};
+    for (const r of Object.keys(maxFpses)) {
+      fpsInfo[r] = constFpses.filter((fps) => fps <= maxFpses[r]);
+    }
+    this.constFpsInfo_[deviceId] = fpsInfo;
+  }
+};
+
+/**
+ * @param {string} deviceId
+ * @param {MediaStream} stream
+ * @param {number} width
+ * @param {number} height
+ * @public
+ * @override
+ */
+cca.views.camera.VideoConstraintsPreferrer.prototype.updateValues = function(
+    deviceId, stream, width, height) {
+  this.deviceId_ = deviceId;
+  this.resolution_ = [width, height];
+  this.prefResolution_[deviceId] = {width, height};
+  chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
+  this.resolBroker_.notifyVideoPrefResolChange(deviceId, width, height);
+
+  const fps = stream.getVideoTracks()[0].getSettings().frameRate;
+  const availableFpses = this.constFpsInfo_[deviceId][[width, height]];
+  if (availableFpses.includes(fps)) {
+    this.setPreferredConstFps_(deviceId, width, height, fps);
+  }
+  cca.state.set('multi-fps', availableFpses.length > 1);
+};
+
+/**
+ * @param {string} deviceId
+ * @param {ResolList} previewResolutions
+ * @return {Array<[[number, number], Array<Object>]>}
+ * @public
+ * @override
+ */
+cca.views.camera.VideoConstraintsPreferrer.prototype.getSortedCandidates =
+    function(deviceId, previewResolutions) {
+  // Due to the limitation of MediaStream API, preview stream is used directly
+  // to do video recording.
+  const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
+  const sortPrefResol = ([w, h], [w2, h2]) => {
+    if (w == w2 && h == h2) {
+      return 0;
+    }
+    // Exactly the preferred resolution.
+    if (w == prefR.width && h == prefR.height) {
+      return -1;
+    }
+    if (w2 == prefR.width && h2 == prefR.height) {
+      return 1;
+    }
+    // Aspect ratio same as preferred resolution.
+    if (w * h2 != w2 * h) {
+      if (w * prefR.height == prefR.width * h) {
+        return -1;
+      }
+      if (w2 * prefR.height == prefR.width * h2) {
+        return 1;
+      }
+    }
+    return w2 * h2 - w * h;
+  };
+
+  // Maps specified video resolution width, height to tuple of width, height and
+  // all supported constant fps under that resolution or null fps for not
+  // support multiple constant fps options. The resolution-fps tuple are sorted
+  // by user preference of constant fps.
+  const getFpses = (r) => {
+    let constFpses = [null];
+    if (this.constFpsInfo_[deviceId][r].includes(30) &&
+        this.constFpsInfo_[deviceId][r].includes(60)) {
+      const prefFps =
+          this.prefFpses_[deviceId] && this.prefFpses_[deviceId][r] || 30;
+      constFpses = prefFps == 30 ? [30, 60] : [60, 30];
+    }
+    return constFpses.map((fps) => [...r, fps]);
+  };
+
+  const toConstraints = (width, height, fps) => ({
+    audio: true,
+    video: {
+      deviceId: {exact: deviceId},
+      frameRate: fps ? {exact: fps} : {min: 24},
+      width,
+      height,
+    },
+  });
+
+  return this.deviceResolutions_[deviceId]
+      .sort(sortPrefResol)
+      .flatMap(getFpses)
+      .map(([width, height, fps]) => ([
+             [width, height],
+             [toConstraints(width, height, fps)],
+           ]));
+};
+
+/**
+ * Controller for handling photo resolution preference.
+ * @param {cca.ResolutionEventBroker} resolBroker
+ * @param {function()} doReconfigureStream
+ * @constructor
+ */
+cca.views.camera.PhotoResolPreferrer = function(
+    resolBroker, doReconfigureStream) {
+  cca.views.camera.ConstraintsPreferrer.call(
+      this, resolBroker, doReconfigureStream);
+
+  // End of properties, seal the object.
+  Object.seal(this);
+
+  // Restore saved preferred photo resolution per video device.
+  chrome.storage.local.get(
+      {devicePhotoResolution: {}},
+      (values) => this.prefResolution_ = values.devicePhotoResolution);
+
+  this.resolBroker_.registerChangePhotoPrefResolHandler(
+      (deviceId, width, height) => {
+        this.prefResolution_[deviceId] = {width, height};
+        chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
+        if (!cca.state.get('video-mode') && deviceId == this.deviceId_) {
+          this.doReconfigureStream_();
+        } else {
+          this.resolBroker_.notifyPhotoPrefResolChange(deviceId, width, height);
+        }
+      });
+};
+
+cca.views.camera.PhotoResolPreferrer.prototype = {
+  __proto__: cca.views.camera.ConstraintsPreferrer.prototype,
+};
+
+/**
+ * @param {?[string, ResolList]} frontResolutions
+ * @param {?[string, ResolList]} backResolutions
+ * @param {Array<[string, ResolList]>} externalResolutions
+ * @public
+ * @override
+ */
+cca.views.camera.PhotoResolPreferrer.prototype.updateResolutions = function(
+    frontResolutions, backResolutions, externalResolutions) {
+  this.deviceResolutions_ = {};
+
+  const toDeviceIdResols = (deviceId, resolutions) => {
+    this.deviceResolutions_[deviceId] = resolutions;
+    let {width = -1, height = -1} = this.prefResolution_[deviceId] || {};
+    if (!resolutions.find(([w, h]) => w == width && h == height)) {
+      [width, height] = resolutions.reduce(
+          (maxR, R) => (maxR[0] * maxR[1] < R[0] * R[1] ? R : maxR), [0, 0]);
+    }
+    this.prefResolution_[deviceId] = {width, height};
+    return [deviceId, width, height, resolutions];
+  };
+
+  this.resolBroker_.notifyPhotoResolChange(
+      frontResolutions && toDeviceIdResols(...frontResolutions),
+      backResolutions && toDeviceIdResols(...backResolutions),
+      externalResolutions.map((ext) => toDeviceIdResols(...ext)));
+  chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
+};
+
+/**
+ * @param {string} deviceId
+ * @param {MediaStream} stream
+ * @param {number} width
+ * @param {number} height
+ * @public
+ * @override
+ */
+cca.views.camera.PhotoResolPreferrer.prototype.updateValues = function(
+    deviceId, stream, width, height) {
+  this.deviceId_ = deviceId;
+  this.prefResolution_[deviceId] = {width, height};
+  chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
+  this.resolBroker_.notifyPhotoPrefResolChange(deviceId, width, height);
+};
+
+/**
+ * Finds and pairs photo resolutions and preview resolutions with the same
+ * aspect ratio.
+ * @param {ResolList} captureResolutions Available photo capturing resolutions.
+ * @param {ResolList} previewResolutions Available preview resolutions.
+ * @return {Array<[ResolList, ResolList]>} Each item of returned array is a pair
+ *     of capture and preview resolutions of same aspect ratio.
+ */
+cca.views.camera.PhotoResolPreferrer.prototype.pairCapturePreviewResolutions_ =
+    function(captureResolutions, previewResolutions) {
+  const toAspectRatio = (w, h) => (w / h).toFixed(4);
+  const previewRatios = previewResolutions.reduce((rs, [w, h]) => {
+    const key = toAspectRatio(w, h);
+    rs[key] = rs[key] || [];
+    rs[key].push([w, h]);
+    return rs;
+  }, {});
+  const captureRatios = captureResolutions.reduce((rs, [w, h]) => {
+    const key = toAspectRatio(w, h);
+    if (key in previewRatios) {
+      rs[key] = rs[key] || [];
+      rs[key].push([w, h]);
+    }
+    return rs;
+  }, {});
+  return Object.entries(captureRatios)
+      .map(([aspectRatio,
+             captureRs]) => [captureRs, previewRatios[aspectRatio]]);
+};
+
+/**
+ * @param {string} deviceId
+ * @param {ResolList} previewResolutions
+ * @return {Array<[[number, number], Array<Object>]>}
+ * @public
+ * @override
+ */
+cca.views.camera.PhotoResolPreferrer.prototype.getSortedCandidates = function(
+    deviceId, previewResolutions) {
+  const photoResolutions = this.deviceResolutions_[deviceId];
+  const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
+  return this
+      .pairCapturePreviewResolutions_(photoResolutions, previewResolutions)
+      .map(([captureRs, previewRs]) => {
+        if (captureRs.some(([w, h]) => w == prefR.width && h == prefR.height)) {
+          var captureR = [prefR.width, prefR.height];
+        } else {
+          var captureR = captureRs.reduce(
+              (captureR, r) => (r[0] > captureR[0] ? r : captureR), [0, -1]);
+        }
+
+        const candidates = previewRs.sort(([w, h], [w2, h2]) => w2 - w)
+                               .map(([width, height]) => ({
+                                      audio: false,
+                                      video: {
+                                        deviceId: {exact: deviceId},
+                                        frameRate: {min: 24},
+                                        width,
+                                        height,
+                                      },
+                                    }));
+        // Format of map result:
+        // [
+        //   [[CaptureW 1, CaptureH 1], [CaptureW 2, CaptureH 2], ...],
+        //   [PreviewConstraint 1, PreviewConstraint 2, ...]
+        // ]
+        return [captureR, candidates];
+      })
+      .sort(([[w, h]], [[w2, h2]]) => {
+        if (w == w2 && h == h2) {
+          return 0;
+        }
+        if (w == prefR.width && h == prefR.height) {
+          return -1;
+        }
+        if (w2 == prefR.width && h2 == prefR.height) {
+          return 1;
+        }
+        return w2 * h2 - w * h;
+      });
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js
index 86601d38..5d922ef9 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/modes.js
@@ -22,14 +22,14 @@
 /**
  * Mode controller managing capture sequence of different camera mode.
  * @param {cca.views.camera.PhotoResolPreferrer} photoResolPreferrer
- * @param {cca.views.camera.VideoResolPreferrer} videoResolPreferrer
+ * @param {cca.views.camera.VideoConstraintsPreferrer} videoPreferrer
  * @param {function()} doSwitchMode Callback to trigger mode switching.
  * @param {function(?Blob, boolean, string): Promise} doSavePicture Callback for
  *     saving picture.
  * @constructor
  */
 cca.views.camera.Modes = function(
-    photoResolPreferrer, videoResolPreferrer, doSwitchMode, doSavePicture) {
+    photoResolPreferrer, videoPreferrer, doSwitchMode, doSavePicture) {
   /**
    * @type {function()}
    * @private
@@ -78,7 +78,7 @@
       captureFactory: () =>
           new cca.views.camera.Video(this.stream_, this.doSavePicture_),
       isSupported: async () => true,
-      resolutionConfig: videoResolPreferrer,
+      resolutionConfig: videoPreferrer,
       v1Config: cca.views.camera.Modes.getV1Constraints.bind(this, true),
       nextMode: 'photo-mode',
     },
@@ -227,7 +227,7 @@
  * @param {string} mode
  * @param {string} deviceId
  * @param {ResolList} previewResolutions
- * @return {Array<[?[number, number], Array<Object>]>} Result capture resolution
+ * @return {Array<[[number, number], Array<Object>]>} Result capture resolution
  *     width, height and constraints-candidates for its preview.
  */
 cca.views.camera.Modes.prototype.getResolutionCandidates = function(
@@ -298,8 +298,8 @@
   this.captureResolution_ = captureResolution;
   this.current = this.allModes_[mode].captureFactory();
   if (deviceId && this.captureResolution_) {
-    this.allModes_[mode].resolutionConfig.updateCurrentResolution(
-        deviceId, ...this.captureResolution_);
+    this.allModes_[mode].resolutionConfig.updateValues(
+        deviceId, stream, ...this.captureResolution_);
   }
 };
 
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js
index 144c2a9..d5d45826 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js
@@ -20,14 +20,81 @@
 cca.views.camera = cca.views.camera || {};
 
 /**
+ * Map of all available resolution to its maximal supported capture fps.
+ * @typedef {Object<[number, number], number>} MaxFpsInfo
+ */
+
+/**
+ * List of supported capture fps ranges.
+ * @typedef {Array<[number, number]>} FpsRangeInfo
+ */
+
+/**
+ * Video device information queried from HALv3 mojo private API.
+ * @param {MediaDeviceInfo} deviceInfo Information of the video device.
+ * @param {cros.mojom.CameraFacing} facing Camera facing of the video device.
+ * @param {ResolList} photoResols Supported available photo resolutions of the
+ *     video device.
+ * @param {Array<[number, number, number]>} videoResolFpses Supported available
+ *     video resolutions and maximal capture fps of the video device.
+ * @param {FpsRangeInfo} fpsRanges Supported fps ranges of the video device.
+ */
+cca.views.camera.Camera3DeviceInfo = function(
+    deviceInfo, facing, photoResols, videoResolFpses, fpsRanges) {
+  /**
+   * @type {string}
+   * @public
+   */
+  this.deviceId = deviceInfo.deviceId;
+
+  /**
+   * @type {cros.mojom.CameraFacing}
+   * @public
+   */
+  this.facing = facing;
+
+  /**
+   * @type {ResolList}
+   * @public
+   */
+  this.photoResols = photoResols;
+
+  /**
+   * @type {ResolList}
+   * @public
+   */
+  this.videoResols = [];
+
+  /**
+   * @type {MaxFpsInfo}
+   * @public
+   */
+  this.videoMaxFps = {};
+
+  /**
+   * @type {FpsRangeInfo}
+   * @public
+   */
+  this.fpsRanges = fpsRanges;
+
+  // End of properties, seal the object.
+  Object.seal(this);
+
+  videoResolFpses.filter(([, , fps]) => fps >= 24).forEach(([w, h, fps]) => {
+    this.videoResols.push([w, h]);
+    this.videoMaxFps[[w, h]] = fps;
+  });
+};
+
+/**
  * Creates a controller for the options of Camera view.
  * @param {cca.views.camera.PhotoResolPreferrer} photoResolPreferrer
- * @param {cca.views.camera.VideoResolPreferrer} videoResolPreferrer
+ * @param {cca.views.camera.VideoConstraintsPreferrer} videoPreferrer
  * @param {function()} doSwitchDevice Callback to trigger device switching.
  * @constructor
  */
 cca.views.camera.Options = function(
-    photoResolPreferrer, videoResolPreferrer, doSwitchDevice) {
+    photoResolPreferrer, videoPreferrer, doSwitchDevice) {
   /**
    * @type {cca.views.camera.PhotoResolPreferrer}
    * @private
@@ -35,10 +102,10 @@
   this.photoResolPreferrer_ = photoResolPreferrer;
 
   /**
-   * @type {cca.views.camera.VideoResolPreferrer}
+   * @type {cca.views.camera.VideoConstraintsPreferrer}
    * @private
    */
-  this.videoResolPreferrer_ = videoResolPreferrer;
+  this.videoPreferrer_ = videoPreferrer;
 
   /**
    * @type {function()}
@@ -80,16 +147,12 @@
   this.videoDevices_ = null;
 
   /**
-   * MediaDeviceInfo and additional information queried from private mojo api of
-   * all available video device. The additional fields in the array entry
-   * represent camera facing, supported photo resolutions, supported video
-   * resolutions. On HALv1 device, the promise will throw exception for its
-   * incapability of building mojo api connection.
-   * @type {Promise<!Array<[MediaDeviceInfo, cros.mojom.CameraFacing, ResolList,
-   *     ResolList]>>}
+   * Promise for querying Camera3DeviceInfo of all available video devices from
+   * mojo private API.
+   * @type {Promise<!Array<Camera3DeviceInfo>>}
    * @private
    */
-  this.devicePrivateInfo_ = null;
+  this.devicesPrivateInfo_ = null;
 
   /**
    * Whether the current device is HALv1 and lacks facing configuration.
@@ -287,36 +350,39 @@
     this.refreshingVideoDeviceIds_ = false;
   });
 
-  this.devicePrivateInfo_ =
-      this.videoDevices_
-          .then((devices) => {
-            return Promise.all(devices.map((d) => Promise.all([
-              d,
-              cca.mojo.getCameraFacing(d.deviceId),
-              cca.mojo.getPhotoResolutions(d.deviceId),
-              cca.mojo.getVideoConfigs(d.deviceId)
-                  .then(
-                      (v) => v.filter(([, , fps]) => fps >= 24)
-                                 .map(([w, h]) => [w, h])),
-            ])));
-          })
-          .catch((e) => {
-            cca.state.set('no-resolution-settings', true);
-            throw e;
-          });
+  this.devicesPrivateInfo_ = (async () => {
+    const devices = await this.videoDevices_;
+    try {
+      var privateInfos = await Promise.all(devices.map((d) => Promise.all([
+        d,
+        cca.mojo.getCameraFacing(d.deviceId),
+        cca.mojo.getPhotoResolutions(d.deviceId),
+        cca.mojo.getVideoConfigs(d.deviceId),
+        cca.mojo.getSupportedFpsRanges(d.deviceId),
+      ])));
+    } catch (e) {
+      cca.state.set('no-resolution-settings', true);
+      throw new Error('HALv1-api');
+    }
+    return privateInfos.map(
+        (info) => new cca.views.camera.Camera3DeviceInfo(...info));
+  })();
 
   (async () => {
     try {
-      var devicePrivateInfo = await this.devicePrivateInfo_;
+      var devicesPrivateInfo = await this.devicesPrivateInfo_;
     } catch (e) {
-      return;
+      if (e.message == 'HALv1-api') {
+        return;
+      }
+      throw e;
     }
     let frontSetting = null;
     let backSetting = null;
     let externalSettings = [];
-    devicePrivateInfo.forEach(([{deviceId}, facing, photoRs, videoRs]) => {
-      const setting = [deviceId, photoRs, videoRs];
-      switch (facing) {
+    devicesPrivateInfo.forEach((info) => {
+      const setting = [info.deviceId, info.photoResols, info.videoResols];
+      switch (info.facing) {
         case cros.mojom.CameraFacing.CAMERA_FACING_FRONT:
           frontSetting = setting;
           break;
@@ -327,17 +393,20 @@
           externalSettings.push(setting);
           break;
         default:
-          console.error(`Ignore device of unknown facing: ${facing}`);
+          console.error(`Ignore device of unknown facing: ${info.facing}`);
       }
     });
     this.photoResolPreferrer_.updateResolutions(
         frontSetting && [frontSetting[0], frontSetting[1]],
         backSetting && [backSetting[0], backSetting[1]],
         externalSettings.map(([deviceId, photoRs]) => [deviceId, photoRs]));
-    this.videoResolPreferrer_.updateResolutions(
+    this.videoPreferrer_.updateResolutions(
         frontSetting && [frontSetting[0], frontSetting[2]],
         backSetting && [backSetting[0], backSetting[2]],
         externalSettings.map(([deviceId, , videoRs]) => [deviceId, videoRs]));
+
+    this.videoPreferrer_.updateFpses(devicesPrivateInfo.map(
+        (info) => [info.deviceId, info.videoMaxFps, info.fpsRanges]));
   })();
 };
 
@@ -354,14 +423,18 @@
     throw new Error('Device list empty.');
   }
   try {
-    var facings = (await this.devicePrivateInfo_)
+    var facings = (await this.devicesPrivateInfo_)
                       .reduce(
-                          (facings, [d, facing]) =>
-                              Object.assign(facings, {[d.deviceId]: facing}),
+                          (facings, info) => Object.assign(
+                              facings, {[info.deviceId]: info.facing}),
                           {});
     this.isV1NoFacingConfig_ = false;
   } catch (e) {
-    facings = null;
+    if (e.message == 'HALv1-api') {
+      facings = null;
+    } else {
+      throw e;
+    }
   }
   // Put the selected video device id first.
   var sorted = devices.map((device) => device.deviceId).sort((a, b) => {
@@ -395,8 +468,7 @@
 cca.views.camera.Options.prototype.getDeviceResolutions =
     async function(deviceId) {
   // HALv1 device will thrown from here.
-  const devicePrivateInfo = await this.devicePrivateInfo_;
-  const [, , photoRs, videoRs] =
-      devicePrivateInfo.find(([d]) => d.deviceId === deviceId);
-  return [photoRs, videoRs];
+  const info = (await this.devicesPrivateInfo_)
+                   .find((info) => info.deviceId === deviceId);
+  return [info.photoResols, info.videoResols];
 };
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/resolution_preference.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/resolution_preference.js
deleted file mode 100644
index 50283ed..0000000
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/resolution_preference.js
+++ /dev/null
@@ -1,382 +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.
-
-'use strict';
-
-/**
- * Namespace for the Camera app.
- */
-var cca = cca || {};
-
-/**
- * Namespace for views.
- */
-cca.views = cca.views || {};
-
-/**
- * Namespace for Camera view.
- */
-cca.views.camera = cca.views.camera || {};
-
-/**
- * Controller for handling resolution preference.
- * @param {cca.ResolutionEventBroker} resolBroker
- * @param {function()} doSwitchResolution
- * @constructor
- */
-cca.views.camera.ResolutionPreferrer = function(
-    resolBroker, doSwitchResolution) {
-  /**
-   * @type {cca.ResolutionEventBroker}
-   * @protected
-   */
-  this.resolBroker_ = resolBroker;
-
-  /**
-   * @type {function()}
-   * @protected
-   */
-  this.doSwitchResolution_ = doSwitchResolution;
-
-  /**
-   * Object saving resolution preference that each of its key as device id and
-   * value to be preferred width, height of resolution of that video device.
-   * @type {?Object<string, {width: number, height: number}>}
-   * @protected
-   */
-  this.prefResolution_ = null;
-
-  /**
-   * Device id of currently working video device.
-   * @type {?string}
-   * @protected
-   */
-  this.deviceId_ = null;
-
-  /**
-   * Object of device id as its key and all of available capture resolutions
-   * supported by that video device as its value.
-   * @type {Object<string, ResolList>}
-   * @protected
-   */
-  this.deviceResolutions_ = null;
-
-  // End of properties, seal the object.
-  Object.seal(this);
-};
-
-/**
- * Updates resolution preference based on newly updated available resolutions.
- * @param {?[string, ResolList]} frontResolutions Device id and available
- *     resolutions of front camera.
- * @param {?[string, ResolList]} backResolutions Device id and available
- *     resolutions of back camera.
- * @param {Array<[string, ResolList]>} externalResolutions Device ids and
- *     available resolutions of all external cameras.
- */
-cca.views.camera.ResolutionPreferrer.prototype.updateResolutions = function(
-    frontResolutions, backResolutions, externalResolutions) {};
-
-/**
- * Updates preferred resolution of currently working video device.
- * @param {string} deviceId Device id of video device to be updated.
- * @param {number} width Width of resolution to be updated to.
- * @param {number} height Height of resolution to be updated to.
- */
-cca.views.camera.ResolutionPreferrer.prototype.updateCurrentResolution =
-    function(deviceId, width, height) {};
-
-/**
- * Gets all available resolutions candidates for capturing under this controller
- * and its corresponding preview constraints for the specified video device.
- * Returned resolutions and constraints candidates are both sorted in desired
- * trying order.
- * @param {string} deviceId Device id of video device.
- * @param {ResolList} previewResolutions Available preview resolutions for the
- *     video device.
- * @return {Array<[number, number, Array<Object>]>} Result capture resolution
- *     width, height and constraints-candidates for its preview.
- */
-cca.views.camera.ResolutionPreferrer.prototype.getSortedCandidates = function(
-    deviceId, previewResolutions) {
-  return null;
-};
-
-/**
- * Controller for handling video resolution preference.
- * @param {cca.ResolutionEventBroker} resolBroker
- * @param {function()} doSwitchResolution
- * @constructor
- */
-cca.views.camera.VideoResolPreferrer = function(
-    resolBroker, doSwitchResolution) {
-  cca.views.camera.ResolutionPreferrer.call(
-      this, resolBroker, doSwitchResolution);
-
-  // Restore saved preferred video resolution per video device.
-  chrome.storage.local.get(
-      {deviceVideoResolution: {}},
-      (values) => this.prefResolution_ = values.deviceVideoResolution);
-
-  this.resolBroker_.registerChangeVideoPrefResolHandler(
-      (deviceId, width, height) => {
-        this.prefResolution_[deviceId] = {width, height};
-        chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
-        if (cca.state.get('video-mode') && deviceId == this.deviceId_) {
-          this.doSwitchResolution_();
-        } else {
-          this.resolBroker_.notifyVideoPrefResolChange(deviceId, width, height);
-        }
-      });
-};
-
-cca.views.camera.VideoResolPreferrer.prototype = {
-  __proto__: cca.views.camera.ResolutionPreferrer.prototype,
-};
-
-/**
- * @param {?[string, ResolList]} frontResolutions
- * @param {?[string, ResolList]} backResolutions
- * @param {Array<[string, ResolList]>} externalResolutions
- * @override
- */
-cca.views.camera.VideoResolPreferrer.prototype.updateResolutions = function(
-    frontResolutions, backResolutions, externalResolutions) {
-  this.deviceResolutions_ = {};
-
-  const toDeviceIdResols = (deviceId, resolutions) => {
-    this.deviceResolutions_[deviceId] = resolutions;
-    let {width = -1, height = -1} = this.prefResolution_[deviceId] || {};
-    if (!resolutions.find(([w, h]) => w == width && h == height)) {
-      [width, height] = resolutions.reduce(
-          (maxR, R) => (maxR[0] * maxR[1] < R[0] * R[1] ? R : maxR), [0, 0]);
-    }
-    this.prefResolution_[deviceId] = {width, height};
-    return [deviceId, width, height, resolutions];
-  };
-
-  this.resolBroker_.notifyVideoResolChange(
-      frontResolutions && toDeviceIdResols(...frontResolutions),
-      backResolutions && toDeviceIdResols(...backResolutions),
-      externalResolutions.map((ext) => toDeviceIdResols(...ext)));
-  chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
-};
-
-/**
- * @param {string} deviceId
- * @param {number} width
- * @param {number} height
- * @override
- */
-cca.views.camera.VideoResolPreferrer.prototype.updateCurrentResolution =
-    function(deviceId, width, height) {
-  this.deviceId_ = deviceId;
-  this.prefResolution_[deviceId] = {width, height};
-  chrome.storage.local.set({deviceVideoResolution: this.prefResolution_});
-  this.resolBroker_.notifyVideoPrefResolChange(deviceId, width, height);
-};
-
-/**
- * @param {string} deviceId
- * @return {Array<[number, number, Array<Object>]>}
- * @override
- */
-cca.views.camera.VideoResolPreferrer.prototype.getSortedCandidates = function(
-    deviceId, previewResolutions) {
-  // Due to the limitation of MediaStream API, preview stream is used directly
-  // to do video recording.
-  const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
-  const preferredOrder = ([w, h], [w2, h2]) => {
-    if (w == w2 && h == h2) {
-      return 0;
-    }
-    // Exactly the preferred resolution.
-    if (w == prefR.width && h == prefR.height) {
-      return -1;
-    }
-    if (w2 == prefR.width && h2 == prefR.height) {
-      return 1;
-    }
-    // Aspect ratio same as preferred resolution.
-    if (w * h2 != w2 * h) {
-      if (w * prefR.height == prefR.width * h) {
-        return -1;
-      }
-      if (w2 * prefR.height == prefR.width * h2) {
-        return 1;
-      }
-    }
-    return w2 * h2 - w * h;
-  };
-  const toConstraints = (width, height) => ({
-    audio: true,
-    video: {
-      deviceId: {exact: deviceId},
-      frameRate: {min: 24},
-      width,
-      height,
-    },
-  });
-  const videoResolutions = this.deviceResolutions_[deviceId];
-  return videoResolutions.sort(preferredOrder).map(([width, height]) => ([
-                                                     [width, height],
-                                                     [toConstraints(
-                                                         width, height)],
-                                                   ]));
-};
-
-/**
- * Controller for handling photo resolution preference.
- * @param {cca.ResolutionEventBroker} resolBroker
- * @param {function()} doSwitchResolution
- * @constructor
- */
-cca.views.camera.PhotoResolPreferrer = function(
-    resolBroker, doSwitchResolution) {
-  cca.views.camera.ResolutionPreferrer.call(
-      this, resolBroker, doSwitchResolution);
-
-  // Restore saved preferred photo resolution per video device.
-  chrome.storage.local.get(
-      {devicePhotoResolution: {}},
-      (values) => this.prefResolution_ = values.devicePhotoResolution);
-
-  this.resolBroker_.registerChangePhotoPrefResolHandler(
-      (deviceId, width, height) => {
-        this.prefResolution_[deviceId] = {width, height};
-        chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
-        if (!cca.state.get('video-mode') && deviceId == this.deviceId_) {
-          this.doSwitchResolution_();
-        } else {
-          this.resolBroker_.notifyPhotoPrefResolChange(deviceId, width, height);
-        }
-      });
-};
-
-cca.views.camera.PhotoResolPreferrer.prototype = {
-  __proto__: cca.views.camera.ResolutionPreferrer.prototype,
-};
-
-/**
- * @param {?[string, ResolList]} frontResolutions
- * @param {?[string, ResolList]} backResolutions
- * @param {Array<[string, ResolList]>} externalResolutions
- * @override
- */
-cca.views.camera.PhotoResolPreferrer.prototype.updateResolutions = function(
-    frontResolutions, backResolutions, externalResolutions) {
-  this.deviceResolutions_ = {};
-
-  const toDeviceIdResols = (deviceId, resolutions) => {
-    this.deviceResolutions_[deviceId] = resolutions;
-    let {width = -1, height = -1} = this.prefResolution_[deviceId] || {};
-    if (!resolutions.find(([w, h]) => w == width && h == height)) {
-      [width, height] = resolutions.reduce(
-          (maxR, R) => (maxR[0] * maxR[1] < R[0] * R[1] ? R : maxR), [0, 0]);
-    }
-    this.prefResolution_[deviceId] = {width, height};
-    return [deviceId, width, height, resolutions];
-  };
-
-  this.resolBroker_.notifyPhotoResolChange(
-      frontResolutions && toDeviceIdResols(...frontResolutions),
-      backResolutions && toDeviceIdResols(...backResolutions),
-      externalResolutions.map((ext) => toDeviceIdResols(...ext)));
-  chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
-};
-
-/**
- * @param {string} deviceId
- * @param {number} width
- * @param {number} height
- * @override
- */
-cca.views.camera.PhotoResolPreferrer.prototype.updateCurrentResolution =
-    function(deviceId, width, height) {
-  this.deviceId_ = deviceId;
-  this.prefResolution_[deviceId] = {width, height};
-  chrome.storage.local.set({devicePhotoResolution: this.prefResolution_});
-  this.resolBroker_.notifyPhotoPrefResolChange(deviceId, width, height);
-};
-
-/**
- * Finds and pairs photo resolutions and preview resolutions with the same
- * aspect ratio.
- * @param {ResolList} captureResolutions Available photo capturing resolutions.
- * @param {ResolList} previewResolutions Available preview resolutions.
- * @return {Array<[ResolList, ResolList]>} Each item of returned array is a pair
- *     of capture and preview resolutions of same aspect ratio.
- */
-cca.views.camera.PhotoResolPreferrer.prototype.pairCapturePreviewResolutions_ =
-    function(captureResolutions, previewResolutions) {
-  const toAspectRatio = (w, h) => (w / h).toFixed(4);
-  const previewRatios = previewResolutions.reduce((rs, [w, h]) => {
-    const key = toAspectRatio(w, h);
-    rs[key] = rs[key] || [];
-    rs[key].push([w, h]);
-    return rs;
-  }, {});
-  const captureRatios = captureResolutions.reduce((rs, [w, h]) => {
-    const key = toAspectRatio(w, h);
-    if (key in previewRatios) {
-      rs[key] = rs[key] || [];
-      rs[key].push([w, h]);
-    }
-    return rs;
-  }, {});
-  return Object.entries(captureRatios)
-      .map(([aspectRatio,
-             captureRs]) => [captureRs, previewRatios[aspectRatio]]);
-};
-
-/**
- * @param {string} deviceId
- * @param {ResolList} previewResolutions
- * @return {Array<[number, number, Array<Object>]>}
- * @override
- */
-cca.views.camera.PhotoResolPreferrer.prototype.getSortedCandidates = function(
-    deviceId, previewResolutions) {
-  const photoResolutions = this.deviceResolutions_[deviceId];
-  const prefR = this.prefResolution_[deviceId] || {width: 0, height: -1};
-  return this
-      .pairCapturePreviewResolutions_(photoResolutions, previewResolutions)
-      .map(([captureRs, previewRs]) => {
-        if (captureRs.some(([w, h]) => w == prefR.width && h == prefR.height)) {
-          var captureR = [prefR.width, prefR.height];
-        } else {
-          var captureR = captureRs.reduce(
-              (captureR, r) => (r[0] > captureR[0] ? r : captureR), [0, -1]);
-        }
-
-        const candidates = previewRs.sort(([w, h], [w2, h2]) => w2 - w)
-                               .map(([width, height]) => ({
-                                      audio: false,
-                                      video: {
-                                        deviceId: {exact: deviceId},
-                                        frameRate: {min: 24},
-                                        width,
-                                        height,
-                                      },
-                                    }));
-        // Format of map result:
-        // [
-        //   [[CaptureW 1, CaptureH 1], [CaptureW 2, CaptureH 2], ...],
-        //   [PreviewConstraint 1, PreviewConstraint 2, ...]
-        // ]
-        return [captureR, candidates];
-      })
-      .sort(([[w, h]], [[w2, h2]]) => {
-        if (w == w2 && h == h2) {
-          return 0;
-        }
-        if (w == prefR.width && h == prefR.height) {
-          return -1;
-        }
-        if (w2 == prefR.width && h2 == prefR.height) {
-          return 1;
-        }
-        return w2 * h2 - w * h;
-      });
-};
diff --git a/chrome/browser/resources/chromeos/camera/src/strings/camera_strings.grd b/chrome/browser/resources/chromeos/camera/src/strings/camera_strings.grd
index 2bd7b40..834cc21 100644
--- a/chrome/browser/resources/chromeos/camera/src/strings/camera_strings.grd
+++ b/chrome/browser/resources/chromeos/camera/src/strings/camera_strings.grd
@@ -285,6 +285,9 @@
       <message desc="Label for the button of grid-type options." name="IDS_GRID_TYPE_BUTTON">
         Grid type
       </message>
+      <message desc="Label for the checkbox to toggle 60 FPS recording." name="IDS_TOGGLE_60FPS_BUTTON">
+        60 FPS
+      </message>
       <message desc="Label for the back button." name="IDS_BACK_BUTTON">
         Go back
       </message>
diff --git a/chrome/browser/resources/chromeos/camera/src/strings/camera_strings_grd/IDS_TOGGLE_60FPS_BUTTON.png.sha1 b/chrome/browser/resources/chromeos/camera/src/strings/camera_strings_grd/IDS_TOGGLE_60FPS_BUTTON.png.sha1
new file mode 100644
index 0000000..e9018f8
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/strings/camera_strings_grd/IDS_TOGGLE_60FPS_BUTTON.png.sha1
@@ -0,0 +1 @@
+3a593c1ca0c47bd934e16bdc366adbe125ab3e6b
\ No newline at end of file
diff --git a/chrome/browser/resources/chromeos/camera/src/views/main.html b/chrome/browser/resources/chromeos/camera/src/views/main.html
index 87ec1d6..3e2f55f 100644
--- a/chrome/browser/resources/chromeos/camera/src/views/main.html
+++ b/chrome/browser/resources/chromeos/camera/src/views/main.html
@@ -36,7 +36,7 @@
     <script src="../js/views/camera/options.js"></script>
     <script src="../js/views/camera/preview.js"></script>
     <script src="../js/views/camera/recordtime.js"></script>
-    <script src="../js/views/camera/resolution_preference.js"></script>
+    <script src="../js/views/camera/constraints_preferrer.js"></script>
     <script src="../js/views/camera/timertick.js"></script>
     <script src="../js/views/camera/modes.js"></script>
     <script src="../js/views/dialog.js"></script>
@@ -131,6 +131,8 @@
                data-key="toggleGrid">
         <input type="checkbox" id="toggle-mirror" tabindex="0"
                i18n-label="toggle_mirror_button" data-state="mirror" checked>
+        <input type="checkbox" id="toggle-fps" tabindex="0"
+               i18n-label="toggle_60fps_button">
       </div>
       <div class="top-stripe left-stripe buttons">
         <button id="open-settings" tabindex="0"
diff --git a/chrome/browser/resources/discards/graph_tab.js b/chrome/browser/resources/discards/graph_tab.js
index 0231a772..e300a07 100644
--- a/chrome/browser/resources/discards/graph_tab.js
+++ b/chrome/browser/resources/discards/graph_tab.js
@@ -99,10 +99,12 @@
   onWebViewReady_: function() {
     this.changeListener_ =
         new graph_tab.WebUIGraphChangeStreamImpl(this.$.webView.contentWindow);
-    const client = new performanceManager.mojom
-                       .WebUIGraphChangeStream(this.changeListener_)
-                       .createProxy();
+    this.client_ = new performanceManager.mojom.WebUIGraphChangeStream(
+        this.changeListener_);
+    // Save helper to work around closure compiler bug: https://crbug.com/969212
+    const helper = this.client_.$;
+
     // Subscribe for graph updates.
-    this.graphDump_.subscribeToChanges(client);
+    this.graphDump_.subscribeToChanges(helper.createProxy());
   },
 });
diff --git a/chrome/browser/resources/downloads/browser_proxy.js b/chrome/browser/resources/downloads/browser_proxy.js
index 7dc6c8ee..924df0f 100644
--- a/chrome/browser/resources/downloads/browser_proxy.js
+++ b/chrome/browser/resources/downloads/browser_proxy.js
@@ -13,7 +13,7 @@
 
       const factory = downloads.mojom.PageHandlerFactory.getProxy();
       factory.createPageHandler(
-          this.callbackRouter.createProxy(), this.handler.$.createRequest());
+          this.callbackRouter.$.createProxy(), this.handler.$.createRequest());
     }
   }
 
diff --git a/chrome/browser/resources/interventions_internals/index.js b/chrome/browser/resources/interventions_internals/index.js
index 40a7d07b..c02243f 100644
--- a/chrome/browser/resources/interventions_internals/index.js
+++ b/chrome/browser/resources/interventions_internals/index.js
@@ -704,7 +704,7 @@
       // Set up client side mojo interface.
       pageImpl = new InterventionsInternalPageImpl();
       const client = new mojom.InterventionsInternalsPage(pageImpl);
-      pageHandler.setClientPage(client.createProxy());
+      pageHandler.setClientPage(client.$.createProxy());
     }
 
     interventions_internals.init(pageHandler);
diff --git a/chrome/browser/resources/local_ntp/animations.css b/chrome/browser/resources/local_ntp/animations.css
index 26ac65f2..3725006 100644
--- a/chrome/browser/resources/local_ntp/animations.css
+++ b/chrome/browser/resources/local_ntp/animations.css
@@ -22,29 +22,29 @@
 }
 
 .ripple-effect {
-  background-color: rgba(var(--GB600-rgb), 0.1);
+  background-color: rgba(var(--GB600-rgb), .1);
   border-radius: 50%;
   height: 1px;
   pointer-events: none;
   transition: width, height, margin, background-color 400ms 250ms;
   transition-duration: 400ms;
-  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  transition-timing-function: cubic-bezier(.4, 0, .2, 1);
   width: 1px;
 }
 
 @media (prefers-color-scheme: dark) {
   .ripple-effect {
-    background-color: rgba(var(--GB300-rgb), 0.32);
+    background-color: rgba(var(--GB300-rgb), .32);
   }
 }
 
 button.primary .ripple-effect {
-  background-color: rgba(255, 255, 255, 0.32);
+  background-color: rgba(255, 255, 255, .32);
 }
 
 @media (prefers-color-scheme: dark) {
   button.primary .ripple-effect {
-    background-color: rgba(0, 0, 0, 0.08);
+    background-color: rgba(0, 0, 0, .08);
   }
 }
 
@@ -59,7 +59,7 @@
   position: relative;
   transition-duration: 200ms;
   transition-property: background-color, color, box-shadow, border;
-  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  transition-timing-function: cubic-bezier(.4, 0, .2, 1);
 }
 
 button.paper:disabled {
@@ -92,8 +92,8 @@
 
 button.paper.primary:hover:not(:disabled) {
   background-color: rgb(41, 123, 231);
-  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
-      0 1px 3px 1px rgba(var(--GB500-rgb), 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), .3),
+      0 1px 3px 1px rgba(var(--GB500-rgb), .15);
 }
 
 @media (prefers-color-scheme: dark) {
@@ -103,15 +103,15 @@
 }
 
 button.paper.primary:active:not(:disabled) {
-  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
-      0 3px 6px 2px rgba(var(--GB500-rgb), 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), .3),
+      0 3px 6px 2px rgba(var(--GB500-rgb), .15);
 }
 
 @media (prefers-color-scheme: dark) {
   button.paper.primary:active:not(:disabled) {
     background-color: rgb(115, 160, 223);
-    box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), 0.3),
-        0 3px 6px 2px rgba(var(--GB500-rgb), 0.15);
+    box-shadow: 0 1px 2px 0 rgba(var(--GB500-rgb), .3),
+        0 3px 6px 2px rgba(var(--GB500-rgb), .15);
   }
 }
 
@@ -142,31 +142,31 @@
 }
 
 button.paper.secondary:hover:not(:disabled) {
-  background-color: rgba(var(--GB500-rgb), 0.04);
+  background-color: rgba(var(--GB500-rgb), .04);
   border-color: rgb(var(--GB100-rgb));
 }
 
 @media (prefers-color-scheme: dark) {
   button.paper.secondary:hover:not(:disabled) {
-    background-color: rgba(var(--GB300-rgb), 0.04);
-    border-color: rgba(var(--GB300-rgb), 0.5);
-    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3),
-        0 1px 3px 1px rgba(0, 0, 0, 0.15);
+    background-color: rgba(var(--GB300-rgb), .04);
+    border-color: rgba(var(--GB300-rgb), .5);
+    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .3),
+        0 1px 3px 1px rgba(0, 0, 0, .15);
   }
 }
 
 button.paper.secondary:active:not(:disabled) {
   background-color: white;
   border-color: white;
-  box-shadow: 0 1px 2px 0 rgba(var(--GG800-rgb), 0.3),
-      0 3px 6px 2px rgba(var(--GG800-rgb), 0.15);
+  box-shadow: 0 1px 2px 0 rgba(var(--GG800-rgb), .3),
+      0 3px 6px 2px rgba(var(--GG800-rgb), .15);
 }
 
 @media (prefers-color-scheme: dark) {
   button.paper.secondary:active:not(:disabled) {
-    background-color: rgba(var(--GB300-rgb), 0.08);
-    border-color: rgba(var(--GB300-rgb), 0.5);
-    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3),
-        0 3px 6px 2px rgba(0, 0, 0, 0.15);
+    background-color: rgba(var(--GB300-rgb), .08);
+    border-color: rgba(var(--GB300-rgb), .5);
+    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .3),
+        0 3px 6px 2px rgba(0, 0, 0, .15);
     }
 }
diff --git a/chrome/browser/resources/local_ntp/custom_links_edit.css b/chrome/browser/resources/local_ntp/custom_links_edit.css
index 86f72137..b0edd041 100644
--- a/chrome/browser/resources/local_ntp/custom_links_edit.css
+++ b/chrome/browser/resources/local_ntp/custom_links_edit.css
@@ -15,8 +15,8 @@
   border: none;
   border-radius: 8px;
   bottom: 0;
-  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
-      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), .3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), .15);
   margin: auto;
   min-width: 320px;
   padding: 16px;
@@ -27,8 +27,8 @@
 @media (prefers-color-scheme: dark) {
   #edit-link-dialog {
     background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3),
-        0 4px 8px 3px rgba(0, 0, 0, 0.15);
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
+        0 4px 8px 3px rgba(0, 0, 0, .15);
   }
 }
 
@@ -95,14 +95,14 @@
 
 @media (prefers-color-scheme: dark) {
   input {
-    background-color: rgba(0, 0, 0, 0.3);
+    background-color: rgba(0, 0, 0, .3);
     caret-color: rgb(var(--GB300-rgb));
     color: rgb(var(--GG200-rgb));
   }
 }
 
 input::placeholder {
-  color: rgba(var(--GG900-rgb), 0.38);
+  color: rgba(var(--GG900-rgb), .38);
 }
 
 @media (prefers-color-scheme: dark) {
diff --git a/chrome/browser/resources/local_ntp/customize.css b/chrome/browser/resources/local_ntp/customize.css
index 0dad8581..b2d6f1b 100644
--- a/chrome/browser/resources/local_ntp/customize.css
+++ b/chrome/browser/resources/local_ntp/customize.css
@@ -33,7 +33,7 @@
 
 #edit-bg.ep-enhanced {
   background-color: rgb(255, 255, 255);
-  box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 1px 2px rgba(0, 0, 0, 0.23);
+  box-shadow: 0 3px 6px rgba(0, 0, 0, .16), 0 1px 2px rgba(0, 0, 0, .23);
 }
 
 #edit-bg:hover,
@@ -134,8 +134,8 @@
   border-radius: 8px;
   border-width: thin;
   bottom: 44px;
-  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
-      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), .3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), .15);
   left: auto;
   padding: 0;
   position: fixed;
@@ -145,8 +145,8 @@
 @media (prefers-color-scheme: dark) {
   #edit-bg-dialog {
     background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3),
-        0 4px 8px 3px rgba(0, 0, 0, 0.15);
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
+        0 4px 8px 3px rgba(0, 0, 0, .15);
   }
 }
 
@@ -220,12 +220,12 @@
 }
 
 .bg-option.bg-option-disabled {
-  opacity: 0.28;
+  opacity: .28;
 }
 
 @media (prefers-color-scheme: dark) {
   .bg-option.bg-option-disabled {
-    opacity: 0.38;
+    opacity: .38;
   }
 }
 
@@ -254,7 +254,7 @@
 
 @media (prefers-color-scheme: dark) {
   #edit-bg-divider {
-    border-color: rgba(255, 255, 255, 0.1);
+    border-color: rgba(255, 255, 255, .1);
   }
 }
 
@@ -286,8 +286,8 @@
   border: none;
   border-radius: 8px;
   bottom: 0;
-  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
-      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), .3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), .15);
   height: 400px;
   padding: 0 0 0 0;
   position: fixed;
@@ -301,8 +301,8 @@
 @media (prefers-color-scheme: dark) {
   #bg-sel-menu {
     background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3),
-        0 4px 8px 3px rgba(0, 0, 0, 0.15);
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
+        0 4px 8px 3px rgba(0, 0, 0, .15);
     }
 }
 
@@ -339,7 +339,7 @@
 
 @media (prefers-color-scheme: dark) {
   #bg-sel-title-bar {
-    border-color: rgba(255, 255, 255, 0.1);
+    border-color: rgba(255, 255, 255, .1);
   }
 }
 
@@ -478,7 +478,7 @@
 
 @media (prefers-color-scheme: dark) {
   #bg-sel-footer {
-    border-color: rgba(255, 255, 255, 0.1);
+    border-color: rgba(255, 255, 255, .1);
   }
 }
 
@@ -629,7 +629,7 @@
 }
 
 .bg-sel-tile-title {
-  background-color: rgba(var(--GG900-rgb), 0.71);
+  background-color: rgba(var(--GG900-rgb), .71);
   bottom: 0;
   box-sizing: border-box;
   color: #FFF;
@@ -660,7 +660,7 @@
   max-width: 80%;
   padding: 8px 8px 8px 8px;
   position: fixed;
-  text-shadow: 0 0 16px rgba(0, 0, 0, 0.3);
+  text-shadow: 0 0 16px rgba(0, 0, 0, .3);
   z-index: -1;
 }
 
diff --git a/chrome/browser/resources/local_ntp/doodles.css b/chrome/browser/resources/local_ntp/doodles.css
index 6e70f2b5..f767928 100644
--- a/chrome/browser/resources/local_ntp/doodles.css
+++ b/chrome/browser/resources/local_ntp/doodles.css
@@ -144,7 +144,7 @@
   width: 37.5%;
 }
 .use-notifier #logo-doodle-notifier .inner {
-  animation: anim-pos 880ms cubic-bezier(0.445, 0.05, 0.55, 0.95)
+  animation: anim-pos 880ms cubic-bezier(.445, .05, .55, .95)
       infinite alternate;
   border-radius: 50%;
   height: 100%;
@@ -189,7 +189,7 @@
   cursor: pointer;
   display: inline-block;
   height: 26px;
-  opacity: 0.8;
+  opacity: .8;
   position: absolute;
   width: 26px;
 }
@@ -210,7 +210,7 @@
   background: #fff;
   border: none;
   border-radius: 8px;
-  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, 0.2);
+  box-shadow: 0 4px 16px 0 rgba(0, 0, 0, .2);
   left: 0;
   margin: auto;
   min-height: 100px;
@@ -224,7 +224,19 @@
 }
 
 #ddlsd::backdrop {
-  background-color: rgba(255, 255, 255, 0.9);
+  background-color: rgba(255, 255, 255, .9);
+}
+
+@media (prefers-color-scheme: dark) {
+  #ddlsd {
+    background-color: rgb(41, 42, 45);
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3),
+        0 4px 8px 3px rgba(0, 0, 0, 0.15);
+  }
+
+  #ddlsd::backdrop {
+    background-color: rgba(0, 0, 0, 0.4);
+  }
 }
 
 #ddlsd button {
@@ -233,7 +245,7 @@
 }
 
 #ddlsd button:hover {
-  opacity: 0.8;
+  opacity: .8;
 }
 
 #ddlsd-title {
@@ -242,6 +254,12 @@
   padding: 0 40px 16px 0;
 }
 
+@media (prefers-color-scheme: dark) {
+  #ddlsd-title {
+    color: rgb(var(--GG200-rgb));
+  }
+}
+
 #ddlsd-close {
   background: url(icons/close.svg) no-repeat;
   height: 24px;
@@ -265,7 +283,7 @@
 
 #ddlsd-text {
   border: 2px solid #aaa;
-  border-color: rgba(0, 0, 0, 0.15);
+  border-color: rgba(0, 0, 0, .15);
   border-radius: 4px;
   color: #555;
   display: inline-block;
@@ -274,6 +292,13 @@
   width: 100%;
 }
 
+@media (prefers-color-scheme: dark) {
+  #ddlsd-text {
+    background-color: rgba(0, 0, 0, 0.3);
+    color: rgb(var(--GG200-rgb));
+  }
+}
+
 #ddlsd-copy {
   background: url(icons/copy.svg) no-repeat center;
   background-size: contain;
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index dc690338..a265b36 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -455,7 +455,7 @@
 @media (prefers-color-scheme: dark) {
   body:not(.light-chip)  #mv-notice {
     background-color: rgb(var(--GG900-rgb));
-    border-color: rgba(0, 0, 0, 0.1);
+    border-color: rgba(0, 0, 0, .1);
   }
 }
 
@@ -498,7 +498,7 @@
 
 #mv-notice-links span:hover,
 #mv-notice-links span:active {
-  background-color: rgba(var(--GB600-rgb), 0.1);
+  background-color: rgba(var(--GB600-rgb), .1);
   text-decoration: none;
   transition: background-color 200ms;
 }
@@ -506,7 +506,7 @@
 @media (prefers-color-scheme: dark) {
   body:not(.light-chip)  #mv-notice-links
   :-webkit-any(span:hover, span:active) {
-    background-color: rgba(var(--GB400-dark-rgb), 0.1);
+    background-color: rgba(var(--GB400-dark-rgb), .1);
   }
 }
 
@@ -566,7 +566,7 @@
 
 @media (prefers-color-scheme: dark) {
   .customize-dialog::backdrop {
-    background-color: rgba(0, 0, 0, 0.4);
+    background-color: rgba(0, 0, 0, .4);
   }
 }
 
@@ -631,7 +631,7 @@
 @media (prefers-color-scheme: dark) {
   body:not(.light-chip) #error-notice {
     background-color: rgb(var(--GG900-rgb));
-    border-color: rgba(0, 0, 0, 0.1);
+    border-color: rgba(0, 0, 0, .1);
     color: rgb(var(--GR500-dark-rgb));
   }
 }
@@ -695,7 +695,7 @@
 
 #error-notice-link:hover,
 #error-notice-link:active {
-  background-color: rgba(var(--GB600-rgb), 0.1);
+  background-color: rgba(var(--GB600-rgb), .1);
   text-decoration: none;
   transition: background-color 200ms;
 }
@@ -703,7 +703,7 @@
 @media (prefers-color-scheme: dark) {
   body:not(.light-chip) #error-notice-link:hover,
   body:not(.light-chip) #error-notice-link:active {
-    background-color: rgba(var(--GB400-dark-rgb), 0.1);
+    background-color: rgba(var(--GB400-dark-rgb), .1);
   }
 }
 
@@ -743,7 +743,7 @@
 @media (prefers-color-scheme: dark) {
   body:not(.light-chip) #promo > div {
     background-color: rgb(var(--GG900-rgb));
-    border-color: rgba(0, 0, 0, 0.1);
+    border-color: rgba(0, 0, 0, .1);
     color: rgb(var(--GG200-rgb));
   }
 }
@@ -818,8 +818,8 @@
 @media (prefers-color-scheme: dark) {
   #customization-menu {
     background-color: rgb(41, 42, 45);
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3),
-        0 4px 8px 3px rgba(0, 0, 0, 0.15);
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .3),
+        0 4px 8px 3px rgba(0, 0, 0, .15);
     color: rgb(var(--GG200-rgb));
   }
 }
@@ -947,7 +947,7 @@
 
 @media (prefers-color-scheme: dark) {
   #menu-footer {
-    border-color: rgba(255, 255, 255, 0.1);
+    border-color: rgba(255, 255, 255, .1);
   }
 }
 
diff --git a/chrome/browser/resources/local_ntp/most_visited_single.css b/chrome/browser/resources/local_ntp/most_visited_single.css
index 40e0593a..e25b90de 100644
--- a/chrome/browser/resources/local_ntp/most_visited_single.css
+++ b/chrome/browser/resources/local_ntp/most_visited_single.css
@@ -139,8 +139,8 @@
 .reorder {
   background-color: white;
   border-radius: 4px;
-  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), 0.3),
-      0 4px 8px 3px rgba(var(--GG800-rgb), 0.15);
+  box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), .3),
+      0 4px 8px 3px rgba(var(--GG800-rgb), .15);
   color: rgb(var(--GG800-rgb));
   transition-duration: 200ms;
 }
@@ -148,8 +148,8 @@
 @media (prefers-color-scheme: dark) {
   .reorder {
     background-color: rgb(var(--dark-mode-bg-rgb));
-    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.4),
-        0 4px 8px 3px rgba(0, 0, 0, 0.25);
+    box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .4),
+        0 4px 8px 3px rgba(0, 0, 0, .25);
     color: rgb(var(--GG100-rgb));
   }
 }
@@ -164,19 +164,19 @@
 
 body:not(.reordering) .md-tile:hover,
 .grid-reorder .md-tile {
-  background-color: rgba(var(--GG900-rgb), 0.06);
+  background-color: rgba(var(--GG900-rgb), .06);
 }
 
 @media (prefers-color-scheme: dark) {
   body:not(.reordering) .md-tile:hover,
   .grid-reorder .md-tile {
-    background-color: rgba(255, 255, 255, 0.1);
+    background-color: rgba(255, 255, 255, .1);
   }
 }
 
 body.dark-theme:not(.reordering) .md-tile:hover,
 body.dark-theme .grid-reorder .md-tile {
-  background-color: rgba(255, 255, 255, 0.1);
+  background-color: rgba(255, 255, 255, .1);
 }
 
 body:not(.reordering) .md-tile:hover .md-menu {
@@ -267,7 +267,7 @@
 
 /* Apply when a custom background is set. */
 body.custom-background .md-tile:not(.reorder) .md-title {
-  filter: drop-shadow(0 0 6px rgba(0, 0, 0, 0.35));
+  filter: drop-shadow(0 0 6px rgba(0, 0, 0, .35));
 }
 
 /* Apply only when a theme with image is installed. */
diff --git a/chrome/browser/resources/local_ntp/voice.css b/chrome/browser/resources/local_ntp/voice.css
index 9cbaabbb..fde0cac 100644
--- a/chrome/browser/resources/local_ntp/voice.css
+++ b/chrome/browser/resources/local_ntp/voice.css
@@ -181,9 +181,9 @@
 @media (prefers-color-scheme: dark) {
   .button {
     background-color: rgb(var(--GG900-rgb));
-    border-color: rgba(0, 0, 0, 0.1);
-    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.3),
-                0 4px 8px 3px rgba(0, 0, 0, 0.15);
+    border-color: rgba(0, 0, 0, .1);
+    box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .3),
+                0 4px 8px 3px rgba(0, 0, 0, .15);
   }
 }
 
diff --git a/chrome/browser/resources/omnibox/omnibox.js b/chrome/browser/resources/omnibox/omnibox.js
index 190b77c..385479d2d5 100644
--- a/chrome/browser/resources/omnibox/omnibox.js
+++ b/chrome/browser/resources/omnibox/omnibox.js
@@ -68,7 +68,7 @@
 
     /** @private {!mojom.OmniboxPageHandlerProxy} */
     this.handler_ = mojom.OmniboxPageHandler.getProxy();
-    this.handler_.setClientPage(this.callbackRouter_.createProxy());
+    this.handler_.setClientPage(this.callbackRouter_.$.createProxy());
 
     /** @private {Request} */
     this.lastRequest;
@@ -260,7 +260,7 @@
             currentUrl: "",
             pageClassification: "4"
           }
-        ],        
+        ],
       };
       console.error(`Invalid batch specifier data.  Expected format: \n${
           JSON.stringify(expected, null, 2)}`);
diff --git a/chrome/browser/resources/snippets_internals/snippets_internals.js b/chrome/browser/resources/snippets_internals/snippets_internals.js
index a76d8b3..d3d057d16 100644
--- a/chrome/browser/resources/snippets_internals/snippets_internals.js
+++ b/chrome/browser/resources/snippets_internals/snippets_internals.js
@@ -261,7 +261,7 @@
       snippetsInternals.mojom.PageHandlerFactory.getProxy();
 
   // Give backend mojo a reference to frontend mojo.
-  const client = new snippetsInternals.mojom.Page(page).createProxy();
+  const client = new snippetsInternals.mojom.Page(page).$.createProxy();
   pageHandlerFactory.createPageHandler(client).then((response) => {
 
     pageHandler = response.handler;
diff --git a/chrome/browser/signin/signin_ui_util.cc b/chrome/browser/signin/signin_ui_util.cc
index a93d5c34..667a659c 100644
--- a/chrome/browser/signin/signin_ui_util.cc
+++ b/chrome/browser/signin/signin_ui_util.cc
@@ -215,8 +215,7 @@
   for (auto& account_info : accounts_with_tokens) {
     DCHECK(!account_info.IsEmpty());
     if (!identity::IsUsernameAllowedByPatternFromPrefs(
-            g_browser_process->local_state(), account_info.email,
-            prefs::kGoogleServicesUsernamePattern)) {
+            g_browser_process->local_state(), account_info.email)) {
       continue;
     }
     if (account_info.account_id == default_account_id)
diff --git a/chrome/browser/signin/signin_util.cc b/chrome/browser/signin/signin_util.cc
index ae4781b..cd0efe39 100644
--- a/chrome/browser/signin/signin_util.cc
+++ b/chrome/browser/signin/signin_util.cc
@@ -222,8 +222,7 @@
   CoreAccountInfo primary_account = identity_manager->GetPrimaryAccountInfo();
   if (profile->GetPrefs()->GetBoolean(prefs::kSigninAllowed) &&
       identity::IsUsernameAllowedByPatternFromPrefs(
-          g_browser_process->local_state(), primary_account.email,
-          prefs::kGoogleServicesUsernamePattern)) {
+          g_browser_process->local_state(), primary_account.email)) {
     return;
   }
 
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 389bd318..6d1c8776b 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -3385,10 +3385,6 @@
       "app_list/search/chrome_search_result.h",
       "app_list/search/common/url_icon_source.cc",
       "app_list/search/common/url_icon_source.h",
-      "app_list/search/crostini/crostini_repository_search_provider.cc",
-      "app_list/search/crostini/crostini_repository_search_provider.h",
-      "app_list/search/crostini/crostini_repository_search_result.cc",
-      "app_list/search/crostini/crostini_repository_search_result.h",
       "app_list/search/extension_app_result.cc",
       "app_list/search/extension_app_result.h",
       "app_list/search/mixer.cc",
@@ -3568,8 +3564,6 @@
         "ash/launcher/shelf_spinner_item_controller.h",
         "views/arc_app_dialog_view.cc",
         "views/arc_data_removal_dialog_view.cc",
-        "views/crostini/crostini_app_installer_view.cc",
-        "views/crostini/crostini_app_installer_view.h",
         "views/crostini/crostini_app_restart_view.cc",
         "views/crostini/crostini_app_restart_view.h",
         "views/crostini/crostini_app_uninstaller_view.cc",
@@ -3691,6 +3685,8 @@
       "web_applications/app_browser_controller.h",
       "web_applications/system_web_app_ui_utils_chromeos.cc",
       "web_applications/system_web_app_ui_utils_chromeos.h",
+      "web_applications/web_app_dialog_manager.cc",
+      "web_applications/web_app_dialog_manager.h",
       "web_applications/web_app_dialog_utils.cc",
       "web_applications/web_app_dialog_utils.h",
       "web_applications/web_app_metrics.cc",
diff --git a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.cc b/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.cc
deleted file mode 100644
index 0dd08d98..0000000
--- a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.cc
+++ /dev/null
@@ -1,57 +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.
-
-#include "chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.h"
-
-#include <stddef.h>
-
-#include "base/strings/utf_string_conversions.h"
-#include "base/bind.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.h"
-
-namespace app_list {
-
-CrostiniRepositorySearchProvider::CrostiniRepositorySearchProvider(
-    Profile* profile)
-    : profile_(profile), weak_ptr_factory_(this) {}
-
-CrostiniRepositorySearchProvider::~CrostiniRepositorySearchProvider() = default;
-
-void CrostiniRepositorySearchProvider::OnStart(
-    const std::vector<std::string>& app_names) {
-  SearchProvider::Results new_results;
-  new_results.reserve(app_names.size());
-  for (auto& app_name : app_names) {
-    new_results.emplace_back(
-        std::make_unique<CrostiniRepositorySearchResult>(profile_, app_name));
-    // Todo(https://crbug.com/921429): Improve relevance logic, this will likely
-    // be implemented in garcon then piped to Chrome
-    new_results.back()->set_relevance(static_cast<float>(query_.size()) /
-                                      app_name.size());
-  }
-  SwapResults(&new_results);
-}
-
-void CrostiniRepositorySearchProvider::Start(const base::string16& query) {
-  // Only perform search when Crostini is running.
-  if (!crostini::IsCrostiniRunning(profile_)) {
-    return;
-  }
-  if (query.empty()) {
-    ClearResults();
-    return;
-  }
-  query_ = base::UTF16ToUTF8(query);
-
-  crostini::CrostiniManager::GetForProfile(profile_)->SearchApp(
-      crostini::kCrostiniDefaultVmName, crostini::kCrostiniDefaultContainerName,
-      query_,
-      base::BindOnce(&CrostiniRepositorySearchProvider::OnStart,
-                     weak_ptr_factory_.GetWeakPtr()));
-}
-
-}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.h b/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.h
deleted file mode 100644
index fcb3e55..0000000
--- a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.h
+++ /dev/null
@@ -1,45 +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.
-
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_CROSTINI_REPOSITORY_SEARCH_PROVIDER_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_CROSTINI_REPOSITORY_SEARCH_PROVIDER_H_
-
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/app_list/search/search_provider.h"
-
-class Profile;
-
-namespace app_list {
-
-// Search provider for Crostini repository search.
-class CrostiniRepositorySearchProvider : public SearchProvider {
- public:
-  explicit CrostiniRepositorySearchProvider(Profile* profile);
-  ~CrostiniRepositorySearchProvider() override;
-
-  // SearchProvider overrides:
-  void Start(const base::string16& query) override;
-
- private:
-  // Callback for CrostiniRepositorySearchProvider::Start.
-  void OnStart(const std::vector<std::string>& app_names);
-
-  // Plaintext query to be searched.
-  std::string query_;
-
-  // Unowned pointer to associated profile.
-  Profile* const profile_;
-
-  base::WeakPtrFactory<CrostiniRepositorySearchProvider> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(CrostiniRepositorySearchProvider);
-};
-
-}  // namespace app_list
-
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_CROSTINI_REPOSITORY_SEARCH_PROVIDER_H_
diff --git a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.cc b/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.cc
deleted file mode 100644
index a659f096..0000000
--- a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.cc
+++ /dev/null
@@ -1,72 +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.
-
-#include "chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.h"
-
-#include <stddef.h>
-
-#include "ash/public/cpp/app_list/app_list_config.h"
-#include "ash/public/cpp/app_list/vector_icons/vector_icons.h"
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "chrome/browser/chromeos/crostini/crostini_package_service.h"
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/grit/generated_resources.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/gfx/paint_vector_icon.h"
-
-namespace app_list {
-
-namespace {
-
-// TODO(https://crbug.com/921429): Need UX spec.
-constexpr SkColor kListIconColor = SkColorSetARGB(0xDE, 0x00, 0x00, 0x00);
-
-}  // namespace
-
-CrostiniRepositorySearchResult::CrostiniRepositorySearchResult(
-    Profile* profile,
-    const std::string& app_name)
-    : profile_(profile), app_name_(app_name), weak_ptr_factory_(this) {
-  // TODO(https://crbug.com/921429): Need UX spec.
-  set_id("crostini:" + app_name_);
-  SetResultType(ash::SearchResultType::kOmnibox);
-
-  // TODO(https://crbug.com/921429): Need UX spec.
-  const gfx::VectorIcon& icon = kFileDownloadIcon;
-  SetIcon(gfx::CreateVectorIcon(
-      icon, AppListConfig::instance().search_list_icon_dimension(),
-      kListIconColor));
-  SetTitle(l10n_util::GetStringFUTF16(
-      IDS_CROSTINI_REPOSITORY_SEARCH_RESULT_TITLE_PLACEHOLDER_TEXT,
-      base::UTF8ToUTF16(app_name_)));
-  SetDetails(l10n_util::GetStringUTF16(
-      IDS_CROSTINI_REPOSITORY_SEARCH_RESULT_DETAILS_PLACEHOLDER_TEXT));
-}
-
-CrostiniRepositorySearchResult::~CrostiniRepositorySearchResult() = default;
-
-void CrostiniRepositorySearchResult::OnOpen(
-    const crostini::LinuxPackageInfo& package_info) {
-  // TODO(https://crbug.com/921429): Handle when |package_info| fails.
-  if (package_info.success) {
-    crostini::ShowCrostiniAppInstallerView(profile_, package_info);
-  }
-}
-
-void CrostiniRepositorySearchResult::Open(int event_flags) {
-  crostini::CrostiniManager::GetForProfile(profile_)
-      ->GetLinuxPackageInfoFromApt(
-          crostini::kCrostiniDefaultVmName,
-          crostini::kCrostiniDefaultContainerName, app_name_,
-          base::BindOnce(&CrostiniRepositorySearchResult::OnOpen,
-                         weak_ptr_factory_.GetWeakPtr()));
-}
-
-SearchResultType CrostiniRepositorySearchResult::GetSearchResultType() const {
-  return CROSTINI_APP;
-}
-
-}  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.h b/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.h
deleted file mode 100644
index 9154a0b0..0000000
--- a/chrome/browser/ui/app_list/search/crostini/crostini_repository_search_result.h
+++ /dev/null
@@ -1,41 +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.
-
-#ifndef CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_CROSTINI_REPOSITORY_SEARCH_RESULT_H_
-#define CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_CROSTINI_REPOSITORY_SEARCH_RESULT_H_
-
-#include <string>
-
-#include "ash/public/cpp/app_list/app_list_metrics.h"
-#include "base/macros.h"
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "chrome/browser/ui/app_list/app_context_menu_delegate.h"
-#include "chrome/browser/ui/app_list/search/chrome_search_result.h"
-
-class Profile;
-
-namespace app_list {
-
-class CrostiniRepositorySearchResult : public ChromeSearchResult {
- public:
-  CrostiniRepositorySearchResult(Profile* profile, const std::string& app_name);
-  ~CrostiniRepositorySearchResult() override;
-
-  // ChromeSearchResult overrides:
-  void Open(int event_flags) override;
-  SearchResultType GetSearchResultType() const override;
-
- private:
-  void OnOpen(const crostini::LinuxPackageInfo& package);
-
-  Profile* profile_;
-  std::string app_name_;
-  base::WeakPtrFactory<CrostiniRepositorySearchResult> weak_ptr_factory_;
-
-  DISALLOW_COPY_AND_ASSIGN(CrostiniRepositorySearchResult);
-};
-
-}  // namespace app_list
-
-#endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_CROSTINI_CROSTINI_REPOSITORY_SEARCH_RESULT_H_
diff --git a/chrome/browser/ui/app_list/search/search_controller_factory.cc b/chrome/browser/ui/app_list/search/search_controller_factory.cc
index baccde2..46ef031 100644
--- a/chrome/browser/ui/app_list/search/search_controller_factory.cc
+++ b/chrome/browser/ui/app_list/search/search_controller_factory.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/ui/app_list/search/arc/arc_app_reinstall_search_provider.h"
 #include "chrome/browser/ui/app_list/search/arc/arc_app_shortcuts_search_provider.h"
 #include "chrome/browser/ui/app_list/search/arc/arc_playstore_search_provider.h"
-#include "chrome/browser/ui/app_list/search/crostini/crostini_repository_search_provider.h"
 #include "chrome/browser/ui/app_list/search/launcher_search/launcher_search_provider.h"
 #include "chrome/browser/ui/app_list/search/mixer.h"
 #include "chrome/browser/ui/app_list/search/omnibox_provider.h"
@@ -63,9 +62,6 @@
 constexpr float kBoostOfSettingsShortcut = 10.0f;
 constexpr float kBoostOfApps = 8.0f;
 
-// TODO(danielng): Need UX spec.
-constexpr size_t kMaxCrostiniRepositoryResults = 2;
-
 }  // namespace
 
 std::unique_ptr<SearchController> CreateSearchController(
@@ -159,16 +155,6 @@
             kMaxAppShortcutResults, profile, list_controller, app_ranker));
   }
 
-  // TODO(https://crbug.com/921429): Put feature switch in ash/public/app_list/
-  // like the other search providers.
-  if (base::FeatureList::IsEnabled(features::kCrostiniAppSearch)) {
-    size_t crostini_repository_group_id =
-        controller->AddGroup(kMaxCrostiniRepositoryResults, 1.0, 0.0);
-    controller->AddProvider(
-        crostini_repository_group_id,
-        std::make_unique<CrostiniRepositorySearchProvider>(profile));
-  }
-
   return controller;
 }
 
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.cc b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.cc
index da62ded..15db2ed 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.h"
 
+#include <utility>
+
 #include "base/time/time.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/frecency_store.pb.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h"
@@ -37,6 +39,18 @@
   return counts_;
 }
 
+void FakePredictor::Cleanup(const std::vector<unsigned int>& valid_targets) {
+  std::map<unsigned int, float> new_counts;
+
+  for (unsigned int id : valid_targets) {
+    const auto& it = counts_.find(id);
+    if (it != counts_.end())
+      new_counts[id] = it->second;
+  }
+
+  counts_.swap(new_counts);
+}
+
 void FakePredictor::ToProto(RecurrencePredictorProto* proto) const {
   auto* counts = proto->mutable_fake_predictor()->mutable_counts();
   for (auto& pair : counts_)
@@ -117,6 +131,49 @@
   return result;
 }
 
+void ConditionalFrequencyPredictor::Cleanup(
+    const std::vector<unsigned int>& valid_targets) {
+  for (auto iter = table_.begin(); iter != table_.end();) {
+    auto& events = iter->second;
+
+    std::map<unsigned int, float> new_freqs;
+    float new_total = 0.0f;
+    for (unsigned int id : valid_targets) {
+      const auto& it = events.freqs.find(id);
+      if (it != events.freqs.end()) {
+        new_freqs[id] = it->second;
+        new_total += it->second;
+      }
+    }
+
+    // Delete the whole condition out of the table if it contains no valid
+    // targets.
+    if (new_freqs.empty()) {
+      // C++11: the return value of erase(iter) is an iterator pointing to the
+      // next element in the container.
+      iter = table_.erase(iter);
+    } else {
+      ++iter;
+      events.freqs.swap(new_freqs);
+      events.total = new_total;
+    }
+  }
+}
+
+void ConditionalFrequencyPredictor::CleanupConditions(
+    const std::vector<unsigned int>& valid_conditions) {
+  std::map<unsigned int, ConditionalFrequencyPredictor::Events> new_table;
+
+  for (unsigned int id : valid_conditions) {
+    const auto& it = table_.find(id);
+    if (it != table_.end()) {
+      new_table[id] = std::move(it->second);
+    }
+  }
+
+  table_.swap(new_table);
+}
+
 void ConditionalFrequencyPredictor::ToProto(
     RecurrencePredictorProto* proto) const {
   auto* predictor = proto->mutable_conditional_frequency_predictor();
@@ -169,6 +226,19 @@
   return result;
 }
 
+void FrecencyPredictor::Cleanup(
+    const std::vector<unsigned int>& valid_targets) {
+  std::map<unsigned int, FrecencyPredictor::TargetData> new_targets;
+
+  for (unsigned int id : valid_targets) {
+    const auto& it = targets_.find(id);
+    if (it != targets_.end())
+      new_targets[id] = it->second;
+  }
+
+  targets_.swap(new_targets);
+}
+
 void FrecencyPredictor::ToProto(RecurrencePredictorProto* proto) const {
   auto* predictor = proto->mutable_frecency_predictor();
 
@@ -280,6 +350,10 @@
   return ranks;
 }
 
+// TODO(921444): Unify the hour bin predictor with the cleanup system used for
+// other predictors. This is different than other predictors so as to be exactly
+// the same as the Roselle predictor.
+
 void HourBinPredictor::ToProto(RecurrencePredictorProto* proto) const {
   *proto->mutable_hour_bin_predictor() = proto_;
 }
@@ -349,6 +423,11 @@
   return std::map<unsigned int, float>();
 }
 
+void MarkovPredictor::Cleanup(const std::vector<unsigned int>& valid_targets) {
+  frequencies_.CleanupConditions(valid_targets);
+  frequencies_.Cleanup(valid_targets);
+}
+
 void MarkovPredictor::ToProto(RecurrencePredictorProto* proto) const {
   auto* predictor = proto->mutable_markov_predictor();
   frequencies_.ToProto(predictor->mutable_frequencies());
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.h b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.h
index 8d7424eb..62d7a50 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.h
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor.h
@@ -7,6 +7,7 @@
 
 #include <map>
 #include <memory>
+#include <vector>
 
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
@@ -45,6 +46,13 @@
   // under this predictor. Scores must be within the range [0,1].
   virtual std::map<unsigned int, float> Rank(unsigned int condition) = 0;
 
+  // Called when the ranker detects that the predictor's rankings are
+  // significantly different to the set of valid targets, and can optionally be
+  // used to clean up internal state. For efficiency reasons, Cleanup is
+  // supplied a const reference to the FrecencyStore's internal state. However
+  // it is likely that only id field in the values is of interest.
+  virtual void Cleanup(const std::vector<unsigned int>& valid_targets) {}
+
   virtual void ToProto(RecurrencePredictorProto* proto) const = 0;
   virtual void FromProto(const RecurrencePredictorProto& proto) = 0;
   virtual const char* GetPredictorName() const = 0;
@@ -64,6 +72,7 @@
   // RecurrencePredictor:
   void Train(unsigned int target, unsigned int condition) override;
   std::map<unsigned int, float> Rank(unsigned int condition) override;
+  void Cleanup(const std::vector<unsigned int>& valid_targets) override;
   void ToProto(RecurrencePredictorProto* proto) const override;
   void FromProto(const RecurrencePredictorProto& proto) override;
   const char* GetPredictorName() const override;
@@ -128,10 +137,17 @@
   void Train(unsigned int target, unsigned int condition) override;
   // The scores in the returned map sum to 1 if the map is non-empty.
   std::map<unsigned int, float> Rank(unsigned int condition) override;
+  void Cleanup(const std::vector<unsigned int>& valid_targets) override;
   void ToProto(RecurrencePredictorProto* proto) const override;
   void FromProto(const RecurrencePredictorProto& proto) override;
   const char* GetPredictorName() const override;
 
+  // Deletes all information about conditions not in |valid_conditions|. This is
+  // analogous to Cleanup for targets. Note that Cleanup already deletes
+  // conditions if they have no associated targets, so CleanupTargets is useful
+  // only in the case of having extra information about invalid conditions.
+  void CleanupConditions(const std::vector<unsigned int>& valid_conditions);
+
   static const char kPredictorName[];
 
   // Add |delta| to the relative frequency of |target| given |condition|.
@@ -165,6 +181,7 @@
   // RecurrencePredictor:
   void Train(unsigned int target, unsigned int condition) override;
   std::map<unsigned int, float> Rank(unsigned int condition) override;
+  void Cleanup(const std::vector<unsigned int>& valid_targets) override;
   void ToProto(RecurrencePredictorProto* proto) const override;
   void FromProto(const RecurrencePredictorProto& proto) override;
   const char* GetPredictorName() const override;
@@ -250,6 +267,7 @@
   // RecurrencePredictor:
   void Train(unsigned int target, unsigned int condition) override;
   std::map<unsigned int, float> Rank(unsigned int condition) override;
+  void Cleanup(const std::vector<unsigned int>& valid_targets) override;
   void ToProto(RecurrencePredictorProto* proto) const override;
   void FromProto(const RecurrencePredictorProto& proto) override;
   const char* GetPredictorName() const override;
@@ -257,6 +275,7 @@
   static const char kPredictorName[];
 
  private:
+  FRIEND_TEST_ALL_PREFIXES(MarkovPredictorTest, Cleanup);
   FRIEND_TEST_ALL_PREFIXES(MarkovPredictorTest, ToAndFromProto);
 
   // Stores transition probabilities: P(target | previous_target).
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor_unittest.cc b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor_unittest.cc
index a2e4b90..525c691b 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor_unittest.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_predictor_unittest.cc
@@ -71,6 +71,15 @@
   }
 }
 
+TEST_F(FrecencyPredictorTest, Cleanup) {
+  for (int i = 0; i < 6; ++i)
+    predictor_->Train(i, 0u);
+  predictor_->Cleanup({0u, 2u, 4u});
+
+  EXPECT_THAT(predictor_->Rank(0u),
+              UnorderedElementsAre(Pair(0u, _), Pair(2u, _), Pair(4u, _)));
+}
+
 TEST_F(FrecencyPredictorTest, ToAndFromProto) {
   predictor_->Train(1u, 0u);
   predictor_->Train(3u, 0u);
@@ -108,6 +117,27 @@
                                    Pair(2u, FloatEq(2.0f / 5.0f))));
 }
 
+TEST_F(ConditionalFrequencyPredictorTest, Cleanup) {
+  ConditionalFrequencyPredictor cfp;
+
+  cfp.Train(0u, 0u);
+  for (int i = 0; i < 6; ++i) {
+    cfp.Train(i, 0u);
+    cfp.Train(2 * i, 1u);
+    cfp.Train(2 * i + 1, 2u);
+  }
+  cfp.Cleanup({0u, 2u, 4u});
+
+  EXPECT_THAT(cfp.Rank(0u), UnorderedElementsAre(Pair(0u, FloatEq(0.5f)),
+                                                 Pair(2u, FloatEq(0.25f)),
+                                                 Pair(4u, FloatEq(0.25f))));
+  EXPECT_THAT(cfp.Rank(1u),
+              UnorderedElementsAre(Pair(0u, FloatEq(1.0f / 3.0f)),
+                                   Pair(2u, FloatEq(1.0f / 3.0f)),
+                                   Pair(4u, FloatEq(1.0f / 3.0f))));
+  EXPECT_TRUE(cfp.Rank(2u).empty());
+}
+
 TEST_F(ConditionalFrequencyPredictorTest, ToFromProto) {
   ConditionalFrequencyPredictor cfp1;
 
@@ -418,6 +448,33 @@
                                    Pair(3u, FloatEq(1.0f / 3.0f))));
 }
 
+TEST_F(MarkovPredictorTest, Cleanup) {
+  // 0 -> {1, 3} and all i -> {i+1}.
+  for (int i = 0; i < 6; ++i)
+    predictor_->Train(i, 0u);
+  predictor_->Train(0, 0u);
+  predictor_->Train(3, 0u);
+
+  predictor_->Cleanup({0u, 1u, 2u});
+
+  // Expect 0 -> {1} with target 3 deleted.
+  predictor_->previous_target_ = 0u;
+  EXPECT_THAT(predictor_->Rank(0u),
+              UnorderedElementsAre(Pair(1u, FloatEq(1.0f))));
+  // Expect 1 -> {2} with nothing deleted.
+  predictor_->previous_target_ = 1u;
+  EXPECT_THAT(predictor_->Rank(1u),
+              UnorderedElementsAre(Pair(2u, FloatEq(1.0f))));
+
+  // Conditions 2, 3, 4, 5 should have been cleaned up. For 2, all targets are
+  // deleted so the condition itself should be too. For the remainder, the
+  // condition is invalid so should be deleted directly.
+  for (int i = 3; i < 6; ++i) {
+    predictor_->previous_target_ = i;
+    EXPECT_TRUE(predictor_->Rank(0u).empty());
+  }
+}
+
 TEST_F(MarkovPredictorTest, ToAndFromProto) {
   // Some complicated transitions.
   for (int i = 0; i < 10; ++i) {
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.cc b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.cc
index 4e09ce6..6e956a8 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.cc
@@ -33,6 +33,11 @@
 using base::Time;
 using base::TimeDelta;
 
+// A predictor may return scores for target IDs that have been deleted. If less
+// than this proportion of IDs are valid, the ranker triggers a cleanup of the
+// predictor's state on a call to RecurrenceRanker::Rank.
+constexpr float kMinValidTargetProportionBeforeCleanup = 0.5f;
+
 void SaveProtoToDisk(const base::FilePath& filepath,
                      const RecurrenceRankerProto& proto) {
   std::string proto_str;
@@ -92,9 +97,25 @@
   return sorted_ranks;
 }
 
-std::map<std::string, float> ZipTargetsWithScores(
+// Given a FrecencyStore's map from target names to IDs, and a
+// RecurrencePredictor's map of IDs to scores, returns a pair containing the
+// following:
+//
+//  - A map from target names to scores.
+//  - The proportion of IDs returned by the predictor that are 'valid', ie.
+//    that exist in the target frecency store.
+//
+// The second value can be used to decide when to trigger a cleanup of the
+// predictor's internal state.
+std::pair<std::map<std::string, float>, float> ZipTargetsWithScores(
     const FrecencyStore::ScoreTable& target_to_id,
     const std::map<unsigned int, float>& id_to_score) {
+  // Early exit if the predictor's ranks are empty. In this case make the
+  // proportion of valid IDs 1.0, as a cleanup would be a noop.
+  if (id_to_score.empty())
+    return {{}, 1.0f};
+
+  float num_valid_targets = 0.0f;
   std::map<std::string, float> target_to_score;
   for (const auto& pair : target_to_id) {
     DCHECK(pair.second.last_num_updates ==
@@ -102,10 +123,11 @@
     const auto& it = id_to_score.find(pair.second.id);
     if (it != id_to_score.end()) {
       target_to_score[pair.first] = it->second;
+      num_valid_targets += 1.0f;
     }
   }
 
-  return target_to_score;
+  return {std::move(target_to_score), num_valid_targets / id_to_score.size()};
 }
 
 std::map<std::string, float> GetScoresFromFrecencyStore(
@@ -255,8 +277,22 @@
   if (condition_id == base::nullopt)
     return {};
 
-  return ZipTargetsWithScores(targets_->GetAll(),
-                              predictor_->Rank(condition_id.value()));
+  const auto& targets = targets_->GetAll();
+  const auto& zipped =
+      ZipTargetsWithScores(targets, predictor_->Rank(condition_id.value()));
+  MaybeCleanup(zipped.second, targets);
+  return std::move(zipped.first);
+}
+
+void RecurrenceRanker::MaybeCleanup(float proportion_valid,
+                                    const FrecencyStore::ScoreTable& targets) {
+  if (proportion_valid > kMinValidTargetProportionBeforeCleanup)
+    return;
+
+  std::vector<unsigned int> valid_targets;
+  for (const auto& target_data : targets)
+    valid_targets.push_back(target_data.second.id);
+  predictor_->Cleanup(valid_targets);
 }
 
 std::vector<std::pair<std::string, float>> RecurrenceRanker::RankTopN(
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h
index 78aa6d1..f32a558 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker.h
@@ -70,9 +70,6 @@
       int n,
       const std::string& condition = std::string());
 
-  // TODO(921444): Create a system for cleaning up internal predictor state that
-  // is stored indepent of the target/condition frecency stores.
-
   // Force saving all model state to disk. If the user is an ephemeral user,
   // this does nothing. This is not necessary in normal operation, as the ranker
   // automatically saves at regular intervals. Example use: syncing to disk
@@ -95,6 +92,7 @@
                            SavedRankerRejectedIfConfigMismatched);
   FRIEND_TEST_ALL_PREFIXES(RecurrenceRankerTest, LoadFromDisk);
   FRIEND_TEST_ALL_PREFIXES(RecurrenceRankerTest, SaveToDisk);
+  FRIEND_TEST_ALL_PREFIXES(RecurrenceRankerTest, Cleanup);
 
   // Finishes initialisation by populating |this| with data from the given
   // proto.
@@ -105,6 +103,14 @@
   void ToProto(RecurrenceRankerProto* proto);
   void FromProto(const RecurrenceRankerProto& proto);
 
+  // Possibly triggers a cleanup of |prdictor_|'s internal state.
+  // |proportion_valid| should be the proportion of targets returned by the
+  // predictor that exist in the target frecency store. If a cleanup is
+  // triggered, RecurrencePredictor::Cleanup is called with a list of valid
+  // targets derived from |targets|.
+  void MaybeCleanup(float proportion_valid,
+                    const FrecencyStore::ScoreTable& targets);
+
   // Internal predictor that drives ranking.
   std::unique_ptr<RecurrencePredictor> predictor_;
 
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker_unittest.cc b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker_unittest.cc
index 43e6fd0..3dc6fca2 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker_unittest.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/recurrence_ranker_unittest.cc
@@ -343,6 +343,42 @@
                                       ConfigurationError::kHashMismatch, 1);
 }
 
+TEST_F(RecurrenceRankerTest, Cleanup) {
+  auto ranker = MakeSimpleRanker();
+
+  // Targets to forget.
+  ranker->Record("A");
+  ranker->Record("B");
+  ranker->Record("C");
+
+  // Valid proportion is 1 so cleanup shouldn't be called. Rank is called once
+  // on the ranker to possibly trigger the cleanup, and a second time on the
+  // predictor to observe the effects without ever triggering a cleanup.
+  ranker->Rank();
+  EXPECT_THAT(ranker->predictor_->Rank(0u),
+              UnorderedElementsAre(Pair(0u, _), Pair(1u, _), Pair(2u, _)));
+
+  // Record enough times that A should be removed from the ranker's store.
+  for (int i = 0; i < 100; ++i) {
+    ranker->Record("B");
+    ranker->Record("C");
+  }
+
+  // Valid proportion is 2/3 so cleanup still shouldn't be called.
+  ranker->Rank();
+  EXPECT_THAT(ranker->predictor_->Rank(0u),
+              UnorderedElementsAre(Pair(0u, _), Pair(1u, _), Pair(2u, _)));
+
+  // Record enough times that B and C be removed from the ranker's store
+  for (int i = 0; i < 100; ++i)
+    ranker->Record("D");
+
+  // Valid proportion is 1/4 so cleanup should be called. Examining the internal
+  // state, the predictor should only contain the ID for D.
+  ranker->Rank();
+  EXPECT_THAT(ranker->predictor_->Rank(0u), UnorderedElementsAre(Pair(3u, _)));
+}
+
 TEST_F(RecurrenceRankerTest, EphemeralUsersUseDefaultPredictor) {
   RecurrenceRanker ephemeral_ranker(ranker_filepath_, MakeSimpleConfig(), true);
   Wait();
diff --git a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
index 771fb2a..ef0c369 100644
--- a/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
+++ b/chrome/browser/ui/extensions/hosted_app_browser_controller.cc
@@ -15,6 +15,8 @@
 #include "chrome/browser/ui/browser_window_state.h"
 #include "chrome/browser/ui/location_bar/location_bar.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
+#include "chrome/browser/ui/web_applications/web_app_ui_service.h"
 #include "chrome/browser/web_applications/components/web_app_helpers.h"
 #include "chrome/common/chrome_features.h"
 #include "chrome/common/extensions/api/url_handlers/url_handlers_parser.h"
@@ -30,8 +32,6 @@
 #include "content/public/common/web_preferences.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
-#include "extensions/browser/management_policy.h"
-#include "extensions/browser/uninstall_reason.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
 #include "third_party/blink/public/mojom/renderer_preferences.mojom.h"
@@ -309,17 +309,19 @@
 }
 
 bool HostedAppBrowserController::CanUninstall() const {
-  return extensions::ExtensionSystem::Get(browser()->profile())
-      ->management_policy()
-      ->UserMayModifySettings(GetExtension(), nullptr);
+  auto* web_app_ui_service =
+      web_app::WebAppUiService::Get(browser()->profile());
+  DCHECK(web_app_ui_service);
+  return web_app_ui_service->dialog_manager().CanUninstallWebApp(extension_id_);
 }
 
 void HostedAppBrowserController::Uninstall() {
-  uninstall_dialog_ = ExtensionUninstallDialog::Create(
-      browser()->profile(), browser()->window()->GetNativeWindow(), this);
-  uninstall_dialog_->ConfirmUninstall(
-      GetExtension(), extensions::UNINSTALL_REASON_USER_INITIATED,
-      extensions::UNINSTALL_SOURCE_HOSTED_APP_MENU);
+  auto* web_app_ui_service =
+      web_app::WebAppUiService::Get(browser()->profile());
+  DCHECK(web_app_ui_service);
+  web_app_ui_service->dialog_manager().UninstallWebApp(
+      extension_id_, web_app::WebAppDialogManager::UninstallSource::kAppMenu,
+      browser()->window(), base::DoNothing());
 }
 
 bool HostedAppBrowserController::IsInstalled() const {
diff --git a/chrome/browser/ui/extensions/hosted_app_browser_controller.h b/chrome/browser/ui/extensions/hosted_app_browser_controller.h
index a985c3c..f261a1e 100644
--- a/chrome/browser/ui/extensions/hosted_app_browser_controller.h
+++ b/chrome/browser/ui/extensions/hosted_app_browser_controller.h
@@ -5,13 +5,11 @@
 #ifndef CHROME_BROWSER_UI_EXTENSIONS_HOSTED_APP_BROWSER_CONTROLLER_H_
 #define CHROME_BROWSER_UI_EXTENSIONS_HOSTED_APP_BROWSER_CONTROLLER_H_
 
-#include <memory>
 #include <string>
 
 #include "base/macros.h"
 #include "base/optional.h"
 #include "base/strings/string16.h"
-#include "chrome/browser/extensions/extension_uninstall_dialog.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -35,8 +33,7 @@
 
 // Class to encapsulate logic to control the browser UI for extension based web
 // apps.
-class HostedAppBrowserController : public ExtensionUninstallDialog::Delegate,
-                                   public web_app::AppBrowserController {
+class HostedAppBrowserController : public web_app::AppBrowserController {
  public:
   // Functions to set preferences that are unique to app windows.
   static void SetAppPrefsForWebContents(
@@ -108,7 +105,6 @@
 
   const std::string extension_id_;
   const bool created_for_installed_pwa_;
-  std::unique_ptr<ExtensionUninstallDialog> uninstall_dialog_;
 
   DISALLOW_COPY_AND_ASSIGN(HostedAppBrowserController);
 };
diff --git a/chrome/browser/ui/login/login_handler_browsertest.cc b/chrome/browser/ui/login/login_handler_browsertest.cc
index ce1ae50..42af222 100644
--- a/chrome/browser/ui/login/login_handler_browsertest.cc
+++ b/chrome/browser/ui/login/login_handler_browsertest.cc
@@ -13,6 +13,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/net/proxy_test_utils.h"
 #include "chrome/browser/prerender/prerender_manager.h"
 #include "chrome/browser/prerender/prerender_origin.h"
 #include "chrome/browser/ssl/ssl_blocking_page.h"
@@ -52,6 +53,60 @@
 
 namespace {
 
+void TestProxyAuth(Browser* browser, const GURL& test_page) {
+  bool https = test_page.SchemeIs(url::kHttpsScheme);
+
+  content::WebContents* contents =
+      browser->tab_strip_model()->GetActiveWebContents();
+  NavigationController* controller = &contents->GetController();
+  LoginPromptBrowserTestObserver observer;
+  observer.Register(content::Source<NavigationController>(controller));
+
+  {
+    WindowedAuthNeededObserver auth_needed_waiter(controller);
+    browser->OpenURL(OpenURLParams(test_page, Referrer(),
+                                   WindowOpenDisposition::CURRENT_TAB,
+                                   ui::PAGE_TRANSITION_TYPED, false));
+    auth_needed_waiter.Wait();
+  }
+
+  // On HTTPS pages, no error page content should be renderer to avoid origin
+  // confusion issues.
+  if (https) {
+    EXPECT_EQ(true, content::EvalJs(contents, "document.body === null"));
+  }
+
+  // Cancel the prompt. On HTTPS pages, the error page content still shouldn't
+  // be shown.
+  {
+    WindowedAuthCancelledObserver auth_cancelled_waiter(controller);
+    LoginHandler* handler = observer.handlers().front();
+    handler->CancelAuth();
+    auth_cancelled_waiter.Wait();
+    if (https) {
+      EXPECT_EQ(true, content::EvalJs(contents, "document.body === null"));
+    }
+  }
+
+  // Reload; this time, supply credentials and check that the page loads.
+  {
+    WindowedAuthNeededObserver auth_needed_waiter(controller);
+    browser->OpenURL(OpenURLParams(test_page, Referrer(),
+                                   WindowOpenDisposition::CURRENT_TAB,
+                                   ui::PAGE_TRANSITION_TYPED, false));
+    auth_needed_waiter.Wait();
+  }
+
+  WindowedAuthSuppliedObserver auth_supplied_waiter(controller);
+  LoginHandler* handler = observer.handlers().front();
+  handler->SetAuth(base::UTF8ToUTF16("foo"), base::UTF8ToUTF16("bar"));
+  auth_supplied_waiter.Wait();
+
+  base::string16 expected_title = base::ASCIIToUTF16("OK");
+  content::TitleWatcher title_watcher(contents, expected_title);
+  EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
+}
+
 content::InterstitialPageDelegate* GetInterstitialDelegate(
     content::WebContents* tab) {
   security_interstitials::SecurityInterstitialTabHelper* helper =
@@ -1699,4 +1754,26 @@
   EXPECT_EQ(0u, observer.handlers().size());
 }
 
+// Tests that basic proxy auth works as expected, for HTTPS pages.
+IN_PROC_BROWSER_TEST_F(ProxyBrowserTest, ProxyAuthHTTPS) {
+  base::test::ScopedFeatureList scoped_feature_list;
+  scoped_feature_list.InitAndEnableFeature(
+      features::kHTTPAuthCommittedInterstitials);
+  net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS);
+  https_server.AddDefaultHandlers(GetChromeTestDataDir());
+  ASSERT_TRUE(https_server.Start());
+  ASSERT_NO_FATAL_FAILURE(
+      TestProxyAuth(browser(), https_server.GetURL("/simple.html")));
+}
+
+// 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);
+  ASSERT_TRUE(embedded_test_server()->Start());
+  ASSERT_NO_FATAL_FAILURE(
+      TestProxyAuth(browser(), embedded_test_server()->GetURL("/simple.html")));
+}
+
 }  // namespace
diff --git a/chrome/browser/ui/login/login_tab_helper.cc b/chrome/browser/ui/login/login_tab_helper.cc
index b3765b867..044a35a 100644
--- a/chrome/browser/ui/login/login_tab_helper.cc
+++ b/chrome/browser/ui/login/login_tab_helper.cc
@@ -21,7 +21,53 @@
     content::NavigationHandle* navigation_handle) {
   // When navigating away, the LoginHandler for the previous navigation (if any)
   // should get cleared.
+
+  // Do not clear the login prompt for subframe or same-document navigations;
+  // these could happen in the case of 401/407 error pages that have fancy
+  // response bodies that have subframes or can trigger same-document
+  // navigations.
+  if (!navigation_handle->IsInMainFrame() ||
+      navigation_handle->IsSameDocument())
+    return;
+
+  if (!delegate_)
+    return;
+
+  // TODO(https://crbug.com/943610): this is a very hacky special-case for a
+  // particular issue that arises when the server sends an empty body for the
+  // 401 or 407 response. In this case, the renderer commits an error page for
+  // the HTTP status code (see
+  // ChromeContentRendererClient::PrepareErrorPageForHttpStatusError). Error
+  // pages are a second commit from the browser's perspective, which runs
+  // DidStartNavigation again and therefore would dismiss the prompt if this
+  // special case weren't here. To make matters worse, at the error page's
+  // second commit, the browser process does not have a NavigationRequest
+  // available (see |is_commit_allowed_to_proceed| in
+  // RenderFrameHostImpl::DidCommitNavigationInternal), and therefore no
+  // AuthChallengeInfo to use to re-show the prompt after it has been
+  // dismissed. This whole mess should be fixed in https://crbug.com/943610,
+  // which is about enforcing that the browser always has a NavigationRequest
+  // available at commit time; once error pages no longer have second commits
+  // for which NavigationRequests are manufactured, this special case will no
+  // longer be needed.
+  //
+  // For now, we can hack around it by preserving the login prompt when starting
+  // a navigation to the same URL for which we are currently showing a login
+  // prompt. There is a possibility that the starting navigation is actually due
+  // to the user refreshing rather than the error page committing, and when the
+  // user refreshes the server might no longer serve an auth challenge. But we
+  // can't distinguish the two scenarios (error page committing vs user
+  // refreshing) at this point, so we clear the prompt in DidFinishNavigation if
+  // it's not an error page. This behavior could lead to a slight oddness where
+  // the prompt lingers around for a bit too long, but this should only happen
+  // in the perfect storm where a server's auth response has an empty body,
+  // the user refreshes when the prompt is showing, and the server no longer
+  // requires auth on the refresh.
+  if (navigation_handle->GetURL() == url_for_delegate_)
+    return;
+
   delegate_.reset();
+  url_for_delegate_ = GURL();
 }
 
 void LoginTabHelper::DidFinishNavigation(
@@ -29,6 +75,12 @@
   DCHECK(
       base::FeatureList::IsEnabled(features::kHTTPAuthCommittedInterstitials));
 
+  // See TODO(https://crbug.com/943610) in DidStartNavigation().
+  if (delegate_ && !navigation_handle->IsErrorPage()) {
+    delegate_.reset();
+    url_for_delegate_ = GURL();
+  }
+
   if (!navigation_handle->GetAuthChallengeInfo()) {
     return;
   }
@@ -50,6 +102,7 @@
 
   challenge_ = navigation_handle->GetAuthChallengeInfo().value();
 
+  url_for_delegate_ = navigation_handle->GetURL();
   delegate_ = CreateLoginPrompt(
       navigation_handle->GetAuthChallengeInfo().value(),
       navigation_handle->GetWebContents(),
@@ -72,10 +125,13 @@
 
 void LoginTabHelper::HandleCredentials(
     const base::Optional<net::AuthCredentials>& credentials) {
+  delegate_.reset();
+  url_for_delegate_ = GURL();
+
   if (!credentials.has_value()) {
-    delegate_.reset();
     return;
   }
+
   // Pass a weak pointer for the callback, as the WebContents (and thus this
   // LoginTabHelper) could be destroyed while the network service is processing
   // the new cache entry.
diff --git a/chrome/browser/ui/login/login_tab_helper.h b/chrome/browser/ui/login/login_tab_helper.h
index c67be75..541b8a4 100644
--- a/chrome/browser/ui/login/login_tab_helper.h
+++ b/chrome/browser/ui/login/login_tab_helper.h
@@ -44,6 +44,7 @@
   void Reload();
 
   std::unique_ptr<content::LoginDelegate> delegate_;
+  GURL url_for_delegate_;
 
   net::AuthChallengeInfo challenge_;
 
diff --git a/chrome/browser/ui/startup/startup_browser_creator.cc b/chrome/browser/ui/startup/startup_browser_creator.cc
index f798328..55fcb0a 100644
--- a/chrome/browser/ui/startup/startup_browser_creator.cc
+++ b/chrome/browser/ui/startup/startup_browser_creator.cc
@@ -48,6 +48,7 @@
 #include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/chrome_constants.h"
+#include "chrome/common/chrome_features.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/pref_names.h"
 #include "chrome/common/url_constants.h"
@@ -76,6 +77,7 @@
 #include "chromeos/cryptohome/cryptohome_parameters.h"
 #include "components/user_manager/user_manager.h"
 #else
+#include "chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h"
 #include "chrome/browser/ui/user_manager.h"
 #endif
 
@@ -684,6 +686,20 @@
     silent_launch = true;
   }
 
+#if !defined(OS_CHROMEOS)
+  if (!process_startup &&
+      base::FeatureList::IsEnabled(features::kOnConnectNative) &&
+      command_line.HasSwitch(switches::kNativeMessagingConnectHost) &&
+      command_line.HasSwitch(switches::kNativeMessagingConnectExtension)) {
+    silent_launch = true;
+    extensions::LaunchNativeMessageHostFromNativeApp(
+        command_line.GetSwitchValueASCII(
+            switches::kNativeMessagingConnectExtension),
+        command_line.GetSwitchValueASCII(switches::kNativeMessagingConnectHost),
+        last_used_profile);
+  }
+#endif
+
   // If --no-startup-window is specified and Chrome is already running then do
   // not open a new window.
   if (!process_startup && command_line.HasSwitch(switches::kNoStartupWindow))
diff --git a/chrome/browser/ui/views/crostini/crostini_app_installer_view.cc b/chrome/browser/ui/views/crostini/crostini_app_installer_view.cc
deleted file mode 100644
index ace83c9..0000000
--- a/chrome/browser/ui/views/crostini/crostini_app_installer_view.cc
+++ /dev/null
@@ -1,124 +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.
-
-#include "chrome/browser/ui/views/crostini/crostini_app_installer_view.h"
-
-#include <memory>
-#include <utility>
-
-#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/chromeos/crostini/crostini_package_service.h"
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser_dialogs.h"
-#include "chrome/browser/ui/views/chrome_layout_provider.h"
-#include "chrome/grit/generated_resources.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/strings/grit/ui_strings.h"
-#include "ui/views/controls/label.h"
-#include "ui/views/controls/scroll_view.h"
-#include "ui/views/layout/box_layout.h"
-#include "ui/views/layout/layout_provider.h"
-
-namespace crostini {
-
-constexpr unsigned int kMinScrollHeight = 0;
-constexpr unsigned int kMaxScrollHeight = 250;
-
-// Declaration in crostini_util.h, definition here. Needed because of include
-// restrictions.
-void ShowCrostiniAppInstallerView(Profile* profile,
-                                  const LinuxPackageInfo& package_info) {
-  views::DialogDelegate::CreateDialogWidget(
-      new CrostiniAppInstallerView(profile, package_info), nullptr, nullptr)
-      ->Show();
-}
-
-}  // namespace crostini
-
-CrostiniAppInstallerView::CrostiniAppInstallerView(
-    Profile* profile,
-    const crostini::LinuxPackageInfo& package_info)
-    : profile_(profile), package_info_(package_info) {
-  views::LayoutProvider* provider = views::LayoutProvider::Get();
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::Orientation::kVertical,
-      provider->GetInsetsMetric(views::InsetsMetric::INSETS_DIALOG),
-      provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL)));
-  set_margins(provider->GetDialogInsetsForContentType(
-      views::DialogContentType::TEXT, views::DialogContentType::TEXT));
-
-  views::Label* message_label_ = new views::Label(
-      l10n_util::GetStringFUTF16(IDS_CROSTINI_APP_INSTALL_DIALOG_BODY_TEXT,
-                                 base::UTF8ToUTF16(package_info_.name)));
-  message_label_->SetMultiLine(true);
-  message_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-  AddChildView(message_label_);
-  base::string16 description;
-
-  if (package_info_.description.empty()) {
-    description = l10n_util::GetStringUTF16(
-        IDS_CROSTINI_APP_INSTALL_DIALOG_NO_DESCRIPTION_TEXT);
-  } else {
-    description = base::UTF8ToUTF16(package_info_.description);
-  }
-
-  base::string16 message = l10n_util::GetStringFUTF16(
-      IDS_CROSTINI_APP_INSTALL_DIALOG_PACKAGE_TEXT,
-      base::UTF8ToUTF16(package_info_.version), description);
-
-  auto message_label = std::make_unique<views::Label>(message);
-  message_label->SetMultiLine(true);
-  message_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
-
-  views::ScrollView* scroll_view = new views::ScrollView;
-  scroll_view->SetDrawOverflowIndicator(true);
-  scroll_view->ClipHeightTo(crostini::kMinScrollHeight,
-                            crostini::kMaxScrollHeight);
-  message_label_ = scroll_view->SetContents(std::move(message_label));
-
-  AddChildView(scroll_view);
-}
-
-int CrostiniAppInstallerView::GetDialogButtons() const {
-  return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
-}
-
-base::string16 CrostiniAppInstallerView::GetDialogButtonLabel(
-    ui::DialogButton button) const {
-  if (button == ui::DIALOG_BUTTON_OK)
-    return l10n_util::GetStringUTF16(
-        IDS_CROSTINI_APP_INSTALL_DIALOG_INSTALL_BUTTON);
-  DCHECK_EQ(button, ui::DIALOG_BUTTON_CANCEL);
-  return l10n_util::GetStringUTF16(IDS_APP_CANCEL);
-}
-
-base::string16 CrostiniAppInstallerView::GetWindowTitle() const {
-  return l10n_util::GetStringFUTF16(IDS_CROSTINI_APP_INSTALL_DIALOG_TITLE_TEXT,
-                                    base::UTF8ToUTF16(package_info_.name));
-}
-
-bool CrostiniAppInstallerView::ShouldShowCloseButton() const {
-  return false;
-}
-
-bool CrostiniAppInstallerView::Accept() {
-  // Start the installation process for the chosen Crosini app.
-
-  // TODO(https://crbug.com/921429): We should handle installation errors.
-  crostini::CrostiniPackageService::GetForProfile(profile_)
-      ->InstallLinuxPackageFromApt(crostini::kCrostiniDefaultVmName,
-                                   crostini::kCrostiniDefaultContainerName,
-                                   package_info_.package_id, base::DoNothing());
-  return true;
-}
-
-gfx::Size CrostiniAppInstallerView::CalculatePreferredSize() const {
-  const int dialog_width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-                               DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH) -
-                           margins().width();
-  return gfx::Size(dialog_width, GetHeightForWidth(dialog_width));
-}
-
-CrostiniAppInstallerView::~CrostiniAppInstallerView() = default;
diff --git a/chrome/browser/ui/views/crostini/crostini_app_installer_view.h b/chrome/browser/ui/views/crostini/crostini_app_installer_view.h
deleted file mode 100644
index c7fd9270..0000000
--- a/chrome/browser/ui/views/crostini/crostini_app_installer_view.h
+++ /dev/null
@@ -1,41 +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.
-
-#ifndef CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_APP_INSTALLER_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_APP_INSTALLER_VIEW_H_
-
-#include "chrome/browser/chromeos/crostini/crostini_manager.h"
-#include "ui/views/window/dialog_delegate.h"
-
-class Profile;
-
-// TODO(https://crbug.com/921429): Whole dialog needs a UX spec, this
-// iteration is based on some UX recommendations for this feature, but
-// a full spec will be required before bringing this feature to live.
-
-// The Crostini app installer view. Provides information about the app that a
-// user is looking to install and confirms if they want to install it or not.
-class CrostiniAppInstallerView : public views::DialogDelegateView {
- public:
-  CrostiniAppInstallerView(Profile* profile,
-                           const crostini::LinuxPackageInfo& package_info);
-
-  // views::DialogDelegateView:
-  int GetDialogButtons() const override;
-  base::string16 GetDialogButtonLabel(ui::DialogButton button) const override;
-  base::string16 GetWindowTitle() const override;
-  bool ShouldShowCloseButton() const override;
-  bool Accept() override;
-  gfx::Size CalculatePreferredSize() const override;
-
- private:
-  ~CrostiniAppInstallerView() override;
-
-  Profile* profile_;
-  crostini::LinuxPackageInfo package_info_;
-
-  DISALLOW_COPY_AND_ASSIGN(CrostiniAppInstallerView);
-};
-
-#endif  // CHROME_BROWSER_UI_VIEWS_CROSTINI_CROSTINI_APP_INSTALLER_VIEW_H_
diff --git a/chrome/browser/ui/views/crostini/crostini_app_installer_view_browsertest.cc b/chrome/browser/ui/views/crostini/crostini_app_installer_view_browsertest.cc
deleted file mode 100644
index 36b24d1..0000000
--- a/chrome/browser/ui/views/crostini/crostini_app_installer_view_browsertest.cc
+++ /dev/null
@@ -1,75 +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.
-
-#include "chrome/browser/ui/views/crostini/crostini_app_installer_view.h"
-
-#include "chrome/browser/chromeos/crostini/crostini_util.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
-#include "chrome/browser/ui/views/crostini/crostini_browser_test_util.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "ui/base/l10n/l10n_util.h"
-#include "ui/views/window/dialog_client_view.h"
-
-class CrostiniAppInstallerViewBrowserTest : public CrostiniDialogBrowserTest {
- public:
-  CrostiniAppInstallerViewBrowserTest()
-      : CrostiniDialogBrowserTest(false /*register_termina*/) {}
-
-  // CrostiniDialogBrowserTest:
-  void ShowUi(const std::string& name) override {
-    input_.success = true;
-    input_.package_id = "gedit;1.1;amd64;";
-    input_.name = "gedit";
-    input_.version = "1.1";
-    input_.description =
-        "A really good package for doing lots of interesting things";
-    input_.summary = "An editor";
-    view_ = new CrostiniAppInstallerView(browser()->profile(), input_);
-    views::DialogDelegate::CreateDialogWidget(view_, nullptr, nullptr)->Show();
-  }
-
-  CrostiniAppInstallerView* ActiveView() { return view_; }
-
-  bool HasAcceptButton() {
-    return ActiveView()->GetDialogClientView()->ok_button() != nullptr;
-  }
-
-  bool HasCancelButton() {
-    return ActiveView()->GetDialogClientView()->cancel_button() != nullptr;
-  }
-
- private:
-  crostini::LinuxPackageInfo input_;
-  CrostiniAppInstallerView* view_;
-  DISALLOW_COPY_AND_ASSIGN(CrostiniAppInstallerViewBrowserTest);
-};
-
-IN_PROC_BROWSER_TEST_F(CrostiniAppInstallerViewBrowserTest, InvokeUi_default) {
-  ShowAndVerifyUi();
-}
-
-IN_PROC_BROWSER_TEST_F(CrostiniAppInstallerViewBrowserTest, AcceptDialog) {
-  ShowUi("default");
-  EXPECT_NE(nullptr, ActiveView());
-
-  EXPECT_TRUE(HasAcceptButton());
-  EXPECT_TRUE(HasCancelButton());
-
-  ActiveView()->GetDialogClientView()->AcceptWindow();
-  EXPECT_TRUE(ActiveView()->GetWidget()->IsClosed());
-  // TODO(https://crbug.com/921429): Check to see if the installation process
-  // is handled appropiately.
-}
-
-IN_PROC_BROWSER_TEST_F(CrostiniAppInstallerViewBrowserTest, CancelDialog) {
-  ShowUi("default");
-  EXPECT_NE(nullptr, ActiveView());
-
-  EXPECT_TRUE(HasAcceptButton());
-  EXPECT_TRUE(HasCancelButton());
-
-  ActiveView()->GetDialogClientView()->CancelWindow();
-  EXPECT_TRUE(ActiveView()->GetWidget()->IsClosed());
-}
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_manager.cc b/chrome/browser/ui/web_applications/web_app_dialog_manager.cc
new file mode 100644
index 0000000..015b8112
--- /dev/null
+++ b/chrome/browser/ui/web_applications/web_app_dialog_manager.cc
@@ -0,0 +1,115 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
+
+#include "base/callback.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "chrome/browser/extensions/extension_uninstall_dialog.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/management_policy.h"
+
+namespace web_app {
+
+namespace {
+
+const extensions::Extension* GetExtension(Profile* profile,
+                                          const AppId& app_id) {
+  return extensions::ExtensionRegistry::Get(profile)->GetExtensionById(
+      app_id, extensions::ExtensionRegistry::EVERYTHING);
+}
+
+}  // namespace
+
+// TODO(loyso): Make it extensions independent.
+class DialogInstance : public extensions::ExtensionUninstallDialog::Delegate {
+ public:
+  DialogInstance() = default;
+  ~DialogInstance() override = default;
+
+  void UninstallWebApp(Profile* profile,
+                       BrowserWindow* parent_window,
+                       const AppId& app_id,
+                       WebAppDialogManager::UninstallSource source,
+                       WebAppDialogManager::Callback callback) {
+    uninstall_dialog_ = extensions::ExtensionUninstallDialog::Create(
+        profile, parent_window ? parent_window->GetNativeWindow() : nullptr,
+        this);
+
+    callback_ = std::move(callback);
+
+    auto* app = GetExtension(profile, app_id);
+    uninstall_dialog_->ConfirmUninstall(
+        app, extensions::UNINSTALL_REASON_USER_INITIATED,
+        ConvertSource(source));
+  }
+
+ private:
+  static extensions::UninstallSource ConvertSource(
+      WebAppDialogManager::UninstallSource source) {
+    switch (source) {
+      case WebAppDialogManager::UninstallSource::kAppMenu:
+        return extensions::UNINSTALL_SOURCE_HOSTED_APP_MENU;
+      case WebAppDialogManager::UninstallSource::kAppsPage:
+        return extensions::UNINSTALL_SOURCE_CHROME_APPS_PAGE;
+    }
+  }
+
+  // ExtensionUninstallDialog::Delegate:
+  void OnExtensionUninstallDialogClosed(bool success,
+                                        const base::string16& error) override {
+    // The dialog can be closed by UI system whenever it likes, but
+    // OnExtensionUninstallDialogClosed will be called anyway.
+    if (callback_)
+      std::move(callback_).Run(success);
+  }
+
+  std::unique_ptr<extensions::ExtensionUninstallDialog> uninstall_dialog_;
+  WebAppDialogManager::Callback callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(DialogInstance);
+};
+
+WebAppDialogManager::WebAppDialogManager(Profile* profile)
+    : profile_(profile) {}
+
+WebAppDialogManager::~WebAppDialogManager() = default;
+
+bool WebAppDialogManager::CanUninstallWebApp(const AppId& app_id) const {
+  auto* app = GetExtension(profile_, app_id);
+
+  return extensions::ExtensionSystem::Get(profile_)
+      ->management_policy()
+      ->UserMayModifySettings(app, nullptr);
+}
+
+void WebAppDialogManager::UninstallWebApp(const AppId& app_id,
+                                          UninstallSource uninstall_source,
+                                          BrowserWindow* parent_window,
+                                          Callback callback) {
+  auto dialog = std::make_unique<DialogInstance>();
+
+  dialog->UninstallWebApp(
+      profile_, parent_window, app_id, uninstall_source,
+      base::BindOnce(&WebAppDialogManager::OnDialogCompleted,
+                     base::Unretained(this), dialog.get(),
+                     std::move(callback)));
+
+  dialogs_.insert(std::move(dialog));
+}
+
+void WebAppDialogManager::OnDialogCompleted(DialogInstance* dialog,
+                                            Callback callback,
+                                            bool success) {
+  DCHECK(dialogs_.contains(dialog));
+  dialogs_.erase(dialog);
+
+  std::move(callback).Run(success);
+}
+
+}  // namespace web_app
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_manager.h b/chrome/browser/ui/web_applications/web_app_dialog_manager.h
new file mode 100644
index 0000000..caa4ac4
--- /dev/null
+++ b/chrome/browser/ui/web_applications/web_app_dialog_manager.h
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_DIALOG_MANAGER_H_
+#define CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_DIALOG_MANAGER_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_set.h"
+#include "base/containers/unique_ptr_adapters.h"
+#include "base/macros.h"
+#include "chrome/browser/web_applications/components/web_app_helpers.h"
+
+class BrowserWindow;
+class Profile;
+
+namespace web_app {
+
+// An internal WebAppDialogManager's representation of any running dialog.
+class DialogInstance;
+
+class WebAppDialogManager {
+ public:
+  explicit WebAppDialogManager(Profile* profile);
+  ~WebAppDialogManager();
+
+  enum class UninstallSource {
+    kAppMenu,
+    kAppsPage,
+  };
+
+  using Callback = base::OnceCallback<void(bool success)>;
+
+  bool CanUninstallWebApp(const AppId& app_id) const;
+  // The uninstall dialog will be modal to |parent_window|, or a non-modal if
+  // |parent_window| is nullptr.
+  void UninstallWebApp(const AppId& app_id,
+                       UninstallSource uninstall_source,
+                       BrowserWindow* parent_window,
+                       Callback callback);
+
+ private:
+  void OnDialogCompleted(DialogInstance* dialog,
+                         Callback callback,
+                         bool success);
+
+  // All owned dialogs, running in parallel.
+  base::flat_set<std::unique_ptr<DialogInstance>, base::UniquePtrComparator>
+      dialogs_;
+
+  Profile* profile_;
+
+  DISALLOW_COPY_AND_ASSIGN(WebAppDialogManager);
+};
+
+}  // namespace web_app
+
+#endif  // CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_DIALOG_MANAGER_H_
diff --git a/chrome/browser/ui/web_applications/web_app_dialog_utils.h b/chrome/browser/ui/web_applications/web_app_dialog_utils.h
index 41159af..a018f298 100644
--- a/chrome/browser/ui/web_applications/web_app_dialog_utils.h
+++ b/chrome/browser/ui/web_applications/web_app_dialog_utils.h
@@ -20,7 +20,7 @@
 enum class InstallResultCode;
 
 // TODO(loyso): Rework these functions (API) once BookmarkAppHelper erased.
-// crbug.com/915043.
+// crbug.com/915043. Move all of them into WebAppDialogManager.
 
 // Returns true if a WebApp installation is allowed for the current page.
 bool CanCreateWebApp(const Browser* browser);
diff --git a/chrome/browser/ui/web_applications/web_app_ui_service.cc b/chrome/browser/ui/web_applications/web_app_ui_service.cc
index f425519..dc95e29 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_service.cc
+++ b/chrome/browser/ui/web_applications/web_app_ui_service.cc
@@ -10,6 +10,7 @@
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/web_applications/app_browser_controller.h"
+#include "chrome/browser/ui/web_applications/web_app_dialog_manager.h"
 #include "chrome/browser/ui/web_applications/web_app_ui_service_factory.h"
 #include "chrome/browser/web_applications/system_web_app_manager.h"
 #include "chrome/browser/web_applications/web_app_provider.h"
@@ -39,6 +40,8 @@
 
   BrowserList::AddObserver(this);
   provider_->set_ui_delegate(this);
+
+  dialog_manager_ = std::make_unique<WebAppDialogManager>(profile);
 }
 
 WebAppUiService::~WebAppUiService() {
diff --git a/chrome/browser/ui/web_applications/web_app_ui_service.h b/chrome/browser/ui/web_applications/web_app_ui_service.h
index 810dd4ce..5bf41cc 100644
--- a/chrome/browser/ui/web_applications/web_app_ui_service.h
+++ b/chrome/browser/ui/web_applications/web_app_ui_service.h
@@ -6,6 +6,7 @@
 #define CHROME_BROWSER_UI_WEB_APPLICATIONS_WEB_APP_UI_SERVICE_H_
 
 #include <map>
+#include <memory>
 #include <vector>
 
 #include "base/callback_forward.h"
@@ -22,6 +23,7 @@
 namespace web_app {
 
 class WebAppProvider;
+class WebAppDialogManager;
 
 // This KeyedService is a UI counterpart for WebAppProvider.
 class WebAppUiService : public KeyedService,
@@ -36,6 +38,8 @@
   // KeyedService
   void Shutdown() override;
 
+  WebAppDialogManager& dialog_manager() { return *dialog_manager_; }
+
   // WebAppUiDelegate
   size_t GetNumWindowsForApp(const AppId& app_id) override;
   void NotifyOnAllAppWindowsClosed(const AppId& app_id,
@@ -49,6 +53,8 @@
  private:
   base::Optional<AppId> GetAppIdForBrowser(Browser* browser);
 
+  std::unique_ptr<WebAppDialogManager> dialog_manager_;
+
   WebAppProvider* provider_;
   Profile* profile_;
 
diff --git a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc
index e0d749d..baa7800 100644
--- a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_handler.cc
@@ -71,7 +71,8 @@
 
 void AddSupervisionHandler::GetOAuthToken(GetOAuthTokenCallback callback) {
   identity::ScopeSet scopes;
-  scopes.insert(GaiaConstants::kKidFamilyOAuth2Scope);
+  scopes.insert(GaiaConstants::kKidsSupervisionSetupChildOAuth2Scope);
+  scopes.insert(GaiaConstants::kPeopleApiReadOnlyOAuth2Scope);
 
   oauth2_access_token_fetcher_ =
       identity_manager_->CreateAccessTokenFetcherForAccount(
diff --git a/chrome/browser/ui/webui/signin/signin_utils_desktop.cc b/chrome/browser/ui/webui/signin/signin_utils_desktop.cc
index e7a7e8c..b907f5a 100644
--- a/chrome/browser/ui/webui/signin/signin_utils_desktop.cc
+++ b/chrome/browser/ui/webui/signin/signin_utils_desktop.cc
@@ -47,8 +47,7 @@
 
     // Make sure this username is not prohibited by policy.
     if (!identity::IsUsernameAllowedByPatternFromPrefs(
-            g_browser_process->local_state(), email,
-            prefs::kGoogleServicesUsernamePattern)) {
+            g_browser_process->local_state(), email)) {
       if (error_message) {
         error_message->assign(
             l10n_util::GetStringUTF8(IDS_SYNC_LOGIN_NAME_PROHIBITED));
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 35dd728a..f8ea981 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -184,11 +184,6 @@
 const base::Feature kCrostiniAdditionalEnterpriseReporting{
     "CrostiniAdditionalEnterpriseReporting", base::FEATURE_DISABLED_BY_DEFAULT};
 
-// Enables not installed apps/packages to be searched for and installed in the
-// App launcher.
-const base::Feature kCrostiniAppSearch{"CrostiniAppSearch",
-                                       base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Enables an uninstall option in the right-click menu of Crostini (Linux)
 // applications.
 // TODO(crbug.com/955797): Remove this flag entirely.
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 2f6fc351..b7c49424 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -114,8 +114,6 @@
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kCrostiniAdditionalEnterpriseReporting;
 COMPONENT_EXPORT(CHROME_FEATURES)
-extern const base::Feature kCrostiniAppSearch;
-COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kCrostiniAppUninstallGui;
 COMPONENT_EXPORT(CHROME_FEATURES)
 extern const base::Feature kCrostiniAnsibleInfrastructure;
diff --git a/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc b/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc
index c535284..397c3f7 100644
--- a/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc
+++ b/chrome/common/extensions/manifest_handlers/natively_connectable_handler.cc
@@ -51,7 +51,7 @@
 
   auto hosts = std::make_unique<NativelyConnectableHosts>();
   for (const auto& host : natively_connectable_hosts->GetList()) {
-    if (!host.is_string()) {
+    if (!host.is_string() || host.GetString().empty()) {
       *error =
           base::ASCIIToUTF16(manifest_errors::kInvalidNativelyConnectableValue);
       return false;
diff --git a/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc b/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc
index fa6f1f3..67e5990 100644
--- a/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc
+++ b/chrome/common/extensions/manifest_handlers/natively_connectable_handler_unittest.cc
@@ -49,4 +49,9 @@
                      manifest_errors::kInvalidNativelyConnectableValue);
 }
 
+TEST_F(NativelyConnectableManifestTest, EmptyHost) {
+  LoadAndExpectError("natively_connectable_empty_host.json",
+                     manifest_errors::kInvalidNativelyConnectableValue);
+}
+
 }  // namespace extensions
diff --git a/chrome/credential_provider/gaiacp/os_user_manager.cc b/chrome/credential_provider/gaiacp/os_user_manager.cc
index ed45672..033478c8 100644
--- a/chrome/credential_provider/gaiacp/os_user_manager.cc
+++ b/chrome/credential_provider/gaiacp/os_user_manager.cc
@@ -361,12 +361,13 @@
     flags_changed = true;
   }
 
-  base::string16 password_domain = base::StringPrintf(L"\\\\%ls", domain);
+  base::string16 password_domain = base::StringPrintf(L"%ls", domain);
 
   NET_API_STATUS changepassword_nsts = ::NetUserChangePassword(
       password_domain.c_str(), username, old_password, new_password);
   if (changepassword_nsts != NERR_Success) {
     LOGFN(ERROR) << "Unable to change password for '" << username
+                 << "' domain '" << password_domain
                  << "' nsts=" << changepassword_nsts;
   }
 
diff --git a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
index d5875e7..64206e9 100644
--- a/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
+++ b/chrome/renderer/resources/extensions/file_manager_private_custom_bindings.js
@@ -54,7 +54,7 @@
 
     // So |callback| doesn't break if response is not defined.
     if (!response)
-      response = {};
+      response = [];
 
     if (callback)
       callback(response);
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 8fcd94db..2775d4e 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -939,6 +939,8 @@
       "../browser/net/nss_context_chromeos_browsertest.cc",
       "../browser/net/profile_network_context_service_browsertest.cc",
       "../browser/net/proxy_browsertest.cc",
+      "../browser/net/proxy_test_utils.cc",
+      "../browser/net/proxy_test_utils.h",
       "../browser/net/reporting_browsertest.cc",
       "../browser/net/system_network_context_manager_browsertest.cc",
       "../browser/net/variations_http_headers_browsertest.cc",
@@ -2171,7 +2173,6 @@
         "../browser/ui/ash/volume_controller_browsertest.cc",
         "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
         "../browser/ui/views/arc_app_dialog_view_browsertest.cc",
-        "../browser/ui/views/crostini/crostini_app_installer_view_browsertest.cc",
         "../browser/ui/views/crostini/crostini_browser_test_util.cc",
         "../browser/ui/views/crostini/crostini_browser_test_util.h",
         "../browser/ui/views/crostini/crostini_installer_view_browsertest.cc",
@@ -4358,6 +4359,7 @@
         "../browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_unittest.cc",
         "../browser/extensions/api/messaging/native_message_process_host_unittest.cc",
         "../browser/extensions/api/messaging/native_messaging_host_manifest_unittest.cc",
+        "../browser/extensions/api/messaging/native_messaging_launch_from_native_unittest.cc",
         "../browser/extensions/api/messaging/native_messaging_policy_handler_unittest.cc",
       ]
     }
diff --git a/chrome/test/chromedriver/chrome/navigation_tracker.cc b/chrome/test/chromedriver/chrome/navigation_tracker.cc
index bf833fa..861f12a 100644
--- a/chrome/test/chromedriver/chrome/navigation_tracker.cc
+++ b/chrome/test/chromedriver/chrome/navigation_tracker.cc
@@ -195,6 +195,8 @@
     }
   } else if (method == "Inspector.targetCrashed") {
     loading_state_ = kNotLoading;
+  } else if (method == "Page.interstitialShown") {
+    client_->SendCommand("Page.stopLoading", base::DictionaryValue());
   }
   if (timed_out_)
     loading_state_ = kNotLoading;
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 6d45925d..7695253 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -12,6 +12,7 @@
 
 #include "base/callback.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "base/strings/utf_string_conversions.h"
@@ -1918,6 +1919,11 @@
   Status status = GetUrl(web_view, session->GetCurrentFrameId(), &url);
   if (status.IsError())
     return status;
+  if (!base::StartsWith(url, "http://", base::CompareCase::INSENSITIVE_ASCII) &&
+      !base::StartsWith(url, "https://",
+                        base::CompareCase::INSENSITIVE_ASCII) &&
+      !base::StartsWith(url, "ftp://", base::CompareCase::INSENSITIVE_ASCII))
+    return Status(kInvalidCookieDomain);
   std::string domain;
   if (!GetOptionalString(cookie, "domain", &domain))
     return Status(kInvalidArgument, "invalid 'domain'");
diff --git a/chrome/test/data/extensions/api_test/native_messaging_launch/manifest.json b/chrome/test/data/extensions/api_test/native_messaging_launch/manifest.json
new file mode 100644
index 0000000..2216581f6
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/native_messaging_launch/manifest.json
@@ -0,0 +1,20 @@
+{
+  // Extension ID: knldjmfmopnpolahpmmgbagdohdnhkik
+  "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB",
+  "version": "1.0.0.0",
+  "manifest_version": 2,
+  "name": "native messaging test",
+  "description": "Test the basic functionality of passing native messages.",
+  "background": {
+    "scripts": ["test.js"],
+    "persistent": false
+  },
+  "natively_connectable": [
+    "com.google.chrome.test.echo",
+    "com.google.chrome.test.inbound_native_echo"
+  ],
+  "permissions": [
+    "nativeMessaging",
+    "transientBackground"
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/native_messaging_launch/test.js b/chrome/test/data/extensions/api_test/native_messaging_launch/test.js
new file mode 100644
index 0000000..ed8e5e2
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/native_messaging_launch/test.js
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var appName = 'com.google.chrome.test.inbound_native_echo';
+
+chrome.runtime.onConnectNative.addListener(port => {
+  chrome.test.getConfig(function(config) {
+    chrome.test.runTests([
+      function sender() {
+        chrome.test.assertEq(port.sender.nativeApplication, appName);
+        chrome.test.succeed();
+      },
+
+      function sendMessages() {
+        var messagesToSend =
+            [{'text': 'foo'}, {'text': 'bar', 'funCount': 9001}, {}];
+        var currentMessage = 0;
+
+        port.postMessage(messagesToSend[currentMessage]);
+
+        function onMessage(message) {
+          chrome.test.assertEq(currentMessage + 1, message.id);
+          chrome.test.assertEq(messagesToSend[currentMessage], message.echo);
+          chrome.test.assertEq(
+              message.caller_url, window.location.origin + '/');
+          currentMessage++;
+
+          if (currentMessage == messagesToSend.length) {
+            port.onMessage.removeListener(onMessage);
+            chrome.test.succeed();
+          } else {
+            port.postMessage(messagesToSend[currentMessage]);
+          }
+        };
+        port.onMessage.addListener(onMessage);
+      },
+
+      // Verify that the case when host stops itself is handled properly.
+      function stopHost() {
+        port.onDisconnect.addListener(
+            chrome.test.callback(function() {}, 'Native host has exited.'));
+
+        // Send first message that should stop the host.
+        port.postMessage({'stopHostTest': true});
+      }
+    ]);
+  });
+});
diff --git a/chrome/test/data/extensions/api_test/native_messaging_launch_unsupported/manifest.json b/chrome/test/data/extensions/api_test/native_messaging_launch_unsupported/manifest.json
new file mode 100644
index 0000000..2216581f6
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/native_messaging_launch_unsupported/manifest.json
@@ -0,0 +1,20 @@
+{
+  // Extension ID: knldjmfmopnpolahpmmgbagdohdnhkik
+  "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcBHwzDvyBQ6bDppkIs9MP4ksKqCMyXQ/A52JivHZKh4YO/9vJsT3oaYhSpDCE9RPocOEQvwsHsFReW2nUEc6OLLyoCFFxIb7KkLGsmfakkut/fFdNJYh0xOTbSN8YvLWcqph09XAY2Y/f0AL7vfO1cuCqtkMt8hFrBGWxDdf9CQIDAQAB",
+  "version": "1.0.0.0",
+  "manifest_version": 2,
+  "name": "native messaging test",
+  "description": "Test the basic functionality of passing native messages.",
+  "background": {
+    "scripts": ["test.js"],
+    "persistent": false
+  },
+  "natively_connectable": [
+    "com.google.chrome.test.echo",
+    "com.google.chrome.test.inbound_native_echo"
+  ],
+  "permissions": [
+    "nativeMessaging",
+    "transientBackground"
+  ]
+}
diff --git a/chrome/test/data/extensions/api_test/native_messaging_launch_unsupported/test.js b/chrome/test/data/extensions/api_test/native_messaging_launch_unsupported/test.js
new file mode 100644
index 0000000..5ee4b0f4
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/native_messaging_launch_unsupported/test.js
@@ -0,0 +1,16 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+var appName = 'com.google.chrome.test.echo';
+
+chrome.runtime.onConnectNative.addListener(port => {
+  chrome.test.runTests([
+    function test() {
+      chrome.test.assertEq(port.sender.nativeApplication, appName);
+      port.onDisconnect.addListener(chrome.test.callback(
+          function() {},
+          'Access to the specified native messaging host is forbidden.'));
+    },
+  ]);
+});
diff --git a/chrome/test/data/extensions/api_test/proxy/events/parse_error.js b/chrome/test/data/extensions/api_test/proxy/events/parse_error.js
index 0b0079f..9d784e1 100644
--- a/chrome/test/data/extensions/api_test/proxy/events/parse_error.js
+++ b/chrome/test/data/extensions/api_test/proxy/events/parse_error.js
@@ -5,17 +5,7 @@
 // proxy api test
 // browser_tests.exe --gtest_filter=ProxySettingsApiTest.ProxyEventsParseError
 
-// TODO(crbug.com/943636): remove old error once
-// https://chromium-review.googlesource.com/c/v8/v8/+/1593307 is rolled into
-// chromium.
-
-var expected_error_v1 = {
-    details: "line: 1: Uncaught SyntaxError: Unexpected token !",
-    error: "net::ERR_PAC_SCRIPT_FAILED",
-    fatal: false
-};
-
-var expected_error_v2 = {
+var expected_error = {
     details: "line: 1: Uncaught SyntaxError: Unexpected token '!'",
     error: "net::ERR_PAC_SCRIPT_FAILED",
     fatal: false
@@ -24,12 +14,7 @@
 function test() {
   // Install error handler and get the test server config.
   chrome.proxy.onProxyError.addListener(function (error) {
-    const actualErrorJson = JSON.stringify(error);
-    chrome.test.assertTrue(
-      actualErrorJson == JSON.stringify(expected_error_v1) ||
-      actualErrorJson == JSON.stringify(expected_error_v2),
-      actualErrorJson
-    );
+    chrome.test.assertEq(expected_error, error);
     chrome.test.notifyPass();
   });
 
diff --git a/chrome/test/data/extensions/manifest_tests/natively_connectable_empty_host.json b/chrome/test/data/extensions/manifest_tests/natively_connectable_empty_host.json
new file mode 100644
index 0000000..fdbd0f2a
--- /dev/null
+++ b/chrome/test/data/extensions/manifest_tests/natively_connectable_empty_host.json
@@ -0,0 +1,8 @@
+{
+  "name": "unit_tests --gtest_filter=NativelyConnectableManifestTest.EmptyHost",
+  "version": "1",
+  "manifest_version": 2,
+  "natively_connectable": [
+    ""
+  ]
+}
diff --git a/chrome/test/data/webui/app_management/test_util.js b/chrome/test/data/webui/app_management/test_util.js
index 536bd08e..fb3e753b 100644
--- a/chrome/test/data/webui/app_management/test_util.js
+++ b/chrome/test/data/webui/app_management/test_util.js
@@ -19,7 +19,7 @@
  */
 function setupFakeHandler() {
   const browserProxy = app_management.BrowserProxy.getInstance();
-  const callbackRouterProxy = browserProxy.callbackRouter.createProxy();
+  const callbackRouterProxy = browserProxy.callbackRouter.$.createProxy();
 
   const fakeHandler = new app_management.FakePageHandler(callbackRouterProxy);
   browserProxy.handler = fakeHandler;
diff --git a/chrome/test/data/webui/bluetooth_internals_browsertest.js b/chrome/test/data/webui/bluetooth_internals_browsertest.js
index 08690f4a..7462fa5db 100644
--- a/chrome/test/data/webui/bluetooth_internals_browsertest.js
+++ b/chrome/test/data/webui/bluetooth_internals_browsertest.js
@@ -57,7 +57,7 @@
 
       async getAdapter() {
         this.methodCalled('getAdapter');
-        return {adapter: this.adapterBinding_.createProxy()};
+        return {adapter: this.adapterBinding_.$.createProxy()};
       }
     }
 
@@ -82,7 +82,7 @@
         assert(this.deviceProxyMap.has(address), 'Device does not exist');
         return {
           result: this.connectResult_,
-          device: this.deviceProxyMap.get(address).router.createProxy(),
+          device: this.deviceProxyMap.get(address).router.$.createProxy(),
         };
       }
 
@@ -140,7 +140,7 @@
         // lots of methods we don't care to mock here. DeviceCallbackRouter
         // callback silently discards messages that have no listeners.
         this.router = new bluetooth.mojom.DeviceCallbackRouter;
-        this.router.disconnect.addListener(() => this.router.closeBindings());
+        this.router.disconnect.addListener(() => this.router.$.close());
         this.router.getInfo.addListener(() => this.getInfo());
         this.router.getServices.addListener(() => this.getServices());
       }
@@ -167,7 +167,7 @@
         this.adapterFactory = new TestAdapterFactoryProxy();
         this.adapterFactoryBinding_ =
             new mojom.BluetoothInternalsHandler(this.adapterFactory);
-        this.adapterFactoryBinding_.bindHandle(e.handle);
+        this.adapterFactoryBinding_.$.bindHandle(e.handle);
 
         this.adapterFactory.adapter.setTestDevices([
           this.fakeDeviceInfo1(),
diff --git a/chrome/test/data/webui/downloads/test_support.js b/chrome/test/data/webui/downloads/test_support.js
index e5757090e..a64334e 100644
--- a/chrome/test/data/webui/downloads/test_support.js
+++ b/chrome/test/data/webui/downloads/test_support.js
@@ -8,7 +8,7 @@
     this.callbackRouter = new downloads.mojom.PageCallbackRouter();
 
     /** @type {!downloads.mojom.PageInterface} */
-    this.pageRouterProxy = this.callbackRouter.createProxy();
+    this.pageRouterProxy = this.callbackRouter.$.createProxy();
 
     /** @type {downloads.mojom.PageHandlerInterface} */
     this.handler = new TestDownloadsMojoHandler(this.pageRouterProxy);
diff --git a/chrome/test/data/webui/usb_internals_browsertest.js b/chrome/test/data/webui/usb_internals_browsertest.js
index c64c027..5b31d2e 100644
--- a/chrome/test/data/webui/usb_internals_browsertest.js
+++ b/chrome/test/data/webui/usb_internals_browsertest.js
@@ -56,7 +56,7 @@
         this.deviceManager = new FakeDeviceManagerProxy();
         this.deviceManagerBinding_ =
             new device.mojom.UsbDeviceManager(this.deviceManager);
-        this.deviceManagerBinding_.bindHandle(deviceManagerRequest.handle);
+        this.deviceManagerBinding_.$.bindHandle(deviceManagerRequest.handle);
       }
 
       async bindTestInterface(testDeviceManagerRequest) {
@@ -99,7 +99,7 @@
       async getDevice(guid, deviceRequest, deviceClient) {
         this.methodCalled('getDevice');
         const deviceProxy = this.deviceProxyMap.get(guid);
-        deviceProxy.router.bindHandle(deviceRequest.handle);
+        deviceProxy.router.$.bindHandle(deviceRequest.handle);
       }
 
       async getDevices() {
@@ -271,7 +271,7 @@
         this.pageHandler = new FakePageHandlerProxy();
         this.pageHandlerBinding_ =
             new mojom.UsbInternalsPageHandler(this.pageHandler);
-        this.pageHandlerBinding_.bindHandle(e.handle);
+        this.pageHandlerBinding_.$.bindHandle(e.handle);
       };
       this.pageHandlerInterceptor.start();
 
diff --git a/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc b/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc
index a165e07..319161e 100644
--- a/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc
+++ b/chromecast/media/cma/base/demuxer_stream_adapter_unittest.cc
@@ -149,7 +149,7 @@
 
 void DemuxerStreamAdapterTest::OnFlushCompleted() {
   ASSERT_EQ(frame_received_count_, total_expected_frames_);
-  ASSERT_FALSE(demuxer_stream_->has_pending_read());
+  ASSERT_FALSE(demuxer_stream_->IsReadPending());
   base::RunLoop::QuitCurrentWhenIdleDeprecated();
 }
 
diff --git a/chromecast/media/cma/base/demuxer_stream_for_test.cc b/chromecast/media/cma/base/demuxer_stream_for_test.cc
index 40270bd..9c585ae 100644
--- a/chromecast/media/cma/base/demuxer_stream_for_test.cc
+++ b/chromecast/media/cma/base/demuxer_stream_for_test.cc
@@ -71,6 +71,10 @@
   return true;
 }
 
+bool DemuxerStreamForTest::IsReadPending() const {
+  return has_pending_read_;
+}
+
 void DemuxerStreamForTest::DoRead(const ReadCB& read_cb) {
   has_pending_read_ = false;
 
diff --git a/chromecast/media/cma/base/demuxer_stream_for_test.h b/chromecast/media/cma/base/demuxer_stream_for_test.h
index 81966a4..feec5ca5 100644
--- a/chromecast/media/cma/base/demuxer_stream_for_test.h
+++ b/chromecast/media/cma/base/demuxer_stream_for_test.h
@@ -46,8 +46,7 @@
   ::media::VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
   bool SupportsConfigChanges() override;
-
-  bool has_pending_read() const { return has_pending_read_; }
+  bool IsReadPending() const override;
 
   // Frame duration
   static const int kDemuxerStreamForTestFrameDuration = 40;
diff --git a/chromeos/components/drivefs/drivefs_auth.cc b/chromeos/components/drivefs/drivefs_auth.cc
index 1940b94..296f0b7 100644
--- a/chromeos/components/drivefs/drivefs_auth.cc
+++ b/chromeos/components/drivefs/drivefs_auth.cc
@@ -18,8 +18,13 @@
 
 DriveFsAuth::DriveFsAuth(const base::Clock* clock,
                          const base::FilePath& profile_path,
+                         std::unique_ptr<base::OneShotTimer> timer,
                          Delegate* delegate)
-    : clock_(clock), profile_path_(profile_path), delegate_(delegate) {}
+    : clock_(clock),
+      profile_path_(profile_path),
+      timer_(std::move(timer)),
+      delegate_(delegate),
+      weak_ptr_factory_(this) {}
 
 DriveFsAuth::~DriveFsAuth() {}
 
@@ -47,12 +52,18 @@
   }
 
   get_access_token_callback_ = std::move(callback);
-  GetIdentityAccessor().GetPrimaryAccountWhenAvailable(
-      base::BindOnce(&DriveFsAuth::AccountReady, base::Unretained(this)));
+  timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(30),
+                base::BindOnce(&DriveFsAuth::AuthTimeout,
+                               weak_ptr_factory_.GetWeakPtr()));
+  GetIdentityAccessor().GetPrimaryAccountWhenAvailable(base::BindOnce(
+      &DriveFsAuth::AccountReady, weak_ptr_factory_.GetWeakPtr()));
 }
 
 void DriveFsAuth::AccountReady(const CoreAccountInfo& info,
                                const identity::AccountState& state) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  timer_->Stop();
   GetIdentityAccessor().GetAccessToken(
       delegate_->GetAccountId().GetUserEmail(),
       {"https://www.googleapis.com/auth/drive"}, kIdentityConsumerId,
@@ -91,6 +102,13 @@
   last_token_expiry_ = expiry;
 }
 
+void DriveFsAuth::AuthTimeout() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  std::move(get_access_token_callback_)
+      .Run(mojom::AccessTokenStatus::kAuthError, "");
+}
+
 identity::mojom::IdentityAccessor& DriveFsAuth::GetIdentityAccessor() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   if (!identity_accessor_) {
diff --git a/chromeos/components/drivefs/drivefs_auth.h b/chromeos/components/drivefs/drivefs_auth.h
index 49d2c58..94d4c6cc 100644
--- a/chromeos/components/drivefs/drivefs_auth.h
+++ b/chromeos/components/drivefs/drivefs_auth.h
@@ -11,7 +11,9 @@
 
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/time/clock.h"
+#include "base/timer/timer.h"
 #include "chromeos/components/drivefs/mojom/drivefs.mojom.h"
 #include "services/identity/public/mojom/identity_accessor.mojom.h"
 
@@ -47,6 +49,7 @@
 
   DriveFsAuth(const base::Clock* clock,
               const base::FilePath& profile_path,
+              std::unique_ptr<base::OneShotTimer> timer,
               Delegate* delegate);
   virtual ~DriveFsAuth();
 
@@ -80,11 +83,14 @@
 
   void UpdateCachedToken(const std::string& token, base::Time expiry);
 
+  void AuthTimeout();
+
   identity::mojom::IdentityAccessor& GetIdentityAccessor();
 
   SEQUENCE_CHECKER(sequence_checker_);
   const base::Clock* const clock_;
   const base::FilePath profile_path_;
+  const std::unique_ptr<base::OneShotTimer> timer_;
   Delegate* const delegate_;
 
   // The connection to the identity service. Access via |GetIdentityAccessor()|.
@@ -96,6 +102,7 @@
   std::string last_token_;
   base::Time last_token_expiry_;
 
+  base::WeakPtrFactory<DriveFsAuth> weak_ptr_factory_;
   DISALLOW_COPY_AND_ASSIGN(DriveFsAuth);
 };
 
diff --git a/chromeos/components/drivefs/drivefs_auth_unittest.cc b/chromeos/components/drivefs/drivefs_auth_unittest.cc
index f5f11ce..7eeecdda 100644
--- a/chromeos/components/drivefs/drivefs_auth_unittest.cc
+++ b/chromeos/components/drivefs/drivefs_auth_unittest.cc
@@ -11,6 +11,7 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/scoped_task_environment.h"
 #include "base/test/simple_test_clock.h"
+#include "base/timer/mock_timer.h"
 #include "services/identity/public/mojom/constants.mojom.h"
 #include "services/identity/public/mojom/identity_accessor.mojom-test-utils.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -85,6 +86,8 @@
 
   ~FakeIdentityService() override { mock_->bindings_ = nullptr; }
 
+  void set_auth_enabled(bool enabled) { auth_enabled_ = enabled; }
+
  private:
   void OnBindInterface(const service_manager::BindSourceInfo& source,
                        const std::string& interface_name,
@@ -100,6 +103,9 @@
   // identity::mojom::IdentityAccessorInterceptorForTesting overrides:
   void GetPrimaryAccountWhenAvailable(
       GetPrimaryAccountWhenAvailableCallback callback) override {
+    if (!auth_enabled_) {
+      return;
+    }
     auto account_id = AccountId::FromUserEmailGaiaId("test@example.com", "ID");
     CoreAccountInfo account_info;
     account_info.email = account_id.GetUserEmail();
@@ -128,6 +134,7 @@
   service_manager::ServiceBinding binding_;
   service_manager::BinderRegistry binder_registry_;
   mojo::BindingSet<identity::mojom::IdentityAccessor> bindings_;
+  bool auth_enabled_ = true;
 
   DISALLOW_COPY_AND_ASSIGN(FakeIdentityService);
 };
@@ -143,13 +150,19 @@
     identity_service_ = std::make_unique<FakeIdentityService>(
         &mock_identity_accessor_, &clock_,
         connector_factory_.RegisterInstance(identity::mojom::kServiceName));
+    auto timer = std::make_unique<base::MockOneShotTimer>();
+    timer_ = timer.get();
     delegate_ = std::make_unique<AuthDelegateImpl>(
         connector_factory_.CreateConnector(), account_id_);
-    auth_ = std::make_unique<DriveFsAuth>(
-        &clock_, base::FilePath("/path/to/profile"), delegate_.get());
+    auth_ = std::make_unique<DriveFsAuth>(&clock_,
+                                          base::FilePath("/path/to/profile"),
+                                          std::move(timer), delegate_.get());
   }
 
-  void TearDown() override { auth_.reset(); }
+  void TearDown() override {
+    EXPECT_FALSE(timer_->IsRunning());
+    auth_.reset();
+  }
 
   void ExpectAccessToken(bool use_cached,
                          mojom::AccessTokenStatus expected_status,
@@ -176,6 +189,7 @@
 
   std::unique_ptr<AuthDelegateImpl> delegate_;
   std::unique_ptr<DriveFsAuth> auth_;
+  base::MockOneShotTimer* timer_ = nullptr;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(DriveFsAuthTest);
@@ -205,20 +219,39 @@
   ExpectAccessToken(false, mojom::AccessTokenStatus::kTransientError, "");
 }
 
-TEST_F(DriveFsAuthTest, GetAccessToken_ParallelRequests) {
+TEST_F(DriveFsAuthTest, GetAccessToken_GetAccessTokenFailure_Timeout) {
+  identity_service_->set_auth_enabled(false);
   base::RunLoop run_loop;
   auto quit_closure = run_loop.QuitClosure();
   auth_->GetAccessToken(
-      false, base::BindOnce(
-                 [](mojom::AccessTokenStatus status, const std::string& token) {
-                   FAIL() << "Unexpected callback";
+      false, base::BindLambdaForTesting(
+                 [&](mojom::AccessTokenStatus status, const std::string&) {
+                   EXPECT_EQ(mojom::AccessTokenStatus::kAuthError, status);
+                   std::move(quit_closure).Run();
                  }));
+  timer_->Fire();
+  run_loop.Run();
+}
+
+TEST_F(DriveFsAuthTest, GetAccessToken_ParallelRequests) {
+  base::RunLoop run_loop;
+  EXPECT_CALL(mock_identity_accessor_,
+              GetAccessToken("test@example.com", _, "drivefs"))
+      .WillOnce(testing::Return(
+          std::make_pair("auth token", GoogleServiceAuthError::NONE)));
+  auto quit_closure = run_loop.QuitClosure();
+  auth_->GetAccessToken(
+      false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
+                                            const std::string& token) {
+        EXPECT_EQ(mojom::AccessTokenStatus::kSuccess, status);
+        EXPECT_EQ("auth token", token);
+        std::move(quit_closure).Run();
+      }));
   auth_->GetAccessToken(
       false, base::BindLambdaForTesting([&](mojom::AccessTokenStatus status,
                                             const std::string& token) {
         EXPECT_EQ(mojom::AccessTokenStatus::kTransientError, status);
         EXPECT_TRUE(token.empty());
-        std::move(quit_closure).Run();
       }));
   run_loop.Run();
 }
diff --git a/chromeos/components/drivefs/drivefs_host.cc b/chromeos/components/drivefs/drivefs_host.cc
index 17219ba..b61bf6c8 100644
--- a/chromeos/components/drivefs/drivefs_host.cc
+++ b/chromeos/components/drivefs/drivefs_host.cc
@@ -190,7 +190,10 @@
       disk_mount_manager_(disk_mount_manager),
       timer_(std::move(timer)),
       account_token_delegate_(
-          std::make_unique<DriveFsAuth>(clock, profile_path, delegate)) {
+          std::make_unique<DriveFsAuth>(clock,
+                                        profile_path,
+                                        std::make_unique<base::OneShotTimer>(),
+                                        delegate)) {
   DCHECK(delegate_);
   DCHECK(mount_observer_);
   DCHECK(network_connection_tracker_);
diff --git a/chromeos/dbus/cicerone_client.cc b/chromeos/dbus/cicerone_client.cc
index 4f6527d..3f21447 100644
--- a/chromeos/dbus/cicerone_client.cc
+++ b/chromeos/dbus/cicerone_client.cc
@@ -321,27 +321,6 @@
                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   }
 
-  void SearchApp(const vm_tools::cicerone::AppSearchRequest& request,
-                 DBusMethodCallback<vm_tools::cicerone::AppSearchResponse>
-                     callback) override {
-    dbus::MethodCall method_call(vm_tools::cicerone::kVmCiceroneInterface,
-                                 vm_tools::cicerone::kAppSearchMethod);
-    dbus::MessageWriter writer(&method_call);
-
-    if (!writer.AppendProtoAsArrayOfBytes(request)) {
-      LOG(ERROR) << "Failed to encode AppSearchRequest protobuf";
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), base::nullopt));
-      return;
-    }
-
-    cicerone_proxy_->CallMethod(
-        &method_call, kDefaultTimeout.InMilliseconds(),
-        base::BindOnce(&CiceroneClientImpl::OnDBusProtoResponse<
-                           vm_tools::cicerone::AppSearchResponse>,
-                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-  }
-
   void ExportLxdContainer(
       const vm_tools::cicerone::ExportLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::ExportLxdContainerResponse>
diff --git a/chromeos/dbus/cicerone_client.h b/chromeos/dbus/cicerone_client.h
index 1de4d21c..633221e 100644
--- a/chromeos/dbus/cicerone_client.h
+++ b/chromeos/dbus/cicerone_client.h
@@ -210,12 +210,6 @@
       DBusMethodCallback<vm_tools::cicerone::SetUpLxdContainerUserResponse>
           callback) = 0;
 
-  // Searches for not installed Linux packages in a container.
-  // |callback| is called after the method call finishes.
-  virtual void SearchApp(
-      const vm_tools::cicerone::AppSearchRequest& request,
-      DBusMethodCallback<vm_tools::cicerone::AppSearchResponse> callback) = 0;
-
   // Exports the Lxd container.
   // |callback| is called when the method completes.
   virtual void ExportLxdContainer(
diff --git a/chromeos/dbus/fake_cicerone_client.cc b/chromeos/dbus/fake_cicerone_client.cc
index 103f33d..5650a5f 100644
--- a/chromeos/dbus/fake_cicerone_client.cc
+++ b/chromeos/dbus/fake_cicerone_client.cc
@@ -33,10 +33,6 @@
   setup_lxd_container_user_response_.set_status(
       vm_tools::cicerone::SetUpLxdContainerUserResponse::SUCCESS);
 
-  vm_tools::cicerone::AppSearchResponse::AppSearchResult* app =
-      search_app_response_.add_packages();
-  app->set_package_name("fake app");
-
   export_lxd_container_response_.set_status(
       vm_tools::cicerone::ExportLxdContainerResponse::EXPORTING);
 
@@ -245,13 +241,6 @@
       base::BindOnce(std::move(callback), setup_lxd_container_user_response_));
 }
 
-void FakeCiceroneClient::SearchApp(
-    const vm_tools::cicerone::AppSearchRequest& request,
-    DBusMethodCallback<vm_tools::cicerone::AppSearchResponse> callback) {
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE, base::BindOnce(std::move(callback), search_app_response_));
-}
-
 void FakeCiceroneClient::ExportLxdContainer(
     const vm_tools::cicerone::ExportLxdContainerRequest& request,
     DBusMethodCallback<vm_tools::cicerone::ExportLxdContainerResponse>
diff --git a/chromeos/dbus/fake_cicerone_client.h b/chromeos/dbus/fake_cicerone_client.h
index 2c38d062..540cb1d 100644
--- a/chromeos/dbus/fake_cicerone_client.h
+++ b/chromeos/dbus/fake_cicerone_client.h
@@ -144,12 +144,6 @@
       DBusMethodCallback<vm_tools::cicerone::SetUpLxdContainerUserResponse>
           callback) override;
 
-  // Fake version of the method that searches for not installed apps.
-  // |callback| is called when the method completes.
-  void SearchApp(const vm_tools::cicerone::AppSearchRequest& request,
-                 DBusMethodCallback<vm_tools::cicerone::AppSearchResponse>
-                     callback) override;
-
   // Fake version of the method that exports the container.
   // |callback| is called when the method completes.
   void ExportLxdContainer(
@@ -249,11 +243,6 @@
     container_app_icon_response_ = container_app_icon_response;
   }
 
-  void set_search_app_response(
-      const vm_tools::cicerone::AppSearchResponse& search_app_response) {
-    search_app_response_ = search_app_response;
-  }
-
   const vm_tools::cicerone::LinuxPackageInfoRequest&
   get_most_recent_linux_package_info_request() const {
     return most_recent_linux_package_info_request_;
@@ -394,7 +383,6 @@
       get_lxd_container_username_response_;
   vm_tools::cicerone::SetUpLxdContainerUserResponse
       setup_lxd_container_user_response_;
-  vm_tools::cicerone::AppSearchResponse search_app_response_;
   vm_tools::cicerone::ExportLxdContainerResponse export_lxd_container_response_;
   vm_tools::cicerone::ImportLxdContainerResponse import_lxd_container_response_;
 
diff --git a/chromeos/printing/epson_driver_matching.cc b/chromeos/printing/epson_driver_matching.cc
index 8f1bd00d..d3c7a411 100644
--- a/chromeos/printing/epson_driver_matching.cc
+++ b/chromeos/printing/epson_driver_matching.cc
@@ -33,8 +33,16 @@
       return base::Contains(sd.supported_document_formats,
                             "application/octet-stream");
 
-    case PrinterSearchData::PrinterDiscoveryType::kUsb:
-      return base::Contains(sd.printer_id.command_set(), "ESC/P-R");
+    case PrinterSearchData::PrinterDiscoveryType::kUsb: {
+      // For USB printers, the command set is retrieved from the 'CMD' field of
+      // the printer's IEEE 1284 Device ID.
+      for (base::StringPiece format : sd.printer_id.command_set()) {
+        if (format.starts_with("ESCPR")) {
+          return true;
+        }
+      }
+      return false;
+    }
 
     case PrinterSearchData::PrinterDiscoveryType::kZeroconf:
       // For printers found through mDNS/DNS-SD discovery,
diff --git a/chromeos/printing/epson_driver_matching_unittest.cc b/chromeos/printing/epson_driver_matching_unittest.cc
index 8bf627a..bcbca4b 100644
--- a/chromeos/printing/epson_driver_matching_unittest.cc
+++ b/chromeos/printing/epson_driver_matching_unittest.cc
@@ -14,7 +14,7 @@
 namespace {
 
 const char kOctetStream[] = "application/octet-stream";
-const char kEscPr[] = "ESC/P-R";
+const char kEscPr[] = "ESCPR1";
 const char kEpsonEscpr[] = "application/vnd.epson.escpr";
 
 using PrinterDiscoveryType = PrinterSearchData::PrinterDiscoveryType;
@@ -113,14 +113,14 @@
   sd.printer_id.set_command_set(command_set);
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
-  command_set.push_back(std::string(kEscPr) + ":asfd");
+  command_set.push_back("abcd" + std::string(kEscPr));
   sd.printer_id.set_command_set(command_set);
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
   sd.supported_document_formats.push_back(kEscPr);
   EXPECT_FALSE(CanUseEpsonGenericPPD(sd));
 
-  command_set.push_back(kEscPr);
+  command_set.push_back(std::string(kEscPr) + "garbage");
   sd.printer_id.set_command_set(command_set);
   EXPECT_TRUE(CanUseEpsonGenericPPD(sd));
 }
diff --git a/chromeos/printing/ppd_provider.cc b/chromeos/printing/ppd_provider.cc
index 3a3d226..7958605 100644
--- a/chromeos/printing/ppd_provider.cc
+++ b/chromeos/printing/ppd_provider.cc
@@ -490,10 +490,11 @@
         // Found a hit, satisfy this resolution.
         RunPpdReferenceResolutionSucceeded(std::move(next.cb),
                                            kEpsonGenericPPD);
+      } else {
+        // We don't have anything else left to try. We've reached unsupported
+        // USB printer, try to grab the manufacturer name.
+        ResolveUsbManufacturer(std::move(next.cb), search_data.usb_vendor_id);
       }
-      // We don't have anything else left to try. We've reached unsupported USB
-      // printer, try to grab the manufacturer name.
-      ResolveUsbManufacturer(std::move(next.cb), search_data.usb_vendor_id);
     }
     // Didn't start any fetches.
     return false;
diff --git a/chromeos/printing/usb_printer_id.h b/chromeos/printing/usb_printer_id.h
index 3df4257..edffbc95 100644
--- a/chromeos/printing/usb_printer_id.h
+++ b/chromeos/printing/usb_printer_id.h
@@ -28,9 +28,9 @@
   explicit UsbPrinterId(const std::vector<uint8_t>& printer_id_data);
 
   // Accessors.
-  std::string make() const { return make_; }
-  std::string model() const { return model_; }
-  std::vector<std::string> command_set() const { return command_set_; }
+  const std::string& make() const { return make_; }
+  const std::string& model() const { return model_; }
+  const std::vector<std::string>& command_set() const { return command_set_; }
 
   // Setters (only used in testing).
   void set_make(std::string make) { make_ = make; }
diff --git a/components/arc/session/arc_session_runner.cc b/components/arc/session/arc_session_runner.cc
index f5deb67..fe656884 100644
--- a/components/arc/session/arc_session_runner.cc
+++ b/components/arc/session/arc_session_runner.cc
@@ -221,8 +221,9 @@
 }
 
 void ArcSessionRunner::SetUserIdHashForProfile(const std::string& hash) {
+  user_id_hash_ = hash;
   if (arc_session_)
-    arc_session_->SetUserIdHashForProfile(hash);
+    arc_session_->SetUserIdHashForProfile(user_id_hash_);
 }
 
 void ArcSessionRunner::SetRestartDelayForTesting(
@@ -240,6 +241,8 @@
   VLOG(1) << "Starting ARC instance";
   if (!arc_session_) {
     arc_session_ = factory_.Run();
+    if (!user_id_hash_.empty())
+      arc_session_->SetUserIdHashForProfile(user_id_hash_);
     arc_session_->AddObserver(this);
     arc_session_->StartMiniInstance();
     // Record the UMA only when |restart_after_crash_count_| is zero to avoid
diff --git a/components/arc/session/arc_session_runner.h b/components/arc/session/arc_session_runner.h
index 005bdc9..42e0c18f 100644
--- a/components/arc/session/arc_session_runner.h
+++ b/components/arc/session/arc_session_runner.h
@@ -143,6 +143,9 @@
   // Parameters to upgrade request.
   ArcSession::UpgradeParams upgrade_params_;
 
+  // A hash string of the profile user ID.
+  std::string user_id_hash_;
+
   // WeakPtrFactory to use callbacks.
   base::WeakPtrFactory<ArcSessionRunner> weak_ptr_factory_;
 
diff --git a/components/background_task_scheduler/BUILD.gn b/components/background_task_scheduler/BUILD.gn
index e89bd9d4..be854b8 100644
--- a/components/background_task_scheduler/BUILD.gn
+++ b/components/background_task_scheduler/BUILD.gn
@@ -64,6 +64,8 @@
     testonly = true
 
     java_files = [
+      "android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplWithMockTest.java",
+      "android/javatests/src/org/chromium/components/background_task_scheduler/MockBackgroundTaskSchedulerDelegate.java",
       "android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerJobServiceTest.java",
       "android/javatests/src/org/chromium/components/background_task_scheduler/BundleToPersistableBundleConverterTest.java",
     ]
@@ -77,6 +79,7 @@
       "$google_play_services_package:google_play_services_tasks_java",
       "//base:base_java",
       "//base:base_java_test_support",
+      "//content/public/test/android:content_java_test_support",
       "//third_party/android_support_test_runner:runner_java",
       "//third_party/junit",
     ]
diff --git a/components/background_task_scheduler/DEPS b/components/background_task_scheduler/DEPS
new file mode 100644
index 0000000..1356512
--- /dev/null
+++ b/components/background_task_scheduler/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+    "+content/public/test/android",
+]
\ No newline at end of file
diff --git a/components/background_task_scheduler/android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplWithMockTest.java b/components/background_task_scheduler/android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplWithMockTest.java
new file mode 100644
index 0000000..67b8520
--- /dev/null
+++ b/components/background_task_scheduler/android/javatests/src/org/chromium/components/background_task_scheduler/BackgroundTaskSchedulerImplWithMockTest.java
@@ -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.
+
+package org.chromium.components.background_task_scheduler;
+
+import android.content.Context;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.content_public.browser.test.util.TestThreadUtils;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link BackgroundTaskSchedulerImpl}.
+ */
+@RunWith(BaseJUnit4ClassRunner.class)
+public class BackgroundTaskSchedulerImplWithMockTest {
+    private static class TestBackgroundTask implements BackgroundTask {
+        @Override
+        public boolean onStartTask(
+                Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
+            return false;
+        }
+
+        @Override
+        public boolean onStopTask(Context context, TaskParameters taskParameters) {
+            return false;
+        }
+
+        @Override
+        public void reschedule(Context context) {}
+    }
+
+    private static final int TEST_MINUTES = 10;
+
+    @Test
+    @SmallTest
+    public void testOneOffTaskScheduling() {
+        TaskInfo oneOffTask = TaskInfo.createOneOffTask(TaskIds.TEST, TestBackgroundTask.class,
+                                              TimeUnit.MINUTES.toMillis(TEST_MINUTES))
+                                      .build();
+
+        MockBackgroundTaskSchedulerDelegate delegate = new MockBackgroundTaskSchedulerDelegate();
+        BackgroundTaskScheduler taskScheduler = new BackgroundTaskSchedulerImpl(delegate);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { Assert.assertTrue(taskScheduler.schedule(null, oneOffTask)); });
+
+        Assert.assertEquals(oneOffTask, delegate.getScheduledTaskInfo());
+        Assert.assertEquals(0, delegate.getCanceledTaskId());
+    }
+
+    @Test
+    @SmallTest
+    public void testPeriodicTaskScheduling() {
+        TaskInfo periodicTask = TaskInfo.createPeriodicTask(TaskIds.TEST, TestBackgroundTask.class,
+                                                TimeUnit.MINUTES.toMillis(TEST_MINUTES))
+                                        .build();
+
+        MockBackgroundTaskSchedulerDelegate delegate = new MockBackgroundTaskSchedulerDelegate();
+        BackgroundTaskScheduler taskScheduler = new BackgroundTaskSchedulerImpl(delegate);
+
+        TestThreadUtils.runOnUiThreadBlocking(
+                () -> { Assert.assertTrue(taskScheduler.schedule(null, periodicTask)); });
+
+        Assert.assertEquals(periodicTask, delegate.getScheduledTaskInfo());
+        Assert.assertEquals(0, delegate.getCanceledTaskId());
+    }
+
+    @Test
+    @SmallTest
+    public void testTaskCanceling() {
+        MockBackgroundTaskSchedulerDelegate delegate = new MockBackgroundTaskSchedulerDelegate();
+        BackgroundTaskScheduler taskScheduler = new BackgroundTaskSchedulerImpl(delegate);
+
+        TestThreadUtils.runOnUiThreadBlocking(() -> { taskScheduler.cancel(null, TaskIds.TEST); });
+
+        Assert.assertEquals(null, delegate.getScheduledTaskInfo());
+        Assert.assertEquals(TaskIds.TEST, delegate.getCanceledTaskId());
+    }
+}
\ No newline at end of file
diff --git a/components/background_task_scheduler/android/javatests/src/org/chromium/components/background_task_scheduler/MockBackgroundTaskSchedulerDelegate.java b/components/background_task_scheduler/android/javatests/src/org/chromium/components/background_task_scheduler/MockBackgroundTaskSchedulerDelegate.java
new file mode 100644
index 0000000..39227cc
--- /dev/null
+++ b/components/background_task_scheduler/android/javatests/src/org/chromium/components/background_task_scheduler/MockBackgroundTaskSchedulerDelegate.java
@@ -0,0 +1,42 @@
+// 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.components.background_task_scheduler;
+
+import android.content.Context;
+
+/**
+ * Mock of BackgroundTaskSchedulerDelegate that tracks which methods are called.
+ * This is used for all delegates that cannot be included in end-to-end testing.
+ */
+public class MockBackgroundTaskSchedulerDelegate implements BackgroundTaskSchedulerDelegate {
+    private TaskInfo mScheduledTaskInfo;
+    private int mCanceledTaskId;
+
+    @Override
+    public boolean schedule(Context context, TaskInfo taskInfo) {
+        mScheduledTaskInfo = taskInfo;
+        mCanceledTaskId = 0;
+        return true;
+    }
+
+    @Override
+    public void cancel(Context context, int taskId) {
+        mCanceledTaskId = taskId;
+        mScheduledTaskInfo = null;
+    }
+
+    public TaskInfo getScheduledTaskInfo() {
+        return mScheduledTaskInfo;
+    }
+
+    public int getCanceledTaskId() {
+        return mCanceledTaskId;
+    }
+
+    public void clear() {
+        mScheduledTaskInfo = null;
+        mCanceledTaskId = 0;
+    }
+}
\ No newline at end of file
diff --git a/components/media_message_center/BUILD.gn b/components/media_message_center/BUILD.gn
index 72e05fe..a1183f2 100644
--- a/components/media_message_center/BUILD.gn
+++ b/components/media_message_center/BUILD.gn
@@ -12,6 +12,8 @@
     "media_notification_controller.h",
     "media_notification_item.cc",
     "media_notification_item.h",
+    "media_notification_util.cc",
+    "media_notification_util.h",
     "media_notification_view.cc",
     "media_notification_view.h",
   ]
diff --git a/components/media_message_center/media_notification_util.cc b/components/media_message_center/media_notification_util.cc
new file mode 100644
index 0000000..cfe947d
--- /dev/null
+++ b/components/media_message_center/media_notification_util.cc
@@ -0,0 +1,30 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "components/media_message_center/media_notification_util.h"
+
+#include "base/strings/utf_string_conversions.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
+
+namespace media_message_center {
+
+base::string16 GetAccessibleNameFromMetadata(
+    media_session::MediaMetadata session_metadata) {
+  std::vector<base::string16> text;
+
+  if (!session_metadata.title.empty())
+    text.push_back(session_metadata.title);
+
+  if (!session_metadata.artist.empty())
+    text.push_back(session_metadata.artist);
+
+  if (!session_metadata.album.empty())
+    text.push_back(session_metadata.album);
+
+  base::string16 accessible_name =
+      base::JoinString(text, base::ASCIIToUTF16(" - "));
+  return accessible_name;
+}
+
+}  // namespace media_message_center
diff --git a/components/media_message_center/media_notification_util.h b/components/media_message_center/media_notification_util.h
new file mode 100644
index 0000000..8c2b027
--- /dev/null
+++ b/components/media_message_center/media_notification_util.h
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_UTIL_H_
+#define COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_UTIL_H_
+
+#include "base/component_export.h"
+#include "services/media_session/public/mojom/media_session.mojom.h"
+
+namespace media_message_center {
+
+// Creates a string describing media session metadata intended to be read out by
+// a screen reader.
+COMPONENT_EXPORT(MEDIA_MESSAGE_CENTER)
+base::string16 GetAccessibleNameFromMetadata(
+    media_session::MediaMetadata session_metadata);
+
+}  // namespace media_message_center
+
+#endif  // COMPONENTS_MEDIA_MESSAGE_CENTER_MEDIA_NOTIFICATION_UTIL_H_
diff --git a/components/media_message_center/media_notification_view.cc b/components/media_message_center/media_notification_view.cc
index 9119b9a..4ea8af1 100644
--- a/components/media_message_center/media_notification_view.cc
+++ b/components/media_message_center/media_notification_view.cc
@@ -6,11 +6,11 @@
 
 #include "base/metrics/histogram_macros.h"
 #include "base/stl_util.h"
-#include "base/strings/utf_string_conversions.h"
 #include "components/media_message_center/media_notification_background.h"
 #include "components/media_message_center/media_notification_constants.h"
 #include "components/media_message_center/media_notification_container.h"
 #include "components/media_message_center/media_notification_item.h"
+#include "components/media_message_center/media_notification_util.h"
 #include "components/strings/grit/components_strings.h"
 #include "components/vector_icons/vector_icons.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
@@ -297,24 +297,16 @@
   artist_label_->SetText(metadata.artist);
   header_row_->SetSummaryText(metadata.album);
 
-  std::vector<base::string16> text;
+  accessible_name_ = GetAccessibleNameFromMetadata(metadata);
 
-  if (!metadata.title.empty()) {
-    text.push_back(metadata.title);
+  if (!metadata.title.empty())
     RecordMetadataHistogram(Metadata::kTitle);
-  }
 
-  if (!metadata.artist.empty()) {
-    text.push_back(metadata.artist);
+  if (!metadata.artist.empty())
     RecordMetadataHistogram(Metadata::kArtist);
-  }
 
-  if (!metadata.album.empty()) {
-    text.push_back(metadata.album);
+  if (!metadata.album.empty())
     RecordMetadataHistogram(Metadata::kAlbum);
-  }
-
-  accessible_name_ = base::JoinString(text, base::ASCIIToUTF16(" - "));
 
   RecordMetadataHistogram(Metadata::kCount);
 
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
index a1b05e5..a4626821 100644
--- a/components/printing/renderer/print_render_frame_helper.cc
+++ b/components/printing/renderer/print_render_frame_helper.cc
@@ -1104,6 +1104,10 @@
   if (delegate_->OverridePrint(web_frame))
     return;
 
+  // Detached documents can't be printed.
+  if (!web_frame->GetDocument().GetFrame())
+    return;
+
   if (g_is_preview_enabled) {
 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
     print_preview_context_.InitWithFrame(web_frame);
diff --git a/components/signin/core/browser/BUILD.gn b/components/signin/core/browser/BUILD.gn
index ab5bc9ad..e638b5f 100644
--- a/components/signin/core/browser/BUILD.gn
+++ b/components/signin/core/browser/BUILD.gn
@@ -281,6 +281,7 @@
     "device_id_helper_unittest.cc",
     "dice_account_reconcilor_delegate_unittest.cc",
     "gaia_cookie_manager_service_unittest.cc",
+    "identity_utils_unittest.cc",
     "mice_account_reconcilor_delegate_unittest.cc",
     "mutable_profile_oauth2_token_service_delegate_unittest.cc",
     "oauth_multilogin_helper_unittest.cc",
diff --git a/components/signin/core/browser/identity_utils.cc b/components/signin/core/browser/identity_utils.cc
index f5551f9..57ea8f1 100644
--- a/components/signin/core/browser/identity_utils.cc
+++ b/components/signin/core/browser/identity_utils.cc
@@ -9,6 +9,7 @@
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "components/prefs/pref_service.h"
+#include "components/signin/core/browser/signin_pref_names.h"
 #include "third_party/icu/source/i18n/unicode/regex.h"
 
 namespace identity {
@@ -52,10 +53,9 @@
 }  // namespace
 
 bool IsUsernameAllowedByPatternFromPrefs(const PrefService* prefs,
-                                         const std::string& username,
-                                         const std::string& pattern_pref_name) {
-  return IsUsernameAllowedByPattern(username,
-                                    prefs->GetString(pattern_pref_name));
+                                         const std::string& username) {
+  return IsUsernameAllowedByPattern(
+      username, prefs->GetString(prefs::kGoogleServicesUsernamePattern));
 }
 
 }  // namespace identity
diff --git a/components/signin/core/browser/identity_utils.h b/components/signin/core/browser/identity_utils.h
index 27afddf1..b28d452 100644
--- a/components/signin/core/browser/identity_utils.h
+++ b/components/signin/core/browser/identity_utils.h
@@ -19,10 +19,10 @@
 namespace identity {
 
 // Returns true if the username is allowed based on a pattern registered
-// as |pattern_pref_name| with the preferences service referenced by |prefs|.
+// |prefs::kGoogleServicesUsernamePattern| with the preferences service
+// referenced by |prefs|.
 bool IsUsernameAllowedByPatternFromPrefs(const PrefService* prefs,
-                                         const std::string& username,
-                                         const std::string& pattern_pref_name);
+                                         const std::string& username);
 }  // namespace identity
 
 #endif  // COMPONENTS_SIGNIN_CORE_BROWSER_IDENTITY_UTILS_H_
diff --git a/components/signin/core/browser/identity_utils_unittest.cc b/components/signin/core/browser/identity_utils_unittest.cc
index 62e8c47..e0cd47e5 100644
--- a/components/signin/core/browser/identity_utils_unittest.cc
+++ b/components/signin/core/browser/identity_utils_unittest.cc
@@ -7,6 +7,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/testing_pref_service.h"
+#include "components/signin/core/browser/signin_pref_names.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace {
@@ -25,14 +26,13 @@
 const char kNonMatchingPattern[] = ".*foo.*";
 const char kNonMatchingUsernamePattern[] = "foo@test.com";
 const char kNonMatchingDomainPattern[] = "test@foo.com";
-
-const char kRegisteredPattern[] = "test.registered.username_pattern";
 }  // namespace
 
 class IdentityUtilsTest : public testing::Test {
  public:
   IdentityUtilsTest() {
-    prefs_.registry()->RegisterStringPref(kRegisteredPattern, std::string());
+    prefs_.registry()->RegisterStringPref(prefs::kGoogleServicesUsernamePattern,
+                                          std::string());
   }
 
   TestingPrefServiceSimple* prefs() { return &prefs_; }
@@ -42,13 +42,13 @@
 };
 
 TEST_F(IdentityUtilsTest, IsUsernameAllowedByPatternFromPrefs_EmptyPatterns) {
-  prefs()->SetString(kRegisteredPattern, "");
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(),kUsername, kRegisteredPattern);
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, "");
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, "   ");
-  EXPECT_FALSE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern);
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, "   ");
+  EXPECT_FALSE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 }
 
 TEST_F(IdentityUtilsTest,
@@ -56,50 +56,55 @@
   // identity::IsUsernameAllowedByPatternFromPrefs should recognize invalid
   // wildcard patterns like "*@foo.com" and insert a "." before them
   // automatically.
-  prefs()->SetString(kRegisteredPattern, kValidWildcardPattern);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern);
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern,
+                     kValidWildcardPattern);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kInvalidWildcardPattern);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern);
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern,
+                     kInvalidWildcardPattern);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 }
 
 TEST_F(IdentityUtilsTest,
        IsUsernameAllowedByPatternFromPrefs_MatchingWildcardPatterns) {
-  prefs()->SetString(kRegisteredPattern, kMatchingPattern1);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, kMatchingPattern1);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kMatchingPattern2);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, kMatchingPattern2);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kMatchingPattern3);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, kMatchingPattern3);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kMatchingPattern4);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, kMatchingPattern4);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kMatchingPattern5);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, kMatchingPattern5);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kMatchingPattern6);
-  EXPECT_TRUE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern, kMatchingPattern6);
+  EXPECT_TRUE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kNonMatchingPattern);
-  EXPECT_FALSE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern,
+                     kNonMatchingPattern);
+  EXPECT_FALSE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kNonMatchingUsernamePattern);
-  EXPECT_FALSE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern,
+                     kNonMatchingUsernamePattern);
+  EXPECT_FALSE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 
-  prefs()->SetString(kRegisteredPattern, kNonMatchingDomainPattern);
-  EXPECT_FALSE(identity::IsUsernameAllowedByPatternFromPrefs(
-      prefs(), kUsername, kRegisteredPattern));
+  prefs()->SetString(prefs::kGoogleServicesUsernamePattern,
+                     kNonMatchingDomainPattern);
+  EXPECT_FALSE(
+      identity::IsUsernameAllowedByPatternFromPrefs(prefs(), kUsername));
 }
diff --git a/components/signin/core/browser/primary_account_policy_manager_impl.cc b/components/signin/core/browser/primary_account_policy_manager_impl.cc
index d6c10b3..514f12a 100644
--- a/components/signin/core/browser/primary_account_policy_manager_impl.cc
+++ b/components/signin/core/browser/primary_account_policy_manager_impl.cc
@@ -107,6 +107,5 @@
   if (!local_state)
     return true;
 
-  return identity::IsUsernameAllowedByPatternFromPrefs(
-      local_state, username, prefs::kGoogleServicesUsernamePattern);
+  return identity::IsUsernameAllowedByPatternFromPrefs(local_state, username);
 }
diff --git a/components/viz/service/display/display.cc b/components/viz/service/display/display.cc
index ca61367..6def139 100644
--- a/components/viz/service/display/display.cc
+++ b/components/viz/service/display/display.cc
@@ -162,10 +162,9 @@
     observer.OnDisplayDestroyed();
   observers_.Clear();
 
-  for (auto& callback_list : pending_presented_callbacks_) {
-    for (auto& callback : callback_list.second)
-      std::move(callback).Run(gfx::PresentationFeedback::Failure());
-  }
+  // Send gfx::PresentationFeedback::Failure() to any surfaces expecting
+  // feedback.
+  pending_surfaces_with_presentation_helpers_.clear();
 
   // Only do this if Initialize() happened.
   if (client_) {
@@ -538,16 +537,17 @@
           scheduler_->current_frame_time(), 1);
     }
 
-    std::vector<Surface::PresentedCallback> callbacks;
+    std::vector<std::unique_ptr<Surface::PresentationHelper>>
+        presentation_helper_list;
     for (const auto& id_entry : aggregator_->previous_contained_surfaces()) {
       Surface* surface = surface_manager_->GetSurfaceForId(id_entry.first);
-      Surface::PresentedCallback callback;
-      if (surface && surface->TakePresentedCallback(&callback)) {
-        callbacks.emplace_back(std::move(callback));
+      if (surface) {
+        presentation_helper_list.push_back(
+            surface->TakePresentationHelperForPresentNotification());
       }
     }
-    pending_presented_callbacks_.emplace_back(
-        std::make_pair(now_time, std::move(callbacks)));
+    pending_surfaces_with_presentation_helpers_.emplace_back(
+        std::make_pair(now_time, std::move(presentation_helper_list)));
 
     ui::LatencyInfo::TraceIntermediateFlowEvents(frame.metadata.latency_info,
                                                  "Display::DrawAndSwap");
@@ -615,12 +615,13 @@
   // and should not be popped until DidReceivePresentationFeedback. Therefore
   // we must not have an empty list when getting the SwapBuffers ACK (this is
   // required to happen between those two events).
-  DCHECK(!pending_presented_callbacks_.empty());
+  DCHECK(!pending_surfaces_with_presentation_helpers_.empty());
 
   // Check that the swap timings correspond with the timestamp from when
   // the swap was triggered. Note that not all output surfaces provide timing
   // information, hence the check for a valid swap_start.
-  const auto swap_time = pending_presented_callbacks_.front().first;
+  const auto swap_time =
+      pending_surfaces_with_presentation_helpers_.front().first;
   if (!timings.swap_start.is_null()) {
     DCHECK_LE(swap_time, timings.swap_start);
     base::TimeDelta delta =
@@ -652,7 +653,7 @@
 
 void Display::DidReceivePresentationFeedback(
     const gfx::PresentationFeedback& feedback) {
-  if (pending_presented_callbacks_.empty()) {
+  if (pending_surfaces_with_presentation_helpers_.empty()) {
     DLOG(ERROR) << "Received unexpected PresentationFeedback";
     return;
   }
@@ -660,16 +661,19 @@
   TRACE_EVENT_ASYNC_END_WITH_TIMESTAMP0(
       "viz,benchmark", "Graphics.Pipeline.DrawAndSwap",
       last_presented_trace_id_, feedback.timestamp);
-  auto& callbacks = pending_presented_callbacks_.front().second;
-  const auto swap_time = pending_presented_callbacks_.front().first;
+  auto& presentation_helper_list =
+      pending_surfaces_with_presentation_helpers_.front().second;
+  const auto swap_time =
+      pending_surfaces_with_presentation_helpers_.front().first;
   auto copy_feedback = SanitizePresentationFeedback(feedback, swap_time);
   TRACE_EVENT_INSTANT_WITH_TIMESTAMP0(
       "benchmark,viz", "Display::FrameDisplayed", TRACE_EVENT_SCOPE_THREAD,
       copy_feedback.timestamp);
-  for (auto& callback : callbacks) {
-    std::move(callback).Run(copy_feedback);
+  for (auto& presentation_helper : presentation_helper_list) {
+    if (presentation_helper)
+      presentation_helper->DidPresent(feedback);
   }
-  pending_presented_callbacks_.pop_front();
+  pending_surfaces_with_presentation_helpers_.pop_front();
 }
 
 void Display::DidFinishLatencyInfo(
diff --git a/components/viz/service/display/display.h b/components/viz/service/display/display.h
index cf97b87f..623c1b4 100644
--- a/components/viz/service/display/display.h
+++ b/components/viz/service/display/display.h
@@ -10,6 +10,7 @@
 
 #include "base/containers/circular_deque.h"
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
 #include "base/single_thread_task_runner.h"
 #include "components/viz/common/frame_sinks/begin_frame_source.h"
@@ -201,9 +202,14 @@
   std::vector<ui::LatencyInfo> stored_latency_info_;
   std::vector<SurfaceId> surfaces_to_ack_on_next_draw_;
 
+  // |pending_surfaces_with_presentation_helpers_| is a list of lists of
+  // Surface::PresentationHelpers. The lists are grouped by swap (each surface
+  // involved in an individual Swap is added to the list. These are then
+  // notified of presentation after the appropriate Swap is completed.
   base::circular_deque<
-      std::pair<base::TimeTicks, std::vector<Surface::PresentedCallback>>>
-      pending_presented_callbacks_;
+      std::pair<base::TimeTicks,
+                std::vector<std::unique_ptr<Surface::PresentationHelper>>>>
+      pending_surfaces_with_presentation_helpers_;
 
   // Callback that will be run after all pending swaps have acked.
   base::OnceClosure no_pending_swaps_callback_;
diff --git a/components/viz/service/surfaces/surface.cc b/components/viz/service/surfaces/surface.cc
index eed28b34..d01b0a8 100644
--- a/components/viz/service/surfaces/surface.cc
+++ b/components/viz/service/surfaces/surface.cc
@@ -26,6 +26,25 @@
 
 namespace viz {
 
+Surface::PresentationHelper::PresentationHelper(base::WeakPtr<Surface> surface,
+                                                uint32_t frame_token)
+    : surface_(std::move(surface)), frame_token_(frame_token) {}
+
+Surface::PresentationHelper::~PresentationHelper() {
+  // The class that called TakePresentationHelperForPresentNotification
+  // should have called present on this helper. If not, give a Failure feedback
+  // to the appropriate surface.
+  DidPresent(gfx::PresentationFeedback::Failure());
+}
+
+void Surface::PresentationHelper::DidPresent(
+    const gfx::PresentationFeedback& feedback) {
+  if (surface_ && frame_token_)
+    surface_->DidPresentSurface(frame_token_, feedback);
+
+  surface_ = nullptr;
+}
+
 Surface::Surface(const SurfaceInfo& surface_info,
                  SurfaceManager* surface_manager,
                  SurfaceAllocationGroup* allocation_group,
@@ -565,15 +584,15 @@
   is_latency_info_taken_ = true;
 }
 
-bool Surface::TakePresentedCallback(PresentedCallback* callback) {
-  if (active_frame_data_ && !active_frame_data_->is_presented_callback_bound) {
-    *callback =
-        base::BindOnce(&Surface::DidPresentSurface, weak_factory_.GetWeakPtr(),
-                       active_frame_data_->frame.metadata.frame_token);
-    active_frame_data_->is_presented_callback_bound = true;
-    return true;
+std::unique_ptr<Surface::PresentationHelper>
+Surface::TakePresentationHelperForPresentNotification() {
+  if (active_frame_data_ &&
+      !active_frame_data_->will_be_notified_of_presentation) {
+    active_frame_data_->will_be_notified_of_presentation = true;
+    return std::make_unique<PresentationHelper>(
+        GetWeakPtr(), active_frame_data_->frame.metadata.frame_token);
   }
-  return false;
+  return nullptr;
 }
 
 void Surface::DidPresentSurface(uint32_t presentation_token,
@@ -622,9 +641,9 @@
   if (!frame_data->frame_acked)
     surface_client_->OnSurfaceProcessed(this);
 
-  // If we have not bound a presented callback, we'll notify the client when
-  // the frame is unref'd.
-  if (!frame_data->is_presented_callback_bound)
+  // If we won't be getting a presented notification, we'll notify the client
+  // when the frame is unref'd.
+  if (!frame_data->will_be_notified_of_presentation)
     DidPresentSurface(frame_data->frame.metadata.frame_token,
                       gfx::PresentationFeedback::Failure());
 }
diff --git a/components/viz/service/surfaces/surface.h b/components/viz/service/surfaces/surface.h
index 8c9ee36..183db2b 100644
--- a/components/viz/service/surfaces/surface.h
+++ b/components/viz/service/surfaces/surface.h
@@ -76,6 +76,20 @@
 // the event of missing dependencies at display time.
 class VIZ_SERVICE_EXPORT Surface final {
  public:
+  class PresentationHelper {
+   public:
+    PresentationHelper(base::WeakPtr<Surface> surface, uint32_t frame_token);
+    ~PresentationHelper();
+
+    void DidPresent(const gfx::PresentationFeedback& feedback);
+
+   private:
+    base::WeakPtr<Surface> surface_;
+    const uint32_t frame_token_;
+
+    DISALLOW_COPY_AND_ASSIGN(PresentationHelper);
+  };
+
   using PresentedCallback =
       base::OnceCallback<void(const gfx::PresentationFeedback&)>;
   enum QueueFrameResult { REJECTED, ACCEPTED_ACTIVE, ACCEPTED_PENDING };
@@ -168,7 +182,10 @@
   void TakeActiveLatencyInfo(std::vector<ui::LatencyInfo>* latency_info);
   void TakeActiveAndPendingLatencyInfo(
       std::vector<ui::LatencyInfo>* latency_info);
-  bool TakePresentedCallback(PresentedCallback* callback);
+  // Callers of this function must call |DidPresent| on the returned
+  // PresentationHelper, at the appropriate point in the future.
+  std::unique_ptr<Surface::PresentationHelper>
+  TakePresentationHelperForPresentNotification();
   void DidPresentSurface(uint32_t presentation_token,
                          const gfx::PresentationFeedback& feedback);
   void SendAckToClient();
@@ -231,6 +248,8 @@
 
   void ActivateIfDeadlinePassed();
 
+  base::WeakPtr<Surface> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
+
  private:
   struct FrameData {
     FrameData(CompositorFrame&& frame, uint64_t frame_index);
@@ -243,10 +262,10 @@
     // Whether the frame has been displayed or not.
     bool frame_drawn = false;
     bool frame_acked = false;
-    // Whether there is a presentation feedback callback bound to this frame.
+    // Whether there is a pending presentation callback (via DidPresentSurface).
     // This typically happens when a frame is swapped - the Display will ask
     // for a callback that will supply presentation feedback to the client.
-    bool is_presented_callback_bound = false;
+    bool will_be_notified_of_presentation = false;
   };
 
   // Updates surface references of the surface using the referenced
diff --git a/content/browser/devtools/protocol/network_handler.cc b/content/browser/devtools/protocol/network_handler.cc
index 5e68b592..9a5a0ea 100644
--- a/content/browser/devtools/protocol/network_handler.cc
+++ b/content/browser/devtools/protocol/network_handler.cc
@@ -35,6 +35,7 @@
 #include "content/browser/web_package/signed_exchange_envelope.h"
 #include "content/browser/web_package/signed_exchange_error.h"
 #include "content/common/navigation_params.h"
+#include "content/common/web_package/signed_exchange_utils.h"
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
@@ -1872,22 +1873,15 @@
     }
     signatures->emplace_back(std::move(signature));
 
-    const net::SHA256HashValue header_integrity =
-        envelope->ComputeHeaderIntegrity();
-    std::string header_integrity_base64;
-    base::Base64Encode(
-        base::StringPiece(reinterpret_cast<const char*>(header_integrity.data),
-                          sizeof(header_integrity.data)),
-        &header_integrity_base64);
-
     signed_exchange_info->SetHeader(
         Network::SignedExchangeHeader::Create()
             .SetRequestUrl(envelope->request_url().url.spec())
             .SetResponseCode(envelope->response_code())
             .SetResponseHeaders(Object::fromValue(headers_dict.get(), nullptr))
             .SetSignatures(std::move(signatures))
-            .SetHeaderIntegrity(std::string("sha256-") +
-                                header_integrity_base64)
+            .SetHeaderIntegrity(
+                signed_exchange_utils::CreateHeaderIntegrityHashString(
+                    envelope->ComputeHeaderIntegrity()))
             .Build());
   }
   if (ssl_info)
diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc
index d1165f6..4575aa23 100644
--- a/content/browser/frame_host/navigation_request.cc
+++ b/content/browser/frame_host/navigation_request.cc
@@ -2067,8 +2067,6 @@
   }
   if (subresource_loader_params_ &&
       !subresource_loader_params_->prefetched_signed_exchanges.empty()) {
-    DCHECK(base::FeatureList::IsEnabled(
-        features::kSignedExchangeSubresourcePrefetch));
     commit_params_.prefetched_signed_exchanges =
         std::move(subresource_loader_params_->prefetched_signed_exchanges);
   }
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 36e6f08..ad82e369 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -126,6 +126,7 @@
 #include "content/common/render_message_filter.mojom.h"
 #include "content/common/renderer.mojom.h"
 #include "content/common/swapped_out_messages.h"
+#include "content/common/unfreezable_frame_messages.h"
 #include "content/common/widget.mojom.h"
 #include "content/public/browser/ax_event_notification_details.h"
 #include "content/public/browser/browser_accessibility_state.h"
@@ -1027,10 +1028,10 @@
   //    flight.
   // 3. The RenderFrame can be detached, as part of removing a subtree (due to
   //    navigation, swap out, or DOM mutation). In this case, the browser sends
-  //    a FrameMsg_Delete for the RenderFrame to detach itself and release its
-  //    associated resources. If the subframe contains an unload handler,
-  //    |unload_state_| is advanced to UnloadState::InProgress to track that the
-  //    detach is in progress; otherwise, it is advanced directly to
+  //    a UnfreezableFrameMsg_Delete for the RenderFrame to detach itself and
+  //    release its associated resources. If the subframe contains an unload
+  //    handler, |unload_state_| is advanced to UnloadState::InProgress to track
+  //    that the detach is in progress; otherwise, it is advanced directly to
   //    UnloadState::Completed.
   //
   // The browser side gives the renderer a small timeout to finish processing
@@ -1043,8 +1044,9 @@
   //
   // TODO(dcheng): Due to how frame detach is signalled today, there are some
   // bugs in this area. In particular, subtree detach is reported from the
-  // bottom up, so the replicated FrameMsg_Delete messages actually operate on a
-  // node-by-node basis rather than detaching an entire subtree at once...
+  // bottom up, so the replicated UnfreezableFrameMsg_Delete messages actually
+  // operate on a node-by-node basis rather than detaching an entire subtree at
+  // once...
   //
   // Note that this logic is fairly subtle. It needs to include all subframes
   // and all speculative frames, but it should exclude case #1 (a main
@@ -1894,7 +1896,7 @@
     return;
 
   if (render_frame_created_) {
-    Send(new FrameMsg_Delete(routing_id_, intent));
+    Send(new UnfreezableFrameMsg_Delete(routing_id_, intent));
 
     if (!frame_tree_node_->IsMainFrame() && IsCurrent()) {
       // If this subframe has an unload handler (and isn't speculative), ensure
@@ -2171,7 +2173,7 @@
           FrameDeleteIntention::kNotMainFrame);
       // Speculative RenderFrameHosts are deleted by the FrameTreeNode's
       // RenderFrameHostManager's destructor. RenderFrameProxyHosts send
-      // FrameMsg_Delete automatically in the destructor.
+      // UnfreezableFrameMsg_Delete automatically in the destructor.
       // TODO(dcheng): This is horribly confusing. Refactor this logic so it's
       // more understandable.
       node_to_delete.reset();
@@ -7191,12 +7193,6 @@
 
 scoped_refptr<PrefetchedSignedExchangeCache>
 RenderFrameHostImpl::EnsurePrefetchedSignedExchangeCache() {
-  if (!base::FeatureList::IsEnabled(
-          features::kSignedExchangeSubresourcePrefetch) &&
-      !base::FeatureList::IsEnabled(
-          features::kSignedExchangePrefetchCacheForNavigations)) {
-    return nullptr;
-  }
   if (!prefetched_signed_exchange_cache_) {
     prefetched_signed_exchange_cache_ =
         base::MakeRefCounted<PrefetchedSignedExchangeCache>();
diff --git a/content/browser/loader/navigation_url_loader_impl.cc b/content/browser/loader/navigation_url_loader_impl.cc
index 911d687..8d406e4 100644
--- a/content/browser/loader/navigation_url_loader_impl.cc
+++ b/content/browser/loader/navigation_url_loader_impl.cc
@@ -706,10 +706,6 @@
       std::string accept_langs) {
     DCHECK(!IsNavigationLoaderOnUIEnabled());
     if (prefetched_signed_exchange_cache) {
-      DCHECK(base::FeatureList::IsEnabled(
-                 features::kSignedExchangeSubresourcePrefetch) ||
-             base::FeatureList::IsEnabled(
-                 features::kSignedExchangePrefetchCacheForNavigations));
       std::unique_ptr<NavigationLoaderInterceptor>
           prefetched_signed_exchange_interceptor =
               prefetched_signed_exchange_cache->MaybeCreateInterceptor(
diff --git a/content/browser/loader/prefetch_url_loader.cc b/content/browser/loader/prefetch_url_loader.cc
index c9486c24..d7a5a56 100644
--- a/content/browser/loader/prefetch_url_loader.cc
+++ b/content/browser/loader/prefetch_url_loader.cc
@@ -65,10 +65,6 @@
         network::kAcceptHeader, kSignedExchangeEnabledAcceptHeaderForPrefetch);
 
     if (prefetched_signed_exchange_cache) {
-      DCHECK(base::FeatureList::IsEnabled(
-                 features::kSignedExchangeSubresourcePrefetch) ||
-             base::FeatureList::IsEnabled(
-                 features::kSignedExchangePrefetchCacheForNavigations));
       prefetched_signed_exchange_cache_adapter_ =
           std::make_unique<PrefetchedSignedExchangeCacheAdapter>(
               std::move(prefetched_signed_exchange_cache),
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index dcf3078..f1ada24 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -207,6 +207,7 @@
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "services/service_manager/sandbox/switches.h"
 #include "services/service_manager/zygote/common/zygote_buildflags.h"
+#include "storage/browser/database/database_tracker.h"
 #include "storage/browser/fileapi/sandbox_file_system_backend.h"
 #include "third_party/blink/public/common/page/launching_process_state.h"
 #include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
@@ -2073,6 +2074,11 @@
   AddUIThreadInterface(registry.get(),
                        base::BindRepeating(&ClipboardHostImpl::Create));
 
+  AddUIThreadInterface(
+      registry.get(),
+      base::BindRepeating(&RenderProcessHostImpl::BindWebDatabaseHostImpl,
+                          base::Unretained(this)));
+
   registry->AddInterface(
       base::BindRepeating(&MimeRegistryImpl::Create),
       base::CreateSequencedTaskRunnerWithTraits(
@@ -2121,12 +2127,6 @@
         &viz::GpuClient::Add, base::Unretained(gpu_client_.get())));
   }
 
-  registry->AddInterface(
-      base::BindRepeating(
-          &WebDatabaseHostImpl::Create, GetID(),
-          base::WrapRefCounted(storage_partition_impl_->GetDatabaseTracker())),
-      storage_partition_impl_->GetDatabaseTracker()->task_runner());
-
   MediaStreamManager* media_stream_manager =
       BrowserMainLoop::GetInstance()->media_stream_manager();
 
@@ -2324,6 +2324,16 @@
   video_decoder_proxy_->Add(std::move(request));
 }
 
+void RenderProcessHostImpl::BindWebDatabaseHostImpl(
+    blink::mojom::WebDatabaseHostRequest request) {
+  storage::DatabaseTracker* db_tracker =
+      storage_partition_impl_->GetDatabaseTracker();
+  db_tracker->task_runner()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&WebDatabaseHostImpl::Create, GetID(),
+                     base::WrapRefCounted(db_tracker), std::move(request)));
+}
+
 void RenderProcessHostImpl::CreateRendererHost(
     mojom::RendererHostAssociatedRequest request) {
   renderer_host_binding_.Bind(std::move(request));
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index ba2efb91..b95e993 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -66,6 +66,7 @@
 #include "third_party/blink/public/mojom/filesystem/file_system.mojom.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-shared.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
+#include "third_party/blink/public/mojom/webdatabase/web_database.mojom.h"
 #include "ui/gfx/gpu_memory_buffer.h"
 
 #if defined(OS_ANDROID)
@@ -565,6 +566,7 @@
       blink::mojom::BroadcastChannelProviderRequest request);
   void CreateRendererHost(mojom::RendererHostAssociatedRequest request);
   void BindVideoDecoderService(media::mojom::InterfaceFactoryRequest request);
+  void BindWebDatabaseHostImpl(blink::mojom::WebDatabaseHostRequest request);
 
   // Control message handlers.
   void OnUserMetricsRecordAction(const std::string& action);
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.cc b/content/browser/renderer_host/render_widget_host_input_event_router.cc
index 41dd994..c552643 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.cc
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.cc
@@ -376,7 +376,7 @@
     mouse_capture_target_ = nullptr;
 
   if (view == touchscreen_gesture_target_)
-    touchscreen_gesture_target_ = nullptr;
+    SetTouchscreenGestureTarget(nullptr);
 
   if (view == touchpad_gesture_target_)
     touchpad_gesture_target_ = nullptr;
@@ -417,6 +417,9 @@
   for (auto entry : owner_map_)
     entry.second->RemoveObserver(this);
   owner_map_.clear();
+  viz::HostFrameSinkManager* manager = GetHostFrameSinkManager();
+  if (manager)
+    manager->RemoveHitTestRegionObserver(this);
 }
 
 RenderWidgetHostInputEventRouter::HittestDelegate::HittestDelegate(
@@ -451,7 +454,11 @@
       event_targeter_(std::make_unique<RenderWidgetTargeter>(this)),
       use_viz_hit_test_(features::IsVizHitTestingEnabled()),
       touch_event_ack_queue_(new TouchEventAckQueue(this)),
-      weak_ptr_factory_(this) {}
+      weak_ptr_factory_(this) {
+  viz::HostFrameSinkManager* manager = GetHostFrameSinkManager();
+  DCHECK(manager);
+  manager->AddHitTestRegionObserver(this);
+}
 
 RenderWidgetHostInputEventRouter::~RenderWidgetHostInputEventRouter() {
   // We may be destroyed before some of the owners in the map, so we must
@@ -1522,7 +1529,6 @@
     // unique_touch_event_id of 0. They must have a non-null target in order
     // to get the coordinate transform.
     DCHECK(target);
-    touchscreen_gesture_target_ = target;
     fallback_target_location = target_location;
   } else if (no_matching_id && is_gesture_start) {
     // A long-standing Windows issues where occasionally a GestureStart is
@@ -1542,20 +1548,27 @@
     // Re https://crbug.com/796656): Since we are already in an error case,
     // don't worry about the fact we're ignoring |result.should_query_view|, as
     // this is the best we can do until we fix https://crbug.com/595422.
-    touchscreen_gesture_target_ = result.view;
+    target = result.view;
     fallback_target_location = transformed_point;
   } else if (is_gesture_start) {
-    touchscreen_gesture_target_ = gesture_target_it->second;
+    target = gesture_target_it->second;
     touchscreen_gesture_target_map_.erase(gesture_target_it);
 
     // Abort any scroll bubbling in progress to avoid double entry.
-    CancelScrollBubblingIfConflicting(touchscreen_gesture_target_);
+    CancelScrollBubblingIfConflicting(target);
+  }
+
+  if (gesture_event.unique_touch_event_id == 0 || is_gesture_start) {
+    bool moved_recently = touchscreen_gesture_target_moved_recently_;
+    if (is_gesture_start)
+      moved_recently = target->ScreenRectIsUnstableFor(gesture_event);
+    SetTouchscreenGestureTarget(target, moved_recently);
   }
 
   // If we set a target and it's not in the map, we won't get notified if the
   // target goes away, so drop the target and the resulting events.
   if (touchscreen_gesture_target_ && !IsViewInMap(touchscreen_gesture_target_))
-    touchscreen_gesture_target_ = nullptr;
+    SetTouchscreenGestureTarget(nullptr);
 
   if (!touchscreen_gesture_target_) {
     root_view->GestureEventAck(gesture_event,
@@ -1564,6 +1577,8 @@
   }
 
   blink::WebGestureEvent event(gesture_event);
+  if (touchscreen_gesture_target_moved_recently_)
+    event.SetTargetFrameMovedRecently();
 
   gfx::PointF point_in_target;
   // This |fallback_target_location| is fast path when
@@ -1601,7 +1616,7 @@
       gesture_event.GetType() == blink::WebInputEvent::kGestureFlingStart;
 
   if (is_gesture_end)
-    touchscreen_gesture_target_ = nullptr;
+    SetTouchscreenGestureTarget(nullptr);
 }
 
 void RenderWidgetHostInputEventRouter::RouteTouchscreenGestureEvent(
@@ -1789,12 +1804,21 @@
   events_being_flushed_ = events_being_flushed;
 }
 
+void RenderWidgetHostInputEventRouter::SetTouchscreenGestureTarget(
+    RenderWidgetHostViewBase* target,
+    bool moved_recently) {
+  touchscreen_gesture_target_ = target;
+  touchscreen_gesture_target_moved_recently_ = moved_recently;
+}
+
 void RenderWidgetHostInputEventRouter::DispatchEventToTarget(
     RenderWidgetHostViewBase* root_view,
     RenderWidgetHostViewBase* target,
     const blink::WebInputEvent& event,
     const ui::LatencyInfo& latency,
     const base::Optional<gfx::PointF>& target_location) {
+  if (target && target->ScreenRectIsUnstableFor(event))
+    event.SetTargetFrameMovedRecently();
   if (blink::WebInputEvent::IsMouseEventType(event.GetType())) {
     DispatchMouseEvent(root_view, target,
                        static_cast<const blink::WebMouseEvent&>(event), latency,
@@ -1909,6 +1933,16 @@
   rwhi->ShowContextMenuAtPoint(point, source_type);
 }
 
+void RenderWidgetHostInputEventRouter::OnAggregatedHitTestRegionListUpdated(
+    const viz::FrameSinkId& frame_sink_id,
+    const std::vector<viz::AggregatedHitTestRegion>& hit_test_data) {
+  for (auto& region : hit_test_data) {
+    auto iter = owner_map_.find(region.frame_sink_id);
+    if (iter != owner_map_.end())
+      iter->second->NotifyHitTestRegionUpdated(region);
+  }
+}
+
 void RenderWidgetHostInputEventRouter::SetMouseCaptureTarget(
     RenderWidgetHostViewBase* target,
     bool capture) {
diff --git a/content/browser/renderer_host/render_widget_host_input_event_router.h b/content/browser/renderer_host/render_widget_host_input_event_router.h
index 3fee278b..6afcdf10 100644
--- a/content/browser/renderer_host/render_widget_host_input_event_router.h
+++ b/content/browser/renderer_host/render_widget_host_input_event_router.h
@@ -17,6 +17,7 @@
 #include "base/memory/weak_ptr.h"
 #include "components/viz/common/surfaces/surface_id.h"
 #include "components/viz/host/hit_test/hit_test_query.h"
+#include "components/viz/host/hit_test/hit_test_region_observer.h"
 #include "components/viz/service/surfaces/surface_hittest_delegate.h"
 #include "content/browser/renderer_host/event_with_latency_info.h"
 #include "content/browser/renderer_host/input/touch_emulator_client.h"
@@ -74,7 +75,8 @@
 class CONTENT_EXPORT RenderWidgetHostInputEventRouter
     : public RenderWidgetHostViewBaseObserver,
       public RenderWidgetTargeter::Delegate,
-      public TouchEmulatorClient {
+      public TouchEmulatorClient,
+      public viz::HitTestRegionObserver {
  public:
   RenderWidgetHostInputEventRouter();
   ~RenderWidgetHostInputEventRouter() final;
@@ -166,6 +168,11 @@
   void ShowContextMenuAtPoint(const gfx::Point& point,
                               const ui::MenuSourceType source_type) override;
 
+  // HitTestRegionObserver
+  void OnAggregatedHitTestRegionListUpdated(
+      const viz::FrameSinkId& frame_sink_id,
+      const std::vector<viz::AggregatedHitTestRegion>& hit_test_data) override;
+
   bool HasEventsPendingDispatch() const;
 
   size_t TouchEventAckQueueLengthForTesting() const;
@@ -328,10 +335,14 @@
     return forced_last_fling_start_target_to_stop_flinging_for_test_;
   }
 
+  void SetTouchscreenGestureTarget(RenderWidgetHostViewBase* target,
+                                   bool moved_recently = false);
+
   FrameSinkIdOwnerMap owner_map_;
   TargetMap touchscreen_gesture_target_map_;
   RenderWidgetHostViewBase* touch_target_ = nullptr;
   RenderWidgetHostViewBase* touchscreen_gesture_target_ = nullptr;
+  bool touchscreen_gesture_target_moved_recently_ = false;
   RenderWidgetHostViewBase* touchpad_gesture_target_ = nullptr;
   RenderWidgetHostViewBase* bubbling_gesture_scroll_target_ = nullptr;
   RenderWidgetHostViewChildFrame* bubbling_gesture_scroll_origin_ = nullptr;
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index a2902ad..5a3b90ec 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -624,6 +624,11 @@
     host()->DidProcessFrame(frame_token);
 }
 
+bool RenderWidgetHostViewBase::ScreenRectIsUnstableFor(
+    const blink::WebInputEvent& event) {
+  return false;
+}
+
 viz::FrameSinkId RenderWidgetHostViewBase::FrameSinkIdAtPoint(
     viz::SurfaceHittestDelegate* delegate,
     const gfx::PointF& point,
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index 81bdc91..a5f11d9 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -336,6 +336,14 @@
   virtual const viz::LocalSurfaceIdAllocation& GetLocalSurfaceIdAllocation()
       const = 0;
 
+  // Called whenever the browser receives updated hit test data from viz.
+  virtual void NotifyHitTestRegionUpdated(
+      const viz::AggregatedHitTestRegion& region) {}
+
+  // Indicates whether the widget has resized or moved within its embedding
+  // page during the 500 milliseconds prior to the event.
+  virtual bool ScreenRectIsUnstableFor(const blink::WebInputEvent& event);
+
   // When there are multiple RenderWidgetHostViews for a single page, input
   // events need to be targeted to the correct one for handling. The following
   // methods are invoked on the RenderWidgetHostView that should be able to
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.cc b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
index f09d356..1eaeb082 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.cc
@@ -678,6 +678,36 @@
   return viz::ParentLocalSurfaceIdAllocator::InvalidLocalSurfaceIdAllocation();
 }
 
+void RenderWidgetHostViewChildFrame::NotifyHitTestRegionUpdated(
+    const viz::AggregatedHitTestRegion& region) {
+  gfx::RectF screen_rect(region.rect);
+  if (!region.transform().TransformRectReverse(&screen_rect)) {
+    last_stable_screen_rect_ = gfx::RectF();
+    screen_rect_stable_since_ = base::TimeTicks::Now();
+    return;
+  }
+  if ((ToRoundedSize(screen_rect.size()) !=
+       ToRoundedSize(last_stable_screen_rect_.size())) ||
+      (std::abs(last_stable_screen_rect_.x() - screen_rect.x()) +
+           std::abs(last_stable_screen_rect_.y() - screen_rect.y()) >
+       blink::kMaxChildFrameScreenRectMovement)) {
+    last_stable_screen_rect_ = screen_rect;
+    screen_rect_stable_since_ = base::TimeTicks::Now();
+  }
+}
+
+bool RenderWidgetHostViewChildFrame::ScreenRectIsUnstableFor(
+    const blink::WebInputEvent& event) {
+  if (event.TimeStamp() -
+          base::TimeDelta::FromMilliseconds(blink::kMinScreenRectStableTimeMs) <
+      screen_rect_stable_since_) {
+    return true;
+  }
+  if (RenderWidgetHostViewBase* parent = GetParentView())
+    return parent->ScreenRectIsUnstableFor(event);
+  return false;
+}
+
 void RenderWidgetHostViewChildFrame::PreProcessTouchEvent(
     const blink::WebTouchEvent& event) {
   if (event.GetType() == blink::WebInputEvent::kTouchStart &&
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame.h b/content/browser/renderer_host/render_widget_host_view_child_frame.h
index e3f7971..2658bd9 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame.h
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame.h
@@ -140,6 +140,8 @@
   const viz::FrameSinkId& GetFrameSinkId() const override;
   const viz::LocalSurfaceIdAllocation& GetLocalSurfaceIdAllocation()
       const override;
+  void NotifyHitTestRegionUpdated(const viz::AggregatedHitTestRegion&) override;
+  bool ScreenRectIsUnstableFor(const blink::WebInputEvent& event) override;
   void PreProcessTouchEvent(const blink::WebTouchEvent& event) override;
   viz::FrameSinkId GetRootFrameSinkId() override;
   viz::SurfaceId GetCurrentSurfaceId() const override;
@@ -311,6 +313,9 @@
   viz::mojom::CompositorFrameSinkClient* renderer_compositor_frame_sink_ =
       nullptr;
 
+  gfx::RectF last_stable_screen_rect_;
+  base::TimeTicks screen_rect_stable_since_;
+
   gfx::Insets insets_;
 
   std::unique_ptr<TouchSelectionControllerClientChildFrame>
diff --git a/content/browser/renderer_host/render_widget_targeter.cc b/content/browser/renderer_host/render_widget_targeter.cc
index 2f540fb..0749033 100644
--- a/content/browser/renderer_host/render_widget_targeter.cc
+++ b/content/browser/renderer_host/render_widget_targeter.cc
@@ -465,6 +465,7 @@
     FlushEventQueue(true);
     return;
   }
+
   delegate_->DispatchEventToTarget(root_view, target, event, latency,
                                    target_location);
   FlushEventQueue(false);
diff --git a/content/browser/renderer_host/web_database_host_impl.cc b/content/browser/renderer_host/web_database_host_impl.cc
index 5405fef8..195dd66 100644
--- a/content/browser/renderer_host/web_database_host_impl.cc
+++ b/content/browser/renderer_host/web_database_host_impl.cc
@@ -17,7 +17,7 @@
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/origin_util.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "storage/browser/database/database_util.h"
 #include "storage/browser/database/vfs_backend.h"
 #include "storage/browser/quota/quota_manager.h"
@@ -90,11 +90,11 @@
 void WebDatabaseHostImpl::Create(
     int process_id,
     scoped_refptr<storage::DatabaseTracker> db_tracker,
-    blink::mojom::WebDatabaseHostRequest request) {
+    mojo::PendingReceiver<blink::mojom::WebDatabaseHost> receiver) {
   DCHECK(db_tracker->task_runner()->RunsTasksInCurrentSequence());
-  mojo::MakeStrongBinding(
+  mojo::MakeSelfOwnedReceiver(
       std::make_unique<WebDatabaseHostImpl>(process_id, std::move(db_tracker)),
-      std::move(request));
+      std::move(receiver));
 }
 
 void WebDatabaseHostImpl::OpenFile(const base::string16& vfs_file_name,
@@ -450,15 +450,17 @@
     base::PostTaskWithTraits(
         FROM_HERE, {BrowserThread::UI},
         base::BindOnce(
-            [](int process_id, blink::mojom::WebDatabaseRequest request) {
+            [](int process_id,
+               mojo::PendingReceiver<blink::mojom::WebDatabase> receiver) {
               RenderProcessHost* host = RenderProcessHost::FromID(process_id);
               if (host) {
-                content::BindInterface(host, std::move(request));
+                host->BindInterface(blink::mojom::WebDatabase::Name_,
+                                    receiver.PassPipe());
               }
             },
-            process_id_, mojo::MakeRequest(&database_provider_)));
+            process_id_, database_provider_.BindNewPipeAndPassReceiver()));
   }
-  return *database_provider_;
+  return *database_provider_.get();
 }
 
 void WebDatabaseHostImpl::ValidateOrigin(const url::Origin& origin,
diff --git a/content/browser/renderer_host/web_database_host_impl.h b/content/browser/renderer_host/web_database_host_impl.h
index 546eac43..4f4b790 100644
--- a/content/browser/renderer_host/web_database_host_impl.h
+++ b/content/browser/renderer_host/web_database_host_impl.h
@@ -12,6 +12,8 @@
 #include "build/build_config.h"
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "storage/browser/database/database_tracker.h"
 #include "third_party/blink/public/mojom/webdatabase/web_database.mojom.h"
 
@@ -29,9 +31,10 @@
                       scoped_refptr<storage::DatabaseTracker> db_tracker);
   ~WebDatabaseHostImpl() override;
 
-  static void Create(int process_id,
-                     scoped_refptr<storage::DatabaseTracker> db_tracker,
-                     blink::mojom::WebDatabaseHostRequest request);
+  static void Create(
+      int process_id,
+      scoped_refptr<storage::DatabaseTracker> db_tracker,
+      mojo::PendingReceiver<blink::mojom::WebDatabaseHost> receiver);
 
  private:
   FRIEND_TEST_ALL_PREFIXES(WebDatabaseHostImplTest, BadMessagesUnauthorized);
@@ -148,7 +151,7 @@
   storage::DatabaseConnections database_connections_;
 
   // Interface to the render process WebDatabase.
-  blink::mojom::WebDatabasePtr database_provider_;
+  mojo::Remote<blink::mojom::WebDatabase> database_provider_;
 
   // The database tracker for the current browser context.
   const scoped_refptr<storage::DatabaseTracker> db_tracker_;
diff --git a/content/browser/service_worker/service_worker_provider_host.cc b/content/browser/service_worker/service_worker_provider_host.cc
index 0042e01..0f28cbdc 100644
--- a/content/browser/service_worker/service_worker_provider_host.cc
+++ b/content/browser/service_worker/service_worker_provider_host.cc
@@ -203,13 +203,11 @@
     scoped_refptr<ServiceWorkerVersion> version,
     blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr*
         out_provider_info) {
-  blink::mojom::ServiceWorkerContainerAssociatedPtrInfo client_ptr_info;
-  (*out_provider_info)->client_request = mojo::MakeRequest(&client_ptr_info);
   auto host = base::WrapUnique(new ServiceWorkerProviderHost(
       blink::mojom::ServiceWorkerProviderType::kForServiceWorker,
-      true /* is_parent_frame_secure */, FrameTreeNode::kFrameTreeNodeInvalidId,
+      /*is_parent_frame_secure=*/true, FrameTreeNode::kFrameTreeNodeInvalidId,
       mojo::MakeRequest(&((*out_provider_info)->host_ptr_info)),
-      std::move(client_ptr_info), context));
+      /*client_ptr_info=*/nullptr, context));
   host->running_hosted_version_ = std::move(version);
 
   auto weak_ptr = host->AsWeakPtr();
@@ -284,11 +282,15 @@
     // controller, and |render_thread_id_| will be set when the service worker
     // context gets started.
     render_thread_id_ = kInvalidEmbeddedWorkerThreadId;
+
+    DCHECK(!client_ptr_info);
+  } else {
+    DCHECK(client_ptr_info.is_valid());
   }
 
   context_->RegisterProviderHostByClientID(client_uuid_, this);
 
-  DCHECK(client_ptr_info.is_valid() && host_request.is_pending());
+  DCHECK(host_request.is_pending());
   container_.Bind(std::move(client_ptr_info));
   binding_.Bind(std::move(host_request));
 }
diff --git a/content/browser/service_worker/service_worker_test_utils.cc b/content/browser/service_worker/service_worker_test_utils.cc
index 8495aec..2699d8b1 100644
--- a/content/browser/service_worker/service_worker_test_utils.cc
+++ b/content/browser/service_worker/service_worker_test_utils.cc
@@ -233,7 +233,6 @@
 
 void ServiceWorkerRemoteProviderEndpoint::BindForServiceWorker(
     blink::mojom::ServiceWorkerProviderInfoForStartWorkerPtr info) {
-  client_request_ = std::move(info->client_request);
   host_ptr_.Bind(std::move(info->host_ptr_info));
 }
 
diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc
index 7a540c1..77aefd5 100644
--- a/content/browser/web_contents/web_contents_impl_browsertest.cc
+++ b/content/browser/web_contents/web_contents_impl_browsertest.cc
@@ -30,6 +30,7 @@
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/browser/web_contents/web_contents_view.h"
 #include "content/common/frame_messages.h"
+#include "content/common/unfreezable_frame_messages.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/file_select_listener.h"
 #include "content/public/browser/invalidate_type.h"
@@ -3042,6 +3043,44 @@
   EXPECT_GT(next_text_length, text_length);
 }
 
+// Checks that UnfreezableFrameMsg IPCs are executed even when the page is
+// frozen.
+IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest, FrozenAndUnfrozenIPC) {
+  EXPECT_TRUE(embedded_test_server()->Start());
+
+  GURL url_a(embedded_test_server()->GetURL(
+      "a.com", "/cross_site_iframe_factory.html?a(b,c)"));
+  EXPECT_TRUE(NavigateToURL(shell(), url_a));
+  EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
+  RenderFrameHostImpl* rfh_a =
+      static_cast<WebContentsImpl*>(shell()->web_contents())
+          ->GetFrameTree()
+          ->root()
+          ->current_frame_host();
+
+  RenderFrameHostImpl* rfh_b = rfh_a->child_at(0)->current_frame_host();
+  RenderFrameHostImpl* rfh_c = rfh_a->child_at(1)->current_frame_host();
+  RenderFrameDeletedObserver delete_rfh_b(rfh_b);
+  RenderFrameDeletedObserver delete_rfh_c(rfh_c);
+
+  // Delete an iframe when the page is active(not frozen), which should succeed.
+  rfh_b->Send(new UnfreezableFrameMsg_Delete(
+      rfh_b->routing_id(), FrameDeleteIntention::kNotMainFrame));
+  delete_rfh_b.WaitUntilDeleted();
+  EXPECT_TRUE(delete_rfh_b.deleted());
+  EXPECT_FALSE(delete_rfh_c.deleted());
+
+  // Freeze the blink page.
+  shell()->web_contents()->WasHidden();
+  shell()->web_contents()->SetPageFrozen(true);
+
+  // Try to delete an iframe, and succeeds because the message is unfreezable.
+  rfh_c->Send(new UnfreezableFrameMsg_Delete(
+      rfh_c->routing_id(), FrameDeleteIntention::kNotMainFrame));
+  delete_rfh_c.WaitUntilDeleted();
+  EXPECT_TRUE(delete_rfh_c.deleted());
+}
+
 IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTest,
                        PopupWindowBrowserNavResumeLoad) {
   // This test verifies a pop up that requires navigation from browser side
@@ -3480,7 +3519,7 @@
   std::vector<int> deleted_routing_ids;
   auto watcher = base::BindRepeating(
       [](std::vector<int>* deleted_routing_ids, const IPC::Message& message) {
-        if (message.type() == FrameMsg_Delete::ID) {
+        if (message.type() == UnfreezableFrameMsg_Delete::ID) {
           deleted_routing_ids->push_back(message.routing_id());
         }
       },
diff --git a/content/browser/web_package/prefetched_signed_exchange_cache.cc b/content/browser/web_package/prefetched_signed_exchange_cache.cc
index 1517c606..8c61557 100644
--- a/content/browser/web_package/prefetched_signed_exchange_cache.cc
+++ b/content/browser/web_package/prefetched_signed_exchange_cache.cc
@@ -568,14 +568,9 @@
   return clone;
 }
 
-PrefetchedSignedExchangeCache::PrefetchedSignedExchangeCache() {
-  DCHECK(base::FeatureList::IsEnabled(
-             features::kSignedExchangeSubresourcePrefetch) ||
-         base::FeatureList::IsEnabled(
-             features::kSignedExchangePrefetchCacheForNavigations));
-}
+PrefetchedSignedExchangeCache::PrefetchedSignedExchangeCache() = default;
 
-PrefetchedSignedExchangeCache::~PrefetchedSignedExchangeCache() {}
+PrefetchedSignedExchangeCache::~PrefetchedSignedExchangeCache() = default;
 
 void PrefetchedSignedExchangeCache::Store(
     std::unique_ptr<const Entry> cached_exchange) {
@@ -658,13 +653,6 @@
                                                         const base::Time& now) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
-  if (!base::FeatureList::IsEnabled(
-          features::kSignedExchangeSubresourcePrefetch)) {
-    DCHECK(base::FeatureList::IsEnabled(
-        features::kSignedExchangePrefetchCacheForNavigations));
-    return std::vector<PrefetchedSignedExchangeInfo>();
-  }
-
   std::vector<PrefetchedSignedExchangeInfo> info_list;
   const url::Origin outer_url_origin = url::Origin::Create(outer_url);
   const url::Origin inner_url_origin = url::Origin::Create(inner_url);
diff --git a/content/browser/web_package/signed_exchange_utils.cc b/content/browser/web_package/signed_exchange_utils.cc
index a7248ef..f0b23a54 100644
--- a/content/browser/web_package/signed_exchange_utils.cc
+++ b/content/browser/web_package/signed_exchange_utils.cc
@@ -223,16 +223,12 @@
   std::string buf;
   std::string link_header;
   if (!is_fallback_redirect &&
-      base::FeatureList::IsEnabled(
-          features::kSignedExchangeSubresourcePrefetch) &&
       outer_response.headers) {
     outer_response.headers->GetNormalizedHeader("link", &link_header);
   }
   if (link_header.empty()) {
     buf = base::StringPrintf("HTTP/1.1 %d %s\r\n", 303, "See Other");
   } else {
-    DCHECK(base::FeatureList::IsEnabled(
-        features::kSignedExchangeSubresourcePrefetch));
     buf = base::StringPrintf(
         "HTTP/1.1 %d %s\r\n"
         "link: %s\r\n",
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index 831ca5d..bf3a9ee4 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -356,8 +356,8 @@
   if (base::FeatureList::IsEnabled(features::kBuiltInModuleKvStorage))
     WebRuntimeFeatures::EnableBuiltInModuleKvStorage(true);
 
-  if (base::FeatureList::IsEnabled(blink::features::kLayoutNG))
-    WebRuntimeFeatures::EnableLayoutNG(true);
+  WebRuntimeFeatures::EnableFeatureFromString(
+      "LayoutNG", base::FeatureList::IsEnabled(blink::features::kLayoutNG));
 
   WebRuntimeFeatures::EnableLazyInitializeMediaControls(
       base::FeatureList::IsEnabled(features::kLazyInitializeMediaControls));
diff --git a/content/common/BUILD.gn b/content/common/BUILD.gn
index a9ffc95..e0efc58 100644
--- a/content/common/BUILD.gn
+++ b/content/common/BUILD.gn
@@ -249,6 +249,8 @@
     "view_messages.h",
     "visual_properties.cc",
     "visual_properties.h",
+    "web_package/signed_exchange_utils.cc",
+    "web_package/signed_exchange_utils.h",
     "widget_messages.h",
   ]
 
diff --git a/content/common/cursors/webcursor_aurawin.cc b/content/common/cursors/webcursor_aurawin.cc
index 435b85c..a41028e2 100644
--- a/content/common/cursors/webcursor_aurawin.cc
+++ b/content/common/cursors/webcursor_aurawin.cc
@@ -18,7 +18,7 @@
   if (platform_cursor_)
     return platform_cursor_;
 
-  platform_cursor_ = IconUtil::CreateCursorFromSkBitmap(info_.custom_image,
+  platform_cursor_ = IconUtil::CreateCursorFromSkBitmap(cursor.GetBitmap(),
                                                         cursor.GetHotspot())
                          .release();
   return platform_cursor_;
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index ec5376b..06dfed6 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -796,9 +796,6 @@
 // commit, activation and frame swap of the current DOM tree in blink.
 IPC_MESSAGE_ROUTED1(FrameMsg_VisualStateRequest, uint64_t /* id */)
 
-// Instructs the renderer to delete the RenderFrame.
-IPC_MESSAGE_ROUTED1(FrameMsg_Delete, content::FrameDeleteIntention)
-
 // Instructs the renderer to invoke the frame's beforeunload event handler.
 // Expects the result to be returned via FrameHostMsg_BeforeUnload_ACK.
 IPC_MESSAGE_ROUTED1(FrameMsg_BeforeUnload, bool /* is_reload */)
diff --git a/content/common/unfreezable_frame_messages.h b/content/common/unfreezable_frame_messages.h
index df3a4a9..a8b3665 100644
--- a/content/common/unfreezable_frame_messages.h
+++ b/content/common/unfreezable_frame_messages.h
@@ -27,4 +27,7 @@
                     bool /* is_loading */,
                     content::FrameReplicationState /* replication_state */)
 
+// Instructs the renderer to delete the RenderFrame.
+IPC_MESSAGE_ROUTED1(UnfreezableFrameMsg_Delete, content::FrameDeleteIntention)
+
 #endif  // CONTENT_COMMON_UNFREEZABLE_FRAME_MESSAGES_H_
diff --git a/content/common/web_package/OWNERS b/content/common/web_package/OWNERS
new file mode 100644
index 0000000..b02bd90
--- /dev/null
+++ b/content/common/web_package/OWNERS
@@ -0,0 +1,4 @@
+file://content/browser/web_package/OWNERS
+
+# TEAM: loading-dev@chromium.org
+# COMPONENT: Blink>Loader>WebPackaging
diff --git a/content/common/web_package/signed_exchange_utils.cc b/content/common/web_package/signed_exchange_utils.cc
new file mode 100644
index 0000000..5b39ec6
--- /dev/null
+++ b/content/common/web_package/signed_exchange_utils.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/common/web_package/signed_exchange_utils.h"
+
+#include "base/base64.h"
+
+namespace content {
+namespace signed_exchange_utils {
+
+std::string CreateHeaderIntegrityHashString(
+    const net::SHA256HashValue& header_integrity) {
+  std::string header_integrity_base64;
+  base::Base64Encode(
+      base::StringPiece(reinterpret_cast<const char*>(header_integrity.data),
+                        sizeof(header_integrity.data)),
+      &header_integrity_base64);
+  return std::string("sha256-") + header_integrity_base64;
+}
+
+}  // namespace signed_exchange_utils
+}  // namespace content
diff --git a/content/common/web_package/signed_exchange_utils.h b/content/common/web_package/signed_exchange_utils.h
new file mode 100644
index 0000000..a60d759f
--- /dev/null
+++ b/content/common/web_package/signed_exchange_utils.h
@@ -0,0 +1,23 @@
+// 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 CONTENT_COMMON_WEB_PACKAGE_SIGNED_EXCHANGE_UTILS_H_
+#define CONTENT_COMMON_WEB_PACKAGE_SIGNED_EXCHANGE_UTILS_H_
+
+#include <string>
+
+#include "net/base/hash_value.h"
+
+namespace content {
+namespace signed_exchange_utils {
+
+// Serializes the |header_integrity| hash in the form of
+// "sha256-<base64-hash-value>".
+std::string CreateHeaderIntegrityHashString(
+    const net::SHA256HashValue& header_integrity);
+
+}  // namespace signed_exchange_utils
+}  // namespace content
+
+#endif  // CONTENT_COMMON_WEB_PACKAGE_SIGNED_EXCHANGE_UTILS_H_
diff --git a/content/renderer/loader/navigation_body_loader.cc b/content/renderer/loader/navigation_body_loader.cc
index 9376447..5cbaa35 100644
--- a/content/renderer/loader/navigation_body_loader.cc
+++ b/content/renderer/loader/navigation_body_loader.cc
@@ -53,7 +53,7 @@
                                    redirect_info, redirect_response);
     WebURLLoaderImpl::PopulateURLResponse(
         url, redirect_response, &redirect.redirect_response,
-        false /* report_security_info */, request_id);
+        response_head.ssl_info.has_value(), request_id);
     if (url.SchemeIs(url::kDataScheme))
       redirect.redirect_response.SetHttpStatusCode(200);
     redirect.new_url = redirect_info.new_url;
@@ -69,7 +69,7 @@
 
   WebURLLoaderImpl::PopulateURLResponse(
       url, response_head, &navigation_params->response,
-      false /* report_security_info */, request_id);
+      response_head.ssl_info.has_value(), request_id);
   if (url.SchemeIs(url::kDataScheme))
     navigation_params->response.SetHttpStatusCode(200);
 
diff --git a/content/renderer/loader/navigation_body_loader_unittest.cc b/content/renderer/loader/navigation_body_loader_unittest.cc
index 18ee5033..77655230 100644
--- a/content/renderer/loader/navigation_body_loader_unittest.cc
+++ b/content/renderer/loader/navigation_body_loader_unittest.cc
@@ -9,6 +9,9 @@
 #include "base/run_loop.h"
 #include "base/single_thread_task_runner.h"
 #include "base/test/scoped_task_environment.h"
+#include "net/cert/x509_util.h"
+#include "net/ssl/ssl_connection_status_flags.h"
+#include "net/test/cert_test_util.h"
 #include "services/network/public/cpp/url_loader_completion_status.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
@@ -285,6 +288,41 @@
   EXPECT_TRUE(error_.has_value());
 }
 
+// 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();
+  net::CertificateList certs;
+  ASSERT_TRUE(net::LoadCertificateFiles(
+      {"subjectAltName_sanity_check.pem", "root_ca_cert.pem"}, &certs));
+  ASSERT_EQ(2U, certs.size());
+
+  base::StringPiece cert0_der =
+      net::x509_util::CryptoBufferAsStringPiece(certs[0]->cert_buffer());
+  base::StringPiece cert1_der =
+      net::x509_util::CryptoBufferAsStringPiece(certs[1]->cert_buffer());
+
+  response.ssl_info->cert =
+      net::X509Certificate::CreateFromDERCertChain({cert0_der, cert1_der});
+  net::SSLConnectionStatusSetVersion(net::SSL_CONNECTION_VERSION_TLS1_2,
+                                     &response.ssl_info->connection_status);
+
+  CommonNavigationParams common_params;
+  common_params.url = GURL("https://example.test");
+
+  blink::WebNavigationParams navigation_params;
+  auto endpoints = network::mojom::URLLoaderClientEndpoints::New();
+  NavigationBodyLoader::FillNavigationParamsResponseAndBodyLoader(
+      common_params, CommitNavigationParams(), 1 /* request_id */, response,
+      mojo::ScopedDataPipeConsumerHandle() /* response_body */,
+      std::move(endpoints),
+      blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
+      2 /* render_frame_id */, true /* is_main_frame */, &navigation_params);
+  EXPECT_TRUE(
+      navigation_params.response.SecurityDetailsForTesting().has_value());
+}
+
 }  // namespace
 
 }  // namespace content
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 4217051..f03ce01d8 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -11,7 +11,6 @@
 #include <vector>
 
 #include "base/auto_reset.h"
-#include "base/base64.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
 #include "base/command_line.h"
@@ -66,7 +65,9 @@
 #include "content/common/savable_subframe.h"
 #include "content/common/service_worker/service_worker_types.h"
 #include "content/common/swapped_out_messages.h"
+#include "content/common/unfreezable_frame_messages.h"
 #include "content/common/view_messages.h"
+#include "content/common/web_package/signed_exchange_utils.h"
 #include "content/public/common/bind_interface_helpers.h"
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/content_constants.h"
@@ -494,8 +495,6 @@
   navigation_params->was_discarded = commit_params.was_discarded;
 
   if (!commit_params.prefetched_signed_exchanges.empty()) {
-    DCHECK(base::FeatureList::IsEnabled(
-        features::kSignedExchangeSubresourcePrefetch));
     navigation_params->prefetched_signed_exchanges = WebVector<std::unique_ptr<
         blink::WebNavigationParams::PrefetchedSignedExchange>>();
 
@@ -504,18 +503,13 @@
       WebURLLoaderImpl::PopulateURLResponse(
           exchange.inner_url, exchange.inner_response, &web_response,
           false /* report_security_info*/, -1 /* request_id */);
-      std::string header_integrity_base64;
-      base::Base64Encode(
-          base::StringPiece(
-              reinterpret_cast<const char*>(exchange.header_integrity.data),
-              sizeof(exchange.header_integrity.data)),
-          &header_integrity_base64);
       navigation_params->prefetched_signed_exchanges.emplace_back(
           std::make_unique<
               blink::WebNavigationParams::PrefetchedSignedExchange>(
               exchange.outer_url,
-              WebString::FromLatin1(std::string("sha256-") +
-                                    header_integrity_base64),
+              WebString::FromLatin1(
+                  signed_exchange_utils::CreateHeaderIntegrityHashString(
+                      exchange.header_integrity)),
               exchange.inner_url, web_response,
               mojo::ScopedMessagePipeHandle(exchange.loader_factory_handle)));
     }
@@ -2223,7 +2217,6 @@
     IPC_MESSAGE_HANDLER(FrameMsg_BeforeUnload, OnBeforeUnload)
     IPC_MESSAGE_HANDLER(FrameMsg_SwapOut, OnSwapOut)
     IPC_MESSAGE_HANDLER(FrameMsg_SwapIn, OnSwapIn)
-    IPC_MESSAGE_HANDLER(FrameMsg_Delete, OnDeleteFrame)
     IPC_MESSAGE_HANDLER(FrameMsg_Stop, OnStop)
     IPC_MESSAGE_HANDLER(FrameMsg_DroppedNavigation, OnDroppedNavigation)
     IPC_MESSAGE_HANDLER(FrameMsg_Collapse, OnCollapse)
@@ -2281,6 +2274,7 @@
     IPC_MESSAGE_HANDLER(FrameMsg_SelectPopupMenuItems, OnSelectPopupMenuItems)
 #endif
 #endif
+    IPC_MESSAGE_HANDLER(UnfreezableFrameMsg_Delete, OnDeleteFrame)
 
   IPC_END_MESSAGE_MAP()
 
diff --git a/content/renderer/render_frame_impl_browsertest.cc b/content/renderer/render_frame_impl_browsertest.cc
index 0ed20199..fb0791715 100644
--- a/content/renderer/render_frame_impl_browsertest.cc
+++ b/content/renderer/render_frame_impl_browsertest.cc
@@ -20,6 +20,7 @@
 #include "content/common/frame_messages.h"
 #include "content/common/frame_owner_properties.h"
 #include "content/common/renderer.mojom.h"
+#include "content/common/unfreezable_frame_messages.h"
 #include "content/common/widget_messages.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/previews_state.h"
@@ -459,7 +460,8 @@
       1, "foo", true /* match_case */, true /* forward */,
       false /* find_next */, true /* force */, false /* wrap_within_frame */);
 
-  FrameMsg_Delete delete_message(0, FrameDeleteIntention::kNotMainFrame);
+  UnfreezableFrameMsg_Delete delete_message(
+      0, FrameDeleteIntention::kNotMainFrame);
   frame()->OnMessageReceived(delete_message);
 }
 
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
index 28b5e17d..b37f47d 100644
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -2435,12 +2435,17 @@
 bool RenderThreadImpl::UnfreezableMessageFilter::OnMessageReceived(
     const IPC::Message& message) {
   if ((IPC_MESSAGE_CLASS(message) == UnfreezableFrameMsgStart)) {
-    return GetUnfreezableTaskRunner(message.routing_id())
-        ->PostTask(FROM_HERE,
-                   base::BindOnce(
-                       base::IgnoreResult(&RenderThreadImpl::OnMessageReceived),
-                       base::Unretained(render_thread_impl_), message));
+    auto task_runner = GetUnfreezableTaskRunner(message.routing_id());
+    if (task_runner)
+      return task_runner->PostTask(
+          FROM_HERE,
+          base::BindOnce(
+              base::IgnoreResult(&RenderThreadImpl::OnMessageReceived),
+              base::Unretained(render_thread_impl_), message));
   }
+  // If unfreezable task runner is not found or the message class is not
+  // UnfreezableFrameMsgStart, return false so that this filter is skipped and
+  // other handlers can continue executing and handle this message.
   return false;
 }
 
@@ -2464,7 +2469,7 @@
   auto it = unfreezable_task_runners_.find(routing_id);
   if (it != unfreezable_task_runners_.end())
     return it->second;
-  return base::ThreadTaskRunnerHandle::Get();
+  return nullptr;
 }
 
 RenderThreadImpl::UnfreezableMessageFilter::~UnfreezableMessageFilter() {}
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
index 2322a9ed..a0d2c24 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl2_conformance_expectations.txt
@@ -47,17 +47,18 @@
 # Conformance expectations
 # ========================
 
+# Recent regression of invariance rules
+# TODO(kbr): re-add suppressions below once this is removed.
+crbug.com/980675 conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
+
+crbug.com/953120 conformance/programs/program-handling.html [ Failure ]
+# TODO(kbr): restore suppression for Android / Qualcomm below after this is fixed.
+crbug.com/949249 conformance2/extensions/ovr_multiview2.html [ Failure ]
+
 # Failing new test
 crbug.com/874620 [ linux nvidia ] conformance2/glsl3/const-struct-from-array-as-function-parameter.html [ Failure ]
 crbug.com/874620 [ opengl win nvidia ] conformance2/glsl3/const-struct-from-array-as-function-parameter.html [ Failure ]
 
-# Unsuppress after both
-# https://chromium-review.googlesource.com/1558678 and
-# https://github.com/KhronosGroup/WebGL/pull/2878 land and are rolled
-# in.
-# TODO(kbr): this is actually crbug.com/angleproject/3285 .
-crbug.com/angleproject/3285 conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
-
 # Too slow (take about one hour to run)
 crbug.com/619403 deqp/functional/gles3/builtinprecision/* [ Skip ]
 
@@ -91,6 +92,9 @@
 crbug.com/angleproject/1465 [ win ] conformance2/glsl3/tricky-loop-conditions.html [ Failure ]
 crbug.com/951628 [ win no-passthrough ] conformance/rendering/blending.html [ Failure ]
 
+# Win / D3D11 backend
+crbug.com/angleproject/3388 [ win d3d11 ] conformance2/uniforms/large-uniform-buffers.html [ Failure ]
+
 # Win / NVidia
 crbug.com/631317 [ d3d11 win nvidia ] deqp/functional/gles3/fbomultisample* [ RetryOnFailure ]
 crbug.com/679639 [ d3d11 win nvidia ] conformance2/rendering/draw-with-integer-texture-base-level.html [ Failure ]
@@ -739,7 +743,8 @@
 
 # Qualcomm (Pixel 2) failures
 crbug.com/906737 [ android qualcomm ] conformance/extensions/webgl-compressed-texture-astc.html [ Failure ]
-crbug.com/906739 [ android qualcomm ] conformance2/extensions/ovr_multiview2.html [ Failure ]
+# TODO(kbr): uncomment after http://crbug.com/949249 fixed
+# crbug.com/906739 [ android qualcomm ] conformance2/extensions/ovr_multiview2.html [ Failure ]
 crbug.com/906742 [ android qualcomm ] conformance2/glsl3/compare-structs-containing-arrays.html [ Failure ]
 crbug.com/906735 [ android qualcomm ] conformance2/textures/video/tex-2d-rgba8ui-rgba_integer-unsigned_byte.html [ Failure ]
 crbug.com/949321 [ android qualcomm ] deqp/functional/gles3/framebufferblit/default_framebuffer_02.html [ RetryOnFailure ]
diff --git a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
index d039bae9..9d6a10d5 100644
--- a/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
+++ b/content/test/gpu/gpu_tests/test_expectations/webgl_conformance_expectations.txt
@@ -66,12 +66,9 @@
 # https://github.com/KhronosGroup/WebGL/pull/2607
 crbug.com/849572 conformance/extensions/angle-instanced-arrays-out-of-bounds.html [ Failure ]
 
-# Unsuppress after both
-# https://chromium-review.googlesource.com/1558678 and
-# https://github.com/KhronosGroup/WebGL/pull/2878 land and are rolled
-# in.
-# TODO(kbr): this is actually crbug.com/angleproject/3285 .
-crbug.com/angleproject/3285 conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
+# Recent regression of invariance rules
+# TODO(kbr): re-add suppressions below once this is removed.
+crbug.com/980675 conformance/glsl/misc/shaders-with-invariance.html [ Failure ]
 
 # Nvidia bugs fixed in latest driver
 # TODO(http://crbug.com/887241): Upgrade the drivers on the bots.
@@ -88,6 +85,7 @@
 crbug.com/735483 conformance/rendering/texture-switch-performance.html [ Skip ]
 crbug.com/951628 [ passthrough ] conformance/rendering/blending.html [ Failure ]
 
+crbug.com/953120 conformance/programs/program-handling.html [ Failure ]
 # TODO(shrekshao): Restore failure expectation for
 # ['win', 'nvidia', 'passthrough', 'd3d11'], bug=737016
 # as Flaky after 953120 is fixed.
diff --git a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
index 799efd0e..26c974a 100644
--- a/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
+++ b/content/test/gpu/gpu_tests/webgl_conformance_revision.txt
@@ -1,3 +1,3 @@
 # AUTOGENERATED FILE - DO NOT EDIT
 # SEE roll_webgl_conformance.py
-Current webgl revision 6f0b34abee8dba611c253738d955c59f703c147a
+Current webgl revision 91350f8ecf9ab2922ee062c114e4a759f24bd8d0
diff --git a/extensions/common/manifest_constants.cc b/extensions/common/manifest_constants.cc
index 4aa72fe0..897ef554 100644
--- a/extensions/common/manifest_constants.cc
+++ b/extensions/common/manifest_constants.cc
@@ -561,7 +561,7 @@
 const char kInvalidNativelyConnectable[] =
     "Invalid natively_connectable. Must be a list.";
 const char kInvalidNativelyConnectableValue[] =
-    "Invalid natively_connectable value. Must be a string.";
+    "Invalid natively_connectable value. Must be a non-empty string.";
 const char kInvalidNaClModules[] =
     "Invalid value for 'nacl_modules'.";
 const char kInvalidNaClModulesPath[] =
diff --git a/extensions/common/permissions/permissions_data.cc b/extensions/common/permissions/permissions_data.cc
index fe5e92c..fa8c20f 100644
--- a/extensions/common/permissions/permissions_data.cc
+++ b/extensions/common/permissions/permissions_data.cc
@@ -212,12 +212,6 @@
       default_policy_allowed_hosts.Clone();
 }
 
-void PermissionsData::SetActivePermissions(
-    std::unique_ptr<const PermissionSet> active) const {
-  AutoLockOnValidThread lock(runtime_lock_, thread_checker_.get());
-  active_permissions_unsafe_ = std::move(active);
-}
-
 void PermissionsData::UpdateTabSpecificPermissions(
     int tab_id,
     const PermissionSet& permissions) const {
diff --git a/extensions/common/permissions/permissions_data.h b/extensions/common/permissions/permissions_data.h
index 8131675d..e56cf72e 100644
--- a/extensions/common/permissions/permissions_data.h
+++ b/extensions/common/permissions/permissions_data.h
@@ -136,9 +136,6 @@
       const URLPatternSet& default_policy_blocked_hosts,
       const URLPatternSet& default_policy_allowed_hosts);
 
-  // Sets the active permissions, leaving withheld the same.
-  void SetActivePermissions(std::unique_ptr<const PermissionSet> active) const;
-
   // Updates the tab-specific permissions of |tab_id| to include those from
   // |permissions|.
   void UpdateTabSpecificPermissions(int tab_id,
diff --git a/google_apis/gaia/gaia_constants.cc b/google_apis/gaia/gaia_constants.cc
index 72c9da4..49f84ad 100644
--- a/google_apis/gaia/gaia_constants.cc
+++ b/google_apis/gaia/gaia_constants.cc
@@ -42,9 +42,9 @@
 const char kChromeSyncSupervisedOAuth2Scope[] =
     "https://www.googleapis.com/auth/chromesync_playpen";
 
-// OAuth2 scope for access to Google Family Link kid scope.
-const char kKidFamilyOAuth2Scope[] =
-    "https://www.googleapis.com/auth/kid.family.readonly";
+// OAuth2 scope for access to Google Family Link Supervision Setup.
+const char kKidsSupervisionSetupChildOAuth2Scope[] =
+    "https://www.googleapis.com/auth/kids.supervision.setup.child";
 
 // OAuth2 scope for access to Google Talk APIs (XMPP).
 const char kGoogleTalkOAuth2Scope[] =
@@ -55,7 +55,11 @@
 const char kGoogleUserInfoProfile[] =
     "https://www.googleapis.com/auth/userinfo.profile";
 
- // Used to mint uber auth tokens when needed.
+// OAuth scope for access to the people API (read-only).
+const char kPeopleApiReadOnlyOAuth2Scope[] =
+    "https://www.googleapis.com/auth/peopleapi.readonly";
+
+// Used to mint uber auth tokens when needed.
 const char kGaiaSid[] = "sid";
 const char kGaiaLsid[] = "lsid";
 const char kGaiaOAuthToken[] = "oauthToken";
diff --git a/google_apis/gaia/gaia_constants.h b/google_apis/gaia/gaia_constants.h
index 42e8076..d25d5ff5 100644
--- a/google_apis/gaia/gaia_constants.h
+++ b/google_apis/gaia/gaia_constants.h
@@ -26,10 +26,11 @@
 extern const char kAnyApiOAuth2Scope[];
 extern const char kChromeSyncOAuth2Scope[];
 extern const char kChromeSyncSupervisedOAuth2Scope[];
-extern const char kKidFamilyOAuth2Scope[];
+extern const char kKidsSupervisionSetupChildOAuth2Scope[];
 extern const char kGoogleTalkOAuth2Scope[];
 extern const char kGoogleUserInfoEmail[];
 extern const char kGoogleUserInfoProfile[];
+extern const char kPeopleApiReadOnlyOAuth2Scope[];
 
 // Used with uber auth tokens when needed.
 extern const char kGaiaSid[];
diff --git a/headless/test/data/protocol/sanity/renderer-javascript-console-errors-expected.txt b/headless/test/data/protocol/sanity/renderer-javascript-console-errors-expected.txt
index 82f579b2..6d50619 100644
--- a/headless/test/data/protocol/sanity/renderer-javascript-console-errors-expected.txt
+++ b/headless/test/data/protocol/sanity/renderer-javascript-console-errors-expected.txt
@@ -1,6 +1,6 @@
 Tests renderer: verify JavaScript console errors reporting.
 requested url: http://example.com/foobar
-Uncaught SyntaxError: Unexpected token <
+Uncaught SyntaxError: Unexpected token '<'
 Uncaught ReferenceError: func1 is not defined
 Uncaught ReferenceError: func2 is not defined
 Hello, Script!
diff --git a/headless/test/headless_browser_test.cc b/headless/test/headless_browser_test.cc
index 536f634..30bc779 100644
--- a/headless/test/headless_browser_test.cc
+++ b/headless/test/headless_browser_test.cc
@@ -214,9 +214,9 @@
 }
 
 void HeadlessBrowserTest::RunAsynchronousTest() {
-  base::MessageLoopCurrent::ScopedNestableTaskAllower nestable_allower;
   EXPECT_FALSE(run_loop_);
-  run_loop_ = std::make_unique<base::RunLoop>();
+  run_loop_ = std::make_unique<base::RunLoop>(
+      base::RunLoop::Type::kNestableTasksAllowed);
   PreRunAsynchronousTest();
   run_loop_->Run();
   PostRunAsynchronousTest();
diff --git a/headless/test/headless_protocol_browsertest.cc b/headless/test/headless_protocol_browsertest.cc
index 931fc0fe..503cba1 100644
--- a/headless/test/headless_protocol_browsertest.cc
+++ b/headless/test/headless_protocol_browsertest.cc
@@ -345,11 +345,8 @@
 HEADLESS_PROTOCOL_COMPOSITOR_TEST(
     RendererOverrideTitleJsDisabled,
     "sanity/renderer-override-title-js-disabled.js")
-// TODO(crbug.com/943636): rebaseline and re-enable once
-// https://chromium-review.googlesource.com/c/v8/v8/+/1593307 is rolled into
-// chromium.
 HEADLESS_PROTOCOL_COMPOSITOR_TEST(
-    DISABLED_RendererJavaScriptConsoleErrors,
+    RendererJavaScriptConsoleErrors,
     "sanity/renderer-javascript-console-errors.js")
 HEADLESS_PROTOCOL_COMPOSITOR_TEST(RendererDelayedCompletion,
                                   "sanity/renderer-delayed-completion.js")
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index 1835994..477063e 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -119,6 +119,7 @@
     "//base",
     "//components/strings",
     "//ios/chrome/browser/signin:feature_flags",
+    "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/collection_view",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/content_suggestions/cells:cells_ui",
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn
index 7b4f0ed4..3cce84d 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/cells/BUILD.gn
@@ -54,6 +54,7 @@
   deps = [
     "//base",
     "//ios/chrome/app/strings",
+    "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/collection_view",
     "//ios/chrome/browser/ui/colors",
     "//ios/chrome/browser/ui/content_suggestions/identifier",
diff --git a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_cell.mm b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_cell.mm
index 9233be5..a01a3d0 100644
--- a/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_cell.mm
+++ b/ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_cell.mm
@@ -4,6 +4,7 @@
 
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_cell.h"
 
+#import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/i18n_string.h"
 #include "ios/chrome/browser/ui/util/ui_util.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
@@ -71,15 +72,6 @@
 
 @implementation ContentSuggestionsCell
 
-@synthesize titleLabel = _titleLabel;
-@synthesize imageContainer = _imageContainer;
-@synthesize noImageIcon = _noImageIcon;
-@synthesize additionalInformationLabel = _additionalInformationLabel;
-@synthesize contentImageView = _contentImageView;
-@synthesize faviconView = _faviconView;
-@synthesize imageSizeConstraint = _imageSizeConstraint;
-@synthesize displayImage = _displayImage;
-
 - (instancetype)initWithFrame:(CGRect)frame {
   self = [super initWithFrame:frame];
   if (self) {
@@ -202,6 +194,10 @@
   CGFloat additionalInfoHeight =
       [additionalInfoLabel sizeThatFits:sizeForLabels].height;
   labelHeight += MAX(additionalInfoHeight, kFaviconSize);
+  // Add extra space at the bottom for separators between cells.
+  if (base::FeatureList::IsEnabled(kOptionalArticleThumbnail)) {
+    return MAX(minimalHeight, labelHeight) + kStandardSpacing;
+  }
   return MAX(minimalHeight, labelHeight);
 }
 
@@ -287,7 +283,7 @@
     [_faviconView.widthAnchor
         constraintEqualToAnchor:_faviconView.heightAnchor],
     [_faviconView.topAnchor
-        constraintGreaterThanOrEqualToAnchor:self.titleLabel.bottomAnchor
+        constraintGreaterThanOrEqualToAnchor:_titleLabel.bottomAnchor
                                     constant:kSmallSpacing],
 
     // No image icon.
@@ -299,6 +295,15 @@
     [_noImageIcon.heightAnchor constraintEqualToAnchor:_noImageIcon.widthAnchor]
   ]];
 
+  // Prevent _additionalInformationLabel from overlapping with _titleLabel when
+  // a11y font size is used.
+  if (base::FeatureList::IsEnabled(kOptionalArticleThumbnail)) {
+    [_additionalInformationLabel.topAnchor
+        constraintGreaterThanOrEqualToAnchor:_titleLabel.bottomAnchor
+                                    constant:kSmallSpacing]
+        .active = YES;
+  }
+
   AddSameConstraints(_contentImageView, _imageContainer);
 
   _imageTitleHorizontalSpacing = [_titleLabel.leadingAnchor
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
index e13f1274..f7cb214 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
@@ -228,8 +228,6 @@
     self.headerView =
         base::mac::ObjCCastStrict<ContentSuggestionsHeaderView>(self.view);
     [self addFakeTapView];
-    if (IsIdentityDiscFeatureEnabled())
-      [self addIdentityDisc];
     [self addFakeOmnibox];
 
     [self.headerView addSubview:self.logoVendor.view];
@@ -239,6 +237,11 @@
 
     [self.headerView addSeparatorToSearchField:self.fakeOmnibox];
 
+    // Identity disc needs to be added after the Google logo/doodle since it
+    // needs to respond to user taps first.
+    if (IsIdentityDiscFeatureEnabled())
+      [self addIdentityDisc];
+
     // -headerForView is regularly called before self.headerView has been added
     // to the view hierarchy, so there's no simple way to get the correct
     // safeAreaInsets.  Since this situation is universally called for the full
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
index ebe9d32f..adba584 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_view_controller.mm
@@ -25,6 +25,7 @@
 #import "ios/chrome/browser/ui/ntp_tile_views/ntp_tile_layout_util.h"
 #import "ios/chrome/browser/ui/overscroll_actions/overscroll_actions_controller.h"
 #import "ios/chrome/browser/ui/toolbar/public/toolbar_utils.h"
+#import "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/chrome/common/ui_util/constraints_ui_util.h"
 
@@ -398,12 +399,19 @@
   CGSize size = [super collectionView:collectionView
                                layout:collectionViewLayout
                sizeForItemAtIndexPath:indexPath];
+
+  // No need to add extra spacing if kOptionalArticleThumbnail is enabled,
+  // because each cell already has spacing at top and bottom for separators.
+  if (base::FeatureList::IsEnabled(kOptionalArticleThumbnail)) {
+    return size;
+  }
+
   // Special case for last item to add extra spacing before the footer.
   if ([self.collectionUpdater isContentSuggestionsSection:indexPath.section] &&
       indexPath.row ==
-          [self.collectionView numberOfItemsInSection:indexPath.section] - 1)
+          [self.collectionView numberOfItemsInSection:indexPath.section] - 1) {
     size.height += [ContentSuggestionsCell standardSpacing];
-
+  }
   return size;
 }
 
@@ -511,6 +519,12 @@
 
 - (BOOL)collectionView:(UICollectionView*)collectionView
     shouldHideItemSeparatorAtIndexPath:(NSIndexPath*)indexPath {
+  // If kOptionalArticleThumbnail is enabled, show separators for all cells in
+  // content suggestion sections.
+  if (base::FeatureList::IsEnabled(kOptionalArticleThumbnail)) {
+    return !
+        [self.collectionUpdater isContentSuggestionsSection:indexPath.section];
+  }
   // Special case, show a seperator between the last regular item and the
   // footer.
   if (![self.collectionUpdater
diff --git a/media/base/demuxer_stream.h b/media/base/demuxer_stream.h
index fff2f3a..83bdd16 100644
--- a/media/base/demuxer_stream.h
+++ b/media/base/demuxer_stream.h
@@ -73,6 +73,10 @@
   typedef base::Callback<void(Status, scoped_refptr<DecoderBuffer>)> ReadCB;
   virtual void Read(const ReadCB& read_cb) = 0;
 
+  // Returns true if a Read() call has been made but the |read_cb| has not yet
+  // been run.
+  virtual bool IsReadPending() const = 0;
+
   // Returns the audio/video decoder configuration. It is an error to call the
   // audio method on a video stream and vice versa. After |kConfigChanged| is
   // returned in a Read(), the caller should call this method again to retrieve
diff --git a/media/base/fake_demuxer_stream.cc b/media/base/fake_demuxer_stream.cc
index fe74fc7..5f601f1 100644
--- a/media/base/fake_demuxer_stream.cc
+++ b/media/base/fake_demuxer_stream.cc
@@ -79,6 +79,10 @@
   DoRead();
 }
 
+bool FakeDemuxerStream::IsReadPending() const {
+  return !read_cb_.is_null();
+}
+
 AudioDecoderConfig FakeDemuxerStream::audio_decoder_config() {
   DCHECK(task_runner_->BelongsToCurrentThread());
   NOTREACHED();
diff --git a/media/base/fake_demuxer_stream.h b/media/base/fake_demuxer_stream.h
index 88e9120..590b32a 100644
--- a/media/base/fake_demuxer_stream.h
+++ b/media/base/fake_demuxer_stream.h
@@ -30,6 +30,7 @@
 
   // DemuxerStream implementation.
   void Read(const ReadCB& read_cb) override;
+  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/base/fake_text_track_stream.cc b/media/base/fake_text_track_stream.cc
index e92287f..43178a6 100644
--- a/media/base/fake_text_track_stream.cc
+++ b/media/base/fake_text_track_stream.cc
@@ -37,6 +37,10 @@
   }
 }
 
+bool FakeTextTrackStream::IsReadPending() const {
+  return !read_cb_.is_null();
+}
+
 DemuxerStream::Type FakeTextTrackStream::type() const {
   return DemuxerStream::TEXT;
 }
diff --git a/media/base/fake_text_track_stream.h b/media/base/fake_text_track_stream.h
index d93e546..2e121c1c 100644
--- a/media/base/fake_text_track_stream.h
+++ b/media/base/fake_text_track_stream.h
@@ -26,6 +26,7 @@
 
   // DemuxerStream implementation.
   void Read(const ReadCB&) override;
+  bool IsReadPending() const override;
   MOCK_METHOD0(audio_decoder_config, AudioDecoderConfig());
   MOCK_METHOD0(video_decoder_config, VideoDecoderConfig());
   Type type() const override;
diff --git a/media/base/mock_filters.h b/media/base/mock_filters.h
index df5cb56..4b522e27 100644
--- a/media/base/mock_filters.h
+++ b/media/base/mock_filters.h
@@ -183,6 +183,7 @@
   Type type() const override;
   Liveness liveness() const override;
   MOCK_METHOD1(Read, void(const ReadCB& read_cb));
+  MOCK_CONST_METHOD0(IsReadPending, bool());
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   MOCK_METHOD0(EnableBitstreamConverter, void());
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 18255d9..843d4f54 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -307,6 +307,11 @@
   CompletePendingReadIfPossible_Locked();
 }
 
+bool ChunkDemuxerStream::IsReadPending() const {
+  base::AutoLock auto_lock(lock_);
+  return !read_cb_.is_null();
+}
+
 DemuxerStream::Type ChunkDemuxerStream::type() const { return type_; }
 
 DemuxerStream::Liveness ChunkDemuxerStream::liveness() const {
diff --git a/media/filters/chunk_demuxer.h b/media/filters/chunk_demuxer.h
index c7d06c3f6..f1951f01 100644
--- a/media/filters/chunk_demuxer.h
+++ b/media/filters/chunk_demuxer.h
@@ -119,6 +119,7 @@
 
   // DemuxerStream methods.
   void Read(const ReadCB& read_cb) override;
+  bool IsReadPending() const override;
   Type type() const override;
   Liveness liveness() const override;
   AudioDecoderConfig audio_decoder_config() override;
diff --git a/media/filters/decrypting_demuxer_stream.cc b/media/filters/decrypting_demuxer_stream.cc
index ba9fc08..c5dbfb7 100644
--- a/media/filters/decrypting_demuxer_stream.cc
+++ b/media/filters/decrypting_demuxer_stream.cc
@@ -89,6 +89,10 @@
       base::Bind(&DecryptingDemuxerStream::DecryptBuffer, weak_this_));
 }
 
+bool DecryptingDemuxerStream::IsReadPending() const {
+  return !read_cb_.is_null();
+}
+
 void DecryptingDemuxerStream::Reset(const base::Closure& closure) {
   DVLOG(2) << __func__ << " - state: " << state_;
   DCHECK(task_runner_->BelongsToCurrentThread());
diff --git a/media/filters/decrypting_demuxer_stream.h b/media/filters/decrypting_demuxer_stream.h
index b0e03731..14b0809 100644
--- a/media/filters/decrypting_demuxer_stream.h
+++ b/media/filters/decrypting_demuxer_stream.h
@@ -56,6 +56,7 @@
 
   // DemuxerStream implementation.
   void Read(const ReadCB& read_cb) override;
+  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
index 19b29d8..7514b53 100644
--- a/media/filters/ffmpeg_demuxer.cc
+++ b/media/filters/ffmpeg_demuxer.cc
@@ -760,6 +760,10 @@
   SatisfyPendingRead();
 }
 
+bool FFmpegDemuxerStream::IsReadPending() const {
+  return !read_cb_.is_null();
+}
+
 void FFmpegDemuxerStream::EnableBitstreamConverter() {
   DCHECK(task_runner_->BelongsToCurrentThread());
 
diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h
index 8246f09..322dc1a7 100644
--- a/media/filters/ffmpeg_demuxer.h
+++ b/media/filters/ffmpeg_demuxer.h
@@ -113,6 +113,7 @@
   Type type() const override;
   Liveness liveness() const override;
   void Read(const ReadCB& read_cb) override;
+  bool IsReadPending() const override;
   void EnableBitstreamConverter() override;
   bool SupportsConfigChanges() override;
   AudioDecoderConfig audio_decoder_config() override;
diff --git a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
index d1f4194..7262689 100644
--- a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
+++ b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.cc
@@ -436,6 +436,9 @@
   output_buffer_coded_size_.SetSize(format.fmt.pix_mp.width,
                                     format.fmt.pix_mp.height);
   output_buffer_num_planes_ = format.fmt.pix_mp.num_planes;
+  for (size_t i = 0; i < output_buffer_num_planes_; ++i) {
+    output_bytesperlines_[i] = format.fmt.pix_mp.plane_fmt[i].bytesperline;
+  }
 
   VideoPixelFormat output_format =
       V4L2Device::V4L2PixFmtToVideoPixelFormat(output_buffer_pixelformat_);
@@ -714,12 +717,13 @@
     }
   } else if (output_buffer_pixelformat_ == V4L2_PIX_FMT_YUV420M ||
              output_buffer_pixelformat_ == V4L2_PIX_FMT_YUV422M) {
+    DCHECK(output_buffer_num_planes_ == 3);
     uint8_t* src_y = static_cast<uint8_t*>(output_buffer.address[0]);
     uint8_t* src_u = static_cast<uint8_t*>(output_buffer.address[1]);
     uint8_t* src_v = static_cast<uint8_t*>(output_buffer.address[2]);
-    size_t src_y_stride = output_buffer_coded_size_.width();
-    size_t src_u_stride = output_buffer_coded_size_.width() / 2;
-    size_t src_v_stride = output_buffer_coded_size_.width() / 2;
+    size_t src_y_stride = output_bytesperlines_[0];
+    size_t src_u_stride = output_bytesperlines_[1];
+    size_t src_v_stride = output_bytesperlines_[2];
     if (output_buffer_pixelformat_ == V4L2_PIX_FMT_YUV420M) {
       if (libyuv::I420Copy(src_y, src_y_stride, src_u, src_u_stride, src_v,
                            src_v_stride, dst_y, dst_y_stride, dst_u,
diff --git a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h
index b0c9442c..3cdc97e 100644
--- a/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h
+++ b/media/gpu/v4l2/v4l2_mjpeg_decode_accelerator.h
@@ -139,6 +139,7 @@
 
   // Number of physical planes the output buffers have.
   size_t output_buffer_num_planes_;
+  size_t output_bytesperlines_[VIDEO_MAX_PLANES];
 
   // ChildThread's task runner.
   scoped_refptr<base::SingleThreadTaskRunner> child_task_runner_;
diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.cc b/media/mojo/services/mojo_demuxer_stream_adapter.cc
index 9f2f7f7..eb7f91f8 100644
--- a/media/mojo/services/mojo_demuxer_stream_adapter.cc
+++ b/media/mojo/services/mojo_demuxer_stream_adapter.cc
@@ -43,6 +43,10 @@
                                    weak_factory_.GetWeakPtr()));
 }
 
+bool MojoDemuxerStreamAdapter::IsReadPending() const {
+  return !read_cb_.is_null();
+}
+
 AudioDecoderConfig MojoDemuxerStreamAdapter::audio_decoder_config() {
   DCHECK_EQ(type_, AUDIO);
   return audio_config_;
diff --git a/media/mojo/services/mojo_demuxer_stream_adapter.h b/media/mojo/services/mojo_demuxer_stream_adapter.h
index a20950b..4a43715 100644
--- a/media/mojo/services/mojo_demuxer_stream_adapter.h
+++ b/media/mojo/services/mojo_demuxer_stream_adapter.h
@@ -38,6 +38,7 @@
 
   // DemuxerStream implementation.
   void Read(const ReadCB& read_cb) override;
+  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/remoting/fake_media_resource.cc b/media/remoting/fake_media_resource.cc
index 61a854e..6b6c3e9 100644
--- a/media/remoting/fake_media_resource.cc
+++ b/media/remoting/fake_media_resource.cc
@@ -49,6 +49,10 @@
   read_cb.Run(kOk, buffer);
 }
 
+bool FakeDemuxerStream::IsReadPending() const {
+  return !pending_read_cb_.is_null();
+}
+
 AudioDecoderConfig FakeDemuxerStream::audio_decoder_config() {
   return audio_config_;
 }
diff --git a/media/remoting/fake_media_resource.h b/media/remoting/fake_media_resource.h
index 5d4f9795..65db686 100644
--- a/media/remoting/fake_media_resource.h
+++ b/media/remoting/fake_media_resource.h
@@ -23,6 +23,7 @@
   // DemuxerStream implementation.
   MOCK_METHOD1(Read, void(const ReadCB& read_cb));
   void FakeRead(const ReadCB& read_cb);
+  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   Type type() const override;
diff --git a/media/remoting/stream_provider.cc b/media/remoting/stream_provider.cc
index 12598b59..3bc559e 100644
--- a/media/remoting/stream_provider.cc
+++ b/media/remoting/stream_provider.cc
@@ -35,6 +35,7 @@
 
   // DemuxerStream implementation.
   void Read(const ReadCB& read_cb) override;
+  bool IsReadPending() const override;
   AudioDecoderConfig audio_decoder_config() override;
   VideoDecoderConfig video_decoder_config() override;
   DemuxerStream::Type type() const override;
@@ -315,6 +316,10 @@
   CompleteRead(DemuxerStream::kOk);
 }
 
+bool MediaStream::IsReadPending() const {
+  return !read_complete_callback_.is_null();
+}
+
 void MediaStream::CompleteRead(DemuxerStream::Status status) {
   DVLOG(3) << __func__ << ": " << status;
   switch (status) {
diff --git a/mojo/public/js/interface_support.js b/mojo/public/js/interface_support.js
index 5c5423cb..36ec5dd7 100644
--- a/mojo/public/js/interface_support.js
+++ b/mojo/public/js/interface_support.js
@@ -520,20 +520,28 @@
 };
 
 /**
- * Listens for incoming request messages on a message pipe, dispatching them to
- * any registered handlers. Handlers are registered against a specific ordinal
- * message number.
+ * Generic helper that listens for incoming request messages on a message pipe,
+ * dispatching them to any registered handlers. Handlers are registered against
+ * a specific ordinal message number. It has methods to perform operations
+ * related to the interface pipe e.g. bind the pipe, close it, etc. Should only
+ * be used by the generated receiver classes.
+ * @template T
  * @export
  */
-mojo.internal.interfaceSupport.InterfaceReceiver = class {
-  /** @public */
-  constructor() {
+mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal = class {
+  /**
+   * @param {!function(new:T)} remoteType
+   * @public
+   */
+  constructor(remoteType) {
     /**
      * @private {!Map<MojoHandle,
      *     !mojo.internal.interfaceSupport.HandleReader>}
      */
     this.readers_ = new Map;
 
+    /** @private {!function(new:T)} */
+    this.remoteType_ = remoteType;
     /**
      * @private {!Map<number, !mojo.internal.interfaceSupport.MessageHandler>}
      */
@@ -575,6 +583,16 @@
         new mojo.internal.interfaceSupport.ControlMessageHandler(handle);
   }
 
+  /**
+   * @return {!T}
+   * @export
+   */
+  bindNewPipeAndPassRemote() {
+    let remote = new this.remoteType_;
+    this.bindHandle(remote.$.bindNewPipeAndPassReceiver().handle);
+    return remote;
+  }
+
   /** @export */
   closeBindings() {
     for (const reader of this.readers_.values())
@@ -663,9 +681,68 @@
 };
 
 /**
+ * Generic helper used to perform operations related to the interface pipe e.g.
+ * bind the pipe, close it, flush it for testing, etc. Wraps
+ * mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal and exposes a
+ * subset of methods that meant to be used by users of a receiver class.
+ *
+ * @template T
+ * @export
+ */
+mojo.internal.interfaceSupport.InterfaceReceiverHelper = class {
+  /**
+   * @param {!mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal<T>}
+   *     helper_internal
+   * @public
+   */
+  constructor(helper_internal) {
+    /**
+     * @private {!mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal<T>}
+     */
+    this.helper_internal_ = helper_internal;
+  }
+
+  /**
+   * Binds a new handle to this object. Messages which arrive on the handle will
+   * be read and dispatched to this object.
+   *
+   * @param {!MojoHandle} handle
+   * @export
+   */
+  bindHandle(handle) {
+    this.helper_internal_.bindHandle(handle);
+  }
+
+  // TODO(ortuno): Remove once new names are used in the exposed interfaces.
+  /**
+   * Returns a remote for this interface which sends messages directly to this
+   * object. Any number of proxies may be created to the same object.
+   *
+   * @return {!T}
+   * @export
+   */
+  createProxy() {
+    return this.helper_internal_.bindNewPipeAndPassRemote();
+  }
+
+  /**
+   * @return {!T}
+   * @export
+   */
+  bindNewPipeAndPassRemote() {
+    return this.helper_internal_.bindNewPipeAndPassRemote();
+  }
+
+  /** @export */
+  close() {
+    this.helper_internal_.closeBindings();
+  }
+}
+
+/**
  * Watches a MojoHandle for readability or peer closure, forwarding either event
  * to one of two callbacks on the reader. Used by both InterfaceRemoteBase and
- * InterfaceReceiver to watch for incoming messages.
+ * InterfaceReceiverHelperInternal to watch for incoming messages.
  */
 mojo.internal.interfaceSupport.HandleReader = class {
   /**
diff --git a/mojo/public/tools/bindings/generators/js_templates/lite/interface_definition.tmpl b/mojo/public/tools/bindings/generators/js_templates/lite/interface_definition.tmpl
index afe81e5..4109319 100644
--- a/mojo/public/tools/bindings/generators/js_templates/lite/interface_definition.tmpl
+++ b/mojo/public/tools/bindings/generators/js_templates/lite/interface_definition.tmpl
@@ -114,11 +114,19 @@
    * @param {!{{module.namespace}}.{{interface.name}}Interface } impl
    */
   constructor(impl) {
-    this.receiver_ = new mojo.internal.interfaceSupport.InterfaceReceiver;
+    /** @private {!mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal<!{{module.namespace}}.{{interface.name}}Proxy>} */
+    this.helper_internal_ = new mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal(
+        {{module.namespace}}.{{interface.name}}Proxy);
+
+    /**
+     * @public {!mojo.internal.interfaceSupport.InterfaceReceiverHelper<!{{module.namespace}}.{{interface.name}}Proxy>}
+     */
+    this.$ = new mojo.internal.interfaceSupport.InterfaceReceiverHelper(this.helper_internal_);
+
 {%  for method in interface.methods %}
 {%-   set interface_message_id =
           interface.mojom_name ~ "_" ~ method.mojom_name %}
-    this.receiver_.registerHandler(
+    this.helper_internal_.registerHandler(
         {{method.ordinal}},
         {{module.namespace}}.{{interface_message_id}}_ParamsSpec.$,
 {%-   if method.response_parameters != None %}
@@ -129,27 +137,7 @@
         impl.{{method.name}}.bind(impl));
 {%- endfor %}
     /** @public {!mojo.internal.interfaceSupport.ConnectionErrorEventRouter} */
-    this.onConnectionError = this.receiver_.getConnectionErrorEventRouter();
-  }
-
-  /**
-   * Binds a new handle to this object. Messages which arrive on the handle will
-   * be read and dispatched to this object.
-   *
-   * @param {!MojoHandle} handle
-   * @export
-   */
-  bindHandle(handle) {
-    this.receiver_.bindHandle(handle);
-  }
-
-  /**
-   * Closes all bindings bound to this target.
-   *
-   * @export
-   */
-  closeBindings() {
-    this.receiver_.closeBindings();
+    this.onConnectionError = this.helper_internal_.getConnectionErrorEventRouter();
   }
 
   /**
@@ -166,19 +154,6 @@
                        proxy.$.bindNewPipeAndPassReceiver().handle);
     return proxy;
   }
-
-  /**
-   * Returns a proxy for this interface which sends messages directly to this
-   * object. Any number of proxies may be created to the same object.
-   *
-   * @return {!{{module.namespace}}.{{interface.name}}Proxy}
-   * @export
-   */
-  createProxy() {
-    let proxy = new {{module.namespace}}.{{interface.name}}Proxy;
-    this.receiver_.bindHandle(proxy.$.bindNewPipeAndPassReceiver().handle);
-    return proxy;
-  }
 };
 
 {#--- Enums #}
@@ -206,7 +181,14 @@
  */
 {{module.namespace}}.{{interface.name}}CallbackRouter = class {
   constructor() {
-    this.receiver_ = new mojo.internal.interfaceSupport.InterfaceReceiver;
+    this.helper_internal_ = new mojo.internal.interfaceSupport.InterfaceReceiverHelperInternal(
+      {{module.namespace}}.{{interface.name}}Proxy);
+
+    /**
+     * @public {!mojo.internal.interfaceSupport.InterfaceReceiverHelper<!{{module.namespace}}.{{interface.name}}Proxy>}
+     */
+    this.$ = new mojo.internal.interfaceSupport.InterfaceReceiverHelper(this.helper_internal_);
+
     this.router_ = new mojo.internal.interfaceSupport.CallbackRouter;
 {%  for method in interface.methods %}
 {%-   set interface_message_id =
@@ -218,7 +200,7 @@
         new mojo.internal.interfaceSupport.InterfaceCallbackReceiver(
             this.router_);
 
-    this.receiver_.registerHandler(
+    this.helper_internal_.registerHandler(
         {{method.ordinal}},
         {{module.namespace}}.{{interface_message_id}}_ParamsSpec.$,
 {%-   if method.response_parameters != None %}
@@ -230,39 +212,7 @@
 {%-   endif %}
 {%- endfor %}
     /** @public {!mojo.internal.interfaceSupport.ConnectionErrorEventRouter} */
-    this.onConnectionError = this.receiver_.getConnectionErrorEventRouter();
-  }
-
-  /**
-   * Binds a new handle to this object. Messages which arrive on the handle will
-   * be read and dispatched as callbacks on this object.
-   *
-   * @param {!MojoHandle} handle
-   * @export
-   */
-  bindHandle(handle) {
-    this.receiver_.bindHandle(handle);
-  }
-
-  /**
-   * Closes all bindings bound to this receiver. The receiver will not receive any
-   * further message message events unless rebound to one or more handles.
-   */
-  closeBindings() {
-    this.receiver_.closeBindings();
-  }
-
-  /**
-   * Returns a proxy for this interface which sends messages directly to this
-   * object. Any number of proxies may be created to the same object.
-   *
-   * @return {!{{module.namespace}}.{{interface.name}}Proxy}
-   * @export
-   */
-  createProxy() {
-    let proxy = new {{module.namespace}}.{{interface.name}}Proxy;
-    this.receiver_.bindHandle(proxy.$.bindNewPipeAndPassReceiver().handle);
-    return proxy;
+    this.onConnectionError = this.helper_internal_.getConnectionErrorEventRouter();
   }
 
   /**
diff --git a/net/BUILD.gn b/net/BUILD.gn
index ae69336d..b271a568 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -574,6 +574,7 @@
       "cert/ev_root_ca_metadata.h",
       "cert/internal/system_trust_store.cc",
       "cert/internal/system_trust_store.h",
+      "cert/internal/system_trust_store_nss.h",
       "cert/internal/trust_store_mac.cc",
       "cert/internal/trust_store_mac.h",
       "cert/internal/trust_store_nss.cc",
@@ -878,6 +879,8 @@
       "nqe/effective_connection_type_observer.h",
       "nqe/event_creator.cc",
       "nqe/event_creator.h",
+      "nqe/network_congestion_analyzer.cc",
+      "nqe/network_congestion_analyzer.h",
       "nqe/network_id.cc",
       "nqe/network_id.h",
       "nqe/network_qualities_prefs_manager.cc",
@@ -1952,6 +1955,7 @@
 
     if (!use_nss_certs) {
       sources -= [
+        "cert/internal/system_trust_store_nss.h",
         "cert/internal/trust_store_nss.cc",
         "cert/internal/trust_store_nss.h",
         "cert/known_roots_nss.cc",
@@ -5095,6 +5099,7 @@
     "cert/internal/revocation_util_unittest.cc",
     "cert/internal/signature_algorithm_unittest.cc",
     "cert/internal/simple_path_builder_delegate_unittest.cc",
+    "cert/internal/system_trust_store_nss_unittest.cc",
     "cert/internal/test_helpers.cc",
     "cert/internal/test_helpers.h",
     "cert/internal/trust_store_collection_unittest.cc",
@@ -5225,6 +5230,7 @@
     "log/trace_net_log_observer_unittest.cc",
     "nqe/effective_connection_type_unittest.cc",
     "nqe/event_creator_unittest.cc",
+    "nqe/network_congestion_analyzer_unittest.cc",
     "nqe/network_id_unittest.cc",
     "nqe/network_qualities_prefs_manager_unittest.cc",
     "nqe/network_quality_estimator_params_unittest.cc",
@@ -5862,6 +5868,7 @@
 
   if (!use_nss_certs) {
     sources -= [
+      "cert/internal/system_trust_store_nss_unittest.cc",
       "cert/internal/trust_store_nss_unittest.cc",
       "cert/nss_cert_database_unittest.cc",
       "cert/x509_util_nss_unittest.cc",
diff --git a/net/cert/cert_verify_proc.cc b/net/cert/cert_verify_proc.cc
index 29cd2b83..906b843 100644
--- a/net/cert/cert_verify_proc.cc
+++ b/net/cert/cert_verify_proc.cc
@@ -461,8 +461,10 @@
 scoped_refptr<CertVerifyProc> CertVerifyProc::CreateDefault(
     scoped_refptr<CertNetFetcher> cert_net_fetcher) {
 #if BUILDFLAG(BUILTIN_CERT_VERIFIER_FEATURE_SUPPORTED)
-  if (base::FeatureList::IsEnabled(features::kCertVerifierBuiltinFeature))
-    return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher));
+  if (base::FeatureList::IsEnabled(features::kCertVerifierBuiltinFeature)) {
+    return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher),
+                                       /*system_trust_store_provider=*/nullptr);
+  }
 #endif
 #if defined(USE_NSS_CERTS)
   return new CertVerifyProcNSS();
@@ -475,7 +477,8 @@
 #elif defined(OS_WIN)
   return new CertVerifyProcWin();
 #elif defined(OS_FUCHSIA)
-  return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher));
+  return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher),
+                                     /*system_trust_store_provider=*/nullptr);
 #else
 #error Unsupported platform
 #endif
diff --git a/net/cert/cert_verify_proc_builtin.cc b/net/cert/cert_verify_proc_builtin.cc
index 60706a5..1b883e0 100644
--- a/net/cert/cert_verify_proc_builtin.cc
+++ b/net/cert/cert_verify_proc_builtin.cc
@@ -258,7 +258,9 @@
 
 class CertVerifyProcBuiltin : public CertVerifyProc {
  public:
-  explicit CertVerifyProcBuiltin(scoped_refptr<CertNetFetcher> net_fetcher);
+  CertVerifyProcBuiltin(
+      scoped_refptr<CertNetFetcher> net_fetcher,
+      std::unique_ptr<SystemTrustStoreProvider> system_trust_store_provider);
 
   bool SupportsAdditionalTrustAnchors() const override;
 
@@ -276,11 +278,14 @@
                      CertVerifyResult* verify_result) override;
 
   scoped_refptr<CertNetFetcher> net_fetcher_;
+  std::unique_ptr<SystemTrustStoreProvider> system_trust_store_provider_;
 };
 
 CertVerifyProcBuiltin::CertVerifyProcBuiltin(
-    scoped_refptr<CertNetFetcher> net_fetcher)
-    : net_fetcher_(std::move(net_fetcher)) {}
+    scoped_refptr<CertNetFetcher> net_fetcher,
+    std::unique_ptr<SystemTrustStoreProvider> system_trust_store_provider)
+    : net_fetcher_(std::move(net_fetcher)),
+      system_trust_store_provider_(std::move(system_trust_store_provider)) {}
 
 CertVerifyProcBuiltin::~CertVerifyProcBuiltin() = default;
 
@@ -589,7 +594,9 @@
 
   // Parse the additional trust anchors and setup trust store.
   std::unique_ptr<SystemTrustStore> ssl_trust_store =
-      CreateSslSystemTrustStore();
+      system_trust_store_provider_
+          ? system_trust_store_provider_->CreateSystemTrustStore()
+          : CreateSslSystemTrustStore();
 
   for (const auto& x509_cert : additional_trust_anchors) {
     scoped_refptr<ParsedCertificate> cert =
@@ -676,8 +683,10 @@
 }  // namespace
 
 scoped_refptr<CertVerifyProc> CreateCertVerifyProcBuiltin(
-    scoped_refptr<CertNetFetcher> net_fetcher) {
-  return base::MakeRefCounted<CertVerifyProcBuiltin>(std::move(net_fetcher));
+    scoped_refptr<CertNetFetcher> net_fetcher,
+    std::unique_ptr<SystemTrustStoreProvider> system_trust_store_provider) {
+  return base::MakeRefCounted<CertVerifyProcBuiltin>(
+      std::move(net_fetcher), std::move(system_trust_store_provider));
 }
 
 }  // namespace net
diff --git a/net/cert/cert_verify_proc_builtin.h b/net/cert/cert_verify_proc_builtin.h
index 97b7741..da2ddac 100644
--- a/net/cert/cert_verify_proc_builtin.h
+++ b/net/cert/cert_verify_proc_builtin.h
@@ -5,6 +5,8 @@
 #ifndef NET_CERT_CERT_VERIFY_PROC_BUILTIN_H_
 #define NET_CERT_CERT_VERIFY_PROC_BUILTIN_H_
 
+#include <memory>
+
 #include "base/memory/ref_counted.h"
 #include "net/base/net_export.h"
 
@@ -12,12 +14,33 @@
 
 class CertNetFetcher;
 class CertVerifyProc;
+class SystemTrustStore;
+
+// Will be used to create the system trust store. Implementations must be
+// thread-safe - CreateSystemTrustStore may be invoked concurrently on worker
+// threads.
+class NET_EXPORT SystemTrustStoreProvider {
+ public:
+  virtual ~SystemTrustStoreProvider() {}
+
+  // Create and return a SystemTrustStore to be used during certificate
+  // verification.
+  // This function may be invoked concurrently on worker threads and must be
+  // thread-safe. However, the returned SystemTrustStore will only be used on
+  // a single thread.
+  virtual std::unique_ptr<SystemTrustStore> CreateSystemTrustStore() = 0;
+};
 
 // TODO(crbug.com/649017): This is not how other cert_verify_proc_*.h are
 // implemented -- they expose the type in the header. Use a consistent style
 // here too.
+// If |system_trust_store_provider| is null, the default SystemTrustStore will
+// be created.
+// If |system_trust_store_provider| is non-null, it will be used to create the
+// SystemTrustStore instance for certificate verification.
 NET_EXPORT scoped_refptr<CertVerifyProc> CreateCertVerifyProcBuiltin(
-    scoped_refptr<CertNetFetcher> net_fetcher);
+    scoped_refptr<CertNetFetcher> net_fetcher,
+    std::unique_ptr<SystemTrustStoreProvider> system_trust_store_provider);
 
 }  // namespace net
 
diff --git a/net/cert/cert_verify_proc_unittest.cc b/net/cert/cert_verify_proc_unittest.cc
index b5e8795..64dd416 100644
--- a/net/cert/cert_verify_proc_unittest.cc
+++ b/net/cert/cert_verify_proc_unittest.cc
@@ -211,7 +211,9 @@
       return new CertVerifyProcWin();
 #endif
     case CERT_VERIFY_PROC_BUILTIN:
-      return CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher));
+      return CreateCertVerifyProcBuiltin(
+          std::move(cert_net_fetcher),
+          nullptr /* system_trust_store_provider */);
     default:
       return nullptr;
   }
diff --git a/net/cert/internal/system_trust_store.cc b/net/cert/internal/system_trust_store.cc
index 4629092e..e8b1e31 100644
--- a/net/cert/internal/system_trust_store.cc
+++ b/net/cert/internal/system_trust_store.cc
@@ -5,6 +5,10 @@
 #include "net/cert/internal/system_trust_store.h"
 
 #if defined(USE_NSS_CERTS)
+#include "net/cert/internal/system_trust_store_nss.h"
+#endif  // defined(USE_NSS_CERTS)
+
+#if defined(USE_NSS_CERTS)
 #include <cert.h>
 #include <pk11pub.h>
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
@@ -73,8 +77,9 @@
 
 class SystemTrustStoreNSS : public BaseSystemTrustStore {
  public:
-  explicit SystemTrustStoreNSS() : trust_store_nss_(trustSSL) {
-    trust_store_.AddTrustStore(&trust_store_nss_);
+  explicit SystemTrustStoreNSS(std::unique_ptr<TrustStoreNSS> trust_store_nss)
+      : trust_store_nss_(std::move(trust_store_nss)) {
+    trust_store_.AddTrustStore(trust_store_nss_.get());
 
     // When running in test mode, also layer in the test-only root certificates.
     //
@@ -112,13 +117,27 @@
   }
 
  private:
-  TrustStoreNSS trust_store_nss_;
+  std::unique_ptr<TrustStoreNSS> trust_store_nss_;
 };
 
 }  // namespace
 
 std::unique_ptr<SystemTrustStore> CreateSslSystemTrustStore() {
-  return std::make_unique<SystemTrustStoreNSS>();
+  return std::make_unique<SystemTrustStoreNSS>(
+      std::make_unique<TrustStoreNSS>(trustSSL));
+}
+
+std::unique_ptr<SystemTrustStore>
+CreateSslSystemTrustStoreNSSWithUserSlotRestriction(
+    crypto::ScopedPK11Slot user_slot) {
+  return std::make_unique<SystemTrustStoreNSS>(
+      std::make_unique<TrustStoreNSS>(trustSSL, std::move(user_slot)));
+}
+
+std::unique_ptr<SystemTrustStore>
+CreateSslSystemTrustStoreNSSWithNoUserSlots() {
+  return std::make_unique<SystemTrustStoreNSS>(std::make_unique<TrustStoreNSS>(
+      trustSSL, TrustStoreNSS::DisallowTrustForCertsOnUserSlots()));
 }
 
 #elif defined(OS_MACOSX) && !defined(OS_IOS)
diff --git a/net/cert/internal/system_trust_store_nss.h b/net/cert/internal/system_trust_store_nss.h
new file mode 100644
index 0000000..b7037d5
--- /dev/null
+++ b/net/cert/internal/system_trust_store_nss.h
@@ -0,0 +1,33 @@
+// 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 NET_CERT_INTERNAL_SYSTEM_TRUST_STORE_NSS_H_
+#define NET_CERT_INTERNAL_SYSTEM_TRUST_STORE_NSS_H_
+
+#include "crypto/scoped_nss_types.h"
+#include "net/base/net_export.h"
+#include "net/cert/internal/system_trust_store.h"
+
+namespace net {
+
+// Create a SystemTrustStore that will accept trust for:
+// (*) built-in certificates
+// (*) test root certificates
+// (*) additional trust anchors (added through SystemTrustStore::AddTrustAnchor)
+// (*) certificates stored on the |user_slot|.
+NET_EXPORT std::unique_ptr<SystemTrustStore>
+CreateSslSystemTrustStoreNSSWithUserSlotRestriction(
+    crypto::ScopedPK11Slot user_slot);
+
+// Create a SystemTrustStore that will accept trust for:
+// (*) built-in certificates
+// (*) test root certificates
+// (*) additional trust anchors (added through SystemTrustStore::AddTrustAnchor)
+// It will not accept trust for certificates stored on other slots.
+NET_EXPORT std::unique_ptr<SystemTrustStore>
+CreateSslSystemTrustStoreNSSWithNoUserSlots();
+
+}  // namespace net
+
+#endif  // NET_CERT_INTERNAL_SYSTEM_TRUST_STORE_NSS_H_
diff --git a/net/cert/internal/system_trust_store_nss_unittest.cc b/net/cert/internal/system_trust_store_nss_unittest.cc
new file mode 100644
index 0000000..9e3e581
--- /dev/null
+++ b/net/cert/internal/system_trust_store_nss_unittest.cc
@@ -0,0 +1,163 @@
+// 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 "net/cert/internal/system_trust_store.h"
+
+#include <cert.h>
+#include <certdb.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "crypto/scoped_nss_types.h"
+#include "crypto/scoped_test_nss_db.h"
+#include "net/cert/internal/cert_errors.h"
+#include "net/cert/internal/parsed_certificate.h"
+#include "net/cert/internal/system_trust_store_nss.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_certificate.h"
+#include "net/cert/x509_util.h"
+#include "net/cert/x509_util_nss.h"
+#include "net/test/cert_test_util.h"
+#include "net/test/test_data_directory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/boringssl/src/include/openssl/evp.h"
+
+namespace net {
+
+namespace {
+
+// Parses |x509_cert| as a ParsedCertificate and stores the output in
+// *|out_parsed_cert|. Wrap in ASSERT_NO_FATAL_FAILURE on callsites.
+::testing::AssertionResult ParseX509Certificate(
+    const scoped_refptr<X509Certificate>& x509_cert,
+    scoped_refptr<ParsedCertificate>* out_parsed_cert) {
+  CertErrors parsing_errors;
+  *out_parsed_cert = ParsedCertificate::Create(
+      bssl::UpRef(x509_cert->cert_buffer()),
+      x509_util::DefaultParseCertificateOptions(), &parsing_errors);
+  if (!*out_parsed_cert) {
+    return ::testing::AssertionFailure()
+           << "ParseCertificate::Create() failed:\n"
+           << parsing_errors.ToDebugString();
+  }
+  return ::testing::AssertionSuccess();
+}
+
+class SystemTrustStoreNSSTest : public ::testing::Test {
+ public:
+  SystemTrustStoreNSSTest() : test_root_certs_(TestRootCerts::GetInstance()) {}
+  ~SystemTrustStoreNSSTest() override = default;
+
+  void SetUp() override {
+    ::testing::Test::SetUp();
+
+    root_cert_ =
+        ImportCertFromFile(GetTestCertsDirectory(), "root_ca_cert.pem");
+    ASSERT_TRUE(root_cert_);
+    ASSERT_NO_FATAL_FAILURE(
+        ParseX509Certificate(root_cert_, &parsed_root_cert_));
+    nss_root_cert_ =
+        x509_util::CreateCERTCertificateFromX509Certificate(root_cert_.get());
+    ASSERT_TRUE(nss_root_cert_);
+
+    ASSERT_TRUE(test_nssdb_.is_open());
+    ASSERT_TRUE(other_test_nssdb_.is_open());
+  }
+
+ protected:
+  // Imports |nss_root_cert_| into |slot| and sets trust flags so that it is a
+  // trusted CA for SSL.
+  void ImportRootCertAsTrusted(PK11SlotInfo* slot) {
+    SECStatus srv = PK11_ImportCert(slot, nss_root_cert_.get(),
+                                    CK_INVALID_HANDLE, "nickname_root_cert",
+                                    PR_FALSE /* includeTrust (unused) */);
+    ASSERT_EQ(SECSuccess, srv);
+
+    CERTCertTrust trust = {0};
+    trust.sslFlags = CERTDB_TRUSTED_CA | CERTDB_VALID_CA;
+    srv = CERT_ChangeCertTrust(CERT_GetDefaultCertDB(), nss_root_cert_.get(),
+                               &trust);
+    ASSERT_EQ(SECSuccess, srv);
+  }
+
+  crypto::ScopedTestNSSDB test_nssdb_;
+  crypto::ScopedTestNSSDB other_test_nssdb_;
+
+  TestRootCerts* test_root_certs_;
+
+  scoped_refptr<X509Certificate> root_cert_;
+  scoped_refptr<ParsedCertificate> parsed_root_cert_;
+  ScopedCERTCertificate nss_root_cert_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(SystemTrustStoreNSSTest);
+};
+
+// Tests that SystemTrustStore respects TestRootCerts.
+TEST_F(SystemTrustStoreNSSTest, TrustTestRootCerts) {
+  std::unique_ptr<SystemTrustStore> system_trust_store =
+      CreateSslSystemTrustStore();
+
+  EXPECT_TRUE(test_root_certs_->Add(root_cert_.get()));
+  CertificateTrust trust;
+  system_trust_store->GetTrustStore()->GetTrust(parsed_root_cert_.get(),
+                                                &trust);
+  EXPECT_EQ(CertificateTrustType::TRUSTED_ANCHOR, trust.type);
+
+  test_root_certs_->Clear();
+  system_trust_store->GetTrustStore()->GetTrust(parsed_root_cert_.get(),
+                                                &trust);
+  EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type);
+}
+
+// Tests that SystemTrustStore created for NSS with a user-slot restriction
+// allows certificates stored on the specified user slot to be trusted.
+TEST_F(SystemTrustStoreNSSTest, UserSlotRestrictionAllows) {
+  std::unique_ptr<SystemTrustStore> system_trust_store =
+      CreateSslSystemTrustStoreNSSWithUserSlotRestriction(
+          crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
+
+  ASSERT_NO_FATAL_FAILURE(ImportRootCertAsTrusted(test_nssdb_.slot()));
+
+  CertificateTrust trust;
+  system_trust_store->GetTrustStore()->GetTrust(parsed_root_cert_.get(),
+                                                &trust);
+  EXPECT_EQ(CertificateTrustType::TRUSTED_ANCHOR, trust.type);
+}
+
+// Tests that SystemTrustStore created for NSS with a user-slot restriction
+// does not allows certificates stored only on user slots different from the one
+// specified to be trusted.
+TEST_F(SystemTrustStoreNSSTest, UserSlotRestrictionDisallows) {
+  std::unique_ptr<SystemTrustStore> system_trust_store =
+      CreateSslSystemTrustStoreNSSWithUserSlotRestriction(
+          crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
+
+  ASSERT_NO_FATAL_FAILURE(ImportRootCertAsTrusted(other_test_nssdb_.slot()));
+
+  CertificateTrust trust;
+  system_trust_store->GetTrustStore()->GetTrust(parsed_root_cert_.get(),
+                                                &trust);
+  EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type);
+}
+
+// Tests that SystemTrustStore created for NSS without allowing trust for
+// certificate stored on user slots.
+TEST_F(SystemTrustStoreNSSTest, NoUserSlots) {
+  std::unique_ptr<SystemTrustStore> system_trust_store =
+      CreateSslSystemTrustStoreNSSWithNoUserSlots();
+
+  ASSERT_NO_FATAL_FAILURE(ImportRootCertAsTrusted(test_nssdb_.slot()));
+
+  CertificateTrust trust;
+  system_trust_store->GetTrustStore()->GetTrust(parsed_root_cert_.get(),
+                                                &trust);
+  EXPECT_EQ(CertificateTrustType::UNSPECIFIED, trust.type);
+}
+
+}  // namespace
+
+}  // namespace net
diff --git a/net/cert/internal/trust_store_nss.cc b/net/cert/internal/trust_store_nss.cc
index 3ab6343..6725971 100644
--- a/net/cert/internal/trust_store_nss.cc
+++ b/net/cert/internal/trust_store_nss.cc
@@ -11,17 +11,27 @@
 #include "net/cert/internal/cert_errors.h"
 #include "net/cert/internal/parsed_certificate.h"
 #include "net/cert/scoped_nss_types.h"
+#include "net/cert/test_root_certs.h"
 #include "net/cert/x509_util.h"
 #include "net/cert/x509_util_nss.h"
 
-// TODO(mattm): structure so that supporting ChromeOS multi-profile stuff is
-// doable (Have a TrustStoreChromeOS which uses net::NSSProfileFilterChromeOS,
-// similar to CertVerifyProcChromeOS.)
-
 namespace net {
 
 TrustStoreNSS::TrustStoreNSS(SECTrustType trust_type)
-    : trust_type_(trust_type) {}
+    : trust_type_(trust_type), filter_trusted_certs_by_slot_(false) {}
+
+TrustStoreNSS::TrustStoreNSS(SECTrustType trust_type,
+                             crypto::ScopedPK11Slot user_slot)
+    : trust_type_(trust_type),
+      filter_trusted_certs_by_slot_(true),
+      user_slot_(std::move(user_slot)) {
+  DCHECK(user_slot_);
+}
+
+TrustStoreNSS::TrustStoreNSS(
+    SECTrustType trust_type,
+    DisallowTrustForCertsOnUserSlots disallow_trust_for_certs_on_user_slots)
+    : trust_type_(trust_type), filter_trusted_certs_by_slot_(true) {}
 
 TrustStoreNSS::~TrustStoreNSS() = default;
 
@@ -46,16 +56,24 @@
 
   for (CERTCertListNode* node = CERT_LIST_HEAD(found_certs);
        !CERT_LIST_END(node, found_certs); node = CERT_LIST_NEXT(node)) {
+#if !defined(OS_CHROMEOS)
     // TODO(mattm): use CERT_GetCertIsTemp when minimum NSS version is >= 3.31.
     if (node->cert->istemp) {
-      // Ignore temporary NSS certs. This ensures that during the trial when
-      // CertVerifyProcNSS and CertVerifyProcBuiltin are being used
-      // simultaneously, the builtin verifier does not get to "cheat" by using
-      // AIA fetched certs from CertVerifyProcNSS.
+      // Ignore temporary NSS certs on platforms other than Chrome OS. This
+      // ensures that during the trial when CertVerifyProcNSS and
+      // CertVerifyProcBuiltin are being used simultaneously, the builtin
+      // verifier does not get to "cheat" by using AIA fetched certs from
+      // CertVerifyProcNSS.
       // TODO(https://crbug.com/951479): remove this when CertVerifyProcBuiltin
       // becomes the default.
+      // This is not done for Chrome OS because temporary NSS certs are
+      // currently used there to implement policy-provided untrusted authority
+      // certificates, and no trials are being done on Chrome OS.
+      // TODO(https://crbug.com/978854): remove the Chrome OS exception when
+      // certificates are passed.
       continue;
     }
+#endif  // !defined(OS_CHROMEOS)
 
     CertErrors parse_errors;
     scoped_refptr<ParsedCertificate> cur_cert = ParsedCertificate::Create(
@@ -95,6 +113,11 @@
     return;
   }
 
+  if (!IsCertAllowedForTrust(nss_cert.get())) {
+    *out_trust = CertificateTrust::ForUnspecified();
+    return;
+  }
+
   // Determine the trustedness of the matched certificate.
   CERTCertTrust trust;
   if (CERT_GetCertTrust(nss_cert.get(), &trust) != SECSuccess) {
@@ -124,4 +147,38 @@
   return;
 }
 
+bool TrustStoreNSS::IsCertAllowedForTrust(CERTCertificate* cert) const {
+  // If |filter_trusted_certs_by_slot_| is false, allow trust for any
+  // certificate, no matter which slot it is stored on.
+  if (!filter_trusted_certs_by_slot_)
+    return true;
+
+  crypto::ScopedPK11SlotList slots_for_cert(
+      PK11_GetAllSlotsForCert(cert, nullptr));
+  if (!slots_for_cert)
+    return false;
+
+  for (PK11SlotListElement* slot_element =
+           PK11_GetFirstSafe(slots_for_cert.get());
+       slot_element;
+       slot_element = PK11_GetNextSafe(slots_for_cert.get(), slot_element,
+                                       /*restart=*/PR_FALSE)) {
+    PK11SlotInfo* slot = slot_element->slot;
+    bool allow_slot =
+        // Allow the root certs module.
+        PK11_HasRootCerts(slot) ||
+        // Allow read-only internal slots.
+        (PK11_IsInternal(slot) && !PK11_IsRemovable(slot)) ||
+        // Allow |user_slot_| if specified.
+        (user_slot_ && slot == user_slot_.get());
+
+    if (allow_slot) {
+      PK11_FreeSlotListElement(slots_for_cert.get(), slot_element);
+      return true;
+    }
+  }
+
+  return false;
+}
+
 }  // namespace net
diff --git a/net/cert/internal/trust_store_nss.h b/net/cert/internal/trust_store_nss.h
index fd66b45d4..9c44093 100644
--- a/net/cert/internal/trust_store_nss.h
+++ b/net/cert/internal/trust_store_nss.h
@@ -5,9 +5,11 @@
 #ifndef NET_CERT_INTERNAL_TRUST_STORE_NSS_H_
 #define NET_CERT_INTERNAL_TRUST_STORE_NSS_H_
 
+#include <cert.h>
 #include <certt.h>
 
 #include "base/memory/ref_counted.h"
+#include "crypto/scoped_nss_types.h"
 #include "net/base/net_export.h"
 #include "net/cert/internal/trust_store.h"
 
@@ -17,9 +19,31 @@
 // anchors for path building.
 class NET_EXPORT TrustStoreNSS : public TrustStore {
  public:
+  struct DisallowTrustForCertsOnUserSlots {};
+
   // Creates a TrustStoreNSS which will find anchors that are trusted for
   // |trust_type|.
+  // The created TrustStoreNSS will not perform any filtering based on the slot
+  // certificates are stored on.
   explicit TrustStoreNSS(SECTrustType trust_type);
+
+  // Creates a TrustStoreNSS which will find anchors that are trusted for
+  // |trust_type|.
+  // The created TrustStoreNSS will allow trust for certificates that:
+  // (*) are built-in certificates
+  // (*) are stored on a read-only internal slot
+  // (*) are stored on the |user_slot|.
+  TrustStoreNSS(SECTrustType trust_type, crypto::ScopedPK11Slot user_slot);
+
+  // Creates a TrustStoreNSS which will find anchors that are trusted for
+  // |trust_type|.
+  // The created TrustStoreNSS will allow trust for certificates that:
+  // (*) are built-in certificates
+  // (*) are stored on a read-only internal slot
+  TrustStoreNSS(
+      SECTrustType trust_type,
+      DisallowTrustForCertsOnUserSlots disallow_trust_for_certs_on_user_slots);
+
   ~TrustStoreNSS() override;
 
   // CertIssuerSource implementation:
@@ -31,8 +55,30 @@
                 CertificateTrust* trust) const override;
 
  private:
+  bool IsCertAllowedForTrust(CERTCertificate* cert) const;
+
   SECTrustType trust_type_;
 
+  // |filter_trusted_certs_by_slot_| and |user_slot_| together specify which
+  // slots certificates must be stored on to be allowed to be trusted. The
+  // possible combinations are:
+  //
+  // |filter_trusted_certs_by_slot_| == false: Allow any certificate to be
+  // trusted, don't filter by slot. |user_slot_| is ignored in this case.
+  //
+  // |filter_trusted_certs_by_slot_| == true and |user_slot_| = nullptr: Allow
+  // certificates to be trusted if they
+  // (*) are built-in certificates or
+  // (*) are stored on a read-only internal slot.
+  //
+  // |filter_trusted_certs_by_slot_| == true and |user_slot_| != nullptr: Allow
+  // certificates to be trusted if they
+  // (*) are built-in certificates or
+  // (*) are stored on a read-only internal slot or
+  // (*) are stored on |user_slot_|.
+  const bool filter_trusted_certs_by_slot_;
+  crypto::ScopedPK11Slot user_slot_;
+
   DISALLOW_COPY_AND_ASSIGN(TrustStoreNSS);
 };
 
diff --git a/net/cert/internal/trust_store_nss_unittest.cc b/net/cert/internal/trust_store_nss_unittest.cc
index 1a7a94c..d22a0360 100644
--- a/net/cert/internal/trust_store_nss_unittest.cc
+++ b/net/cert/internal/trust_store_nss_unittest.cc
@@ -6,25 +6,82 @@
 
 #include <cert.h>
 #include <certdb.h>
+#include <secmod.h>
 
 #include <memory>
 
 #include "base/strings/string_number_conversions.h"
+#include "crypto/nss_util_internal.h"
 #include "crypto/scoped_test_nss_db.h"
 #include "net/cert/internal/cert_issuer_source_sync_unittest.h"
+#include "net/cert/internal/parsed_certificate.h"
 #include "net/cert/internal/test_helpers.h"
 #include "net/cert/scoped_nss_types.h"
+#include "net/cert/test_root_certs.h"
+#include "net/cert/x509_util.h"
 #include "net/cert/x509_util_nss.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/boringssl/src/include/openssl/pool.h"
 
 namespace net {
 
 namespace {
 
-class TrustStoreNSSTest : public testing::Test {
+// Returns the slot which holds the built-in root certificates.
+crypto::ScopedPK11Slot GetRootCertsSlot() {
+  crypto::AutoSECMODListReadLock auto_lock;
+  SECMODModuleList* head = SECMOD_GetDefaultModuleList();
+  for (SECMODModuleList* item = head; item != NULL; item = item->next) {
+    int slot_count = item->module->loaded ? item->module->slotCount : 0;
+    for (int i = 0; i < slot_count; i++) {
+      PK11SlotInfo* slot = item->module->slots[i];
+      if (!PK11_IsPresent(slot))
+        continue;
+      if (PK11_HasRootCerts(slot))
+        return crypto::ScopedPK11Slot(PK11_ReferenceSlot(slot));
+    }
+  }
+  return crypto::ScopedPK11Slot();
+}
+
+// Returns a built-in trusted root certificte. If multiple ones are available,
+// it is not specified which one is returned. If none are available, returns
+// nullptr.
+scoped_refptr<ParsedCertificate> GetASSLTrustedBuiltinRoot() {
+  crypto::ScopedPK11Slot root_certs_slot = GetRootCertsSlot();
+  if (!root_certs_slot)
+    return nullptr;
+
+  scoped_refptr<X509Certificate> ssl_trusted_root;
+
+  CERTCertList* cert_list = PK11_ListCertsInSlot(root_certs_slot.get());
+  for (CERTCertListNode* node = CERT_LIST_HEAD(cert_list);
+       !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) {
+    CERTCertTrust trust;
+    if (CERT_GetCertTrust(node->cert, &trust) != SECSuccess)
+      continue;
+    int trust_flags = SEC_GET_TRUST_FLAGS(&trust, trustSSL);
+    if ((trust_flags & CERTDB_TRUSTED_CA) == CERTDB_TRUSTED_CA) {
+      ssl_trusted_root =
+          x509_util::CreateX509CertificateFromCERTCertificate(node->cert);
+      break;
+    }
+  }
+  CERT_DestroyCertList(cert_list);
+  if (!ssl_trusted_root)
+    return nullptr;
+
+  CertErrors parsing_errors;
+  return ParsedCertificate::Create(bssl::UpRef(ssl_trusted_root->cert_buffer()),
+                                   x509_util::DefaultParseCertificateOptions(),
+                                   &parsing_errors);
+}
+
+class TrustStoreNSSTestBase : public ::testing::Test {
  public:
   void SetUp() override {
     ASSERT_TRUE(test_nssdb_.is_open());
+    ASSERT_TRUE(other_test_nssdb_.is_open());
     ParsedCertificateList chain;
     ReadCertChainFromFile(
         "net/data/verify_certificate_chain_unittest/key-rollover/oldchain.pem",
@@ -51,31 +108,35 @@
     ASSERT_TRUE(newroot_);
     ASSERT_TRUE(newrootrollover_);
 
-    trust_store_nss_.reset(new TrustStoreNSS(trustSSL));
+    trust_store_nss_ = CreateTrustStoreNSS();
   }
 
+  // Creates the TrustStoreNSS instance. Subclasses will customize the slot
+  // filtering behavior here.
+  virtual std::unique_ptr<TrustStoreNSS> CreateTrustStoreNSS() = 0;
+
   std::string GetUniqueNickname() {
     return "trust_store_nss_unittest" +
            base::NumberToString(nickname_counter_++);
   }
 
-  void AddCertToNSS(const ParsedCertificate* cert) {
+  void AddCertToNSSSlot(const ParsedCertificate* cert, PK11SlotInfo* slot) {
     ScopedCERTCertificate nss_cert(x509_util::CreateCERTCertificateFromBytes(
         cert->der_cert().UnsafeData(), cert->der_cert().Length()));
     ASSERT_TRUE(nss_cert);
-    SECStatus srv = PK11_ImportCert(
-        test_nssdb_.slot(), nss_cert.get(), CK_INVALID_HANDLE,
-        GetUniqueNickname().c_str(), PR_FALSE /* includeTrust (unused) */);
+    SECStatus srv = PK11_ImportCert(slot, nss_cert.get(), CK_INVALID_HANDLE,
+                                    GetUniqueNickname().c_str(),
+                                    PR_FALSE /* includeTrust (unused) */);
     ASSERT_EQ(SECSuccess, srv);
   }
 
   void AddCertsToNSS() {
-    AddCertToNSS(target_.get());
-    AddCertToNSS(oldintermediate_.get());
-    AddCertToNSS(newintermediate_.get());
-    AddCertToNSS(oldroot_.get());
-    AddCertToNSS(newroot_.get());
-    AddCertToNSS(newrootrollover_.get());
+    AddCertToNSSSlot(target_.get(), test_nssdb_.slot());
+    AddCertToNSSSlot(oldintermediate_.get(), test_nssdb_.slot());
+    AddCertToNSSSlot(newintermediate_.get(), test_nssdb_.slot());
+    AddCertToNSSSlot(oldroot_.get(), test_nssdb_.slot());
+    AddCertToNSSSlot(newroot_.get(), test_nssdb_.slot());
+    AddCertToNSSSlot(newrootrollover_.get(), test_nssdb_.slot());
 
     // Check that the certificates can be retrieved as expected.
     EXPECT_TRUE(
@@ -192,30 +253,102 @@
   scoped_refptr<ParsedCertificate> newintermediate_;
   scoped_refptr<ParsedCertificate> newrootrollover_;
   crypto::ScopedTestNSSDB test_nssdb_;
+  crypto::ScopedTestNSSDB other_test_nssdb_;
   std::unique_ptr<TrustStoreNSS> trust_store_nss_;
   unsigned nickname_counter_ = 0;
 };
 
-// Without adding any certs to the NSS DB, should get no anchor results for any
-// of the test certs.
-TEST_F(TrustStoreNSSTest, CertsNotPresent) {
+// Specifies which kind of per-slot filtering the TrustStoreNSS is supposed to
+// perform in the parametrized TrustStoreNSSTestWithSlotFilterType.
+enum class SlotFilterType {
+  kDontFilter,
+  kDoNotAllowUserSlots,
+  kAllowSpecifiedUserSlot
+};
+
+// Used for testing a TrustStoreNSS with the slot filter type specified by the
+// test parameter.
+class TrustStoreNSSTestWithSlotFilterType
+    : public TrustStoreNSSTestBase,
+      public testing::WithParamInterface<SlotFilterType> {
+ public:
+  TrustStoreNSSTestWithSlotFilterType() = default;
+  ~TrustStoreNSSTestWithSlotFilterType() override = default;
+
+  std::unique_ptr<TrustStoreNSS> CreateTrustStoreNSS() override {
+    switch (GetParam()) {
+      case SlotFilterType::kDontFilter:
+        return std::make_unique<TrustStoreNSS>(trustSSL);
+      case SlotFilterType::kDoNotAllowUserSlots:
+        return std::make_unique<TrustStoreNSS>(
+            trustSSL, TrustStoreNSS::DisallowTrustForCertsOnUserSlots());
+      case SlotFilterType::kAllowSpecifiedUserSlot:
+        return std::make_unique<TrustStoreNSS>(
+            trustSSL,
+            crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
+    }
+  }
+};
+
+// Without adding any certs to the NSS DB, should get no anchor results for
+// any of the test certs.
+TEST_P(TrustStoreNSSTestWithSlotFilterType, CertsNotPresent) {
   EXPECT_TRUE(TrustStoreContains(target_, ParsedCertificateList()));
   EXPECT_TRUE(TrustStoreContains(newintermediate_, ParsedCertificateList()));
   EXPECT_TRUE(TrustStoreContains(newroot_, ParsedCertificateList()));
 }
 
+#if !defined(OS_CHROMEOS)
 // TrustStoreNSS should not return temporary certs. (See
 // https://crbug.com/951166)
-TEST_F(TrustStoreNSSTest, TempCertNotPresent) {
+TEST_P(TrustStoreNSSTestWithSlotFilterType, TempCertNotPresent) {
   ScopedCERTCertificate temp_nss_cert(x509_util::CreateCERTCertificateFromBytes(
       newintermediate_->der_cert().UnsafeData(),
       newintermediate_->der_cert().Length()));
   EXPECT_TRUE(TrustStoreContains(target_, ParsedCertificateList()));
 }
+#else   // !defined(OS_CHROMEOS)
+// TrustStoreNSS should return temporary certs on Chrome OS, because on Chrome
+// OS temporary certs are used to supply policy-provided untrusted authority
+// certs. (See https://crbug.com/978854)
+TEST_P(TrustStoreNSSTestWithSlotFilterType, TempCertPresent) {
+  ScopedCERTCertificate temp_nss_cert(x509_util::CreateCERTCertificateFromBytes(
+      newintermediate_->der_cert().UnsafeData(),
+      newintermediate_->der_cert().Length()));
+  EXPECT_TRUE(TrustStoreContains(target_, {newintermediate_}));
+}
+#endif  // !defined(OS_CHROMEOS)
+
+// Independent of the specified slot-based filtering mode, built-in root certs
+// should always be trusted.
+TEST_P(TrustStoreNSSTestWithSlotFilterType, TrustAllowedForBuiltinRootCerts) {
+  auto builtin_root_cert = GetASSLTrustedBuiltinRoot();
+  ASSERT_TRUE(builtin_root_cert);
+  EXPECT_TRUE(
+      HasTrust({builtin_root_cert}, CertificateTrustType::TRUSTED_ANCHOR));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    TrustStoreNSSTest_AllSlotFilterTypes,
+    TrustStoreNSSTestWithSlotFilterType,
+    ::testing::Values(SlotFilterType::kDontFilter,
+                      SlotFilterType::kDoNotAllowUserSlots,
+                      SlotFilterType::kAllowSpecifiedUserSlot));
+
+// Tests a TrustStoreNSS that does not filter which certificates
+class TrustStoreNSSTestWithoutSlotFilter : public TrustStoreNSSTestBase {
+ public:
+  TrustStoreNSSTestWithoutSlotFilter() = default;
+  ~TrustStoreNSSTestWithoutSlotFilter() override = default;
+
+  std::unique_ptr<TrustStoreNSS> CreateTrustStoreNSS() override {
+    return std::make_unique<TrustStoreNSS>(trustSSL);
+  }
+};
 
 // If certs are present in NSS DB but aren't marked as trusted, should get no
 // anchor results for any of the test certs.
-TEST_F(TrustStoreNSSTest, CertsPresentButNotTrusted) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, CertsPresentButNotTrusted) {
   AddCertsToNSS();
 
   // None of the certificates are trusted.
@@ -225,7 +358,7 @@
 }
 
 // Trust a single self-signed CA certificate.
-TEST_F(TrustStoreNSSTest, TrustedCA) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, TrustedCA) {
   AddCertsToNSS();
   TrustCert(newroot_.get());
 
@@ -238,7 +371,7 @@
 }
 
 // Distrust a single self-signed CA certificate.
-TEST_F(TrustStoreNSSTest, DistrustedCA) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, DistrustedCA) {
   AddCertsToNSS();
   DistrustCert(newroot_.get());
 
@@ -251,7 +384,7 @@
 }
 
 // Trust a single intermediate certificate.
-TEST_F(TrustStoreNSSTest, TrustedIntermediate) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, TrustedIntermediate) {
   AddCertsToNSS();
   TrustCert(newintermediate_.get());
 
@@ -263,7 +396,7 @@
 }
 
 // Distrust a single intermediate certificate.
-TEST_F(TrustStoreNSSTest, DistrustedIntermediate) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, DistrustedIntermediate) {
   AddCertsToNSS();
   DistrustCert(newintermediate_.get());
 
@@ -274,7 +407,7 @@
 }
 
 // Trust a single server certificate.
-TEST_F(TrustStoreNSSTest, TrustedServer) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, TrustedServer) {
   AddCertsToNSS();
   TrustServerCert(target_.get());
 
@@ -287,7 +420,7 @@
 }
 
 // Trust multiple self-signed CA certificates with the same name.
-TEST_F(TrustStoreNSSTest, MultipleTrustedCAWithSameSubject) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, MultipleTrustedCAWithSameSubject) {
   AddCertsToNSS();
   TrustCert(oldroot_.get());
   TrustCert(newroot_.get());
@@ -301,7 +434,7 @@
 
 // Different trust settings for multiple self-signed CA certificates with the
 // same name.
-TEST_F(TrustStoreNSSTest, DifferingTrustCAWithSameSubject) {
+TEST_F(TrustStoreNSSTestWithoutSlotFilter, DifferingTrustCAWithSameSubject) {
   AddCertsToNSS();
   DistrustCert(oldroot_.get());
   TrustCert(newroot_.get());
@@ -313,6 +446,62 @@
   EXPECT_TRUE(HasTrust({newroot_}, CertificateTrustType::TRUSTED_ANCHOR));
 }
 
+// Tests for a TrustStoreNSS which does not allow certificates on user slots
+// to be trusted.
+class TrustStoreNSSTestDoNotAllowUserSlots : public TrustStoreNSSTestBase {
+ public:
+  TrustStoreNSSTestDoNotAllowUserSlots() = default;
+  ~TrustStoreNSSTestDoNotAllowUserSlots() override = default;
+
+  std::unique_ptr<TrustStoreNSS> CreateTrustStoreNSS() override {
+    return std::make_unique<TrustStoreNSS>(
+        trustSSL, TrustStoreNSS::DisallowTrustForCertsOnUserSlots());
+  }
+};
+
+// A certificate that is stored on a "user slot" is not trusted if the
+// TrustStoreNSS is not allowed to trust certificates on user slots.
+TEST_F(TrustStoreNSSTestDoNotAllowUserSlots, CertOnUserSlot) {
+  AddCertToNSSSlot(newroot_.get(), test_nssdb_.slot());
+  TrustCert(newroot_.get());
+  EXPECT_TRUE(HasTrust({newroot_}, CertificateTrustType::UNSPECIFIED));
+}
+
+// Tests for a TrustStoreNSS which does allows certificates on user slots to
+// be only trusted if they are on a specific user slot.
+class TrustStoreNSSTestAllowSpecifiedUserSlot : public TrustStoreNSSTestBase {
+ public:
+  TrustStoreNSSTestAllowSpecifiedUserSlot() = default;
+  ~TrustStoreNSSTestAllowSpecifiedUserSlot() override = default;
+
+  std::unique_ptr<TrustStoreNSS> CreateTrustStoreNSS() override {
+    return std::make_unique<TrustStoreNSS>(
+        trustSSL,
+        crypto::ScopedPK11Slot(PK11_ReferenceSlot(test_nssdb_.slot())));
+  }
+};
+
+// A certificate that is stored on a "user slot" is trusted if the
+// TrustStoreNSS is allowed to trust that user slot.
+TEST_F(TrustStoreNSSTestAllowSpecifiedUserSlot, CertOnUserSlot) {
+  AddCertToNSSSlot(newroot_.get(), test_nssdb_.slot());
+  TrustCert(newroot_.get());
+  EXPECT_TRUE(HasTrust({newroot_}, CertificateTrustType::TRUSTED_ANCHOR));
+}
+
+// A certificate that is stored on a "user slot" is not trusted if the
+// TrustStoreNSS is allowed to trust a user slot, but the certificate is
+// stored on another user slot.
+TEST_F(TrustStoreNSSTestAllowSpecifiedUserSlot, CertOnOtherUserSlot) {
+  AddCertToNSSSlot(newroot_.get(), other_test_nssdb_.slot());
+  TrustCert(newroot_.get());
+  EXPECT_TRUE(HasTrust({newroot_}, CertificateTrustType::UNSPECIFIED));
+}
+
+// TODO(https://crbug.com/980443): If the internal non-removable slot is
+// relevant on Chrome OS, add a test for allowing trust for certificates
+// stored on that slot.
+
 class TrustStoreNSSTestDelegate {
  public:
   TrustStoreNSSTestDelegate() : trust_store_nss_(trustSSL) {}
@@ -345,8 +534,8 @@
                                CertIssuerSourceSyncTest,
                                TrustStoreNSSTestDelegate);
 
-// NSS doesn't normalize UTF8String values, so use the not-normalized version of
-// those tests.
+// NSS doesn't normalize UTF8String values, so use the not-normalized version
+// of those tests.
 INSTANTIATE_TYPED_TEST_SUITE_P(TrustStoreNSSNotNormalizedTest,
                                CertIssuerSourceSyncNotNormalizedTest,
                                TrustStoreNSSTestDelegate);
diff --git a/net/features.gni b/net/features.gni
index 3cdaf70..26aa4b1 100644
--- a/net/features.gni
+++ b/net/features.gni
@@ -47,5 +47,6 @@
   # supported and may be switched between using the CertVerifierBuiltin feature
   # flag. This does not include platforms where the builtin cert verifier is
   # the only verifier supported.
-  builtin_cert_verifier_feature_supported = is_desktop_linux || is_mac
+  builtin_cert_verifier_feature_supported =
+      is_desktop_linux || is_mac || is_chromeos
 }
diff --git a/net/nqe/network_congestion_analyzer.cc b/net/nqe/network_congestion_analyzer.cc
new file mode 100644
index 0000000..0401585
--- /dev/null
+++ b/net/nqe/network_congestion_analyzer.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 <net/nqe/network_congestion_analyzer.h>
+
+namespace net {
+
+namespace nqe {
+
+namespace internal {
+
+NetworkCongestionAnalyzer::NetworkCongestionAnalyzer()
+    : recent_active_hosts_count_(0u) {}
+NetworkCongestionAnalyzer::~NetworkCongestionAnalyzer() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+size_t NetworkCongestionAnalyzer::GetActiveHostsCount() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  return recent_active_hosts_count_;
+}
+
+void NetworkCongestionAnalyzer::ComputeRecentQueueingDelay(
+    const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
+        recent_rtt_stats,
+    const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
+        historical_rtt_stats,
+    const int32_t downlink_kbps) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Updates downlink throughput if a new valid observation comes.
+  if (downlink_kbps != nqe::internal::INVALID_RTT_THROUGHPUT)
+    set_recent_downlink_throughput_kbps(downlink_kbps);
+  if (recent_rtt_stats.empty())
+    return;
+
+  int32_t delay_sample_sum = 0;
+  recent_active_hosts_count_ = 0u;
+  for (const auto& host_stats : recent_rtt_stats) {
+    nqe::internal::IPHash host = host_stats.first;
+    // Skip hosts that do not have historical statistics.
+    if (historical_rtt_stats.find(host) == historical_rtt_stats.end())
+      continue;
+
+    // Skip hosts that have one or fewer RTT samples or do not have the min
+    // value. They cannot provide an effective queueing delay sample.
+    if (historical_rtt_stats.at(host).observation_count <= 1 ||
+        historical_rtt_stats.at(host).canonical_pcts.find(kStatVal0p) ==
+            historical_rtt_stats.at(host).canonical_pcts.end())
+      continue;
+
+    ++recent_active_hosts_count_;
+    delay_sample_sum +=
+        recent_rtt_stats.at(host).most_recent_val -
+        historical_rtt_stats.at(host).canonical_pcts.at(kStatVal0p);
+  }
+
+  if (recent_active_hosts_count_ == 0u)
+    return;
+
+  DCHECK_LT(0u, recent_active_hosts_count_);
+
+  int32_t delay_ms =
+      delay_sample_sum / static_cast<int>(recent_active_hosts_count_);
+  recent_queueing_delay_ = base::TimeDelta::FromMilliseconds(delay_ms);
+  if (recent_downlink_per_packet_time_ms_ != base::nullopt) {
+    recent_queue_length_ = static_cast<float>(delay_ms) /
+                           recent_downlink_per_packet_time_ms_.value();
+  }
+}
+
+void NetworkCongestionAnalyzer::set_recent_downlink_throughput_kbps(
+    const int32_t downlink_kbps) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  recent_downlink_throughput_kbps_ = downlink_kbps;
+  // Time in msec to transmit one TCP packet (1500 Bytes).
+  // |recent_downlink_per_packet_time_ms_| = 1500 * 8 /
+  // |recent_downlink_throughput_kbps_|.
+  recent_downlink_per_packet_time_ms_ = 12000 / downlink_kbps;
+}
+
+}  // namespace internal
+
+}  // namespace nqe
+
+}  // namespace net
diff --git a/net/nqe/network_congestion_analyzer.h b/net/nqe/network_congestion_analyzer.h
new file mode 100644
index 0000000..f00fbcb
--- /dev/null
+++ b/net/nqe/network_congestion_analyzer.h
@@ -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.
+
+#ifndef NET_NQE_NETWORK_CONGESTION_ANALYZER_H_
+#define NET_NQE_NETWORK_CONGESTION_ANALYZER_H_
+
+#include <cmath>
+#include <map>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "net/base/net_export.h"
+#include "net/nqe/network_quality.h"
+#include "net/nqe/network_quality_estimator_util.h"
+#include "net/nqe/observation_buffer.h"
+
+namespace net {
+
+namespace nqe {
+
+namespace internal {
+
+// NetworkCongestionAnalyzer holds the network congestion information, such
+// as the recent packet queue length in the mobile edge, recent queueing delay,
+// recent downlink throughput.
+class NET_EXPORT_PRIVATE NetworkCongestionAnalyzer {
+ public:
+  NetworkCongestionAnalyzer();
+  ~NetworkCongestionAnalyzer();
+
+  // Returns the number of hosts that are involved in the last attempt of
+  // computing the recent queueing delay. These hosts are recent active hosts.
+  size_t GetActiveHostsCount() const;
+
+  // Computes the recent queueing delay. Records the observed queueing delay
+  // samples for all recent active hosts. The mean queueing delay is recorded in
+  // |recent_queueing_delay_|. |recent_rtt_stats| has all canonical statistic
+  // values of recent observations for active hosts. |historical_rtt_stats| has
+  // all canonical statistic values of historical observations for active hosts.
+  // If |downlink_kbps| is valid, updates |recent_downlink_throughput_kbps_| and
+  // |recent_downlink_per_packet_time_ms_| based on its value. If
+  // |recent_downlink_per_packet_time_ms_| has a valid value, updates
+  // |recent_queue_length_| as well.
+  void ComputeRecentQueueingDelay(
+      const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
+          recent_rtt_stats,
+      const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
+          historical_rtt_stats,
+      const int32_t downlink_kbps);
+
+  base::Optional<float> recent_queue_length() const {
+    return recent_queue_length_;
+  }
+
+  base::TimeDelta recent_queueing_delay() const {
+    return recent_queueing_delay_;
+  }
+
+ private:
+  // Sets the |recent_downlink_throughput_kbps_| with the estimated downlink
+  // throughput in kbps. Also, computes the time frame (in millisecond) to
+  // transmit one TCP packet (1500 Bytes) under this downlink throughput.
+  void set_recent_downlink_throughput_kbps(const int32_t downlink_kbps);
+
+  base::Optional<int32_t> recent_downlink_throughput_kbps() const {
+    return recent_downlink_throughput_kbps_;
+  }
+
+  // Recent downstream throughput estimate. It is the median of most recent
+  // downstream throughput observations (in kilobits per second).
+  base::Optional<int32_t> recent_downlink_throughput_kbps_;
+  // Time of transmitting one 1500-Byte TCP packet under
+  // |recent_downlink_throughput_kbps_|.
+  base::Optional<int32_t> recent_downlink_per_packet_time_ms_;
+
+  // The estimate of packet queue length. Computation is done based on the last
+  // observed downlink throughput.
+  base::Optional<float> recent_queue_length_;
+  // The estimate of queueing delay induced by packet queue.
+  base::TimeDelta recent_queueing_delay_;
+
+  // Counts the number of hosts involved in the last attempt of computing the
+  // recent queueing delay.
+  size_t recent_active_hosts_count_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  DISALLOW_COPY_AND_ASSIGN(NetworkCongestionAnalyzer);
+};
+
+}  // namespace internal
+
+}  // namespace nqe
+
+}  // namespace net
+
+#endif  // NET_NQE_NETWORK_CONGESTION_ANALYZER_H_
diff --git a/net/nqe/network_congestion_analyzer_unittest.cc b/net/nqe/network_congestion_analyzer_unittest.cc
new file mode 100644
index 0000000..a693e72
--- /dev/null
+++ b/net/nqe/network_congestion_analyzer_unittest.cc
@@ -0,0 +1,82 @@
+// 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 "net/nqe/network_congestion_analyzer.h"
+#include <map>
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "net/nqe/network_quality.h"
+#include "net/nqe/observation_buffer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace nqe {
+
+namespace internal {
+
+namespace {
+constexpr float kEpsilon = 0.001f;
+
+// Verify that the network queueing delay is computed correctly based on RTT
+// and downlink throughput observations.
+TEST(NetworkCongestionAnalyzerTest, TestComputingQueueingDelay) {
+  NetworkCongestionAnalyzer analyzer;
+  std::map<uint64_t, CanonicalStats> recent_rtt_stats;
+  std::map<uint64_t, CanonicalStats> historical_rtt_stats;
+  int32_t downlink_kbps = nqe::internal::INVALID_RTT_THROUGHPUT;
+
+  // Checks that no result is updated when providing empty RTT observations and
+  // an invalid downlink throughput observation.
+  analyzer.ComputeRecentQueueingDelay(recent_rtt_stats, historical_rtt_stats,
+                                      downlink_kbps);
+  EXPECT_TRUE(analyzer.recent_queueing_delay().is_zero());
+
+  const uint64_t host_1 = 0x101010UL;
+  const uint64_t host_2 = 0x202020UL;
+  // Checks that the queueing delay is updated based on hosts with valid RTT
+  // observations. For example, the computation should be done by using data
+  // from host 1 only because host 2 does not provide a valid min RTT value.
+  std::map<int32_t, int32_t> recent_stat_host_1 = {{kStatVal0p, 1100}};
+  std::map<int32_t, int32_t> historical_stat_host_1 = {{kStatVal0p, 600}};
+  CanonicalStats recent_rtt_host_1 =
+      CanonicalStats(recent_stat_host_1, 1400, 5);
+  CanonicalStats historical_rtt_host_1 =
+      CanonicalStats(historical_stat_host_1, 1400, 15);
+
+  std::map<int32_t, int32_t> recent_stat_host_2 = {{kStatVal0p, 1200}};
+  std::map<int32_t, int32_t> historical_stat_host_2 = {{kStatVal50p, 1200}};
+  CanonicalStats recent_rtt_host_2 =
+      CanonicalStats(recent_stat_host_2, 1600, 3);
+  CanonicalStats historical_rtt_host_2 =
+      CanonicalStats(historical_stat_host_2, 1600, 8);
+  recent_rtt_stats.emplace(host_1, recent_rtt_host_1);
+  recent_rtt_stats.emplace(host_2, recent_rtt_host_2);
+  historical_rtt_stats.emplace(host_1, historical_rtt_host_1);
+  historical_rtt_stats.emplace(host_2, historical_rtt_host_2);
+
+  analyzer.ComputeRecentQueueingDelay(recent_rtt_stats, historical_rtt_stats,
+                                      downlink_kbps);
+  EXPECT_EQ(800, analyzer.recent_queueing_delay().InMilliseconds());
+
+  // Checks that the queueing delay is updated correctly based on all hosts when
+  // RTT observations and the throughput observation are valid.
+  historical_rtt_stats[host_2].canonical_pcts[kStatVal0p] = 1000;
+  downlink_kbps = 120;
+  analyzer.ComputeRecentQueueingDelay(recent_rtt_stats, historical_rtt_stats,
+                                      downlink_kbps);
+  EXPECT_EQ(700, analyzer.recent_queueing_delay().InMilliseconds());
+  EXPECT_NEAR(7.0, analyzer.recent_queue_length().value_or(0), kEpsilon);
+}
+
+}  // namespace
+
+}  // namespace internal
+
+}  // namespace nqe
+
+}  // namespace net
\ No newline at end of file
diff --git a/net/nqe/network_quality_estimator.cc b/net/nqe/network_quality_estimator.cc
index 2aa2322..9eafc5fa 100644
--- a/net/nqe/network_quality_estimator.cc
+++ b/net/nqe/network_quality_estimator.cc
@@ -161,6 +161,7 @@
               params_->weight_multiplier_per_signal_strength_level())},
       effective_connection_type_at_last_main_frame_(
           EFFECTIVE_CONNECTION_TYPE_UNKNOWN),
+      queueing_delay_update_interval_(base::TimeDelta::FromMilliseconds(2000)),
       effective_connection_type_recomputation_interval_(
           base::TimeDelta::FromSeconds(10)),
       rtt_observations_size_at_last_ect_computation_(0),
@@ -716,6 +717,62 @@
                             EFFECTIVE_CONNECTION_TYPE_LAST);
 }
 
+bool NetworkQualityEstimator::ShouldComputeNetworkQueueingDelay() const {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  const base::TimeTicks now = tick_clock_->NowTicks();
+  // Recomputes the queueing delay estimate if |queueing_delay_update_interval_|
+  // has passed.
+  return (now - last_queueing_delay_computation_ >=
+          queueing_delay_update_interval_);
+}
+
+void NetworkQualityEstimator::ComputeNetworkQueueingDelay() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  if (!ShouldComputeNetworkQueueingDelay())
+    return;
+
+  const base::TimeTicks now = tick_clock_->NowTicks();
+  last_queueing_delay_computation_ = now;
+  // The time after which observations are considered as recent data.
+  const base::TimeTicks recent_start_time =
+      now - base::TimeDelta::FromMilliseconds(1000);
+  // The time after which observations are considered as historical data.
+  const base::TimeTicks historical_start_time =
+      now - base::TimeDelta::FromMilliseconds(30000);
+
+  // Checks if a valid downlink throughput estimation is available.
+  int32_t downlink_kbps = 0;
+  if (!GetRecentDownlinkThroughputKbps(recent_start_time, &downlink_kbps))
+    downlink_kbps = nqe::internal::INVALID_RTT_THROUGHPUT;
+
+  // Gets recent RTT statistic values.
+  std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>
+      recent_rtt_stats =
+          rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT]
+              .GetCanonicalStatsKeyedByHosts(recent_start_time,
+                                             std::set<nqe::internal::IPHash>());
+
+  if (recent_rtt_stats.empty())
+    return;
+
+  // Gets the set of active hosts. Only computes the historical stats for recent
+  // active hosts.
+  std::set<nqe::internal::IPHash> active_hosts;
+  for (const auto& host_stat : recent_rtt_stats)
+    active_hosts.insert(host_stat.first);
+
+  std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>
+      historical_rtt_stats =
+          rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT]
+              .GetCanonicalStatsKeyedByHosts(historical_start_time,
+                                             active_hosts);
+
+  network_congestion_analyzer_.ComputeRecentQueueingDelay(
+      recent_rtt_stats, historical_rtt_stats, downlink_kbps);
+}
+
 void NetworkQualityEstimator::ComputeEffectiveConnectionType() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
@@ -1330,11 +1387,12 @@
     const base::Optional<nqe::internal::IPHash>& host) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK_LT(nqe::internal::INVALID_RTT_THROUGHPUT, rtt.InMilliseconds());
-
   Observation observation(rtt.InMilliseconds(), tick_clock_->NowTicks(),
                           current_network_id_.signal_strength,
                           ProtocolSourceToObservationSource(protocol), host);
   AddAndNotifyObserversOfRTT(observation);
+
+  ComputeNetworkQueueingDelay();
 }
 
 void NetworkQualityEstimator::AddAndNotifyObserversOfRTT(
diff --git a/net/nqe/network_quality_estimator.h b/net/nqe/network_quality_estimator.h
index 81c1b3b..3c111ea 100644
--- a/net/nqe/network_quality_estimator.h
+++ b/net/nqe/network_quality_estimator.h
@@ -29,6 +29,7 @@
 #include "net/nqe/effective_connection_type.h"
 #include "net/nqe/effective_connection_type_observer.h"
 #include "net/nqe/event_creator.h"
+#include "net/nqe/network_congestion_analyzer.h"
 #include "net/nqe/network_id.h"
 #include "net/nqe/network_quality.h"
 #include "net/nqe/network_quality_estimator_params.h"
@@ -51,8 +52,8 @@
 namespace nqe {
 namespace internal {
 class ThroughputAnalyzer;
-}
-}
+}  // namespace internal
+}  // namespace nqe
 
 class URLRequest;
 
@@ -392,6 +393,12 @@
   // the signal quality.
   virtual int32_t GetCurrentSignalStrength() const;
 
+  // Computes the recent network queueing delay. It updates the recent
+  // per-packet queueing delay introduced by the packet queue in the mobile
+  // edge. Also, it tracks the recent downlink throughput and computes the
+  // packet queue length. Results are kept in |network_congestion_analyzer_|.
+  void ComputeNetworkQueueingDelay();
+
   // Forces computation of effective connection type, and notifies observers
   // if there is a change in its value.
   void ComputeEffectiveConnectionType();
@@ -435,6 +442,8 @@
                            ObservationDiscardedIfCachedEstimateAvailable);
   FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest,
                            TestRttThroughputObservers);
+  FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest,
+                           TestComputingNetworkQueueingDelay);
 
   // Returns the RTT value to be used when the valid RTT is unavailable. Readers
   // should discard RTT if it is set to the value returned by |InvalidRTT()|.
@@ -463,6 +472,9 @@
   // Returns true only if the |request| can be used for RTT estimation.
   bool RequestProvidesRTTObservation(const URLRequest& request) const;
 
+  // Returns true if the network queueing delay should be evaluated.
+  bool ShouldComputeNetworkQueueingDelay() const;
+
   // Returns true if ECT should be recomputed.
   bool ShouldComputeEffectiveConnectionType() const;
 
@@ -615,6 +627,13 @@
   // estimating the throughput.
   std::unique_ptr<nqe::internal::ThroughputAnalyzer> throughput_analyzer_;
 
+  // Minimum duration between two consecutive attampts of computing the network
+  // queueing delay.
+  const base::TimeDelta queueing_delay_update_interval_;
+
+  // Time when the computation of network queueing delay was last attempted.
+  base::TimeTicks last_queueing_delay_computation_;
+
   // Minimum duration between two consecutive computations of effective
   // connection type. Set to non-zero value as a performance optimization.
   const base::TimeDelta effective_connection_type_recomputation_interval_;
@@ -642,6 +661,10 @@
   nqe::internal::NetworkQuality network_quality_;
   base::Optional<base::TimeDelta> end_to_end_rtt_;
 
+  // Recent network congestion status cache. It has methods to update
+  // information related to network congestion.
+  nqe::internal::NetworkCongestionAnalyzer network_congestion_analyzer_;
+
   // Current effective connection type. It is updated on connection change
   // events. It is also updated every time there is network traffic (provided
   // the last computation was more than
diff --git a/net/nqe/network_quality_estimator_unittest.cc b/net/nqe/network_quality_estimator_unittest.cc
index 2852a3f..39b7d5f 100644
--- a/net/nqe/network_quality_estimator_unittest.cc
+++ b/net/nqe/network_quality_estimator_unittest.cc
@@ -202,6 +202,7 @@
 
 }  // namespace
 
+constexpr float kEpsilon = 0.001f;
 using NetworkQualityEstimatorTest = TestWithScopedTaskEnvironment;
 
 TEST_F(NetworkQualityEstimatorTest, TestKbpsRTTUpdates) {
@@ -565,6 +566,88 @@
   EXPECT_EQ(0U, throughput_observer.observations().size());
 }
 
+// Tests that the network queueing delay is updated correctly.
+TEST_F(NetworkQualityEstimatorTest, TestComputingNetworkQueueingDelay) {
+  base::SimpleTestTickClock tick_clock;
+  std::map<std::string, std::string> variation_params;
+  variation_params["add_default_platform_observations"] = "false";
+  TestNetworkQualityEstimator estimator(variation_params);
+  estimator.SetTickClockForTesting(&tick_clock);
+
+  // Adds historical and recent RTT observations. Active hosts are
+  // 0x101010-0x303030. Host 0x404040 did not receive any transport RTT sample
+  // recently. Host 0x505050 did not have enough RTT samples.
+  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
+  const base::TimeTicks history = tick_clock.NowTicks();
+
+  std::map<uint64_t, base::TimeDelta> historical_rtts = {
+      {0x101010UL, base::TimeDelta::FromMilliseconds(600)},
+      {0x202020UL, base::TimeDelta::FromMilliseconds(1000)},
+      {0x303030UL, base::TimeDelta::FromMilliseconds(1400)},
+      {0x303030UL, base::TimeDelta::FromMilliseconds(1600)},
+      {0x303030UL, base::TimeDelta::FromMilliseconds(1800)},
+      {0x404040UL, base::TimeDelta::FromMilliseconds(3000)}};
+  for (const auto& host_rtt : historical_rtts) {
+    const uint64_t host = host_rtt.first;
+    NetworkQualityEstimator::Observation historical_rtt(
+        historical_rtts[host].InMilliseconds(), history, INT32_MIN,
+        NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, host);
+    estimator.AddAndNotifyObserversOfRTT(historical_rtt);
+  }
+
+  // Sets the start time of the current window for computing queueing delay.
+  tick_clock.Advance(base::TimeDelta::FromMilliseconds(28000));
+  const base::TimeTicks window_start_time = tick_clock.NowTicks();
+  estimator.last_queueing_delay_computation_ = window_start_time;
+
+  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
+  const base::TimeTicks recent = tick_clock.NowTicks();
+
+  std::map<uint64_t, base::TimeDelta> recent_rtts = {
+      {0x101010UL, base::TimeDelta::FromMilliseconds(1500)},
+      {0x202020UL, base::TimeDelta::FromMilliseconds(2000)},
+      {0x303030UL, base::TimeDelta::FromMilliseconds(2500)},
+      {0x505050UL, base::TimeDelta::FromMilliseconds(2000)}};
+  for (const auto& host_rtt : recent_rtts) {
+    const uint64_t host = host_rtt.first;
+    NetworkQualityEstimator::Observation recent_rtt(
+        recent_rtts[host].InMilliseconds(), recent, INT32_MIN,
+        NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, host);
+    estimator.AddAndNotifyObserversOfRTT(recent_rtt);
+  }
+
+  // Checks that the queueing delay should not be updated because the last
+  // computation was done within the last 2 seconds.
+  EXPECT_FALSE(estimator.ShouldComputeNetworkQueueingDelay());
+
+  // Checks that the number of active hosts is 3. Also, checks that the queueing
+  // delay is computed correctly based on their RTT observations.
+  tick_clock.Advance(base::TimeDelta::FromMilliseconds(1000));
+  EXPECT_TRUE(estimator.ShouldComputeNetworkQueueingDelay());
+  estimator.ComputeNetworkQueueingDelay();
+  EXPECT_EQ(3u, estimator.network_congestion_analyzer_.GetActiveHostsCount());
+  EXPECT_EQ(base::TimeDelta::FromMilliseconds(1000),
+            estimator.network_congestion_analyzer_.recent_queueing_delay());
+  EXPECT_EQ(base::nullopt,
+            estimator.network_congestion_analyzer_.recent_queue_length());
+
+  // Adds a recent throughput observation.
+  NetworkQualityEstimator::Observation throughput_observation(
+      120, recent, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP,
+      base::nullopt);
+  estimator.AddAndNotifyObserversOfThroughput(throughput_observation);
+  int32_t downlink_kbps = 0;
+  EXPECT_TRUE(
+      estimator.GetRecentDownlinkThroughputKbps(recent, &downlink_kbps));
+
+  // Checks the queue length is updated when the downlink throughput is valid.
+  estimator.last_queueing_delay_computation_ = window_start_time;
+  estimator.ComputeNetworkQueueingDelay();
+  EXPECT_NEAR(
+      estimator.network_congestion_analyzer_.recent_queue_length().value_or(0),
+      10.0, kEpsilon);
+}
+
 TEST_F(NetworkQualityEstimatorTest, QuicObservations) {
   base::HistogramTester histogram_tester;
   std::map<std::string, std::string> variation_params;
diff --git a/net/nqe/observation_buffer.cc b/net/nqe/observation_buffer.cc
index 8332ba3..4633ea1 100644
--- a/net/nqe/observation_buffer.cc
+++ b/net/nqe/observation_buffer.cc
@@ -21,6 +21,24 @@
 namespace nqe {
 
 namespace internal {
+CanonicalStats::CanonicalStats() = default;
+
+CanonicalStats::CanonicalStats(std::map<int32_t, int32_t>& canonical_pcts,
+                               int32_t most_recent_val,
+                               size_t observation_count)
+    : canonical_pcts(canonical_pcts),
+      most_recent_val(most_recent_val),
+      observation_count(observation_count) {}
+
+CanonicalStats::CanonicalStats(const CanonicalStats& other)
+    : canonical_pcts(other.canonical_pcts),
+      most_recent_val(other.most_recent_val),
+      observation_count(other.observation_count) {}
+
+CanonicalStats::~CanonicalStats() = default;
+
+CanonicalStats& CanonicalStats::operator=(const CanonicalStats& other) =
+    default;
 
 ObservationBuffer::ObservationBuffer(
     const NetworkQualityEstimatorParams* params,
@@ -112,6 +130,65 @@
   return weighted_observations.at(weighted_observations.size() - 1).value;
 }
 
+std::map<IPHash, CanonicalStats>
+ObservationBuffer::GetCanonicalStatsKeyedByHosts(
+    const base::TimeTicks& begin_timestamp,
+    const std::set<IPHash>& target_hosts) const {
+  DCHECK_GE(Capacity(), Size());
+
+  // Computes for all hosts if |target_hosts| is empty. Otherwise, only
+  // updates map entries for hosts in |target_hosts| and ignores observations
+  // from other hosts.
+  bool filter_on_target_hosts = !(target_hosts.empty());
+
+  // Split observations into several subgroups keyed by their corresponding
+  // hosts. Skip observations without a host tag. Filter observations based
+  // on begin_timestamp. If |target_hosts| is not empty, filter obesrvations
+  // that do not belong to any host in the set.
+  std::map<IPHash, std::vector<int32_t>> host_keyed_observations;
+  for (const auto& observation : observations_) {
+    if (!observation.host())
+      continue;
+    if (observation.timestamp() < begin_timestamp)
+      continue;
+    // Skip zero values. Transport RTTs can have zero values in the beginning
+    // of a connection. It happens because the implementation of TCP's
+    // Exponentially Weighted Moving Average (EWMA) starts from zero.
+    if (observation.value() < 1)
+      continue;
+
+    IPHash host = observation.host().value();
+    if (filter_on_target_hosts && target_hosts.find(host) == target_hosts.end())
+      continue;
+
+    // Create the map entry if it did not already exist.
+    host_keyed_observations.emplace(host, std::vector<int32_t>());
+    host_keyed_observations[host].push_back(observation.value());
+  }
+
+  std::map<IPHash, CanonicalStats> host_keyed_stats;
+  if (host_keyed_observations.empty())
+    return host_keyed_stats;
+
+  // Calculate the canonical percentile values for each host.
+  for (auto& host_observations : host_keyed_observations) {
+    const IPHash& host = host_observations.first;
+    auto& observations = host_observations.second;
+    host_keyed_stats.emplace(host, CanonicalStats());
+    size_t count = observations.size();
+
+    std::sort(observations.begin(), observations.end());
+    for (size_t i = 0; i < base::size(kCanonicalPercentiles); ++i) {
+      int pct_index = (count - 1) * kCanonicalPercentiles[i] / 100;
+      host_keyed_stats[host].canonical_pcts[kCanonicalPercentiles[i]] =
+          observations[pct_index];
+    }
+    host_keyed_stats[host].most_recent_val = observations.back();
+    host_keyed_stats[host].observation_count = count;
+  }
+  return host_keyed_stats;
+}
+
 void ObservationBuffer::RemoveObservationsWithSource(
     bool deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_MAX]) {
   base::EraseIf(observations_,
diff --git a/net/nqe/observation_buffer.h b/net/nqe/observation_buffer.h
index d3526581..300f7b3 100644
--- a/net/nqe/observation_buffer.h
+++ b/net/nqe/observation_buffer.h
@@ -33,7 +33,34 @@
 namespace nqe {
 
 namespace internal {
+constexpr int32_t kStatVal0p = 0;
+constexpr int32_t kStatVal5p = 5;
+constexpr int32_t kStatVal50p = 50;
+constexpr int32_t kStatVal95p = 95;
+constexpr int32_t kStatVal99p = 99;
+constexpr int32_t kCanonicalPercentiles[] = {
+    kStatVal0p, kStatVal5p, kStatVal50p, kStatVal95p, kStatVal99p};
 
+struct NET_EXPORT_PRIVATE CanonicalStats {
+  CanonicalStats();
+  CanonicalStats(std::map<int32_t, int32_t>& canonical_pcts,
+                 int32_t most_recent_val,
+                 size_t observation_count);
+  CanonicalStats(const CanonicalStats& other);
+  ~CanonicalStats();
+
+  CanonicalStats& operator=(const CanonicalStats& other);
+
+  // Canonical percentiles values for a distribution.
+  std::map<int32_t, int32_t> canonical_pcts;
+
+  // The most recent value.
+  int32_t most_recent_val = 0;
+
+  // Counts the number of observations that were available for
+  // computing these results.
+  size_t observation_count = 0;
+};
 struct WeightedObservation;
 
 // Stores observations sorted by time and provides utility functions for
@@ -79,6 +106,16 @@
                                         int percentile,
                                         size_t* observations_count) const;
 
+  // Computes canonical statistic values of the observations for all hosts if
+  // |target_hosts| is empty. Otherwise, computes canonical statistic values
+  // only for hosts that are in the |target_hosts| set. Only observations made
+  // on or after |begin_timestamp| are considered. Returns all canonical
+  // statistics keyed by hosts. These include canonical percentile values, the
+  // most recent value, and the number of observations to compute these values.
+  std::map<IPHash, CanonicalStats> GetCanonicalStatsKeyedByHosts(
+      const base::TimeTicks& begin_timestamp,
+      const std::set<IPHash>& target_hosts) const;
+
   void SetTickClockForTesting(const base::TickClock* tick_clock) {
     tick_clock_ = tick_clock;
   }
diff --git a/net/nqe/observation_buffer_unittest.cc b/net/nqe/observation_buffer_unittest.cc
index f5d946f..4ccd9b5 100644
--- a/net/nqe/observation_buffer_unittest.cc
+++ b/net/nqe/observation_buffer_unittest.cc
@@ -87,6 +87,91 @@
   EXPECT_LT(result_lowest, result_highest);
 }
 
+// Verifies that the percentiles are correctly computed when results must be
+// update for each individual host. All observations can have the same timestamp
+// or different timestamps.
+TEST(NetworkQualityObservationBufferTest, GetPercentileStatsForAllHosts) {
+  std::map<std::string, std::string> variation_params;
+  NetworkQualityEstimatorParams params(variation_params);
+  base::SimpleTestTickClock tick_clock;
+  tick_clock.Advance(base::TimeDelta::FromMinutes(1));
+  // The observation buffer holds mixed observations for different hosts.
+  ObservationBuffer mixed_buffer(&params, &tick_clock, 0.5, 1.0);
+  const base::TimeTicks now = tick_clock.NowTicks();
+  const base::TimeTicks history = now - base::TimeDelta::FromMilliseconds(1);
+  const base::TimeTicks future = now + base::TimeDelta::FromMilliseconds(1);
+  const uint64_t host_1 = 0x101010UL;
+  const uint64_t host_2 = 0x202020UL;
+  const size_t total_observaions_count = 100;
+
+  // Inserts samples from {1,2,3,...,100} for |host_1|. Insert samples from
+  // {1,1,2,2,3,3,...,50,50} for |host_2|. Verifies all percentiles are
+  // computed correctly for both hosts.
+  for (size_t i = 1; i <= total_observaions_count; ++i) {
+    mixed_buffer.AddObservation(Observation(
+        i, now, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, host_1));
+    mixed_buffer.AddObservation(
+        Observation((i + 1) / 2, now, INT32_MIN,
+                    NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, host_2));
+  }
+  EXPECT_EQ(total_observaions_count * 2, mixed_buffer.Size());
+
+  std::set<uint64_t> empty_hosts_set;
+  std::map<uint64_t, CanonicalStats> recent_rtt_stats =
+      mixed_buffer.GetCanonicalStatsKeyedByHosts(history, empty_hosts_set);
+
+  // All observations are categories into two groups keyed by two hosts.
+  // In each group, all percentile statistics are updated and the number of
+  // available observations are also updated correctly.
+  EXPECT_EQ(2u, recent_rtt_stats.size());
+  EXPECT_EQ(total_observaions_count,
+            recent_rtt_stats[host_1].observation_count);
+  EXPECT_EQ(total_observaions_count,
+            recent_rtt_stats[host_2].observation_count);
+
+  // Checks all canonical percentile values are correct.
+  // For |host_1|, percentile_val = percentile.
+  EXPECT_EQ(1, recent_rtt_stats[host_1].canonical_pcts[kStatVal0p]);
+  EXPECT_EQ(5, recent_rtt_stats[host_1].canonical_pcts[kStatVal5p]);
+  EXPECT_EQ(50, recent_rtt_stats[host_1].canonical_pcts[kStatVal50p]);
+  EXPECT_EQ(95, recent_rtt_stats[host_1].canonical_pcts[kStatVal95p]);
+  EXPECT_EQ(99, recent_rtt_stats[host_1].canonical_pcts[kStatVal99p]);
+  // For |host_2|, percentile_val = (percentile + 1) / 2.
+  EXPECT_EQ(1, recent_rtt_stats[host_2].canonical_pcts[kStatVal0p]);
+  EXPECT_EQ(3, recent_rtt_stats[host_2].canonical_pcts[kStatVal5p]);
+  EXPECT_EQ(25, recent_rtt_stats[host_2].canonical_pcts[kStatVal50p]);
+  EXPECT_EQ(48, recent_rtt_stats[host_2].canonical_pcts[kStatVal95p]);
+  EXPECT_EQ(50, recent_rtt_stats[host_2].canonical_pcts[kStatVal99p]);
+
+  // Checks results are cleared because all buffered observations expire.
+  // Expects the result map is empty.
+  recent_rtt_stats =
+      mixed_buffer.GetCanonicalStatsKeyedByHosts(future, empty_hosts_set);
+
+  EXPECT_TRUE(recent_rtt_stats.empty());
+
+  // Checks results contain stats only for hosts that were in the set.
+  std::set<uint64_t> target_hosts_set = {host_1};
+  recent_rtt_stats =
+      mixed_buffer.GetCanonicalStatsKeyedByHosts(history, target_hosts_set);
+  EXPECT_EQ(1u, recent_rtt_stats.size());
+  EXPECT_EQ(total_observaions_count,
+            recent_rtt_stats[host_1].observation_count);
+  EXPECT_EQ(1, recent_rtt_stats[host_1].canonical_pcts[kStatVal0p]);
+  EXPECT_EQ(5, recent_rtt_stats[host_1].canonical_pcts[kStatVal5p]);
+  EXPECT_EQ(50, recent_rtt_stats[host_1].canonical_pcts[kStatVal50p]);
+  EXPECT_EQ(95, recent_rtt_stats[host_1].canonical_pcts[kStatVal95p]);
+  EXPECT_EQ(99, recent_rtt_stats[host_1].canonical_pcts[kStatVal99p]);
+  // Checks that host 2 does not present in the results.
+  EXPECT_TRUE(recent_rtt_stats.find(host_2) == recent_rtt_stats.end());
+
+  bool deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_MAX] = {
+      false};
+  deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_TCP] = true;
+  mixed_buffer.RemoveObservationsWithSource(deleted_observation_sources);
+  EXPECT_EQ(0u, mixed_buffer.Size());
+}
+
 // Verifies that the percentiles are correctly computed. All observations have
 // the same timestamp.
 TEST(NetworkQualityObservationBufferTest, PercentileSameTimestamps) {
diff --git a/net/quic/platform/impl/quic_chromium_clock.cc b/net/quic/platform/impl/quic_chromium_clock.cc
index 7e3ff3b5..f6be6b2 100644
--- a/net/quic/platform/impl/quic_chromium_clock.cc
+++ b/net/quic/platform/impl/quic_chromium_clock.cc
@@ -4,13 +4,14 @@
 
 #include "net/quic/platform/impl/quic_chromium_clock.h"
 
-#include "base/memory/singleton.h"
+#include "base/no_destructor.h"
 #include "base/time/time.h"
 
 namespace quic {
 
 QuicChromiumClock* QuicChromiumClock::GetInstance() {
-  return base::Singleton<QuicChromiumClock>::get();
+  static base::NoDestructor<QuicChromiumClock> instance;
+  return instance.get();
 }
 QuicChromiumClock::QuicChromiumClock() {}
 
diff --git a/net/tools/cert_verify_tool/cert_verify_tool.cc b/net/tools/cert_verify_tool/cert_verify_tool.cc
index 75600e71..ef089bd 100644
--- a/net/tools/cert_verify_tool/cert_verify_tool.cc
+++ b/net/tools/cert_verify_tool/cert_verify_tool.cc
@@ -188,7 +188,9 @@
   if (impl_name == "builtin") {
     return std::make_unique<CertVerifyImplUsingProc>(
         "CertVerifyProcBuiltin",
-        net::CreateCertVerifyProcBuiltin(std::move(cert_net_fetcher)));
+        net::CreateCertVerifyProcBuiltin(
+            std::move(cert_net_fetcher),
+            nullptr /* system_trust_store_provider */));
   }
 
   if (impl_name == "pathbuilder")
diff --git a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java
index 39d0a6a7..b16743d 100644
--- a/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java
+++ b/services/device/nfc/android/java/src/org/chromium/device/nfc/NfcImpl.java
@@ -228,7 +228,7 @@
         }
 
         if (mPendingPushOperation == null) {
-            callback.call(createError(NfcErrorType.NOT_FOUND));
+            callback.call(createError(NfcErrorType.CANNOT_CANCEL));
         } else {
             completePendingPushOperation(createError(NfcErrorType.OPERATION_CANCELLED));
             callback.call(null);
@@ -376,11 +376,9 @@
      */
     private NfcError checkIfReady() {
         if (!mHasPermission || mActivity == null) {
-            return createError(NfcErrorType.SECURITY);
-        } else if (mNfcManager == null || mNfcAdapter == null) {
-            return createError(NfcErrorType.NOT_SUPPORTED);
-        } else if (!mNfcAdapter.isEnabled()) {
-            return createError(NfcErrorType.DEVICE_DISABLED);
+            return createError(NfcErrorType.NOT_ALLOWED);
+        } else if (mNfcManager == null || mNfcAdapter == null || !mNfcAdapter.isEnabled()) {
+            return createError(NfcErrorType.NOT_READABLE);
         }
         return null;
     }
diff --git a/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java b/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java
index 1cb6cf7..817024e 100644
--- a/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java
+++ b/services/device/nfc/android/junit/src/org/chromium/device/nfc/NFCTest.java
@@ -164,7 +164,7 @@
     }
 
     /**
-     * Test that error with type NOT_SUPPORTED is returned if NFC is not supported.
+     * Test that error with type NOT_READABLE is returned if NFC is not supported.
      */
     @Test
     @Feature({"NFCTest"})
@@ -175,7 +175,7 @@
         CancelAllWatchesResponse mockCallback = mock(CancelAllWatchesResponse.class);
         nfc.cancelAllWatches(mockCallback);
         verify(mockCallback).call(mErrorCaptor.capture());
-        assertEquals(NfcErrorType.NOT_SUPPORTED, mErrorCaptor.getValue().errorType);
+        assertEquals(NfcErrorType.NOT_READABLE, mErrorCaptor.getValue().errorType);
     }
 
     /**
@@ -191,7 +191,7 @@
         CancelAllWatchesResponse mockCallback = mock(CancelAllWatchesResponse.class);
         nfc.cancelAllWatches(mockCallback);
         verify(mockCallback).call(mErrorCaptor.capture());
-        assertEquals(NfcErrorType.SECURITY, mErrorCaptor.getValue().errorType);
+        assertEquals(NfcErrorType.NOT_ALLOWED, mErrorCaptor.getValue().errorType);
     }
 
     /**
diff --git a/services/device/public/mojom/nfc.mojom b/services/device/public/mojom/nfc.mojom
index 7c20ad3..9c9757c 100644
--- a/services/device/public/mojom/nfc.mojom
+++ b/services/device/public/mojom/nfc.mojom
@@ -5,14 +5,19 @@
 module device.mojom;
 
 enum NFCErrorType {
-  SECURITY,
+  // No permssion.
+  NOT_ALLOWED,
+  // Operation is not supported by the NFC Adapter
   NOT_SUPPORTED,
-  DEVICE_DISABLED,
+  // No hardware support, no NFC adapter or the adapter is disabled,
+  // or the connection cannot be established.
+  NOT_READABLE,
   NOT_FOUND,
   INVALID_MESSAGE,
   OPERATION_CANCELLED,
   TIMER_EXPIRED,
   CANNOT_CANCEL,
+  // Transfer data error.
   IO_ERROR
 };
 
diff --git a/services/identity/public/cpp/identity_manager_unittest.cc b/services/identity/public/cpp/identity_manager_unittest.cc
index 2e2561b..b32ad48 100644
--- a/services/identity/public/cpp/identity_manager_unittest.cc
+++ b/services/identity/public/cpp/identity_manager_unittest.cc
@@ -72,12 +72,13 @@
 const char kTestEmailWithPeriod[] = "m.e@gmail.com";
 #endif
 
-// Subclass of FakeProfileOAuth2TokenService with bespoke behavior.
-class CustomFakeProfileOAuth2TokenService
-    : public FakeProfileOAuth2TokenService {
+// Subclass of FakeOAuth2AccessTokenManager with bespoke behavior.
+class CustomFakeOAuth2AccessTokenManager : public FakeOAuth2AccessTokenManager {
  public:
-  CustomFakeProfileOAuth2TokenService(PrefService* user_prefs)
-      : FakeProfileOAuth2TokenService(user_prefs) {}
+  CustomFakeOAuth2AccessTokenManager(
+      OAuth2TokenService* token_service,
+      OAuth2AccessTokenManager::Delegate* delegate)
+      : FakeOAuth2AccessTokenManager(token_service, delegate) {}
 
   void set_on_access_token_invalidated_info(
       CoreAccountId expected_account_id_to_invalidate,
@@ -91,7 +92,8 @@
   }
 
  private:
-  // OAuth2TokenService:
+  friend class CustomFakeProfileOAuth2TokenService;
+  // OAuth2AccessTokenManager:
   void InvalidateAccessTokenImpl(const CoreAccountId& account_id,
                                  const std::string& client_id,
                                  const ScopeSet& scopes,
@@ -103,7 +105,7 @@
 
       // It should trigger OnAccessTokenRemovedFromCache from
       // IdentityManager::DiagnosticsObserver.
-      for (auto& observer : GetAccessTokenDiagnosticsObservers())
+      for (auto& observer : GetDiagnosticsObserversForTesting())
         observer.OnAccessTokenRemoved(account_id, scopes);
 
       std::move(on_access_token_invalidated_callback_).Run();
@@ -116,6 +118,44 @@
   base::OnceClosure on_access_token_invalidated_callback_;
 };
 
+// Subclass of FakeProfileOAuth2TokenService with bespoke behavior.
+class CustomFakeProfileOAuth2TokenService
+    : public FakeProfileOAuth2TokenService {
+ public:
+  CustomFakeProfileOAuth2TokenService(PrefService* user_prefs)
+      : FakeProfileOAuth2TokenService(user_prefs) {
+    OverrideAccessTokenManagerForTesting(
+        std::make_unique<CustomFakeOAuth2AccessTokenManager>(
+            this /* OAuth2TokenService* */,
+            this /* OAuth2AccessTokenManager::Delegate* */));
+  }
+
+  void set_on_access_token_invalidated_info(
+      CoreAccountId expected_account_id_to_invalidate,
+      std::set<std::string> expected_scopes_to_invalidate,
+      std::string expected_access_token_to_invalidate,
+      base::OnceClosure callback) {
+    GetCustomAccessTokenManager()->set_on_access_token_invalidated_info(
+        expected_account_id_to_invalidate, expected_scopes_to_invalidate,
+        expected_access_token_to_invalidate, std::move(callback));
+  }
+
+ private:
+  // OAuth2TokenService:
+  void InvalidateAccessTokenImpl(const CoreAccountId& account_id,
+                                 const std::string& client_id,
+                                 const ScopeSet& scopes,
+                                 const std::string& access_token) override {
+    GetCustomAccessTokenManager()->InvalidateAccessTokenImpl(
+        account_id, client_id, scopes, access_token);
+  }
+
+  CustomFakeOAuth2AccessTokenManager* GetCustomAccessTokenManager() {
+    return static_cast<CustomFakeOAuth2AccessTokenManager*>(
+        GetAccessTokenManager());
+  }
+};
+
 class TestIdentityManagerDiagnosticsObserver
     : IdentityManager::DiagnosticsObserver {
  public:
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 4689fe5..59306bf 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -2134,7 +2134,8 @@
               std::move(params_->trial_comparison_cert_verifier_params
                             ->report_client),
               net::CertVerifyProc::CreateDefault(cert_net_fetcher_),
-              net::CreateCertVerifyProcBuiltin(cert_net_fetcher_)));
+              net::CreateCertVerifyProcBuiltin(
+                  cert_net_fetcher_, /*system_trust_store_provider=*/nullptr)));
     }
 #endif
     if (!cert_verifier)
diff --git a/styleguide/python/blink-python.md b/styleguide/python/blink-python.md
new file mode 100644
index 0000000..f37dd42
--- /dev/null
+++ b/styleguide/python/blink-python.md
@@ -0,0 +1,18 @@
+# Blink Python Style Guide
+
+Blink follows [PEP-8](https://www.python.org/dev/peps/pep-0008/) unless an
+exception is listed below. See
+[blink/tools/blinkpy/pylintrc](https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/tools/blinkpy/pylintrc).
+
+_Note: We likely want to converge with [Chromium style](python.md), so this
+style recommendation is likely to change._
+
+## Differences from PEP-8
+
+* Line length limit is 132
+
+## Differences from Chromium style
+
+* Line length limit is 132
+* Uses four-space indent
+* Uses `function_name`, `method_name` rather than `FunctionName`, `MethodName`
diff --git a/styleguide/python/python.md b/styleguide/python/python.md
index f6cde0e..12f2c52 100644
--- a/styleguide/python/python.md
+++ b/styleguide/python/python.md
@@ -15,6 +15,8 @@
 [`//styleguide/python/OWNERS`](https://chromium.googlesource.com/chromium/src/+/master/styleguide/python/OWNERS)
 get to decide.
 
+Blink code in `third_party/blink` uses [Blink style](blink-python.md).
+
 [TOC]
 
 ## Differences from PEP-8
@@ -60,4 +62,4 @@
 * For Chromium-specific bugs, please discuss on `python@chromium.org`.
 
 #### Editor Integration
-See: https://github.com/google/yapf/tree/master/plugins
\ No newline at end of file
+See: https://github.com/google/yapf/tree/master/plugins
diff --git a/testing/buildbot/chrome.json b/testing/buildbot/chrome.json
index 81fceb6..0bf581d 100644
--- a/testing/buildbot/chrome.json
+++ b/testing/buildbot/chrome.json
@@ -985,6 +985,9 @@
         "test": "components_browsertests"
       },
       {
+        "args": [
+          "--gtest_filter=-CrashAnalyzerTest.StackTraceCollection"
+        ],
         "merge": {
           "args": [],
           "script": "//testing/merge_scripts/standard_gtest_merge.py"
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index 85a1718a..dc89335 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -408,6 +408,12 @@
           'shards': 5,
         },
       },
+      'linux-chromeos-google-rel': {
+        # TODO(crbug.com/980748): Remove this filter.
+        'args': [
+          '--gtest_filter=-CrashAnalyzerTest.StackTraceCollection',
+        ],
+      },
     },
   },
   'content_browsertests': {
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 76f47e2..9c35120 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -80,7 +80,7 @@
                                       base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enable LayoutNG.
-const base::Feature kLayoutNG{"LayoutNG", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kLayoutNG{"LayoutNG", base::FEATURE_ENABLED_BY_DEFAULT};
 
 const base::Feature kMixedContentAutoupgrade{"AutoupgradeMixedContent",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/third_party/blink/public/common/frame/occlusion_state.h b/third_party/blink/public/common/frame/occlusion_state.h
index 1faa8d4..4020be2 100644
--- a/third_party/blink/public/common/frame/occlusion_state.h
+++ b/third_party/blink/public/common/frame/occlusion_state.h
@@ -5,6 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_OCCLUSION_STATE_H_
 #define THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_OCCLUSION_STATE_H_
 
+#include <stdint.h>
+
 #include "third_party/blink/public/common/common_export.h"
 
 namespace blink {
@@ -21,6 +23,13 @@
   kMaxValue = kGuaranteedNotOccluded,
 };
 
+// These values are used to implement a browser intervention: if a cross-
+// origin iframe has moved more than 30 screen pixels (manhattan distance)
+// within its embedding page's viewport within the last 500 milliseconds, most
+// input events targeting the iframe will be quietly discarded.
+static const uint32_t kMaxChildFrameScreenRectMovement = 30;
+static const uint32_t kMinScreenRectStableTimeMs = 500;
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_PUBLIC_COMMON_FRAME_OCCLUSION_STATE_H_
diff --git a/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom b/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom
index 4350f427..ad0ff46 100644
--- a/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom
+++ b/third_party/blink/public/mojom/service_worker/service_worker_provider.mojom
@@ -29,8 +29,10 @@
 // Sent from the browser process to the renderer. Contains parameters for the
 // WebServiceWorkerNetworkProvider used for starting a service worker.
 struct ServiceWorkerProviderInfoForStartWorker {
+  // No methods on |host_ptr_info| are called, but it's needed to keep the host
+  // implementation in the browser process alive.
+  // TODO(https://crbug.com/931087): Use a separate interface.
   associated ServiceWorkerContainerHost host_ptr_info;
-  associated ServiceWorkerContainer& client_request;
 
   // The loader to use for loading the worker's main script and
   // importScripts().
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index adbd4e62..2c35dd5 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2338,6 +2338,7 @@
   kCredentialManagerGetWithUVM = 2949,
   kCredentialManagerCreateSuccessWithUVM = 2950,
   kCredentialManagerGetSuccessWithUVM = 2951,
+  kDiscardInputEventToMovingIframe = 2952,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/public/platform/web_input_event.h b/third_party/blink/public/platform/web_input_event.h
index 2bff74e..cbb93ce 100644
--- a/third_party/blink/public/platform/web_input_event.h
+++ b/third_party/blink/public/platform/web_input_event.h
@@ -258,6 +258,10 @@
     // in event sending.
     kFromDebugger = 1 << 23,
 
+    // Indicates this event is targeting an OOPIF, and the iframe or one of its
+    // ancestor frames moved within its embedding page's viewport recently.
+    kTargetFrameMovedRecently = 1 << 24,
+
     // The set of non-stateful modifiers that specifically change the
     // interpretation of the key being pressed. For example; IsLeft,
     // IsRight, IsComposing don't change the meaning of the key
@@ -440,6 +444,10 @@
 
   unsigned size() const { return size_; }
 
+  void SetTargetFrameMovedRecently() const {
+    const_cast<WebInputEvent*>(this)->modifiers_ |= kTargetFrameMovedRecently;
+  }
+
  protected:
   // The root frame scale.
   float frame_scale_;
diff --git a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc
index b947474..8b42160 100644
--- a/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc
+++ b/third_party/blink/renderer/bindings/core/v8/script_promise_resolver.cc
@@ -20,7 +20,8 @@
     : ContextLifecycleObserver(ExecutionContext::From(script_state)),
       state_(kPending),
       script_state_(script_state),
-      resolver_(script_state) {
+      resolver_(script_state),
+      keep_alive_(PERSISTENT_FROM_HERE) {
   if (GetExecutionContext()->IsContextDestroyed()) {
     state_ = kDetached;
     resolver_.Clear();
diff --git a/third_party/blink/renderer/core/animation/list_interpolation_functions_test.cc b/third_party/blink/renderer/core/animation/list_interpolation_functions_test.cc
index 22ca8a75..c99edeea 100644
--- a/third_party/blink/renderer/core/animation/list_interpolation_functions_test.cc
+++ b/third_party/blink/renderer/core/animation/list_interpolation_functions_test.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/core/animation/list_interpolation_functions.h"
 
 #include <utility>
-#include <vector>
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/animation/css_number_interpolation_type.h"
@@ -47,7 +46,7 @@
 // Creates an InterpolationValue containing a list of interpolable and
 // non-interpolable values from the pairs of input.
 InterpolationValue CreateInterpolableList(
-    const std::vector<std::pair<double, int>>& values) {
+    const Vector<std::pair<double, int>>& values) {
   return ListInterpolationFunctions::CreateList(
       values.size(), [&values](size_t i) {
         return InterpolationValue(
@@ -58,7 +57,7 @@
 
 // Creates an InterpolationValue which contains a list of interpolable values,
 // but a non-interpolable list of nullptrs.
-InterpolationValue CreateInterpolableList(const std::vector<double>& values) {
+InterpolationValue CreateInterpolableList(const Vector<double>& values) {
   return ListInterpolationFunctions::CreateList(
       values.size(), [&values](size_t i) {
         return InterpolationValue(
diff --git a/third_party/blink/renderer/core/css/css_image_generator_value.cc b/third_party/blink/renderer/core/css/css_image_generator_value.cc
index c2227e8..d3c55fe 100644
--- a/third_party/blink/renderer/core/css/css_image_generator_value.cc
+++ b/third_party/blink/renderer/core/css/css_image_generator_value.cc
@@ -70,7 +70,7 @@
 }
 
 CSSImageGeneratorValue::CSSImageGeneratorValue(ClassType class_type)
-    : CSSValue(class_type) {}
+    : CSSValue(class_type), keep_alive_(PERSISTENT_FROM_HERE) {}
 
 CSSImageGeneratorValue::~CSSImageGeneratorValue() = default;
 
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 ad63a673..6625f089 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
@@ -76,8 +76,8 @@
   // prevented, we don't need to force the subtree layout, so use exclusive
   // ancestors in that case.
   auto ancestor_view = [node] {
-    if (node->IsElementNode()) {
-      auto* context = ToElement(node)->GetDisplayLockContext();
+    if (auto* element = DynamicTo<Element>(node)) {
+      auto* context = element->GetDisplayLockContext();
       if (context && !context->ShouldLayout(DisplayLockContext::kSelf))
         return FlatTreeTraversal::InclusiveAncestorsOf(*node);
     }
@@ -100,16 +100,17 @@
 
 const Element* DisplayLockUtilities::NearestLockedInclusiveAncestor(
     const Node& node) {
-  if (!node.IsElementNode())
+  auto* element = DynamicTo<Element>(node);
+  if (!element)
     return NearestLockedExclusiveAncestor(node);
   if (!RuntimeEnabledFeatures::DisplayLockingEnabled() || !node.isConnected() ||
       node.GetDocument().LockedDisplayLockCount() == 0 ||
       !node.CanParticipateInFlatTree()) {
     return nullptr;
   }
-  if (auto* context = ToElement(node).GetDisplayLockContext()) {
+  if (auto* context = element->GetDisplayLockContext()) {
     if (context->IsLocked())
-      return &ToElement(node);
+      return element;
   }
   return NearestLockedExclusiveAncestor(node);
 }
@@ -179,8 +180,9 @@
   const Node* node = &source_node;
 
   // Special case self-node checking.
-  if (node->GetDocument().LockedDisplayLockCount() && node->IsElementNode()) {
-    auto* context = ToElement(node)->GetDisplayLockContext();
+  auto* element = DynamicTo<Element>(node);
+  if (element && node->GetDocument().LockedDisplayLockCount()) {
+    auto* context = element->GetDisplayLockContext();
     if (context && !context->ShouldLayout(DisplayLockContext::kSelf))
       return true;
   }
diff --git a/third_party/blink/renderer/core/dom/container_node.cc b/third_party/blink/renderer/core/dom/container_node.cc
index 9939d396..e25e54d4 100644
--- a/third_party/blink/renderer/core/dom/container_node.cc
+++ b/third_party/blink/renderer/core/dom/container_node.cc
@@ -976,8 +976,9 @@
 
 DISABLE_CFI_PERF
 void ContainerNode::AttachLayoutTree(AttachContext& context) {
-  if (IsElementNode() && ToElement(this)->StyleRecalcBlockedByDisplayLock(
-                             DisplayLockContext::kChildren)) {
+  auto* element = DynamicTo<Element>(this);
+  if (element &&
+      element->StyleRecalcBlockedByDisplayLock(DisplayLockContext::kChildren)) {
     // Since we block style recalc on descendants of this node due to display
     // locking, none of its descendants should have the NeedsReattachLayoutTree
     // bit set.
@@ -985,9 +986,7 @@
     // If an element is locked we shouldn't attach the layout tree for its
     // descendants. We should notify that we blocked a reattach so that we will
     // correctly attach the descendants when allowed.
-    ToElement(this)
-        ->GetDisplayLockContext()
-        ->NotifyReattachLayoutTreeWasBlocked();
+    element->GetDisplayLockContext()->NotifyReattachLayoutTreeWasBlocked();
     Node::AttachLayoutTree(context);
     return;
   }
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 97f7acb..a532335fb 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -5241,13 +5241,10 @@
       } while (previous && !previous->IsElementNode());
       return ToElement(previous);
     }
-    if (next_node->IsElementNode())
-      return ToElement(next_node);
-    Node* next = next_node;
-    do {
-      next = FlatTreeTraversal::Next(*next);
-    } while (next && !next->IsElementNode());
-    return ToElement(next);
+    for (Node* next = next_node; next; next = FlatTreeTraversal::Next(*next)) {
+      if (auto* element = DynamicTo<Element>(next))
+        return element;
+    }
   }
   return nullptr;
 }
diff --git a/third_party/blink/renderer/core/events/web_input_event_conversion.h b/third_party/blink/renderer/core/events/web_input_event_conversion.h
index 7fa760a..81c55799 100644
--- a/third_party/blink/renderer/core/events/web_input_event_conversion.h
+++ b/third_party/blink/renderer/core/events/web_input_event_conversion.h
@@ -31,7 +31,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_EVENTS_WEB_INPUT_EVENT_CONVERSION_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_EVENTS_WEB_INPUT_EVENT_CONVERSION_H_
 
-#include <vector>
 #include "third_party/blink/public/platform/web_coalesced_input_event.h"
 #include "third_party/blink/public/platform/web_input_event.h"
 #include "third_party/blink/public/platform/web_keyboard_event.h"
diff --git a/third_party/blink/renderer/core/exported/web_document_subresource_filter_test.cc b/third_party/blink/renderer/core/exported/web_document_subresource_filter_test.cc
index 65462aa..bee856da 100644
--- a/third_party/blink/renderer/core/exported/web_document_subresource_filter_test.cc
+++ b/third_party/blink/renderer/core/exported/web_document_subresource_filter_test.cc
@@ -4,9 +4,6 @@
 
 #include "third_party/blink/public/platform/web_document_subresource_filter.h"
 
-#include <string>
-#include <vector>
-
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/platform.h"
@@ -33,7 +30,7 @@
 
   LoadPolicy GetLoadPolicy(const WebURL& resource_url,
                            mojom::RequestContextType) override {
-    std::string resource_path = WebString(KURL(resource_url).GetPath()).Utf8();
+    String resource_path = KURL(resource_url).GetPath();
     if (std::find(queried_subresource_paths_.begin(),
                   queried_subresource_paths_.end(),
                   resource_path) == queried_subresource_paths_.end()) {
@@ -59,13 +56,13 @@
     blacklisted_suffixes_.push_back(suffix);
   }
 
-  const std::vector<std::string>& QueriedSubresourcePaths() const {
+  const Vector<String>& QueriedSubresourcePaths() const {
     return queried_subresource_paths_;
   }
 
  private:
   // Using STL types for compatibility with gtest/gmock.
-  std::vector<std::string> queried_subresource_paths_;
+  Vector<String> queried_subresource_paths_;
   Vector<String> blacklisted_suffixes_;
   LoadPolicy load_policy_;
 };
@@ -110,7 +107,7 @@
   void LoadDocument(TestDocumentSubresourceFilter::LoadPolicy policy) {
     client_.SetLoadPolicyFromNextLoad(policy);
     frame_test_helpers::LoadFrame(MainFrame(),
-                                  BaseURL() + "foo_with_image.html");
+                                  BaseURL().Utf8() + "foo_with_image.html");
   }
 
   void ExpectSubresourceWasLoaded(bool loaded) {
@@ -120,17 +117,16 @@
     EXPECT_EQ(loaded, !!image_element->naturalWidth());
   }
 
-  const std::string& BaseURL() const { return base_url_; }
+  const String& BaseURL() const { return base_url_; }
   WebLocalFrameImpl* MainFrame() { return web_view_helper_.LocalMainFrame(); }
-  const std::vector<std::string>& QueriedSubresourcePaths() const {
+  const Vector<String>& QueriedSubresourcePaths() const {
     return client_.SubresourceFilter()->QueriedSubresourcePaths();
   }
 
  private:
-  void RegisterMockedHttpURLLoad(const std::string& file_name) {
+  void RegisterMockedHttpURLLoad(const String& file_name) {
     url_test_helpers::RegisterMockedURLLoadFromBase(
-        WebString::FromUTF8(base_url_), test::CoreTestDataPath(),
-        WebString::FromUTF8(file_name));
+        WebString(base_url_), test::CoreTestDataPath(), WebString(file_name));
   }
 
   // testing::Test:
@@ -142,7 +138,7 @@
 
   SubresourceFilteringWebFrameClient client_;
   frame_test_helpers::WebViewHelper web_view_helper_;
-  std::string base_url_;
+  String base_url_;
 };
 
 TEST_F(WebDocumentSubresourceFilterTest, AllowedSubresource) {
diff --git a/third_party/blink/renderer/core/exported/web_form_element_observer_impl.cc b/third_party/blink/renderer/core/exported/web_form_element_observer_impl.cc
index 722965f..93f0974 100644
--- a/third_party/blink/renderer/core/exported/web_form_element_observer_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_form_element_observer_impl.cc
@@ -122,7 +122,7 @@
 WebFormElementObserverImpl::WebFormElementObserverImpl(
     HTMLElement& element,
     base::OnceClosure callback)
-    : self_keep_alive_(this) {
+    : self_keep_alive_(PERSISTENT_FROM_HERE, this) {
   mutation_callback_ =
       MakeGarbageCollected<ObserverCallback>(element, std::move(callback));
 }
diff --git a/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc b/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc
index 969f491..f5b634e1 100644
--- a/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_remote_frame_impl.cc
@@ -496,7 +496,7 @@
     : WebRemoteFrame(scope),
       client_(client),
       frame_client_(MakeGarbageCollected<RemoteFrameClientImpl>(this)),
-      self_keep_alive_(this) {
+      self_keep_alive_(PERSISTENT_FROM_HERE, this) {
   DCHECK(client);
 }
 
diff --git a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
index 853bfaa..c7f11d4 100644
--- a/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
+++ b/third_party/blink/renderer/core/frame/csp/content_security_policy.cc
@@ -27,7 +27,6 @@
 
 #include <memory>
 #include <utility>
-#include <vector>
 
 #include "third_party/blink/public/mojom/net/ip_address_space.mojom-blink.h"
 #include "third_party/blink/public/platform/platform.h"
diff --git a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
index 1792c14..8fb0a2e 100644
--- a/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
+++ b/third_party/blink/renderer/core/frame/csp/csp_directive_list.cc
@@ -6,7 +6,6 @@
 
 #include <memory>
 #include <utility>
-#include <vector>
 
 #include "services/network/public/cpp/features.h"
 #include "third_party/blink/renderer/bindings/core/v8/source_location.h"
diff --git a/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc b/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
index 099801e..83bd3d8 100644
--- a/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
+++ b/third_party/blink/renderer/core/frame/csp/source_list_directive_test.cc
@@ -4,8 +4,6 @@
 
 #include "third_party/blink/renderer/core/frame/csp/source_list_directive.h"
 
-#include <vector>
-
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
diff --git a/third_party/blink/renderer/core/frame/dom_timer_test.cc b/third_party/blink/renderer/core/frame/dom_timer_test.cc
index add9400a..9f982ed49e 100644
--- a/third_party/blink/renderer/core/frame/dom_timer_test.cc
+++ b/third_party/blink/renderer/core/frame/dom_timer_test.cc
@@ -4,8 +4,6 @@
 
 #include "third_party/blink/renderer/core/frame/dom_timer.h"
 
-#include <vector>
-
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/bindings/core/v8/idl_types.h"
@@ -35,7 +33,7 @@
   // Expected time between each iterator for setInterval(..., 1) or nested
   // setTimeout(..., 1) are 1, 1, 1, 1, 4, 4, ... as a minimum clamp of 4ms
   // is applied from the 5th iteration onwards.
-  const std::vector<Matcher<double>> kExpectedTimings = {
+  const Vector<Matcher<double>> kExpectedTimings = {
       DoubleNear(1., kThreshold), DoubleNear(1., kThreshold),
       DoubleNear(1., kThreshold), DoubleNear(1., kThreshold),
       DoubleNear(4., kThreshold), DoubleNear(4., kThreshold),
diff --git a/third_party/blink/renderer/core/frame/frame_view.cc b/third_party/blink/renderer/core/frame/frame_view.cc
index 0ae6f08..21081403 100644
--- a/third_party/blink/renderer/core/frame/frame_view.cc
+++ b/third_party/blink/renderer/core/frame/frame_view.cc
@@ -11,6 +11,7 @@
 #include "third_party/blink/renderer/core/intersection_observer/intersection_geometry.h"
 #include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
 #include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
+#include "third_party/blink/renderer/core/page/page.h"
 
 namespace blink {
 
@@ -70,8 +71,21 @@
     IntersectionGeometry geometry(nullptr, *owner_element, {},
                                   {IntersectionObserver::kMinimumThreshold},
                                   geometry_flags);
-    // geometry.IntersectionRect() is in absolute coordinates of the owning
-    // document. Map it down to absolute coordinates in the child document.
+    PhysicalRect new_rect_in_parent = geometry.IntersectionRect();
+    if (new_rect_in_parent.size != rect_in_parent_.size ||
+        ((new_rect_in_parent.X() - rect_in_parent_.X()).Abs() +
+             (new_rect_in_parent.Y() - rect_in_parent_.Y()).Abs() >
+         LayoutUnit(kMaxChildFrameScreenRectMovement))) {
+      rect_in_parent_ = new_rect_in_parent;
+      if (Page* page = GetFrame().GetPage()) {
+        rect_in_parent_stable_since_ =
+            base::TimeTicks() + base::TimeDelta::FromSecondsD(
+                                    page->Animator().Clock().CurrentTime());
+      } else {
+        rect_in_parent_stable_since_ = base::TimeTicks::Now();
+      }
+    }
+
     PhysicalRect intersection_rect = owner_layout_object->AncestorToLocalRect(
         nullptr, geometry.IntersectionRect());
     // Map from the box coordinates of the owner to the inner frame.
@@ -149,4 +163,16 @@
   }
 }
 
+bool FrameView::RectInParentIsStable(
+    const base::TimeTicks& event_timestamp) const {
+  if (event_timestamp - rect_in_parent_stable_since_ <
+      base::TimeDelta::FromMilliseconds(kMinScreenRectStableTimeMs)) {
+    return false;
+  }
+  LocalFrameView* parent = ParentFrameView();
+  if (!parent)
+    return true;
+  return parent->RectInParentIsStable(event_timestamp);
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/frame/frame_view.h b/third_party/blink/renderer/core/frame/frame_view.h
index 2de8435..86b0c41c 100644
--- a/third_party/blink/renderer/core/frame/frame_view.h
+++ b/third_party/blink/renderer/core/frame/frame_view.h
@@ -8,6 +8,7 @@
 #include "third_party/blink/public/common/frame/occlusion_state.h"
 #include "third_party/blink/public/mojom/frame/lifecycle.mojom-blink.h"
 #include "third_party/blink/renderer/core/frame/embedded_content_view.h"
+#include "third_party/blink/renderer/core/layout/geometry/physical_rect.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
@@ -52,6 +53,8 @@
                                     bool subtree_throttled,
                                     bool recurse = false);
 
+  bool RectInParentIsStable(const base::TimeTicks& timestamp) const;
+
  protected:
   virtual void SetViewportIntersection(const IntRect& viewport_intersection,
                                        FrameOcclusionState occlusion_state) = 0;
@@ -63,6 +66,8 @@
   void UpdateFrameVisibility(bool);
 
  private:
+  PhysicalRect rect_in_parent_;
+  base::TimeTicks rect_in_parent_stable_since_;
   blink::mojom::FrameVisibility frame_visibility_ =
       blink::mojom::FrameVisibility::kRenderedInViewport;
   bool hidden_for_throttling_;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 2bf09644b..b4e75c8b 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -83,6 +83,7 @@
 #include "third_party/blink/renderer/core/inspector/console_message.h"
 #include "third_party/blink/renderer/core/inspector/inspector_task_runner.h"
 #include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
+#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h"
 #include "third_party/blink/renderer/core/layout/hit_test_result.h"
 #include "third_party/blink/renderer/core/layout/layout_embedded_content.h"
 #include "third_party/blink/renderer/core/layout/layout_view.h"
@@ -1325,6 +1326,16 @@
   return LocalFrameRoot().GetOcclusionState();
 }
 
+bool LocalFrame::NeedsOcclusionTracking() const {
+  if (Document* document = GetDocument()) {
+    if (IntersectionObserverController* controller =
+            document->GetIntersectionObserverController()) {
+      return controller->NeedsOcclusionTracking();
+    }
+  }
+  return false;
+}
+
 void LocalFrame::ForceSynchronousDocumentInstall(
     const AtomicString& mime_type,
     scoped_refptr<SharedBuffer> data) {
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
index 44e202a..581f938 100644
--- a/third_party/blink/renderer/core/frame/local_frame.h
+++ b/third_party/blink/renderer/core/frame/local_frame.h
@@ -356,6 +356,7 @@
     return remote_viewport_intersection_;
   }
   FrameOcclusionState GetOcclusionState() const;
+  bool NeedsOcclusionTracking() const;
 
   // Replaces the initial empty document with a Document suitable for
   // |mime_type| and populated with the contents of |data|. Only intended for
diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc
index 69f4db5..e7f0785 100644
--- a/third_party/blink/renderer/core/frame/local_frame_view.cc
+++ b/third_party/blink/renderer/core/frame/local_frame_view.cc
@@ -1036,7 +1036,6 @@
   UpdateViewportIntersectionsForSubtree(
       IntersectionObservation::kImplicitRootObserversNeedUpdate |
       IntersectionObservation::kIgnoreDelay);
-  frame_->Owner()->SetNeedsOcclusionTracking(false);
 }
 
 LayoutSVGRoot* LocalFrameView::EmbeddedReplacedContent() const {
diff --git a/third_party/blink/renderer/core/frame/sandbox_flags.h b/third_party/blink/renderer/core/frame/sandbox_flags.h
index df8ef0ed..4c5c622 100644
--- a/third_party/blink/renderer/core/frame/sandbox_flags.h
+++ b/third_party/blink/renderer/core/frame/sandbox_flags.h
@@ -27,8 +27,6 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_SANDBOX_FLAGS_H_
 #define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_SANDBOX_FLAGS_H_
 
-#include <vector>
-
 #include "third_party/blink/public/common/feature_policy/feature_policy.h"
 #include "third_party/blink/public/common/frame/sandbox_flags.h"
 #include "third_party/blink/renderer/core/dom/space_split_string.h"
diff --git a/third_party/blink/renderer/core/frame/settings.json5 b/third_party/blink/renderer/core/frame/settings.json5
index e80e5f8..7895029 100644
--- a/third_party/blink/renderer/core/frame/settings.json5
+++ b/third_party/blink/renderer/core/frame/settings.json5
@@ -902,7 +902,7 @@
     },
     {
       name: "darkModeImagePolicy",
-      initial: "DarkModeImagePolicy::kFilterAll",
+      initial: "DarkModeImagePolicy::kFilterNone",
       type: "DarkModeImagePolicy",
       invalidate: "Paint",
     },
diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
index abe502c..2fcd0d1 100644
--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
@@ -145,7 +145,7 @@
 
 WebFrameWidgetImpl::WebFrameWidgetImpl(WebWidgetClient& client)
     : WebFrameWidgetBase(client),
-      self_keep_alive_(this) {}
+      self_keep_alive_(PERSISTENT_FROM_HERE, this) {}
 
 WebFrameWidgetImpl::~WebFrameWidgetImpl() = default;
 
diff --git a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
index 2ea36ad..e55ac915 100644
--- a/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
+++ b/third_party/blink/renderer/core/frame/web_local_frame_impl.cc
@@ -1714,7 +1714,7 @@
       interface_registry_(interface_registry),
       input_method_controller_(*this),
       spell_check_panel_host_client_(nullptr),
-      self_keep_alive_(this) {
+      self_keep_alive_(PERSISTENT_FROM_HERE, this) {
   DCHECK(client_);
   g_frame_count++;
   client_->BindToFrame(this);
diff --git a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
index 3941e27..531edb3 100644
--- a/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
+++ b/third_party/blink/renderer/core/frame/web_view_frame_widget.cc
@@ -11,8 +11,9 @@
 
 WebViewFrameWidget::WebViewFrameWidget(WebWidgetClient& client,
                                        WebViewImpl& web_view)
-    : WebFrameWidgetBase(client), web_view_(&web_view), self_keep_alive_(this) {
-}
+    : WebFrameWidgetBase(client),
+      web_view_(&web_view),
+      self_keep_alive_(PERSISTENT_FROM_HERE, this) {}
 
 WebViewFrameWidget::~WebViewFrameWidget() = default;
 
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_reaction_queue_test.cc b/third_party/blink/renderer/core/html/custom/custom_element_reaction_queue_test.cc
index b0d1320..4f3c750 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_reaction_queue_test.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_reaction_queue_test.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/core/html/custom/custom_element_reaction_queue.h"
 
 #include <initializer_list>
-#include <vector>
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element_reaction.h"
 #include "third_party/blink/renderer/core/html/custom/custom_element_reaction_test_helpers.h"
@@ -16,7 +15,7 @@
 namespace blink {
 
 TEST(CustomElementReactionQueueTest, invokeReactions_one) {
-  std::vector<char> log;
+  Vector<char> log;
   CustomElementReactionQueue* queue =
       MakeGarbageCollected<CustomElementReactionQueue>();
   HeapVector<Member<Command>>* commands =
@@ -25,12 +24,12 @@
   queue->Add(*MakeGarbageCollected<TestReaction>(commands));
   Element* test_element = CreateElement(AtomicString("my-element"));
   queue->InvokeReactions(*test_element);
-  EXPECT_EQ(log, std::vector<char>({'a'}))
+  EXPECT_EQ(log, Vector<char>({'a'}))
       << "the reaction should have been invoked";
 }
 
 TEST(CustomElementReactionQueueTest, invokeReactions_many) {
-  std::vector<char> log;
+  Vector<char> log;
   CustomElementReactionQueue* queue =
       MakeGarbageCollected<CustomElementReactionQueue>();
   {
@@ -53,12 +52,12 @@
   }
   Element* test_element = CreateElement(AtomicString("my-element"));
   queue->InvokeReactions(*test_element);
-  EXPECT_EQ(log, std::vector<char>({'a', 'b', 'c'}))
+  EXPECT_EQ(log, Vector<char>({'a', 'b', 'c'}))
       << "the reaction should have been invoked";
 }
 
 TEST(CustomElementReactionQueueTest, invokeReactions_recursive) {
-  std::vector<char> log;
+  Vector<char> log;
   CustomElementReactionQueue* queue =
       MakeGarbageCollected<CustomElementReactionQueue>();
 
@@ -87,12 +86,12 @@
   queue->Add(*first);
   Element* test_element = CreateElement(AtomicString("my-element"));
   queue->InvokeReactions(*test_element);
-  EXPECT_EQ(log, std::vector<char>({'a', 'b', 'c'}))
+  EXPECT_EQ(log, Vector<char>({'a', 'b', 'c'}))
       << "the reactions should have been invoked";
 }
 
 TEST(CustomElementReactionQueueTest, clear_duringInvoke) {
-  std::vector<char> log;
+  Vector<char> log;
   CustomElementReactionQueue* queue =
       MakeGarbageCollected<CustomElementReactionQueue>();
 
@@ -119,7 +118,7 @@
 
   Element* test_element = CreateElement(AtomicString("my-element"));
   queue->InvokeReactions(*test_element);
-  EXPECT_EQ(log, std::vector<char>({'a'}))
+  EXPECT_EQ(log, Vector<char>({'a'}))
       << "only 'a' should be logged; the second log should have been cleared";
 }
 
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack_test.cc b/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack_test.cc
index b46132e..7bfb171 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack_test.cc
+++ b/third_party/blink/renderer/core/html/custom/custom_element_reaction_stack_test.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/core/html/custom/custom_element_reaction_stack.h"
 
 #include <initializer_list>
-#include <vector>
 
 #include "base/macros.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -17,7 +16,7 @@
 namespace blink {
 
 TEST(CustomElementReactionStackTest, one) {
-  std::vector<char> log;
+  Vector<char> log;
 
   CustomElementReactionStack* stack =
       MakeGarbageCollected<CustomElementReactionStack>();
@@ -29,12 +28,12 @@
                                *MakeGarbageCollected<TestReaction>(commands));
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>({'a'}))
+  EXPECT_EQ(log, Vector<char>({'a'}))
       << "popping the reaction stack should run reactions";
 }
 
 TEST(CustomElementReactionStackTest, multipleElements) {
-  std::vector<char> log;
+  Vector<char> log;
 
   CustomElementReactionStack* stack =
       MakeGarbageCollected<CustomElementReactionStack>();
@@ -55,12 +54,12 @@
   }
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>({'a', 'b'}))
+  EXPECT_EQ(log, Vector<char>({'a', 'b'}))
       << "reactions should run in the order the elements queued";
 }
 
 TEST(CustomElementReactionStackTest, popTopEmpty) {
-  std::vector<char> log;
+  Vector<char> log;
 
   CustomElementReactionStack* stack =
       MakeGarbageCollected<CustomElementReactionStack>();
@@ -73,12 +72,12 @@
   stack->Push();
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>())
+  EXPECT_EQ(log, Vector<char>())
       << "popping the empty top-of-stack should not run any reactions";
 }
 
 TEST(CustomElementReactionStackTest, popTop) {
-  std::vector<char> log;
+  Vector<char> log;
 
   CustomElementReactionStack* stack =
       MakeGarbageCollected<CustomElementReactionStack>();
@@ -100,12 +99,12 @@
   }
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>({'b'}))
+  EXPECT_EQ(log, Vector<char>({'b'}))
       << "popping the top-of-stack should only run top-of-stack reactions";
 }
 
 TEST(CustomElementReactionStackTest, requeueingDoesNotReorderElements) {
-  std::vector<char> log;
+  Vector<char> log;
 
   Element& element = *CreateElement("a");
 
@@ -135,12 +134,12 @@
   }
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>({'a', 'b', 'z'}))
+  EXPECT_EQ(log, Vector<char>({'a', 'b', 'z'}))
       << "reactions should run together in the order elements were queued";
 }
 
 TEST(CustomElementReactionStackTest, oneReactionQueuePerElement) {
-  std::vector<char> log;
+  Vector<char> log;
 
   Element& element = *CreateElement("a");
 
@@ -178,12 +177,12 @@
   }
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>({'y', 'a', 'b'}))
+  EXPECT_EQ(log, Vector<char>({'y', 'a', 'b'}))
       << "reactions should run together in the order elements were queued";
 
   log.clear();
   stack->PopInvokingReactions();
-  EXPECT_EQ(log, std::vector<char>({'z'})) << "reactions should be run once";
+  EXPECT_EQ(log, Vector<char>({'z'})) << "reactions should be run once";
 }
 
 class EnqueueToStack : public Command {
@@ -212,7 +211,7 @@
 };
 
 TEST(CustomElementReactionStackTest, enqueueFromReaction) {
-  std::vector<char> log;
+  Vector<char> log;
 
   Element& element = *CreateElement("a");
 
@@ -232,9 +231,9 @@
   }
   stack->PopInvokingReactions();
 
-  EXPECT_EQ(log, std::vector<char>({'a'})) << "enqueued reaction from another "
-                                              "reaction should run in the same "
-                                              "invoke";
+  EXPECT_EQ(log, Vector<char>({'a'})) << "enqueued reaction from another "
+                                         "reaction should run in the same "
+                                         "invoke";
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_reaction_test_helpers.h b/third_party/blink/renderer/core/html/custom/custom_element_reaction_test_helpers.h
index a746fe3d..b240c4d 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_reaction_test_helpers.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_reaction_test_helpers.h
@@ -9,7 +9,6 @@
 
 #include <initializer_list>
 #include <memory>
-#include <vector>
 
 #include "base/macros.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -60,13 +59,13 @@
 
 class Log : public Command {
  public:
-  Log(char what, std::vector<char>& where) : what_(what), where_(where) {}
+  Log(char what, Vector<char>& where) : what_(what), where_(where) {}
   ~Log() override = default;
   void Run(Element&) override { where_.push_back(what_); }
 
  private:
   char what_;
-  std::vector<char>& where_;
+  Vector<char>& where_;
 
   DISALLOW_COPY_AND_ASSIGN(Log);
 };
diff --git a/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h b/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h
index 6f5062b..e4de6deb 100644
--- a/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h
+++ b/third_party/blink/renderer/core/html/custom/custom_element_test_helpers.h
@@ -171,7 +171,7 @@
   AtomicString namespace_uri_;
   AtomicString local_name_;
   AtomicString is_value_;
-  std::vector<std::pair<QualifiedName, AtomicString>> attributes_;
+  Vector<std::pair<QualifiedName, AtomicString>> attributes_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/forms/html_select_element.cc b/third_party/blink/renderer/core/html/forms/html_select_element.cc
index d31e5eee..869f8a0 100644
--- a/third_party/blink/renderer/core/html/forms/html_select_element.cc
+++ b/third_party/blink/renderer/core/html/forms/html_select_element.cc
@@ -2065,7 +2065,7 @@
       return;
     for (const auto& record : records) {
       if (record->type() == "attributes") {
-        const Element& element = *ToElement(record->target());
+        const auto& element = *To<Element>(record->target());
         if (record->oldValue() == element.getAttribute(record->attributeName()))
           continue;
       } else if (record->type() == "characterData") {
diff --git a/third_party/blink/renderer/core/html/html_slot_element_test.cc b/third_party/blink/renderer/core/html/html_slot_element_test.cc
index 085fe72..a0012df 100644
--- a/third_party/blink/renderer/core/html/html_slot_element_test.cc
+++ b/third_party/blink/renderer/core/html/html_slot_element_test.cc
@@ -16,7 +16,7 @@
 
 namespace {
 constexpr int kTableSize = 16;
-using Seq = std::vector<char>;
+using Seq = Vector<char>;
 using Backtrack = std::pair<size_t, size_t>;
 }
 
@@ -28,9 +28,8 @@
   std::array<std::array<Backtrack, kTableSize>, kTableSize> backtrack_table_;
 };
 
-std::vector<char> HTMLSlotElementTest::LongestCommonSubsequence(
-    const Seq& seq1,
-    const Seq& seq2) {
+Vector<char> HTMLSlotElementTest::LongestCommonSubsequence(const Seq& seq1,
+                                                           const Seq& seq2) {
   HTMLSlotElement::FillLongestCommonSubsequenceDynamicProgrammingTable(
       seq1, seq2, lcs_table_, backtrack_table_);
   Seq lcs;
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
index d1bdd7a..61b7cde 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_event_listeners_test.cc
@@ -211,7 +211,7 @@
   Persistent<MediaCustomControlsFullscreenDetector> detector =
       FullscreenDetector();
 
-  std::vector<blink::WebFullscreenVideoStatus> observed_results;
+  Vector<blink::WebFullscreenVideoStatus> observed_results;
 
   ON_CALL(*WebMediaPlayer(), SetIsEffectivelyFullscreen(_))
       .WillByDefault(testing::Invoke(
diff --git a/third_party/blink/renderer/core/html/media/html_video_element_test.cc b/third_party/blink/renderer/core/html/media/html_video_element_test.cc
index a0dc8fe..954f311 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element_test.cc
@@ -119,7 +119,7 @@
   // This is testing all possible values of WebFullscreenVideoStatus and then
   // sets the value back to a value that should put the DisplayType back to
   // inline.
-  std::vector<std::pair<WebFullscreenVideoStatus, WebMediaPlayer::DisplayType>>
+  Vector<std::pair<WebFullscreenVideoStatus, WebMediaPlayer::DisplayType>>
       tests = {
           {WebFullscreenVideoStatus::kNotEffectivelyFullscreen,
            WebMediaPlayer::DisplayType::kInline},
diff --git a/third_party/blink/renderer/core/input/event_handler.cc b/third_party/blink/renderer/core/input/event_handler.cc
index 344bd58..8e088ae 100644
--- a/third_party/blink/renderer/core/input/event_handler.cc
+++ b/third_party/blink/renderer/core/input/event_handler.cc
@@ -712,6 +712,10 @@
     return result;
   }
 
+  if (event_handling_util::ShouldDiscardEventTargetingFrame(mev.Event(),
+                                                            *frame_))
+    return WebInputEventResult::kHandledSuppressed;
+
   std::unique_ptr<UserGestureIndicator> gesture_indicator =
       LocalFrame::NotifyUserActivation(frame_);
   frame_->LocalFrameRoot()
@@ -1096,28 +1100,35 @@
   if (subframe)
     return PassMouseReleaseEventToSubframe(mev, subframe);
 
-  // Mouse events will be associated with the Document where mousedown
-  // occurred. If, e.g., there is a mousedown, then a drag to a different
-  // Document and mouseup there, the mouseup's gesture will be associated with
-  // the mousedown's Document. It's not absolutely certain that this is the
-  // correct behavior.
-  std::unique_ptr<UserGestureIndicator> gesture_indicator;
-  if (frame_->LocalFrameRoot()
-          .GetEventHandler()
-          .last_mouse_down_user_gesture_token_) {
-    gesture_indicator = std::make_unique<UserGestureIndicator>(
-        std::move(frame_->LocalFrameRoot()
-                      .GetEventHandler()
-                      .last_mouse_down_user_gesture_token_));
-  } else {
-    gesture_indicator = LocalFrame::NotifyUserActivation(frame_);
-  }
+  WebInputEventResult event_result = WebInputEventResult::kNotHandled;
 
-  WebInputEventResult event_result = DispatchMousePointerEvent(
-      WebInputEvent::kPointerUp, mev.InnerElement(), mev.CanvasRegionId(),
-      mev.Event(), Vector<WebMouseEvent>(), Vector<WebMouseEvent>(),
-      (GetSelectionController().HasExtendedSelection() &&
-       IsSelectionOverLink(mev)));
+  std::unique_ptr<UserGestureIndicator> gesture_indicator;
+  if (event_handling_util::ShouldDiscardEventTargetingFrame(mev.Event(),
+                                                            *frame_)) {
+    event_result = WebInputEventResult::kHandledSuppressed;
+  } else {
+    // Mouse events will be associated with the Document where mousedown
+    // occurred. If, e.g., there is a mousedown, then a drag to a different
+    // Document and mouseup there, the mouseup's gesture will be associated with
+    // the mousedown's Document. It's not absolutely certain that this is the
+    // correct behavior.
+    if (frame_->LocalFrameRoot()
+            .GetEventHandler()
+            .last_mouse_down_user_gesture_token_) {
+      gesture_indicator = std::make_unique<UserGestureIndicator>(
+          std::move(frame_->LocalFrameRoot()
+                        .GetEventHandler()
+                        .last_mouse_down_user_gesture_token_));
+    } else {
+      gesture_indicator = LocalFrame::NotifyUserActivation(frame_);
+    }
+
+    event_result = DispatchMousePointerEvent(
+        WebInputEvent::kPointerUp, mev.InnerElement(), mev.CanvasRegionId(),
+        mev.Event(), Vector<WebMouseEvent>(), Vector<WebMouseEvent>(),
+        (GetSelectionController().HasExtendedSelection() &&
+         IsSelectionOverLink(mev)));
+  }
 
   scroll_manager_->ClearResizeScrollableArea(false);
 
@@ -1498,6 +1509,10 @@
 
 WebInputEventResult EventHandler::HandleGestureEventInFrame(
     const GestureEventWithHitTestResults& targeted_event) {
+  if (event_handling_util::ShouldDiscardEventTargetingFrame(
+          targeted_event.Event(), *frame_)) {
+    return WebInputEventResult::kHandledSuppressed;
+  }
   return gesture_manager_->HandleGestureEventInFrame(targeted_event);
 }
 
diff --git a/third_party/blink/renderer/core/input/event_handling_util.cc b/third_party/blink/renderer/core/input/event_handling_util.cc
index 0125ff2..fc1d1b9 100644
--- a/third_party/blink/renderer/core/input/event_handling_util.cc
+++ b/third_party/blink/renderer/core/input/event_handling_util.cc
@@ -127,6 +127,30 @@
       mev);
 }
 
+bool ShouldDiscardEventTargetingFrame(const WebInputEvent& event,
+                                      const LocalFrame& frame) {
+  if (!RuntimeEnabledFeatures::DiscardInputToMovingIframesEnabled())
+    return false;
+
+  // There are two different mechanisms for tracking whether an iframe has moved
+  // recently, for OOPIF and in-process iframes. For OOPIF's, frame movement is
+  // tracked in the browser process using hit test data, and it's propagated
+  // in event.GetModifiers(). For in-process iframes, frame movement is tracked
+  // during lifecycle updates, in FrameView::UpdateViewportIntersection, and
+  // propagated via FrameView::RectInParentIsStable.
+  bool should_discard = false;
+  if (frame.NeedsOcclusionTracking() && frame.IsCrossOriginSubframe()) {
+    should_discard =
+        (event.GetModifiers() & WebInputEvent::kTargetFrameMovedRecently) ||
+        !frame.View()->RectInParentIsStable(event.TimeStamp());
+  }
+  if (should_discard) {
+    UseCounter::Count(frame.GetDocument(),
+                      WebFeature::kDiscardInputEventToMovingIframe);
+  }
+  return should_discard;
+}
+
 LocalFrame* SubframeForTargetNode(Node* node, bool* is_remote_frame) {
   if (!node)
     return nullptr;
diff --git a/third_party/blink/renderer/core/input/event_handling_util.h b/third_party/blink/renderer/core/input/event_handling_util.h
index 36871eb..9d5e6b9 100644
--- a/third_party/blink/renderer/core/input/event_handling_util.h
+++ b/third_party/blink/renderer/core/input/event_handling_util.h
@@ -53,6 +53,13 @@
 
 LocalFrame* SubframeForTargetNode(Node*, bool* is_remote_frame = nullptr);
 
+// Intervention: if an input event lands on a cross-origin iframe that has
+// moved or resized recently (recent==500ms), and which contains an
+// IntersectionObserver that is tracking visibility, then the event is quietly
+// discarded.
+bool ShouldDiscardEventTargetingFrame(const WebInputEvent& event,
+                                      const LocalFrame& frame);
+
 class PointerEventTarget {
   DISALLOW_NEW();
 
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index ee5126d..cf33cd3a 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -598,6 +598,30 @@
   event_handling_util::PointerEventTarget pointer_event_target =
       ComputePointerEventTarget(pointer_event);
 
+  bool discard = pointer_event_target.target_frame &&
+                 event_handling_util::ShouldDiscardEventTargetingFrame(
+                     event, *pointer_event_target.target_frame);
+  if (discard) {
+    PointerEvent* pointer_event = pointer_event_factory_.Create(
+        event, coalesced_events, predicted_events,
+        pointer_event_target.target_element
+            ? pointer_event_target.target_element->GetDocument().domWindow()
+            : nullptr);
+    SendTouchPointerEvent(pointer_event_target.target_element,
+                          pointer_event_factory_.CreatePointerCancelEvent(
+                              pointer_event->pointerId(), event.TimeStamp()),
+                          event.hovering);
+
+    WebPointerEvent pointer_cancel_event;
+    pointer_cancel_event.pointer_type = event.pointer_type;
+    pointer_cancel_event.SetTimeStamp(event.TimeStamp());
+    pointer_cancel_event.SetType(WebInputEvent::kPointerCancel);
+    touch_event_manager_->HandleTouchPoint(
+        pointer_cancel_event, coalesced_events, pointer_event_target);
+
+    return WebInputEventResult::kHandledSuppressed;
+  }
+
   // Any finger lifting is a user gesture only when it wasn't associated with a
   // scroll.
   // https://docs.google.com/document/d/1oF1T3O7_E4t1PYHV6gyCwHxOi3ystm0eSL5xZu7nvOg/edit#
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observer.idl b/third_party/blink/renderer/core/intersection_observer/intersection_observer.idl
index b53d71e1..f96388f 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observer.idl
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observer.idl
@@ -2,13 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// https://wicg.github.io/IntersectionObserver/#intersection-observer-callback
+// https://www.w3.org/TR/intersection-observer/#intersection-observer-interface
 
 callback IntersectionObserverCallback = void (sequence<IntersectionObserverEntry> entries, IntersectionObserver observer);
 
-// https://wicg.github.io/IntersectionObserver/#intersection-observer-interface
-
 [
+    Exposed=Window,
     ActiveScriptWrappable,
     Constructor(IntersectionObserverCallback callback, optional IntersectionObserverInit options),
     ConstructorCallWith=ScriptState,
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.cc b/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.cc
index 0fe8f35..1da67bf 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.cc
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.cc
@@ -69,7 +69,7 @@
 
 bool IntersectionObserverController::ComputeTrackedIntersectionObservations(
     unsigned flags) {
-  bool needs_occlusion_tracking = false;
+  needs_occlusion_tracking_ = false;
   if (Document* document = To<Document>(GetExecutionContext())) {
     TRACE_EVENT0("blink",
                  "IntersectionObserverController::"
@@ -77,11 +77,11 @@
     HeapVector<Member<Element>> elements_to_process;
     CopyToVector(tracked_observation_targets_, elements_to_process);
     for (auto& element : elements_to_process) {
-      needs_occlusion_tracking |=
+      needs_occlusion_tracking_ |=
           element->ComputeIntersectionObservations(flags);
     }
   }
-  return needs_occlusion_tracking;
+  return needs_occlusion_tracking_;
 }
 
 void IntersectionObserverController::AddTrackedTarget(Element& target,
@@ -89,6 +89,7 @@
   tracked_observation_targets_.insert(&target);
   if (!track_occlusion)
     return;
+  needs_occlusion_tracking_ = true;
   if (LocalFrameView* frame_view = target.GetDocument().View()) {
     if (FrameOwner* frame_owner = frame_view->GetFrame().Owner()) {
       // Set this bit as early as possible, rather than waiting for a lifecycle
diff --git a/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h b/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h
index 516756a..7986f18 100644
--- a/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h
+++ b/third_party/blink/renderer/core/intersection_observer/intersection_observer_controller.h
@@ -45,6 +45,7 @@
   // observers for which observer->trackVisibility() is true.
   void AddTrackedTarget(Element&, bool);
   void RemoveTrackedTarget(Element&);
+  bool NeedsOcclusionTracking() const { return needs_occlusion_tracking_; }
 
   void Trace(blink::Visitor*) override;
   const char* NameInHeapSnapshot() const override {
@@ -68,6 +69,9 @@
   // get supported by either of wrapper-tracing or unified GC.
   HeapVector<Member<IntersectionObserver>>
       intersection_observers_being_invoked_;
+  // This is 'true' if any tracked observation target is being tracked by an
+  // observer for which observer->trackVisibility() is true.
+  bool needs_occlusion_tracking_;
 };
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.cc b/third_party/blink/renderer/core/layout/hit_test_result.cc
index 4eb246b..e4a6a64 100644
--- a/third_party/blink/renderer/core/layout/hit_test_result.cc
+++ b/third_party/blink/renderer/core/layout/hit_test_result.cc
@@ -229,8 +229,8 @@
     inner_node_ = area;
     inner_possibly_pseudo_node_ = area;
   }
-  if (inner_node_->IsElementNode())
-    inner_element_ = ToElement(inner_node_);
+  if (auto* element = DynamicTo<Element>(inner_node_.Get()))
+    inner_element_ = element;
   else
     inner_element_ = FlatTreeTraversal::ParentElement(*inner_node_);
 }
@@ -275,8 +275,8 @@
     inner_node_->UpdateDistributionForFlatTreeTraversal();
   for (Node* title_node = inner_node_.Get(); title_node;
        title_node = FlatTreeTraversal::Parent(*title_node)) {
-    if (title_node->IsElementNode()) {
-      String title = ToElement(title_node)->title();
+    if (auto* element = DynamicTo<Element>(title_node)) {
+      String title = element->title();
       if (!title.IsNull()) {
         if (LayoutObject* layout_object = title_node->GetLayoutObject())
           dir = layout_object->StyleRef().Direction();
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 2e9fa45..a1794ba 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -2779,7 +2779,7 @@
     // get the right dirty rect.  Since this is called from
     // LayoutObject::setStyle, the relative position flag on the LayoutObject
     // has been cleared, so use the one on the style().
-    container_offset += Layer()->OffsetForInFlowPosition();
+    container_offset += OffsetForInFlowPosition();
   }
 
   if (skip_info.FilterSkipped()) {
diff --git a/third_party/blink/renderer/core/layout/layout_inline.cc b/third_party/blink/renderer/core/layout/layout_inline.cc
index f46d59c..738ec5d 100644
--- a/third_party/blink/renderer/core/layout/layout_inline.cc
+++ b/third_party/blink/renderer/core/layout/layout_inline.cc
@@ -1425,7 +1425,8 @@
     // get the right dirty rect. Since this is called from LayoutObject::
     // setStyle, the relative position flag on the LayoutObject has been
     // cleared, so use the one on the style().
-    transform_state.Move(Layer()->OffsetForInFlowPosition(), accumulation);
+    transform_state.Move(Layer()->GetLayoutObject().OffsetForInFlowPosition(),
+                         accumulation);
   }
 
   LayoutBox* container_box =
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
index 78f3c1f..eb15ec9 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.cc
@@ -684,10 +684,39 @@
 bool NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
     ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
         const String& string,
-        const ComputedStyle& style) const {
-  return text_.IsEmpty() && string.length() > 0 &&
-         string[0] == kSpaceCharacter && !style.CollapseWhiteSpace() &&
-         style.AutoWrap();
+        const ComputedStyle& style,
+        unsigned index) const {
+  DCHECK_LE(index, string.length());
+  // Check if we are at a preserved space character and auto-wrap is enabled.
+  if (style.CollapseWhiteSpace() || !style.AutoWrap() || !string.length() ||
+      index >= string.length() || string[index] != kSpaceCharacter)
+    return false;
+
+  // Preserved leading spaces must be at the beginning of the first line or just
+  // after a forced break.
+  if (index)
+    return string[index - 1] == kNewlineCharacter;
+  return text_.IsEmpty() || text_[text_.length() - 1] == kNewlineCharacter;
+}
+
+template <typename OffsetMappingBuilder>
+void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
+    InsertBreakOpportunityAfterLeadingPreservedSpaces(
+        const String& string,
+        const ComputedStyle& style,
+        LayoutText* layout_object,
+        unsigned* start) {
+  DCHECK(start);
+  if (UNLIKELY(ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
+          string, style, *start))) {
+    wtf_size_t end = *start;
+    do {
+      ++end;
+    } while (end < string.length() && string[end] == kSpaceCharacter);
+    AppendTextItem(StringView(string, *start, end - *start), layout_object);
+    AppendGeneratedBreakOpportunity(layout_object);
+    *start = end;
+  }
 }
 
 // TODO(yosin): We should remove |style| and |string| parameter because of
@@ -707,21 +736,19 @@
   // opportunity after leading preserved spaces needs a special code in the line
   // breaker. Generate an opportunity to make it easy.
   unsigned start = 0;
-  if (UNLIKELY(ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
-          string, *style))) {
-    do {
-      ++start;
-    } while (start < string.length() && string[start] == kSpaceCharacter);
-    AppendTextItem(StringView(string, 0, start), layout_object);
-    AppendGeneratedBreakOpportunity(layout_object);
-  }
-
+  InsertBreakOpportunityAfterLeadingPreservedSpaces(string, *style,
+                                                    layout_object, &start);
   for (; start < string.length();) {
     UChar c = string[start];
     if (IsControlItemCharacter(c)) {
       if (c == kNewlineCharacter) {
         AppendForcedBreak(layout_object);
         start++;
+        // A forced break is not a collapsible space, but following collapsible
+        // spaces are leading spaces and they need a special code in the line
+        // breaker. Generate an opportunity to make it easy.
+        InsertBreakOpportunityAfterLeadingPreservedSpaces(
+            string, *style, layout_object, &start);
       } else if (c == kTabulationCharacter) {
         wtf_size_t end = string.Find(
             [](UChar c) { return c != kTabulationCharacter; }, start + 1);
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
index 78cf02d..9645ee7 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder.h
@@ -220,7 +220,12 @@
 
   bool ShouldInsertBreakOpportunityAfterLeadingPreservedSpaces(
       const String&,
-      const ComputedStyle&) const;
+      const ComputedStyle&,
+      unsigned index = 0) const;
+  void InsertBreakOpportunityAfterLeadingPreservedSpaces(const String&,
+                                                         const ComputedStyle&,
+                                                         LayoutText*,
+                                                         unsigned* start);
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
index 71f5399..df95d2f 100644
--- a/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/inline/ng_inline_items_builder_test.cc
@@ -187,7 +187,7 @@
 }
 
 TEST_F(NGInlineItemsBuilderTest, CollapseNewLines) {
-  String input("text\ntext \n text\n\ntext");
+  String input("text\ntext \ntext\n\ntext");
   String collapsed("text text text text");
   TestWhitespaceValue(collapsed, input, EWhiteSpace::kNormal);
   TestWhitespaceValue(collapsed, input, EWhiteSpace::kNowrap);
@@ -425,6 +425,9 @@
                    u"\u200B"
                    "a"),
             TestAppend({{"  a", EWhiteSpace::kPreWrap}}));
+  EXPECT_EQ(String("a\n"
+                   u" \u200B"),
+            TestAppend({{"a\n ", EWhiteSpace::kPreWrap}}));
 }
 
 TEST_F(NGInlineItemsBuilderTest, BidiBlockOverride) {
diff --git a/third_party/blink/renderer/core/loader/appcache/application_cache.idl b/third_party/blink/renderer/core/loader/appcache/application_cache.idl
index e5826d20..b2d793b 100644
--- a/third_party/blink/renderer/core/loader/appcache/application_cache.idl
+++ b/third_party/blink/renderer/core/loader/appcache/application_cache.idl
@@ -26,6 +26,7 @@
 // https://html.spec.whatwg.org/C/#application-cache-api
 
 [
+    Exposed=Window,
     DoNotCheckConstants,
     SecureContext=RestrictAppCacheToSecureContexts
     // TODO(foolip): Exposed=(Window,SharedWorker)
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 36f4b8b..fc2d4d8 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -1781,7 +1781,6 @@
 void DocumentLoader::InitializePrefetchedSignedExchangeManager() {
   if (params_->prefetched_signed_exchanges.empty())
     return;
-  DCHECK(RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled());
   // |prefetched_signed_exchanges| is set only when the page is loaded from a
   // signed exchange.
   DCHECK(GetResponse().IsSignedExchangeInnerResponse());
diff --git a/third_party/blink/renderer/core/loader/loader_factory_for_frame.cc b/third_party/blink/renderer/core/loader/loader_factory_for_frame.cc
index bf76226..14196adc 100644
--- a/third_party/blink/renderer/core/loader/loader_factory_for_frame.cc
+++ b/third_party/blink/renderer/core/loader/loader_factory_for_frame.cc
@@ -95,7 +95,6 @@
   }
 
   if (prefetched_signed_exchange_manager_) {
-    DCHECK(RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled());
     auto loader =
         prefetched_signed_exchange_manager_->MaybeCreateURLLoader(webreq);
     if (loader)
diff --git a/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc b/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc
index 9c563a6..082844f 100644
--- a/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc
+++ b/third_party/blink/renderer/core/loader/prefetched_signed_exchange_manager.cc
@@ -131,7 +131,6 @@
     const String& inner_link_header,
     WebVector<std::unique_ptr<WebNavigationParams::PrefetchedSignedExchange>>
         prefetched_signed_exchanges) {
-  DCHECK(RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled());
   if (prefetched_signed_exchanges.empty())
     return nullptr;
   std::unique_ptr<AlternateSignedExchangeResourceInfo> alternative_resources =
@@ -163,7 +162,6 @@
     : frame_(frame),
       alternative_resources_(std::move(alternative_resources)),
       prefetched_exchanges_map_(std::move(prefetched_exchanges_map)) {
-  DCHECK(RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled());
 }
 
 PrefetchedSignedExchangeManager::~PrefetchedSignedExchangeManager() {}
diff --git a/third_party/blink/renderer/core/loader/preload_helper.cc b/third_party/blink/renderer/core/loader/preload_helper.cc
index aba3ed59..ef4981c 100644
--- a/third_party/blink/renderer/core/loader/preload_helper.cc
+++ b/third_party/blink/renderer/core/loader/preload_helper.cc
@@ -504,12 +504,13 @@
 
     LinkLoadParameters params(header, base_url);
     if (alternate_resource_info && params.rel.IsLinkPreload()) {
-      DCHECK(
-          RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled());
+      DCHECK(document);
+      DCHECK(RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled(
+          document));
       KURL url = params.href;
       base::Optional<ResourceType> resource_type =
           PreloadHelper::GetResourceTypeFromAsAttribute(params.as);
-      if (document && resource_type == ResourceType::kImage &&
+      if (resource_type == ResourceType::kImage &&
           !params.image_srcset.IsEmpty()) {
         // |media_values| is created based on the viewport dimensions of the
         // current page that prefetched SXGs, not on the viewport of the SXG
diff --git a/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc b/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
index d3a056ee..17784531 100644
--- a/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
+++ b/third_party/blink/renderer/core/loader/previews_resource_loading_hints_test.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/core/loader/previews_resource_loading_hints.h"
 
 #include <memory>
-#include <vector>
 
 #include "base/test/metrics/histogram_tester.h"
 #include "base/test/scoped_feature_list.h"
diff --git a/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc b/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc
index c684aa74..ab60354 100644
--- a/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc
+++ b/third_party/blink/renderer/core/loader/resource_load_observer_for_frame.cc
@@ -145,7 +145,8 @@
                                                response.RemoteIPAddress());
 
   std::unique_ptr<AlternateSignedExchangeResourceInfo> alternate_resource_info;
-  if (RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled() &&
+  if (RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled(
+          &frame_or_imported_document_->GetDocument()) &&
       response.IsSignedExchangeInnerResponse() &&
       resource->GetType() == ResourceType::kLinkPrefetch &&
       resource->LastResourceResponse()) {
diff --git a/third_party/blink/renderer/core/loader/threadable_loader.cc b/third_party/blink/renderer/core/loader/threadable_loader.cc
index 3adca3d..c216056 100644
--- a/third_party/blink/renderer/core/loader/threadable_loader.cc
+++ b/third_party/blink/renderer/core/loader/threadable_loader.cc
@@ -110,7 +110,7 @@
 
  public:
   explicit DetachedClient(ThreadableLoader* loader)
-      : self_keep_alive_(this), loader_(loader) {}
+      : self_keep_alive_(PERSISTENT_FROM_HERE, this), loader_(loader) {}
   ~DetachedClient() override {}
 
   void DidFinishLoading(uint64_t identifier) override {
diff --git a/third_party/blink/renderer/core/messaging/blink_transferable_message_struct_traits_test.cc b/third_party/blink/renderer/core/messaging/blink_transferable_message_struct_traits_test.cc
index 5864542..db72b274 100644
--- a/third_party/blink/renderer/core/messaging/blink_transferable_message_struct_traits_test.cc
+++ b/third_party/blink/renderer/core/messaging/blink_transferable_message_struct_traits_test.cc
@@ -68,11 +68,11 @@
   ASSERT_EQ(out.message->GetArrayBufferContentsArray().size(), 1U);
   WTF::ArrayBufferContents& deserialized_contents =
       out.message->GetArrayBufferContentsArray()[0];
-  std::vector<uint8_t> deserialized_data(
-      static_cast<uint8_t*>(deserialized_contents.Data()),
-      static_cast<uint8_t*>(deserialized_contents.Data()) + 8);
+  Vector<uint8_t> deserialized_data;
+  deserialized_data.Append(static_cast<uint8_t*>(deserialized_contents.Data()),
+                           8);
   ASSERT_EQ(deserialized_data.size(), 8U);
-  for (uint8_t i = 0; i < deserialized_data.size(); i++) {
+  for (wtf_size_t i = 0; i < deserialized_data.size(); i++) {
     ASSERT_TRUE(deserialized_data[i] == i);
   }
 }
diff --git a/third_party/blink/renderer/core/page/context_menu_controller_test.cc b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
index 1a2d7aa4b..a7815a0f 100644
--- a/third_party/blink/renderer/core/page/context_menu_controller_test.cc
+++ b/third_party/blink/renderer/core/page/context_menu_controller_test.cc
@@ -136,7 +136,7 @@
   EXPECT_EQ(WebContextMenuData::kMediaTypeVideo, context_menu_data.media_type);
   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
 
-  const std::vector<std::pair<WebContextMenuData::MediaFlags, bool>>
+  const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
       expected_media_flags = {
           {WebContextMenuData::kMediaInError, false},
           {WebContextMenuData::kMediaPaused, true},
@@ -197,7 +197,7 @@
   EXPECT_EQ(WebContextMenuData::kMediaTypeAudio, context_menu_data.media_type);
   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
 
-  const std::vector<std::pair<WebContextMenuData::MediaFlags, bool>>
+  const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
       expected_media_flags = {
           {WebContextMenuData::kMediaInError, false},
           {WebContextMenuData::kMediaPaused, true},
@@ -254,7 +254,7 @@
   EXPECT_EQ(WebContextMenuData::kMediaTypeVideo, context_menu_data.media_type);
   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
 
-  const std::vector<std::pair<WebContextMenuData::MediaFlags, bool>>
+  const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
       expected_media_flags = {
           {WebContextMenuData::kMediaInError, false},
           {WebContextMenuData::kMediaPaused, true},
@@ -311,7 +311,7 @@
   EXPECT_EQ(WebContextMenuData::kMediaTypeVideo, context_menu_data.media_type);
   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
 
-  const std::vector<std::pair<WebContextMenuData::MediaFlags, bool>>
+  const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
       expected_media_flags = {
           {WebContextMenuData::kMediaInError, false},
           {WebContextMenuData::kMediaPaused, true},
@@ -369,7 +369,7 @@
       GetWebFrameClient().GetContextMenuData();
   EXPECT_EQ(WebContextMenuData::kMediaTypeVideo, context_menu_data.media_type);
 
-  const std::vector<std::pair<WebContextMenuData::MediaFlags, bool>>
+  const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
       expected_media_flags = {
           {WebContextMenuData::kMediaInError, false},
           {WebContextMenuData::kMediaPaused, true},
@@ -432,7 +432,7 @@
   EXPECT_EQ(WebContextMenuData::kMediaTypeVideo, context_menu_data.media_type);
   EXPECT_EQ(video_url, context_menu_data.src_url.GetString());
 
-  const std::vector<std::pair<WebContextMenuData::MediaFlags, bool>>
+  const Vector<std::pair<WebContextMenuData::MediaFlags, bool>>
       expected_media_flags = {
           {WebContextMenuData::kMediaInError, false},
           {WebContextMenuData::kMediaPaused, true},
diff --git a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
index 8bf85ff..b4193c4 100644
--- a/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
+++ b/third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.cc
@@ -3513,9 +3513,9 @@
   auto* node = GetLayoutObject().GetNode();
   if (!node)
     return false;
-  if (target == DisplayLockContextLifecycleTarget::kSelf &&
-      node->IsElementNode()) {
-    if (auto* context = ToElement(node)->GetDisplayLockContext()) {
+  auto* element = DynamicTo<Element>(node);
+  if (target == DisplayLockContextLifecycleTarget::kSelf && element) {
+    if (auto* context = element->GetDisplayLockContext()) {
       if (!context->ShouldPaint(DisplayLockContext::kSelf))
         return true;
     }
diff --git a/third_party/blink/renderer/core/paint/paint_layer.cc b/third_party/blink/renderer/core/paint/paint_layer.cc
index 954f57fd..45d5f64 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer.cc
@@ -845,14 +845,8 @@
     }
   }
 
-  if (GetLayoutObject().IsInFlowPositioned()) {
-    PhysicalOffset new_offset = GetLayoutObject().OffsetForInFlowPosition();
-    if (rare_data_ || !new_offset.IsZero())
-      EnsureRareData().offset_for_in_flow_position = new_offset;
-    local_point += new_offset;
-  } else if (rare_data_) {
-    rare_data_->offset_for_in_flow_position = PhysicalOffset();
-  }
+  if (GetLayoutObject().IsInFlowPositioned())
+    local_point += GetLayoutObject().OffsetForInFlowPosition();
 
   location_ = local_point;
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index 31312c27..725923a 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -122,9 +122,6 @@
   PaintLayerRareData();
   ~PaintLayerRareData();
 
-  // Our current relative position offset.
-  PhysicalOffset offset_for_in_flow_position;
-
   std::unique_ptr<TransformationMatrix> transform;
 
   // Pointer to the enclosing Layer that caused us to be paginated. It is 0 if
@@ -345,11 +342,6 @@
   PaintLayer* RenderingContextRoot();
   const PaintLayer* RenderingContextRoot() const;
 
-  PhysicalOffset OffsetForInFlowPosition() const {
-    return rare_data_ ? rare_data_->offset_for_in_flow_position
-                      : PhysicalOffset();
-  }
-
   bool IsStackingContextWithNegativeZOrderChildren() const {
     DCHECK(!stacking_node_ || GetLayoutObject().StyleRef().IsStackingContext());
     return stacking_node_ && !stacking_node_->NegZOrderList().IsEmpty();
diff --git a/third_party/blink/renderer/core/paint/text_element_timing.cc b/third_party/blink/renderer/core/paint/text_element_timing.cc
index 62179ed..d61c176 100644
--- a/third_party/blink/renderer/core/paint/text_element_timing.cc
+++ b/third_party/blink/renderer/core/paint/text_element_timing.cc
@@ -39,11 +39,13 @@
 
 // static
 FloatRect TextElementTiming::ComputeIntersectionRect(
-    Node* node,
+    const LayoutObject& object,
     const IntRect& aggregated_visual_rect,
     const PropertyTreeState& property_tree_state,
     const LocalFrameView* frame_view) {
-  if (!NeededForElementTiming(node))
+  Node* node = object.GetNode();
+  DCHECK(node);
+  if (!NeededForElementTiming(*node))
     return FloatRect();
 
   FloatClipRect float_clip_visual_rect =
@@ -55,7 +57,7 @@
   return float_clip_visual_rect.Rect();
 }
 
-void TextElementTiming::OnTextNodesPainted(
+void TextElementTiming::OnTextObjectsPainted(
     const Deque<base::WeakPtr<TextRecord>>& text_nodes_painted) {
   DCHECK(performance_);
   // If the entries cannot be exposed via PerformanceObserver nor added to the
@@ -71,7 +73,7 @@
 
     // Text aggregators should be Elements!
     DCHECK(node->IsElementNode());
-    Element* element = ToElement(node);
+    auto* element = To<Element>(node);
     const AtomicString& attr =
         element->FastGetAttribute(html_names::kElementtimingAttr);
     if (attr.IsEmpty())
diff --git a/third_party/blink/renderer/core/paint/text_element_timing.h b/third_party/blink/renderer/core/paint/text_element_timing.h
index 79a2eb2..1c78ee96 100644
--- a/third_party/blink/renderer/core/paint/text_element_timing.h
+++ b/third_party/blink/renderer/core/paint/text_element_timing.h
@@ -8,6 +8,7 @@
 #include "base/memory/weak_ptr.h"
 #include "third_party/blink/renderer/core/dom/element.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
+#include "third_party/blink/renderer/core/layout/layout_object.h"
 #include "third_party/blink/renderer/core/timing/window_performance.h"
 #include "third_party/blink/renderer/platform/supplementable.h"
 #include "third_party/blink/renderer/platform/wtf/deque.h"
@@ -33,22 +34,22 @@
 
   static TextElementTiming& From(LocalDOMWindow&);
 
-  static inline bool NeededForElementTiming(Node* node) {
-    return !node->IsInShadowTree() && node->IsElementNode() &&
-           !ToElement(node)
+  static inline bool NeededForElementTiming(Node& node) {
+    return !node.IsInShadowTree() && node.IsElementNode() &&
+           !ToElement(&node)
                 ->FastGetAttribute(html_names::kElementtimingAttr)
                 .IsEmpty();
   }
 
   static FloatRect ComputeIntersectionRect(
-      Node*,
+      const LayoutObject&,
       const IntRect& aggregated_visual_rect,
       const PropertyTreeState&,
       const LocalFrameView*);
 
   // Called when the swap promise queued by TextPaintTimingDetector has been
   // resolved. Dispatches PerformanceElementTiming entries to WindowPerformance.
-  void OnTextNodesPainted(const Deque<base::WeakPtr<TextRecord>>&);
+  void OnTextObjectsPainted(const Deque<base::WeakPtr<TextRecord>>&);
 
   void Trace(blink::Visitor* visitor) override;
 
diff --git a/third_party/blink/renderer/core/paint/text_paint_timing_detector.cc b/third_party/blink/renderer/core/paint/text_paint_timing_detector.cc
index 967a94d4..20228cf 100644
--- a/third_party/blink/renderer/core/paint/text_paint_timing_detector.cc
+++ b/third_party/blink/renderer/core/paint/text_paint_timing_detector.cc
@@ -27,7 +27,7 @@
 
 namespace {
 
-constexpr size_t kTextNodeNumberLimit = 5000;
+constexpr size_t kTextObjectNumberLimit = 5000;
 
 bool LargeTextFirst(const base::WeakPtr<TextRecord>& a,
                     const base::WeakPtr<TextRecord>& b) {
@@ -165,7 +165,7 @@
       }
     }
   }
-  records_manager_.AssignPaintTimeToQueuedNodes(timestamp);
+  records_manager_.AssignPaintTimeToQueuedRecords(timestamp);
   if (records_manager_.GetLargestTextPaintManager())
     records_manager_.GetLargestTextPaintManager()->UpdateCandidate();
   awaiting_swap_promise_ = false;
@@ -184,7 +184,7 @@
   // shadow element or has no elementtiming attribute, then we should not record
   // its text.
   if (!records_manager_.IsRecordingLargestTextPaint() &&
-      !TextElementTiming::NeededForElementTiming(node))
+      !TextElementTiming::NeededForElementTiming(*node))
     return false;
 
   DOMNodeId node_id = DOMNodeIds::ExistingIdForNode(node);
@@ -200,12 +200,7 @@
     const IntRect& aggregated_visual_rect,
     const PropertyTreeState& property_tree_state) {
   DCHECK(ShouldWalkObject(aggregator));
-  DCHECK(!records_manager_.HasTooManyNodes());
-
-  Node* node = aggregator.GetNode();
-  DCHECK(node);
-  DOMNodeId node_id = DOMNodeIds::IdForNode(node);
-  DCHECK_NE(node_id, kInvalidDOMNodeId);
+  DCHECK(!records_manager_.HasTooManyObjects());
 
   // The caller should check this.
   DCHECK(!aggregated_visual_rect.IsEmpty());
@@ -221,15 +216,15 @@
     records_manager_.RecordVisibleObject(
         aggregator, aggregated_size,
         TextElementTiming::ComputeIntersectionRect(
-            node, aggregated_visual_rect, property_tree_state, frame_view_),
-        node_id);
+            aggregator, aggregated_visual_rect, property_tree_state,
+            frame_view_));
   }
 
-  if (records_manager_.HasTooManyNodes()) {
-    TRACE_EVENT_INSTANT2("loading", "TextPaintTimingDetector::OverNodeLimit",
-                         TRACE_EVENT_SCOPE_THREAD, "count_size_non_zero_nodes",
+  if (records_manager_.HasTooManyObjects()) {
+    TRACE_EVENT_INSTANT2("loading", "TextPaintTimingDetector::OverRecordLimit",
+                         TRACE_EVENT_SCOPE_THREAD, "count_visible_objects",
                          records_manager_.CountVisibleObjects(),
-                         "count_size_zero_nodes",
+                         "count_invisible_objects",
                          records_manager_.CountInvisibleObjects());
     StopRecordEntries();
   }
@@ -262,12 +257,12 @@
 }
 
 void TextRecordsManager::RemoveVisibleRecord(const LayoutObject& object) {
-  DCHECK(visible_node_map_.Contains(&object));
+  DCHECK(visible_objects_.Contains(&object));
   if (ltp_manager_) {
     ltp_manager_->RemoveVisibleRecord(
-        visible_node_map_.at(&object)->AsWeakPtr());
+        visible_objects_.at(&object)->AsWeakPtr());
   }
-  visible_node_map_.erase(&object);
+  visible_objects_.erase(&object);
   // We don't need to remove elements in |texts_queued_for_paint_time_| and
   // |cached_largest_paint_candidate_| as they are weak ptr.
 }
@@ -277,11 +272,11 @@
 }
 
 void TextRecordsManager::RemoveInvisibleRecord(const LayoutObject& object) {
-  DCHECK(invisible_node_ids_.Contains(&object));
-  invisible_node_ids_.erase(&object);
+  DCHECK(invisible_objects_.Contains(&object));
+  invisible_objects_.erase(&object);
 }
 
-void TextRecordsManager::AssignPaintTimeToQueuedNodes(
+void TextRecordsManager::AssignPaintTimeToQueuedRecords(
     const base::TimeTicks& timestamp) {
   // If texts_queued_for_paint_time_.size == 0, it means the array has been
   // consumed in a callback earlier than this one. That violates the assumption
@@ -300,7 +295,7 @@
     record->paint_time = timestamp;
   }
   if (text_element_timing_)
-    text_element_timing_->OnTextNodesPainted(texts_queued_for_paint_time_);
+    text_element_timing_->OnTextObjectsPainted(texts_queued_for_paint_time_);
   texts_queued_for_paint_time_.clear();
   if (ltp_manager_)
     ltp_manager_->SetCachedResultInvalidated(true);
@@ -309,11 +304,14 @@
 void TextRecordsManager::RecordVisibleObject(
     const LayoutObject& object,
     const uint64_t& visual_size,
-    const FloatRect& element_timing_rect,
-    DOMNodeId node_id) {
-  DCHECK(!HasTooManyNodes());
+    const FloatRect& element_timing_rect) {
+  DCHECK(!HasTooManyObjects());
   DCHECK_GT(visual_size, 0u);
 
+  Node* node = object.GetNode();
+  DCHECK(node);
+  DOMNodeId node_id = DOMNodeIds::IdForNode(node);
+  DCHECK_NE(node_id, kInvalidDOMNodeId);
   std::unique_ptr<TextRecord> record =
       std::make_unique<TextRecord>(node_id, visual_size, element_timing_rect);
   base::WeakPtr<TextRecord> record_weak_ptr = record->AsWeakPtr();
@@ -321,12 +319,12 @@
     ltp_manager_->InsertRecord(record_weak_ptr);
 
   QueueToMeasurePaintTime(record_weak_ptr);
-  visible_node_map_.insert(&object, std::move(record));
+  visible_objects_.insert(&object, std::move(record));
 }
 
-bool TextRecordsManager::HasTooManyNodes() const {
-  return visible_node_map_.size() + invisible_node_ids_.size() >=
-         kTextNodeNumberLimit;
+bool TextRecordsManager::HasTooManyObjects() const {
+  return visible_objects_.size() + invisible_objects_.size() >=
+         kTextObjectNumberLimit;
 }
 
 base::WeakPtr<TextRecord> LargestTextPaintManager::FindLargestPaintCandidate() {
@@ -353,8 +351,8 @@
 }
 
 void TextRecordsManager::CleanUp() {
-  visible_node_map_.clear();
-  invisible_node_ids_.clear();
+  visible_objects_.clear();
+  invisible_objects_.clear();
   texts_queued_for_paint_time_.clear();
   CleanUpLargestTextPaint();
 }
diff --git a/third_party/blink/renderer/core/paint/text_paint_timing_detector.h b/third_party/blink/renderer/core/paint/text_paint_timing_detector.h
index d296c9fc..42cf69d8 100644
--- a/third_party/blink/renderer/core/paint/text_paint_timing_detector.h
+++ b/third_party/blink/renderer/core/paint/text_paint_timing_detector.h
@@ -104,33 +104,32 @@
   void RemoveVisibleRecord(const LayoutObject&);
   void RemoveInvisibleRecord(const LayoutObject&);
   inline void RecordInvisibleObject(const LayoutObject& object) {
-    DCHECK(!HasTooManyNodes());
-    invisible_node_ids_.insert(&object);
+    DCHECK(!HasTooManyObjects());
+    invisible_objects_.insert(&object);
   }
   void RecordVisibleObject(const LayoutObject&,
                            const uint64_t& visual_size,
-                           const FloatRect& element_timing_rect,
-                           DOMNodeId);
+                           const FloatRect& element_timing_rect);
   bool NeedMeausuringPaintTime() const {
     return !texts_queued_for_paint_time_.IsEmpty();
   }
-  void AssignPaintTimeToQueuedNodes(const base::TimeTicks&);
+  void AssignPaintTimeToQueuedRecords(const base::TimeTicks&);
 
-  bool HasTooManyNodes() const;
+  bool HasTooManyObjects() const;
   inline bool HasRecorded(const LayoutObject& object) const {
-    return visible_node_map_.Contains(&object) ||
-           invisible_node_ids_.Contains(&object);
+    return visible_objects_.Contains(&object) ||
+           invisible_objects_.Contains(&object);
   }
 
-  size_t CountVisibleObjects() const { return visible_node_map_.size(); }
-  size_t CountInvisibleObjects() const { return invisible_node_ids_.size(); }
+  size_t CountVisibleObjects() const { return visible_objects_.size(); }
+  size_t CountInvisibleObjects() const { return invisible_objects_.size(); }
 
   inline bool IsKnownVisible(const LayoutObject& object) const {
-    return visible_node_map_.Contains(&object);
+    return visible_objects_.Contains(&object);
   }
 
   inline bool IsKnownInvisible(const LayoutObject& object) const {
-    return invisible_node_ids_.Contains(&object);
+    return invisible_objects_.Contains(&object);
   }
 
   void CleanUp();
@@ -159,11 +158,11 @@
     texts_queued_for_paint_time_.push_back(std::move(record));
   }
 
-  // Once LayoutObject* is destroyed, |visible_node_map_| and
-  // |invisible_node_ids_| must immediately clear the corresponding record from
+  // Once LayoutObject* is destroyed, |visible_objects_| and
+  // |invisible_objects_| must immediately clear the corresponding record from
   // themselves.
-  HashMap<const LayoutObject*, std::unique_ptr<TextRecord>> visible_node_map_;
-  HashSet<const LayoutObject*> invisible_node_ids_;
+  HashMap<const LayoutObject*, std::unique_ptr<TextRecord>> visible_objects_;
+  HashSet<const LayoutObject*> invisible_objects_;
 
   Deque<base::WeakPtr<TextRecord>> texts_queued_for_paint_time_;
   base::Optional<LargestTextPaintManager> ltp_manager_;
diff --git a/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc b/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
index 8cf8e34..4abf3aa 100644
--- a/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
+++ b/third_party/blink/renderer/core/paint/text_paint_timing_detector_test.cc
@@ -50,7 +50,7 @@
   wtf_size_t CountVisibleTexts() {
     DCHECK(GetTextPaintTimingDetector());
     return GetTextPaintTimingDetector()
-        ->records_manager_.visible_node_map_.size();
+        ->records_manager_.visible_objects_.size();
   }
 
   wtf_size_t CountRankingSetSize() {
diff --git a/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc b/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc
index 2725de3..7c1c09e 100644
--- a/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc
+++ b/third_party/blink/renderer/core/scheduler/scheduler_affecting_features_test.cc
@@ -30,8 +30,8 @@
 
   // Some features (e.g. document.load) are expected to appear in almost
   // any output. Filter them out to make most of the tests simpler.
-  std::vector<SchedulingPolicy::Feature> GetNonTrivialMainFrameFeatures() {
-    std::vector<SchedulingPolicy::Feature> result;
+  Vector<SchedulingPolicy::Feature> GetNonTrivialMainFrameFeatures() {
+    Vector<SchedulingPolicy::Feature> result;
     for (SchedulingPolicy::Feature feature :
          MainFrameScheduler()
              ->GetActiveFeaturesTrackedForBackForwardCacheMetrics()) {
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_theme.cc b/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
index 798a062..7150774 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_theme.cc
@@ -27,6 +27,7 @@
 
 #include "base/optional.h"
 #include "build/build_config.h"
+#include "cc/input/scrollbar.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/web_mouse_event.h"
 #include "third_party/blink/public/platform/web_point.h"
@@ -363,11 +364,11 @@
 }
 
 base::TimeDelta ScrollbarTheme::InitialAutoscrollTimerDelay() {
-  return base::TimeDelta::FromMilliseconds(250);
+  return kInitialAutoscrollTimerDelay;
 }
 
 base::TimeDelta ScrollbarTheme::AutoscrollTimerDelay() {
-  return base::TimeDelta::FromMilliseconds(50);
+  return base::TimeDelta::FromSecondsD(1.f / kAutoscrollMultiplier);
 }
 
 ScrollbarTheme& ScrollbarTheme::DeprecatedStaticGetTheme() {
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
index b11557be..8efb464 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.cc
@@ -39,6 +39,26 @@
 
 namespace blink {
 
+struct PriorityCompare {
+  PriorityCompare(double elapsed) : elapsed_(elapsed) {}
+  bool operator()(const Member<SVGSMILElement>& a,
+                  const Member<SVGSMILElement>& b) {
+    // FIXME: This should also consider possible timing relations between the
+    // elements.
+    SMILTime a_begin = a->IntervalBegin();
+    SMILTime b_begin = b->IntervalBegin();
+    // Frozen elements need to be prioritized based on their previous interval.
+    a_begin = a->IsFrozen() && elapsed_ < a_begin ? a->PreviousIntervalBegin()
+                                                  : a_begin;
+    b_begin = b->IsFrozen() && elapsed_ < b_begin ? b->PreviousIntervalBegin()
+                                                  : b_begin;
+    if (a_begin == b_begin)
+      return a->DocumentOrderIndex() < b->DocumentOrderIndex();
+    return a_begin < b_begin;
+  }
+  double elapsed_;
+};
+
 static constexpr base::TimeDelta kAnimationPolicyOnceDuration =
     base::TimeDelta::FromSeconds(3);
 
@@ -79,15 +99,16 @@
   DCHECK(!prevent_scheduled_animations_changes_);
 #endif
 
-  AttributeAnimationsMap& attribute_map =
-      scheduled_animations_.insert(target, AttributeAnimationsMap())
-          .stored_value->value;
-  Member<AnimationsLinkedHashSet>& scheduled =
+  auto& attribute_map =
+      scheduled_animations_.insert(target, AttributeMap()).stored_value->value;
+
+  auto& scheduled =
       attribute_map.insert(attribute_name, nullptr).stored_value->value;
   if (!scheduled)
-    scheduled = MakeGarbageCollected<AnimationsLinkedHashSet>();
+    scheduled = MakeGarbageCollected<ScheduledVector>();
+
   DCHECK(!scheduled->Contains(animation));
-  scheduled->insert(animation);
+  scheduled->push_back(animation);
 
   SMILTime next_fire_time = animation->NextProgressTime();
   if (next_fire_time.IsFinite())
@@ -103,18 +124,18 @@
   DCHECK(!prevent_scheduled_animations_changes_);
 #endif
 
-  GroupedAnimationsMap::iterator it = scheduled_animations_.find(target);
+  AnimationsMap::iterator it = scheduled_animations_.find(target);
   CHECK(it != scheduled_animations_.end());
-  AttributeAnimationsMap& attribute_map = it->value;
-  AttributeAnimationsMap::iterator attribute_map_it =
-      attribute_map.find(attribute_name);
+  AttributeMap& attribute_map = it->value;
+  AttributeMap::iterator attribute_map_it = attribute_map.find(attribute_name);
   DCHECK(attribute_map_it != attribute_map.end());
-  AnimationsLinkedHashSet* scheduled = attribute_map_it->value;
-  AnimationsLinkedHashSet::iterator it_animation = scheduled->find(animation);
-  DCHECK(it_animation != scheduled->end());
-  scheduled->erase(it_animation);
+  auto& scheduled = *(attribute_map_it->value);
 
-  if (scheduled->IsEmpty())
+  auto* position = std::find(scheduled.begin(), scheduled.end(), animation);
+  DCHECK(scheduled.end() != position);
+  scheduled.erase(position);
+
+  if (scheduled.IsEmpty())
     attribute_map.erase(attribute_map_it);
   if (attribute_map.IsEmpty())
     scheduled_animations_.erase(it);
@@ -237,8 +258,7 @@
 #endif
   for (const auto& attribute_entry : scheduled_animations_) {
     for (const auto& entry : attribute_entry.value) {
-      const AnimationsLinkedHashSet* scheduled = entry.value;
-      for (SVGSMILElement* element : *scheduled) {
+      for (SVGSMILElement* element : *entry.value) {
         element->Reset();
       }
     }
@@ -353,26 +373,6 @@
   document_order_indexes_dirty_ = false;
 }
 
-struct PriorityCompare {
-  PriorityCompare(double elapsed) : elapsed_(elapsed) {}
-  bool operator()(const Member<SVGSMILElement>& a,
-                  const Member<SVGSMILElement>& b) {
-    // FIXME: This should also consider possible timing relations between the
-    // elements.
-    SMILTime a_begin = a->IntervalBegin();
-    SMILTime b_begin = b->IntervalBegin();
-    // Frozen elements need to be prioritized based on their previous interval.
-    a_begin = a->IsFrozen() && elapsed_ < a_begin ? a->PreviousIntervalBegin()
-                                                  : a_begin;
-    b_begin = b->IsFrozen() && elapsed_ < b_begin ? b->PreviousIntervalBegin()
-                                                  : b_begin;
-    if (a_begin == b_begin)
-      return a->DocumentOrderIndex() < b->DocumentOrderIndex();
-    return a_begin < b_begin;
-  }
-  double elapsed_;
-};
-
 SVGSVGElement& SMILTimeContainer::OwnerSVGElement() const {
   return *owner_svg_element_;
 }
@@ -426,7 +426,7 @@
 
 #if DCHECK_IS_ON()
   // This boolean will catch any attempts to schedule/unschedule
-  // scheduledAnimations during this critical section.  Similarly, any elements
+  // scheduledAnimations during this critical section. Similarly, any elements
   // removed will unschedule themselves, so this will catch modification of
   // animationsToApply.
   prevent_scheduled_animations_changes_ = true;
@@ -435,37 +435,38 @@
   if (document_order_indexes_dirty_)
     UpdateDocumentOrderIndexes();
 
-  using AnimationsVector = HeapVector<Member<SVGSMILElement>>;
-  AnimationsVector animations_to_apply;
-  AnimationsVector scheduled_animations_in_same_group;
   for (auto& attribute_entry : scheduled_animations_) {
-    AttributeAnimationsMap& attribute_map = attribute_entry.value;
+    AttributeMap& attribute_map = attribute_entry.value;
     Vector<QualifiedName> invalid_keys;
-    for (const auto& entry : attribute_map) {
-      DCHECK(entry.value);
-      if (entry.value->IsEmpty()) {
+    for (auto& entry : attribute_map) {
+      if (entry.value->IsEmpty())
         invalid_keys.push_back(entry.key);
-        continue;
+    }
+    attribute_map.RemoveAll(invalid_keys);
+  }
+
+  HeapVector<ScheduledVector> active_sandwiches;
+  active_sandwiches.ReserveInitialCapacity(scheduled_animations_.size());
+  for (auto& attribute_entry : scheduled_animations_) {
+    AttributeMap& attribute_map = attribute_entry.value;
+    for (auto& entry : attribute_map) {
+      auto& scheduled = *entry.value;
+      if (!std::is_sorted(scheduled.begin(), scheduled.end(),
+                          PriorityCompare(elapsed))) {
+        std::sort(scheduled.begin(), scheduled.end(), PriorityCompare(elapsed));
       }
-
-      // Sort according to priority. Elements with later begin time have higher
-      // priority.  In case of a tie, document order decides.
-      // FIXME: This should also consider timing relationships between the
-      // elements. Dependents have higher priority.
-      CopyToVector(*entry.value, scheduled_animations_in_same_group);
-      std::sort(scheduled_animations_in_same_group.begin(),
-                scheduled_animations_in_same_group.end(),
-                PriorityCompare(elapsed));
-
-      AnimationsVector sandwich;
-      for (const auto& it_animation : scheduled_animations_in_same_group) {
+      ScheduledVector sandwich;
+      for (const auto& it_animation : scheduled) {
         SVGSMILElement* animation = it_animation.Get();
         DCHECK_EQ(animation->TimeContainer(), this);
         DCHECK(animation->HasValidTarget());
 
         // This will calculate the contribution from the animation and update
         // timing.
-        if (animation->Progress(elapsed, seek_to_time)) {
+        if (animation->NeedsToProgress(elapsed, seek_to_time)) {
+          animation->Progress(elapsed, seek_to_time);
+          sandwich.push_back(animation);
+        } else if (animation->IsContributing(elapsed)) {
           sandwich.push_back(animation);
         } else {
           animation->ClearAnimatedType();
@@ -477,23 +478,64 @@
       }
 
       if (!sandwich.IsEmpty()) {
-        // Results are accumulated to the first animation that animates and
-        // contributes to a particular element/attribute pair.
-        // Only reset the animated type to the base value once for
-        // the lowest priority animation that animates and
-        // contributes to a particular element/attribute pair.
-        SVGSMILElement* result_element = sandwich.front();
-        result_element->ResetAnimatedType();
-
-        // Go through the sandwich from lowest prio to highest and generate
-        // the animated value (if any.)
-        for (const auto& animation : sandwich)
-          animation->UpdateAnimatedValue(result_element);
-
-        animations_to_apply.push_back(result_element);
+        active_sandwiches.push_back(sandwich);
       }
     }
-    attribute_map.RemoveAll(invalid_keys);
+  }
+
+  if (active_sandwiches.IsEmpty()) {
+#if DCHECK_IS_ON()
+    prevent_scheduled_animations_changes_ = false;
+#endif
+    return earliest_fire_time;
+  }
+
+  ScheduledVector animations_to_apply;
+  for (auto& sandwich : active_sandwiches) {
+    if (seek_to_time) {
+      for (auto& animation : sandwich) {
+        animation->TriggerPendingEvents(elapsed);
+      }
+    }
+
+    for (auto& animation : sandwich) {
+      animation->UpdateNextProgressTime(elapsed);
+    }
+
+    auto* it = sandwich.begin();
+    while (it != sandwich.end()) {
+      auto* scheduled = it->Get();
+      if (scheduled->IsContributing(elapsed)) {
+        it++;
+        continue;
+      }
+      scheduled->ClearAnimatedType();
+      it = sandwich.erase(it);
+    }
+
+    if (sandwich.IsEmpty()) {
+      continue;
+    }
+
+    for (auto& animation : sandwich) {
+      SMILTime next_fire_time = animation->NextProgressTime();
+      if (next_fire_time.IsFinite())
+        earliest_fire_time = std::min(next_fire_time, earliest_fire_time);
+    }
+
+    // Results are accumulated to the first animation that animates and
+    // contributes to a particular element/attribute pair.
+    // Only reset the animated type to the base value once for
+    // the lowest priority animation that animates and
+    // contributes to a particular element/attribute pair.
+    SVGSMILElement* result_element = sandwich.front();
+    result_element->ResetAnimatedType();
+    // Go through the sandwich from lowest prio to highest and generate
+    // the animated value (if any.)
+    for (auto& animation : sandwich) {
+      animation->UpdateAnimatedValue(result_element);
+    }
+    animations_to_apply.push_back(result_element);
   }
 
   if (animations_to_apply.IsEmpty()) {
diff --git a/third_party/blink/renderer/core/svg/animation/smil_time_container.h b/third_party/blink/renderer/core/svg/animation/smil_time_container.h
index ad3e1ad..24a9ca84 100644
--- a/third_party/blink/renderer/core/svg/animation/smil_time_container.h
+++ b/third_party/blink/renderer/core/svg/animation/smil_time_container.h
@@ -45,6 +45,11 @@
 
 class SMILTimeContainer : public GarbageCollectedFinalized<SMILTimeContainer> {
  public:
+  // Sorted list
+  using ScheduledVector = HeapVector<Member<SVGSMILElement>>;
+  using AttributeMap = HeapHashMap<QualifiedName, Member<ScheduledVector>>;
+  using AnimationsMap = HeapHashMap<WeakMember<SVGElement>, AttributeMap>;
+
   explicit SMILTimeContainer(SVGSVGElement& owner);
   ~SMILTimeContainer();
 
@@ -131,12 +136,7 @@
   TaskRunnerTimer<SMILTimeContainer> wakeup_timer_;
   TaskRunnerTimer<SMILTimeContainer> animation_policy_once_timer_;
 
-  using AnimationsLinkedHashSet = HeapLinkedHashSet<WeakMember<SVGSMILElement>>;
-  using AttributeAnimationsMap =
-      HeapHashMap<QualifiedName, Member<AnimationsLinkedHashSet>>;
-  using GroupedAnimationsMap =
-      HeapHashMap<WeakMember<SVGElement>, AttributeAnimationsMap>;
-  GroupedAnimationsMap scheduled_animations_;
+  AnimationsMap scheduled_animations_;
 
   Member<SVGSVGElement> owner_svg_element_;
 
diff --git a/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc b/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
index ea22338..4a2118df 100644
--- a/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
+++ b/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
@@ -804,9 +804,9 @@
     IntervalSelector interval_selector) const {
   bool first = interval_selector == kFirstInterval;
   // See the pseudocode in http://www.w3.org/TR/SMIL3/smil-timing.html#q90.
-  SMILTime begin_after =
-      first ? -std::numeric_limits<double>::infinity() : interval_.end;
-  SMILTime last_interval_temp_end = std::numeric_limits<double>::infinity();
+  auto constexpr infinity = std::numeric_limits<double>::infinity();
+  SMILTime begin_after = first ? -infinity : interval_.end;
+  SMILTime last_interval_temp_end = infinity;
   while (true) {
     bool equals_minimum_ok = !first || interval_.end > interval_.begin;
     SMILTime temp_begin =
@@ -950,7 +950,7 @@
 
 void SVGSMILElement::SeekToIntervalCorrespondingToTime(double elapsed) {
   DCHECK(!is_waiting_for_first_interval_);
-  DCHECK(elapsed >= interval_.begin);
+  DCHECK(interval_.begin.IsFinite());
 
   // Manually seek from interval to interval, just as if the animation would run
   // regulary.
@@ -988,47 +988,60 @@
   }
 }
 
-float SVGSMILElement::CalculateAnimationPercentAndRepeat(
-    double elapsed,
-    unsigned& repeat) const {
-  SMILTime simple_duration = this->SimpleDuration();
-  repeat = 0;
+unsigned SVGSMILElement::CalculateAnimationRepeat(double elapsed) const {
+  const SMILTime simple_duration = SimpleDuration();
+  if (simple_duration.IsIndefinite() || !simple_duration)
+    return 0;
+  DCHECK(simple_duration.IsFinite());
+  DCHECK(interval_.begin.IsFinite());
+
+  double active_time = std::max(elapsed - interval_.begin.Value(), 0.0);
+  double simple_duration_value = simple_duration.Value();
+  double repeating_duration = RepeatingDuration().Value();
+  if (elapsed >= interval_.end || active_time > repeating_duration) {
+    unsigned repeat =
+        static_cast<unsigned>(repeating_duration / simple_duration_value);
+    if (!fmod(repeating_duration, simple_duration_value))
+      return repeat - 1;
+    return repeat;
+  }
+  unsigned repeat = static_cast<unsigned>(active_time / simple_duration_value);
+  return repeat;
+}
+
+float SVGSMILElement::CalculateAnimationPercent(double elapsed) const {
+  SMILTime simple_duration = SimpleDuration();
   if (simple_duration.IsIndefinite()) {
-    repeat = 0;
     return 0.f;
   }
   if (!simple_duration) {
-    repeat = 0;
     return 1.f;
   }
-  DCHECK(interval_.begin.IsFinite());
   DCHECK(simple_duration.IsFinite());
-  double active_time = elapsed - interval_.begin.Value();
-  SMILTime repeating_duration = this->RepeatingDuration();
-  if (elapsed >= interval_.end || active_time > repeating_duration) {
-    repeat = static_cast<unsigned>(repeating_duration.Value() /
-                                   simple_duration.Value());
-    if (!fmod(repeating_duration.Value(), simple_duration.Value()))
-      repeat--;
+  DCHECK(interval_.begin.IsFinite());
 
+  double active_time = elapsed - interval_.begin.Value();
+  double simple_duration_value = simple_duration.Value();
+  double repeating_duration = RepeatingDuration().Value();
+  if (elapsed >= interval_.end || active_time > repeating_duration) {
     // Use the interval to compute the interval position if we've passed the
     // interval end, otherwise use the "repeating duration". This prevents a
     // stale interval (with for instance an 'indefinite' end) from yielding an
     // invalid interval position.
-    double last_active_duration =
-        elapsed >= interval_.end
-            ? interval_.end.Value() - interval_.begin.Value()
-            : repeating_duration.Value();
-    double percent = last_active_duration / simple_duration.Value();
+    double last_active_duration;
+    if (elapsed >= interval_.end)
+      last_active_duration = interval_.end.Value() - interval_.begin.Value();
+    else
+      last_active_duration = repeating_duration;
+    double percent = last_active_duration / simple_duration_value;
     percent = percent - floor(percent);
-    if (percent < std::numeric_limits<float>::epsilon() ||
-        1 - percent < std::numeric_limits<float>::epsilon())
+    float epsilon = std::numeric_limits<float>::epsilon();
+    if (percent < epsilon || 1 - percent < epsilon)
       return 1.0f;
     return clampTo<float>(percent);
   }
-  repeat = static_cast<unsigned>(active_time / simple_duration.Value());
-  double simple_time = fmod(active_time, simple_duration.Value());
-  return clampTo<float>(simple_time / simple_duration.Value());
+  double simple_time = fmod(active_time, simple_duration_value);
+  return clampTo<float>(simple_time / simple_duration_value);
 }
 
 SMILTime SVGSMILElement::CalculateNextProgressTime(double elapsed) const {
@@ -1069,13 +1082,19 @@
          GetActiveState() == kFrozen;
 }
 
-bool SVGSMILElement::Progress(double elapsed, bool seek_to_time) {
+// The first part of the processing of the animation,
+// this checks if there are any further calculations needed
+// to continue and makes sure the intervals are correct.
+bool SVGSMILElement::NeedsToProgress(double elapsed, bool seek_to_time) {
+  // Check we're connected to something.
   DCHECK(time_container_);
+  // Check that we have some form of start or are prepared to find it.
   DCHECK(is_waiting_for_first_interval_ || interval_.begin.IsFinite());
 
   if (!sync_base_conditions_connected_)
     ConnectSyncBaseConditions();
 
+  // Check if we need updating, otherwise just return.
   if (!interval_.begin.IsFinite()) {
     DCHECK_EQ(GetActiveState(), kInactive);
     next_progress_time_ = SMILTime::Unresolved();
@@ -1085,8 +1104,7 @@
   if (elapsed < interval_.begin) {
     DCHECK_NE(GetActiveState(), kActive);
     next_progress_time_ = interval_.begin;
-    // If the animation is frozen, it's still contributing.
-    return GetActiveState() == kFrozen;
+    return false;
   }
 
   previous_interval_begin_ = interval_.begin;
@@ -1095,29 +1113,51 @@
     is_waiting_for_first_interval_ = false;
     ResolveFirstInterval();
   }
+  return true;
+}
 
+void SVGSMILElement::TriggerPendingEvents(double elapsed) {
+  if (GetActiveState() == kInactive)
+    ScheduleEvent(event_type_names::kBeginEvent);
+
+  unsigned repeat = CalculateAnimationRepeat(elapsed);
+  if (repeat) {
+    for (unsigned repeat_event_count = 1; repeat_event_count < repeat;
+         repeat_event_count++)
+      ScheduleRepeatEvents(repeat_event_count);
+    if (GetActiveState() == kInactive)
+      ScheduleRepeatEvents(repeat);
+  }
+
+  if (GetActiveState() == kInactive || GetActiveState() == kFrozen)
+    ScheduleEvent(event_type_names::kEndEvent);
+}
+
+void SVGSMILElement::UpdateNextProgressTime(double elapsed) {
+  next_progress_time_ = CalculateNextProgressTime(elapsed);
+}
+
+void SVGSMILElement::Progress(double elapsed, bool seek_to_time) {
   // This call may obtain a new interval -- never call
-  // calculateAnimationPercentAndRepeat() before!
+  // CalculateAnimation{ Percent, Repeat }() before!
   if (seek_to_time) {
     SeekToIntervalCorrespondingToTime(elapsed);
     if (elapsed < interval_.begin) {
       // elapsed is not within an interval.
       next_progress_time_ = interval_.begin;
-      return false;
+      return;
     }
   }
 
-  unsigned repeat = 0;
-  float percent = CalculateAnimationPercentAndRepeat(elapsed, repeat);
-  RestartedInterval restarted_interval = MaybeRestartInterval(elapsed);
+  float percent = CalculateAnimationPercent(elapsed);
+  unsigned repeat = CalculateAnimationRepeat(elapsed);
+  bool restarted_interval = MaybeRestartInterval(elapsed);
 
   ActiveState old_active_state = GetActiveState();
   active_state_ = DetermineActiveState(elapsed);
-  bool animation_is_contributing = IsContributing(elapsed);
 
-  if (animation_is_contributing) {
-    if (old_active_state == kInactive ||
-        restarted_interval == kDidRestartInterval) {
+  if (IsContributing(elapsed)) {
+    if (old_active_state == kInactive || restarted_interval) {
       ScheduleEvent(event_type_names::kBeginEvent);
       StartedActiveInterval();
     }
@@ -1130,30 +1170,10 @@
   }
 
   if ((old_active_state == kActive && GetActiveState() != kActive) ||
-      restarted_interval == kDidRestartInterval) {
+      restarted_interval) {
     ScheduleEvent(event_type_names::kEndEvent);
     EndedActiveInterval();
   }
-
-  // Triggering all the pending events if the animation timeline is changed.
-  if (seek_to_time) {
-    if (GetActiveState() == kInactive)
-      ScheduleEvent(event_type_names::kBeginEvent);
-
-    if (repeat) {
-      for (unsigned repeat_event_count = 1; repeat_event_count < repeat;
-           repeat_event_count++)
-        ScheduleRepeatEvents(repeat_event_count);
-      if (GetActiveState() == kInactive)
-        ScheduleRepeatEvents(repeat);
-    }
-
-    if (GetActiveState() == kInactive || GetActiveState() == kFrozen)
-      ScheduleEvent(event_type_names::kEndEvent);
-  }
-
-  next_progress_time_ = CalculateNextProgressTime(elapsed);
-  return animation_is_contributing;
 }
 
 void SVGSMILElement::NotifyDependentsIntervalChanged() {
diff --git a/third_party/blink/renderer/core/svg/animation/svg_smil_element.h b/third_party/blink/renderer/core/svg/animation/svg_smil_element.h
index 99e646e..9b8be92 100644
--- a/third_party/blink/renderer/core/svg/animation/svg_smil_element.h
+++ b/third_party/blink/renderer/core/svg/animation/svg_smil_element.h
@@ -85,7 +85,12 @@
   SMILTime SimpleDuration() const;
 
   void SeekToIntervalCorrespondingToTime(double elapsed);
-  bool Progress(double elapsed, bool seek_to_time);
+
+  bool NeedsToProgress(double elapsed, bool seek_to_time);
+  void TriggerPendingEvents(double elapsed);
+  void UpdateNextProgressTime(double elapsed);
+  void Progress(double elapsed, bool seek_to_time);
+
   SMILTime NextProgressTime() const;
   void UpdateAnimatedValue(SVGSMILElement* result_element) {
     UpdateAnimation(last_percent_, last_repeat_, result_element);
@@ -240,8 +245,8 @@
     return static_cast<ActiveState>(active_state_);
   }
   ActiveState DetermineActiveState(SMILTime elapsed) const;
-  float CalculateAnimationPercentAndRepeat(double elapsed,
-                                           unsigned& repeat) const;
+  float CalculateAnimationPercent(double elapsed) const;
+  unsigned CalculateAnimationRepeat(double elapsed) const;
   SMILTime CalculateNextProgressTime(double elapsed) const;
 
   Member<SVGElement> target_element_;
diff --git a/third_party/blink/renderer/core/testing/sim/sim_request.cc b/third_party/blink/renderer/core/testing/sim/sim_request.cc
index 29a12ec..f078561 100644
--- a/third_party/blink/renderer/core/testing/sim/sim_request.cc
+++ b/third_party/blink/renderer/core/testing/sim/sim_request.cc
@@ -4,7 +4,6 @@
 
 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
 
-#include <vector>
 #include "third_party/blink/public/platform/web_url_loader_client.h"
 #include "third_party/blink/renderer/core/testing/sim/sim_network.h"
 #include "third_party/blink/renderer/platform/loader/static_data_navigation_body_loader.h"
diff --git a/third_party/blink/renderer/core/timing/memory_info_test.cc b/third_party/blink/renderer/core/timing/memory_info_test.cc
index e930aa6..73eb3803 100644
--- a/third_party/blink/renderer/core/timing/memory_info_test.cc
+++ b/third_party/blink/renderer/core/timing/memory_info_test.cc
@@ -127,7 +127,7 @@
   // allocated alive even if GC happens. In practice, the objects only get GC'd
   // after we go out of V8TestingScope. But having them in a vector makes it
   // impossible for GC to clear them up unexpectedly early.
-  std::vector<v8::Local<v8::ArrayBuffer>> objects;
+  Vector<v8::Local<v8::ArrayBuffer>> objects;
 
   MemoryInfoTestScopedMockTime mock_time(MemoryInfo::Precision::Bucketized);
   MemoryInfo* bucketized_memory =
@@ -172,7 +172,7 @@
 TEST_F(MemoryInfoTest, Precise) {
   V8TestingScope scope;
   v8::Isolate* isolate = scope.GetIsolate();
-  std::vector<v8::Local<v8::ArrayBuffer>> objects;
+  Vector<v8::Local<v8::ArrayBuffer>> objects;
 
   MemoryInfoTestScopedMockTime mock_time(MemoryInfo::Precision::Precise);
   MemoryInfo* precise_memory =
@@ -217,7 +217,7 @@
   ScopedPreciseMemoryInfoForTest precise_memory_info(true);
   V8TestingScope scope;
   v8::Isolate* isolate = scope.GetIsolate();
-  std::vector<v8::Local<v8::ArrayBuffer>> objects;
+  Vector<v8::Local<v8::ArrayBuffer>> objects;
 
   // Using MemoryInfo::Precision::Bucketized to ensure that the runtime-enabled
   // flag overrides the Precision passed onto the method.
@@ -247,7 +247,7 @@
   mock_time.AdvanceClock(base::TimeDelta::FromMicroseconds(100));
   V8TestingScope scope;
   v8::Isolate* isolate = scope.GetIsolate();
-  std::vector<v8::Local<v8::ArrayBuffer>> objects;
+  Vector<v8::Local<v8::ArrayBuffer>> objects;
   objects.push_back(v8::ArrayBuffer::New(isolate, 100));
 
   MemoryInfo* precise_memory =
diff --git a/third_party/blink/renderer/core/workers/threaded_messaging_proxy_base.cc b/third_party/blink/renderer/core/workers/threaded_messaging_proxy_base.cc
index 69d5ac2..65ba66c 100644
--- a/third_party/blink/renderer/core/workers/threaded_messaging_proxy_base.cc
+++ b/third_party/blink/renderer/core/workers/threaded_messaging_proxy_base.cc
@@ -39,7 +39,7 @@
           execution_context->GetScheduler()->RegisterFeature(
               SchedulingPolicy::Feature::kDedicatedWorkerOrWorklet,
               {SchedulingPolicy::RecordMetricsForBackForwardCache()})),
-      keep_alive_(this) {
+      keep_alive_(PERSISTENT_FROM_HERE, this) {
   DCHECK(IsParentContextThread());
   g_live_messaging_proxy_count++;
 }
diff --git a/third_party/blink/renderer/devtools/PRESUBMIT.py b/third_party/blink/renderer/devtools/PRESUBMIT.py
index 4dcfae3b..0749c1a 100644
--- a/third_party/blink/renderer/devtools/PRESUBMIT.py
+++ b/third_party/blink/renderer/devtools/PRESUBMIT.py
@@ -79,7 +79,8 @@
     # Also fix semicolon to avoid confusing clang-format.
     eslint_process = popen([
         local_node.node_path(), local_node.eslint_path(),
-        '--no-eslintrc', '--fix', '--env=es6', '--rule={"curly": [2, "multi-or-nest", "consistent"], "semi": 2}'
+        '--no-eslintrc', '--fix', '--env=es6', '--parser-options=ecmaVersion:9',
+        '--rule={"curly": [2, "multi-or-nest", "consistent"], "semi": 2}'
     ] + affected_files)
     eslint_process.communicate()
 
diff --git a/third_party/blink/renderer/devtools/front_end/console/ConsoleContextSelector.js b/third_party/blink/renderer/devtools/front_end/console/ConsoleContextSelector.js
index 6ff70fc..c1f67651 100644
--- a/third_party/blink/renderer/devtools/front_end/console/ConsoleContextSelector.js
+++ b/third_party/blink/renderer/devtools/front_end/console/ConsoleContextSelector.js
@@ -14,7 +14,7 @@
     this._dropDown.setRowHeight(36);
     this._toolbarItem = new UI.ToolbarItem(this._dropDown.element);
     this._toolbarItem.setEnabled(false);
-    this._toolbarItem.setTitle(ls`JavaScript contexts`);
+    this._toolbarItem.setTitle(ls`JavaScript context: Not selected`);
     this._items.addEventListener(
         UI.ListModel.Events.ItemsReplaced, () => this._toolbarItem.setEnabled(!!this._items.length));
 
@@ -314,6 +314,8 @@
    */
   itemSelected(item) {
     this._toolbarItem.element.classList.toggle('warning', !this._isTopContext(item) && this._hasTopContext());
+    const title = item ? ls`JavaScript context: ${this.titleFor(item)}` : ls`JavaScript context: Not selected`;
+    this._toolbarItem.setTitle(title);
     UI.context.setFlavor(SDK.ExecutionContext, item);
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/console/ConsoleView.js b/third_party/blink/renderer/devtools/front_end/console/ConsoleView.js
index 42ef6f0..2bfe11f 100644
--- a/third_party/blink/renderer/devtools/front_end/console/ConsoleView.js
+++ b/third_party/blink/renderer/devtools/front_end/console/ConsoleView.js
@@ -1330,6 +1330,7 @@
       text = text || Common.UIString('Hide all');
     this._levelMenuButton.element.classList.toggle('warning', !isAll && !isDefault);
     this._levelMenuButton.setText(text);
+    this._levelMenuButton.setTitle(ls`Log level: ${text}`);
   }
 
   /**
diff --git a/third_party/blink/renderer/devtools/front_end/console/console_strings.grdp b/third_party/blink/renderer/devtools/front_end/console/console_strings.grdp
index b51975fa..a0b9c952 100644
--- a/third_party/blink/renderer/devtools/front_end/console/console_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/console/console_strings.grdp
@@ -3,6 +3,9 @@
   <message name="IDS_DEVTOOLS_02969932cf572213e2401893f4182af2" desc="">
     Remove all expressions
   </message>
+  <message name="IDS_DEVTOOLS_0351df5b6bdc3a8cfb762210881619a4" desc="">
+    JavaScript context: Not selected
+  </message>
   <message name="IDS_DEVTOOLS_047e62ee63b0c14a249a79bc6be0a493" desc="">
     Do not group similar messages in console
   </message>
@@ -186,9 +189,6 @@
   <message name="IDS_DEVTOOLS_b2f8489bbd55a4e9b9edbbbfe49edf70" desc="">
     not available
   </message>
-  <message name="IDS_DEVTOOLS_b5735f8c6deff6306a2216612ca80f0e" desc="">
-    JavaScript contexts
-  </message>
   <message name="IDS_DEVTOOLS_bafd7322c6e97d25b6299b5d6fe8920b" desc="">
     No
   </message>
@@ -264,12 +264,18 @@
   <message name="IDS_DEVTOOLS_e1dd1b8e32626f508bd3a5612a60ab97" desc="">
     Create live expression
   </message>
+  <message name="IDS_DEVTOOLS_e6320ee2b5f66ac1520e6156e5ac8fb3" desc="">
+    JavaScript context: <ph name="THIS_TITLEFOR_ITEM_">$1s</ph>
+  </message>
   <message name="IDS_DEVTOOLS_eacaf2bd1c1414e5086cf04573e154ef" desc="">
     e.g. /event\d/ -cdn url:a.com
   </message>
   <message name="IDS_DEVTOOLS_ebfab4df1bb91688c62d4523eba870a5" desc="">
     Evaluate triggers user activation
   </message>
+  <message name="IDS_DEVTOOLS_f0b0e075af71ecd9b3435697417c1f05" desc="">
+    Log level: <ph name="TEXT">$1s</ph>
+  </message>
   <message name="IDS_DEVTOOLS_f8ec55cf268ca6106fb23a98523b7549" desc="">
     user message
   </message>
diff --git a/third_party/blink/renderer/devtools/front_end/persistence/EditFileSystemView.js b/third_party/blink/renderer/devtools/front_end/persistence/EditFileSystemView.js
index 2fe58d0b..9d9d181 100644
--- a/third_party/blink/renderer/devtools/front_end/persistence/EditFileSystemView.js
+++ b/third_party/blink/renderer/devtools/front_end/persistence/EditFileSystemView.js
@@ -60,7 +60,6 @@
     this._excludedFoldersList.setEmptyPlaceholder(excludedFoldersPlaceholder);
     this._excludedFoldersList.show(this.contentElement);
 
-    this.contentElement.tabIndex = 0;
     this._update();
   }
 
diff --git a/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js b/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js
index cbff5f61..3896a99 100644
--- a/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js
+++ b/third_party/blink/renderer/devtools/front_end/persistence/WorkspaceSettingsTab.js
@@ -8,7 +8,7 @@
     this.registerRequiredCSS('persistence/workspaceSettingsTab.css');
 
     const header = this.element.createChild('header');
-    header.createChild('h3').createTextChild(Common.UIString('Workspace'));
+    header.createChild('h1').createTextChild(Common.UIString('Workspace'));
 
     this.containerElement = this.element.createChild('div', 'settings-container-wrapper')
                                 .createChild('div', 'settings-tab settings-content settings-container');
diff --git a/third_party/blink/renderer/devtools/front_end/persistence/workspaceSettingsTab.css b/third_party/blink/renderer/devtools/front_end/persistence/workspaceSettingsTab.css
index ee24213..d6070e9f 100644
--- a/third_party/blink/renderer/devtools/front_end/persistence/workspaceSettingsTab.css
+++ b/third_party/blink/renderer/devtools/front_end/persistence/workspaceSettingsTab.css
@@ -9,7 +9,7 @@
     border-bottom: 1px solid #EEEEEE;
 }
 
-header > h3 {
+header > h1 {
     font-size: 18px;
     font-weight: normal;
     margin: 0;
diff --git a/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc b/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc
index 91a5066..be714dbb 100644
--- a/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc
+++ b/third_party/blink/renderer/modules/animationworklet/worklet_animation_test.cc
@@ -233,10 +233,10 @@
   state.reset(new AnimationWorkletDispatcherInput);
 
   // Last peek request fulfilled. No need to peek.
-  std::vector<base::Optional<base::TimeDelta>> local_times;
-  local_times.push_back(base::TimeDelta());
+  WebVector<base::Optional<base::TimeDelta>> local_times;
+  local_times.emplace_back(base::TimeDelta());
   AnimationWorkletOutput::AnimationState output_with_value(id);
-  output_with_value.local_times = local_times;
+  output_with_value.local_times = local_times.ReleaseVector();
   worklet_animation_->SetOutputState(output_with_value);
   worklet_animation_->UpdateInputState(state.get());
   input = state->TakeWorkletState(id.worklet_id);
diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc
index 721fbdf..1917168f 100644
--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc
+++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d_test.cc
@@ -855,10 +855,10 @@
       canvas->GetCanvasRenderingContext("2d", attributes));
 
   // Prepare the png file path and call the test routine
-  std::vector<String> interlace_status = {"", "_interlaced"};
-  std::vector<String> color_profiles = {"_sRGB",      "_e-sRGB",   "_AdobeRGB",
-                                        "_DisplayP3", "_ProPhoto", "_Rec2020"};
-  std::vector<String> alpha_status = {"_opaque", "_transparent"};
+  Vector<String> interlace_status = {"", "_interlaced"};
+  Vector<String> color_profiles = {"_sRGB",      "_e-sRGB",   "_AdobeRGB",
+                                   "_DisplayP3", "_ProPhoto", "_Rec2020"};
+  Vector<String> alpha_status = {"_opaque", "_transparent"};
 
   StringBuilder path;
   path.Append(test::CoreTestDataPath());
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.cc b/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.cc
index 7d934f7..6b8e89b1 100644
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.cc
+++ b/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.cc
@@ -6,9 +6,6 @@
 
 #include <stddef.h>
 
-#include <string>
-#include <vector>
-
 #include "base/single_thread_task_runner.h"
 #include "mojo/public/cpp/bindings/strong_associated_binding.h"
 #include "third_party/blink/public/platform/modules/indexeddb/web_idb_database_exception.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.h b/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.h
index 5a9a8c0..e58f1d8 100644
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.h
+++ b/third_party/blink/renderer/modules/indexeddb/web_idb_cursor_impl.h
@@ -7,8 +7,6 @@
 
 #include <stdint.h>
 
-#include <vector>
-
 #include "base/gtest_prod_util.h"
 #include "third_party/blink/public/mojom/indexeddb/indexeddb.mojom-blink.h"
 #include "third_party/blink/renderer/modules/indexeddb/idb_value.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_database_callbacks.h b/third_party/blink/renderer/modules/indexeddb/web_idb_database_callbacks.h
index 6fc4c63..9ab144b 100644
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_database_callbacks.h
+++ b/third_party/blink/renderer/modules/indexeddb/web_idb_database_callbacks.h
@@ -28,7 +28,6 @@
 
 #include <unordered_map>
 #include <utility>
-#include <vector>
 
 #include "third_party/blink/renderer/modules/indexeddb/idb_database_error.h"
 #include "third_party/blink/renderer/modules/modules_export.h"
diff --git a/third_party/blink/renderer/modules/indexeddb/web_idb_transaction_impl_unittest.cc b/third_party/blink/renderer/modules/indexeddb/web_idb_transaction_impl_unittest.cc
index 0ee4e18..d736af9 100644
--- a/third_party/blink/renderer/modules/indexeddb/web_idb_transaction_impl_unittest.cc
+++ b/third_party/blink/renderer/modules/indexeddb/web_idb_transaction_impl_unittest.cc
@@ -30,7 +30,7 @@
   // of memory, which crashes on memory-constrained systems.
   const size_t kMaxValueSizeForTesting = 10 * 1024 * 1024;  // 10 MB
 
-  const std::vector<char> data(kMaxValueSizeForTesting + 1);
+  const Vector<char> data(kMaxValueSizeForTesting + 1);
   const scoped_refptr<SharedBuffer> value_data =
       SharedBuffer::Create(&data.front(), data.size());
   const Vector<WebBlobInfo> blob_info;
@@ -58,7 +58,7 @@
   const size_t kMaxValueSizeForTesting = 10 * 1024 * 1024;  // 10 MB
   const size_t kKeySize = 1024 * 1024;
 
-  const std::vector<char> data(kMaxValueSizeForTesting - kKeySize);
+  const Vector<char> data(kMaxValueSizeForTesting - kKeySize);
   const scoped_refptr<SharedBuffer> value_data =
       SharedBuffer::Create(&data.front(), data.size());
   const Vector<WebBlobInfo> blob_info;
diff --git a/third_party/blink/renderer/modules/modules_idl_files.gni b/third_party/blink/renderer/modules/modules_idl_files.gni
index 8916dcb..9bc0fea 100644
--- a/third_party/blink/renderer/modules/modules_idl_files.gni
+++ b/third_party/blink/renderer/modules/modules_idl_files.gni
@@ -226,6 +226,7 @@
           "netinfo/network_information.idl",
           "nfc/nfc.idl",
           "nfc/nfc_error_event.idl",
+          "nfc/nfc_writer.idl",
           "notifications/notification.idl",
           "notifications/notification_event.idl",
           "notifications/timestamp_trigger.idl",
diff --git a/third_party/blink/renderer/modules/nfc/BUILD.gn b/third_party/blink/renderer/modules/nfc/BUILD.gn
index c4e3ecd..6e256b1 100644
--- a/third_party/blink/renderer/modules/nfc/BUILD.gn
+++ b/third_party/blink/renderer/modules/nfc/BUILD.gn
@@ -24,5 +24,7 @@
     "nfc_type_converters.h",
     "nfc_utils.cc",
     "nfc_utils.h",
+    "nfc_writer.cc",
+    "nfc_writer.h",
   ]
 }
diff --git a/third_party/blink/renderer/modules/nfc/nfc.cc b/third_party/blink/renderer/modules/nfc/nfc.cc
index 0c5e3b2..ae491f3 100644
--- a/third_party/blink/renderer/modules/nfc/nfc.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc.cc
@@ -240,6 +240,7 @@
 
 void NFC::OnConnectionError() {
   nfc_.reset();
+  client_binding_.Close();
   callbacks_.clear();
 
   // If NFCService is not available or disappears when NFC hardware is
@@ -278,7 +279,7 @@
   // WebNFC API must be only accessible from top level browsing context.
   if (!To<Document>(context)->domWindow()->GetFrame() ||
       !To<Document>(context)->GetFrame()->IsMainFrame()) {
-    error_message = "Must be in a top-level browsing context";
+    error_message = kNfcAccessInNonTopFrame;
     return false;
   }
 
@@ -291,7 +292,7 @@
                             error_message)) {
     return ScriptPromise::RejectWithDOMException(
         script_state, MakeGarbageCollected<DOMException>(
-                          DOMExceptionCode::kSecurityError, error_message));
+                          DOMExceptionCode::kNotAllowedError, error_message));
   }
 
   if (!nfc_) {
diff --git a/third_party/blink/renderer/modules/nfc/nfc_constants.cc b/third_party/blink/renderer/modules/nfc/nfc_constants.cc
index 2ed183c..e5313a81 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_constants.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_constants.cc
@@ -16,7 +16,9 @@
 const char kNfcCharSetUTF8[] = ";charset=UTF-8";
 
 // Error messages.
-const char kNfcNotSupported[] = "WebNFC is not supported.";
+const char kNfcNotSupported[] = "NFC operation not supported.";
+const char kNfcNotReadable[] = "No NFC adapter or cannot establish connection.";
+const char kNfcNotAllowed[] = "NFC operation not allowed.";
 const char kNfcTextRecordTypeError[] =
     "The data for 'text' NDEFRecords must be of String or UnrestrctedDouble.";
 const char kNfcSetIdError[] = "Cannot set WebNFC Id.";
@@ -36,6 +38,7 @@
 const char kNfcRecordError[] = "Invalid NDEFRecordType was provided.";
 const char kNfcMsgTypeError[] = "Invalid NDEFMessageSource type was provided.";
 const char kNfcEmptyMsg[] = "Empty NDEFMessage was provided.";
+const char kNfcInvalidMsg[] = "Invalid NFC message was provided.";
 const char kNfcMsgConvertError[] = "Cannot convert NDEFMessage.";
 const char kNfcMsgMaxSizeError[] =
     "NDEFMessage exceeds maximum supported size.";
@@ -43,5 +46,12 @@
 const char kNfcInvalidPushTimeout[] =
     "Invalid NFCPushOptions.timeout value was provided.";
 const char kNfcWatchIdNotFound[] = "Provided watch id cannot be found.";
+const char kNfcAccessInNonTopFrame[] =
+    "NFC interfaces are only avaliable in a top-level browsing context";
+const char kNfcCancelled[] = "The NFC operation was cancelled.";
+const char kNfcTimeout[] = "NFC operation has timed out.";
+const char kNfcUnknownError[] = "An unknown NFC error has occurred.";
+const char kNfcDataTransferError[] = "NFC data transfer error has occurred.";
+const char kNfcNoModificationAllowed[] = "NFC operation cannot be cancelled.";
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/nfc_constants.h b/third_party/blink/renderer/modules/nfc/nfc_constants.h
index 74d8dec..4fef80ef 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_constants.h
+++ b/third_party/blink/renderer/modules/nfc/nfc_constants.h
@@ -25,6 +25,8 @@
 
 // Error messages.
 extern const char kNfcNotSupported[];
+extern const char kNfcNotReadable[];
+extern const char kNfcNotAllowed[];
 extern const char kNfcTextRecordTypeError[];
 extern const char kNfcSetIdError[];
 extern const char kNfcTextRecordMediaTypeError[];
@@ -38,11 +40,18 @@
 extern const char kNfcRecordError[];
 extern const char kNfcMsgTypeError[];
 extern const char kNfcEmptyMsg[];
+extern const char kNfcInvalidMsg[];
 extern const char kNfcMsgConvertError[];
 extern const char kNfcMsgMaxSizeError[];
 extern const char kNfcUrlPatternError[];
 extern const char kNfcInvalidPushTimeout[];
 extern const char kNfcWatchIdNotFound[];
+extern const char kNfcAccessInNonTopFrame[];
+extern const char kNfcCancelled[];
+extern const char kNfcTimeout[];
+extern const char kNfcUnknownError[];
+extern const char kNfcDataTransferError[];
+extern const char kNfcNoModificationAllowed[];
 
 }  // namespace blink
 
diff --git a/third_party/blink/renderer/modules/nfc/nfc_error.cc b/third_party/blink/renderer/modules/nfc/nfc_error.cc
index b1b1de4..af305b0 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_error.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_error.cc
@@ -5,6 +5,7 @@
 #include "third_party/blink/renderer/modules/nfc/nfc_error.h"
 
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_constants.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
 
 using device::mojom::blink::NFCErrorType;
@@ -14,38 +15,38 @@
 DOMException* NFCError::Take(ScriptPromiseResolver*,
                              const NFCErrorType& error_type) {
   switch (error_type) {
-    case NFCErrorType::SECURITY:
+    case NFCErrorType::NOT_ALLOWED:
       return MakeGarbageCollected<DOMException>(
-          DOMExceptionCode::kSecurityError, "NFC operation not allowed.");
+          DOMExceptionCode::kNotAllowedError, kNfcNotAllowed);
     case NFCErrorType::NOT_SUPPORTED:
-    case NFCErrorType::DEVICE_DISABLED:
       return MakeGarbageCollected<DOMException>(
-          DOMExceptionCode::kNotSupportedError, "NFC operation not supported.");
+          DOMExceptionCode::kNotSupportedError, kNfcNotSupported);
+    case NFCErrorType::NOT_READABLE:
+      return MakeGarbageCollected<DOMException>(
+          DOMExceptionCode::kNotReadableError, kNfcNotReadable);
     case NFCErrorType::NOT_FOUND:
       return MakeGarbageCollected<DOMException>(
-          DOMExceptionCode::kNotFoundError,
-          "Invalid NFC watch Id was provided.");
+          DOMExceptionCode::kNotFoundError, kNfcWatchIdNotFound);
     case NFCErrorType::INVALID_MESSAGE:
-      return MakeGarbageCollected<DOMException>(
-          DOMExceptionCode::kSyntaxError, "Invalid NFC message was provided.");
+      return MakeGarbageCollected<DOMException>(DOMExceptionCode::kSyntaxError,
+                                                kNfcInvalidMsg);
     case NFCErrorType::OPERATION_CANCELLED:
-      return MakeGarbageCollected<DOMException>(
-          DOMExceptionCode::kAbortError, "The NFC operation was cancelled.");
+      return MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError,
+                                                kNfcCancelled);
     case NFCErrorType::TIMER_EXPIRED:
       return MakeGarbageCollected<DOMException>(DOMExceptionCode::kTimeoutError,
-                                                "NFC operation has timed-out.");
+                                                kNfcTimeout);
     case NFCErrorType::CANNOT_CANCEL:
       return MakeGarbageCollected<DOMException>(
           DOMExceptionCode::kNoModificationAllowedError,
-          "NFC operation cannot be canceled.");
+          kNfcNoModificationAllowed);
     case NFCErrorType::IO_ERROR:
-      return MakeGarbageCollected<DOMException>(
-          DOMExceptionCode::kNetworkError,
-          "NFC data transfer error has occurred.");
+      return MakeGarbageCollected<DOMException>(DOMExceptionCode::kNetworkError,
+                                                kNfcDataTransferError);
   }
   NOTREACHED();
-  return MakeGarbageCollected<DOMException>(
-      DOMExceptionCode::kUnknownError, "An unknown NFC error has occurred.");
+  // Don't need to handle the case after a NOTREACHED().
+  return nullptr;
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy.cc b/third_party/blink/renderer/modules/nfc/nfc_proxy.cc
index 6bb51f5..1a8bbb1 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy.cc
@@ -8,6 +8,7 @@
 
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/renderer/modules/nfc/nfc_reader.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_writer.h"
 #include "third_party/blink/renderer/platform/mojo/mojo_helper.h"
 
 namespace blink {
@@ -19,9 +20,7 @@
 NFCProxy* NFCProxy::From(Document& document) {
   // https://w3c.github.io/web-nfc/#security-policies
   // WebNFC API must be only accessible from top level browsing context.
-  if (!document.IsInMainFrame()) {
-    return nullptr;
-  }
+  DCHECK(document.IsInMainFrame());
 
   NFCProxy* nfc_proxy = Supplement<Document>::From<NFCProxy>(document);
   if (!nfc_proxy) {
@@ -44,6 +43,7 @@
 }
 
 void NFCProxy::Trace(blink::Visitor* visitor) {
+  visitor->Trace(writers_);
   visitor->Trace(readers_);
   PageVisibilityObserver::Trace(visitor);
   Supplement<Document>::Trace(visitor);
@@ -71,6 +71,11 @@
   }
 }
 
+void NFCProxy::AddWriter(NFCWriter* writer) {
+  DCHECK(!writers_.Contains(writer));
+  writers_.insert(writer);
+}
+
 void NFCProxy::Push(device::mojom::blink::NDEFMessagePtr message,
                     device::mojom::blink::NFCPushOptionsPtr options,
                     device::mojom::blink::NFC::PushCallback cb) {
@@ -109,7 +114,7 @@
   DCHECK(reader);
 
   if (error) {
-    reader->OnError(*error);
+    reader->OnReadingError(*error);
     return;
   }
 
@@ -121,7 +126,7 @@
   DCHECK(reader);
 
   if (error) {
-    reader->OnError(*error);
+    reader->OnReadingError(*error);
   }
 }
 
@@ -138,6 +143,7 @@
       WTF::Bind(&NFCProxy::OnMojoConnectionError, WrapWeakPersistent(this)));
 
   // Set client for OnWatch event.
+  DCHECK(!client_binding_);
   device::mojom::blink::NFCClientPtr client;
   client_binding_.Bind(mojo::MakeRequest(&client, task_runner), task_runner);
   nfc_->SetClient(std::move(client));
@@ -145,18 +151,21 @@
 
 void NFCProxy::OnMojoConnectionError() {
   nfc_.reset();
+  client_binding_.Close();
 
   // Notify all active readers about the connection error.
-  // NFCWriter::push() should wrap the callback using
-  // mojo::WrapCallbackWithDefaultInvokeIfNotRun() with a default error.
-  auto error = device::mojom::blink::NFCError::New(
-      device::mojom::blink::NFCErrorType::NOT_SUPPORTED);
   for (auto pair : readers_) {
-    pair.key->OnError(*error);
+    pair.key->OnMojoConnectionError();
   }
-
   // Clear the reader list.
   readers_.clear();
+
+  // Notify all writers about the connection error.
+  for (auto& writer : writers_) {
+    writer->OnMojoConnectionError();
+  }
+  // Clear the reader list.
+  writers_.clear();
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy.h b/third_party/blink/renderer/modules/nfc/nfc_proxy.h
index f5cb556a..2fec61a 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy.h
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy.h
@@ -16,6 +16,7 @@
 
 class Document;
 class NFCReader;
+class NFCWriter;
 
 // This is a proxy class used by NFCWriter(s) and NFCReader(s) to connect
 // to the DeviceService for device::mojom::blink::NFC service.
@@ -38,6 +39,11 @@
 
   void Trace(blink::Visitor*) override;
 
+  // There is no matching RemoveWriter() method because writers are
+  // automatically removed from the weak hash set when they are garbage
+  // collected.
+  void AddWriter(NFCWriter*);
+
   void AddReader(NFCReader*);
   void RemoveReader(NFCReader*);
   void Push(device::mojom::blink::NDEFMessagePtr message,
@@ -55,6 +61,7 @@
   void OnReaderRegistered(NFCReader* reader,
                           uint32_t id,
                           device::mojom::blink::NFCErrorPtr error);
+
   void OnCancelWatch(NFCReader* reader,
                      device::mojom::blink::NFCErrorPtr error);
 
@@ -66,6 +73,9 @@
   using ReaderMap = HeapHashMap<WeakMember<NFCReader>, uint32_t>;
   ReaderMap readers_;
 
+  using WriterSet = HeapHashSet<WeakMember<NFCWriter>>;
+  WriterSet writers_;
+
   device::mojom::blink::NFCPtr nfc_;
   mojo::Binding<device::mojom::blink::NFCClient> client_binding_;
 };
diff --git a/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc b/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
index 7402148..4d5378f 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_proxy_test.cc
@@ -63,7 +63,7 @@
   void Trace(blink::Visitor* visitor) override { NFCReader::Trace(visitor); }
 
   MOCK_METHOD1(OnMessage, void(const NDEFMessage& message));
-  MOCK_METHOD1(OnError, void(const NFCError& error));
+  MOCK_METHOD1(OnReadingError, void(const NFCError& error));
 };
 
 class FakeNfcService : public device::mojom::blink::NFC {
@@ -223,7 +223,7 @@
   test::RunPendingTasks();
 
   base::RunLoop loop;
-  EXPECT_CALL(*reader, OnError(_)).WillOnce(Invoke([&](const NFCError&) {
+  EXPECT_CALL(*reader, OnReadingError(_)).WillOnce(Invoke([&](const NFCError&) {
     loop.Quit();
   }));
   DestroyNfcService();
diff --git a/third_party/blink/renderer/modules/nfc/nfc_reader.cc b/third_party/blink/renderer/modules/nfc/nfc_reader.cc
index 88d42b3..5a92501 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_reader.cc
+++ b/third_party/blink/renderer/modules/nfc/nfc_reader.cc
@@ -17,6 +17,13 @@
 void NFCReader::OnMessage(const device::mojom::blink::NDEFMessage& message) {}
 
 // An reading error has occurred.
-void NFCReader::OnError(const device::mojom::blink::NFCError& error) {}
+void NFCReader::OnReadingError(const device::mojom::blink::NFCError& error) {}
+
+// NfcProxy::Observer overrides.
+void NFCReader::OnMojoConnectionError() {
+  auto error = device::mojom::blink::NFCError::New(
+      device::mojom::blink::NFCErrorType::NOT_READABLE);
+  OnReadingError(*error);
+}
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/nfc_reader.h b/third_party/blink/renderer/modules/nfc/nfc_reader.h
index 87f32d57..b5d41e7b 100644
--- a/third_party/blink/renderer/modules/nfc/nfc_reader.h
+++ b/third_party/blink/renderer/modules/nfc/nfc_reader.h
@@ -27,7 +27,10 @@
   virtual void OnMessage(const device::mojom::blink::NDEFMessage& message);
 
   // An reading error has occurred.
-  virtual void OnError(const device::mojom::blink::NFCError& error);
+  virtual void OnReadingError(const device::mojom::blink::NFCError& error);
+
+  // Called by NFCProxy for notification about connection error.
+  void OnMojoConnectionError();
 
  private:
   device::mojom::blink::NFCReaderOptionsPtr options_;
diff --git a/third_party/blink/renderer/modules/nfc/nfc_writer.cc b/third_party/blink/renderer/modules/nfc/nfc_writer.cc
new file mode 100644
index 0000000..dcbfd8da
--- /dev/null
+++ b/third_party/blink/renderer/modules/nfc/nfc_writer.cc
@@ -0,0 +1,134 @@
+// 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/nfc/nfc_writer.h"
+
+#include <utility>
+
+#include "mojo/public/cpp/bindings/callback_helpers.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
+#include "third_party/blink/renderer/core/dom/dom_exception.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_error.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_push_options.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_type_converters.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_utils.h"
+
+namespace blink {
+
+// static
+NFCWriter* NFCWriter::Create(ExecutionContext* context) {
+  return MakeGarbageCollected<NFCWriter>(context);
+}
+
+NFCWriter::NFCWriter(ExecutionContext* context) : ContextClient(context) {}
+
+void NFCWriter::Trace(blink::Visitor* visitor) {
+  visitor->Trace(nfc_proxy_);
+  visitor->Trace(requests_);
+  ScriptWrappable::Trace(visitor);
+  ContextClient::Trace(visitor);
+}
+
+// https://w3c.github.io/web-nfc/#writing-or-pushing-content
+// https://w3c.github.io/web-nfc/#dom-nfc-push
+ScriptPromise NFCWriter::push(ScriptState* script_state,
+                              const NDEFMessageSource& push_message,
+                              const NFCPushOptions* options) {
+  ExecutionContext* execution_context = GetExecutionContext();
+  // https://w3c.github.io/web-nfc/#security-policies
+  // WebNFC API must be only accessible from top level browsing context.
+  if (!execution_context || !To<Document>(execution_context)->IsInMainFrame()) {
+    return ScriptPromise::RejectWithDOMException(
+        script_state,
+        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotAllowedError,
+                                           kNfcAccessInNonTopFrame));
+  }
+
+  ScriptPromise is_valid_message =
+      RejectIfInvalidNDEFMessageSource(script_state, push_message);
+  if (!is_valid_message.IsEmpty())
+    return is_valid_message;
+
+  // https://w3c.github.io/web-nfc/#dom-nfc-push
+  // 9. If timeout value is NaN or negative, reject promise with "TypeError"
+  // and abort these steps.
+  if (options->hasTimeout() &&
+      (std::isnan(options->timeout()) || options->timeout() < 0)) {
+    return ScriptPromise::Reject(
+        script_state, V8ThrowException::CreateTypeError(
+                          script_state->GetIsolate(), kNfcInvalidPushTimeout));
+  }
+
+  auto message = device::mojom::blink::NDEFMessage::From(push_message);
+  if (!message) {
+    return ScriptPromise::RejectWithDOMException(
+        script_state, MakeGarbageCollected<DOMException>(
+                          DOMExceptionCode::kSyntaxError, kNfcMsgConvertError));
+  }
+
+  if (!SetNDEFMessageURL(execution_context->GetSecurityOrigin()->ToString(),
+                         message)) {
+    return ScriptPromise::RejectWithDOMException(
+        script_state, MakeGarbageCollected<DOMException>(
+                          DOMExceptionCode::kSyntaxError, kNfcSetIdError));
+  }
+
+  if (GetNDEFMessageSize(message) >
+      device::mojom::blink::NDEFMessage::kMaxSize) {
+    return ScriptPromise::RejectWithDOMException(
+        script_state,
+        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotSupportedError,
+                                           kNfcMsgMaxSizeError));
+  }
+
+  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
+  requests_.insert(resolver);
+  auto callback = WTF::Bind(&NFCWriter::OnRequestCompleted,
+                            WrapPersistent(this), WrapPersistent(resolver));
+
+  InitNfcProxyIfNeeded();
+  nfc_proxy_->Push(std::move(message),
+                   device::mojom::blink::NFCPushOptions::From(options),
+                   std::move(callback));
+
+  return resolver->Promise();
+}
+
+void NFCWriter::OnMojoConnectionError() {
+  nfc_proxy_.Clear();
+
+  // If the mojo connection breaks, all push requests will be reject with a
+  // default error.
+  for (ScriptPromiseResolver* resolver : requests_) {
+    resolver->Reject(NFCError::Take(
+        resolver, device::mojom::blink::NFCErrorType::NOT_READABLE));
+  }
+  requests_.clear();
+}
+
+void NFCWriter::InitNfcProxyIfNeeded() {
+  // Init NfcProxy if needed.
+  if (nfc_proxy_)
+    return;
+
+  nfc_proxy_ = NFCProxy::From(*To<Document>(GetExecutionContext()));
+  DCHECK(nfc_proxy_);
+
+  // Add the writer to proxy's writer list for mojo connection error
+  // notification.
+  nfc_proxy_->AddWriter(this);
+}
+
+void NFCWriter::OnRequestCompleted(ScriptPromiseResolver* resolver,
+                                   device::mojom::blink::NFCErrorPtr error) {
+  DCHECK(requests_.Contains(resolver));
+  requests_.erase(resolver);
+
+  if (error.is_null())
+    resolver->Resolve();
+  else
+    resolver->Reject(NFCError::Take(resolver, error->error_type));
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/nfc/nfc_writer.h b/third_party/blink/renderer/modules/nfc/nfc_writer.h
new file mode 100644
index 0000000..915ec8a
--- /dev/null
+++ b/third_party/blink/renderer/modules/nfc/nfc_writer.h
@@ -0,0 +1,56 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_NFC_NFC_WRITER_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_NFC_NFC_WRITER_H_
+
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_constants.h"
+#include "third_party/blink/renderer/modules/nfc/nfc_proxy.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/heap/heap_allocator.h"
+
+namespace blink {
+
+class NFCPushOptions;
+class ExecutionContext;
+class ScriptPromise;
+
+class NFCWriter : public ScriptWrappable, public ContextClient {
+  DEFINE_WRAPPERTYPEINFO();
+  USING_GARBAGE_COLLECTED_MIXIN(NFCWriter);
+
+ public:
+  static NFCWriter* Create(ExecutionContext*);
+
+  explicit NFCWriter(ExecutionContext*);
+  ~NFCWriter() override = default;
+
+  void Trace(blink::Visitor*) override;
+
+  // Pushes NDEFMessageSource asynchronously to NFC tag / peer.
+  ScriptPromise push(ScriptState*,
+                     const NDEFMessageSource&,
+                     const NFCPushOptions*);
+
+  // Called by NFCProxy for notification about connection error.
+  void OnMojoConnectionError();
+
+ private:
+  void InitNfcProxyIfNeeded();
+  void OnRequestCompleted(ScriptPromiseResolver* resolver,
+                          device::mojom::blink::NFCErrorPtr error);
+
+  // |requests_| are kept here to handle Mojo connection failures because
+  // in that case the callback passed to Push() won't be called and
+  // mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink.
+  // This list will also be used by AbortSignal.
+  HeapHashSet<Member<ScriptPromiseResolver>> requests_;
+  Member<NFCProxy> nfc_proxy_;
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_NFC_NFC_WRITER_H_
diff --git a/third_party/blink/renderer/modules/nfc/nfc_writer.idl b/third_party/blink/renderer/modules/nfc/nfc_writer.idl
new file mode 100644
index 0000000..954fabc
--- /dev/null
+++ b/third_party/blink/renderer/modules/nfc/nfc_writer.idl
@@ -0,0 +1,11 @@
+typedef (DOMString or ArrayBuffer or NDEFMessage) NDEFMessageSource;
+
+[
+  RuntimeEnabled=WebNFC,
+  SecureContext,
+  Constructor(),
+  ConstructorCallWith=ExecutionContext,
+  Exposed=Window
+] interface NFCWriter {
+  [CallWith=ScriptState] Promise<void> push (NDEFMessageSource message, optional NFCPushOptions options);
+};
\ No newline at end of file
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.cc
index 3a5597ff..9f64e4f4 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.cc
+++ b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.cc
@@ -75,6 +75,24 @@
     GLenum pname) {
   if (!ValidateWebGLProgramOrShader("getProgramInterfaceParameter", program))
     return ScriptValue::CreateNull(script_state);
+  if (!ValidateProgramInterface(
+      "getProgramInterfaceParameter", program_interface))
+    return ScriptValue::CreateNull(script_state);
+  if (program_interface == GL_ATOMIC_COUNTER_BUFFER &&
+      pname == GL_MAX_NAME_LENGTH) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "getProgramInterfaceParameter",
+                      "atomic counter resources are not assigned name strings");
+    return ScriptValue::CreateNull(script_state);
+  }
+  if (program_interface != GL_ATOMIC_COUNTER_BUFFER &&
+      program_interface != GL_SHADER_STORAGE_BLOCK &&
+      program_interface != GL_UNIFORM_BLOCK &&
+      pname == GL_MAX_NUM_ACTIVE_VARIABLES) {
+    SynthesizeGLError(
+        GL_INVALID_OPERATION, "getProgramInterfaceParameter",
+        "invalid parameter name for the specified program interface");
+    return ScriptValue::CreateNull(script_state);
+  }
 
   switch (pname) {
     case GL_ACTIVE_RESOURCES:
@@ -97,7 +115,16 @@
     GLenum program_interface,
     const String& name) {
   if (!ValidateWebGLProgramOrShader("getProgramResourceIndex", program))
-    return 0;
+    return GL_INVALID_INDEX;
+  if (!ValidateProgramInterface("getProgramResourceIndex", program_interface))
+    return GL_INVALID_INDEX;
+  if (program_interface == GL_ATOMIC_COUNTER_BUFFER) {
+    SynthesizeGLError(GL_INVALID_ENUM, "getProgramResourceIndex",
+                      "atomic counter resources are not assigned name strings");
+    return GL_INVALID_INDEX;
+  }
+  if (!ValidateString("getProgramResourceIndex", name))
+    return GL_INVALID_INDEX;
 
   return ContextGL()->GetProgramResourceIndex(
       ObjectOrZero(program), program_interface, name.Utf8().c_str());
@@ -109,15 +136,22 @@
     GLuint index) {
   if (!ValidateWebGLProgramOrShader("getProgramResourceName", program))
     return String();
+  if (!ValidateProgramInterface("getProgramResourceName", program_interface))
+    return String();
+  if (program_interface == GL_ATOMIC_COUNTER_BUFFER) {
+    SynthesizeGLError(GL_INVALID_ENUM, "getProgramResourceName",
+                      "atomic counter resources are not assigned name strings");
+    return String();
+  }
+  if (!ValidateProgramResourceIndex(
+      "getProgramResourceName", program, program_interface, index))
+    return String();
 
   GLint max_name_length = -1;
   ContextGL()->GetProgramInterfaceiv(ObjectOrZero(program), program_interface,
                                      GL_MAX_NAME_LENGTH, &max_name_length);
-  if (max_name_length <= 0) {
-    SynthesizeGLError(GL_INVALID_VALUE, "getProgramResourceName",
-                      "invalid program interface");
+  if (max_name_length <= 0)
     return String();
-  }
   auto name = std::make_unique<GLchar[]>(max_name_length);
 
   GLsizei length = 0;
@@ -138,54 +172,34 @@
     const Vector<GLenum>& props) {
   if (!ValidateWebGLProgramOrShader("getProgramResource", program))
     return base::nullopt;
+  if (!ValidateProgramInterface("getProgramResource", program_interface))
+    return base::nullopt;
+  if (props.IsEmpty()) {
+    SynthesizeGLError(GL_INVALID_VALUE, "getProgramResource",
+                      "resource prop array is empty");
+    return base::nullopt;
+  }
+  if (!ValidateProgramResourceIndex(
+      "getProgramResource", program, program_interface, index))
+    return base::nullopt;
+
+  // For props with variable-length return values, their lengths will be queried
+  // first with |auxiliary_props|, and |extended_params| will be adequately
+  // sized for the whole result after that.
 
   Vector<GLenum> auxiliary_props;
   Vector<GLint> auxiliary_params;
   Vector<GLenum> extended_props;
   Vector<GLint> extended_params;
-  for (GLenum prop : props) {
-    switch (prop) {
-      // Handle props with fixed-length return values.
-      case GL_ARRAY_SIZE:
-      case GL_ARRAY_STRIDE:
-      case GL_ATOMIC_COUNTER_BUFFER_INDEX:
-      case GL_BLOCK_INDEX:
-      case GL_BUFFER_BINDING:
-      case GL_BUFFER_DATA_SIZE:
-      case GL_IS_ROW_MAJOR:
-      case GL_LOCATION:
-      case GL_MATRIX_STRIDE:
-      case GL_NAME_LENGTH:
-      case GL_NUM_ACTIVE_VARIABLES:
-      case GL_OFFSET:
-      case GL_REFERENCED_BY_COMPUTE_SHADER:
-      case GL_REFERENCED_BY_FRAGMENT_SHADER:
-      case GL_REFERENCED_BY_VERTEX_SHADER:
-      case GL_TOP_LEVEL_ARRAY_SIZE:
-      case GL_TOP_LEVEL_ARRAY_STRIDE:
-      case GL_TYPE:
-        extended_props.push_back(prop);
-        extended_params.push_back(0);
-        break;
-
-      // Handle props with variable-length return values. For these props, their
-      // lengths will be queried first by constructing |auxiliary_props| as the
-      // following, and |extended_params| will be adequately sized for the whole
-      // result after that.
-      case GL_ACTIVE_VARIABLES:
-        auxiliary_props.push_back(GL_NUM_ACTIVE_VARIABLES);
-        auxiliary_params.push_back(-1);
-        extended_props.push_back(GL_ACTIVE_VARIABLES);
-        break;
-
-      default:
-        SynthesizeGLError(GL_INVALID_ENUM, "getProgramResource",
-                          "invalid program resource property");
-        return base::nullopt;
-    }
+  if (!ValidateAndExtendProgramResourceProperties(
+      "getProgramResource", program_interface, props, extended_props))
+    return base::nullopt;
+  extended_params.resize(extended_props.size());
+  for (wtf_size_t i = 0; i < extended_props.size() - props.size(); ++i) {
+    auxiliary_props.push_back(extended_props[i]);
+    auxiliary_params.push_back(-1);
+    extended_params.pop_back();
   }
-  extended_props.PrependVector(auxiliary_props);
-  extended_params.PrependVector(auxiliary_params);
 
   if (auxiliary_props.size()) {
     ContextGL()->GetProgramResourceiv(ObjectOrZero(program), program_interface,
@@ -287,6 +301,20 @@
     const String& name) {
   if (!ValidateWebGLProgramOrShader("getProgramResourceLocation", program))
     return WrapLocation(script_state, -1, program, program_interface);
+  if (!ValidateProgramInterface(
+      "getProgramResourceLocation", program_interface))
+    return WrapLocation(script_state, -1, program, program_interface);
+  if (!ValidateLocationLength("getProgramResourceLocation", name))
+    return WrapLocation(script_state, -1, program, program_interface);
+  if (!ValidateString("getProgramResourceLocation", name))
+    return WrapLocation(script_state, -1, program, program_interface);
+  if (IsPrefixReserved(name))
+    return WrapLocation(script_state, -1, program, program_interface);
+  if (!program->LinkStatus(this)) {
+    SynthesizeGLError(GL_INVALID_OPERATION, "getProgramResourceLocation",
+                      "program not linked");
+    return WrapLocation(script_state, -1, program, program_interface);
+  }
 
   GLint location = ContextGL()->GetProgramResourceLocation(
       ObjectOrZero(program), program_interface, name.Utf8().c_str());
@@ -410,6 +438,172 @@
   WebGL2RenderingContextBase::Trace(visitor);
 }
 
+bool WebGL2ComputeRenderingContextBase::ValidateProgramInterface(
+    const char* function_name,
+    GLenum program_interface) {
+  switch (program_interface) {
+    case GL_ATOMIC_COUNTER_BUFFER:
+    case GL_BUFFER_VARIABLE:
+    case GL_PROGRAM_INPUT:
+    case GL_PROGRAM_OUTPUT:
+    case GL_SHADER_STORAGE_BLOCK:
+    case GL_TRANSFORM_FEEDBACK_VARYING:
+    case GL_UNIFORM:
+    case GL_UNIFORM_BLOCK:
+      return true;
+    default:
+      SynthesizeGLError(GL_INVALID_ENUM, function_name,
+                        "invalid program interface");
+      return false;
+  }
+}
+
+bool WebGL2ComputeRenderingContextBase::ValidateProgramResourceIndex(
+    const char* function_name,
+    WebGLProgram* program,
+    GLenum program_interface,
+    GLuint index) {
+  DCHECK(program);
+  if (!program->LinkStatus(this)) {
+    SynthesizeGLError(GL_INVALID_OPERATION, function_name,
+                      "program not linked");
+    return false;
+  }
+  GLint active_resources = 0;
+  ContextGL()->GetProgramInterfaceiv(ObjectOrZero(program), program_interface,
+                                     GL_ACTIVE_RESOURCES, &active_resources);
+  if (index >= static_cast<GLuint>(active_resources)) {
+    SynthesizeGLError(GL_INVALID_VALUE, function_name,
+                      "invalid program resource index");
+    return false;
+  }
+  return true;
+}
+
+bool
+WebGL2ComputeRenderingContextBase::ValidateAndExtendProgramResourceProperties(
+    const char* function_name,
+    GLenum program_interface,
+    const Vector<GLenum>& props,
+    Vector<GLenum>& extended_props) {
+  Vector<GLenum> auxiliary_props;
+
+  for (GLenum prop : props) {
+    GLenum error = GL_NO_ERROR;
+
+    switch (prop) {
+      // Handle props with fixed-length return values.
+      case GL_BUFFER_BINDING:
+      case GL_NUM_ACTIVE_VARIABLES:
+        if (program_interface != GL_ATOMIC_COUNTER_BUFFER &&
+            program_interface != GL_SHADER_STORAGE_BLOCK &&
+            program_interface != GL_UNIFORM_BLOCK)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_ARRAY_SIZE:
+        if (program_interface != GL_BUFFER_VARIABLE &&
+            program_interface != GL_PROGRAM_INPUT &&
+            program_interface != GL_PROGRAM_OUTPUT &&
+            program_interface != GL_TRANSFORM_FEEDBACK_VARYING &&
+            program_interface != GL_UNIFORM)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_ARRAY_STRIDE:
+      case GL_BLOCK_INDEX:
+      case GL_IS_ROW_MAJOR:
+      case GL_MATRIX_STRIDE:
+        if (program_interface != GL_BUFFER_VARIABLE &&
+            program_interface != GL_UNIFORM)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_ATOMIC_COUNTER_BUFFER_INDEX:
+        if (program_interface != GL_UNIFORM)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_BUFFER_DATA_SIZE:
+        if (program_interface != GL_ATOMIC_COUNTER_BUFFER &&
+            program_interface != GL_SHADER_STORAGE_BLOCK &&
+            program_interface != GL_UNIFORM_BLOCK)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_LOCATION:
+        if (program_interface != GL_PROGRAM_INPUT &&
+            program_interface != GL_PROGRAM_OUTPUT &&
+            program_interface != GL_UNIFORM)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_NAME_LENGTH:
+        if (program_interface == GL_ATOMIC_COUNTER_BUFFER)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_OFFSET:
+        if (program_interface != GL_BUFFER_VARIABLE &&
+            program_interface != GL_UNIFORM)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_REFERENCED_BY_VERTEX_SHADER:
+      case GL_REFERENCED_BY_FRAGMENT_SHADER:
+      case GL_REFERENCED_BY_COMPUTE_SHADER:
+        if (program_interface != GL_ATOMIC_COUNTER_BUFFER &&
+            program_interface != GL_BUFFER_VARIABLE &&
+            program_interface != GL_PROGRAM_INPUT &&
+            program_interface != GL_PROGRAM_OUTPUT &&
+            program_interface != GL_SHADER_STORAGE_BLOCK &&
+            program_interface != GL_UNIFORM &&
+            program_interface != GL_UNIFORM_BLOCK)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_TOP_LEVEL_ARRAY_SIZE:
+      case GL_TOP_LEVEL_ARRAY_STRIDE:
+        if (program_interface != GL_BUFFER_VARIABLE)
+          error = GL_INVALID_OPERATION;
+        break;
+      case GL_TYPE:
+        if (program_interface != GL_BUFFER_VARIABLE &&
+            program_interface != GL_PROGRAM_INPUT &&
+            program_interface != GL_PROGRAM_OUTPUT &&
+            program_interface != GL_TRANSFORM_FEEDBACK_VARYING &&
+            program_interface != GL_UNIFORM)
+          error = GL_INVALID_OPERATION;
+        break;
+
+      // Handle props with variable-length return values.
+      case GL_ACTIVE_VARIABLES:
+        if (program_interface != GL_ATOMIC_COUNTER_BUFFER &&
+            program_interface != GL_SHADER_STORAGE_BLOCK &&
+            program_interface != GL_UNIFORM_BLOCK) {
+          error = GL_INVALID_OPERATION;
+          break;
+        }
+        auxiliary_props.push_back(GL_NUM_ACTIVE_VARIABLES);
+        break;
+
+      default:
+        error = GL_INVALID_ENUM;
+    }
+
+    switch (error) {
+      case GL_NO_ERROR:
+        break;
+      case GL_INVALID_ENUM:
+        SynthesizeGLError(GL_INVALID_ENUM, function_name,
+                          "invalid program resource prop");
+        return false;
+      case GL_INVALID_OPERATION:
+        SynthesizeGLError(
+            GL_INVALID_OPERATION, function_name,
+            "invalid resource prop for the specified program interface");
+        return false;
+      default:
+        NOTREACHED();
+    }
+  }
+
+  extended_props = auxiliary_props;
+  extended_props.AppendVector(props);
+  return true;
+}
+
 ScriptValue WebGL2ComputeRenderingContextBase::WrapLocation(
     ScriptState* script_state,
     GLint location,
diff --git a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.h b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.h
index 28b4954..9e6ca07 100644
--- a/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.h
+++ b/third_party/blink/renderer/modules/webgl/webgl2_compute_rendering_context_base.h
@@ -74,6 +74,18 @@
       bool using_gpu_compositing,
       const CanvasContextCreationAttributesCore& requested_attributes);
 
+  virtual bool ValidateProgramInterface(const char* function_name,
+                                        GLenum program_interface);
+  virtual bool ValidateProgramResourceIndex(const char* function_name,
+                                            WebGLProgram*,
+                                            GLenum program_interface,
+                                            GLuint index);
+  virtual bool ValidateAndExtendProgramResourceProperties(
+      const char* function_name,
+      GLenum program_interface,
+      const Vector<GLenum>& props,
+      Vector<GLenum>& extended_props);
+
   ScriptValue WrapLocation(ScriptState*,
                            GLint location,
                            WebGLProgram* program,
diff --git a/third_party/blink/renderer/modules/xr/xr_rigid_transform_test.cc b/third_party/blink/renderer/modules/xr/xr_rigid_transform_test.cc
index 20b2e39..0e31b61 100644
--- a/third_party/blink/renderer/modules/xr/xr_rigid_transform_test.cc
+++ b/third_party/blink/renderer/modules/xr/xr_rigid_transform_test.cc
@@ -4,8 +4,6 @@
 
 #include "third_party/blink/renderer/modules/xr/xr_rigid_transform.h"
 
-#include <vector>
-
 #include "third_party/blink/renderer/modules/xr/xr_test_utils.h"
 #include "third_party/blink/renderer/modules/xr/xr_utils.h"
 
diff --git a/third_party/blink/renderer/modules/xr/xr_test_utils.h b/third_party/blink/renderer/modules/xr/xr_test_utils.h
index 93fbe6a2..da35d67 100644
--- a/third_party/blink/renderer/modules/xr/xr_test_utils.h
+++ b/third_party/blink/renderer/modules/xr/xr_test_utils.h
@@ -6,7 +6,6 @@
 #define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_TEST_UTILS_H_
 
 #include <memory>
-#include <vector>
 
 #include "third_party/blink/renderer/core/geometry/dom_point_init.h"
 #include "third_party/blink/renderer/core/geometry/dom_point_read_only.h"
diff --git a/third_party/blink/renderer/platform/audio/audio_destination_test.cc b/third_party/blink/renderer/platform/audio/audio_destination_test.cc
index d1a43dc2..072ca234 100644
--- a/third_party/blink/renderer/platform/audio/audio_destination_test.cc
+++ b/third_party/blink/renderer/platform/audio/audio_destination_test.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/renderer/platform/audio/audio_destination.h"
 
 #include <memory>
-#include <vector>
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_audio_device.h"
 #include "third_party/blink/public/platform/web_audio_latency_hint.h"
@@ -75,7 +74,7 @@
   scoped_refptr<AudioDestination> destination = AudioDestination::Create(
       callback, channel_count, latency_hint, sample_rate);
 
-  std::vector<float> channels[channel_count];
+  Vector<float> channels[channel_count];
   WebVector<float*> dest_data(static_cast<size_t>(channel_count));
   for (int i = 0; i < channel_count; ++i) {
     channels[i].resize(request_frames);
diff --git a/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc b/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc
index ee15599..abdc15b 100644
--- a/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc
+++ b/third_party/blink/renderer/platform/audio/push_pull_fifo_multithread_test.cc
@@ -172,7 +172,7 @@
   std::unique_ptr<PushClient> push_client = std::make_unique<PushClient>(
       test_fifo.get(), param.push_buffer_size, param.push_jitter_range_ms);
 
-  std::vector<base::WaitableEvent*> done_events;
+  Vector<base::WaitableEvent*> done_events;
   done_events.push_back(
       pull_client->Start(param.test_duration_ms, pull_interval_ms));
   done_events.push_back(
diff --git a/third_party/blink/renderer/platform/audio/vector_math_test.cc b/third_party/blink/renderer/platform/audio/vector_math_test.cc
index 6f8430e..6c48cf6 100644
--- a/third_party/blink/renderer/platform/audio/vector_math_test.cc
+++ b/third_party/blink/renderer/platform/audio/vector_math_test.cc
@@ -10,12 +10,12 @@
 #include <limits>
 #include <numeric>
 #include <random>
-#include <vector>
 
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
+#include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
 namespace vector_math {
@@ -440,7 +440,7 @@
 
 TEST_F(VectorMathTest, Zvmul) {
   constexpr float kMax = std::numeric_limits<float>::max();
-  std::vector<std::array<float, kFloatArraySize + 1u>> sources(4u);
+  Vector<std::array<float, kFloatArraySize + 1u>> sources(4u);
   for (size_t i = 0u; i < sources.size(); ++i) {
     // Initialize a local source with a randomized test case source.
     std::copy_n(GetSource(i), kFloatArraySize, sources[i].begin());
diff --git a/third_party/blink/renderer/platform/bindings/parkable_string_test.cc b/third_party/blink/renderer/platform/bindings/parkable_string_test.cc
index ca57e9e..c26a69f0 100644
--- a/third_party/blink/renderer/platform/bindings/parkable_string_test.cc
+++ b/third_party/blink/renderer/platform/bindings/parkable_string_test.cc
@@ -5,7 +5,6 @@
 #include <limits>
 #include <random>
 #include <thread>
-#include <vector>
 
 #include "base/run_loop.h"
 #include "base/test/metrics/histogram_tester.h"
@@ -35,7 +34,7 @@
 constexpr size_t kCompressedSize = 55;
 
 String MakeLargeString(char c = 'a') {
-  std::vector<char> data(kSizeKb * 1000, c);
+  Vector<char> data(kSizeKb * 1000, c);
   return String(data.data(), data.size()).ReleaseImpl();
 }
 
@@ -140,7 +139,7 @@
   // ensure its compressed size is larger than the initial size (at least from
   // gzip's header). Mersenne-Twister implementation is specified, making the
   // test deterministic.
-  std::vector<unsigned char> data(kSizeKb * 1000);
+  Vector<unsigned char> data(kSizeKb * 1000);
   std::mt19937 engine(42);
   // uniform_int_distribution<T> is undefined behavior for T = unsigned char.
   std::uniform_int_distribution<int> dist(
@@ -170,7 +169,7 @@
   UChar emoji_grinning_face[2] = {0xd83d, 0xde00};
   size_t size_in_chars = 2 * kSizeKb * 1000 / sizeof(UChar);
 
-  std::vector<UChar> data(size_in_chars);
+  Vector<UChar> data(size_in_chars);
   for (size_t i = 0; i < size_in_chars / 2; ++i) {
     data[i * 2] = emoji_grinning_face[0];
     data[i * 2 + 1] = emoji_grinning_face[1];
@@ -614,13 +613,13 @@
 TEST_F(ParkableStringTest, ShouldPark) {
   String empty_string("");
   EXPECT_FALSE(ParkableStringManager::ShouldPark(*empty_string.Impl()));
-  std::vector<char> data(20 * 1000, 'a');
+  Vector<char> data(20 * 1000, 'a');
 
   String parkable(String(data.data(), data.size()).ReleaseImpl());
   EXPECT_TRUE(ParkableStringManager::ShouldPark(*parkable.Impl()));
 
   std::thread t([]() {
-    std::vector<char> data(20 * 1000, 'a');
+    Vector<char> data(20 * 1000, 'a');
     String parkable(String(data.data(), data.size()).ReleaseImpl());
     EXPECT_FALSE(ParkableStringManager::ShouldPark(*parkable.Impl()));
   });
@@ -1004,7 +1003,7 @@
   // Need to make the string really large, otherwise unparking takes less than
   // 1ms, and the 0 bucket is populated.
   const size_t original_size = 5 * 1000 * 1000;
-  std::vector<char> data(original_size, 'a');
+  Vector<char> data(original_size, 'a');
   ParkableString parkable(String(data.data(), data.size()).ReleaseImpl());
 
   ParkAndWait(parkable);
diff --git a/third_party/blink/renderer/platform/exported/web_canonical_cookie.cc b/third_party/blink/renderer/platform/exported/web_canonical_cookie.cc
index 52fad8525..08a9c874 100644
--- a/third_party/blink/renderer/platform/exported/web_canonical_cookie.cc
+++ b/third_party/blink/renderer/platform/exported/web_canonical_cookie.cc
@@ -5,7 +5,6 @@
 #include "third_party/blink/public/platform/web_canonical_cookie.h"
 
 #include <memory>
-#include <vector>
 
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_constants.h"
diff --git a/third_party/blink/renderer/platform/fonts/font_selection_types_test.cc b/third_party/blink/renderer/platform/fonts/font_selection_types_test.cc
index dd41e1f..11480c8 100644
--- a/third_party/blink/renderer/platform/fonts/font_selection_types_test.cc
+++ b/third_party/blink/renderer/platform/fonts/font_selection_types_test.cc
@@ -10,10 +10,9 @@
 namespace blink {
 
 TEST(FontSelectionTypesTest, HashCollisions) {
-  std::vector<int> weights = {100, 200, 300, 400, 500, 600, 700, 800, 900};
-  std::vector<float> slopes = {-90, -67.5, -30, -20,  -10, 0,
-                               10,  20,    30,  67.5, 90};
-  std::vector<float> widths = {50, 67.5, 75, 100, 125, 150, 167.5, 175, 200};
+  Vector<int> weights = {100, 200, 300, 400, 500, 600, 700, 800, 900};
+  Vector<float> slopes = {-90, -67.5, -30, -20, -10, 0, 10, 20, 30, 67.5, 90};
+  Vector<float> widths = {50, 67.5, 75, 100, 125, 150, 167.5, 175, 200};
 
   HashSet<unsigned> hashes;
   for (auto weight : weights) {
diff --git a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_test.cc b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_test.cc
index f009755..2ce9ed84 100644
--- a/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_test.cc
+++ b/third_party/blink/renderer/platform/fonts/opentype/open_type_caps_support_test.cc
@@ -18,12 +18,12 @@
 
 namespace blink {
 
-void ensureHasNativeSmallCaps(const std::string& font_family_name) {
+void ensureHasNativeSmallCaps(const char* font_family_name) {
   sk_sp<SkTypeface> test_typeface =
-      SkTypeface::MakeFromName(font_family_name.c_str(), SkFontStyle());
-  FontPlatformData font_platform_data(test_typeface, font_family_name.c_str(),
-                                      16, false, false);
-  ASSERT_EQ(font_platform_data.FontFamilyName(), font_family_name.c_str());
+      SkTypeface::MakeFromName(font_family_name, SkFontStyle());
+  FontPlatformData font_platform_data(test_typeface, font_family_name, 16,
+                                      false, false);
+  ASSERT_EQ(font_platform_data.FontFamilyName(), font_family_name);
 
   OpenTypeCapsSupport caps_support(font_platform_data.GetHarfBuzzFace(),
                                    FontDescription::FontVariantCaps::kSmallCaps,
@@ -46,13 +46,13 @@
   if (!base::mac::IsAtLeastOS10_13())
     return;
 #endif
-  std::vector<std::string> test_fonts = {
+  Vector<const char*> test_fonts = {
       ".SF NS Text",     // has OpenType small-caps
       "Apple Chancery",  // has old-style (feature id 3,"Letter Case")
                          // small-caps
       "Baskerville"};    // has new-style (feature id 38, "Upper Case")
                          // small-case.
-  for (auto& test_font : test_fonts)
+  for (auto* test_font : test_fonts)
     ensureHasNativeSmallCaps(test_font);
 }
 
diff --git a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
index b4794c7a..4545b3f4 100644
--- a/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
+++ b/third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper_test.cc
@@ -1485,7 +1485,7 @@
   HarfBuzzShaper shaper(string);
   scoped_refptr<ShapeResult> result = shaper.Shape(&font, TextDirection::kRtl);
 
-  std::vector<unsigned> safe_to_break_positions;
+  Vector<unsigned> safe_to_break_positions;
 
 #if defined(OS_MACOSX)
   safe_to_break_positions = {0, 2, 3, 4, 11};
diff --git a/third_party/blink/renderer/platform/heap/heap_test.cc b/third_party/blink/renderer/platform/heap/heap_test.cc
index 7bfe3b7..ba873a9 100644
--- a/third_party/blink/renderer/platform/heap/heap_test.cc
+++ b/third_party/blink/renderer/platform/heap/heap_test.cc
@@ -917,7 +917,7 @@
 class RefCountedAndGarbageCollected
     : public GarbageCollectedFinalized<RefCountedAndGarbageCollected> {
  public:
-  RefCountedAndGarbageCollected() : ref_count_(0) {}
+  RefCountedAndGarbageCollected() : keep_alive_(PERSISTENT_FROM_HERE) {}
   ~RefCountedAndGarbageCollected() { ++destructor_calls_; }
 
   void AddRef() {
@@ -942,7 +942,7 @@
   static int destructor_calls_;
 
  private:
-  int ref_count_;
+  int ref_count_ = 0;
   SelfKeepAlive<RefCountedAndGarbageCollected> keep_alive_;
 };
 
@@ -952,7 +952,7 @@
     : public HeapTestOtherSuperClass,
       public GarbageCollectedFinalized<RefCountedAndGarbageCollected2> {
  public:
-  RefCountedAndGarbageCollected2() : ref_count_(0) {}
+  RefCountedAndGarbageCollected2() : keep_alive_(PERSISTENT_FROM_HERE) {}
   ~RefCountedAndGarbageCollected2() { ++destructor_calls_; }
 
   void Ref() {
@@ -977,7 +977,7 @@
   static int destructor_calls_;
 
  private:
-  int ref_count_;
+  int ref_count_ = 0;
   SelfKeepAlive<RefCountedAndGarbageCollected2> keep_alive_;
 };
 
diff --git a/third_party/blink/renderer/platform/heap/incremental_marking_test.cc b/third_party/blink/renderer/platform/heap/incremental_marking_test.cc
index 4dbe630..49d9c27 100644
--- a/third_party/blink/renderer/platform/heap/incremental_marking_test.cc
+++ b/third_party/blink/renderer/platform/heap/incremental_marking_test.cc
@@ -3,7 +3,6 @@
 // found in the LICENSE file.
 
 #include <initializer_list>
-#include <vector>
 
 #include "base/bind.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -26,7 +25,7 @@
 // store to be in the set of provided objects.
 class BackingVisitor : public Visitor {
  public:
-  BackingVisitor(ThreadState* state, std::vector<void*>* objects)
+  BackingVisitor(ThreadState* state, Vector<void*>* objects)
       : Visitor(state), objects_(objects) {}
   ~BackingVisitor() final {}
 
@@ -41,7 +40,7 @@
 
   void Visit(void* obj, TraceDescriptor desc) final {
     EXPECT_TRUE(obj);
-    auto pos = std::find(objects_->begin(), objects_->end(), obj);
+    auto** pos = std::find(objects_->begin(), objects_->end(), obj);
     if (objects_->end() != pos)
       objects_->erase(pos);
     // The garbage collector will find those objects so we can mark them.
@@ -74,7 +73,7 @@
   void Visit(const TraceWrapperV8Reference<v8::Value>&) final {}
 
  private:
-  std::vector<void*>* objects_;
+  Vector<void*>* objects_;
 };
 
 // Base class for initializing worklists.
@@ -158,7 +157,7 @@
       headers_.push_back(HeapObjectHeader::FromPayload(object));
       EXPECT_FALSE(headers_.back()->IsMarked());
     }
-    EXPECT_FALSE(objects_.empty());
+    EXPECT_FALSE(objects_.IsEmpty());
   }
 
   ~ExpectWriteBarrierFires() {
@@ -174,11 +173,11 @@
             HeapObjectHeader::FromPayload(item.object));
         continue;
       }
-      auto pos = std::find(objects_.begin(), objects_.end(), item.object);
+      auto** pos = std::find(objects_.begin(), objects_.end(), item.object);
       if (objects_.end() != pos)
         objects_.erase(pos);
     }
-    EXPECT_TRUE(objects_.empty());
+    EXPECT_TRUE(objects_.IsEmpty());
     // All headers of objects watched should be marked at this point.
     for (HeapObjectHeader* header : headers_) {
       EXPECT_TRUE(header->IsMarked());
@@ -188,8 +187,8 @@
   }
 
  private:
-  std::vector<void*> objects_;
-  std::vector<HeapObjectHeader*> headers_;
+  Vector<void*> objects_;
+  Vector<HeapObjectHeader*> headers_;
   BackingVisitor backing_visitor_;
 };
 
@@ -204,7 +203,7 @@
     EXPECT_TRUE(marking_worklist_->IsGlobalEmpty());
     for (void* object : objects_) {
       HeapObjectHeader* header = HeapObjectHeader::FromPayload(object);
-      headers_.push_back({header, header->IsMarked()});
+      headers_.push_back(std::make_pair(header, header->IsMarked()));
     }
   }
 
@@ -217,8 +216,8 @@
   }
 
  private:
-  std::vector<void*> objects_;
-  std::vector<std::pair<HeapObjectHeader*, bool /* was marked */>> headers_;
+  Vector<void*> objects_;
+  Vector<std::pair<HeapObjectHeader*, bool /* was marked */>> headers_;
 };
 
 class Object : public LinkedObject {
diff --git a/third_party/blink/renderer/platform/heap/self_keep_alive.h b/third_party/blink/renderer/platform/heap/self_keep_alive.h
index 25550674..3eaff5d5 100644
--- a/third_party/blink/renderer/platform/heap/self_keep_alive.h
+++ b/third_party/blink/renderer/platform/heap/self_keep_alive.h
@@ -17,6 +17,7 @@
 //
 //  class Opener : public GarbageCollected<Opener> {
 //   public:
+//    Opener() : keep_alive_(PERSISTENT_FROM_HERE) {}
 //    ...
 //    void Open() {
 //      // Retain a self-reference while in an Open()ed state:
@@ -44,9 +45,8 @@
   DISALLOW_NEW();
 
  public:
-  SelfKeepAlive() = default;
-
-  explicit SelfKeepAlive(Self* self) { Assign(self); }
+  explicit SelfKeepAlive(const PersistentLocation& location)
+      : keep_alive_(location) {}
   SelfKeepAlive(const PersistentLocation& location, Self* self)
       : keep_alive_(location) {
     Assign(self);
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.cc b/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.cc
index 6eb4223..50b4c78 100644
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.cc
@@ -41,7 +41,7 @@
          (image_size > threshold);
 }
 
-void ReadFileToVector(const base::FilePath& path, std::vector<char>* contents) {
+void ReadFileToVector(const base::FilePath& path, Vector<char>* contents) {
   std::string raw_image_data;
   base::ReadFileToString(path, &raw_image_data);
   contents->resize(raw_image_data.size());
@@ -106,7 +106,7 @@
   base::FilePath data_dir;
   ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &data_dir));
   data_dir_ = data_dir.AppendASCII("webkit").AppendASCII("data").AppendASCII(
-      format_ + "_decoder");
+      format_.Utf8() + "_decoder");
   if (!base::PathExists(data_dir_)) {
     const testing::TestInfo* const test_info =
         testing::UnitTest::GetInstance()->current_test_info();
@@ -123,11 +123,11 @@
   return base::FilePath(path.value() + kDecodedDataExtension);
 }
 
-std::vector<base::FilePath> ImageDecoderBaseTest::GetImageFiles() const {
-  std::string pattern = "*." + format_;
+Vector<base::FilePath> ImageDecoderBaseTest::GetImageFiles() const {
+  std::string pattern = "*." + format_.Utf8();
   base::FileEnumerator enumerator(data_dir_, false,
                                   base::FileEnumerator::FILES);
-  std::vector<base::FilePath> image_files;
+  Vector<base::FilePath> image_files;
   for (base::FilePath next_file_name = enumerator.Next();
        !next_file_name.empty(); next_file_name = enumerator.Next()) {
     base::FilePath base_name = next_file_name.BaseName();
@@ -156,8 +156,8 @@
     const int64_t threshold) {
   if (data_dir_.empty())
     return;
-  const std::vector<base::FilePath> image_files(GetImageFiles());
-  for (std::vector<base::FilePath>::const_iterator i = image_files.begin();
+  const Vector<base::FilePath> image_files(GetImageFiles());
+  for (Vector<base::FilePath>::const_iterator i = image_files.begin();
        i != image_files.end(); ++i) {
     if (!ShouldSkipFile(*i, file_selection, threshold))
       TestImageDecoder(*i, GetMD5SumPath(*i), kFirstFrameIndex);
@@ -173,7 +173,7 @@
     return;
 #endif
 
-  std::vector<char> image_contents;
+  Vector<char> image_contents;
   ReadFileToVector(image_path, &image_contents);
   EXPECT_TRUE(image_contents.size());
   std::unique_ptr<ImageDecoder> decoder(CreateImageDecoder());
diff --git a/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.h b/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.h
index bb350da..07ff94e 100644
--- a/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.h
+++ b/third_party/blink/renderer/platform/image-decoders/image_decoder_base_test.h
@@ -7,9 +7,6 @@
 
 #include <stdint.h>
 
-#include <string>
-#include <vector>
-
 #include "base/compiler_specific.h"
 #include "base/files/file_path.h"
 #include "base/macros.h"
@@ -33,7 +30,7 @@
 
 class ImageDecoderBaseTest : public testing::Test {
  public:
-  explicit ImageDecoderBaseTest(const std::string& format) : format_(format) {}
+  explicit ImageDecoderBaseTest(const String& format) : format_(format) {}
 
   enum class FileSelection {
     kAll,
@@ -48,7 +45,7 @@
   base::FilePath GetMD5SumPath(const base::FilePath& path);
 
   // Returns the vector of image files for testing.
-  std::vector<base::FilePath> GetImageFiles() const;
+  Vector<base::FilePath> GetImageFiles() const;
 
   // Returns true if the image is bogus and should not be successfully decoded.
   bool ShouldImageFail(const base::FilePath& path) const;
@@ -72,7 +69,7 @@
   virtual std::unique_ptr<ImageDecoder> CreateImageDecoder() const = 0;
 
   // The format to be decoded, like "bmp" or "ico".
-  std::string format_;
+  String format_;
 
  protected:
   const base::FilePath& data_dir() const { return data_dir_; }
diff --git a/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc
index c243cf6..d4bf881 100644
--- a/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc
@@ -33,7 +33,6 @@
 #include <limits>
 #include <memory>
 #include <utility>
-#include <vector>
 
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/web_data.h"
diff --git a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc
index 48d910e..b263c8fc 100644
--- a/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc
+++ b/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc
@@ -1043,7 +1043,7 @@
   bool is_transparent;
   bool is_high_bit_depth;
   scoped_refptr<SharedBuffer> png_contents;
-  std::vector<float> expected_pixels;
+  Vector<float> expected_pixels;
 };
 
 static void TestHighBitDepthPNGDecoding(const PNGSample& png_sample,
@@ -1092,7 +1092,7 @@
       skcms_AlphaFormat_Unpremul, nullptr, decoded_pixels_float_32,
       skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul, nullptr, 4));
 
-  std::vector<float> expected_pixels = png_sample.expected_pixels;
+  Vector<float> expected_pixels = png_sample.expected_pixels;
   bool test_succeed = true;
   const float decoding_tolerance = 0.001;
   for (int i = 0; i < 16; i++) {
@@ -1109,7 +1109,7 @@
   ASSERT_TRUE(test_succeed);
 }
 
-static void FillPNGSamplesSourcePixels(std::vector<PNGSample>& png_samples) {
+static void FillPNGSamplesSourcePixels(Vector<PNGSample>& png_samples) {
   // Color components of opaque and transparent 16 bit PNG, read with libpng
   // in BigEndian and scaled to [0,1]. The values are read from non-interlaced
   // samples, but used for both interlaced and non-interlaced test cases.
@@ -1118,63 +1118,63 @@
   // Adobe software created a non-matching color profile (see crbug.com/874939).
   // Hence, SkEncoder was used to generate the e-sRGB file (see the skia fiddle
   // here: https://fiddle.skia.org/c/17beedfd66dac1ec930f0c414c50f847).
-  static const std::vector<float> source_pixels_opaque_srgb = {
+  static const Vector<float> source_pixels_opaque_srgb = {
       0.4986953536, 0.5826657511, 0.7013199054, 1,   // Top left pixel
       0.907988098,  0.8309605554, 0.492011902,  1,   // Top right pixel
       0.6233157855, 0.9726558328, 0.9766536965, 1,   // Bottom left pixel
       0.8946517128, 0.9663080797, 0.9053025101, 1};  // Bottom right pixel
-  static const std::vector<float> source_pixels_opaque_adobe_rgb = {
+  static const Vector<float> source_pixels_opaque_adobe_rgb = {
       0.4448004883, 0.5216296635, 0.6506294347, 1,   // Top left pixel
       0.8830548562, 0.7978179599, 0.4323186084, 1,   // Top right pixel
       0.6841992828, 0.9704280156, 0.9711299306, 1,   // Bottom left pixel
       0.8874799725, 0.96099794,   0.8875715267, 1};  // Bottom right pixel
-  static const std::vector<float> source_pixels_opaque_p3 = {
+  static const Vector<float> source_pixels_opaque_p3 = {
       0.515648127,  0.5802243076, 0.6912489509, 1,   // Top left pixel
       0.8954146639, 0.8337987335, 0.5691767758, 1,   // Top right pixel
       0.772121767,  0.9671625849, 0.973510338,  1,   // Bottom left pixel
       0.9118944076, 0.9645685512, 0.9110704204, 1};  // Bottom right pixel
-  static const std::vector<float> source_pixels_opaque_e_srgb = {
+  static const Vector<float> source_pixels_opaque_e_srgb = {
       0.6977539062, 0.5839843750, 0.4978027344, 1,   // Top left pixel
       0.4899902344, 0.8310546875, 0.9096679688, 1,   // Top right pixel
       0.9760742188, 0.9721679688, 0.6230468750, 1,   // Bottom left pixel
       0.9057617188, 0.9643554688, 0.8940429688, 1};  // Bottom right pixel
-  static const std::vector<float> source_pixels_opaque_prophoto = {
+  static const Vector<float> source_pixels_opaque_prophoto = {
       0.5032883192, 0.5191271839, 0.6309147784, 1,   // Top left pixel
       0.8184176394, 0.8002899214, 0.5526970321, 1,   // Top right pixel
       0.842526894,  0.945616846,  0.9667048142, 1,   // Bottom left pixel
       0.9119554437, 0.9507133593, 0.9001754788, 1};  // Bottom right pixel
-  static const std::vector<float> source_pixels_opaque_rec2020 = {
+  static const Vector<float> source_pixels_opaque_rec2020 = {
       0.5390554665, 0.5766842145, 0.6851758602, 1,   // Top left pixel
       0.871061265,  0.831326772,  0.5805294881, 1,   // Top right pixel
       0.8386205844, 0.9599603265, 0.9727168688, 1,   // Bottom left pixel
       0.9235217823, 0.9611200122, 0.9112840467, 1};  // Bottom right pixel
 
-  static const std::vector<float> source_pixels_transparent_srgb = {
+  static const Vector<float> source_pixels_transparent_srgb = {
       0.3733272297,  0.4783093004, 0.6266422522, 0.8,   // Top left pixel
       0.8466468299,  0.7182879377, 0.153322652,  0.6,   // Top right pixel
       0.05831998169, 0.9316395819, 0.9416495003, 0.4,   // Bottom left pixel
       0.4733043412,  0.8316319524, 0.5266346227, 0.2};  // Bottom right pixel
-  static const std::vector<float> source_pixels_transparent_adobe_rgb = {
+  static const Vector<float> source_pixels_transparent_adobe_rgb = {
       0.305943389,  0.4019836728, 0.5632867933,  0.8,   // Top left pixel
       0.8051117723, 0.6630197604, 0.05374227512, 0.6,   // Top right pixel
       0.210482948,  0.926115816,  0.9278248264,  0.4,   // Bottom left pixel
       0.4374456397, 0.8050812543, 0.4379644465,  0.2};  // Bottom right pixel
-  static const std::vector<float> source_pixels_transparent_p3 = {
+  static const Vector<float> source_pixels_transparent_p3 = {
       0.3945372702, 0.475257496,  0.6140383001, 0.8,   // Top left pixel
       0.8257114519, 0.7230182345, 0.2819256886, 0.6,   // Top right pixel
       0.4302738994, 0.9179064622, 0.933806363,  0.4,   // Bottom left pixel
       0.5595330739, 0.8228122377, 0.5554436561, 0.2};  // Bottom right pixel
-  static const std::vector<float> source_pixels_transparent_e_srgb = {
+  static const Vector<float> source_pixels_transparent_e_srgb = {
       0.6230468750, 0.4782714844, 0.3723144531, 0.8,   // Top left pixel
       0.1528320312, 0.7172851562, 0.8466796875, 0.6,   // Top right pixel
       0.9409179688, 0.9331054688, 0.0588073730, 0.4,   // Bottom left pixel
       0.5253906250, 0.8310546875, 0.4743652344, 0.2};  // Bottom right pixel
-  static const std::vector<float> source_pixels_transparent_prophoto = {
+  static const Vector<float> source_pixels_transparent_prophoto = {
       0.379064622,  0.3988708324, 0.5386282139, 0.8,   // Top left pixel
       0.6973525597, 0.6671396963, 0.2544289311, 0.6,   // Top right pixel
       0.6063477531, 0.864103151,  0.9168078126, 0.4,   // Bottom left pixel
       0.5598077363, 0.7536278325, 0.5009384298, 0.2};  // Bottom right pixel
-  static const std::vector<float> source_pixels_transparent_rec2020 = {
+  static const Vector<float> source_pixels_transparent_rec2020 = {
       0.4237735561, 0.4708323796, 0.6064698253, 0.8,   // Top left pixel
       0.7851224537, 0.7188677806, 0.3008468757, 0.6,   // Top right pixel
       0.5965819791, 0.8999618524, 0.9318532082, 0.4,   // Bottom left pixel
@@ -1211,12 +1211,12 @@
   }
 }
 
-static std::vector<PNGSample> GetPNGSamplesInfo(bool include_8bit_pngs) {
-  std::vector<PNGSample> png_samples;
-  std::vector<String> interlace_status = {"", "_interlaced"};
-  std::vector<String> color_spaces = {"sRGB",   "AdobeRGB", "DisplayP3",
-                                      "e-sRGB", "ProPhoto", "Rec2020"};
-  std::vector<String> alpha_status = {"_opaque", "_transparent"};
+static Vector<PNGSample> GetPNGSamplesInfo(bool include_8bit_pngs) {
+  Vector<PNGSample> png_samples;
+  Vector<String> interlace_status = {"", "_interlaced"};
+  Vector<String> color_spaces = {"sRGB",   "AdobeRGB", "DisplayP3",
+                                 "e-sRGB", "ProPhoto", "Rec2020"};
+  Vector<String> alpha_status = {"_opaque", "_transparent"};
 
   for (String color_space : color_spaces) {
     for (String alpha : alpha_status) {
@@ -1252,7 +1252,7 @@
 
 TEST(StaticPNGTests, DecodeHighBitDepthPngToHalfFloat) {
   const bool include_8bit_pngs = false;
-  std::vector<PNGSample> png_samples = GetPNGSamplesInfo(include_8bit_pngs);
+  Vector<PNGSample> png_samples = GetPNGSamplesInfo(include_8bit_pngs);
   FillPNGSamplesSourcePixels(png_samples);
   String path = "/images/resources/png-16bit/";
   for (PNGSample& png_sample : png_samples) {
@@ -1265,7 +1265,7 @@
 
 TEST(StaticPNGTests, ImageIsHighBitDepth) {
   const bool include_8bit_pngs = true;
-  std::vector<PNGSample> png_samples = GetPNGSamplesInfo(include_8bit_pngs);
+  Vector<PNGSample> png_samples = GetPNGSamplesInfo(include_8bit_pngs);
   IntSize size(2, 2);
 
   String path = "/images/resources/png-16bit/";
diff --git a/third_party/blink/renderer/platform/json/json_parser_test.cc b/third_party/blink/renderer/platform/json/json_parser_test.cc
index f07f78b..550023d 100644
--- a/third_party/blink/renderer/platform/json/json_parser_test.cc
+++ b/third_party/blink/renderer/platform/json/json_parser_test.cc
@@ -650,7 +650,7 @@
 
   // Test cases. Each pair is a JSON string, and the minimum depth required
   // to successfully parse that string.
-  std::vector<std::pair<const char*, int>> test_cases = {
+  Vector<std::pair<const char*, int>> test_cases = {
       {"[[[[[]]]]]", 5},
       {"[[[[[\"a\"]]]]]", 6},
       {"[[],[],[],[],[]]", 2},
diff --git a/third_party/blink/renderer/platform/loader/link_header.cc b/third_party/blink/renderer/platform/loader/link_header.cc
index 1972ff9a..9a6942e 100644
--- a/third_party/blink/renderer/platform/loader/link_header.cc
+++ b/third_party/blink/renderer/platform/loader/link_header.cc
@@ -108,13 +108,10 @@
   // According to Section 5.2 of RFC 5988, "anchor" parameters in Link headers
   // must be either respected, or the entire header must be ignored:
   // https://tools.ietf.org/html/rfc5988#section-5.2
-  // Blink uses "anchor" parameters only when SignedExchangeSubresourcePrefetch
-  // is enabled and the rel is "alternate".
-  if (anchor_.has_value() &&
-      (!RuntimeEnabledFeatures::SignedExchangeSubresourcePrefetchEnabled() ||
-       rel_ != "alternate")) {
+  // Blink uses "anchor" parameters only for SignedExchangeSubresourcePrefetch
+  // and the rel is "alternate".
+  if (anchor_.has_value() && rel_ != "alternate")
     is_valid_ = false;
-  }
 }
 
 LinkHeaderSet::LinkHeaderSet(const String& header) {
diff --git a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
index 7d5531b..7629c5f 100644
--- a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
+++ b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc
@@ -6,7 +6,6 @@
 
 #include <atomic>
 #include <utility>
-#include <vector>
 
 #include "base/bind.h"
 #include "base/logging.h"
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 1fd5181..be1635a 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -476,6 +476,10 @@
       status: "experimental",
     },
     {
+      name: "DiscardInputToMovingIframes",
+      status: "experimental",
+    },
+    {
       name: "DisplayCutoutAPI",
       settable_from_internals: true,
     },
@@ -1453,6 +1457,8 @@
     },
     {
       name: "SignedExchangeSubresourcePrefetch",
+      origin_trial_feature_name: "SignedExchangeSubresourcePrefetch",
+      status: "experimental",
     },
     {
       name: "SkipAd",
diff --git a/third_party/blink/renderer/platform/timer_test.cc b/third_party/blink/renderer/platform/timer_test.cc
index a72a273..bbd8fb94 100644
--- a/third_party/blink/renderer/platform/timer_test.cc
+++ b/third_party/blink/renderer/platform/timer_test.cc
@@ -682,9 +682,8 @@
 
 class TaskObserver : public base::MessageLoop::TaskObserver {
  public:
-  TaskObserver(
-      scoped_refptr<base::SingleThreadTaskRunner> task_runner,
-      std::vector<scoped_refptr<base::SingleThreadTaskRunner>>* run_order)
+  TaskObserver(scoped_refptr<base::SingleThreadTaskRunner> task_runner,
+               Vector<scoped_refptr<base::SingleThreadTaskRunner>>* run_order)
       : task_runner_(std::move(task_runner)), run_order_(run_order) {}
 
   void WillProcessTask(const base::PendingTask&) override {}
@@ -695,13 +694,13 @@
 
  private:
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
-  std::vector<scoped_refptr<base::SingleThreadTaskRunner>>* run_order_;
+  Vector<scoped_refptr<base::SingleThreadTaskRunner>>* run_order_;
 };
 
 }  // namespace
 
 TEST_F(TimerTest, MoveToNewTaskRunnerOneShot) {
-  std::vector<scoped_refptr<base::SingleThreadTaskRunner>> run_order;
+  Vector<scoped_refptr<base::SingleThreadTaskRunner>> run_order;
 
   scoped_refptr<MainThreadTaskQueue> task_queue1(
       platform_->GetMainThreadScheduler()->NewTimerTaskQueue(
@@ -743,7 +742,7 @@
 }
 
 TEST_F(TimerTest, MoveToNewTaskRunnerRepeating) {
-  std::vector<scoped_refptr<base::SingleThreadTaskRunner>> run_order;
+  Vector<scoped_refptr<base::SingleThreadTaskRunner>> run_order;
 
   scoped_refptr<MainThreadTaskQueue> task_queue1(
       platform_->GetMainThreadScheduler()->NewTimerTaskQueue(
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
index 041cfba7..5330899 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=LayoutNG
@@ -25,10 +25,6 @@
 
 # rightsizing-grid.html is truly flaky, show flakiness on reload
 
-# CSS Text 3 test that pass in legacy layout
-crbug.com/972992 external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-003.html [ Failure ]
-
-
 # Needs rebaselining.
 
 # Features that do not have active plans to support or turn on.
@@ -138,7 +134,6 @@
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-replaced-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-replaced-002.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-replaced-003.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/overflow-wrap/overflow-wrap-break-word-005.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/overflow-wrap/overflow-wrap-shaping-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/shaping/shaping-000.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/shaping/shaping-001.html [ Pass ]
@@ -167,7 +162,6 @@
 crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-001.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/white-space/line-edge-white-space-collapse-002.html [ Pass ]
 crbug.com/40634 external/wpt/css/css-text/white-space/trailing-space-before-br-001.html [ Pass ]
-crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-004.html [ Pass ]
 crbug.com/591099 external/wpt/css/css-text/word-break/word-break-break-all-inline-006.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-transitions/no-transition-from-ua-to-blocking-stylesheet.html [ Failure Pass ]
 crbug.com/591099 external/wpt/css/css-ui/text-overflow-010.html [ Pass ]
diff --git a/third_party/blink/web_tests/SlowTests b/third_party/blink/web_tests/SlowTests
index ec8daf6..056a2c20 100644
--- a/third_party/blink/web_tests/SlowTests
+++ b/third_party/blink/web_tests/SlowTests
@@ -645,3 +645,11 @@
 crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-twice.html [ Slow ]
 crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-overridesexpires.html [ Slow ]
 crbug.com/874695 virtual/blink-cors/http/tests/xmlhttprequest/timeout/xmlhttprequest-timeout-worker-twice.html [ Slow ]
+
+crbug.com/980804 fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
+crbug.com/980804 virtual/compositor_threaded_scrollbar_scrolling/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
+crbug.com/980804 virtual/fractional_scrolling/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
+crbug.com/980804 virtual/scroll_customization/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
+crbug.com/980804 virtual/threaded/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
+crbug.com/980804 virtual/fractional_scrolling_threaded/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
+crbug.com/980804 virtual/main_thread_scrollbar_gestures/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Slow ]
\ No newline at end of file
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 4b3244df..295a07c 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -710,7 +710,6 @@
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-atomic-001.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-atomic-002.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/line-breaking/line-breaking-replaced-001.html [ Failure ]
-crbug.com/591099 external/wpt/css/css-text/overflow-wrap/overflow-wrap-break-word-005.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/shaping/shaping-009.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/shaping/shaping-010.html [ Failure ]
 crbug.com/591099 external/wpt/css/css-text/shaping/shaping-011.html [ Failure ]
@@ -3080,23 +3079,30 @@
 crbug.com/953847 [ Mac ] fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] fast/scrolling/scrollbars/scrollbar-button-gesture-target.html [ Failure ]
+crbug.com/953847 [ Mac ] fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 crbug.com/953847 [ Mac ] virtual/compositor_threaded_scrollbar_scrolling/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/compositor_threaded_scrollbar_scrolling/fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
+crbug.com/953847 [ Mac ] virtual/compositor_threaded_scrollbar_scrolling/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 crbug.com/953847 [ Mac ] virtual/fractional_scrolling/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/fractional_scrolling/fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/fractional_scrolling/fast/scrolling/scrollbars/scrollbar-button-gesture-target.html [ Failure ]
+crbug.com/953847 [ Mac ] virtual/fractional_scrolling/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 crbug.com/953847 [ Mac ] virtual/scroll_customization/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/scroll_customization/fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/scroll_customization/fast/scrolling/scrollbars/scrollbar-button-gesture-target.html [ Failure ]
+crbug.com/953847 [ Mac ] virtual/scroll_customization/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 crbug.com/953847 [ Mac ] virtual/threaded/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/threaded/fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/threaded/fast/scrolling/scrollbars/scrollbar-button-gesture-target.html [ Failure ]
+crbug.com/953847 [ Mac ] virtual/threaded/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 crbug.com/953847 [ Mac ] virtual/fractional_scrolling_threaded/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/fractional_scrolling_threaded/fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/fractional_scrolling_threaded/fast/scrolling/scrollbars/scrollbar-button-gesture-target.html [ Failure ]
+crbug.com/953847 [ Mac ] virtual/fractional_scrolling_threaded/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 crbug.com/953847 [ Mac ] virtual/main_thread_scrollbar_gestures/fast/scrolling/scrollbars/mouse-scrolling-on-div-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/main_thread_scrollbar_gestures/fast/scrolling/scrollbars/mouse-scrolling-on-root-scrollbar.html [ Failure ]
 crbug.com/953847 [ Mac ] virtual/main_thread_scrollbar_gestures/fast/scrolling/scrollbars/scrollbar-button-gesture-target.html [ Failure ]
+crbug.com/953847 [ Mac ] virtual/main_thread_scrollbar_gestures/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html [ Failure Timeout ]
 
 # Compositor threaded scrollbar scrolls parent of subscroller.
 # Note: even when fixed will still fail on Mac until crbug.com/953847
@@ -6112,43 +6118,6 @@
 crbug.com/964239 external/wpt/css/css-scroll-snap/scroll-margin.html [ Pass Failure ]
 crbug.com/965389 [ Mac ] media/track/track-cue-rendering-position-auto.html [ Pass Failure ]
 
-# TODO(crbug.com/943636): rebaseline and re-enable once https://chromium-review.googlesource.com/c/v8/v8/+/1593307 is rolled into chromium.
-crbug.com/943636 external/wpt/screen-orientation/lock-unlock-check.html [ Pass Failure ]
-crbug.com/943636 fast/dom/script-module-inline-error-gc.html [ Pass Failure ]
-crbug.com/943636 fast/dom/HTMLAnchorElement/anchor-ismap-crash.html [ Pass Failure ]
-crbug.com/943636 fast/dom/SelectorAPI/unknown-pseudo.html [ Pass Failure ]
-crbug.com/943636 fast/encoding/meta-in-script.html [ Pass Failure ]
-crbug.com/943636 fast/events/attribute-listener-deletion-crash.html [ Pass Failure ]
-crbug.com/943636 fast/events/set-attribute-listener-window-onerror-crash.html [ Pass Failure ]
-crbug.com/943636 fast/events/window-onerror-05.html [ Pass Failure ]
-crbug.com/943636 fast/js/postfix-syntax.html [ Pass Failure ]
-crbug.com/943636 fast/parser/entity-end-script-tag.html [ Pass Failure ]
-crbug.com/943636 fast/workers/worker-onerror-01.html [ Pass Failure ]
-crbug.com/943636 html5lib/generated/run-tests1-data.html [ Pass Failure ]
-crbug.com/943636 html5lib/generated/run-tests1-write.html [ Pass Failure ]
-crbug.com/943636 html5lib/generated/run-tests18-data.html [ Pass Failure ]
-crbug.com/943636 html5lib/generated/run-tests18-write.html [ Pass Failure ]
-crbug.com/943636 html5lib/generated/run-tests7-data.html [ Pass Failure ]
-crbug.com/943636 html5lib/generated/run-tests7-write.html [ Pass Failure ]
-crbug.com/966932 http/tests/devtools/a11y-axe-core/elements/main-tool-test.js [ Timeout Pass ]
-crbug.com/943636 http/tests/devtools/console/console-eval-object-literal.js [ Pass Failure ]
-crbug.com/943636 http/tests/devtools/console/console-link-to-snippet.js [ Pass Failure ]
-crbug.com/943636 http/tests/devtools/console/console-log-eval-syntax-error.js [ Pass Failure ]
-crbug.com/943636 http/tests/devtools/console/console-log-syntax-error.js [ Pass Failure ]
-crbug.com/943636 http/tests/devtools/console/console-message-from-inline-with-url.js [ Pass Failure ]
-crbug.com/943636 http/tests/devtools/sources/debugger/debugger-autocontinue-on-syntax-error.js [ Pass Failure ]
-crbug.com/943636 http/tests/devtools/sources/debugger/debugger-compile-and-run.js [ Pass Failure ]
-crbug.com/943636 inspector-protocol/runtime/runtime-callFunctionOn-async.js [ Pass Failure ]
-crbug.com/943636 inspector-protocol/runtime/runtime-runScript-async.js [ Pass Failure ]
-crbug.com/943636 paint/invalidation/svg/viewport-mask-update.html [ Pass Failure ]
-crbug.com/943636 security/lazy-event-listener.html [ Pass Failure ]
-crbug.com/943636 virtual/disable-blink-gen-property-trees/paint/invalidation/svg/viewport-mask-update.html [ Pass Failure ]
-crbug.com/943636 virtual/mouseevent_fractional/fast/events/attribute-listener-deletion-crash.html [ Pass Failure ]
-crbug.com/943636 virtual/mouseevent_fractional/fast/events/set-attribute-listener-window-onerror-crash.html [ Pass Failure ]
-crbug.com/943636 virtual/mouseevent_fractional/fast/events/window-onerror-05.html [ Pass Failure ]
-crbug.com/943636 virtual/omt-worker-fetch/fast/workers/worker-onerror-01.html [ Pass Failure ]
-crbug.com/943636 virtual/not-omt-sw-fetch/fast/workers/worker-onerror-01.html [ Pass Failure ]
-
 # Sheriff 2019-05-27
 crbug.com/942411 [ Win ] http/tests/devtools/network/network-search.js [ Pass Timeout ]
 
@@ -6243,3 +6212,6 @@
 crbug.com/979593 [ Linux Win ] virtual/blink-cors/external/wpt/service-workers/service-worker/registration-schedule-job.https.html [ Pass Failure ]
 crbug.com/979593 [ Linux Win ] virtual/not-omt-sw-fetch/external/wpt/service-workers/service-worker/registration-schedule-job.https.html [ Pass Failure ]
 crbug.com/979593 [ Linux Win ] virtual/omt-worker-fetch/external/wpt/service-workers/service-worker/registration-schedule-job.https.html [ Pass Failure ]
+
+# TODO(crbug.com/980588): reenable once WPT is fixed
+crbug.com/980588 external/wpt/screen-orientation/lock-unlock-check.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/device_orientation/orientation/absolute-fallback.html b/third_party/blink/web_tests/device_orientation/orientation/absolute-fallback.html
index 65f2b50..7b17a41 100644
--- a/third_party/blink/web_tests/device_orientation/orientation/absolute-fallback.html
+++ b/third_party/blink/web_tests/device_orientation/orientation/absolute-fallback.html
@@ -15,7 +15,7 @@
 
   // Make the relative orientation sensor unavailable and set mock data for
   // the absolute one.
-  sensorProvider.setGetSensorShouldFail(device.mojom.SensorType.RELATIVE_ORIENTATION_EULER_ANGLES, true);
+  sensorProvider.setGetSensorShouldFail('RelativeOrientationEulerAngles', true);
   setMockOrientationData(sensorProvider, orientationData);
   return waitForOrientation(orientationData);
 }, 'Tests that deviceorientation falls back to using absolute orientation data if relative is unavailable.');
diff --git a/third_party/blink/web_tests/device_orientation/resources/device-orientation-helpers.js b/third_party/blink/web_tests/device_orientation/resources/device-orientation-helpers.js
index 35e3f0e..747407e2 100644
--- a/third_party/blink/web_tests/device_orientation/resources/device-orientation-helpers.js
+++ b/third_party/blink/web_tests/device_orientation/resources/device-orientation-helpers.js
@@ -40,17 +40,17 @@
 function setMockMotionData(sensorProvider, motionData) {
   const degToRad = Math.PI / 180;
   return Promise.all([
-      setMockSensorDataForType(sensorProvider, device.mojom.SensorType.ACCELEROMETER, [
+      setMockSensorDataForType(sensorProvider, "Accelerometer", [
           nullToNan(motionData.accelerationIncludingGravityX),
           nullToNan(motionData.accelerationIncludingGravityY),
           nullToNan(motionData.accelerationIncludingGravityZ),
       ]),
-      setMockSensorDataForType(sensorProvider, device.mojom.SensorType.LINEAR_ACCELERATION, [
+      setMockSensorDataForType(sensorProvider, "LinearAccelerationSensor", [
           nullToNan(motionData.accelerationX),
           nullToNan(motionData.accelerationY),
           nullToNan(motionData.accelerationZ),
       ]),
-      setMockSensorDataForType(sensorProvider, device.mojom.SensorType.GYROSCOPE, [
+      setMockSensorDataForType(sensorProvider, "Gyroscope", [
           nullToNan(motionData.rotationRateAlpha) * degToRad,
           nullToNan(motionData.rotationRateBeta) * degToRad,
           nullToNan(motionData.rotationRateGamma) * degToRad,
@@ -60,8 +60,7 @@
 
 function setMockOrientationData(sensorProvider, orientationData) {
   let sensorType = orientationData.absolute
-      ? device.mojom.SensorType.ABSOLUTE_ORIENTATION_EULER_ANGLES
-      : device.mojom.SensorType.RELATIVE_ORIENTATION_EULER_ANGLES;
+      ? "AbsoluteOrientationEulerAngles" : "RelativeOrientationEulerAngles";
   return setMockSensorDataForType(sensorProvider, sensorType, [
       nullToNan(orientationData.beta),
       nullToNan(orientationData.gamma),
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 d4ae383c..f4e47aa 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
@@ -71061,6 +71061,174 @@
      {}
     ]
    ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-001.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-001.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-002.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-002.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-003.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-003.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-004.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-004.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-005.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-005.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-006.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-006.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-007.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-007.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-008.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-008.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-009.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-009.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-010.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-010.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-011.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-011.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-012.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-012.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-013.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-013.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-text/white-space/pre-wrap-leading-spaces-014.html": [
+    [
+     "css/css-text/white-space/pre-wrap-leading-spaces-014.html",
+     [
+      [
+       "/css/css-text/white-space/reference/white-space-break-spaces-005-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-text/white-space/tab-stop-threshold-001.html": [
     [
      "css/css-text/white-space/tab-stop-threshold-001.html",
@@ -162128,9 +162296,6 @@
    "pointerevents/resources/pointerevent_mouse_pointercapture-iframe.html": [
     []
    ],
-   "pointerevents/resources/pointerevent_mouse_pointercapture_inactivate_pointer-iframe.html": [
-    []
-   ],
    "pointerevents/resources/pointerevent_pointerId_scope-iframe.html": [
     []
    ],
@@ -162422,6 +162587,9 @@
    "presentation-api/receiving-ua/support/stash.py": [
     []
    ],
+   "printing/resources/destination.html": [
+    []
+   ],
    "priority-hints/META.yml": [
     []
    ],
@@ -196948,6 +197116,12 @@
      {}
     ]
    ],
+   "css/CSS2/linebox/inline-negative-margin-001.html": [
+    [
+     "css/CSS2/linebox/inline-negative-margin-001.html",
+     {}
+    ]
+   ],
    "css/CSS2/normal-flow/auto-margins-root-element.html": [
     [
      "css/CSS2/normal-flow/auto-margins-root-element.html",
@@ -246613,7 +246787,9 @@
    "layout-instability/observe-layout-shift.html": [
     [
      "layout-instability/observe-layout-shift.html",
-     {}
+     {
+      "testdriver": true
+     }
     ]
    ],
    "layout-instability/supported-layout-type.html": [
@@ -262354,14 +262530,6 @@
      }
     ]
    ],
-   "pointerevents/pointerevent_mouse_pointercapture_inactivate_pointer.html": [
-    [
-     "pointerevents/pointerevent_mouse_pointercapture_inactivate_pointer.html",
-     {
-      "testdriver": true
-     }
-    ]
-   ],
    "pointerevents/pointerevent_on_event_handlers.html": [
     [
      "pointerevents/pointerevent_on_event_handlers.html",
@@ -262744,9 +262912,9 @@
      }
     ]
    ],
-   "pointerevents/pointerlock/pointerevent_movementxy_when_locked.html": [
+   "pointerevents/pointerlock/pointerevent_movementxy_with_pointerlock.html": [
     [
-     "pointerevents/pointerlock/pointerevent_movementxy_when_locked.html",
+     "pointerevents/pointerlock/pointerevent_movementxy_with_pointerlock.html",
      {
       "testdriver": true
      }
@@ -263199,6 +263367,12 @@
      {}
     ]
    ],
+   "printing/print-microtask-after-navigate.html": [
+    [
+     "printing/print-microtask-after-navigate.html",
+     {}
+    ]
+   ],
    "priority-hints/fetch-api-request.tentative.any.js": [
     [
      "priority-hints/fetch-api-request.tentative.any.html",
@@ -327407,6 +327581,10 @@
    "80b72e080b7114fb0f9467272e174a9c76f6bb33",
    "reftest"
   ],
+  "css/CSS2/linebox/inline-negative-margin-001.html": [
+   "e8a00ec09bd798d45f110de2ef231fff9c102905",
+   "testharness"
+  ],
   "css/CSS2/linebox/leading-001-ref.xht": [
    "2d6e784d2ed2f6b66e4be72d9b6d261d63bc98e7",
    "support"
@@ -376751,6 +376929,62 @@
    "af29b0505e0eefbab09b011798c0dd6136598cca",
    "reftest"
   ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-001.html": [
+   "6d17921e17c28664533f3e091de9a8075770b544",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-002.html": [
+   "082bce78c4ff56dd4b5af1c60a51c04a0df070c2",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-003.html": [
+   "2bdef018e72250cd8672ea89fe16cb26971bad7a",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-004.html": [
+   "ea409af2ab9da5c6a651f0c937bba5905bcd7b7e",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-005.html": [
+   "ccf613051ffa661fc2ba60c563b5fba3d2b09f7d",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-006.html": [
+   "cff928c2fd537b4f2db6ed282882c9a14f795452",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-007.html": [
+   "ca27b98c2f68940287fc61d4f24709af196ced10",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-008.html": [
+   "7331142c2ea0cf56441bd98504610b6ae3150f79",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-009.html": [
+   "e2786419c1a56466fc3850fd371bdac45203fdf3",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-010.html": [
+   "b1b14ea3afbabbce55996cdcc47a9995a4c5e418",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-011.html": [
+   "6167e9ce4538f64baa0a81cd498838be3c759664",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-012.html": [
+   "657cd89d89fbbee229ed481d71b73766ff76c0b0",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-013.html": [
+   "476f76398daaaa962f09ad5e67f949b529bf8802",
+   "reftest"
+  ],
+  "css/css-text/white-space/pre-wrap-leading-spaces-014.html": [
+   "ab2759fdc7b094fa9f4012b557abc27fb6bf8ffd",
+   "reftest"
+  ],
   "css/css-text/white-space/reference/control-chars-000-ref.html": [
    "9d5fcb27147a8c53e410d08511cb5035b612f80c",
    "support"
@@ -409072,7 +409306,7 @@
    "testharness"
   ],
   "element-timing/invisible-images.html": [
-   "eb53cd7c2a692c76bb8220062d33edd3c3a48eef",
+   "06d9bfd07a0f342dbacd00c23bc36b155335f531",
    "testharness"
   ],
   "element-timing/multiple-background-images.html": [
@@ -437708,7 +437942,7 @@
    "testharness"
   ],
   "layout-instability/observe-layout-shift.html": [
-   "db8d3ae406d42b16be4308e329c53b6f0d7f944c",
+   "25e4950f6a7d830097781923e80d82f130cf23a5",
    "testharness"
   ],
   "layout-instability/resources/slow-image.py": [
@@ -437772,7 +438006,7 @@
    "testharness"
   ],
   "lint.whitelist": [
-   "c344c3a834444db4be24db1d0132c85a4b14395c",
+   "02b1bb0c78f9765257a35aed076701e2f0aa757e",
    "support"
   ],
   "loading/preloader-css-import-no-quote.tentative.html": [
@@ -450855,10 +451089,6 @@
    "83b4c1becc48339f74948fd01bdf15dfd27f96c1",
    "testharness"
   ],
-  "pointerevents/pointerevent_mouse_pointercapture_inactivate_pointer.html": [
-   "524d19eecebfce60e4fb1e4a74c2c1d20e0ba770",
-   "testharness"
-  ],
   "pointerevents/pointerevent_on_event_handlers.html": [
    "d8cfa7a0f4c482be606e4e98f9a7900a0340a477",
    "testharness"
@@ -450968,7 +451198,7 @@
    "support"
   ],
   "pointerevents/pointerevent_support.js": [
-   "9a491dd677d40749d5dde47dd2ed3b05b545a61d",
+   "ae9b55c43d2c884b882214924dc459ba516ff42b",
    "support"
   ],
   "pointerevents/pointerevent_suppress_compat_events_on_click.html": [
@@ -451071,8 +451301,8 @@
    "2d6147dae562af7efadacee9dfb44cc080005742",
    "testharness"
   ],
-  "pointerevents/pointerlock/pointerevent_movementxy_when_locked.html": [
-   "bdad97df04b2ca67fc1f92e256c979c137a4c66a",
+  "pointerevents/pointerlock/pointerevent_movementxy_with_pointerlock.html": [
+   "376d0e63a18e8610dd20bf2843e5e804721226ff",
    "testharness"
   ],
   "pointerevents/pointerlock/pointerevent_pointerlock_after_pointercapture.html": [
@@ -451111,10 +451341,6 @@
    "817c6123cf96b0e966c04a48414725d794549c77",
    "support"
   ],
-  "pointerevents/resources/pointerevent_mouse_pointercapture_inactivate_pointer-iframe.html": [
-   "d4b4af1fba1d0090ee87038af5fd686495d4cb38",
-   "support"
-  ],
   "pointerevents/resources/pointerevent_pointerId_scope-iframe.html": [
    "ab33560b35216ea0976d1c037650122d9336ae39",
    "support"
@@ -451883,6 +452109,14 @@
    "5bddc7160f279363cd9ff3368e9420447a773564",
    "support"
   ],
+  "printing/print-microtask-after-navigate.html": [
+   "b3f7f769daf243169e76871adda57a70d49d21a4",
+   "testharness"
+  ],
+  "printing/resources/destination.html": [
+   "00e89594b253bd45f21d15752d565d4d62b600b9",
+   "support"
+  ],
   "priority-hints/META.yml": [
    "487b4013a02209cf35be8c9d5099c4d7842ba915",
    "support"
@@ -461744,7 +461978,7 @@
    "support"
   ],
   "resources/chromium/webxr-test.js": [
-   "e7bb7e9fa8a2580ffa7cbffafe0145b17746568c",
+   "31bf916caf80bdbd8c2d4173ff6a22e661524b38",
    "support"
   ],
   "resources/chromium/webxr-test.js.headers": [
@@ -467032,7 +467266,7 @@
    "testharness"
   ],
   "sms/sms_provider.js": [
-   "eaa31ebea3113db8ecac51d6d0dec08d2d234444",
+   "e0689bbd82c6ae668030c167ea17da39e20e6367",
    "support"
   ],
   "sms/sms_receiver.idl": [
@@ -477528,7 +477762,7 @@
    "testharness"
   ],
   "web-nfc/NFCReader_options_mediaType-manual.https-expected.txt": [
-   "37557d299c8198ff566dd4e3e9a37cbfb14b1d95",
+   "129e4ce74bcb9d8296dd6d2223ae06d4acec807a",
    "support"
   ],
   "web-nfc/NFCReader_options_mediaType-manual.https.html": [
@@ -477536,7 +477770,7 @@
    "manual"
   ],
   "web-nfc/NFCReader_options_recordType_empty-manual.https-expected.txt": [
-   "ebfd316c80f2a0b469c92f13ad66b89d27f8ca28",
+   "438f4b6793f044b90d7614a099a29eb243096c7c",
    "support"
   ],
   "web-nfc/NFCReader_options_recordType_empty-manual.https.html": [
@@ -477544,7 +477778,7 @@
    "manual"
   ],
   "web-nfc/NFCReader_options_recordType_json-manual.https-expected.txt": [
-   "363ded8f7c6d590dd42a08e24f839bd5e7bf25e0",
+   "a8f75b34c373b47bf1ac65de29bf802ffcab0012",
    "support"
   ],
   "web-nfc/NFCReader_options_recordType_json-manual.https.html": [
@@ -477552,7 +477786,7 @@
    "manual"
   ],
   "web-nfc/NFCReader_options_recordType_opaque-manual.https-expected.txt": [
-   "84dd5c07528c2eeec9c74e3fb4fac30596dc35b7",
+   "bd09f8bd971319c6a39dde64c993c4f21339d269",
    "support"
   ],
   "web-nfc/NFCReader_options_recordType_opaque-manual.https.html": [
@@ -477560,7 +477794,7 @@
    "manual"
   ],
   "web-nfc/NFCReader_options_recordType_text-manual.https-expected.txt": [
-   "0eee75285691f994dc549d7ff42aceec5a978a9e",
+   "b2b18c5455fef22f83b55bf9657210fcd214a0ad",
    "support"
   ],
   "web-nfc/NFCReader_options_recordType_text-manual.https.html": [
@@ -477568,7 +477802,7 @@
    "manual"
   ],
   "web-nfc/NFCReader_options_recordType_url-manual.https-expected.txt": [
-   "980da2a18eac37e20e9c8f53e5661dececd822c8",
+   "f53c2e72e5af9901a5cd3d896fe92c99b5622918",
    "support"
   ],
   "web-nfc/NFCReader_options_recordType_url-manual.https.html": [
@@ -477576,7 +477810,7 @@
    "manual"
   ],
   "web-nfc/NFCReader_options_url-manual.https-expected.txt": [
-   "97cc23738ebe96d5e6e719719465ee9c716653cc",
+   "fdf8c72499da615f36431e0f5b1e3bfa869ea3df",
    "support"
   ],
   "web-nfc/NFCReader_options_url-manual.https.html": [
@@ -477592,7 +477826,7 @@
    "testharness"
   ],
   "web-nfc/NFCWriter_push.https-expected.txt": [
-   "abf8d15019abb2708db843eaaf9829196f31c05d",
+   "7495de78b148dd6ebcd3c3bc2ba9a315c326dbb0",
    "support"
   ],
   "web-nfc/NFCWriter_push.https.html": [
@@ -477600,7 +477834,7 @@
    "testharness"
   ],
   "web-nfc/NFCWriter_push_signal-manual.https-expected.txt": [
-   "e1a2b1f1d34746fb0d79d9ff03a05ede46f424c7",
+   "27dd82df88a4bb43fc02733183e89d6ce57b337d",
    "support"
   ],
   "web-nfc/NFCWriter_push_signal-manual.https.html": [
@@ -477612,7 +477846,7 @@
    "support"
   ],
   "web-nfc/idlharness.https.window-expected.txt": [
-   "797a5819e71a32f722ec5d877672d083c5c58bd2",
+   "975bd5e1d9d8a8207be992fc646ea1938ce8b8b7",
    "support"
   ],
   "web-nfc/idlharness.https.window.js": [
@@ -477620,7 +477854,7 @@
    "testharness"
   ],
   "web-nfc/nfc_hw_disabled-manual.https-expected.txt": [
-   "104d42a9e9a0b5d82ab2b21d9d701dd0a27b9dfa",
+   "21c4cb1c3cdb39147f12b88075f0a85a889f3994",
    "support"
   ],
   "web-nfc/nfc_hw_disabled-manual.https.html": [
@@ -477632,7 +477866,7 @@
    "testharness"
   ],
   "web-nfc/nfc_push_ArrayBuffer-manual.https-expected.txt": [
-   "5d17eaffffb98814dcd28399c8cc03fe608b2d7c",
+   "9f00a673b4f983c6c0a1d1222dd382108836b6a6",
    "support"
   ],
   "web-nfc/nfc_push_ArrayBuffer-manual.https.html": [
@@ -477640,7 +477874,7 @@
    "manual"
   ],
   "web-nfc/nfc_push_DOMString-manual.https-expected.txt": [
-   "a2ba63b629239d47ee8074afc28c8cfe91bc2a79",
+   "29791a39dd73e276d92febd8ea82f467e73b6a64",
    "support"
   ],
   "web-nfc/nfc_push_DOMString-manual.https.html": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html
new file mode 100644
index 0000000..6d17921
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: breaking opportunities at leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space at the beginning of the line are breaking opportunities when white-space is pre-wrap.">
+
+<style>
+div {
+  font: 50px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 2ch;
+  white-space: pre-wrap;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref"><span>XX</span><br>XX</div>
+<div class="test"> XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html
new file mode 100644
index 0000000..082bce7
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: breaking opportunities at leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
+
+<style>
+div {
+  font: 25px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 4ch;
+  white-space: pre-wrap;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<span>XX<br>XXXX</span><br>XXXX<br><span>XXXX</span></div>
+<div class="test">XX&#x000a; XXXX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html
new file mode 100644
index 0000000..2bdef01
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: breaking opportunities at leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
+
+<style>
+div {
+  font: 25px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 4ch;
+  white-space: pre-wrap;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<span>XX<br>XXXX</span><br>XXXX<br><span>XXXX</span></div>
+<div class="test">XX<br> XXXX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html
new file mode 100644
index 0000000..ea409af
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XXX<span>XX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX<br>XXXXX</span></div>
+<div class="test">XXX<br>   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html
new file mode 100644
index 0000000..ccf61305
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XXX<span>XX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX<br>XXXXX</span></div>
+<div class="test">XXX  <br>   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html
new file mode 100644
index 0000000..cff928c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XXX<span>XX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX<br>XXXXX</span></div>
+<div class="test">XXX 
+   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html
new file mode 100644
index 0000000..ca27b98
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XXX<span>XX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX<br>XXXXX</span></div>
+<div class="test">XXX  
+   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html
new file mode 100644
index 0000000..7331142
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XX<span>XXX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX<br>XXXXX</span></div>
+<div class="test">XX   
+   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html
new file mode 100644
index 0000000..e278641
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">X<span>XXXX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX<br>XXXXX</span></div>
+<div class="test">X    
+   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html
new file mode 100644
index 0000000..b1b14ea
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XXX<span>XX<br>XXXXX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX</span></div>
+<div class="test">XXX<br>
+   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html
new file mode 100644
index 0000000..6167e9ce
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: forced breaks create preserverd leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref">XXX<span>XX</span><br>XXX<span>XX<br>XXX</span>XX<br><span>XXXXX<br>XXXXX</span></div>
+<div class="test">XXX XXX
+   XX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html
new file mode 100644
index 0000000..657cd89d
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: breaking opportunities at leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
+
+<style>
+div {
+  font: 25px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 4ch;
+  white-space: pre-wrap;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref"><span>X</span>X<span>XX<br>XXXX</span><br>XXXX<br><span>XXXX</span></div>
+<div class="test"> <span>X </span>&#x000a; XXXX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html
new file mode 100644
index 0000000..476f763
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: breaking opportunities at leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
+
+<style>
+div {
+  font: 25px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 4ch;
+  white-space: pre-wrap;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref"><span>XX</span>X<span>X<br>XXXX</span><br>XXXX<br><span>XXXX</span></div>
+<div class="test"> <span> </span>X&#x000a; XXXX</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html
new file mode 100644
index 0000000..ab2759fd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>CSS Text test: breaking opportunities at leading spaces with white-space:pre-wrap</title>
+<link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-property">
+<link rel="help" href="https://drafts.csswg.org/css-text-3/#valdef-white-space-pre-wrap">
+<link rel="match" href="reference/white-space-break-spaces-005-ref.html">
+<meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
+
+<style>
+div {
+  font: 20px/1 Ahem;
+}
+.ref {
+  position: absolute;
+  color: red;
+  z-index: -1;
+}
+span { color: green; }
+.test {
+  color: green;
+  width: 5ch;
+  white-space: pre-wrap;
+}
+</style>
+
+<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
+<div class="ref"><span>X</span>X<span>XX</span>X<br><span>XXXXX</span><br>XXXXX<br><span>XXXXX<br>XXXXX</span></div>
+<div class="test"> X<span> </span> X&#x000a; XXXXX</div>
diff --git a/third_party/blink/web_tests/external/wpt/lint.whitelist b/third_party/blink/web_tests/external/wpt/lint.whitelist
index c344c3a..02b1bb0c 100644
--- a/third_party/blink/web_tests/external/wpt/lint.whitelist
+++ b/third_party/blink/web_tests/external/wpt/lint.whitelist
@@ -28,6 +28,10 @@
 TRAILING WHITESPACE: WebIDL/*
 TRAILING WHITESPACE: webvtt/*
 TRAILING WHITESPACE: server-timing/resources/parsing/*.sub.headers
+TRAILING WHITESPACE: css/css-text/white-space/pre-wrap-leading-spaces-006.html
+TRAILING WHITESPACE: css/css-text/white-space/pre-wrap-leading-spaces-007.html
+TRAILING WHITESPACE: css/css-text/white-space/pre-wrap-leading-spaces-008.html
+TRAILING WHITESPACE: css/css-text/white-space/pre-wrap-leading-spaces-009.html
 
 ## File types that should never be checked ##
 
diff --git a/third_party/blink/web_tests/external/wpt/printing/print-microtask-after-navigate.html b/third_party/blink/web_tests/external/wpt/printing/print-microtask-after-navigate.html
new file mode 100644
index 0000000..b3f7f769
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/printing/print-microtask-after-navigate.html
@@ -0,0 +1,22 @@
+<!doctype HTML>
+<meta charset=utf-8>
+<title>Printing in microtask after navigation</title>
+<link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#printing">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+Passes if it does not crash.
+<script>
+  let print_promise = new Promise(function(resolve, reject) {
+    resolve();
+  });
+
+  // Call print in a microtask. This will execute after the navigation click
+  // has happened, which will cause the document to become neutered and
+  // ineligible for the print command.
+  print_promise.then(() => print());
+</script>
+ <a href="resources/destination.html">
+<script>
+  document.getElementsByTagName("a")[0].click();
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/printing/resources/destination.html b/third_party/blink/web_tests/external/wpt/printing/resources/destination.html
new file mode 100644
index 0000000..00e8959
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/printing/resources/destination.html
@@ -0,0 +1,8 @@
+<!doctype HTML>
+<link rel="author" title="Chris Harrelson" href="mailto:chrishtr@chromium.org">
+Passes if it does not crash.
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  test(()=> {}, "Did not crash");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/resize-observer/observe.html b/third_party/blink/web_tests/external/wpt/resize-observer/observe.html
index ab7521c..7360264 100644
--- a/third_party/blink/web_tests/external/wpt/resize-observer/observe.html
+++ b/third_party/blink/web_tests/external/wpt/resize-observer/observe.html
@@ -125,12 +125,12 @@
 }
 
 function test5() {
-  let img = createAndAppendElement("img");
+  const img = new Image();
   img.style.width = "15px";
   img.style.height = "15px";
   img.src = "resources/image.png";
 
-  var helper = new ResizeTestHelper("test5: observe img",[
+  let helper = new ResizeTestHelper("test5: observe img",[
     {
       setup: observer => {
         observer.observe(img);
@@ -148,8 +148,15 @@
       }
     }
   ]);
-  return new Promise((resolve, reject) => {
-    img.onload = () => resolve();
+  return img.decode().then(() => {
+    return new Promise(resolve => {
+      requestAnimationFrame(() => {
+        document.body.appendChild(img);
+        resolve();
+      });
+    });
+  }).catch(error => {
+    assert_unreached("decode image failed");
   }).then(() => {
     return helper.start(() => img.remove());
   });
diff --git a/third_party/blink/web_tests/external/wpt/resources/chromium/webxr-test.js b/third_party/blink/web_tests/external/wpt/resources/chromium/webxr-test.js
index e7bb7e9..31bf916 100644
--- a/third_party/blink/web_tests/external/wpt/resources/chromium/webxr-test.js
+++ b/third_party/blink/web_tests/external/wpt/resources/chromium/webxr-test.js
@@ -259,7 +259,7 @@
           leftDegrees: 50.899,
           rightDegrees: 35.197
         },
-        offset: new gfx.mojom.Vector3dF(-0.032, 0, 0),
+        offset: { x: -0.032, y: 0, z: 0 },
         renderWidth: 20,
         renderHeight: 20
       },
@@ -270,7 +270,7 @@
           leftDegrees: 50.899,
           rightDegrees: 35.197
         },
-        offset: new gfx.mojom.Vector3dF(0.032, 0, 0),
+        offset: { x: 0.032, y: 0, z: 0 },
         renderWidth: 20,
         renderHeight: 20
       },
@@ -303,7 +303,7 @@
         leftDegrees: toDegrees(leftTan),
         rightDegrees: toDegrees(rightTan)
       },
-      offset: new gfx.mojom.Vector3dF(0, 0, 0),
+      offset: { x: 0, y: 0, z: 0 },
       renderWidth: 20,
       renderHeight: 20
     };
diff --git a/third_party/blink/web_tests/external/wpt/sms/sms_provider.js b/third_party/blink/web_tests/external/wpt/sms/sms_provider.js
index eaa31ebe..e0689bb 100644
--- a/third_party/blink/web_tests/external/wpt/sms/sms_provider.js
+++ b/third_party/blink/web_tests/external/wpt/sms/sms_provider.js
@@ -61,7 +61,7 @@
       blink.mojom.SmsReceiver.$interfaceName);
   interceptor.oninterfacerequest = (e) => {
     let impl = new blink.mojom.SmsReceiver(provider);
-    impl.bindHandle(e.handle);
+    impl.$.bindHandle(e.handle);
   }
 
   interceptor.start();
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_mediaType-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_mediaType-manual.https-expected.txt
index 37557d299..129e4ce 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_mediaType-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_mediaType-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that the mediaType of NFCReaderOptions filters relevant data sources correctly. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that the mediaType of NFCReaderOptions filters relevant data sources correctly. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_empty-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_empty-manual.https-expected.txt
index ebfd316c8..438f4b6 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_empty-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_empty-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'empty'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'empty'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_json-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_json-manual.https-expected.txt
index 363ded8..a8f75b3 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_json-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_json-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'json'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'json'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_opaque-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_opaque-manual.https-expected.txt
index 84dd5c07..bd09f8bd 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_opaque-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_opaque-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'opaque'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'opaque'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_text-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_text-manual.https-expected.txt
index 0eee752..b2b18c54 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_text-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_text-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'text'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'text'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_url-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_url-manual.https-expected.txt
index 980da2a..f53c2e72 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_url-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_recordType_url-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'url'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that write and read data succeed when NFCReaderOptions's recordType is set to 'url'. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_url-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_url-manual.https-expected.txt
index 97cc2373..fdf8c72 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_url-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCReader_options_url-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that the url of NFCReaderOptions filters relevant data sources correctly. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that the url of NFCReaderOptions filters relevant data sources correctly. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https-expected.txt
index abf8d150..e7939625 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https-expected.txt
@@ -1,16 +1,16 @@
 This is a testharness.js-based test.
-FAIL Test that promise is rejected with TypeError if NDEFMessageSource is invalid. NFCWriter is not defined
-FAIL 'Test that promise is rejected with SyntaxError if NDEFMessageSource contains invalid records. NFCWriter is not defined
-FAIL NFCWriter.push should fail if abort push request before push happends. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
-FAIL NFCWriter.push should fail if signal's aborted flag is set. NFCWriter is not defined
-FAIL NFCWriter.push should fail if signal is not an AbortSignal. NFCWriter is not defined
-FAIL NFCWriter.push should fail with TypeError when invalid timeout is provided. NFCWriter is not defined
-FAIL NFCWriter.push should fail with TypeError when invalid negative timeout value is provided. NFCWriter is not defined
-FAIL NFCWriter.push should fail with TimeoutError when timer expires. NFCWriter is not defined
-FAIL Reject promise with NotSupportedError if NFC message size exceeds 32KB. NFCWriter is not defined
-FAIL Reject promise with SyntaxError if WebNFC Id cannot be created from provided URL. NFCWriter is not defined
-FAIL Reject promise with SyntaxError if 'json' record cannot be serialized. NFCWriter is not defined
-FAIL NFCWriter.push should fail with TypeError when invalid target value is provided. NFCWriter is not defined
-FAIL Test that WebNFC API is not accessible from iframe context. promise_test: Unhandled rejection with value: undefined
+PASS Test that promise is rejected with TypeError if NDEFMessageSource is invalid.
+PASS 'Test that promise is rejected with SyntaxError if NDEFMessageSource contains invalid records.
+FAIL NFCWriter.push should fail if abort push request before push happends. assert_throws: function "function() { throw e }" threw object "NotReadableError: No NFC adapter or cannot establish connection." that is not a DOMException AbortError: property "code" is equal to 0, expected 20
+FAIL NFCWriter.push should fail if signal's aborted flag is set. assert_throws: function "function() { throw e }" threw object "NotReadableError: No NFC adapter or cannot establish connection." that is not a DOMException AbortError: property "code" is equal to 0, expected 20
+FAIL NFCWriter.push should fail if signal is not an AbortSignal. Test bug: unrecognized DOMException code "TypeError" passed to assert_throws()
+PASS NFCWriter.push should fail with TypeError when invalid timeout is provided.
+PASS NFCWriter.push should fail with TypeError when invalid negative timeout value is provided.
+FAIL NFCWriter.push should fail with TimeoutError when timer expires. assert_throws: function "function() { throw e }" threw object "NotReadableError: No NFC adapter or cannot establish connection." that is not a DOMException TimeoutError: property "code" is equal to 0, expected 23
+PASS Reject promise with NotSupportedError if NFC message size exceeds 32KB.
+PASS Reject promise with SyntaxError if WebNFC Id cannot be created from provided URL.
+PASS Reject promise with SyntaxError if 'json' record cannot be serialized.
+PASS NFCWriter.push should fail with TypeError when invalid target value is provided.
+PASS Test that WebNFC API is not accessible from iframe context.
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https.html b/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https.html
index f5bbfcd..5a14b409 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https.html
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push.https.html
@@ -184,7 +184,7 @@
                         if (message.data === "Ready") {
                           const onSuccess = () => { parent.postMessage("Failure", "*"); };
                           const onError = error => {
-                            if (error.name == "SecurityError") {
+                            if (error.name == "NotAllowedError") {
                               parent.postMessage("Success", "*");
                             } else {
                               parent.postMessage("Failure", "*");
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push_signal-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push_signal-manual.https-expected.txt
index e1a2b1f1..27dd82d 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push_signal-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/NFCWriter_push_signal-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Synchronously signaled abort. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Synchronously signaled abort. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/idlharness.https.window-expected.txt
index 797a5819..975bd5e 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/idlharness.https.window-expected.txt
@@ -1,17 +1,17 @@
 This is a testharness.js-based test.
-Found 50 tests; 11 PASS, 39 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 50 tests; 22 PASS, 28 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS idl_test setup
-FAIL NFCWriter interface: existence and properties of interface object assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter interface object length assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter interface object name assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter interface: existence and properties of interface prototype object assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter interface: existence and properties of interface prototype object's "constructor" property assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter interface: existence and properties of interface prototype object's @@unscopables property assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter interface: operation push(NDEFMessageSource, NFCPushOptions) assert_own_property: self does not have own property "NFCWriter" expected property "NFCWriter" missing
-FAIL NFCWriter must be primary interface of new NFCWriter(); assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: NFCWriter is not defined"
-FAIL Stringification of new NFCWriter(); assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: NFCWriter is not defined"
-FAIL NFCWriter interface: new NFCWriter(); must inherit property "push(NDEFMessageSource, NFCPushOptions)" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: NFCWriter is not defined"
-FAIL NFCWriter interface: calling push(NDEFMessageSource, NFCPushOptions) on new NFCWriter(); with too few arguments must throw TypeError assert_equals: Unexpected exception when evaluating object expected null but got object "ReferenceError: NFCWriter is not defined"
+PASS NFCWriter interface: existence and properties of interface object
+PASS NFCWriter interface object length
+PASS NFCWriter interface object name
+PASS NFCWriter interface: existence and properties of interface prototype object
+PASS NFCWriter interface: existence and properties of interface prototype object's "constructor" property
+PASS NFCWriter interface: existence and properties of interface prototype object's @@unscopables property
+PASS NFCWriter interface: operation push(NDEFMessageSource, NFCPushOptions)
+PASS NFCWriter must be primary interface of new NFCWriter();
+PASS Stringification of new NFCWriter();
+PASS NFCWriter interface: new NFCWriter(); must inherit property "push(NDEFMessageSource, NFCPushOptions)" with the proper type
+PASS NFCWriter interface: calling push(NDEFMessageSource, NFCPushOptions) on new NFCWriter(); with too few arguments must throw TypeError
 FAIL NFCReader interface: existence and properties of interface object assert_own_property: self does not have own property "NFCReader" expected property "NFCReader" missing
 FAIL NFCReader interface object length assert_own_property: self does not have own property "NFCReader" expected property "NFCReader" missing
 FAIL NFCReader interface object name assert_own_property: self does not have own property "NFCReader" expected property "NFCReader" missing
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/nfc_hw_disabled-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/nfc_hw_disabled-manual.https-expected.txt
index 104d42a..fd955dd5 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/nfc_hw_disabled-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/nfc_hw_disabled-manual.https-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
 FAIL NFCReader.start should fail if NFC HW is disabled. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
-FAIL NFCWriter.push should fail when NFC HW is disabled. NFCWriter is not defined
+PASS NFCWriter.push should fail when NFC HW is disabled.
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_ArrayBuffer-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_ArrayBuffer-manual.https-expected.txt
index 5d17eaf..9f00a673b 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_ArrayBuffer-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_ArrayBuffer-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that NFCWriter.push succeeds when message is ArrayBuffer. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that NFCWriter.push succeeds when message is ArrayBuffer. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_DOMString-manual.https-expected.txt b/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_DOMString-manual.https-expected.txt
index a2ba63b..29791a39 100644
--- a/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_DOMString-manual.https-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-nfc/nfc_push_DOMString-manual.https-expected.txt
@@ -1,4 +1,4 @@
 This is a testharness.js-based test.
-FAIL Test that NFCWriter.push succeeds when message is DOMString. promise_test: Unhandled rejection with value: object "ReferenceError: NFCWriter is not defined"
+FAIL Test that NFCWriter.push succeeds when message is DOMString. promise_test: Unhandled rejection with value: object "ReferenceError: NFCReader is not defined"
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/anchor-ismap-crash.html b/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/anchor-ismap-crash.html
index b80bf37..c1217ac 100644
--- a/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/anchor-ismap-crash.html
+++ b/third_party/blink/web_tests/fast/dom/HTMLAnchorElement/anchor-ismap-crash.html
@@ -12,6 +12,6 @@
   //This error is because of "javascript:" url is not expected in case of ismap
   //This can not be caught in try-catch block as the click() execution could
   //finish before Syntax error was thrown from '<a href="javascript:">'
-  assert_equals(message, 'Uncaught SyntaxError: Unexpected token ?');
+  assert_equals(message, 'Uncaught SyntaxError: Unexpected token \'?\'');
 });
 </script>
diff --git a/third_party/blink/web_tests/fast/dom/SelectorAPI/unknown-pseudo-expected.txt b/third_party/blink/web_tests/fast/dom/SelectorAPI/unknown-pseudo-expected.txt
index f9888c0a..eca3f0866 100644
--- a/third_party/blink/web_tests/fast/dom/SelectorAPI/unknown-pseudo-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/SelectorAPI/unknown-pseudo-expected.txt
@@ -1,3 +1,3 @@
-PASS: document.querySelector(:unknownpseudo) throws: SyntaxError: Unexpected token :
-PASS: document.querySelector(:-webkit-any(:unknownpseudo)) throws: SyntaxError: Unexpected token :
+PASS: document.querySelector(:unknownpseudo) throws: SyntaxError: Unexpected token ':'
+PASS: document.querySelector(:-webkit-any(:unknownpseudo)) throws: SyntaxError: Unexpected token ':'
 
diff --git a/third_party/blink/web_tests/fast/dom/script-module-inline-error-gc-expected.txt b/third_party/blink/web_tests/fast/dom/script-module-inline-error-gc-expected.txt
index 4e30633..1d7d05c6 100644
--- a/third_party/blink/web_tests/fast/dom/script-module-inline-error-gc-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/script-module-inline-error-gc-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 12: Uncaught SyntaxError: Unexpected token %
+CONSOLE ERROR: line 12: Uncaught SyntaxError: Unexpected token '%'
 This test pass if no crash.
diff --git a/third_party/blink/web_tests/fast/encoding/meta-in-script-expected.txt b/third_party/blink/web_tests/fast/encoding/meta-in-script-expected.txt
index faff75b..af684c74 100644
--- a/third_party/blink/web_tests/fast/encoding/meta-in-script-expected.txt
+++ b/third_party/blink/web_tests/fast/encoding/meta-in-script-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 4: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 4: Uncaught SyntaxError: Unexpected token '<'
 PASS: windows-1255
 
 
diff --git a/third_party/blink/web_tests/fast/events/attribute-listener-deletion-crash-expected.txt b/third_party/blink/web_tests/fast/events/attribute-listener-deletion-crash-expected.txt
index 6d66135..24844bd 100644
--- a/third_party/blink/web_tests/fast/events/attribute-listener-deletion-crash-expected.txt
+++ b/third_party/blink/web_tests/fast/events/attribute-listener-deletion-crash-expected.txt
@@ -1,21 +1,21 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token |
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '|'
 PASS
diff --git a/third_party/blink/web_tests/fast/events/set-attribute-listener-window-onerror-crash-expected.txt b/third_party/blink/web_tests/fast/events/set-attribute-listener-window-onerror-crash-expected.txt
index 472edbf..09d4c26 100644
--- a/third_party/blink/web_tests/fast/events/set-attribute-listener-window-onerror-crash-expected.txt
+++ b/third_party/blink/web_tests/fast/events/set-attribute-listener-window-onerror-crash-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 16: Uncaught SyntaxError: Unexpected token ;
+CONSOLE ERROR: line 16: Uncaught SyntaxError: Unexpected token ';'
 Test passes if it does not crash.
diff --git a/third_party/blink/web_tests/fast/events/window-onerror-05-expected.txt b/third_party/blink/web_tests/fast/events/window-onerror-05-expected.txt
index d0aa6a9..4a5598d8 100644
--- a/third_party/blink/web_tests/fast/events/window-onerror-05-expected.txt
+++ b/third_party/blink/web_tests/fast/events/window-onerror-05-expected.txt
@@ -1,6 +1,6 @@
-window.onerror: "Uncaught SyntaxError: Unexpected token )" at window-onerror-05.html (Line: 15, Column: 10)
+window.onerror: "Uncaught SyntaxError: Unexpected token ')'" at window-onerror-05.html (Line: 15, Column: 10)
 Stack Trace:
-SyntaxError: Unexpected token )
+SyntaxError: Unexpected token ')'
 
 Returning 'true': the error should not be reported in the console as an unhandled exception.
 
diff --git a/third_party/blink/web_tests/fast/js/postfix-syntax-expected.txt b/third_party/blink/web_tests/fast/js/postfix-syntax-expected.txt
index 1a0e561..37012dd 100644
--- a/third_party/blink/web_tests/fast/js/postfix-syntax-expected.txt
+++ b/third_party/blink/web_tests/fast/js/postfix-syntax-expected.txt
@@ -14,7 +14,7 @@
 PASS ((window["x"]))++ is 9
 PASS (y, x)++ threw exception ReferenceError: Invalid left-hand side expression in postfix operation.
 PASS (true ? x : y)++ threw exception ReferenceError: Invalid left-hand side expression in postfix operation.
-PASS x++++ threw exception SyntaxError: Unexpected token ++.
+PASS x++++ threw exception SyntaxError: Unexpected token '++'.
 PASS x is 0
 PASS y is 0
 PASS successfullyParsed is true
diff --git a/third_party/blink/web_tests/fast/parser/entity-end-script-tag-expected.txt b/third_party/blink/web_tests/fast/parser/entity-end-script-tag-expected.txt
index c0a7158..5f8fbef 100644
--- a/third_party/blink/web_tests/fast/parser/entity-end-script-tag-expected.txt
+++ b/third_party/blink/web_tests/fast/parser/entity-end-script-tag-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE ERROR: line 11: Uncaught SyntaxError: Unexpected token &
+CONSOLE ERROR: line 11: Uncaught SyntaxError: Unexpected token '&'
 Test parsing of entity-escaped </script> tag for Bug 7931: Escaped elements within a textarea block can cause the textarea box to be closed prematurely
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
diff --git a/third_party/blink/web_tests/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html b/third_party/blink/web_tests/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html
new file mode 100644
index 0000000..57a27240
--- /dev/null
+++ b/third_party/blink/web_tests/fast/scrolling/scrollbars/mouse-autoscrolling-on-scrollbar.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>Tests mouse autoscroll interactions on a non-custom composited div scrollbar.</title>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script src="../../../resources/gesture-util.js"></script>
+<script src="../../../resources/scrollbar-util.js"></script>
+<style>
+.appearance {
+  width: 100px;
+  height: 100px;
+  overflow: scroll;
+  border: 1px solid black;
+}
+.location {
+  position: absolute;
+  top: 100px;
+  left: 100px;
+}
+.fast {
+  will-change: transform;
+}
+.space {
+  height: 2000px;
+  width: 2000px;
+}
+
+</style>
+
+<!-- Composited non-custom fast scroller -->
+<div id="scroller" class="appearance location fast">
+  <div class="space"></div>
+</div>
+
+<script>
+  // Turn off animated scrolling. The "conditionHolds" API expects the scrollTop to
+  // *not* change for 10 frames. This will fail since the last GSU would still be
+  // animating if animated scrolling were on.
+  if (window.internals)
+    internals.settings.setScrollAnimatorEnabled(false);
+
+  const scroller = document.getElementById("scroller");
+  const scrollerRect = scroller.getBoundingClientRect();
+
+  const TRACK_WIDTH = calculateScrollbarThickness();
+  const BUTTON_WIDTH = TRACK_WIDTH;
+  const SCROLL_CORNER = TRACK_WIDTH;
+  const SCROLL_DELTA = 400;
+  const MAX_SCROLLER_OFFSET = 1915;
+  const PRESS_DURATION = 1000;
+  const down_arrow_x = scrollerRect.right - BUTTON_WIDTH / 2;
+  const down_arrow_y = scrollerRect.bottom - SCROLL_CORNER - BUTTON_WIDTH / 2;
+
+  promise_test (async () => {
+    await waitForCompositorCommit();
+    scroller.scrollTop = 0;
+
+    // TODO(arakeri): Split the mousePressOn API calls to mouseDownAt, waitFor
+    // and mouseUpAt once crbug.com/979408 is fixed.
+    await mousePressOn(down_arrow_x, down_arrow_y, PRESS_DURATION);
+    const err = `Autoscroll down failed (scroller.scrollTop = ${scroller.scrollTop})`;
+
+    // Verify that autoscroll happened.
+    assert_greater_than(scroller.scrollTop, SCROLL_DELTA, err);
+
+    // Since autoscroll for arrows happens at 800 px per second, verify that the
+    // scrollTop has not reached the end.
+    assert_less_than(scroller.scrollTop, MAX_SCROLLER_OFFSET, "Reached scroller end.");
+
+    await waitForCompositorCommit();
+    const current_offset = scroller.scrollTop;
+    await conditionHolds(() => { return parseInt(scroller.scrollTop) == parseInt(current_offset); },
+    `scroller.scrollTop = ${scroller.scrollTop} current_offset = ${current_offset}`);
+  },"Test autoscroll down and autoscroll stop.");
+</script>
diff --git a/third_party/blink/web_tests/fast/text/whitespace/pre-wrap-spaces-after-newline.html b/third_party/blink/web_tests/fast/text/whitespace/pre-wrap-spaces-after-newline.html
deleted file mode 100644
index 3c632d4..0000000
--- a/third_party/blink/web_tests/fast/text/whitespace/pre-wrap-spaces-after-newline.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<style>
-    pre { white-space: pre-wrap; background: silver; width: 7ex; }
-</style>
-<p>
-    Test for
-    <i><a href="https://bugs.webkit.org/show_bug.cgi?id=7216">http://bugzilla.opendarwin.org/show_bug.cgi?id=7216</a>
-    white-space: pre-wrap collapses leading whitespace following a newline</i>.
-</p>
-<hr>
-<p>Here <code>bar</code> should be right under <code>foo</code>:</p>
-<pre>foo      bar</pre>
-<p>Here <code>bar</code> should be on the right hand side of the second line:</p>
-<pre>foo<br>   bar</pre>
-<pre>foo    <br>   bar</pre>
-<pre>foo
-   bar</pre>
-<pre>foo 
-   bar</pre>
-<pre>foo  
-   bar</pre>
-<pre>foo   
-   bar</pre>
-<pre>foo    
-   bar</pre>
-<p>Here <code>bar</code> should be on the right hand side of the third line:</p>
-<pre>foo<br>
-   bar</pre>
-<pre>foo baz
-   bar</pre>
diff --git a/third_party/blink/web_tests/fast/workers/worker-onerror-01-expected.txt b/third_party/blink/web_tests/fast/workers/worker-onerror-01-expected.txt
index 0d1faef1..d4d30876 100644
--- a/third_party/blink/web_tests/fast/workers/worker-onerror-01-expected.txt
+++ b/third_party/blink/web_tests/fast/workers/worker-onerror-01-expected.txt
@@ -4,7 +4,7 @@
 
 
 Page-level worker.onerror handler triggered:
-PASS errorEvent.message is "Uncaught SyntaxError: Unexpected token }"
+PASS errorEvent.message is "Uncaught SyntaxError: Unexpected token '}'"
 PASS stripURL(errorEvent.filename) is "[blob: URL]"
 PASS errorEvent.lineno is 2
 PASS errorEvent.colno is 9
diff --git a/third_party/blink/web_tests/fast/workers/worker-onerror-01.html b/third_party/blink/web_tests/fast/workers/worker-onerror-01.html
index 99233f61..39d8b86 100644
--- a/third_party/blink/web_tests/fast/workers/worker-onerror-01.html
+++ b/third_party/blink/web_tests/fast/workers/worker-onerror-01.html
@@ -16,7 +16,7 @@
         description("This tests that invalid syntax triggers 'worker.onerror'.");
 
         checkErrorEventInHandler({
-            message: "Uncaught SyntaxError: Unexpected token }",
+            message: "Uncaught SyntaxError: Unexpected token '}'",
             filename: "[blob: URL]",
             lineno: 2,
             colno: 9,
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt
index b59a26f..7054549 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests1-data-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
 ../resources/tests1.dat: PASS
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests1-write-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests1-write-expected.txt
index b59a26f..7054549 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests1-write-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests1-write-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
 ../resources/tests1.dat: PASS
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt
index 8e9c739..2a67095 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests18-data-expected.txt
@@ -1,5 +1,5 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
 ../resources/tests18.dat: PASS
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests18-write-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests18-write-expected.txt
index 8e9c739..2a67095 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests18-write-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests18-write-expected.txt
@@ -1,5 +1,5 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
 ../resources/tests18.dat: PASS
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt
index a34b5b0..d38385f 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests7-data-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
 ../resources/tests7.dat: PASS
diff --git a/third_party/blink/web_tests/html5lib/generated/run-tests7-write-expected.txt b/third_party/blink/web_tests/html5lib/generated/run-tests7-write-expected.txt
index a34b5b0..d38385f 100644
--- a/third_party/blink/web_tests/html5lib/generated/run-tests7-write-expected.txt
+++ b/third_party/blink/web_tests/html5lib/generated/run-tests7-write-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token <
+CONSOLE ERROR: line 1: Uncaught SyntaxError: Unexpected token '<'
 ../resources/tests7.dat: PASS
diff --git a/third_party/blink/web_tests/http/tests/credentialmanager/resources/mock-navigator-credentials.js b/third_party/blink/web_tests/http/tests/credentialmanager/resources/mock-navigator-credentials.js
index 54c96068..9f7f63d 100644
--- a/third_party/blink/web_tests/http/tests/credentialmanager/resources/mock-navigator-credentials.js
+++ b/third_party/blink/web_tests/http/tests/credentialmanager/resources/mock-navigator-credentials.js
@@ -186,10 +186,10 @@
 setDocumentInterfaceBrokerOverrides({
   getAuthenticator: request => {
     var authenticator = new blink.mojom.Authenticator(mockAuthenticator);
-    authenticator.bindHandle(request.handle);
+    authenticator.$.bindHandle(request.handle);
   },
   getCredentialManager: request => {
     var credentialManager = new blink.mojom.CredentialManager(mockCredentialManager);
-    credentialManager.bindHandle(request.handle);
+    credentialManager.$.bindHandle(request.handle);
   }
 });
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-eval-object-literal-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-eval-object-literal-expected.txt
index f8fa7240..ce2c7dd 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-eval-object-literal-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-eval-object-literal-expected.txt
@@ -5,12 +5,12 @@
 {a:1}
 {a: 1}
 {var a = 1; eval("{ a:1, b:2 }");}
-VM:1 Uncaught SyntaxError: Unexpected token :
+VM:1 Uncaught SyntaxError: Unexpected token ':'
 (anonymous) @ VM:1
 { for (var i = 0; i < 5; ++i); }
 undefined
 { a: 4 }),({ b: 7 }
-VM:1 Uncaught SyntaxError: Unexpected token )
+VM:1 Uncaught SyntaxError: Unexpected token ')'
 { let a = 4; a; }
 4
 { let a = 4; }; { let b = 5; };
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-link-to-snippet-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-link-to-snippet-expected.txt
index 23275769..015f5e9 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-link-to-snippet-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-link-to-snippet-expected.txt
@@ -6,8 +6,8 @@
 name1:1 42
 
 Running: testSnippetSyntaxError
-Line Message was added: snippet:///name2 Error 'Uncaught SyntaxError: Unexpected token }':1:1
-name2:2 Uncaught SyntaxError: Unexpected token }
+Line Message was added: snippet:///name2 Error 'Uncaught SyntaxError: Unexpected token '}'':1:1
+name2:2 Uncaught SyntaxError: Unexpected token '}'
 
 Running: testConsoleErrorHighlight
 Line Message was added: snippet:///name3 Error '42':1:8
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-log-eval-syntax-error-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-log-eval-syntax-error-expected.txt
index 7cac82b..ba13acc4 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-log-eval-syntax-error-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-log-eval-syntax-error-expected.txt
@@ -1,11 +1,11 @@
 Tests that syntax errors in eval are logged into console, contains correct link and doesn't cause browser crash.
 
-VM:1 Uncaught SyntaxError: Unexpected token }
+VM:1 Uncaught SyntaxError: Unexpected token '}'
     at foo (<anonymous>:13:11)
 foo @ VM:13
 setTimeout (async)
 (anonymous) @ console-log-eval-syntax-error.js:21
-boo.js:2 Uncaught SyntaxError: Unexpected token }
+boo.js:2 Uncaught SyntaxError: Unexpected token '}'
     at boo (<anonymous>:17:11)
 boo @ VM:17
 setTimeout (async)
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-log-syntax-error-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-log-syntax-error-expected.txt
index 02fc3d1f..c6b4645 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-log-syntax-error-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-log-syntax-error-expected.txt
@@ -1,4 +1,4 @@
 Tests that syntax errors are logged into console and doesn't cause browser crash.
 
-syntax-error.js:3 Uncaught SyntaxError: Unexpected token )
+syntax-error.js:3 Uncaught SyntaxError: Unexpected token ')'
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/console/console-message-from-inline-with-url-expected.txt b/third_party/blink/web_tests/http/tests/devtools/console/console-message-from-inline-with-url-expected.txt
index 3524e238..468142d 100644
--- a/third_party/blink/web_tests/http/tests/devtools/console/console-message-from-inline-with-url-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/console/console-message-from-inline-with-url-expected.txt
@@ -1,7 +1,7 @@
 Tests that log message and syntax errors from inline scripts with sourceURL are logged into console, contains correct link and doesn't cause browser crash.
 
 foo.js:13 foo
-boo.js:1 Uncaught SyntaxError: Unexpected token }
+boo.js:1 Uncaught SyntaxError: Unexpected token '}'
     at addInlineWithSyntaxError (foo.js:21)
 addInlineWithSyntaxError @ foo.js:21
 setTimeout (async)
diff --git a/third_party/blink/web_tests/http/tests/devtools/device-orientation-success.js b/third_party/blink/web_tests/http/tests/devtools/device-orientation-success.js
index c8f35e4..8bae72a 100644
--- a/third_party/blink/web_tests/http/tests/devtools/device-orientation-success.js
+++ b/third_party/blink/web_tests/http/tests/devtools/device-orientation-success.js
@@ -21,7 +21,7 @@
           sensorProvider = sensorMocks();
           let mockDataPromise = setMockSensorDataForType(
               sensorProvider,
-              device.mojom.SensorType.RELATIVE_ORIENTATION_EULER_ANGLES,
+              "RelativeOrientationEulerAngles",
               [mockBeta, mockGamma, mockAlpha]);
           window.addEventListener("deviceorientation", handler);
           return mockDataPromise;
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-autocontinue-on-syntax-error-expected.txt b/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-autocontinue-on-syntax-error-expected.txt
index f0526df..90f619e 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-autocontinue-on-syntax-error-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-autocontinue-on-syntax-error-expected.txt
@@ -1,5 +1,5 @@
 Tests that debugger won't stop on syntax errors even if "pause on uncaught exceptions" is on.
 
-syntax-error.html:4 Uncaught SyntaxError: Unexpected token )
+syntax-error.html:4 Uncaught SyntaxError: Unexpected token ')'
 syntax-error.html:8 Iframe loaded
 
diff --git a/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-compile-and-run-expected.txt b/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-compile-and-run-expected.txt
index fa7e86f..0a7fd9e 100644
--- a/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-compile-and-run-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/sources/debugger/debugger-compile-and-run-expected.txt
@@ -21,7 +21,7 @@
 Running: testCompileError
 Compiling script
 exceptionDetails:
-   Uncaught SyntaxError: Unexpected token }
+   Uncaught SyntaxError: Unexpected token '}'
    line: 0, column: 0
    no stack trace attached to exceptionDetails
 
diff --git a/third_party/blink/web_tests/http/tests/input/discard-events-to-unstable-iframe.html b/third_party/blink/web_tests/http/tests/input/discard-events-to-unstable-iframe.html
new file mode 100644
index 0000000..015a2043
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/input/discard-events-to-unstable-iframe.html
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+
+<style>
+html, body {
+  margin: 0;
+}
+iframe {
+  border: 0;
+  position: absolute;
+}
+</style>
+
+<iframe id=iframe src="http://localhost:8080/input/resources/discard-events-to-unstable-iframe-subframe.html" sandbox="allow-scripts"></iframe>
+
+<script>
+async_test(t => {
+  if (!window.chrome || !chrome.gpuBenchmarking) {
+    t.done();
+    return;
+  }
+
+  let iframe = document.getElementById("iframe");
+  let expectedCounts = {};
+
+  let tapAndCount = (x, y, mousedownCount, mouseupCount, clickCount) => {
+    // Use double-rAF to stabilize layout before dispatching input event.
+    requestAnimationFrame(() => {
+      requestAnimationFrame(() => {
+        iframe.contentWindow.postMessage({tap: [x, y]}, "*");
+        expectedCounts = {
+          mousedown: mousedownCount,
+          mouseup: mouseupCount,
+          click: clickCount
+        };
+      });
+    });
+  };
+
+  let step0 = () => {
+    tapAndCount(20, 20, 1, 1, 1);
+  };
+
+  let step1 = () => {
+    iframe.style.top = "50px";
+    // Iframe moved, but it's not running an IntersectionObserver, so input events
+    // are unaffected.
+    tapAndCount(20, 70, 1, 1, 1);
+  };
+
+  let step2 = () => {
+    // Start running IntersectionObserver in the iframe.
+    iframe.contentWindow.postMessage("observe", "*");
+  };
+
+  let step3 = () => {
+    iframe.style.left = "50px";
+    // Iframe moved, and it's running an IntersectionObserver with
+    // trackVisibility=true. Input events should be discarded.
+    tapAndCount(70, 70, 0, 0, 0);
+  };
+
+  let step4 = () => {
+    setTimeout(() => {
+      // Iframe position has been stable for 500ms, so input events should not
+      // be discarded.
+      tapAndCount(70, 70, 1, 1, 1);
+    }, 500);
+  };
+
+  let steps = [step0, step1, step2, step3, step4];
+  let runNextStep = () => {
+    if (steps.length) {
+      let step = steps.shift();
+      step();
+    } else {
+      t.done();
+    }
+  };
+
+  addEventListener("message", evt => {
+    if (evt.data.hasOwnProperty("count")) {
+      t.step(() => {
+        Object.keys(expectedCounts).forEach(key => {
+          assert_equals(evt.data["count"][key], expectedCounts[key], key);
+        });
+      });
+    }
+    runNextStep();
+  });
+
+  iframe.addEventListener("load", runNextStep);
+}, "Input events targeting a cross-origin iframe which has moved recently, and which is using IntersectionObserver V2, should be discarded.");
+</script>
diff --git a/third_party/blink/web_tests/http/tests/input/resources/discard-events-to-unstable-iframe-subframe.html b/third_party/blink/web_tests/http/tests/input/resources/discard-events-to-unstable-iframe-subframe.html
new file mode 100644
index 0000000..6807bc69
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/input/resources/discard-events-to-unstable-iframe-subframe.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+
+<div id=target>Hello, world!</div>
+
+<script>
+let eventCounts = {};
+let eventTypes = [
+  "mousedown",
+  "mouseup",
+  "click"];
+eventTypes.forEach(eventType => {
+  eventCounts[eventType] = 0;
+  document.addEventListener(eventType, evt => {
+    eventCounts[eventType]++;
+  });
+});
+
+addEventListener("message", evt => {
+  if (evt.data == "observe") {
+    new IntersectionObserver(entries => {
+      evt.source.postMessage("didObserve", "*");
+    }, {trackVisibility: true, delay: 100}).observe(document.getElementById("target"));
+  }
+  if (evt.data.hasOwnProperty("tap")) {
+    chrome.gpuBenchmarking.tap(evt.data["tap"][0], evt.data["tap"][1], () => {
+      evt.source.postMessage({count: eventCounts}, "*");
+      Object.keys(eventCounts).forEach(key => { eventCounts[key] = 0 });
+    });
+  }
+});
+</script>
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/resources/generate-test-sxgs.sh b/third_party/blink/web_tests/http/tests/loading/sxg/resources/generate-test-sxgs.sh
index 1767904..048eaae 100755
--- a/third_party/blink/web_tests/http/tests/loading/sxg/resources/generate-test-sxgs.sh
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/resources/generate-test-sxgs.sh
@@ -104,4 +104,42 @@
   -o sxg-larger-than-10k.sxg \
   -miRecordSize 100
 
+# Generate the signed exchange file of sxg-subresource-script-inner.js.
+gen-signedexchange \
+  -version $sxg_version \
+  -uri https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-script.js \
+  -status 200 \
+  -content sxg-subresource-script-inner.js \
+  -certificate $certs_dir/127.0.0.1.sxg.pem \
+  -certUrl https://127.0.0.1:8443/loading/sxg/resources/127.0.0.1.sxg.pem.cbor \
+  -validityUrl https://127.0.0.1:8443/loading/sxg/resources/resource.validity.msg \
+  -privateKey $certs_dir/127.0.0.1.sxg.key \
+  -date 2030-04-01T00:00:00Z \
+  -expire 168h \
+  -o sxg-subresource-script.sxg \
+  -miRecordSize 100 \
+  -responseHeader "content-type:text/javascript; charset=utf-8"
+
+# Get the header integrity hash value of sxg-subresource-script.sxg.
+header_integrity=$(dump-signedexchange -i sxg-subresource-script.sxg | \
+                   grep -o "header integrity: sha256-.*" | \
+                   grep -o "sha256-.*$")
+
+# Generate the signed exchange file for Origin Trial test.
+gen-signedexchange \
+  -version $sxg_version \
+  -uri https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-origin-trial-inner-page.html \
+  -status 200 \
+  -content sxg-subresource-origin-trial-page.html \
+  -certificate $certs_dir/127.0.0.1.sxg.pem \
+  -certUrl https://127.0.0.1:8443/loading/sxg/resources/127.0.0.1.sxg.pem.cbor \
+  -validityUrl https://127.0.0.1:8443/loading/sxg/resources/resource.validity.msg \
+  -privateKey $certs_dir/127.0.0.1.sxg.key \
+  -date 2030-04-01T00:00:00Z \
+  -expire 168h \
+  -o sxg-subresource-origin-trial-page.sxg \
+  -miRecordSize 100 \
+  -responseHeader "link:<https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-script.js>;rel=allowed-alt-sxg;header-integrity=\"$header_integrity\",<https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-script.js>;rel=preload;as=script"
+
+
 rm -fr $tmpdir
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.html b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.html
new file mode 100644
index 0000000..5d1ff24
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<head>
+<title>Signed Exchange subresource Origin Trial inner page</title>
+<script src="./sxg-util.js"></script>
+<script>
+let value = "empty";
+</script>
+<script src="./sxg-subresource-script.js"></script>
+</head>
+<body>
+<script>
+(async function() {
+  await waitUntilDidFinishLoadForFrame;
+  const div = document.createElement('div');
+  div.appendChild(document.createTextNode('Hello ' + value));
+  document.body.appendChild(div);
+  testRunner.notifyDone();
+})()
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.php b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.php
new file mode 100644
index 0000000..b92628e
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.php
@@ -0,0 +1,6 @@
+<?
+header("Content-Type: application/signed-exchange;v=b3");
+header("X-Content-Type-Options: nosniff");
+header("Link: <https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-script.sxg>;rel=alternate;type=\"application/signed-exchange;v=b3\";anchor=\"https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-script.js\";");
+echo(file_get_contents("sxg-subresource-origin-trial-page.sxg"));
+?>
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.sxg b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.sxg
new file mode 100644
index 0000000..9d80574
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-origin-trial-page.sxg
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-script-inner.js b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-script-inner.js
new file mode 100644
index 0000000..ba0a37ab
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-script-inner.js
@@ -0,0 +1 @@
+value = "from sxg";
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-script.sxg b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-script.sxg
new file mode 100644
index 0000000..dba6039
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/resources/sxg-subresource-script.sxg
Binary files differ
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/sxg-subresource-origin-trial.https-expected.txt b/third_party/blink/web_tests/http/tests/loading/sxg/sxg-subresource-origin-trial.https-expected.txt
new file mode 100644
index 0000000..ca4d0c07
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/sxg-subresource-origin-trial.https-expected.txt
@@ -0,0 +1,16 @@
+main frame - DidStartNavigation
+main frame - ReadyToCommitNavigation
+main frame - didCommitLoadForFrame
+main frame - didReceiveTitle: Signed Exchange subresource Origin Trial
+main frame - didFinishDocumentLoadForFrame
+main frame - didHandleOnloadEventsForFrame
+main frame - didFinishLoadForFrame
+main frame - BeginNavigation request to 'https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-origin-trial-page.php', http method GET
+main frame - DidStartNavigation
+main frame - ReadyToCommitNavigation
+main frame - didCommitLoadForFrame
+main frame - didReceiveTitle: Signed Exchange subresource Origin Trial inner page
+main frame - didFinishDocumentLoadForFrame
+main frame - didHandleOnloadEventsForFrame
+main frame - didFinishLoadForFrame
+Hello from sxg
diff --git a/third_party/blink/web_tests/http/tests/loading/sxg/sxg-subresource-origin-trial.https.html b/third_party/blink/web_tests/http/tests/loading/sxg/sxg-subresource-origin-trial.https.html
new file mode 100644
index 0000000..df34bef
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/loading/sxg/sxg-subresource-origin-trial.https.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<head>
+<title>Signed Exchange subresource Origin Trial</title>
+<!-- Generate token with the command:
+generate_token.py https://127.0.0.1:8443 SignedExchangeSubresourcePrefetch --expire-timestamp=2000000000
+-- -->
+<meta http-equiv="origin-trial" content="AkPuNZ6wN6pkhO5BsawPJCR9+8TDqRn4NUKsrhif+5Hs6l3dbRCdhhBpImxV13ZM8luH7wYH+rC9LYwD9xIQOAwAAABqeyJvcmlnaW4iOiAiaHR0cHM6Ly8xMjcuMC4wLjE6ODQ0MyIsICJmZWF0dXJlIjogIlNpZ25lZEV4Y2hhbmdlU3VicmVzb3VyY2VQcmVmZXRjaCIsICJleHBpcnkiOiAyMDAwMDAwMDAwfQ==">
+<script src="./resources/sxg-util.js"></script>
+</head>
+<body>
+<script>
+if (window.testRunner) {
+  testRunner.dumpAsText();
+  testRunner.waitUntilDone();
+}
+(async function() {
+  await waitUntilDidFinishLoadForFrame;
+  const sxg_path = "https://127.0.0.1:8443/loading/sxg/resources/sxg-subresource-origin-trial-page.php";
+  new PerformanceObserver((list) => {
+    for (let e of list.getEntries()) {
+      if (e.name.endsWith('sxg-subresource-script.sxg')) {
+        location.href = sxg_path;
+      }
+    }
+  }).observe({ entryTypes: ['resource'] });
+  const link = document.createElement('link');
+  link.rel = 'prefetch';
+  link.href = sxg_path;
+  document.body.appendChild(link);
+})();
+// cache-control: public, max-age=600
+</script>
+</body>
diff --git a/third_party/blink/web_tests/http/tests/resources/sensor-helpers.js b/third_party/blink/web_tests/http/tests/resources/sensor-helpers.js
index ee1d6398..22a7523 100644
--- a/third_party/blink/web_tests/http/tests/resources/sensor-helpers.js
+++ b/third_party/blink/web_tests/http/tests/resources/sensor-helpers.js
@@ -31,7 +31,7 @@
       this.notifyOnReadingChange_ = true;
       this.reportingMode_ = reportingMode;
       this.sensorReadingTimerId_ = null;
-      this.updateReadingFunction_ = null;
+      this.readingData_ = null;
       this.suspendCalled_ = null;
       this.resumeCalled_ = null;
       this.addConfigurationCalled_ = null;
@@ -55,7 +55,7 @@
     }
 
     // Adds configuration for the sensor and starts reporting fake data
-    // through updateReadingFunction_ callback.
+    // through setSensorReading function.
     addConfiguration(configuration) {
       assert_not_equals(configuration, null, "Invalid sensor configuration.");
 
@@ -115,7 +115,7 @@
 
       this.startShouldFail_ = false;
       this.notifyOnReadingChange_ = true;
-      this.updateReadingFunction_ = null;
+      this.readingData_ = null;
       this.requestedFrequencies_ = [];
       this.suspendCalled_ = null;
       this.resumeCalled_ = null;
@@ -133,9 +133,9 @@
       }
     }
 
-    // Sets callback that is used to deliver sensor reading updates.
-    setUpdateSensorReadingFunction(updateReadingFunction) {
-      this.updateReadingFunction_ = updateReadingFunction;
+    // Sets fake data that is used to deliver sensor reading updates.
+    setSensorReading(readingData) {
+      this.readingData_ = readingData;
       return Promise.resolve(this);
     }
 
@@ -179,13 +179,13 @@
     }
 
     startReading() {
-      if (this.updateReadingFunction_ != null) {
+      if (this.readingData_ != null) {
         this.stopReading();
         let maxFrequencyUsed = this.requestedFrequencies_[0];
         let timeout = (1 / maxFrequencyUsed) * 1000;
         this.sensorReadingTimerId_ = window.setInterval(() => {
-          if (this.updateReadingFunction_) {
-            this.updateReadingFunction_(this.buffer_);
+          if (this.readingData_) {
+            this.buffer_.set(this.readingData_, 2);
             // For all tests sensor reading should have monotonically
             // increasing timestamp in seconds.
             this.buffer_[1] = window.performance.now() * 0.001;
@@ -230,6 +230,17 @@
       this.isContinuous_ = false;
       this.maxFrequency_ = 60;
       this.minFrequency_ = 1;
+      this.mojomSensorType_ = new Map([
+        ['Accelerometer', device.mojom.SensorType.ACCELEROMETER],
+        ['LinearAccelerationSensor', device.mojom.SensorType.LINEAR_ACCELERATION],
+        ['AmbientLightSensor', device.mojom.SensorType.AMBIENT_LIGHT],
+        ['Gyroscope', device.mojom.SensorType.GYROSCOPE],
+        ['Magnetometer', device.mojom.SensorType.MAGNETOMETER],
+        ['AbsoluteOrientationSensor', device.mojom.SensorType.ABSOLUTE_ORIENTATION_QUATERNION],
+        ['AbsoluteOrientationEulerAngles', device.mojom.SensorType.ABSOLUTE_ORIENTATION_EULER_ANGLES],
+        ['RelativeOrientationSensor', device.mojom.SensorType.RELATIVE_ORIENTATION_QUATERNION],
+        ['RelativeOrientationEulerAngles', device.mojom.SensorType.RELATIVE_ORIENTATION_EULER_ANGLES]
+      ]);
       this.binding_ = new mojo.Binding(device.mojom.SensorProvider, this);
 
       this.interceptor_ = new MojoInterfaceInterceptor(
@@ -329,16 +340,17 @@
 
     // Sets flag that forces mock SensorProvider to fail when getSensor() is
     // invoked.
-    setGetSensorShouldFail(type, shouldFail) {
-      this.getSensorShouldFail_.set(type, shouldFail);
+    setGetSensorShouldFail(sensorType, shouldFail) {
+      this.getSensorShouldFail_.set(this.mojomSensorType_.get(sensorType), shouldFail);
     }
 
-    setPermissionsDenied(type, permissionsDenied) {
-      this.permissionsDenied_.set(type, permissionsDenied);
+    setPermissionsDenied(sensorType, permissionsDenied) {
+      this.permissionsDenied_.set(this.mojomSensorType_.get(sensorType), permissionsDenied);
     }
 
     // Returns mock sensor that was created in getSensor to the layout test.
-    getCreatedSensor(type) {
+    getCreatedSensor(sensorType) {
+      const type = this.mojomSensorType_.get(sensorType);
       assert_equals(typeof type, "number", "A sensor type must be specified.");
 
       if (this.activeSensors_.has(type)) {
@@ -390,9 +402,7 @@
 
 async function setMockSensorDataForType(sensorProvider, sensorType, mockDataArray) {
   let createdSensor = await sensorProvider.getCreatedSensor(sensorType);
-  return createdSensor.setUpdateSensorReadingFunction(buffer => {
-    buffer.set(mockDataArray, 2);
-  });
+  return createdSensor.setSensorReading(mockDataArray);
 }
 
 // Returns a promise that will be resolved when an event equal to the given
@@ -443,26 +453,3 @@
     }, 500);
   });
 }
-
-// TODO(Mikhail): Refactor further to remove code duplication
-// in <concrete sensor>.html files.
-function verify_sensor_reading(pattern, values, timestamp, is_null) {
-  function round(val) {
-    return Number.parseFloat(val).toPrecision(6);
-  }
-
-  if (is_null) {
-    return (values === null || values.every(r => r === null)) &&
-           timestamp === null;
-  }
-  return values.every((r, i) => round(r) === round(pattern[i])) &&
-         timestamp !== null;
-}
-
-function verify_xyz_sensor_reading(pattern, {x, y, z, timestamp}, is_null) {
-  return verify_sensor_reading(pattern, [x, y, z], timestamp, is_null);
-}
-
-function verify_quat_sensor_reading(pattern, {quaternion, timestamp}, is_null) {
-  return verify_sensor_reading(pattern, quaternion, timestamp, is_null);
-}
diff --git a/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-events-unavailable-on-insecure-origins.html b/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-events-unavailable-on-insecure-origins.html
index 9ba2557..72e04a1 100644
--- a/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-events-unavailable-on-insecure-origins.html
+++ b/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-events-unavailable-on-insecure-origins.html
@@ -40,23 +40,23 @@
     const FAKE_ACCELERATION_DATA = [1, 2, 3];
     const FAKE_LINEAR_ACCELERATION_DATA = [4, 5, 6];
     const FAKE_GYROSCOPE_DATA = [7, 8, 9];
-    setMockSensorDataForType(sensorProvider, device.mojom.SensorType.ACCELEROMETER, FAKE_ACCELERATION_DATA);
-    setMockSensorDataForType(sensorProvider, device.mojom.SensorType.LINEAR_ACCELERATION, FAKE_LINEAR_ACCELERATION_DATA);
-    setMockSensorDataForType(sensorProvider, device.mojom.SensorType.GYROSCOPE, FAKE_GYROSCOPE_DATA);
+    setMockSensorDataForType(sensorProvider, 'Accelerometer', FAKE_ACCELERATION_DATA);
+    setMockSensorDataForType(sensorProvider, 'LinearAccelerationSensor', FAKE_LINEAR_ACCELERATION_DATA);
+    setMockSensorDataForType(sensorProvider, 'Gyroscope', FAKE_GYROSCOPE_DATA);
 
     return waitForLackOfEvent('devicemotion');
   }, 'addEventListener() for `devicemotion` does not crash but the handler never fires.');
 
   sensor_test(sensorProvider => {
     const FAKE_ORIENTATION_DATA = [1.1, 2.2, 3.3];
-    setMockSensorDataForType(sensorProvider, device.mojom.SensorType.RELATIVE_ORIENTATION_EULER_ANGLES, FAKE_ORIENTATION_DATA);
+    setMockSensorDataForType(sensorProvider, 'RelativeOrientationEulerAngles', FAKE_ORIENTATION_DATA);
 
     return waitForLackOfEvent('deviceorientation');
   }, 'addEventListener() for `deviceorientation` does not crash but the handler never fires.');
 
   sensor_test(sensorProvider => {
     const FAKE_ORIENTATION_DATA = [1.1, 2.2, 3.3];
-    setMockSensorDataForType(sensorProvider, device.mojom.SensorType.ABSOLUTE_ORIENTATION_EULER_ANGLES, FAKE_ORIENTATION_DATA);
+    setMockSensorDataForType(sensorProvider, 'AbsoluteOrientationEulerAngles', FAKE_ORIENTATION_DATA);
 
     return waitForLackOfEvent('deviceorientationabsolute');
   }, 'addEventListener() for `deviceorientationabsolute` does not crash but the handler never fires.');
diff --git a/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-on-secure-origin.https.html b/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-on-secure-origin.https.html
index f3fd011..ef22996 100644
--- a/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-on-secure-origin.https.html
+++ b/third_party/blink/web_tests/http/tests/security/powerfulFeatureRestrictions/device-orientation-on-secure-origin.https.html
@@ -21,9 +21,9 @@
   const FAKE_ACCELERATION_DATA = [1, 2, 3];
   const FAKE_LINEAR_ACCELERATION_DATA = [4, 5, 6];
   const FAKE_GYROSCOPE_DATA = [7, 8, 9];
-  setMockSensorDataForType(sensorProvider, device.mojom.SensorType.ACCELEROMETER, FAKE_ACCELERATION_DATA);
-  setMockSensorDataForType(sensorProvider, device.mojom.SensorType.LINEAR_ACCELERATION, FAKE_LINEAR_ACCELERATION_DATA);
-  setMockSensorDataForType(sensorProvider, device.mojom.SensorType.GYROSCOPE, FAKE_GYROSCOPE_DATA);
+  setMockSensorDataForType(sensorProvider, 'Accelerometer', FAKE_ACCELERATION_DATA);
+  setMockSensorDataForType(sensorProvider, 'LinearAccelerationSensor', FAKE_LINEAR_ACCELERATION_DATA);
+  setMockSensorDataForType(sensorProvider, 'Gyroscope', FAKE_GYROSCOPE_DATA);
 
   const radToDeg = 180 / Math.PI;
   return waitForEvent(new DeviceMotionEvent('devicemotion', {
@@ -48,7 +48,7 @@
 
 sensor_test(async sensorProvider => {
   const FAKE_ORIENTATION_DATA = [1.1, 2.2, 3.3];
-  setMockSensorDataForType(sensorProvider, device.mojom.SensorType.RELATIVE_ORIENTATION_EULER_ANGLES, FAKE_ORIENTATION_DATA);
+  setMockSensorDataForType(sensorProvider, 'RelativeOrientationEulerAngles', FAKE_ORIENTATION_DATA);
 
   return waitForEvent(new DeviceOrientationEvent('deviceorientation', {
       alpha: FAKE_ORIENTATION_DATA[2],
@@ -60,7 +60,7 @@
 
 sensor_test(async sensorProvider => {
   const FAKE_ORIENTATION_DATA = [1.1, 2.2, 3.3];
-  setMockSensorDataForType(sensorProvider, device.mojom.SensorType.ABSOLUTE_ORIENTATION_EULER_ANGLES, FAKE_ORIENTATION_DATA);
+  setMockSensorDataForType(sensorProvider, 'AbsoluteOrientationEulerAngles', FAKE_ORIENTATION_DATA);
 
   return waitForEvent(new DeviceOrientationEvent('deviceorientationabsolute', {
       alpha: FAKE_ORIENTATION_DATA[2],
diff --git a/third_party/blink/web_tests/inspector-protocol/runtime/runtime-callFunctionOn-async-expected.txt b/third_party/blink/web_tests/inspector-protocol/runtime/runtime-callFunctionOn-async-expected.txt
index 19b6e61..b7d8b8f 100644
--- a/third_party/blink/web_tests/inspector-protocol/runtime/runtime-callFunctionOn-async-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/runtime/runtime-callFunctionOn-async-expected.txt
@@ -14,7 +14,7 @@
         columnNumber : 2
         exception : {
             className : SyntaxError
-            description : SyntaxError: Unexpected token }
+            description : SyntaxError: Unexpected token '}'
             objectId : <number>
             subtype : error
             type : object
@@ -26,7 +26,7 @@
     }
     result : {
         className : SyntaxError
-        description : SyntaxError: Unexpected token }
+        description : SyntaxError: Unexpected token '}'
         objectId : <string>
         subtype : error
         type : object
diff --git a/third_party/blink/web_tests/inspector-protocol/runtime/runtime-runScript-async-expected.txt b/third_party/blink/web_tests/inspector-protocol/runtime/runtime-runScript-async-expected.txt
index ddf72f0..2f7c3ea 100644
--- a/third_party/blink/web_tests/inspector-protocol/runtime/runtime-runScript-async-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/runtime/runtime-runScript-async-expected.txt
@@ -16,7 +16,7 @@
         columnNumber : 1
         exception : {
             className : SyntaxError
-            description : SyntaxError: Unexpected token }
+            description : SyntaxError: Unexpected token '}'
             objectId : <number>
             subtype : error
             type : object
@@ -34,7 +34,7 @@
         columnNumber : 0
         exception : {
             className : SyntaxError
-            description : SyntaxError: Unexpected token }     at boo.js:2:2
+            description : SyntaxError: Unexpected token '}'     at boo.js:2:2
             objectId : <number>
             subtype : error
             type : object
@@ -57,7 +57,7 @@
     }
     result : {
         className : SyntaxError
-        description : SyntaxError: Unexpected token }     at boo.js:2:2
+        description : SyntaxError: Unexpected token '}'     at boo.js:2:2
         objectId : <string>
         subtype : error
         type : object
diff --git a/third_party/blink/web_tests/mojo/bindings-lite.html b/third_party/blink/web_tests/mojo/bindings-lite.html
index 16c5eace..5026483 100644
--- a/third_party/blink/web_tests/mojo/bindings-lite.html
+++ b/third_party/blink/web_tests/mojo/bindings-lite.html
@@ -25,7 +25,7 @@
 
 promise_test(() => {
   let impl = new TargetImpl;
-  let proxy = impl.target.createProxy();
+  let proxy = impl.target.$.createProxy();
   proxy.poke();
   return proxy.ping().then(() => {
     assert_equals(impl.numPokes, 1);
@@ -34,7 +34,7 @@
 
 promise_test(() => {
   let impl = new TargetImpl;
-  let proxy = impl.target.createProxy();
+  let proxy = impl.target.$.createProxy();
   return proxy.repeat(kTestMessage, kTestNumbers)
       .then(reply => {
         assert_equals(reply.message, kTestMessage);
@@ -44,7 +44,7 @@
 
 promise_test(async (t) => {
   const impl = new TargetImpl;
-  const proxy = impl.target.createProxy();
+  const proxy = impl.target.$.createProxy();
 
   await proxy.ping();
   proxy.$.close();
@@ -54,7 +54,7 @@
 
 promise_test(async (t) => {
   const impl = new TargetImpl;
-  const proxy = impl.target.createProxy();
+  const proxy = impl.target.$.createProxy();
 
   // None of these promises should successfully resolve because we are
   // immediately closing the pipe.
@@ -78,7 +78,7 @@
   let interceptor = new MojoInterfaceInterceptor(
       liteJsTest.mojom.TestMessageTarget.$interfaceName);
   interceptor.oninterfacerequest = e => {
-    impl.target.bindHandle(e.handle);
+    impl.target.$.bindHandle(e.handle);
   }
   interceptor.start();
 
@@ -91,7 +91,7 @@
 
 promise_test(() => {
   let router = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  let proxy = router.createProxy();
+  let proxy = router.$.createProxy();
   return new Promise(resolve => {
     router.poke.addListener(resolve);
     proxy.poke();
@@ -100,7 +100,7 @@
 
 promise_test(() => {
   let router = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  let proxy = router.createProxy();
+  let proxy = router.$.createProxy();
   let numPokes = 0;
   router.poke.addListener(() => ++numPokes);
   router.ping.addListener(() => Promise.resolve());
@@ -110,7 +110,7 @@
 
 promise_test(() => {
   let router = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  let proxy = router.createProxy();
+  let proxy = router.$.createProxy();
   router.repeat.addListener(
       (message, numbers) => ({message: message, numbers: numbers}));
   return proxy.repeat(kTestMessage, kTestNumbers)
@@ -122,11 +122,11 @@
 
 promise_test(() => {
   let targetRouter = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  let targetProxy = targetRouter.createProxy();
+  let targetProxy = targetRouter.$.createProxy();
   let subinterfaceRouter = new liteJsTest.mojom.SubinterfaceCallbackRouter;
   targetRouter.requestSubinterface.addListener((request, client) => {
     let values = [];
-    subinterfaceRouter.bindHandle(request.handle);
+    subinterfaceRouter.$.bindHandle(request.handle);
     subinterfaceRouter.push.addListener(value => values.push(value));
     subinterfaceRouter.flush.addListener(() => {
       client.didFlush(values);
@@ -137,7 +137,7 @@
   let clientRouter = new liteJsTest.mojom.SubinterfaceClientCallbackRouter;
   let subinterfaceProxy = new liteJsTest.mojom.SubinterfaceProxy;
   targetProxy.requestSubinterface(
-      subinterfaceProxy.$.createRequest(), clientRouter.createProxy());
+      subinterfaceProxy.$.createRequest(), clientRouter.$.createProxy());
   return new Promise(resolve => {
     clientRouter.didFlush.addListener(values => {
       assert_array_equals(values, kTestNumbers);
@@ -151,7 +151,7 @@
 
 promise_test(() => {
   const targetRouter = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  const targetProxy = targetRouter.createProxy();
+  const targetProxy = targetRouter.$.createProxy();
   targetRouter.flatten.addListener(values => ({values: values.map(v => v.x)}));
   return targetProxy.flatten([{x: 1}, {x: 2}, {x: 3}]).then(reply => {
     assert_array_equals(reply.values, [1, 2, 3]);
@@ -160,7 +160,7 @@
 
 promise_test(() => {
   const targetRouter = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  const targetProxy = targetRouter.createProxy();
+  const targetProxy = targetRouter.$.createProxy();
   targetRouter.flattenUnions.addListener(unions => {
     return {x: unions.filter(u => u.x !== undefined).map(u => u.x),
             s: unions.filter(u => u.s !== undefined).map(u => u.s.x)};
@@ -176,7 +176,7 @@
 
 promise_test(() => {
   let impl = new TargetImpl;
-  let proxy = impl.target.createProxy();
+  let proxy = impl.target.$.createProxy();
 
   // Poke a bunch of times. These should never race with the assertion below,
   // because the |flushForTesting| request/response is ordered against other
@@ -191,7 +191,7 @@
 
 promise_test(async(t) => {
   const impl = new TargetImpl;
-  const proxy = impl.target.createProxy();
+  const proxy = impl.target.$.createProxy();
   const disconnectPromise = new Promise(resolve => impl.target.onConnectionError.addListener(resolve));
   proxy.$.close();
   return disconnectPromise;
@@ -199,7 +199,7 @@
 
 promise_test(() => {
   const router = new liteJsTest.mojom.TestMessageTargetCallbackRouter;
-  const proxy = router.createProxy();
+  const proxy = router.$.createProxy();
   const disconnectPromise = new Promise(resolve => router.onConnectionError.addListener(resolve));
   proxy.$.close();
   return disconnectPromise;
diff --git a/third_party/blink/web_tests/mojo/document-interface-broker-override.html b/third_party/blink/web_tests/mojo/document-interface-broker-override.html
index e7d88667..7763c1a 100644
--- a/third_party/blink/web_tests/mojo/document-interface-broker-override.html
+++ b/third_party/blink/web_tests/mojo/document-interface-broker-override.html
@@ -25,7 +25,7 @@
   brokerProxy.getFrameHostTestInterface(testInterfaceProxyBeforeOverride.$.createRequest());
 
   setDocumentInterfaceBrokerOverrides({ getFrameHostTestInterface: request => {
-    frameHostTestImpl.bindHandle(request.handle);
+    frameHostTestImpl.$.bindHandle(request.handle);
   }});
 
   const testInterfaceProxyAfterOverride = new blink.mojom.FrameHostTestInterfaceProxy;
@@ -43,4 +43,4 @@
 
 </script>
  </body>
- </html>
\ No newline at end of file
+ </html>
diff --git a/third_party/blink/web_tests/nfc/push.html b/third_party/blink/web_tests/nfc/push.html
index 5a6d231..72ebe4e 100644
--- a/third_party/blink/web_tests/nfc/push.html
+++ b/third_party/blink/web_tests/nfc/push.html
@@ -90,13 +90,13 @@
 nfc_test(() => {
   mockNFC.setHWStatus(NFCHWStatus.DISABLED);
   return assertRejectsWithError(navigator.nfc.push(test_text_data),
-                                'NotSupportedError');
+                                'NotReadableError');
 }, 'nfc.push should fail when NFC HW is disabled.');
 
 nfc_test(() => {
   mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED);
   return assertRejectsWithError(navigator.nfc.push(test_text_data),
-                                'NotSupportedError');
+                                'NotReadableError');
 }, 'nfc.push should fail when NFC HW is not supported.');
 
 nfc_test(() => {
diff --git a/third_party/blink/web_tests/nfc/resources/nfc-helpers.js b/third_party/blink/web_tests/nfc/resources/nfc-helpers.js
index 35b9072..f419701 100644
--- a/third_party/blink/web_tests/nfc/resources/nfc-helpers.js
+++ b/third_party/blink/web_tests/nfc/resources/nfc-helpers.js
@@ -10,8 +10,11 @@
 var test_buffer_view = new Uint8Array(test_buffer_data).set(test_text_byte_array);
 
 var NFCHWStatus = {};
+// OS-level NFC setting is ON
 NFCHWStatus.ENABLED = 1;
+// no NFC chip
 NFCHWStatus.NOT_SUPPORTED = NFCHWStatus.ENABLED + 1;
+// OS-level NFC setting OFF
 NFCHWStatus.DISABLED = NFCHWStatus.NOT_SUPPORTED + 1;
 
 function noop() {}
@@ -378,9 +381,9 @@
 
   isReady() {
     if (this.hw_status_ === NFCHWStatus.DISABLED)
-      return createNFCError(device.mojom.NFCErrorType.DEVICE_DISABLED);
+      return createNFCError(device.mojom.NFCErrorType.NOT_READABLE);
     if (this.hw_status_ === NFCHWStatus.NOT_SUPPORTED)
-      return createNFCError(device.mojom.NFCErrorType.NOT_SUPPORTED);
+      return createNFCError(device.mojom.NFCErrorType.NOT_READABLE);
     return null;
   }
 
diff --git a/third_party/blink/web_tests/nfc/resources/push-from-iframe.html b/third_party/blink/web_tests/nfc/resources/push-from-iframe.html
index cb7faed..82a9c21 100644
--- a/third_party/blink/web_tests/nfc/resources/push-from-iframe.html
+++ b/third_party/blink/web_tests/nfc/resources/push-from-iframe.html
@@ -6,7 +6,7 @@
   if (message.data === 'Ready') {
     let onSuccess = () => { parent.postMessage('Failure', '*'); };
     let onError = error => {
-      if (error.name == 'SecurityError') {
+      if (error.name == 'NotAllowedError') {
         parent.postMessage('Success', '*');
       } else {
         parent.postMessage('Failure', '*');
diff --git a/third_party/blink/web_tests/paint/invalidation/svg/viewport-mask-update-expected.txt b/third_party/blink/web_tests/paint/invalidation/svg/viewport-mask-update-expected.txt
index 61212956..7ed4f4c9 100644
--- a/third_party/blink/web_tests/paint/invalidation/svg/viewport-mask-update-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/svg/viewport-mask-update-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 8: Uncaught SyntaxError: Unexpected token ,
+CONSOLE ERROR: line 8: Uncaught SyntaxError: Unexpected token ','
 CONSOLE ERROR: line 11: Uncaught ReferenceError: repaintTest is not defined
diff --git a/third_party/blink/web_tests/platform/linux/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png b/third_party/blink/web_tests/platform/linux/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
deleted file mode 100644
index 3f09fd1c..0000000
--- a/third_party/blink/web_tests/platform/linux/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.10/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png b/third_party/blink/web_tests/platform/mac-mac10.10/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
deleted file mode 100644
index 9e30c7b..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.10/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac-mac10.12/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png b/third_party/blink/web_tests/platform/mac-mac10.12/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
deleted file mode 100644
index dbce636..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.12/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/mac/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png b/third_party/blink/web_tests/platform/mac/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
deleted file mode 100644
index aa9aec1b..0000000
--- a/third_party/blink/web_tests/platform/mac/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png b/third_party/blink/web_tests/platform/win/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
deleted file mode 100644
index 35863e6..0000000
--- a/third_party/blink/web_tests/platform/win/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/platform/win7/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png b/third_party/blink/web_tests/platform/win7/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
deleted file mode 100644
index 4715e44..0000000
--- a/third_party/blink/web_tests/platform/win7/fast/text/whitespace/pre-wrap-spaces-after-newline-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/resources/document-interface-broker-helpers.js b/third_party/blink/web_tests/resources/document-interface-broker-helpers.js
index e27e910a..aa945bc 100644
--- a/third_party/blink/web_tests/resources/document-interface-broker-helpers.js
+++ b/third_party/blink/web_tests/resources/document-interface-broker-helpers.js
@@ -11,7 +11,7 @@
  *   const testFooImpl = new FooInterfaceCallbackRouter;
  *   ... override FooInterface methods ...
  *   setDocumentInterfaceBrokerOverrides({getFooInterface: request => {
- *     testFooImpl.bindHandle(request.handle);
+ *     testFooImpl.$.bindHandle(request.handle);
  *   }});
  */
 function setDocumentInterfaceBrokerOverrides(overrides) {
@@ -25,5 +25,5 @@
 
   // Use the real broker (with overrides) as the implementation of the JS-side broker
   const testBrokerBinding = new blink.mojom.DocumentInterfaceBroker(realBrokerProxy);
-  testBrokerBinding.bindHandle(handle1);
+  testBrokerBinding.$.bindHandle(handle1);
 }
diff --git a/third_party/blink/web_tests/security/lazy-event-listener-expected.txt b/third_party/blink/web_tests/security/lazy-event-listener-expected.txt
index 0b63146..96cf2f1c 100644
--- a/third_party/blink/web_tests/security/lazy-event-listener-expected.txt
+++ b/third_party/blink/web_tests/security/lazy-event-listener-expected.txt
@@ -1,2 +1,2 @@
-CONSOLE ERROR: line 2: Uncaught SyntaxError: Unexpected token }
+CONSOLE ERROR: line 2: Uncaught SyntaxError: Unexpected token '}'
 Test passes if it doesn't crash.
diff --git a/third_party/blink/web_tests/sensor/accelerometer.html b/third_party/blink/web_tests/sensor/accelerometer.html
index 169304f..732f698e 100644
--- a/third_party/blink/web_tests/sensor/accelerometer.html
+++ b/third_party/blink/web_tests/sensor/accelerometer.html
@@ -4,6 +4,7 @@
 <script src="../http/tests/resources/sensor-helpers.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/device/public/mojom/sensor_provider.mojom.js"></script>
+<script src="resources/generic-sensor-utils.js"></script>
 <script src="resources/generic-sensor-tests.js"></script>
 <script>
 
@@ -15,23 +16,17 @@
 const kDefaultReading = [1.12345, 2.12345, 3.12345];
 const kMappedReading = [-2.12345, 1.12345, 3.12345];
 
-function update_sensor_reading(buffer) {
-  buffer.set(kDefaultReading, 2);
-}
-
 runGenericSensorTests(
     Accelerometer,
-    device.mojom.SensorType.ACCELEROMETER,
-    update_sensor_reading,
-    verify_xyz_sensor_reading.bind(null, kDefaultReading),
-    verify_xyz_sensor_reading.bind(null, kMappedReading),
+    kDefaultReading,
+    verifyXyzSensorReading.bind(null, kDefaultReading),
+    verifyXyzSensorReading.bind(null, kMappedReading),
     ['accelerometer']);
 
 runGenericSensorTests(
     LinearAccelerationSensor,
-    device.mojom.SensorType.LINEAR_ACCELERATION,
-    update_sensor_reading,
-    verify_xyz_sensor_reading.bind(null, kDefaultReading),
-    verify_xyz_sensor_reading.bind(null, kMappedReading),
+    kDefaultReading,
+    verifyXyzSensorReading.bind(null, kDefaultReading),
+    verifyXyzSensorReading.bind(null, kMappedReading),
     ['accelerometer']);
 </script>
diff --git a/third_party/blink/web_tests/sensor/ambient-light-sensor.html b/third_party/blink/web_tests/sensor/ambient-light-sensor.html
index f4ef0ac..18b81b4 100644
--- a/third_party/blink/web_tests/sensor/ambient-light-sensor.html
+++ b/third_party/blink/web_tests/sensor/ambient-light-sensor.html
@@ -4,6 +4,7 @@
 <script src="../http/tests/resources/sensor-helpers.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/device/public/mojom/sensor_provider.mojom.js"></script>
+<script src="resources/generic-sensor-utils.js"></script>
 <script src="resources/generic-sensor-tests.js"></script>
 <script>
 
@@ -12,22 +13,12 @@
 if (!window.testRunner)
     debug('This test cannot be run without the TestRunner');
 
-const kDefaultReadingValue = 3.1415;
+const kDefaultReading = [3.1415];
 
-function update_sensor_reading(buffer) {
-  buffer[2] = kDefaultReadingValue;
-}
-
-function verify_sensor_reading({illuminance, timestamp}, is_null) {
-  if (is_null)
-    return illuminance === null && timestamp === null;
-  return illuminance === kDefaultReadingValue && timestamp !== null;
-}
-
-runGenericSensorTests(AmbientLightSensor,
-                      device.mojom.SensorType.AMBIENT_LIGHT,
-                      update_sensor_reading,
-                      verify_sensor_reading,
-                      null,
-                      ['ambient-light-sensor']);
+runGenericSensorTests(
+    AmbientLightSensor,
+    kDefaultReading,
+    verifyAlsSensorReading.bind(null, kDefaultReading),
+    null,
+    ['ambient-light-sensor']);
 </script>
diff --git a/third_party/blink/web_tests/sensor/gyroscope.html b/third_party/blink/web_tests/sensor/gyroscope.html
index 6ebcf9a2..0c34f33b 100644
--- a/third_party/blink/web_tests/sensor/gyroscope.html
+++ b/third_party/blink/web_tests/sensor/gyroscope.html
@@ -4,6 +4,7 @@
 <script src="../http/tests/resources/sensor-helpers.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/device/public/mojom/sensor_provider.mojom.js"></script>
+<script src="resources/generic-sensor-utils.js"></script>
 <script src="resources/generic-sensor-tests.js"></script>
 <script>
 
@@ -15,15 +16,10 @@
 const kDefaultReading = [1.12345, 2.12345, 3.12345];
 const kMappedReading = [-2.12345, 1.12345, 3.12345];
 
-function update_sensor_reading(buffer) {
-  buffer.set(kDefaultReading, 2);
-}
-
 runGenericSensorTests(
     Gyroscope,
-    device.mojom.SensorType.GYROSCOPE,
-    update_sensor_reading,
-    verify_xyz_sensor_reading.bind(null, kDefaultReading),
-    verify_xyz_sensor_reading.bind(null, kMappedReading),
+    kDefaultReading,
+    verifyXyzSensorReading.bind(null, kDefaultReading),
+    verifyXyzSensorReading.bind(null, kMappedReading),
     ['gyroscope']);
 </script>
diff --git a/third_party/blink/web_tests/sensor/magnetometer.html b/third_party/blink/web_tests/sensor/magnetometer.html
index bdeadc66..930e23d 100644
--- a/third_party/blink/web_tests/sensor/magnetometer.html
+++ b/third_party/blink/web_tests/sensor/magnetometer.html
@@ -4,6 +4,7 @@
 <script src="../http/tests/resources/sensor-helpers.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/device/public/mojom/sensor_provider.mojom.js"></script>
+<script src="resources/generic-sensor-utils.js"></script>
 <script src="resources/generic-sensor-tests.js"></script>
 <script>
 
@@ -15,15 +16,10 @@
 const kDefaultReading = [-19.2, 12.1, -44.3];
 const kMappedReading = [-12.1, -19.2, -44.3];
 
-function update_sensor_reading(buffer) {
-  buffer.set(kDefaultReading, 2);
-}
-
 runGenericSensorTests(
     Magnetometer,
-    device.mojom.SensorType.MAGNETOMETER,
-    update_sensor_reading,
-    verify_xyz_sensor_reading.bind(null, kDefaultReading),
-    verify_xyz_sensor_reading.bind(null, kMappedReading),
+    kDefaultReading,
+    verifyXyzSensorReading.bind(null, kDefaultReading),
+    verifyXyzSensorReading.bind(null, kMappedReading),
     ['magnetometer']);
 </script>
diff --git a/third_party/blink/web_tests/sensor/orientation-sensor.html b/third_party/blink/web_tests/sensor/orientation-sensor.html
index 2652750..c224d3f 100644
--- a/third_party/blink/web_tests/sensor/orientation-sensor.html
+++ b/third_party/blink/web_tests/sensor/orientation-sensor.html
@@ -4,6 +4,7 @@
 <script src="../http/tests/resources/sensor-helpers.js"></script>
 <script src="file:///gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
 <script src="file:///gen/services/device/public/mojom/sensor_provider.mojom.js"></script>
+<script src="resources/generic-sensor-utils.js"></script>
 <script src="resources/generic-sensor-tests.js"></script>
 <script>
 
@@ -12,7 +13,7 @@
 if (!window.testRunner)
     debug('This test cannot be run without the TestRunner');
 
-const kQuaternion = [1, 0, 0, 0]; // 180 degrees around X axis.
+const kDefaultReading = [1, 0, 0, 0]; // 180 degrees around X axis.
 const kRotationMatrix = [1,  0,  0,  0,
                          0, -1,  0,  0,
                          0,  0, -1,  0,
@@ -21,13 +22,9 @@
 
 // For 'orientation.angle == 270', which is set for tests at
 // at SensorProxy::GetScreenOrientationAngle().
-const kMappedQuaternion = [-0.707107, 0.707107, 0, 0];
+const kMappedReading = [-0.707107, 0.707107, 0, 0];
 
-function update_sensor_reading(buffer) {
-  buffer.set(kQuaternion, 2);
-}
-
-async function checkPopulateMatrix(sensorProvider, sensorType, mojomSensorType) {
+async function checkPopulateMatrix(sensorProvider, sensorType) {
   let sensorObject = new sensorType();
 
   // Throws with insufficient buffer space.
@@ -43,8 +40,8 @@
 
   sensorObject.start();
 
-  let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-  await mockSensor.setUpdateSensorReadingFunction(update_sensor_reading);
+  let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+  await mockSensor.setSensorReading(kDefaultReading);
   await new Promise((resolve, reject) => {
     let wrapper = new CallbackWrapper(() => {
       // Works for all supported types.
@@ -78,31 +75,27 @@
 
 runGenericSensorTests(
     AbsoluteOrientationSensor,
-    device.mojom.SensorType.ABSOLUTE_ORIENTATION_QUATERNION,
-    update_sensor_reading,
-    verify_quat_sensor_reading.bind(null, kQuaternion),
-    verify_quat_sensor_reading.bind(null, kMappedQuaternion),
+    kDefaultReading,
+    verifyQuatSensorReading.bind(null, kDefaultReading),
+    verifyQuatSensorReading.bind(null, kMappedReading),
     ['accelerometer', 'gyroscope', 'magnetometer']);
 
 sensor_test(sensorProvider => {
   return checkPopulateMatrix(
       sensorProvider,
-      AbsoluteOrientationSensor,
-      device.mojom.SensorType.ABSOLUTE_ORIENTATION_QUATERNION);
+      AbsoluteOrientationSensor);
 }, 'Test AbsoluteOrientationSensor.populateMatrix() method works correctly.');
 
 runGenericSensorTests(
     RelativeOrientationSensor,
-    device.mojom.SensorType.RELATIVE_ORIENTATION_QUATERNION,
-    update_sensor_reading,
-    verify_quat_sensor_reading.bind(null, kQuaternion),
-    verify_quat_sensor_reading.bind(null, kMappedQuaternion),
+    kDefaultReading,
+    verifyQuatSensorReading.bind(null, kDefaultReading),
+    verifyQuatSensorReading.bind(null, kMappedReading),
     ['accelerometer', 'gyroscope']);
 
 sensor_test(sensorProvider => {
   return checkPopulateMatrix(
       sensorProvider,
-      RelativeOrientationSensor,
-      device.mojom.SensorType.RELATIVE_ORIENTATION_QUATERNION);
+      RelativeOrientationSensor);
 }, 'Test RelativeOrientationSensor.populateMatrix() method works correctly.');
 </script>
diff --git a/third_party/blink/web_tests/sensor/resources/generic-sensor-tests.js b/third_party/blink/web_tests/sensor/resources/generic-sensor-tests.js
index c4540b38..8c354df 100644
--- a/third_party/blink/web_tests/sensor/resources/generic-sensor-tests.js
+++ b/third_party/blink/web_tests/sensor/resources/generic-sensor-tests.js
@@ -1,17 +1,19 @@
 'use strict';
 
-// Run a set of tests for a given |sensorType|. |updateReading| is
-// a called by the test to provide the mock values for sensor. |verifyReading|
-// is called so that the value read in JavaScript are the values expected (the ones
-// sent by |updateReading|).
+// Run a set of tests for a given |sensorType|. |readingData| is
+// set for providing the mock values for sensor. |verifyReading|
+// is called so that the value read in JavaScript are the values expected.
+// |verifyRemappedReading| is called for verifying the reading is mapped
+// to the screen coordinates for a spatial sensor. |featurePolicies| represents
+// the |sensorType|’s associated sensor feature name.
+
 function runGenericSensorTests(sensorType,
-                               mojomSensorType,
-                               updateReading,
+                               readingData,
                                verifyReading,
                                verifyRemappedReading,
                                featurePolicies) {
   sensor_test(sensorProvider => {
-    sensorProvider.setGetSensorShouldFail(mojomSensorType, true);
+    sensorProvider.setGetSensorShouldFail(sensorType.name, true);
     let sensorObject = new sensorType;
     sensorObject.start();
     return new Promise((resolve, reject) => {
@@ -27,7 +29,7 @@
   }, `${sensorType.name}: Test that onerror is sent when sensor is not supported.`);
 
   sensor_test(sensorProvider => {
-    sensorProvider.setPermissionsDenied(mojomSensorType, true);
+    sensorProvider.setPermissionsDenied(sensorType.name, true);
     let sensorObject = new sensorType;
     sensorObject.start();
     return new Promise((resolve, reject) => {
@@ -46,7 +48,7 @@
     let sensorObject = new sensorType({frequency: 560});
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     mockSensor.setStartShouldFail(true);
     await mockSensor.addConfigurationCalled();
     await new Promise((resolve, reject) => {
@@ -65,7 +67,7 @@
     let sensorObject = new sensorType();
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     mockSensor.setStartShouldFail(true);
     await mockSensor.addConfigurationCalled();
     return mockSensor.removeConfigurationCalled();
@@ -75,7 +77,7 @@
     let sensorObject = new sensorType({frequency: 560});
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await mockSensor.addConfigurationCalled();
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
@@ -93,7 +95,7 @@
   sensor_test(async sensorProvider => {
     let sensorObject = new sensorType();
     sensorObject.start();
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await mockSensor.addConfigurationCalled();
     await new Promise((resolve, reject) => {
       sensorObject.onactivate = () => {
@@ -115,7 +117,7 @@
     let sensorObject = new sensorType({frequency: 50});
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await mockSensor.addConfigurationCalled();
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
@@ -136,7 +138,7 @@
     let sensorObject = new sensorType({frequency: -1});
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await mockSensor.addConfigurationCalled();
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
@@ -157,7 +159,7 @@
     sensorObject.start();
     assert_false(sensorObject.activated);
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
         assert_true(sensorObject.activated);
@@ -175,7 +177,7 @@
     let sensorObject = new sensorType();
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
         assert_true(sensorObject.activated);
@@ -194,7 +196,7 @@
     let sensorObject = new sensorType({frequency: 60});
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
     await mockSensor.addConfigurationCalled();
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
@@ -214,8 +216,8 @@
     sensorObject.start();
     assert_false(sensorObject.hasReading);
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(updateReading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
         assert_true(verifyReading(sensorObject));
@@ -244,8 +246,8 @@
     let sensorObject = new sensorType({frequency: 60});
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(updateReading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
         assert_true(verifyReading(sensorObject));
@@ -276,8 +278,8 @@
     iframe.src = encodeURI(iframeSrc);
     iframe.allow = "focus-without-user-activation";
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(updateReading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
 
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
@@ -308,8 +310,8 @@
     let sensor2 = new sensorType({frequency: 20});
     sensor2.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(updateReading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
         // Reading values are correct for both sensors.
@@ -341,8 +343,8 @@
 
     let slowSensor;  // To be initialized later.
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(updateReading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
     await new Promise((resolve, reject) => {
       let fastSensorNotifiedCounter = 0;
       let slowSensorNotifiedCounter = 0;
@@ -451,8 +453,8 @@
     let timestamp = 0;
     sensorObject.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(updateReading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
     await new Promise((resolve, reject) => {
       let wrapper1 = new CallbackWrapper(() => {
         assert_true(sensorObject.hasReading);
@@ -494,8 +496,8 @@
     sensor1.start();
     sensor2.start();
 
-    let mockSensor = await sensorProvider.getCreatedSensor(mojomSensorType);
-    await mockSensor.setUpdateSensorReadingFunction(update_sensor_reading);
+    let mockSensor = await sensorProvider.getCreatedSensor(sensorType.name);
+    await mockSensor.setSensorReading(readingData);
     await new Promise((resolve, reject) => {
       let wrapper = new CallbackWrapper(() => {
         assert_true(verifyReading(sensor1));
diff --git a/third_party/blink/web_tests/sensor/resources/generic-sensor-utils.js b/third_party/blink/web_tests/sensor/resources/generic-sensor-utils.js
new file mode 100644
index 0000000..8cad13b
--- /dev/null
+++ b/third_party/blink/web_tests/sensor/resources/generic-sensor-utils.js
@@ -0,0 +1,27 @@
+'use strict';
+
+function verifySensorReading(pattern, values, timestamp, isNull) {
+  function round(val) {
+    return Number.parseFloat(val).toPrecision(6);
+  }
+
+  if (isNull) {
+    return (values === null || values.every(r => r === null)) &&
+           timestamp === null;
+  }
+
+  return values.every((r, i) => round(r) === round(pattern[i])) &&
+         timestamp !== null;
+}
+
+function verifyXyzSensorReading(pattern, {x, y, z, timestamp}, isNull) {
+  return verifySensorReading(pattern, [x, y, z], timestamp, isNull);
+}
+
+function verifyQuatSensorReading(pattern, {quaternion, timestamp}, isNull) {
+  return verifySensorReading(pattern, quaternion, timestamp, isNull);
+}
+
+function verifyAlsSensorReading(pattern, {illuminance, timestamp}, isNull) {
+  return verifySensorReading(pattern, [illuminance], timestamp, isNull);
+}
diff --git a/third_party/blink/web_tests/svg/animations/syncbases_with_tangled_dependencies.html b/third_party/blink/web_tests/svg/animations/syncbases_with_tangled_dependencies.html
new file mode 100644
index 0000000..fb559092
--- /dev/null
+++ b/third_party/blink/web_tests/svg/animations/syncbases_with_tangled_dependencies.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+
+<title>Syncbases with tangled dependencies</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+
+<svg viewBox="0 0 500 200" xmlns="http://www.w3.org/2000/svg">
+  <rect width="100%" height="100%" fill="none">
+    <animate id="minani" attributeName="x" fill="freeze" begin="min.click" dur="100ms" values="0; 0"/>
+    <animate id="maxani" attributeName="x" fill="freeze" begin="max.click" dur="100ms" values="0; 0"/>
+  </rect>
+  <rect id="min" width="100" height="100" fill="green">
+    <set attributeName="height" fill="freeze" begin="minani.begin" to="0"/>
+    <set attributeName="height" fill="freeze" begin="maxani.begin+0.1s" to="100"/>
+  </rect>
+  <rect id="max" x="110" width="100" height="100" fill="blue"/>
+</svg>
+<script>
+  async_test(t => {
+    let clicks = 3;
+    let min = document.querySelector("#min");
+    let max = document.querySelector("#max");
+    let i = setInterval(t.step_func(() => {
+      clicks--;
+      if (clicks == 0) {
+        clearInterval(i);
+        t.step_timeout(t.step_func_done(() => {
+          assert_equals(window.getComputedStyle(min, null).height, "100px");
+        }), 500);
+      }
+      min.dispatchEvent(new Event("click"));
+      window.setTimeout(() => max.dispatchEvent(new Event("click")), 130);
+    }), 250);
+  });
+</script>
diff --git a/third_party/blink/web_tests/vr/resources/vr-test-utils.js b/third_party/blink/web_tests/vr/resources/vr-test-utils.js
index 2bac718..c129ade7 100644
--- a/third_party/blink/web_tests/vr/resources/vr-test-utils.js
+++ b/third_party/blink/web_tests/vr/resources/vr-test-utils.js
@@ -30,12 +30,12 @@
       if (this.pose_.hasOwnProperty(field)) {
         let val = pose[field];
         if (field === "position") {
-          this.pose_[field] = new gfx.mojom.Point3F(val[0], val[1], val[2]);
+          this.pose_[field] = { x: val[0], y: val[1], z: val[2] };
         } else if (field === "orientation") {
-          this.pose_[field] = new gfx.mojom.Quaternion(val[0], val[1], val[2], val[3]);
+          this.pose_[field] = { x: val[0], y: val[1], z: val[2], w: val[3] };
         }else if (field === "angularVelocity" || field === "linearVelocity" ||
-                   field === "angularAcceleration" || field === "lienarAcceleration") {
-          this.pose_[field] = new gfx.mojom.Vector3dF(val[0], val[1], val[2]);
+                   field === "angularAcceleration" || field === "linearAcceleration") {
+          this.pose_[field] = { x: val[0], y: val[1], z: val[2] };
         } else {
           this.pose_[field] = pose[field];
         }
@@ -86,14 +86,14 @@
 
   let generic_left_eye = {
     fieldOfView : generic_left_fov,
-    offset : new gfx.mojom.Vector3dF(-0.03, 0, 0),
+    offset : { x: -0.03, y: 0, z: 0 },
     renderWidth : 1024,
     renderHeight : 1024
   };
 
   let generic_right_eye = {
     fieldOfView :generic_right_fov,
-    offset : new gfx.mojom.Vector3dF(0.03, 0, 0),
+    offset : { x: 0.03, y: 0, z: 0 },
     renderWidth : 1024,
     renderHeight : 1024
   };
@@ -152,7 +152,7 @@
           leftDegrees : 35.197,
           rightDegrees : 50.899,
         },
-        offset : new gfx.mojom.Vector3dF(-0.032, 0, 0),
+        offset : { x: -0.032, y: 0, z: 0 },
         renderWidth : 1920,
         renderHeight : 2160
       },
@@ -163,7 +163,7 @@
           leftDegrees: 50.899,
           rightDegrees: 35.197
         },
-        offset : new gfx.mojom.Vector3dF(0.032, 0, 0),
+        offset : { x: 0.032, y: 0, z: 0 },
         renderWidth : 1920,
         renderHeight : 2160
       },
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 69e48ef9..13b7fc1 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
@@ -4905,6 +4905,10 @@
     attribute @@toStringTag
     getter error
     method constructor
+interface NFCWriter
+    attribute @@toStringTag
+    method constructor
+    method push
 interface NamedNodeMap
     attribute @@toStringTag
     getter length
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 22e9359..9a0b5c2 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -23811,6 +23811,7 @@
   <int value="2949" label="CredentialManagerGetWithUVM"/>
   <int value="2950" label="CredentialManagerCreateSuccessWithUVM"/>
   <int value="2951" label="CredentialManagerGetSuccessWithUVM"/>
+  <int value="2952" label="DiscardInputEventToMovingIframe"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index a433eb10..082d932a 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -70323,7 +70323,7 @@
 </histogram>
 
 <histogram name="Net.HttpAuthPromptType" enum="HttpAuthPromptType"
-    expires_after="M77">
+    expires_after="M80">
   <owner>meacer@chromium.org</owner>
   <summary>Type of the HTTP auth prompt displayed.</summary>
 </histogram>
diff --git a/tools/perf/cli_tools/soundwave/studies/__init__.py b/tools/perf/cli_tools/soundwave/studies/__init__.py
index 72b4e19..8e18c687 100644
--- a/tools/perf/cli_tools/soundwave/studies/__init__.py
+++ b/tools/perf/cli_tools/soundwave/studies/__init__.py
@@ -26,11 +26,11 @@
   df['reference'] = df['timestamp'].dt.date == df.groupby(
       'quarter')['timestamp'].transform('max').dt.date
 
-  # Change unit for values in ms to seconds.
-  # TODO: Get and use unit information from the dashboard instead of trying to
-  # guess by the measurement name.
-  is_ms_unit = (df['measurement'].str.startswith('timeTo') |
-                df['measurement'].str.endswith(':duration'))
+  # Change units for values in ms to seconds, and percent values.
+  is_ms_unit = df['units'].str.startswith('ms_')
   df.loc[is_ms_unit, 'value'] = df['value'] / 1000
 
+  is_percentage = df['units'].str.startswith('n%_')
+  df.loc[is_percentage, 'value'] = df['value'] * 100
+
   return df
diff --git a/tools/perf/cli_tools/soundwave/studies/health_study.py b/tools/perf/cli_tools/soundwave/studies/health_study.py
index f00115b..4abbf05 100644
--- a/tools/perf/cli_tools/soundwave/studies/health_study.py
+++ b/tools/perf/cli_tools/soundwave/studies/health_study.py
@@ -2,46 +2,71 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from core.services import dashboard_service
+from cli_tools.soundwave.tables import timeseries
+
+
 CLOUD_PATH = 'gs://chrome-health-tvdata/datasets/health_study.csv'
 
-OVERALL_PSS = ('memory:{browser}:all_processes:reported_by_os:system_memory'
-               ':proportional_resident_size_avg')
-
-BATTERY = [
-    'power.typical_10_mobile',
-    'application_energy_consumption_mwh'
+SYSTEM_HEALTH = [
+    {
+        'test_suite': 'system_health.memory_mobile',
+        'measurement': ('memory:{browser}:all_processes:reported_by_os:'
+                        'private_footprint_size'),
+    },
+    {
+        'test_suite': 'system_health.common_mobile',
+        'measurement': 'cpu_time_percentage'
+    }
 ]
 
 STARTUP_BY_BROWSER = {
-    'chrome': [
-        'startup.mobile',
-        'first_contentful_paint_time_avg',
-        'intent_coldish_bbc'
-    ],
-    'webview': [
-        'system_health.webview_startup',
-        'webview_startup_wall_time_avg',
-        'load_chrome/load_chrome_blank'
-    ]
+    'chrome': {
+        'test_suite': 'startup.mobile',
+        'measurement': 'first_contentful_paint_time',
+        'test_case': 'intent_coldish_bbc'
+    },
+    'webview': {
+        'test_suite': 'system_health.webview_startup',
+        'measurement': 'webview_startup_wall_time_avg',
+        'test_case': 'load:chrome:blank'
+    }
 }
 
 
 def IterSystemHealthBots():
-  yield 'ChromiumPerf/android-go-perf'
-  yield 'ChromiumPerf/android-go_webview-perf'
-  yield 'ChromiumPerf/android-pixel2-perf'
-  yield 'ChromiumPerf/android-pixel2_webview-perf'
+  yield 'ChromiumPerf:android-go-perf'
+  yield 'ChromiumPerf:android-go_webview-perf'
+  yield 'ChromiumPerf:android-pixel2-perf'
+  yield 'ChromiumPerf:android-pixel2_webview-perf'
 
 
 def GetBrowserFromBot(bot):
   return 'webview' if 'webview' in bot else 'chrome'
 
 
+def GetHealthCheckStories():
+  description = dashboard_service.Describe('system_health.common_mobile')
+  return description['caseTags']['health_check']
+
+
 def IterTestPaths():
+  test_cases = GetHealthCheckStories()
   for bot in IterSystemHealthBots():
     browser = GetBrowserFromBot(bot)
-    overall_pss = OVERALL_PSS.format(browser=browser)
-    for story_group in ('foreground', 'background'):
-      yield '/'.join([bot, 'memory.top_10_mobile', overall_pss, story_group])
-    yield '/'.join([bot] + BATTERY)
-    yield '/'.join([bot] + STARTUP_BY_BROWSER[browser])
+    series = STARTUP_BY_BROWSER[browser].copy()
+    series['bot'] = bot
+    yield timeseries.Key(**series)
+
+    if bot == 'ChromiumPerf:android-pixel2_webview-perf':
+      # The pixel2 webview bot incorrectly reports memory as if coming from
+      # chrome. TODO(crbug.com/972620): Remove this when bug is fixed.
+      browser = 'chrome'
+
+    for series in SYSTEM_HEALTH:
+      series = series.copy()
+      series['bot'] = bot
+      series['measurement'] = series['measurement'].format(browser=browser)
+      for test_case in test_cases:
+        series['test_case'] = test_case
+        yield timeseries.Key(**series)
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
index e841ba0c..bdc2f60 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -168,7 +168,11 @@
           ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary);
       break;
     case TextUnit_Paragraph:
-      return E_NOTIMPL;
+      start_ = start_->CreatePreviousParagraphStartPosition(
+          ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+      end_ = start_->CreateNextParagraphEndPosition(
+          ui::AXBoundaryBehavior::StopIfAlreadyAtBoundary);
+      break;
     // Since web content is not paginated, TextUnit_Page is not supported.
     // Substituting it by the next larger unit: TextUnit_Document.
     case TextUnit_Page:
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index 5c06ac2..b04cbda 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -400,6 +400,8 @@
     ui::AXNodeData group1_data;
     group1_data.id = 2;
     group1_data.role = ax::mojom::Role::kGenericContainer;
+    group1_data.AddBoolAttribute(
+        ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
 
     ui::AXNodeData text_data;
     text_data.id = 3;
@@ -484,6 +486,8 @@
         ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
     paragraph1_text_data.AddIntListAttribute(
         ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
+    paragraph1_data.AddBoolAttribute(
+        ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
     paragraph1_data.child_ids = {10};
 
     ui::AXNodeData paragraph2_data;
@@ -503,6 +507,8 @@
         ax::mojom::IntListAttribute::kWordStarts, word_start_offsets);
     paragraph2_text_data.AddIntListAttribute(
         ax::mojom::IntListAttribute::kWordEnds, word_end_offsets);
+    paragraph1_data.AddBoolAttribute(
+        ax::mojom::BoolAttribute::kIsLineBreakingObject, true);
     paragraph2_data.child_ids = {12};
 
     ui::AXNodeData root_data;
@@ -1052,6 +1058,67 @@
 }
 
 TEST_F(AXPlatformNodeTextRangeProviderTest,
+       TestITextRangeProviderExpandToEnclosingParagraph) {
+  Init(BuildAXTreeForMove());
+  AXNodePosition::SetTreeForTesting(tree_.get());
+  AXNode* root_node = GetRootNode();
+
+  ComPtr<ITextRangeProvider> text_range_provider;
+  GetTextRangeProviderFromTextNode(text_range_provider, root_node);
+
+  EXPECT_UIA_TEXTRANGE_EQ(
+      text_range_provider,
+      L"First line of text\nStandalone line\nbold textParagraph 1Paragraph 2");
+
+  // Start endpoint is already on a paragraph's start boundary.
+  int count;
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_End, TextUnit_Paragraph, /*count*/ -6, &count));
+  EXPECT_EQ(-6, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
+
+  ASSERT_HRESULT_SUCCEEDED(
+      text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"First line of text");
+
+  // Moving the start by two lines will create a degenerate range positioned
+  // at the next paragraph (skipping the newline)
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 2, &count));
+  EXPECT_EQ(2, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
+
+  ASSERT_HRESULT_SUCCEEDED(
+      text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Standalone line\n");
+
+  // Move to the next paragraph via MoveEndpointByUnit (line), then move to
+  // the middle of the paragraph via Move (word), then expand by paragraph.
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_Start, TextUnit_Line, /*count*/ 1, &count));
+  EXPECT_EQ(1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
+  EXPECT_UIA_MOVE(text_range_provider, TextUnit_Word,
+                  /*count*/ 1,
+                  /*expected_text*/
+                  L"",
+                  /*expected_count*/ 1);
+  ASSERT_HRESULT_SUCCEEDED(
+      text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"bold text");
+
+  // Create a degenerate range at the end of the document, then expand by
+  // paragraph
+  ASSERT_HRESULT_SUCCEEDED(text_range_provider->MoveEndpointByUnit(
+      TextPatternRangeEndpoint_Start, TextUnit_Document, /*count*/ 1, &count));
+  EXPECT_EQ(1, count);
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"");
+  ASSERT_HRESULT_SUCCEEDED(
+      text_range_provider->ExpandToEnclosingUnit(TextUnit_Paragraph));
+  EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, L"Paragraph 2");
+}
+
+TEST_F(AXPlatformNodeTextRangeProviderTest,
        TestITextRangeProviderExpandToEnclosingFormat) {
   Init(BuildAXTreeForMoveByFormat());
   AXNodePosition::SetTreeForTesting(tree_.get());
@@ -1697,18 +1764,22 @@
   // Move forward.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                   /*count*/ 1,
-                  /*expected_text*/ L"Standalone line\n",
+                  /*expected_text*/ L"\n",
                   /*expected_count*/ 1);
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                   /*count*/ 2,
-                  /*expected_text*/ L"Paragraph 1",
+                  /*expected_text*/ L"bold text",
                   /*expected_count*/ 2);
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                   /*count*/ 1,
+                  /*expected_text*/ L"Paragraph 1",
+                  /*expected_count*/ 1);
+  EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
+                  /*count*/ 2,
                   /*expected_text*/ L"Paragraph 2",
                   /*expected_count*/ 1);
 
-  // Trying to move past the last format should have no effect.
+  // Trying to move past the last paragraph should have no effect.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                   /*count*/ 1,
                   /*expected_text*/ L"Paragraph 2",
@@ -1720,16 +1791,16 @@
                   /*expected_text*/ L"Standalone line\n",
                   /*expected_count*/ -3);
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
-                  /*count*/ -1,
-                  /*expected_text*/ L"First line of text\n",
-                  /*expected_count*/ -1);
+                  /*count*/ -2,
+                  /*expected_text*/ L"First line of text",
+                  /*expected_count*/ -2);
 
   // Moving backward by any number of paragraphs at the start of document
   // should have no effect.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                   /*count*/ -1,
                   /*expected_text*/
-                  L"First line of text\n",
+                  L"First line of text",
                   /*expected_count*/ 0);
 
   // Test degenerate range creation at the beginning of the document.
@@ -1741,14 +1812,14 @@
   EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
       text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
       /*count*/ 1,
-      /*expected_text*/ L"First line of text\n",
+      /*expected_text*/ L"First line of text",
       /*expected_count*/ 1);
 
   // Test degenerate range creation at the end of the document.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
-                  /*count*/ 5,
+                  /*count*/ 6,
                   /*expected_text*/ L"Paragraph 2",
-                  /*expected_count*/ 4);
+                  /*expected_count*/ 5);
   EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
       text_range_provider, TextPatternRangeEndpoint_Start, TextUnit_Paragraph,
       /*count*/ 1,
@@ -1772,9 +1843,9 @@
 
   // Degenerate range moves.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
-                  /*count*/ -6,
-                  /*expected_text*/ L"First line of text\n",
-                  /*expected_count*/ -5);
+                  /*count*/ -7,
+                  /*expected_text*/ L"First line of text",
+                  /*expected_count*/ -6);
   EXPECT_UIA_MOVE_ENDPOINT_BY_UNIT(
       text_range_provider, TextPatternRangeEndpoint_End, TextUnit_Paragraph,
       /*count*/ -1,
@@ -1787,7 +1858,7 @@
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
                   /*count*/ 70,
                   /*expected_text*/ L"",
-                  /*expected_count*/ 2);
+                  /*expected_count*/ 3);
 
   // Trying to move past the last paragraph should have no effect.
   EXPECT_UIA_MOVE(text_range_provider, TextUnit_Paragraph,
diff --git a/ui/compositor/layer.cc b/ui/compositor/layer.cc
index 0a1dcf6..7ac8af38 100644
--- a/ui/compositor/layer.cc
+++ b/ui/compositor/layer.cc
@@ -484,7 +484,7 @@
 }
 
 void Layer::SetClipRect(const gfx::Rect& clip_rect) {
-  cc_layer_->SetClipRect(clip_rect);
+  GetAnimator()->SetClipRect(clip_rect);
 }
 
 void Layer::SetOpacity(float opacity) {
@@ -1416,6 +1416,11 @@
   SetFillsBoundsOpaquely(SkColorGetA(color) == 0xFF);
 }
 
+void Layer::SetClipRectFromAnimation(const gfx::Rect& clip_rect,
+                                     PropertyChangeReason reason) {
+  cc_layer_->SetClipRect(clip_rect);
+}
+
 void Layer::ScheduleDrawForAnimation() {
   ScheduleDraw();
 }
@@ -1451,6 +1456,12 @@
       solid_color_layer_->background_color() : SK_ColorBLACK;
 }
 
+gfx::Rect Layer::GetClipRectForAnimation() const {
+  if (clip_rect().IsEmpty())
+    return gfx::Rect(size());
+  return clip_rect();
+}
+
 float Layer::GetDeviceScaleFactor() const {
   return device_scale_factor_;
 }
diff --git a/ui/compositor/layer.h b/ui/compositor/layer.h
index 25421525..3579e9a 100644
--- a/ui/compositor/layer.h
+++ b/ui/compositor/layer.h
@@ -541,6 +541,8 @@
                                  PropertyChangeReason reason) override;
   void SetColorFromAnimation(SkColor color,
                              PropertyChangeReason reason) override;
+  void SetClipRectFromAnimation(const gfx::Rect& clip_rect,
+                                PropertyChangeReason reason) override;
   void ScheduleDrawForAnimation() override;
   const gfx::Rect& GetBoundsForAnimation() const override;
   gfx::Transform GetTransformForAnimation() const override;
@@ -549,6 +551,7 @@
   float GetBrightnessForAnimation() const override;
   float GetGrayscaleForAnimation() const override;
   SkColor GetColorForAnimation() const override;
+  gfx::Rect GetClipRectForAnimation() const override;
   float GetDeviceScaleFactor() const override;
   ui::Layer* GetLayer() override;
   cc::Layer* GetCcLayer() const override;
diff --git a/ui/compositor/layer_animation_delegate.h b/ui/compositor/layer_animation_delegate.h
index fcd0de1..9c700c54 100644
--- a/ui/compositor/layer_animation_delegate.h
+++ b/ui/compositor/layer_animation_delegate.h
@@ -38,6 +38,8 @@
                                          PropertyChangeReason reason) = 0;
   virtual void SetColorFromAnimation(SkColor color,
                                      PropertyChangeReason reason) = 0;
+  virtual void SetClipRectFromAnimation(const gfx::Rect& clip_rect,
+                                        PropertyChangeReason reason) = 0;
   virtual void ScheduleDrawForAnimation() = 0;
   virtual const gfx::Rect& GetBoundsForAnimation() const = 0;
   virtual gfx::Transform GetTransformForAnimation() const = 0;
@@ -46,6 +48,7 @@
   virtual float GetBrightnessForAnimation() const = 0;
   virtual float GetGrayscaleForAnimation() const = 0;
   virtual SkColor GetColorForAnimation() const = 0;
+  virtual gfx::Rect GetClipRectForAnimation() const = 0;
   virtual float GetDeviceScaleFactor() const = 0;
   virtual ui::Layer* GetLayer() = 0;
   virtual cc::Layer* GetCcLayer() const = 0;
diff --git a/ui/compositor/layer_animation_element.cc b/ui/compositor/layer_animation_element.cc
index 3d965dd..2c7b44e 100644
--- a/ui/compositor/layer_animation_element.cc
+++ b/ui/compositor/layer_animation_element.cc
@@ -269,6 +269,40 @@
   DISALLOW_COPY_AND_ASSIGN(ColorTransition);
 };
 
+// ClipRectTransition ----------------------------------------------------------
+
+class ClipRectTransition : public LayerAnimationElement {
+ public:
+  ClipRectTransition(const gfx::Rect& target, base::TimeDelta duration)
+      : LayerAnimationElement(CLIP, duration), target_(target) {}
+  ~ClipRectTransition() override {}
+
+ protected:
+  std::string DebugName() const override { return "ClipRectTransition"; }
+  void OnStart(LayerAnimationDelegate* delegate) override {
+    start_ = delegate->GetClipRectForAnimation();
+  }
+
+  bool OnProgress(double t, LayerAnimationDelegate* delegate) override {
+    delegate->SetClipRectFromAnimation(
+        gfx::Tween::RectValueBetween(t, start_, target_),
+        PropertyChangeReason::FROM_ANIMATION);
+    return true;
+  }
+
+  void OnGetTarget(TargetValue* target) const override {
+    target->clip_rect = target_;
+  }
+
+  void OnAbort(LayerAnimationDelegate* delegate) override {}
+
+ private:
+  gfx::Rect start_;
+  const gfx::Rect target_;
+
+  DISALLOW_COPY_AND_ASSIGN(ClipRectTransition);
+};
+
 // ThreadedLayerAnimationElement -----------------------------------------------
 
 class ThreadedLayerAnimationElement : public LayerAnimationElement {
@@ -487,8 +521,8 @@
       visibility(delegate ? delegate->GetVisibilityForAnimation() : false),
       brightness(delegate ? delegate->GetBrightnessForAnimation() : 0.0f),
       grayscale(delegate ? delegate->GetGrayscaleForAnimation() : 0.0f),
-      color(delegate ? delegate->GetColorForAnimation() : SK_ColorTRANSPARENT) {
-}
+      color(delegate ? delegate->GetColorForAnimation() : SK_ColorTRANSPARENT),
+      clip_rect(delegate ? delegate->GetClipRectForAnimation() : gfx::Rect()) {}
 
 // LayerAnimationElement -------------------------------------------------------
 
@@ -698,6 +732,9 @@
         case COLOR:
           str.append("COLOR");
           break;
+        case CLIP:
+          str.append("CLIP");
+          break;
         case SENTINEL:
           NOTREACHED();
           break;
@@ -793,4 +830,10 @@
   return std::make_unique<ColorTransition>(color, duration);
 }
 
+std::unique_ptr<LayerAnimationElement>
+LayerAnimationElement::CreateClipRectElement(const gfx::Rect& clip_rect,
+                                             base::TimeDelta duration) {
+  return std::make_unique<ClipRectTransition>(clip_rect, duration);
+}
+
 }  // namespace ui
diff --git a/ui/compositor/layer_animation_element.h b/ui/compositor/layer_animation_element.h
index 5088f879..9c5df9b 100644
--- a/ui/compositor/layer_animation_element.h
+++ b/ui/compositor/layer_animation_element.h
@@ -46,10 +46,11 @@
     BRIGHTNESS = (1 << 4),
     GRAYSCALE = (1 << 5),
     COLOR = (1 << 6),
+    CLIP = (1 << 7),
 
     // Used when iterating over properties.
     FIRST_PROPERTY = TRANSFORM,
-    SENTINEL = (1 << 7)
+    SENTINEL = (1 << 8)
   };
 
   static AnimatableProperty ToAnimatableProperty(
@@ -67,6 +68,7 @@
     float brightness;
     float grayscale;
     SkColor color;
+    gfx::Rect clip_rect;
   };
 
   typedef uint32_t AnimatableProperties;
@@ -139,6 +141,12 @@
       SkColor color,
       base::TimeDelta duration);
 
+  // Creates an element that transitions the clip rect of the layer to the given
+  // bounds. The caller owns the return value.
+  static std::unique_ptr<LayerAnimationElement> CreateClipRectElement(
+      const gfx::Rect& clip_rect,
+      base::TimeDelta duration);
+
   // Sets the start time for the animation. This must be called before the first
   // call to {Start, IsFinished}. Once the animation is finished, this must
   // be called again in order to restart the animation.
diff --git a/ui/compositor/layer_animation_element_unittest.cc b/ui/compositor/layer_animation_element_unittest.cc
index 664daed..22a3b22 100644
--- a/ui/compositor/layer_animation_element_unittest.cc
+++ b/ui/compositor/layer_animation_element_unittest.cc
@@ -31,6 +31,7 @@
   const float kBrightness = 2.358f;
   const float kGrayscale = 2.5813f;
   const SkColor kColor = SK_ColorCYAN;
+  const gfx::Rect kClipRect(2, 3, 4, 5);
 
   TestLayerAnimationDelegate delegate;
   delegate.SetBoundsFromAnimation(kBounds,
@@ -47,6 +48,8 @@
                                      PropertyChangeReason::NOT_FROM_ANIMATION);
   delegate.SetColorFromAnimation(kColor,
                                  PropertyChangeReason::NOT_FROM_ANIMATION);
+  delegate.SetClipRectFromAnimation(kClipRect,
+                                    PropertyChangeReason::NOT_FROM_ANIMATION);
 
   LayerAnimationElement::TargetValue target_value(&delegate);
 
@@ -57,6 +60,7 @@
   EXPECT_FLOAT_EQ(kBrightness, target_value.brightness);
   EXPECT_FLOAT_EQ(kGrayscale, target_value.grayscale);
   EXPECT_EQ(SK_ColorCYAN, target_value.color);
+  EXPECT_EQ(kClipRect, target_value.clip_rect);
 }
 
 // Check that the transformation element progresses the delegate as expected and
@@ -365,6 +369,57 @@
                   copy.GetGrayscaleForAnimation());
 }
 
+// Check that the ClipRect element progresses the delegate as expected and
+// that the element can be reused after it completes.
+TEST(LayerAnimationElementTest, ClipRectElement) {
+  TestLayerAnimationDelegate delegate;
+  gfx::Rect start, target, middle;
+  start = target = middle = gfx::Rect(0, 0, 50, 50);
+
+  start.set_x(-10);
+  target.set_x(10);
+
+  start.set_y(-20);
+  target.set_y(20);
+
+  start.set_width(70);
+  target.set_width(30);
+  base::TimeTicks start_time;
+  base::TimeDelta delta = base::TimeDelta::FromSeconds(1);
+
+  std::unique_ptr<LayerAnimationElement> element =
+      LayerAnimationElement::CreateClipRectElement(target, delta);
+
+  for (int i = 0; i < 2; ++i) {
+    start_time += delta;
+    element->set_requested_start_time(start_time);
+    delegate.SetClipRectFromAnimation(start,
+                                      PropertyChangeReason::NOT_FROM_ANIMATION);
+    element->Start(&delegate, 1);
+    element->Progress(start_time, &delegate);
+    CheckApproximatelyEqual(start, delegate.GetClipRectForAnimation());
+    delegate.ExpectLastPropertyChangeReason(
+        PropertyChangeReason::FROM_ANIMATION);
+    element->Progress(start_time + delta / 2, &delegate);
+    CheckApproximatelyEqual(middle, delegate.GetClipRectForAnimation());
+    delegate.ExpectLastPropertyChangeReason(
+        PropertyChangeReason::FROM_ANIMATION);
+
+    base::TimeDelta element_duration;
+    EXPECT_TRUE(element->IsFinished(start_time + delta, &element_duration));
+    EXPECT_EQ(delta, element_duration);
+
+    element->Progress(start_time + delta, &delegate);
+    CheckApproximatelyEqual(target, delegate.GetClipRectForAnimation());
+    delegate.ExpectLastPropertyChangeReason(
+        PropertyChangeReason::FROM_ANIMATION);
+  }
+
+  LayerAnimationElement::TargetValue target_value(&delegate);
+  element->GetTargetValue(&target_value);
+  CheckApproximatelyEqual(target, target_value.clip_rect);
+}
+
 // Check that a threaded opacity element updates the delegate as expected when
 // aborted.
 TEST(LayerAnimationElementTest, AbortOpacityElement) {
diff --git a/ui/compositor/layer_animator.cc b/ui/compositor/layer_animator.cc
index c82b179..3b42c54 100644
--- a/ui/compositor/layer_animator.cc
+++ b/ui/compositor/layer_animator.cc
@@ -120,6 +120,7 @@
 ANIMATED_PROPERTY(float, BRIGHTNESS, Brightness, float, brightness)
 ANIMATED_PROPERTY(float, GRAYSCALE, Grayscale, float, grayscale)
 ANIMATED_PROPERTY(SkColor, COLOR, Color, SkColor, color)
+ANIMATED_PROPERTY(const gfx::Rect&, CLIP, ClipRect, gfx::Rect, clip_rect)
 
 #undef ANIMATED_PROPERTY
 
diff --git a/ui/compositor/layer_animator.h b/ui/compositor/layer_animator.h
index 842f111..0d555b3 100644
--- a/ui/compositor/layer_animator.h
+++ b/ui/compositor/layer_animator.h
@@ -100,6 +100,10 @@
   virtual void SetColor(SkColor color);
   SkColor GetTargetColor() const;
 
+  // Sets the clip rect on the delegate. May cause an implicit animation.
+  virtual void SetClipRect(const gfx::Rect& clip_rect);
+  gfx::Rect GetTargetClipRect() const;
+
   // Returns the default length of animations, including adjustment for slow
   // animation mode if set.
   base::TimeDelta GetTransitionDuration() const;
diff --git a/ui/compositor/test/test_layer_animation_delegate.cc b/ui/compositor/test/test_layer_animation_delegate.cc
index d4ae03be..e8781fd 100644
--- a/ui/compositor/test/test_layer_animation_delegate.cc
+++ b/ui/compositor/test/test_layer_animation_delegate.cc
@@ -109,6 +109,14 @@
   last_property_change_reason_is_set_ = true;
 }
 
+void TestLayerAnimationDelegate::SetClipRectFromAnimation(
+    const gfx::Rect& clip_rect,
+    PropertyChangeReason reason) {
+  clip_rect_ = clip_rect;
+  last_property_change_reason_ = reason;
+  last_property_change_reason_is_set_ = true;
+}
+
 void TestLayerAnimationDelegate::ScheduleDrawForAnimation() {
 }
 
@@ -140,6 +148,10 @@
   return color_;
 }
 
+gfx::Rect TestLayerAnimationDelegate::GetClipRectForAnimation() const {
+  return clip_rect_;
+}
+
 float TestLayerAnimationDelegate::GetDeviceScaleFactor() const {
   return 1.0f;
 }
diff --git a/ui/compositor/test/test_layer_animation_delegate.h b/ui/compositor/test/test_layer_animation_delegate.h
index ea9ed71a..2bac9f25 100644
--- a/ui/compositor/test/test_layer_animation_delegate.h
+++ b/ui/compositor/test/test_layer_animation_delegate.h
@@ -60,6 +60,8 @@
                                  PropertyChangeReason reason) override;
   void SetColorFromAnimation(SkColor color,
                              PropertyChangeReason reason) override;
+  void SetClipRectFromAnimation(const gfx::Rect& clip_rect,
+                                PropertyChangeReason reason) override;
   void ScheduleDrawForAnimation() override;
   const gfx::Rect& GetBoundsForAnimation() const override;
   gfx::Transform GetTransformForAnimation() const override;
@@ -68,6 +70,7 @@
   float GetBrightnessForAnimation() const override;
   float GetGrayscaleForAnimation() const override;
   SkColor GetColorForAnimation() const override;
+  gfx::Rect GetClipRectForAnimation() const override;
   float GetDeviceScaleFactor() const override;
   LayerAnimatorCollection* GetLayerAnimatorCollection() override;
   ui::Layer* GetLayer() override;
@@ -92,6 +95,7 @@
   float brightness_;
   float grayscale_;
   SkColor color_;
+  gfx::Rect clip_rect_;
   scoped_refptr<cc::Layer> cc_layer_;
   int frame_number_ = 0;
 
diff --git a/ui/file_manager/externs/entry_location.js b/ui/file_manager/externs/entry_location.js
index b78a238..7174733 100644
--- a/ui/file_manager/externs/entry_location.js
+++ b/ui/file_manager/externs/entry_location.js
@@ -7,49 +7,51 @@
  * file system.
  * @interface
  */
-function EntryLocation() {}
+class EntryLocation {
+  constructor() {
+    /**
+     * Volume information.
+     * @type {!VolumeInfo}
+     */
+    this.volumeInfo;
 
-/**
- * Volume information.
- * @type {!VolumeInfo}
- */
-EntryLocation.prototype.volumeInfo;
+    /**
+     * Root type.
+     * @type {VolumeManagerCommon.RootType}
+     */
+    this.rootType;
 
-/**
- * Root type.
- * @type {VolumeManagerCommon.RootType}
- */
-EntryLocation.prototype.rootType;
+    /**
+     * Whether the entry is root entry or not.
+     * @type {boolean}
+     */
+    this.isRootEntry;
 
-/**
- * Whether the entry is root entry or not.
- * @type {boolean}
- */
-EntryLocation.prototype.isRootEntry;
+    /**
+     * Whether the location obtained from the fake entry corresponds to special
+     * searches.
+     * @type {boolean}
+     */
+    this.isSpecialSearchRoot;
 
-/**
- * Whether the location obtained from the fake entry corresponds to special
- * searches.
- * @type {boolean}
- */
-EntryLocation.prototype.isSpecialSearchRoot;
+    /**
+     * Whether the location is under Google Drive or a special search root which
+     * represents a special search from Google Drive.
+     * @type {boolean}
+     */
+    this.isDriveBased;
 
-/**
- * Whether the location is under Google Drive or a special search root which
- * represents a special search from Google Drive.
- * @type {boolean}
- */
-EntryLocation.prototype.isDriveBased;
+    /**
+     * Whether the entry is read only or not.
+     * @type {boolean}
+     */
+    this.isReadOnly;
 
-/**
- * Whether the entry is read only or not.
- * @type {boolean}
- */
-EntryLocation.prototype.isReadOnly;
-
-/**
- * Whether the entry should be displayed with a fixed name instead of individual
- * entry's name. (e.g. "Downloads" is a fixed name)
- * @type {boolean}
- */
-EntryLocation.prototype.hasFixedLabel;
+    /**
+     * Whether the entry should be displayed with a fixed name instead of
+     * individual entry's name. (e.g. "Downloads" is a fixed name)
+     * @type {boolean}
+     */
+    this.hasFixedLabel;
+  }
+}
diff --git a/ui/file_manager/file_manager/background/js/entry_location_impl.js b/ui/file_manager/file_manager/background/js/entry_location_impl.js
index 90213cf..9c369a7 100644
--- a/ui/file_manager/file_manager/background/js/entry_location_impl.js
+++ b/ui/file_manager/file_manager/background/js/entry_location_impl.js
@@ -6,49 +6,52 @@
  * Location information which shows where the path points in FileManager's
  * file system.
  *
- * @param {VolumeInfo} volumeInfo Volume information.
- * @param {VolumeManagerCommon.RootType} rootType Root type.
- * @param {boolean} isRootEntry Whether the entry is root entry or not.
- * @param {boolean} isReadOnly Whether the entry is read only or not.
- * @constructor
  * @implements {EntryLocation}
  */
-function EntryLocationImpl(volumeInfo, rootType, isRootEntry, isReadOnly) {
-  /** @override */
-  this.volumeInfo = volumeInfo;
+class EntryLocationImpl {
+  /**
+   * @param {VolumeInfo} volumeInfo Volume information.
+   * @param {VolumeManagerCommon.RootType} rootType Root type.
+   * @param {boolean} isRootEntry Whether the entry is root entry or not.
+   * @param {boolean} isReadOnly Whether the entry is read only or not.
+   */
+  constructor(volumeInfo, rootType, isRootEntry, isReadOnly) {
+    /** @override */
+    this.volumeInfo = volumeInfo;
 
-  /** @override */
-  this.rootType = rootType;
+    /** @override */
+    this.rootType = rootType;
 
-  /** @override */
-  this.isRootEntry = isRootEntry;
+    /** @override */
+    this.isRootEntry = isRootEntry;
 
-  /** @override */
-  this.isSpecialSearchRoot =
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
-      this.rootType === VolumeManagerCommon.RootType.RECENT;
+    /** @override */
+    this.isSpecialSearchRoot =
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
+        this.rootType === VolumeManagerCommon.RootType.RECENT;
 
-  /** @override */
-  this.isDriveBased = this.rootType === VolumeManagerCommon.RootType.DRIVE ||
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER ||
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
-      this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
-      this.rootType === VolumeManagerCommon.RootType.SHARED_DRIVES_GRAND_ROOT ||
-      this.rootType === VolumeManagerCommon.RootType.SHARED_DRIVE ||
-      this.rootType === VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT ||
-      this.rootType === VolumeManagerCommon.RootType.COMPUTER;
+    /** @override */
+    this.isDriveBased = this.rootType === VolumeManagerCommon.RootType.DRIVE ||
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_OTHER ||
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_SHARED_WITH_ME ||
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_RECENT ||
+        this.rootType === VolumeManagerCommon.RootType.DRIVE_OFFLINE ||
+        this.rootType ===
+            VolumeManagerCommon.RootType.SHARED_DRIVES_GRAND_ROOT ||
+        this.rootType === VolumeManagerCommon.RootType.SHARED_DRIVE ||
+        this.rootType === VolumeManagerCommon.RootType.COMPUTERS_GRAND_ROOT ||
+        this.rootType === VolumeManagerCommon.RootType.COMPUTER;
 
-  /** @override */
-  this.isReadOnly = isReadOnly;
+    /** @override */
+    this.isReadOnly = isReadOnly;
 
-  /** @type{boolean} */
-  this.hasFixedLabel = this.isRootEntry &&
-      (rootType !== VolumeManagerCommon.RootType.SHARED_DRIVE &&
-       rootType !== VolumeManagerCommon.RootType.COMPUTER &&
-       rootType !== VolumeManagerCommon.RootType.REMOVABLE);
-
-  Object.freeze(this);
+    /** @type{boolean} */
+    this.hasFixedLabel = this.isRootEntry &&
+        (rootType !== VolumeManagerCommon.RootType.SHARED_DRIVE &&
+         rootType !== VolumeManagerCommon.RootType.COMPUTER &&
+         rootType !== VolumeManagerCommon.RootType.REMOVABLE);
+    Object.freeze(this);
+  }
 }
diff --git a/ui/file_manager/file_manager/foreground/js/directory_contents.js b/ui/file_manager/file_manager/foreground/js/directory_contents.js
index 192d493..33bbabb 100644
--- a/ui/file_manager/file_manager/foreground/js/directory_contents.js
+++ b/ui/file_manager/file_manager/foreground/js/directory_contents.js
@@ -106,6 +106,10 @@
       }
       chrome.fileManagerPrivate.searchDrive(
           {query: this.query_, nextFeed: ''}, (entries, nextFeed) => {
+            if (chrome.runtime.lastError) {
+              console.error(chrome.runtime.lastError.message);
+            }
+
             if (this.cancelled_) {
               errorCallback(util.createDOMError(util.FileError.ABORT_ERR));
               return;
@@ -202,6 +206,9 @@
   scan(entriesCallback, successCallback, errorCallback) {
     chrome.fileManagerPrivate.searchDriveMetadata(
         {query: '', types: this.searchType_, maxResults: 100}, results => {
+          if (chrome.runtime.lastError) {
+            console.error(chrome.runtime.lastError.message);
+          }
           if (this.cancelled_) {
             errorCallback(util.createDOMError(util.FileError.ABORT_ERR));
             return;
diff --git a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
index 234d2d0..305f59f 100644
--- a/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
+++ b/ui/file_manager/integration_tests/file_manager/directory_tree_context_menu.js
@@ -872,17 +872,17 @@
 
     // Mount removable volumes.
     await sendTestMessage({name: 'mountUsbWithPartitions'});
-    await sendTestMessage({name: 'mountFakeUsb'});
+    await sendTestMessage({name: 'mountFakeUsb', filesystem: 'ext4'});
 
     // Open Files app on local Downloads.
     const appId = await setupAndWaitUntilReady(
         RootPath.DOWNLOADS, [ENTRIES.beautiful], []);
 
-    // Check the context menu for single partition USB.
+    // Check the context menu for single partition ext4 USB.
     await checkContextMenu(
         appId, '/fake-usb', singleUsbMenus, true /* rootMenu */);
 
-    // Check the context menu for a folder inside a singlue USB partition.
+    // Check the context menu for a folder inside a single USB partition.
     await checkContextMenu(
         appId, '/fake-usb/A', folderMenus, false /* rootMenu */);
 
@@ -897,8 +897,7 @@
 
     // Check the context menu for a folder inside a partition1.
     await checkContextMenu(
-        appId, '/Drive Label/partition-1/Folder', folderMenus,
-        false /* rootMenu */);
+        appId, '/Drive Label/partition-1/A', folderMenus, false /* rootMenu */);
   };
 
   /**
diff --git a/ui/file_manager/integration_tests/file_manager/file_display.js b/ui/file_manager/integration_tests/file_manager/file_display.js
index 428f8ea..3b5f474 100644
--- a/ui/file_manager/integration_tests/file_manager/file_display.js
+++ b/ui/file_manager/integration_tests/file_manager/file_display.js
@@ -766,10 +766,7 @@
   if (removableDirectory === 'partition-1' ||
       removableDirectory === 'partition-2') {
     const partitionQuery = `#file-list [file-name="${removableDirectory}"]`;
-    const partitionFiles = [
-      ENTRIES.hello.getExpectedRow(),
-      ['Folder', '--', 'Folder', Date()],
-    ];
+    const partitionFiles = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
     await remoteCall.callRemoteTestUtil(
         'fakeMouseDoubleClick', appId, [partitionQuery]);
     await remoteCall.waitUntilCurrentDirectoryIsChanged(
diff --git a/ui/file_manager/integration_tests/file_manager/quick_view.js b/ui/file_manager/integration_tests/file_manager/quick_view.js
index 8a73f3d..dc3bfde1 100644
--- a/ui/file_manager/integration_tests/file_manager/quick_view.js
+++ b/ui/file_manager/integration_tests/file_manager/quick_view.js
@@ -221,11 +221,8 @@
             'fakeMouseClick', appId, [PARTITION_QUERY]),
         'fakeMouseClick failed');
 
-    // Check: the 'hello.txt' file should appear in the file list.
-    const files = [
-      ENTRIES.hello.getExpectedRow(),
-      ['Folder', '--', 'Folder', Date()],
-    ];
+    // Check: the USB files should appear in the file list.
+    const files = TestEntryInfo.getExpectedRows(BASIC_FAKE_ENTRY_SET);
     await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
 
     // Open the file in Quick View.
diff --git a/ui/webui/resources/cr_elements/chromeos/network/cr_network_listener_behavior.js b/ui/webui/resources/cr_elements/chromeos/network/cr_network_listener_behavior.js
index 91c2c23..39052d3 100644
--- a/ui/webui/resources/cr_elements/chromeos/network/cr_network_listener_behavior.js
+++ b/ui/webui/resources/cr_elements/chromeos/network/cr_network_listener_behavior.js
@@ -18,7 +18,7 @@
         new chromeos.networkConfig.mojom.CrosNetworkConfigObserver(this);
     network_config.MojoInterfaceProviderImpl.getInstance()
         .getMojoServiceProxy()
-        .addObserver(this.observer_.createProxy());
+        .addObserver(this.observer_.$.createProxy());
   },
 
   // CrosNetworkConfigObserver methods. Override these in the implementation.
diff --git a/ui/webui/resources/cr_elements/cr_button/cr_button.js b/ui/webui/resources/cr_elements/cr_button/cr_button.js
index 301a0606..6f006f42 100644
--- a/ui/webui/resources/cr_elements/cr_button/cr_button.js
+++ b/ui/webui/resources/cr_elements/cr_button/cr_button.js
@@ -37,9 +37,35 @@
     tap: 'onTap_',
   },
 
+  /** @private {Set<number>} */
+  timeoutIds_: null,
+
   /** @override */
   ready: function() {
     cr.ui.FocusOutlineManager.forDocument(document);
+    this.timeoutIds_ = new Set();
+  },
+
+  /** @override */
+  detached: function() {
+    this.timeoutIds_.forEach(clearTimeout);
+    this.timeoutIds_.clear();
+  },
+
+  /**
+   * @param {!Function} fn
+   * @param {number=} delay
+   * @private
+   */
+  setTimeout_: function(fn, delay) {
+    if (!this.isConnected) {
+      return;
+    }
+    const id = setTimeout(() => {
+      this.timeoutIds_.delete(id);
+      fn();
+    }, delay);
+    this.timeoutIds_.add(id);
   },
 
   /**
@@ -79,14 +105,17 @@
 
     e.preventDefault();
     e.stopPropagation();
+
     if (e.repeat) {
       return;
     }
 
+    this.getRipple().uiDownAction();
     if (e.key == 'Enter') {
       this.click();
-    } else {
-      this.getRipple().uiDownAction();
+      // Delay was chosen manually as a good time period for the ripple to be
+      // visible.
+      this.setTimeout_(() => this.getRipple().uiUpAction(), 100);
     }
   },
 
@@ -95,11 +124,13 @@
    * @private
    */
   onKeyUp_: function(e) {
-    if (e.key == ' ' || e.key == 'Enter') {
-      e.preventDefault();
-      e.stopPropagation();
+    if (e.key != ' ' && e.key != 'Enter') {
+      return;
     }
 
+    e.preventDefault();
+    e.stopPropagation();
+
     if (e.key == ' ') {
       this.click();
       this.getRipple().uiUpAction();