diff --git a/DEPS b/DEPS
index 1a727e3..195861f 100644
--- a/DEPS
+++ b/DEPS
@@ -195,11 +195,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': '532a264c141d7cb7741a20cd7049f4fa03ee1a56',
+  'skia_revision': '952f088d41e16c5524d68c4a00c038f24d734102',
   # 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': '81f3d945e03d8b0d9ae798175dd026d0ba98dd65',
+  'v8_revision': '1f2195b49c6d7611165ebd149b2825f27693930c',
   # 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.
@@ -207,7 +207,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': '282596778418f1b35c809cdba3dceb778f1d4a26',
+  'angle_revision': '1e068e1dbb6250bd45e72c5ef73d5fe563d6ca95',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
@@ -258,7 +258,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': '788aa6fd2c18c8c4ee84e5c15c86f451def6be07',
+  'catapult_revision': 'f9ede33deef5dadb50a3b00cd1607e508c9229a4',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -266,7 +266,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling devtools-frontend
   # and whatever else without interference from each other.
-  'devtools_frontend_revision': '1fd76f85b10e7b31ef3da29a26a4832011c241bb',
+  'devtools_frontend_revision': 'df4b3fe83ab26882e6a38a2ec709548a97b63941',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libprotobuf-mutator
   # and whatever else without interference from each other.
@@ -322,7 +322,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.
-  'quiche_revision': 'd8fb72d5ad9cb8ae3499b07b37b7857d88d26794',
+  'quiche_revision': 'e447bc6090479a4b89a2689768a8b05bafffe50c',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -1249,7 +1249,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'ed2e4739280f8f0971c39966ac6319295b6598e4',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '33a6332a92323279d37beafa0ee8cf181a759672',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1543,7 +1543,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@ecf0d2c0f65100694a8a98526969d1e15c454d17',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@cba56d2d3c0923ff8aba635dc5e9b5794714c41d',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 48e25ea..803d4e9 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -2695,15 +2695,9 @@
       <message name="IDS_ASH_SCREEN_CAPTURE_MESSAGE" desc="The message shows in the screenshot or screen recording notification.">
         Show in folder
       </message>
-      <message name="IDS_ASH_SCREEN_CAPTURE_BUTTON_SHARE" desc="The share button label for the screen capture notifiction.">
-        Share
-      </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_BUTTON_EDIT" desc="The edit button label for the screen catpure notification.">
         Edit
       </message>
-      <message name="IDS_ASH_SCREEN_CAPTURE_BUTTON_FLOAT_ON_TOP" desc="The float-on-top button label for the screen capture notification.">
-        Float on top
-      </message>
       <message name="IDS_ASH_SCREEN_CAPTURE_BUTTON_DELETE" desc="The delete button label for the screen capture notificaiton.">
         Delete
       </message>
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_BUTTON_FLOAT_ON_TOP.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_BUTTON_FLOAT_ON_TOP.png.sha1
deleted file mode 100644
index 57b5c98..0000000
--- a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_BUTTON_FLOAT_ON_TOP.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ac998dc1c50f46552c283f30c1d784b214f3f799
\ No newline at end of file
diff --git a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_BUTTON_SHARE.png.sha1 b/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_BUTTON_SHARE.png.sha1
deleted file mode 100644
index 57b5c98..0000000
--- a/ash/ash_strings_grd/IDS_ASH_SCREEN_CAPTURE_BUTTON_SHARE.png.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ac998dc1c50f46552c283f30c1d784b214f3f799
\ No newline at end of file
diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc
index 631503f..f9322cc 100644
--- a/ash/capture_mode/capture_mode_controller.cc
+++ b/ash/capture_mode/capture_mode_controller.cc
@@ -33,9 +33,7 @@
 
 // The notification button index.
 enum NotificationButtonIndex {
-  BUTTON_SHARE = 0,
-  BUTTON_EDIT,
-  BUTTON_FLOAT_ON_TOP,
+  BUTTON_EDIT = 0,
   BUTTON_DELETE,
 };
 
@@ -111,15 +109,9 @@
       l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_MESSAGE);
 
   message_center::RichNotificationData optional_field;
-  message_center::ButtonInfo share_button(
-      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_BUTTON_SHARE));
-  optional_field.buttons.push_back(share_button);
   message_center::ButtonInfo edit_button(
       l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_BUTTON_EDIT));
   optional_field.buttons.push_back(edit_button);
-  message_center::ButtonInfo float_on_top_button(
-      l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_BUTTON_FLOAT_ON_TOP));
-  optional_field.buttons.push_back(float_on_top_button);
   message_center::ButtonInfo delete_button(
       l10n_util::GetStringUTF16(IDS_ASH_SCREEN_CAPTURE_BUTTON_DELETE));
   optional_field.buttons.push_back(delete_button);
@@ -166,12 +158,8 @@
 
   // TODO: fill in here.
   switch (button_index.value()) {
-    case NotificationButtonIndex::BUTTON_SHARE:
-      break;
     case NotificationButtonIndex::BUTTON_EDIT:
       break;
-    case NotificationButtonIndex::BUTTON_FLOAT_ON_TOP:
-      break;
     case NotificationButtonIndex::BUTTON_DELETE:
       break;
   }
diff --git a/ash/frame/default_frame_header_unittest.cc b/ash/frame/default_frame_header_unittest.cc
index 4b57baa..76d6df3 100644
--- a/ash/frame/default_frame_header_unittest.cc
+++ b/ash/frame/default_frame_header_unittest.cc
@@ -6,6 +6,7 @@
 
 #include <memory>
 
+#include "ash/frame/non_client_frame_view_ash.h"
 #include "ash/public/cpp/caption_buttons/frame_back_button.h"
 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
 #include "ash/public/cpp/shell_window_ids.h"
@@ -13,13 +14,16 @@
 #include "ash/test/ash_test_base.h"
 #include "ash/wm/desks/desks_util.h"
 #include "base/i18n/rtl.h"
+#include "base/stl_util.h"
 #include "base/test/icu_test_util.h"
 #include "ui/aura/window.h"
+#include "ui/compositor/scoped_animation_duration_scale_mode.h"
 #include "ui/gfx/animation/animation_test_api.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/views/test/test_views.h"
 #include "ui/views/widget/widget.h"
 #include "ui/views/window/non_client_view.h"
+#include "ui/wm/core/window_util.h"
 
 using views::NonClientFrameView;
 using views::Widget;
@@ -82,70 +86,197 @@
 
 // Ensure the right frame colors are used.
 TEST_F(DefaultFrameHeaderTest, FrameColors) {
-  std::unique_ptr<Widget> widget = CreateTestWidget(
-      nullptr, desks_util::GetActiveDeskContainerId(), gfx::Rect(1, 2, 3, 4));
-  FrameCaptionButtonContainerView container(widget.get());
-  views::StaticSizedView window_icon(gfx::Size(16, 16));
-  window_icon.SetBounds(0, 0, 16, 16);
-  widget->SetBounds(gfx::Rect(0, 0, 500, 500));
-  widget->Show();
-
-  DefaultFrameHeader frame_header(
-      widget.get(), widget->non_client_view()->frame_view(), &container);
+  const auto win0_bounds = gfx::Rect{1, 2, 3, 4};
+  auto win0 = CreateAppWindow(win0_bounds, AppType::BROWSER);
+  Widget* widget = Widget::GetWidgetForNativeWindow(win0.get());
+  DefaultFrameHeader* frame_header =
+      static_cast<DefaultFrameHeader*>(FrameHeader::Get(widget));
   // Check frame color is sensitive to mode.
   SkColor active = SkColorSetRGB(70, 70, 70);
   SkColor inactive = SkColorSetRGB(200, 200, 200);
-  widget->GetNativeWindow()->SetProperty(kFrameActiveColorKey, active);
-  widget->GetNativeWindow()->SetProperty(kFrameInactiveColorKey, inactive);
-  frame_header.UpdateFrameColors();
-  frame_header.mode_ = FrameHeader::MODE_ACTIVE;
-  EXPECT_EQ(active, frame_header.GetCurrentFrameColor());
-  frame_header.mode_ = FrameHeader::MODE_INACTIVE;
-  EXPECT_EQ(inactive, frame_header.GetCurrentFrameColor());
-  EXPECT_EQ(active, frame_header.GetActiveFrameColorForPaintForTest());
+  win0->SetProperty(kFrameActiveColorKey, active);
+  win0->SetProperty(kFrameInactiveColorKey, inactive);
+  frame_header->UpdateFrameColors();
+  frame_header->mode_ = FrameHeader::MODE_ACTIVE;
+  EXPECT_EQ(active, frame_header->GetCurrentFrameColor());
+  frame_header->mode_ = FrameHeader::MODE_INACTIVE;
+  EXPECT_EQ(inactive, frame_header->GetCurrentFrameColor());
+  EXPECT_EQ(active, frame_header->GetActiveFrameColorForPaintForTest());
 
   // Update to the new value which has no blue, which should animate.
-  frame_header.mode_ = FrameHeader::MODE_ACTIVE;
+  frame_header->mode_ = FrameHeader::MODE_ACTIVE;
   SkColor new_active = SkColorSetRGB(70, 70, 0);
-  widget->GetNativeWindow()->SetProperty(kFrameActiveColorKey, new_active);
-  frame_header.UpdateFrameColors();
-
-  gfx::SlideAnimation* animation =
-      frame_header.GetAnimationForActiveFrameColorForTest();
-  gfx::AnimationTestApi test_api(animation);
-
-  // animate half way through.
-  base::TimeTicks now = base::TimeTicks::Now();
-  test_api.SetStartTime(now);
-  test_api.Step(now + base::TimeDelta::FromMilliseconds(120));
-
-  // GetCurrentFrameColor should return the target color.
-  EXPECT_EQ(new_active, frame_header.GetCurrentFrameColor());
-
-  // The color used for paint should be somewhere between 0 and 70.
-  SkColor new_active_for_paint =
-      frame_header.GetActiveFrameColorForPaintForTest();
-  EXPECT_NE(new_active, new_active_for_paint);
-  EXPECT_EQ(53u, SkColorGetB(new_active_for_paint));
+  win0->SetProperty(kFrameActiveColorKey, new_active);
+  frame_header->UpdateFrameColors();
 
   // Now update to the new value which is full blue.
   SkColor new_new_active = SkColorSetRGB(70, 70, 255);
-  widget->GetNativeWindow()->SetProperty(kFrameActiveColorKey, new_new_active);
-  frame_header.UpdateFrameColors();
-
-  now = base::TimeTicks::Now();
-  test_api.SetStartTime(now);
-  test_api.Step(now + base::TimeDelta::FromMilliseconds(20));
+  win0->SetProperty(kFrameActiveColorKey, new_new_active);
+  frame_header->UpdateFrameColors();
 
   // Again, GetCurrentFrameColor should return the target color.
-  EXPECT_EQ(new_new_active, frame_header.GetCurrentFrameColor());
+  EXPECT_EQ(new_new_active, frame_header->GetCurrentFrameColor());
+}
 
-  // The start value should be the previous paint color, so it should be
-  // near 53.
-  SkColor new_new_active_for_paint =
-      frame_header.GetActiveFrameColorForPaintForTest();
-  EXPECT_NE(new_active_for_paint, new_new_active_for_paint);
-  EXPECT_EQ(54u, SkColorGetB(new_new_active_for_paint));
+namespace {
+
+class LayerDestroyedChecker : public ui::LayerObserver {
+ public:
+  explicit LayerDestroyedChecker(ui::Layer* layer) { layer->AddObserver(this); }
+  LayerDestroyedChecker(const LayerDestroyedChecker&) = delete;
+  LayerDestroyedChecker& operator=(const LayerDestroyedChecker&) = delete;
+  ~LayerDestroyedChecker() override = default;
+
+  void LayerDestroyed(ui::Layer* layer) override {
+    layer->RemoveObserver(this);
+    destroyed_ = true;
+  }
+  bool destroyed() const { return destroyed_; }
+
+ private:
+  bool destroyed_ = false;
+};
+
+}  // namespace
+
+// A class to wait until hthe frame header is painted.
+class FramePaintWaiter : public ui::CompositorObserver {
+ public:
+  explicit FramePaintWaiter(aura::Window* window)
+      : frame_header_(
+            FrameHeader::Get(Widget::GetWidgetForNativeWindow(window))) {
+    frame_header_->view()->GetWidget()->GetCompositor()->AddObserver(this);
+  }
+  FramePaintWaiter(const FramePaintWaiter&) = delete;
+  FramePaintWaiter& operator=(FramePaintWaiter&) = delete;
+  ~FramePaintWaiter() override {
+    frame_header_->view()->GetWidget()->GetCompositor()->RemoveObserver(this);
+  }
+
+  // ui::CompositorObserver:
+  void OnCompositingDidCommit(ui::Compositor* compositor) override {
+    if (frame_header_->painted_)
+      run_loop_.Quit();
+  }
+
+  void Wait() { run_loop_.Run(); }
+
+ private:
+  base::RunLoop run_loop_;
+  FrameHeader* frame_header_ = nullptr;
+};
+
+TEST_F(DefaultFrameHeaderTest, DeleteDuringAnimation) {
+  const auto bounds = gfx::Rect(100, 100);
+  auto win0 = CreateAppWindow(bounds, AppType::BROWSER);
+  auto win1 = CreateAppWindow(bounds, AppType::BROWSER);
+
+  Widget* widget = Widget::GetWidgetForNativeWindow(win0.get());
+  EXPECT_TRUE(FrameHeader::Get(widget));
+
+  EXPECT_TRUE(wm::IsActiveWindow(win1.get()));
+
+  // A frame will not animate until it is painted first.
+  FramePaintWaiter(win0.get()).Wait();
+  FramePaintWaiter(win1.get()).Wait();
+
+  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+  wm::ActivateWindow(win0.get());
+
+  auto* header_view = NonClientFrameViewAsh::Get(win0.get())->GetHeaderView();
+  ASSERT_TRUE(header_view);
+  auto* animating_layer_holding_view = header_view->children()[0];
+  EXPECT_TRUE(!std::strcmp(animating_layer_holding_view->GetClassName(),
+                           "FrameAnimatorView"));
+  ASSERT_TRUE(animating_layer_holding_view->layer());
+  ASSERT_GT(animating_layer_holding_view->layer()->parent()->children().size(),
+            2u);
+  auto* animating_layer =
+      animating_layer_holding_view->layer()->parent()->children()[0];
+  EXPECT_EQ(ui::LAYER_TEXTURED, animating_layer->type());
+  EXPECT_NE(std::string::npos, animating_layer->name().find(":Old", 0));
+  EXPECT_TRUE(animating_layer->GetAnimator()->is_animating());
+
+  LayerDestroyedChecker checker(animating_layer);
+
+  win0.reset();
+
+  EXPECT_TRUE(checker.destroyed());
+}
+
+// Make sure that the animation is canceled when resized.
+TEST_F(DefaultFrameHeaderTest, ResizeAndReorderDuringAnimation) {
+  const auto bounds = gfx::Rect(100, 100);
+  auto win_0 = CreateAppWindow(bounds, AppType::BROWSER);
+  auto win_1 = CreateAppWindow(bounds, AppType::BROWSER);
+
+  EXPECT_TRUE(wm::IsActiveWindow(win_1.get()));
+
+  // A frame will not animate until it is painted first.
+  FramePaintWaiter(win_0.get()).Wait();
+  FramePaintWaiter(win_1.get()).Wait();
+
+  ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
+      ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
+
+  auto* header_view_0 =
+      NonClientFrameViewAsh::Get(win_0.get())->GetHeaderView();
+  auto* animating_layer_holding_view_0 = header_view_0->children()[0];
+  EXPECT_TRUE(!std::strcmp(animating_layer_holding_view_0->GetClassName(),
+                           "FrameAnimatorView"));
+  size_t original_layers_count_0 =
+      animating_layer_holding_view_0->layer()->parent()->children().size();
+
+  auto* header_view_1 =
+      NonClientFrameViewAsh::Get(win_1.get())->GetHeaderView();
+  auto* animating_layer_holding_view_1 = header_view_1->children()[0];
+  EXPECT_TRUE(!std::strcmp(animating_layer_holding_view_1->GetClassName(),
+                           "FrameAnimatorView"));
+  size_t original_layers_count_1 =
+      animating_layer_holding_view_1->layer()->parent()->children().size();
+
+  wm::ActivateWindow(win_0.get());
+
+  {
+    // Resize during animation
+    EXPECT_EQ(
+        animating_layer_holding_view_0->layer()->parent()->children().size(),
+        original_layers_count_0 + 1);
+    auto* animating_layer =
+        animating_layer_holding_view_0->layer()->parent()->children()[0];
+    EXPECT_TRUE(animating_layer->GetAnimator()->is_animating());
+
+    LayerDestroyedChecker checker(animating_layer);
+
+    win_0->SetBounds(gfx::Rect(200, 200));
+
+    // Animating layer shuld have been removed.
+    EXPECT_EQ(
+        animating_layer_holding_view_0->layer()->parent()->children().size(),
+        original_layers_count_0);
+    EXPECT_TRUE(checker.destroyed());
+  }
+
+  {
+    // wind_1 should still be animating.
+    EXPECT_EQ(
+        animating_layer_holding_view_1->layer()->parent()->children().size(),
+        original_layers_count_1 + 1);
+    auto* animating_layer =
+        animating_layer_holding_view_1->layer()->parent()->children()[0];
+    EXPECT_TRUE(animating_layer->GetAnimator()->is_animating());
+    LayerDestroyedChecker checker(animating_layer);
+
+    // Change the view's stacking order should stop the animation.
+    ASSERT_EQ(3u, header_view_1->children().size());
+    header_view_1->ReorderChildView(header_view_1->children()[2], 0);
+
+    EXPECT_EQ(
+        animating_layer_holding_view_1->layer()->parent()->children().size(),
+        original_layers_count_1);
+    EXPECT_TRUE(checker.destroyed());
+  }
 }
 
 }  // namespace ash
diff --git a/ash/frame/header_view.cc b/ash/frame/header_view.cc
index 71a2cfd..053d30f 100644
--- a/ash/frame/header_view.cc
+++ b/ash/frame/header_view.cc
@@ -291,13 +291,7 @@
   if (!should_paint_ || !target_widget_)
     return;
 
-  bool paint_as_active =
-      target_widget_->non_client_view()->frame_view()->ShouldPaintAsActive();
-  frame_header_->SetPaintAsActive(paint_as_active);
-
-  FrameHeader::Mode header_mode =
-      paint_as_active ? FrameHeader::MODE_ACTIVE : FrameHeader::MODE_INACTIVE;
-  frame_header_->PaintHeader(canvas, header_mode);
+  frame_header_->PaintHeader(canvas);
 }
 
 void HeaderView::UpdateBackButton() {
diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc
index f3f7b7b..77c8eb06 100644
--- a/ash/frame/non_client_frame_view_ash.cc
+++ b/ash/frame/non_client_frame_view_ash.cc
@@ -410,8 +410,7 @@
 }
 
 void NonClientFrameViewAsh::PaintAsActiveChanged() {
-  // The icons differ between active and inactive.
-  header_view_->SchedulePaint();
+  header_view_->GetFrameHeader()->SetPaintAsActive(ShouldPaintAsActive());
   frame_->non_client_view()->Layout();
 }
 
diff --git a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
index 882aefb8..9c87699dc 100644
--- a/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
+++ b/ash/public/cpp/caption_buttons/frame_caption_button_container_view.cc
@@ -228,6 +228,7 @@
   minimize_button_->set_paint_as_active(paint_as_active);
   size_button_->set_paint_as_active(paint_as_active);
   close_button_->set_paint_as_active(paint_as_active);
+  SchedulePaint();
 }
 
 void FrameCaptionButtonContainerView::SetBackgroundColor(
diff --git a/ash/public/cpp/default_frame_header.cc b/ash/public/cpp/default_frame_header.cc
index ab4518f..181707df 100644
--- a/ash/public/cpp/default_frame_header.cc
+++ b/ash/public/cpp/default_frame_header.cc
@@ -25,6 +25,10 @@
 
 namespace {
 
+// Duration of animation scheduled when frame color is changed.
+constexpr base::TimeDelta kFrameColorChangeAnimationDuration =
+    base::TimeDelta::FromMilliseconds(240);
+
 // Tiles an image into an area, rounding the top corners.
 void TileRoundRect(gfx::Canvas* canvas,
                    const cc::PaintFlags& flags,
@@ -53,36 +57,6 @@
 
 namespace ash {
 
-DefaultFrameHeader::ColorAnimator::ColorAnimator(
-    gfx::AnimationDelegate* delegate)
-    : animation_(delegate) {
-  animation_.SetSlideDuration(base::TimeDelta::FromMilliseconds(240));
-  animation_.SetTweenType(gfx::Tween::EASE_IN);
-  animation_.Reset(1);
-}
-
-DefaultFrameHeader::ColorAnimator::ColorAnimator::~ColorAnimator() = default;
-
-void DefaultFrameHeader::ColorAnimator::SetTargetColor(SkColor target) {
-  target_color_ = target;
-  start_color_ = current_color_;
-  if (current_color_ == kDefaultFrameColor) {
-    // Changing from default should be set immediately.
-    current_color_ = target_color_;
-    animation_.Reset(1);
-  } else {
-    animation_.Reset(0);
-  }
-  animation_.Show();
-}
-
-SkColor DefaultFrameHeader::ColorAnimator::GetCurrentColor() {
-  current_color_ =
-      color_utils::AlphaBlend(target_color_, start_color_,
-                              static_cast<float>(animation_.GetCurrentValue()));
-  return current_color_;
-}
-
 ///////////////////////////////////////////////////////////////////////////////
 // DefaultFrameHeader, public:
 
@@ -90,9 +64,7 @@
     views::Widget* target_widget,
     views::View* header_view,
     FrameCaptionButtonContainerView* caption_button_container)
-    : FrameHeader(target_widget, header_view),
-      active_frame_color_(this),
-      inactive_frame_color_(this) {
+    : FrameHeader(target_widget, header_view) {
   DCHECK(caption_button_container);
   SetCaptionButtonContainer(caption_button_container);
 }
@@ -114,18 +86,19 @@
       target_window->GetProperty(kFrameInactiveColorKey);
 
   bool updated = false;
-  if (active_frame_color_.target_color() != active_frame_color) {
-    active_frame_color_.SetTargetColor(active_frame_color);
-    updated = true;
+  // Update the frame if the frame color for the current active state chagnes.
+  if (active_frame_color_ != active_frame_color) {
+    active_frame_color_ = active_frame_color;
+    updated = mode() == Mode::MODE_ACTIVE;
   }
-  if (inactive_frame_color_.target_color() != inactive_frame_color) {
-    inactive_frame_color_.SetTargetColor(inactive_frame_color);
-    updated = true;
+  if (inactive_frame_color_ != inactive_frame_color) {
+    inactive_frame_color_ = inactive_frame_color;
+    updated |= mode() == Mode::MODE_INACTIVE;
   }
 
   if (updated) {
     UpdateCaptionButtonColors();
-    view()->SchedulePaint();
+    StartTransitionAnimation(kFrameColorChangeAnimationDuration);
   }
 }
 
@@ -139,10 +112,8 @@
                           : 0;
 
   cc::PaintFlags flags;
-  flags.setColor(color_utils::AlphaBlend(
-      active_frame_color_.GetCurrentColor(),
-      inactive_frame_color_.GetCurrentColor(),
-      static_cast<float>(activation_animation().GetCurrentValue())));
+  flags.setColor(mode() == Mode::MODE_ACTIVE ? active_frame_color_
+                                             : inactive_frame_color_);
   flags.setAntiAlias(true);
   if (width_in_pixels_ > 0) {
     canvas->Save();
@@ -186,17 +157,11 @@
 }
 
 SkColor DefaultFrameHeader::GetCurrentFrameColor() const {
-  return mode() == MODE_ACTIVE ? active_frame_color_.target_color()
-                               : inactive_frame_color_.target_color();
-}
-
-gfx::SlideAnimation*
-DefaultFrameHeader::GetAnimationForActiveFrameColorForTest() {
-  return active_frame_color_.animation();
+  return mode() == MODE_ACTIVE ? active_frame_color_ : inactive_frame_color_;
 }
 
 SkColor DefaultFrameHeader::GetActiveFrameColorForPaintForTest() {
-  return active_frame_color_.GetCurrentColor();
+  return active_frame_color_;
 }
 
 }  // namespace ash
diff --git a/ash/public/cpp/default_frame_header.h b/ash/public/cpp/default_frame_header.h
index b528ee2..67b8381e6 100644
--- a/ash/public/cpp/default_frame_header.h
+++ b/ash/public/cpp/default_frame_header.h
@@ -26,12 +26,8 @@
                      FrameCaptionButtonContainerView* caption_button_container);
   ~DefaultFrameHeader() override;
 
-  SkColor active_frame_color_for_testing() {
-    return active_frame_color_.target_color();
-  }
-  SkColor inactive_frame_color_for_testing() {
-    return inactive_frame_color_.target_color();
-  }
+  SkColor active_frame_color_for_testing() { return active_frame_color_; }
+  SkColor inactive_frame_color_for_testing() { return inactive_frame_color_; }
 
   void SetWidthInPixels(int width_in_pixels);
 
@@ -51,33 +47,10 @@
   // Returns the window of the target widget.
   aura::Window* GetTargetWindow();
 
-  gfx::SlideAnimation* GetAnimationForActiveFrameColorForTest();
   SkColor GetActiveFrameColorForPaintForTest();
 
-  // A utility class to animate color value.
-  class ColorAnimator {
-   public:
-    explicit ColorAnimator(gfx::AnimationDelegate* delegate);
-    ~ColorAnimator();
-
-    void SetTargetColor(SkColor target);
-    SkColor target_color() const { return target_color_; }
-    SkColor GetCurrentColor();
-    float get_value() const { return animation_.GetCurrentValue(); }
-
-    gfx::SlideAnimation* animation() { return &animation_; }
-
-   private:
-    gfx::SlideAnimation animation_;
-    SkColor start_color_ = kDefaultFrameColor;
-    SkColor target_color_ = kDefaultFrameColor;
-    SkColor current_color_ = kDefaultFrameColor;
-
-    DISALLOW_COPY_AND_ASSIGN(ColorAnimator);
-  };
-
-  ColorAnimator active_frame_color_;
-  ColorAnimator inactive_frame_color_;
+  SkColor active_frame_color_ = kDefaultFrameColor;
+  SkColor inactive_frame_color_ = kDefaultFrameColor;
 
   int width_in_pixels_ = -1;
 
diff --git a/ash/public/cpp/frame_header.cc b/ash/public/cpp/frame_header.cc
index 97ad1644..6b549a4 100644
--- a/ash/public/cpp/frame_header.cc
+++ b/ash/public/cpp/frame_header.cc
@@ -11,6 +11,9 @@
 #include "ash/public/cpp/window_properties.h"
 #include "base/logging.h"  // DCHECK
 #include "ui/base/class_property.h"
+#include "ui/compositor/layer_animation_observer.h"
+#include "ui/compositor/layer_tree_owner.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_utils.h"
 #include "ui/gfx/font_list.h"
@@ -29,6 +32,9 @@
 
 namespace {
 
+constexpr base::TimeDelta kFrameActivationAnimationDuration =
+    base::TimeDelta::FromMilliseconds(200);
+
 DEFINE_UI_CLASS_PROPERTY_KEY(FrameHeader*, kFrameHeaderKey, nullptr)
 
 // Returns the available bounds for the header's title given the views to the
@@ -61,29 +67,6 @@
   return gfx::Rect(x, y, width, title_height);
 }
 
-// Returns true if the header for |widget| can animate to new visuals when the
-// widget's activation changes. Returns false if the header should switch to
-// new visuals instantaneously.
-bool CanAnimateActivation(views::Widget* widget) {
-  // Do not animate the header if the parent (e.g. the active desk container) is
-  // already animating. All of the implementers of FrameHeader animate
-  // activation by continuously painting during the animation. This gives the
-  // parent's animation a slower frame rate.
-  // TODO(sky): Expose a better way to determine this rather than assuming the
-  // parent is a toplevel container.
-  aura::Window* window = widget->GetNativeWindow();
-  // TODO(sky): parent()->layer() is for mash until animations ported.
-  if (!window || !window->parent() || !window->parent()->layer())
-    return true;
-
-  ui::LayerAnimator* parent_layer_animator =
-      window->parent()->layer()->GetAnimator();
-  return !parent_layer_animator->IsAnimatingProperty(
-             ui::LayerAnimationElement::OPACITY) &&
-         !parent_layer_animator->IsAnimatingProperty(
-             ui::LayerAnimationElement::VISIBILITY);
-}
-
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -107,29 +90,102 @@
          caption_button_container_->GetMinimumSize().width();
 }
 
-void FrameHeader::PaintHeader(gfx::Canvas* canvas, Mode mode) {
-  Mode old_mode = mode_;
-  mode_ = mode;
-
-  if (mode_ != old_mode) {
-    UpdateCaptionButtonColors();
-
-    if (!initial_paint_ && CanAnimateActivation(target_widget_)) {
-      activation_animation_.SetSlideDuration(
-          base::TimeDelta::FromMilliseconds(200));
-      if (mode_ == MODE_ACTIVE)
-        activation_animation_.Show();
-      else
-        activation_animation_.Hide();
-    } else {
-      if (mode_ == MODE_ACTIVE)
-        activation_animation_.Reset(1);
-      else
-        activation_animation_.Reset(0);
-    }
-    initial_paint_ = false;
+// An invisible view that drives the frame's animation. This holds the animating
+// layer as a layer beneath this view so that it's behind all other child layers
+// of the window to avoid hiding their contents.
+class FrameHeader::FrameAnimatorView : public views::View,
+                                       public views::ViewObserver,
+                                       public ui::ImplicitAnimationObserver {
+ public:
+  FrameAnimatorView(FrameHeader* frame_header, views::View* parent)
+      : frame_header_(frame_header), parent_(parent) {
+    SetPaintToLayer(ui::LAYER_NOT_DRAWN);
+    parent_->AddChildViewAt(this, 0);
+    parent_->AddObserver(this);
+  }
+  FrameAnimatorView(const FrameAnimatorView&) = delete;
+  FrameAnimatorView& operator=(const FrameAnimatorView&) = delete;
+  ~FrameAnimatorView() override {
+    StopAnimation();
+    // A child view should always be removed first.
+    parent_->RemoveObserver(this);
   }
 
+  void StartAnimation(base::TimeDelta duration) {
+    StopAnimation();
+    aura::Window* window = frame_header_->target_widget()->GetNativeWindow();
+
+    // Make sure the this view is at the bottom of root view's children.
+    parent_->ReorderChildView(this, 0);
+
+    std::unique_ptr<ui::LayerTreeOwner> old_layer_owner =
+        std::make_unique<ui::LayerTreeOwner>(window->RecreateLayer());
+    ui::Layer* old_layer = old_layer_owner->root();
+    ui::Layer* new_layer = window->layer();
+    new_layer->SetName(old_layer->name());
+    old_layer->SetName(old_layer->name() + ":Old");
+    old_layer->SetTransform(gfx::Transform());
+
+    layer_owner_ = std::move(old_layer_owner);
+
+    AddLayerBeneathView(old_layer);
+
+    // The old layer is on top and should fade out.
+    old_layer->SetOpacity(1.f);
+    new_layer->SetOpacity(1.f);
+    {
+      ui::ScopedLayerAnimationSettings settings(old_layer->GetAnimator());
+      settings.SetPreemptionStrategy(
+          ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
+      settings.AddObserver(this);
+      settings.SetTransitionDuration(duration);
+      old_layer->SetOpacity(0.f);
+      settings.SetTweenType(gfx::Tween::EASE_OUT);
+    }
+  }
+
+  // views::Views:
+  const char* GetClassName() const override { return "FrameAnimatorView"; }
+  std::unique_ptr<ui::Layer> RecreateLayer() override {
+    // A layer may be recreated for another animation (maximize/restore).
+    // Just cancel the animation if that happens during animation.
+    StopAnimation();
+    return views::View::RecreateLayer();
+  }
+
+  // ViewObserver::
+  void OnChildViewReordered(views::View* observed_view,
+                            views::View* child) override {
+    // Stop animation if the child view order has changed during animation.
+    StopAnimation();
+  }
+  void OnViewBoundsChanged(views::View* observed_view) override {
+    // Stop animation if the frame size changed during animation.
+    StopAnimation();
+    SetBoundsRect(parent_->GetLocalBounds());
+  }
+
+  // ui::ImplicitAnimationObserver overrides:
+  void OnImplicitAnimationsCompleted() override {
+    RemoveLayerBeneathView(layer_owner_->root());
+    layer_owner_ = nullptr;
+  }
+
+ private:
+  void StopAnimation() {
+    if (layer_owner_) {
+      layer_owner_->root()->GetAnimator()->StopAnimating();
+      layer_owner_ = nullptr;
+    }
+  }
+
+  FrameHeader* frame_header_;
+  views::View* parent_;
+  std::unique_ptr<ui::LayerTreeOwner> layer_owner_;
+};
+
+void FrameHeader::PaintHeader(gfx::Canvas* canvas) {
+  painted_ = true;
   DoPaintHeader(canvas);
 }
 
@@ -157,6 +213,18 @@
 }
 
 void FrameHeader::SetPaintAsActive(bool paint_as_active) {
+  // No need to animate if already active.
+  const bool already_active = (mode_ == Mode::MODE_ACTIVE);
+
+  if (already_active == paint_as_active)
+    return;
+
+  mode_ = paint_as_active ? MODE_ACTIVE : MODE_INACTIVE;
+
+  // The frame has no content yet to animatie.
+  if (painted_)
+    StartTransitionAnimation(kFrameActivationAnimationDuration);
+
   caption_button_container_->SetPaintAsActive(paint_as_active);
   if (back_button_)
     back_button_->set_paint_as_active(paint_as_active);
@@ -199,22 +267,14 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
-// gfx::AnimationDelegate overrides:
-
-void FrameHeader::AnimationProgressed(const gfx::Animation* animation) {
-  view_->SchedulePaintInRect(GetPaintedBounds());
-}
-
-///////////////////////////////////////////////////////////////////////////////
 // FrameHeader, protected:
 
 FrameHeader::FrameHeader(views::Widget* target_widget, views::View* view)
-    : views::AnimationDelegateViews(view),
-      target_widget_(target_widget),
-      view_(view) {
+    : target_widget_(target_widget), view_(view) {
   DCHECK(target_widget);
   DCHECK(view);
   UpdateFrameHeaderKey();
+  frame_animator_ = new FrameAnimatorView(this, view);
 }
 
 void FrameHeader::UpdateFrameHeaderKey() {
@@ -269,6 +329,18 @@
   LayoutHeaderInternal();
 }
 
+void FrameHeader::StartTransitionAnimation(base::TimeDelta duration) {
+  aura::Window* window = target_widget_->GetNativeWindow();
+  // Don't start another animation if the window is already animating
+  // such as maximize/restore/unminimize.
+  if (window->layer()->GetAnimator()->is_animating())
+    return;
+
+  frame_animator_->StartAnimation(duration);
+
+  frame_animator_->SchedulePaint();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // FrameHeader, private:
 
diff --git a/ash/public/cpp/frame_header.h b/ash/public/cpp/frame_header.h
index 6f8165d..89ce587 100644
--- a/ash/public/cpp/frame_header.h
+++ b/ash/public/cpp/frame_header.h
@@ -7,11 +7,12 @@
 
 #include "ash/public/cpp/ash_public_export.h"
 #include "ash/public/cpp/caption_buttons/frame_caption_button_container_view.h"
+#include "base/callback.h"
+#include "base/optional.h"
 #include "base/strings/string16.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/ui_base_types.h"
-#include "ui/gfx/animation/slide_animation.h"
-#include "ui/views/animation/animation_delegate_views.h"
+#include "ui/compositor/layer_animation_observer.h"
 #include "ui/views/window/frame_caption_button.h"
 
 namespace gfx {
@@ -29,13 +30,13 @@
 class CaptionButtonModel;
 
 // Helper class for managing the window header.
-class ASH_PUBLIC_EXPORT FrameHeader : public views::AnimationDelegateViews {
+class ASH_PUBLIC_EXPORT FrameHeader {
  public:
   enum Mode { MODE_ACTIVE, MODE_INACTIVE };
 
   static FrameHeader* Get(views::Widget* widget);
 
-  ~FrameHeader() override;
+  virtual ~FrameHeader();
 
   const base::string16& frame_text_override() const {
     return frame_text_override_;
@@ -45,7 +46,7 @@
   int GetMinimumHeaderWidth() const;
 
   // Paints the header.
-  void PaintHeader(gfx::Canvas* canvas, Mode mode);
+  void PaintHeader(gfx::Canvas* canvas);
 
   // Performs layout for the header.
   void LayoutHeader();
@@ -81,9 +82,6 @@
   // regardless of what ShouldShowWindowTitle() returns.
   void SetFrameTextOverride(const base::string16& frame_text_override);
 
-  // views::AnimationDelegateViews:
-  void AnimationProgressed(const gfx::Animation* animation) override;
-
   void UpdateFrameHeaderKey();
 
   views::View* view() { return view_; }
@@ -112,19 +110,20 @@
 
   Mode mode() const { return mode_; }
 
-  const gfx::SlideAnimation& activation_animation() {
-    return activation_animation_;
-  }
-
   virtual void DoPaintHeader(gfx::Canvas* canvas) = 0;
   virtual views::CaptionButtonLayoutSize GetButtonLayoutSize() const = 0;
   virtual SkColor GetTitleColor() const = 0;
   virtual SkColor GetCurrentFrameColor() const = 0;
 
+  // Starts fade transition animation with given duration.
+  void StartTransitionAnimation(base::TimeDelta duration);
+
  private:
+  class FrameAnimatorView;
   FRIEND_TEST_ALL_PREFIXES(DefaultFrameHeaderTest, BackButtonAlignment);
   FRIEND_TEST_ALL_PREFIXES(DefaultFrameHeaderTest, TitleIconAlignment);
   FRIEND_TEST_ALL_PREFIXES(DefaultFrameHeaderTest, FrameColors);
+  friend class FramePaintWaiter;
 
   void LayoutHeaderInternal();
 
@@ -139,20 +138,19 @@
   views::FrameCaptionButton* back_button_ = nullptr;  // May remain nullptr.
   views::View* left_header_view_ = nullptr;    // May remain nullptr.
   FrameCaptionButtonContainerView* caption_button_container_ = nullptr;
+  FrameAnimatorView* frame_animator_ = nullptr;  // owned by view tree.
 
   // The height of the header to paint.
   int painted_height_ = 0;
 
+  // Used to skip animation when the frame hasn't painted yet.
+  bool painted_ = false;
+
   // Whether the header should be painted as active.
   Mode mode_ = MODE_INACTIVE;
 
-  // Whether the header is painted for the first time.
-  bool initial_paint_ = true;
-
   base::string16 frame_text_override_;
 
-  gfx::SlideAnimation activation_animation_{this};
-
   DISALLOW_COPY_AND_ASSIGN(FrameHeader);
 };
 
diff --git a/ash/system/overview/overview_button_tray.cc b/ash/system/overview/overview_button_tray.cc
index 6b5d9005..85b95805 100644
--- a/ash/system/overview/overview_button_tray.cc
+++ b/ash/system/overview/overview_button_tray.cc
@@ -10,6 +10,7 @@
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
+#include "ash/style/ash_color_provider.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_container.h"
 #include "ash/wm/mru_window_tracker.h"
@@ -36,7 +37,10 @@
       icon_(new views::ImageView()),
       scoped_session_observer_(this) {
   gfx::ImageSkia image = gfx::CreateVectorIcon(
-      kShelfOverviewIcon, ShelfConfig::Get()->shelf_icon_color());
+      kShelfOverviewIcon,
+      AshColorProvider::Get()->GetContentLayerColor(
+          AshColorProvider::ContentLayerType::kButtonIconColor,
+          AshColorProvider::AshColorMode::kDark));
   icon_->SetImage(image);
   const int vertical_padding = (kTrayItemSize - image.height()) / 2;
   const int horizontal_padding = (kTrayItemSize - image.width()) / 2;
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 876ebc2b..123c099 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -740,6 +740,7 @@
     "threading/scoped_thread_priority.cc",
     "threading/scoped_thread_priority.h",
     "threading/sequence_bound.h",
+    "threading/sequence_bound_internal.h",
     "threading/sequence_local_storage_map.cc",
     "threading/sequence_local_storage_map.h",
     "threading/sequence_local_storage_slot.cc",
diff --git a/base/allocator/allocator_interception_mac.mm b/base/allocator/allocator_interception_mac.mm
index 001142a..10f2b97 100644
--- a/base/allocator/allocator_interception_mac.mm
+++ b/base/allocator/allocator_interception_mac.mm
@@ -77,13 +77,14 @@
   MACH_CHECK(result == KERN_SUCCESS, result) << "vm_region_64";
 
   // The kernel always returns a null object for VM_REGION_BASIC_INFO_64, but
-  // balance it with a deallocate in case this ever changes. See 10.9.2
-  // xnu-2422.90.20/osfmk/vm/vm_map.c vm_map_region.
+  // balance it with a deallocate in case this ever changes. See
+  // the VM_REGION_BASIC_INFO_64 case in vm_map_region() in 10.15's
+  // https://opensource.apple.com/source/xnu/xnu-6153.11.26/osfmk/vm/vm_map.c .
   mach_port_deallocate(mach_task_self(), unused);
 
   // Does the region fully enclose the zone pointers? Possibly unwarranted
-  // simplification used: using the size of a full version 8 malloc zone rather
-  // than the actual smaller size if the passed-in zone is not version 8.
+  // simplification used: using the size of a full version 10 malloc zone rather
+  // than the actual smaller size if the passed-in zone is not version 10.
   CHECK(*reprotection_start <= reinterpret_cast<vm_address_t>(default_zone));
   vm_size_t zone_offset = reinterpret_cast<vm_address_t>(default_zone) -
                           reinterpret_cast<vm_address_t>(*reprotection_start);
@@ -147,8 +148,8 @@
                           size_t size) {
   void* result = g_old_zone.memalign(zone, alignment, size);
   // Only die if posix_memalign would have returned ENOMEM, since there are
-  // other reasons why NULL might be returned (see
-  // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ).
+  // other reasons why null might be returned. See posix_memalign() in 10.15's
+  // https://opensource.apple.com/source/libmalloc/libmalloc-283/src/malloc.c .
   if (!result && size && alignment >= sizeof(void*) &&
       base::bits::IsPowerOfTwo(alignment)) {
     TerminateBecauseOutOfMemory(size);
@@ -197,8 +198,8 @@
                                     size_t size) {
   void* result = g_old_purgeable_zone.memalign(zone, alignment, size);
   // Only die if posix_memalign would have returned ENOMEM, since there are
-  // other reasons why NULL might be returned (see
-  // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c ).
+  // other reasons why null might be returned. See posix_memalign() in 10.15's
+  // https://opensource.apple.com/source/libmalloc/libmalloc-283/src/malloc.c .
   if (!result && size && alignment >= sizeof(void*) &&
       base::bits::IsPowerOfTwo(alignment)) {
     TerminateBecauseOutOfMemory(size);
@@ -363,11 +364,11 @@
 // === C malloc/calloc/valloc/realloc/posix_memalign ===
 
 // This approach is not perfect, as requests for amounts of memory larger than
-// MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will
-// still fail with a NULL rather than dying (see
-// http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details).
-// Unfortunately, it's the best we can do. Also note that this does not affect
-// allocations from non-default zones.
+// MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will still
+// fail with a NULL rather than dying (see malloc_zone_malloc() in
+// https://opensource.apple.com/source/libmalloc/libmalloc-283/src/malloc.c for
+// details). Unfortunately, it's the best we can do. Also note that this does
+// not affect allocations from non-default zones.
 
 #if !defined(ADDRESS_SANITIZER)
   // Don't do anything special on OOM for the malloc zones replaced by
diff --git a/base/allocator/malloc_zone_functions_mac.cc b/base/allocator/malloc_zone_functions_mac.cc
index a193cda..6c53042 100644
--- a/base/allocator/malloc_zone_functions_mac.cc
+++ b/base/allocator/malloc_zone_functions_mac.cc
@@ -40,6 +40,10 @@
     functions->free_definite_size = zone->free_definite_size;
   }
 
+  // Note that zone version 8 introduced a pressure relief callback, and version
+  // 10 introduced a claimed address callback, but neither are allocation or
+  // deallocation callbacks and so aren't important to intercept.
+
   functions->context = zone;
 }
 
diff --git a/base/strings/string_piece.h b/base/strings/string_piece.h
index 6e41bae0..3fc7620 100644
--- a/base/strings/string_piece.h
+++ b/base/strings/string_piece.h
@@ -325,8 +325,7 @@
   constexpr BasicStringPiece substr(
       size_type pos,
       size_type n = BasicStringPiece::npos) const {
-    // TODO(crbug.com/1049498): Be less lenient here and CHECK(pos <= size()).
-    pos = std::min(pos, size());
+    CHECK_LE(pos, size());
     return {data() + pos, std::min(n, size() - pos)};
   }
 
diff --git a/base/strings/string_piece_unittest.cc b/base/strings/string_piece_unittest.cc
index 5540baaa..ee4e0cf0 100644
--- a/base/strings/string_piece_unittest.cc
+++ b/base/strings/string_piece_unittest.cc
@@ -474,11 +474,7 @@
   ASSERT_EQ(a.substr(23, 99), c);
   ASSERT_EQ(a.substr(0), a);
   ASSERT_EQ(a.substr(3, 2), TestFixture::as_string("de"));
-  // empty string nonsense
-  ASSERT_EQ(a.substr(99, 2), e);
-  ASSERT_EQ(d.substr(99), e);
   ASSERT_EQ(d.substr(0, 99), e);
-  ASSERT_EQ(d.substr(99, 99), e);
 }
 
 TYPED_TEST(CommonStringPieceTest, CheckCustom) {
@@ -678,6 +674,11 @@
     StringPiece piece;
     ASSERT_DEATH_IF_SUPPORTED(piece.remove_prefix(1), "");
   }
+
+  {
+    StringPiece piece;
+    ASSERT_DEATH_IF_SUPPORTED(piece.substr(1), "");
+  }
 }
 
 TEST(StringPieceTest, ConstexprData) {
diff --git a/base/strings/string_util_internal.h b/base/strings/string_util_internal.h
index da3fb07..3ec97a7a 100644
--- a/base/strings/string_util_internal.h
+++ b/base/strings/string_util_internal.h
@@ -5,6 +5,8 @@
 #ifndef BASE_STRINGS_STRING_UTIL_INTERNAL_H_
 #define BASE_STRINGS_STRING_UTIL_INTERNAL_H_
 
+#include <algorithm>
+
 #include "base/logging.h"
 #include "base/notreached.h"
 #include "base/strings/string_piece.h"
@@ -130,7 +132,7 @@
   size_t end = (positions & TRIM_TRAILING)
                    ? input.find_last_not_of(trim_chars) + 1
                    : input.size();
-  return input.substr(begin, end - begin);
+  return input.substr(std::min(begin, input.size()), end - begin);
 }
 
 template <typename STR>
diff --git a/base/threading/sequence_bound.h b/base/threading/sequence_bound.h
index 3ee8ef6..217a1d53 100644
--- a/base/threading/sequence_bound.h
+++ b/base/threading/sequence_bound.h
@@ -6,156 +6,196 @@
 #define BASE_THREADING_SEQUENCE_BOUND_H_
 
 #include <new>
+#include <tuple>
 #include <type_traits>
+#include <utility>
 
 #include "base/bind.h"
 #include "base/callback.h"
+#include "base/callback_helpers.h"
 #include "base/compiler_specific.h"
 #include "base/location.h"
 #include "base/memory/aligned_memory.h"
 #include "base/memory/ptr_util.h"
+#include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
+#include "base/threading/sequence_bound_internal.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 
 namespace base {
 
-// SequenceBound facilitates owning objects that live on a specified sequence,
-// which is potentially different than the owner's sequence.  It encapsulates
-// the work of posting tasks to the specified sequence to construct T, call
-// methods on T, and destroy T.
+// Performing blocking work on a different task runner is a common pattern for
+// improving responsiveness of foreground task runners. `SequenceBound<T>`
+// provides an abstraction for an owner object living on the owner sequence, to
+// construct, call methods on, and destroy an object of type T that lives on a
+// different sequence (the bound sequence).
 //
-// It does not provide explicit access to the underlying object directly, to
-// prevent accidentally using it from the wrong sequence.
+// This makes it natural for code running on different sequences to be
+// partitioned along class boundaries, e.g.:
 //
-// Like std::unique_ptr<T>, a SequenceBound<T> may be moved between owners,
-// and posted across threads.  It may also be up-casted (only), to permit
-// SequenceBound to be used with interfaces.
+// class Tab {
+//  private:
+//   void OnScroll() {
+//     // ...
+//     io_helper_.AsyncCall(&IOHelper::SaveScrollPosition);
+//   }
+//   SequenceBound<IOHelper> io_helper_{GetBackgroundTaskRunner()};
+// };
 //
-// Basic usage looks like this:
+// Note: `SequenceBound<T>` intentionally does not expose a raw pointer to the
+// managed `T` to ensure its internal sequence-safety invariants are not
+// violated. As a result, `AsyncCall()` cannot simply use `base::OnceCallback`
 //
-//   // Some class that lives on |main_task_runner|.
-//   class MyClass {
+// SequenceBound also supports replies:
+//
+//   class Database {
 //    public:
-//     explicit MyClass(const char* widget_title) {}
-//     virtual ~MyClass() { ... }
-//     virtual void DoSomething(int arg) { ... }
+//     int Query(int value) {
+//       return value * value;
+//     }
 //   };
 //
-//   // On any thread...
-//   scoped_refptr<SequencedTaskRunner> main_task_runner = ...;
-//   auto widget = SequenceBound<MyClass>(main_task_runner, "My Title");
+//   // SequenceBound itself is owned on `SequencedTaskRunnerHandle::Get()`.
+//   // The managed Database instance managed by it is constructed and owned on
+//   // `GetDBTaskRunner()`.
+//   SequenceBound<Database> db(GetDBTaskRunner());
 //
-//   // Execute a single method on the object, on |main_task_runner|.
-//   widget.Post(FROM_HERE, &MyClass::DoSomething, 1234);
-//
-//   // Execute an arbitrary task on |main_task_runner| with a non-const pointer
-//   // to the object.
-//   widget.PostTaskWithThisObject(
-//       FROM_HERE,
-//       base::BindOnce([](MyClass* widget) {
-//         // Unlike with Post, we can issue multiple calls on |widget| within
-//         // the same stack frame.
-//         widget->DoSomething(42);
-//         widget->DoSomething(13);
-//       }));
-//
-//   // Execute an arbitrary task on |main_task_runner| with a const reference
-//   // to the object.
-//   widget.PostTaskWithThisObject(
-//       FROM_HERE,
-//       base::BindOnce([](const MyClass& widget) { ... }));
-//
-// Note that |widget| is constructed asynchronously on |main_task_runner|,
-// but calling Post() immediately is safe, since the actual call is posted
-// to |main_task_runner| as well.
-//
-// |widget| will be deleted on |main_task_runner| asynchronously when it goes
-// out of scope, or when Reset() is called.
-//
-// Here is a more complicated example that shows injection and upcasting:
-//
-//   // Some unrelated class that uses a |MyClass| to do something.
-//   class SomeConsumer {
-//    public:
-//    // Note that ownership of |widget| is given to us!
-//    explicit SomeConsumer(SequenceBound<MyClass> widget)
-//        : widget_(std::move(widget)) { ... }
-//
-//    ~SomeConsumer() {
-//      // |widget_| will be destroyed on the associated task runner.
-//    }
-//
-//     SequenceBound<MyClass> widget_;
+//   // `Database::Query()` runs on `GetDBTaskRunner()`, but
+//   // `reply_callback` will run on the owner task runner.
+//   auto reply_callback = [] (int result) {
+//     LOG(ERROR) << result;  // Prints 25.
 //   };
+//   db.AsyncCall(&Database::Query).WithArgs(5)
+//     .Then(base::BindOnce(reply_callback));
 //
-//   // Implementation of MyClass.
-//   class MyDerivedClass : public MyClass { ... };
+//   // When `db` goes out of scope, the Database instance will also be
+//   // destroyed via a task posted to `GetDBTaskRunner()`.
 //
-//   auto widget =
-//     SequenceBound<MyDerivedClass>(main_task_runner, ctor args);
-//   auto c = new SomeConsumer(std::move(widget));  // upcasts to MyClass
-
+// TODO(dcheng): SequenceBound should only be constructed, used, and destroyed
+// on a single sequence. This enforcement will gradually be enabled over time.
 template <typename T>
 class SequenceBound {
  public:
-  // Allow explicit null.
+  // Note: on construction, SequenceBound binds to the current sequence. Any
+  // subsequent SequenceBound calls (including destruction) must run on that
+  // same sequence.
+
+  // Constructs a null SequenceBound with no managed `T`.
+  // TODO(dcheng): Add an `Emplace()` method to go with `Reset()`.
   SequenceBound() = default;
 
-  // Construct a new instance of |T| that will be accessed only on
-  // |task_runner|.  One may post calls to it immediately upon return.
-  // This is marked as NO_SANITIZE because cfi doesn't like that we're casting
-  // uninitialized memory to a |T*|.  However, it's safe since (a) the cast is
-  // defined (see http://eel.is/c++draft/basic.life#6 for details), and (b) we
-  // don't use the resulting pointer in any way that requries it to be
-  // constructed, except by posting such a access to |impl_task_runner_| after
-  // posting construction there as well.
+  // Schedules asynchronous construction of a new instance of `T` on
+  // `task_runner`.
+  //
+  // Once the SequenceBound constructor completes, the caller can immediately
+  // use `AsyncCall()`, et cetera, to schedule work after the construction of
+  // `T` on `task_runner`.
+  //
+  // Marked NO_SANITIZE because cfi doesn't like casting uninitialized memory to
+  // `T*`. However, this is safe here because:
+  //
+  // 1. The cast is well-defined (see https://eel.is/c++draft/basic.life#6) and
+  // 2. The resulting pointer is only ever dereferenced on `impl_task_runner_`.
+  //    By the time SequenceBound's constructor returns, the task to construct
+  //    `T` will already be posted; thus, subsequent dereference of `t_` on
+  //    `impl_task_runner_` are safe.
   template <typename... Args>
   NO_SANITIZE("cfi-unrelated-cast")
   SequenceBound(scoped_refptr<base::SequencedTaskRunner> task_runner,
                 Args&&... args)
       : impl_task_runner_(std::move(task_runner)) {
-    // Allocate space for but do not construct an instance of |T|.
+    // Allocate space for but do not construct an instance of `T`.
     // AlignedAlloc() requires alignment be a multiple of sizeof(void*).
     storage_ = AlignedAlloc(
         sizeof(T), sizeof(void*) > alignof(T) ? sizeof(void*) : alignof(T));
     t_ = reinterpret_cast<T*>(storage_);
 
-    // Post construction to the impl thread.
+    // Ensure that `t_` will be initialized
     impl_task_runner_->PostTask(
         FROM_HERE,
         base::BindOnce(&ConstructOwnerRecord<Args...>, base::Unretained(t_),
                        std::forward<Args>(args)...));
   }
 
+  // If non-null, destruction of the managed `T` is posted to
+  // `impl_task_runner_`.`
   ~SequenceBound() { Reset(); }
 
-  // Move construction from the same type can just take the pointer without
-  // adjusting anything.  This is required in addition to the move conversion
-  // constructor below.
+  // Disallow copy or assignment. SequenceBound has single ownership of the
+  // managed `T`.
+  SequenceBound(const SequenceBound&) = delete;
+  SequenceBound& operator=(const SequenceBound&) = delete;
+
+  // Move construction and assignment.
   SequenceBound(SequenceBound&& other) { MoveRecordFrom(other); }
 
-  // Move construction is supported from any type that's compatible with |T|.
-  // This case handles |From| != |T|, so we must adjust the pointer offset.
+  SequenceBound& operator=(SequenceBound&& other) {
+    Reset();
+    MoveRecordFrom(other);
+    return *this;
+  }
+
+  // Move conversion helpers: allows upcasting from SequenceBound<Derived> to
+  // SequenceBound<Base>.
   template <typename From>
   SequenceBound(SequenceBound<From>&& other) {
     MoveRecordFrom(other);
   }
 
-  SequenceBound& operator=(SequenceBound&& other) {
-    // Clean up any object we currently own.
-    Reset();
-    MoveRecordFrom(other);
-    return *this;
-  }
-
   template <typename From>
   SequenceBound<T>& operator=(SequenceBound<From>&& other) {
-    // Clean up any object that we currently own.
     Reset();
     MoveRecordFrom(other);
     return *this;
   }
 
-  // Post a call to |method| to |impl_task_runner_|.
+  // Invokes `method` of the managed `T` on `impl_task_runner_`. May only be
+  // used when `is_null()` is false.
+  //
+  // Basic usage:
+  //
+  //   helper.AsyncCall(&IOHelper::DoWork);
+  //
+  // If `method` accepts arguments, use of `WithArgs()` to bind them is
+  // mandatory:
+  //
+  //   helper.AsyncCall(&IOHelper::DoWorkWithArgs).WithArgs(args);
+  //
+  // Optionally, use `Then()` to chain to a callback on the owner sequence after
+  // `method` completes. If `method` returns a non-void type, the return value
+  // will be passed to the chained callback.
+  //
+  //   helper.AsyncCall(&IOHelper::GetValue).Then(std::move(process_result));
+  //
+  // `WithArgs()` and `Then()` may also be combined:
+  //
+  //   helper.AsyncCall(&IOHelper::GetValueWithArgs).WithArgs(args)
+  //         .Then(std::move(process_result));
+  //
+  // but note that ordering is strict: `Then()` must always be last.
+  //
+  // Note: internally, this is implemented using a series of templated builders.
+  // Destruction of the builder may trigger task posting; as a result, using the
+  // builder as anything other than a temporary is not allowed.
+  //
+  // Similarly, triggering lifetime extension of the temporary (e.g. by binding
+  // to a const lvalue reference) is not allowed.
+  template <typename R, typename... Args>
+  auto AsyncCall(R (T::*method)(Args...),
+                 const Location& location = Location::Current()) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return AsyncCallBuilder<R (T::*)(Args...)>(this, &location, method);
+  }
+
+  template <typename R, typename... Args>
+  auto AsyncCall(R (T::*method)(Args...) const,
+                 const Location& location = Location::Current()) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    return AsyncCallBuilder<R (T::*)(Args...) const>(this, &location, method);
+  }
+
+  // Post a call to `method` to `impl_task_runner_`.
+  // TODO(dcheng): Deprecate this in favor of `AsyncCall()`.
   template <typename... MethodArgs, typename... Args>
   void Post(const base::Location& from_here,
             void (T::*method)(MethodArgs...),
@@ -166,7 +206,7 @@
                                                std::forward<Args>(args)...));
   }
 
-  // Posts |task| to |impl_task_runner_|, passing it a reference to the wrapped
+  // Posts `task` to `impl_task_runner_`, passing it a reference to the wrapped
   // object. This allows arbitrary logic to be safely executed on the object's
   // task runner. The object is guaranteed to remain alive for the duration of
   // the task.
@@ -194,17 +234,12 @@
   // TODO(liberato): Add PostOrCall(), to support cases where synchronous calls
   // are okay if it's the same task runner.
 
-  // TODO(liberato): Add PostAndReply()
-
-  // TODO(liberato): Allow creation of callbacks that bind to a weak pointer,
-  // and thread-hop to |impl_task_runner_| if needed.
-
-  // Post destruction of any object we own, and return to the null state.
+  // Resets `this` to null. If `this` is not currently null, posts destruction
+  // of the managed `T` to `impl_task_runner_`.
   void Reset() {
     if (is_null())
       return;
 
-    // Destruct the object on the impl thread.
     impl_task_runner_->PostTask(
         FROM_HERE, base::BindOnce(&DeleteOwnerRecord, base::Unretained(t_),
                                   base::Unretained(storage_)));
@@ -216,7 +251,12 @@
 
   // Same as above, but allows the caller to provide a closure to be invoked
   // immediately after destruction. The Closure is invoked on
-  // |impl_task_runner_|, iff the owned object was non-null.
+  // `impl_task_runner_`, iff the owned object was non-null.
+  //
+  // TODO(dcheng): Consider removing this; this appears to be used for test
+  // synchronization, but that could be achieved by posting
+  // `run_loop.QuitClosure()` to the destination sequence after calling
+  // `Reset()`.
   void ResetWithCallbackAfterDestruction(base::OnceClosure callback) {
     if (is_null())
       return;
@@ -234,66 +274,370 @@
     storage_ = nullptr;
   }
 
-  // Return whether we own anything.  Note that this does not guarantee that any
-  // previously owned object has been destroyed.  In particular, it will return
-  // true immediately after a call to Reset(), though the underlying object
-  // might still be pending destruction on the impl thread.
+  // Return true if `this` is logically null; otherwise, returns false.
+  //
+  // A SequenceBound is logically null if there is no managed `T`; it is only
+  // valid to call `AsyncCall()` on a non-null SequenceBound.
+  //
+  // Note that the concept of 'logically null' here does not exactly match the
+  // lifetime of `T`, which lives on `impl_task_runner_`. In particular, when
+  // SequenceBound is first constructed, `is_null()` may return false, even
+  // though the lifetime of `T` may not have begun yet on `impl_task_runner_`.
+  // Similarly, after `SequenceBound::Reset()`, `is_null()` may return true,
+  // even though the lifetime of `T` may not have ended yet on
+  // `impl_task_runner_`.
   bool is_null() const { return !t_; }
 
-  // True if and only if we have an object, with the same caveats as is_null().
+  // True if `this` is not logically null. See `is_null()`.
   explicit operator bool() const { return !is_null(); }
 
  private:
-  // Move everything from |other|, doing pointer adjustment as needed.
-  // This method is marked as NO_SANITIZE since (a) it might run before the
-  // posted ctor runs on |impl_task_runner_|, and (b) implicit conversions to
-  // non-virtual base classes are allowed before construction by the standard.
-  // See http://eel.is/c++draft/basic.life#6 for more information.
-  template <typename From>
-  void NO_SANITIZE("cfi-unrelated-cast") MoveRecordFrom(From&& other) {
-    // |other| might be is_null(), but that's okay.
-    impl_task_runner_ = std::move(other.impl_task_runner_);
-
-    // Note that static_cast<> isn't, in general, safe, since |other| might not
-    // be constructed yet.  Implicit conversion is supported, as long as it
-    // doesn't convert to a virtual base.  Of course, it allows only upcasts.
-    t_ = other.t_;
-
-    // The original storage is kept unmodified, so we can free it later.
-    storage_ = other.storage_;
-
-    other.storage_ = nullptr;
-    other.t_ = nullptr;
-  }
-
-  // Pointer to the object,  Pointer may be modified on the owning thread.
-  T* t_ = nullptr;
-
-  // Original allocated storage for the object.
-  void* storage_ = nullptr;
-
-  // The task runner on which all access to |t_| should happen.
-  scoped_refptr<base::SequencedTaskRunner> impl_task_runner_;
-
   // For move conversion.
   template <typename U>
   friend class SequenceBound;
 
-  // Run on impl thread to construct |t|'s storage.
+  // Support helpers for `AsyncCall()` implementation.
+  //
+  // Several implementation notes:
+  // 1. Tasks are posted via destroying the builder or an explicit call to
+  //    `Then()`.
+  //
+  // 2. A builder may be consumed by:
+  //
+  //    - calling `Then()`, which immediately posts the task chain
+  //    - calling `WithArgs()`, which returns a new builder with the captured
+  //      arguments
+  //
+  //    Builders that are consumed have the internal `sequence_bound_` field
+  //    nulled out; the hope is the compiler can see this and use it to
+  //    eliminate dead branches (e.g. correctness checks that aren't needed
+  //    since the code can be statically proved correct).
+  //
+  // 3. Builder methods are rvalue-qualified to try to enforce that the builder
+  //    is only used as a temporary. Note that this only helps so much; nothing
+  //    prevents a determined caller from using `std::move()` to force calls to
+  //    a non-temporary instance.
+  //
+  // TODO(dcheng): It might also be possible to use Gmock-style matcher
+  // composition, e.g. something like:
+  //
+  //   sb.AsyncCall(&Helper::DoWork, WithArgs(args),
+  //                Then(std::move(process_result));
+  //
+  // In theory, this might allow the elimination of magic destructors and
+  // better static checking by the compiler.
+  template <typename MethodPtrType>
+  class AsyncCallBuilderBase {
+   protected:
+    AsyncCallBuilderBase(SequenceBound* sequence_bound,
+                         const Location* location,
+                         MethodPtrType method)
+        : sequence_bound_(sequence_bound),
+          location_(location),
+          method_(method) {
+      // Common entry point for `AsyncCall()`, so check preconditions here.
+      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_bound_->sequence_checker_);
+      DCHECK(sequence_bound_->t_);
+    }
+
+    AsyncCallBuilderBase(AsyncCallBuilderBase&&) = default;
+    AsyncCallBuilderBase& operator=(AsyncCallBuilderBase&&) = default;
+
+    // `sequence_bound_` is consumed and set to `nullptr` when `Then()` is
+    // invoked. This is used as a flag for two potential states
+    //
+    // - if a method returns void, invoking `Then()` is optional. The destructor
+    //   will check if `sequence_bound_` is null; if it is, `Then()` was
+    //   already invoked and the task chain has already been posted, so the
+    //   destructor does not need to do anything. Otherwise, the destructor
+    //   needs to post the task to make the async call. In theory, the compiler
+    //   should be able to eliminate this branch based on the presence or
+    //   absence of a call to `Then()`.
+    //
+    // - if a method returns a non-void type, `Then()` *must* be invoked. The
+    //   destructor will `CHECK()` if `sequence_bound_` is non-null, since that
+    //   indicates `Then()` was not invoked. Similarly, note this branch should
+    //   be eliminated by the optimizer if the code is free of bugs. :)
+    SequenceBound* sequence_bound_;
+    // Subtle: this typically points at a Location *temporary*. This is used to
+    // try to detect errors resulting from lifetime extension of the async call
+    // factory temporaries, since the factory destructors can perform work. If
+    // the lifetime of the factory is incorrectly extended, dereferencing
+    // `location_` will trigger a stack-use-after-scope when running with ASan.
+    const Location* const location_;
+    MethodPtrType method_;
+  };
+
+  template <typename MethodPtrType, typename ReturnType, typename... Args>
+  class AsyncCallBuilderImpl;
+
+  // Selected method has no arguments and returns void.
+  template <typename MethodPtrType>
+  class AsyncCallBuilderImpl<MethodPtrType, void, std::tuple<>>
+      : public AsyncCallBuilderBase<MethodPtrType> {
+   public:
+    // Note: despite being here, this is actually still protected, since it is
+    // protected on the base class.
+    using AsyncCallBuilderBase<MethodPtrType>::AsyncCallBuilderBase;
+
+    ~AsyncCallBuilderImpl() {
+      if (this->sequence_bound_) {
+        this->sequence_bound_->impl_task_runner_->PostTask(
+            *this->location_,
+            BindOnce(this->method_, Unretained(this->sequence_bound_->t_)));
+      }
+    }
+
+    void Then(OnceClosure then_callback) && {
+      this->sequence_bound_->PostTaskAndThenHelper(
+          *this->location_,
+          BindOnce(this->method_, Unretained(this->sequence_bound_->t_)),
+          std::move(then_callback));
+      this->sequence_bound_ = nullptr;
+    }
+
+   private:
+    friend SequenceBound;
+
+    AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
+    AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
+  };
+
+  // Selected method has no arguments and returns non-void.
+  template <typename MethodPtrType, typename ReturnType>
+  class AsyncCallBuilderImpl<MethodPtrType, ReturnType, std::tuple<>>
+      : public AsyncCallBuilderBase<MethodPtrType> {
+   public:
+    // Note: despite being here, this is actually still protected, since it is
+    // protected on the base class.
+    using AsyncCallBuilderBase<MethodPtrType>::AsyncCallBuilderBase;
+
+    ~AsyncCallBuilderImpl() {
+      // Must use Then() since the method's return type is not void.
+      // Should be optimized out if the code is bug-free.
+      CHECK(!this->sequence_bound_)
+          << "Then() not invoked for a method that returns a non-void type; "
+          << "make sure to invoke Then() or use base::IgnoreResult()";
+    }
+
+    template <template <typename> class CallbackType,
+              typename ThenArg,
+              typename = EnableIfIsBaseCallback<CallbackType>>
+    void Then(CallbackType<void(ThenArg)> then_callback) && {
+      this->sequence_bound_->PostTaskAndThenHelper(
+          *this->location_,
+          BindOnce(this->method_, Unretained(this->sequence_bound_->t_)),
+          std::move(then_callback));
+      this->sequence_bound_ = nullptr;
+    }
+
+   private:
+    friend SequenceBound;
+
+    AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
+    AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
+  };
+
+  // Selected method has arguments. Return type can be void or non-void.
+  template <typename MethodPtrType, typename ReturnType, typename... Args>
+  class AsyncCallBuilderImpl<MethodPtrType, ReturnType, std::tuple<Args...>>
+      : public AsyncCallBuilderBase<MethodPtrType> {
+   public:
+    // Note: despite being here, this is actually still protected, since it is
+    // protected on the base class.
+    using AsyncCallBuilderBase<MethodPtrType>::AsyncCallBuilderBase;
+
+    ~AsyncCallBuilderImpl() {
+      // Must use WithArgs() since the method takes arguments.
+      // Should be optimized out if the code is bug-free.
+      CHECK(!this->sequence_bound_);
+    }
+
+    template <typename... BoundArgs>
+    auto WithArgs(BoundArgs&&... bound_args) {
+      SequenceBound* const sequence_bound =
+          std::exchange(this->sequence_bound_, nullptr);
+      return AsyncCallWithBoundArgsBuilder<ReturnType>(
+          sequence_bound, this->location_,
+          BindOnce(this->method_, Unretained(sequence_bound->t_),
+                   std::forward<BoundArgs>(bound_args)...));
+    }
+
+   private:
+    friend SequenceBound;
+
+    AsyncCallBuilderImpl(AsyncCallBuilderImpl&&) = default;
+    AsyncCallBuilderImpl& operator=(AsyncCallBuilderImpl&&) = default;
+  };
+
+  template <typename MethodPtrType,
+            typename R = internal::ExtractMethodReturnType<MethodPtrType>,
+            typename ArgsTuple =
+                internal::ExtractMethodArgsTuple<MethodPtrType>>
+  using AsyncCallBuilder = AsyncCallBuilderImpl<MethodPtrType, R, ArgsTuple>;
+
+  // Support factories when arguments are bound using `WithArgs()`. These
+  // factories don't need to handle raw method pointers, since everything has
+  // already been packaged into a base::OnceCallback.
+  template <typename ReturnType>
+  class AsyncCallWithBoundArgsBuilderBase {
+   protected:
+    AsyncCallWithBoundArgsBuilderBase(SequenceBound* sequence_bound,
+                                      const Location* location,
+                                      base::OnceCallback<ReturnType()> callback)
+        : sequence_bound_(sequence_bound),
+          location_(location),
+          callback_(std::move(callback)) {
+      DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_bound_->sequence_checker_);
+      DCHECK(sequence_bound_->t_);
+    }
+
+    // Subtle: the internal helpers rely on move elision. Preventing move
+    // elision (e.g. using `std::move()` when returning the temporary) will
+    // trigger a `CHECK()` since `sequence_bound_` is not reset to nullptr on
+    // move.
+    AsyncCallWithBoundArgsBuilderBase(
+        AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
+    AsyncCallWithBoundArgsBuilderBase& operator=(
+        AsyncCallWithBoundArgsBuilderBase&&) noexcept = default;
+
+    SequenceBound* sequence_bound_;
+    const Location* const location_;
+    base::OnceCallback<ReturnType()> callback_;
+  };
+
+  // Note: this doesn't handle a void return type, which has an explicit
+  // specialization below.
+  template <typename ReturnType>
+  class AsyncCallWithBoundArgsBuilder
+      : public AsyncCallWithBoundArgsBuilderBase<ReturnType> {
+   public:
+    ~AsyncCallWithBoundArgsBuilder() {
+      // Must use Then() since the method's return type is not void.
+      // Should be optimized out if the code is bug-free.
+      CHECK(!this->sequence_bound_);
+    }
+
+    template <template <typename> class CallbackType,
+              typename ThenArg,
+              typename = EnableIfIsBaseCallback<CallbackType>>
+    void Then(CallbackType<void(ThenArg)> then_callback) && {
+      this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
+                                                   std::move(this->callback_),
+                                                   std::move(then_callback));
+      this->sequence_bound_ = nullptr;
+    }
+
+   protected:
+    using AsyncCallWithBoundArgsBuilderBase<
+        ReturnType>::AsyncCallWithBoundArgsBuilderBase;
+
+   private:
+    friend SequenceBound;
+
+    AsyncCallWithBoundArgsBuilder(AsyncCallWithBoundArgsBuilder&&) = default;
+    AsyncCallWithBoundArgsBuilder& operator=(AsyncCallWithBoundArgsBuilder&&) =
+        default;
+  };
+
+  template <>
+  class AsyncCallWithBoundArgsBuilder<void>
+      : public AsyncCallWithBoundArgsBuilderBase<void> {
+   public:
+    // Note: despite being here, this is actually still protected, since it is
+    // protected on the base class.
+    using AsyncCallWithBoundArgsBuilderBase<
+        void>::AsyncCallWithBoundArgsBuilderBase;
+
+    ~AsyncCallWithBoundArgsBuilder() {
+      if (this->sequence_bound_) {
+        this->sequence_bound_->impl_task_runner_->PostTask(
+            *this->location_, std::move(this->callback_));
+      }
+    }
+
+    void Then(OnceClosure then_callback) && {
+      this->sequence_bound_->PostTaskAndThenHelper(*this->location_,
+                                                   std::move(this->callback_),
+                                                   std::move(then_callback));
+      this->sequence_bound_ = nullptr;
+    }
+
+   private:
+    friend SequenceBound;
+
+    AsyncCallWithBoundArgsBuilder(AsyncCallWithBoundArgsBuilder&&) = default;
+    AsyncCallWithBoundArgsBuilder& operator=(AsyncCallWithBoundArgsBuilder&&) =
+        default;
+  };
+
+  void PostTaskAndThenHelper(const Location& location,
+                             OnceCallback<void()> callback,
+                             OnceClosure then_callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    impl_task_runner_->PostTaskAndReply(location, std::move(callback),
+                                        std::move(then_callback));
+  }
+
+  template <typename ReturnType,
+            template <typename>
+            class CallbackType,
+            typename ThenArg,
+            typename = EnableIfIsBaseCallback<CallbackType>>
+  void PostTaskAndThenHelper(const Location& location,
+                             OnceCallback<ReturnType()> callback,
+                             CallbackType<void(ThenArg)> then_callback) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    OnceCallback<void(ThenArg)>&& once_then_callback = std::move(then_callback);
+    impl_task_runner_->PostTaskAndReplyWithResult(
+        location, std::move(callback), std::move(once_then_callback));
+  }
+
+  // Helper to support move construction and move assignment.
+  //
+  // Marked NO_SANITIZE since:
+  // 1. SequenceBound can be moved before `t_` is constructed on
+  //    `impl_task_runner_` but
+  // 2. Implicit conversions to non-virtual base classes are allowed before the
+  //    lifetime of `t_` has started (see https://eel.is/c++draft/basic.life#6).
+  template <typename From>
+  void NO_SANITIZE("cfi-unrelated-cast") MoveRecordFrom(From&& other) {
+    // TODO(dcheng): Consider adding a static_assert to provide a friendlier
+    // error message.
+    impl_task_runner_ = std::move(other.impl_task_runner_);
+
+    // Subtle: this must not use static_cast<>, since the lifetime of the
+    // managed `T` may not have begun yet. However, the standard explicitly
+    // still allows implicit conversion to a non-virtual base class.
+    t_ = std::exchange(other.t_, nullptr);
+    storage_ = std::exchange(other.storage_, nullptr);
+  }
+
+  // Pointer to the managed `T`. This field is only read and written on
+  // the sequence associated with `sequence_checker_`.
+  T* t_ = nullptr;
+
+  // Storage originally allocated by `AlignedAlloc()`. Maintained separately
+  // from  `t_` since the original, unadjusted pointer needs to be passed to
+  // `AlignedFree()`.
+  void* storage_ = nullptr;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  // Task runner which manages `t_`. `t_` is constructed, destroyed, and
+  // dereferenced only on this task runner.
+  scoped_refptr<base::SequencedTaskRunner> impl_task_runner_;
+
+  // Helpers for constructing and destroying `T` on `impl_task_runner_`.
   template <typename... Args>
   static void ConstructOwnerRecord(T* t, std::decay_t<Args>&&... args) {
     new (t) T(std::move(args)...);
   }
 
-  // Destruct the object associated with |t|, and delete |storage|.
   static void DeleteOwnerRecord(T* t, void* storage) {
     t->~T();
     AlignedFree(storage);
   }
-
-  // To preserve ownership semantics, we disallow copy construction / copy
-  // assignment.  Move construction / assignment is fine.
-  DISALLOW_COPY_AND_ASSIGN(SequenceBound);
 };
 
 }  // namespace base
diff --git a/base/threading/sequence_bound_internal.h b/base/threading/sequence_bound_internal.h
new file mode 100644
index 0000000..c3d3c3f
--- /dev/null
+++ b/base/threading/sequence_bound_internal.h
@@ -0,0 +1,48 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
+#define BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
+
+#include <tuple>
+
+#include "base/compiler_specific.h"
+
+namespace base {
+
+namespace internal {
+
+// Helpers to simplify sharing templates between non-const and const methods.
+// Normally, matching against a method pointer type requires defining both a
+// `R (T::*)(Args...)` and a `R (T::*)(Args...) const` overload of the template
+// function. Rather than doing that, these helpers allow extraction of `R` and
+// `Args...` from a method pointer type deduced as `MethodPointerType`.
+
+template <typename MethodPtrType>
+struct MethodTraits;
+
+template <typename R, typename T, typename... Args>
+struct MethodTraits<R (T::*)(Args...)> {
+  using ReturnType = R;
+  using ArgsTuple = std::tuple<Args...>;
+};
+
+template <typename R, typename T, typename... Args>
+struct MethodTraits<R (T::*)(Args...) const> {
+  using ReturnType = R;
+  using ArgsTuple = std::tuple<Args...>;
+};
+
+template <typename MethodPtrType>
+using ExtractMethodReturnType =
+    typename MethodTraits<MethodPtrType>::ReturnType;
+
+template <typename MethodPtrType>
+using ExtractMethodArgsTuple = typename MethodTraits<MethodPtrType>::ArgsTuple;
+
+}  // namespace internal
+
+}  // namespace base
+
+#endif  // BASE_THREADING_SEQUENCE_BOUND_INTERNAL_H_
diff --git a/base/threading/sequence_bound_unittest.cc b/base/threading/sequence_bound_unittest.cc
index ecf0e3543..1b75d1b 100644
--- a/base/threading/sequence_bound_unittest.cc
+++ b/base/threading/sequence_bound_unittest.cc
@@ -4,11 +4,13 @@
 
 #include "base/threading/sequence_bound.h"
 
+#include <utility>
+
 #include "base/macros.h"
 #include "base/run_loop.h"
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
-#include "base/threading/thread_task_runner_handle.h"
+#include "base/threading/sequenced_task_runner_handle.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -30,9 +32,13 @@
     kOtherDtorValue = 444,
   };
 
-  void SetUp() override { task_runner_ = base::ThreadTaskRunnerHandle::Get(); }
-
-  void TearDown() override { task_environment_.RunUntilIdle(); }
+  void TearDown() override {
+    // Make sure that any objects owned by `SequenceBound` have been destroyed
+    // to avoid tripping leak detection.
+    RunLoop run_loop;
+    task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
+    run_loop.Run();
+  }
 
   // Do-nothing base class, just so we can test assignment of derived classes.
   // It introduces a virtual destructor, so that casting derived classes to
@@ -66,10 +72,14 @@
     MultiplyDerived(Value* ptr1, Value* ptr2) : Other(ptr1), Derived(ptr2) {}
   };
 
+  // TODO(dcheng): This isn't used, but upcasting to a virtual base class is
+  // unsafe and is currently unchecked! Add these safety checks back in.
   struct VirtuallyDerived : public virtual Base {};
 
   base::test::TaskEnvironment task_environment_;
-  scoped_refptr<base::SequencedTaskRunner> task_runner_;
+  scoped_refptr<base::SequencedTaskRunner> task_runner_ =
+      base::SequencedTaskRunnerHandle::Get();
+
   Value value_ = kInitialValue;
 };
 
@@ -397,4 +407,275 @@
   // Test passes if SequenceBound constructor does not crash in AlignedAlloc().
 }
 
+TEST_F(SequenceBoundTest, SelfMoveAssign) {
+  class EmptyClass {};
+  SequenceBound<EmptyClass> value(task_runner_);
+  EXPECT_FALSE(value.is_null());
+  // Clang has a warning for self-move, so be clever.
+  auto& actually_the_same_value = value;
+  value = std::move(actually_the_same_value);
+  // Note: in general, moved-from objects are in a valid but undefined state.
+  // This is merely a test that self-move doesn't result in something bad
+  // happening; this is not an assertion that self-move will always have this
+  // behavior.
+  EXPECT_TRUE(value.is_null());
+}
+
+namespace {
+
+class NoArgsVoidReturn {
+ public:
+  void Method() {
+    if (loop_)
+      loop_->Quit();
+  }
+  void ConstMethod() const {
+    if (loop_)
+      loop_->Quit();
+  }
+
+  void set_loop(RunLoop* loop) { loop_ = loop; }
+
+ private:
+  RunLoop* loop_ = nullptr;
+};
+
+class NoArgsIntReturn {
+ public:
+  int Method() { return 123; }
+  int ConstMethod() const { return 456; }
+};
+
+class IntArgVoidReturn {
+ public:
+  IntArgVoidReturn(int* method_called_with, int* const_method_called_with)
+      : method_called_with_(method_called_with),
+        const_method_called_with_(const_method_called_with) {}
+
+  void Method(int x) {
+    *method_called_with_ = x;
+    if (loop_)
+      loop_->Quit();
+  }
+  void ConstMethod(int x) const {
+    *const_method_called_with_ = x;
+    if (loop_)
+      loop_->Quit();
+  }
+
+  void set_loop(RunLoop* loop) { loop_ = loop; }
+
+ private:
+  int* const method_called_with_;
+  int* const const_method_called_with_;
+
+  RunLoop* loop_ = nullptr;
+};
+
+class IntArgIntReturn {
+ public:
+  int Method(int x) { return -x; }
+  int ConstMethod(int x) const { return -x; }
+};
+
+}  // namespace
+
+TEST_F(SequenceBoundTest, AsyncCallNoArgsNoThen) {
+  SequenceBound<NoArgsVoidReturn> s(task_runner_);
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
+    s.AsyncCall(&NoArgsVoidReturn::Method);
+    loop.Run();
+  }
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&NoArgsVoidReturn::set_loop).WithArgs(&loop);
+    s.AsyncCall(&NoArgsVoidReturn::ConstMethod);
+    loop.Run();
+  }
+}
+
+TEST_F(SequenceBoundTest, AsyncCallIntArgNoThen) {
+  int method_called_with = 0;
+  int const_method_called_with = 0;
+  SequenceBound<IntArgVoidReturn> s(task_runner_, &method_called_with,
+                                    &const_method_called_with);
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
+    s.AsyncCall(&IntArgVoidReturn::Method).WithArgs(123);
+    loop.Run();
+    EXPECT_EQ(123, method_called_with);
+  }
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&IntArgVoidReturn::set_loop).WithArgs(&loop);
+    s.AsyncCall(&IntArgVoidReturn::ConstMethod).WithArgs(456);
+    loop.Run();
+    EXPECT_EQ(456, const_method_called_with);
+  }
+}
+
+TEST_F(SequenceBoundTest, AsyncCallNoArgsVoidThen) {
+  SequenceBound<NoArgsVoidReturn> s(task_runner_);
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&NoArgsVoidReturn::Method).Then(BindLambdaForTesting([&]() {
+      loop.Quit();
+    }));
+    loop.Run();
+  }
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&NoArgsVoidReturn::ConstMethod)
+        .Then(BindLambdaForTesting([&]() { loop.Quit(); }));
+    loop.Run();
+  }
+}
+
+TEST_F(SequenceBoundTest, AsyncCallNoArgsIntThen) {
+  SequenceBound<NoArgsIntReturn> s(task_runner_);
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&NoArgsIntReturn::Method)
+        .Then(BindLambdaForTesting([&](int result) {
+          EXPECT_EQ(123, result);
+          loop.Quit();
+        }));
+    loop.Run();
+  }
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&NoArgsIntReturn::ConstMethod)
+        .Then(BindLambdaForTesting([&](int result) {
+          EXPECT_EQ(456, result);
+          loop.Quit();
+        }));
+    loop.Run();
+  }
+}
+
+TEST_F(SequenceBoundTest, AsyncCallWithArgsVoidThen) {
+  int method_called_with = 0;
+  int const_method_called_with = 0;
+  SequenceBound<IntArgVoidReturn> s(task_runner_, &method_called_with,
+                                    &const_method_called_with);
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&IntArgVoidReturn::Method)
+        .WithArgs(123)
+        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+    loop.Run();
+    EXPECT_EQ(123, method_called_with);
+  }
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&IntArgVoidReturn::ConstMethod)
+        .WithArgs(456)
+        .Then(BindLambdaForTesting([&] { loop.Quit(); }));
+    loop.Run();
+    EXPECT_EQ(456, const_method_called_with);
+  }
+}
+
+TEST_F(SequenceBoundTest, AsyncCallWithArgsIntThen) {
+  SequenceBound<IntArgIntReturn> s(task_runner_);
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&IntArgIntReturn::Method)
+        .WithArgs(123)
+        .Then(BindLambdaForTesting([&](int result) {
+          EXPECT_EQ(-123, result);
+          loop.Quit();
+        }));
+    loop.Run();
+  }
+
+  {
+    RunLoop loop;
+    s.AsyncCall(&IntArgIntReturn::ConstMethod)
+        .WithArgs(456)
+        .Then(BindLambdaForTesting([&](int result) {
+          EXPECT_EQ(-456, result);
+          loop.Quit();
+        }));
+    loop.Run();
+  }
+}
+
+// TODO(dcheng): Maybe use the nocompile harness here instead of being
+// "clever"...
+TEST_F(SequenceBoundTest, NoCompileTests) {
+  // TODO(dcheng): Test calling WithArgs() on a method that takes no arguments.
+  // Given:
+  //   class C {
+  //     void F();
+  //   };
+  //
+  // Then:
+  //   SequenceBound<C> s(...);
+  //   s.AsyncCall(&C::F).WithArgs(...);
+  //
+  // should not compile.
+  //
+  // TODO(dcheng): Test calling Then() before calling WithArgs().
+  // Given:
+  //   class C {
+  //     void F(int);
+  //   };
+  //
+  // Then:
+  //   SequenceBound<C> s(...);
+  //   s.AsyncCall(&C::F).Then(...).WithArgs(...);
+  //
+  // should not compile.
+  //
+}
+
+class SequenceBoundDeathTest : public ::testing::Test {
+ protected:
+  void TearDown() override {
+    // Make sure that any objects owned by `SequenceBound` have been destroyed
+    // to avoid tripping leak detection.
+    RunLoop run_loop;
+    task_runner_->PostTask(FROM_HERE, run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
+  // Death tests use fork(), which can interact (very) poorly with threads.
+  test::SingleThreadTaskEnvironment task_environment_;
+  scoped_refptr<SequencedTaskRunner> task_runner_ =
+      base::SequencedTaskRunnerHandle::Get();
+};
+
+TEST_F(SequenceBoundDeathTest, AsyncCallIntArgNoWithArgsShouldCheck) {
+  SequenceBound<IntArgIntReturn> s(task_runner_);
+  EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method), "");
+}
+
+TEST_F(SequenceBoundDeathTest, AsyncCallIntReturnNoThenShouldCheck) {
+  {
+    SequenceBound<NoArgsIntReturn> s(task_runner_);
+    EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&NoArgsIntReturn::Method), "");
+  }
+
+  {
+    SequenceBound<IntArgIntReturn> s(task_runner_);
+    EXPECT_DEATH_IF_SUPPORTED(s.AsyncCall(&IntArgIntReturn::Method).WithArgs(0),
+                              "");
+  }
+}
+
 }  // namespace base
diff --git a/base/util/memory_pressure/BUILD.gn b/base/util/memory_pressure/BUILD.gn
index 7d142d6..296ec52 100644
--- a/base/util/memory_pressure/BUILD.gn
+++ b/base/util/memory_pressure/BUILD.gn
@@ -44,6 +44,13 @@
       "system_memory_pressure_evaluator_fuchsia.h",
     ]
   }
+
+  if (is_linux && !is_chromeos) {
+    sources += [
+      "system_memory_pressure_evaluator_linux.cc",
+      "system_memory_pressure_evaluator_linux.h",
+    ]
+  }
 }
 
 source_set("unittests") {
@@ -74,6 +81,10 @@
     deps += [ "//third_party/fuchsia-sdk/sdk/fidl/fuchsia.memorypressure" ]
     sources += [ "system_memory_pressure_evaluator_fuchsia_unittest.cc" ]
   }
+
+  if (is_linux && !is_chromeos) {
+    sources += [ "system_memory_pressure_evaluator_linux_unittest.cc" ]
+  }
 }
 
 static_library("test_support") {
diff --git a/base/util/memory_pressure/system_memory_pressure_evaluator.cc b/base/util/memory_pressure/system_memory_pressure_evaluator.cc
index 7179fa09..babb4f8 100644
--- a/base/util/memory_pressure/system_memory_pressure_evaluator.cc
+++ b/base/util/memory_pressure/system_memory_pressure_evaluator.cc
@@ -14,6 +14,8 @@
 #elif defined(OS_WIN)
 #include "base/util/memory_pressure/system_memory_pressure_evaluator_win.h"
 #include "base/win/windows_version.h"
+#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
+#include "base/util/memory_pressure/system_memory_pressure_evaluator_linux.h"
 #endif
 
 namespace util {
@@ -43,6 +45,9 @@
     evaluator->CreateOSSignalPressureEvaluator(monitor->CreateVoter());
   }
   return evaluator;
+#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
+  return std::make_unique<util::linux::SystemMemoryPressureEvaluator>(
+      monitor->CreateVoter());
 #endif
   return nullptr;
 }
diff --git a/base/util/memory_pressure/system_memory_pressure_evaluator_linux.cc b/base/util/memory_pressure/system_memory_pressure_evaluator_linux.cc
new file mode 100644
index 0000000..258d983
--- /dev/null
+++ b/base/util/memory_pressure/system_memory_pressure_evaluator_linux.cc
@@ -0,0 +1,157 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/util/memory_pressure/system_memory_pressure_evaluator_linux.h"
+
+#include "base/bind.h"
+#include "base/process/process_metrics.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/time/time.h"
+#include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
+
+namespace {
+
+constexpr int kKiBperMiB = 1024;
+
+int GetAvailableSystemMemoryMiB(const base::SystemMemoryInfoKB& mem_info) {
+  // Use 'available' metric if is is present,
+  // if no (kernels < 3.14), let's make a rough evaluation using free physical
+  // memory plus buffers and caches (that OS can free in case of low memory
+  // state)
+  int mem_available =
+      mem_info.available ? mem_info.available
+                         : (mem_info.free + mem_info.buffers + mem_info.cached);
+  // How much physical memory is actively available for use right now, in MBs.
+  return mem_available / kKiBperMiB;
+}
+
+}  // namespace
+
+namespace util {
+namespace linux {
+
+const base::TimeDelta SystemMemoryPressureEvaluator::kModeratePressureCooldown =
+    base::TimeDelta::FromSeconds(10);
+
+const int SystemMemoryPressureEvaluator::kDefaultModerateThresholdPc = 75;
+const int SystemMemoryPressureEvaluator::kDefaultCriticalThresholdPc = 85;
+
+SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
+    std::unique_ptr<MemoryPressureVoter> voter)
+    : util::SystemMemoryPressureEvaluator(std::move(voter)),
+      moderate_pressure_repeat_count_(0) {
+  if (InferThresholds())
+    StartObserving();
+}
+
+SystemMemoryPressureEvaluator::SystemMemoryPressureEvaluator(
+    int moderate_threshold_mb,
+    int critical_threshold_mb,
+    std::unique_ptr<MemoryPressureVoter> voter)
+    : util::SystemMemoryPressureEvaluator(std::move(voter)),
+      moderate_threshold_mb_(moderate_threshold_mb),
+      critical_threshold_mb_(critical_threshold_mb),
+      moderate_pressure_repeat_count_(0) {
+  DCHECK_GE(moderate_threshold_mb_, critical_threshold_mb_);
+  DCHECK_GT(critical_threshold_mb_, 0);
+  StartObserving();
+}
+
+void SystemMemoryPressureEvaluator::StartObserving() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  timer_.Start(
+      FROM_HERE, base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod,
+      base::BindRepeating(&SystemMemoryPressureEvaluator::CheckMemoryPressure,
+                          base::Unretained(this)));
+}
+
+void SystemMemoryPressureEvaluator::StopObserving() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // If StartObserving failed, StopObserving will still get called.
+  timer_.Stop();
+}
+
+bool SystemMemoryPressureEvaluator::GetSystemMemoryInfo(
+    base::SystemMemoryInfoKB* mem_info) {
+  return base::GetSystemMemoryInfo(mem_info);
+}
+
+void SystemMemoryPressureEvaluator::CheckMemoryPressure() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // Get the previous pressure level and update the current one.
+  MemoryPressureLevel old_vote = current_vote();
+  SetCurrentVote(CalculateCurrentPressureLevel());
+
+  // |notify| will be set to true if MemoryPressureListeners need to be
+  // notified of a memory pressure level state change.
+  bool notify = false;
+  switch (current_vote()) {
+    case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE:
+      break;
+
+    case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE:
+      if (old_vote != current_vote()) {
+        // This is a new transition to moderate pressure so notify.
+        moderate_pressure_repeat_count_ = 0;
+        notify = true;
+      } else {
+        // Already in moderate pressure, only notify if sustained over the
+        // cooldown period.
+        const int kModeratePressureCooldownCycles =
+            kModeratePressureCooldown /
+            base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod;
+        if (++moderate_pressure_repeat_count_ ==
+            kModeratePressureCooldownCycles) {
+          moderate_pressure_repeat_count_ = 0;
+          notify = true;
+        }
+      }
+      break;
+
+    case base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL:
+      // Always notify of critical pressure levels.
+      notify = true;
+      break;
+  }
+
+  SendCurrentVote(notify);
+}
+
+bool SystemMemoryPressureEvaluator::InferThresholds() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  base::SystemMemoryInfoKB mem_info;
+  if (!GetSystemMemoryInfo(&mem_info))
+    return false;
+  critical_threshold_mb_ =
+      mem_info.total * (100 - kDefaultCriticalThresholdPc) / 100 / kKiBperMiB;
+  moderate_threshold_mb_ =
+      mem_info.total * (100 - kDefaultModerateThresholdPc) / 100 / kKiBperMiB;
+  return true;
+}
+
+base::MemoryPressureListener::MemoryPressureLevel
+SystemMemoryPressureEvaluator::CalculateCurrentPressureLevel() {
+  base::SystemMemoryInfoKB mem_info;
+  if (GetSystemMemoryInfo(&mem_info)) {
+    // How much system memory is actively available for use right now, in MBs.
+    int available = GetAvailableSystemMemoryMiB(mem_info);
+
+    // Determine if the available memory is under critical memory pressure.
+    if (available <= critical_threshold_mb_)
+      return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL;
+
+    // Determine if the available memory is under moderate memory pressure.
+    if (available <= moderate_threshold_mb_)
+      return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE;
+  }
+  // No memory pressure was detected.
+  return base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE;
+}
+
+}  // namespace linux
+}  // namespace util
diff --git a/base/util/memory_pressure/system_memory_pressure_evaluator_linux.h b/base/util/memory_pressure/system_memory_pressure_evaluator_linux.h
new file mode 100644
index 0000000..0f5c43d8
--- /dev/null
+++ b/base/util/memory_pressure/system_memory_pressure_evaluator_linux.h
@@ -0,0 +1,112 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_UTIL_MEMORY_PRESSURE_SYSTEM_MEMORY_PRESSURE_EVALUATOR_LINUX_H_
+#define BASE_UTIL_MEMORY_PRESSURE_SYSTEM_MEMORY_PRESSURE_EVALUATOR_LINUX_H_
+
+#include "base/memory/memory_pressure_listener.h"
+#include "base/process/process_metrics.h"
+#include "base/sequence_checker.h"
+#include "base/timer/timer.h"
+#include "base/util/memory_pressure/memory_pressure_voter.h"
+#include "base/util/memory_pressure/system_memory_pressure_evaluator.h"
+
+namespace util {
+namespace linux {
+
+// Linux memory pressure voter. Because there is no OS provided signal this
+// polls at a low frequency, and applies internal hysteresis.
+// TODO(https://crbug.com/1119396): use Pressure Stall Information (PSI) on
+// kernels >4.20.
+class SystemMemoryPressureEvaluator
+    : public util::SystemMemoryPressureEvaluator {
+ public:
+  using MemoryPressureLevel = base::MemoryPressureListener::MemoryPressureLevel;
+
+  // Constants governing the polling and hysteresis behaviour of the observer.
+  // The time which should pass between 2 successive moderate memory pressure
+  // signals.
+  static const base::TimeDelta kModeratePressureCooldown;
+
+  // Default minimum free memory thresholds, in percents.
+  static const int kDefaultModerateThresholdPc;
+  static const int kDefaultCriticalThresholdPc;
+
+  // Default constructor. Will choose thresholds automatically based on the
+  // actual amount of system memory.
+  explicit SystemMemoryPressureEvaluator(
+      std::unique_ptr<MemoryPressureVoter> voter);
+
+  // Constructor with explicit memory thresholds. These represent the amount of
+  // free memory below which the applicable memory pressure state engages.
+  SystemMemoryPressureEvaluator(int moderate_threshold_mb,
+                                int critical_threshold_mb,
+                                std::unique_ptr<MemoryPressureVoter> voter);
+
+  ~SystemMemoryPressureEvaluator() override = default;
+
+  SystemMemoryPressureEvaluator(const SystemMemoryPressureEvaluator&) = delete;
+  SystemMemoryPressureEvaluator& operator=(
+      const SystemMemoryPressureEvaluator&) = delete;
+
+  // Returns the moderate pressure level free memory threshold, in MB.
+  int moderate_threshold_mb() const { return moderate_threshold_mb_; }
+
+  // Returns the critical pressure level free memory threshold, in MB.
+  int critical_threshold_mb() const { return critical_threshold_mb_; }
+
+ protected:
+  // Internals are exposed for unittests.
+
+  // Starts observing the memory fill level. Calls to StartObserving should
+  // always be matched with calls to StopObserving.
+  void StartObserving();
+
+  // Stop observing the memory fill level. May be safely called if
+  // StartObserving has not been called. Must be called from the same thread on
+  // which the monitor was instantiated.
+  void StopObserving();
+
+  // Checks memory pressure, storing the current level, applying any hysteresis
+  // and emitting memory pressure level change signals as necessary. This
+  // function is called periodically while the monitor is observing memory
+  // pressure. Must be called from the same thread on which the monitor was
+  // instantiated.
+  void CheckMemoryPressure();
+
+  // Automatically infers threshold values based on system memory.
+  // Returns 'true' if succeeded.
+  bool InferThresholds();
+
+  // Calculates the current instantaneous memory pressure level. This does not
+  // use any hysteresis and simply returns the result at the current moment. Can
+  // be called on any thread.
+  MemoryPressureLevel CalculateCurrentPressureLevel();
+
+  // This is just a wrapper for base:: function;
+  // declared as virtual for unit testing
+  virtual bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* mem_info);
+
+ private:
+  // Threshold amounts of available memory that trigger pressure levels
+  int moderate_threshold_mb_;
+  int critical_threshold_mb_;
+
+  // A periodic timer to check for memory pressure changes.
+  base::RepeatingTimer timer_;
+
+  // To slow down the amount of moderate pressure event calls, this gets used to
+  // count the number of events since the last event occurred. This is used by
+  // |CheckMemoryPressure| to apply hysteresis on the raw results of
+  // |CalculateCurrentPressureLevel|.
+  int moderate_pressure_repeat_count_;
+
+  // Ensures that this object is used from a single sequence.
+  SEQUENCE_CHECKER(sequence_checker_);
+};
+
+}  // namespace linux
+}  // namespace util
+
+#endif  // BASE_UTIL_MEMORY_PRESSURE_SYSTEM_MEMORY_PRESSURE_EVALUATOR_LINUX_H_
diff --git a/base/util/memory_pressure/system_memory_pressure_evaluator_linux_unittest.cc b/base/util/memory_pressure/system_memory_pressure_evaluator_linux_unittest.cc
new file mode 100644
index 0000000..e5db67e
--- /dev/null
+++ b/base/util/memory_pressure/system_memory_pressure_evaluator_linux_unittest.cc
@@ -0,0 +1,261 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/util/memory_pressure/system_memory_pressure_evaluator_linux.h"
+
+#include "base/bind.h"
+#include "base/run_loop.h"
+#include "base/test/task_environment.h"
+#include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace util {
+namespace linux {
+
+namespace {
+
+struct PressureSettings {
+  int phys_left_mb;
+  base::MemoryPressureListener::MemoryPressureLevel level;
+};
+
+}  // namespace
+
+// This is outside of the anonymous namespace so that it can be seen as a friend
+// to the evaluator class.
+class TestSystemMemoryPressureEvaluator : public SystemMemoryPressureEvaluator {
+ public:
+  using SystemMemoryPressureEvaluator::CalculateCurrentPressureLevel;
+  using SystemMemoryPressureEvaluator::CheckMemoryPressure;
+
+  static const unsigned long kKiBperMiB = 1024;
+  static const unsigned long kMemoryTotalMb = 4096;
+
+  TestSystemMemoryPressureEvaluator(bool large_memory,
+                                    std::unique_ptr<MemoryPressureVoter> voter)
+      : SystemMemoryPressureEvaluator(std::move(voter)) {
+    // Generate a plausible amount of memory.
+    mem_status_.total = kMemoryTotalMb * kKiBperMiB;
+
+    // Rerun InferThresholds.
+    InferThresholds();
+    // Stop the timer.
+    StopObserving();
+  }
+
+  TestSystemMemoryPressureEvaluator(int system_memory_mb,
+                                    int moderate_threshold_mb,
+                                    int critical_threshold_mb)
+      : SystemMemoryPressureEvaluator(moderate_threshold_mb,
+                                      critical_threshold_mb,
+                                      nullptr) {
+    // Set the amount of system memory.
+    mem_status_.total = system_memory_mb * kKiBperMiB;
+
+    // Stop the timer.
+    StopObserving();
+  }
+
+  TestSystemMemoryPressureEvaluator(const TestSystemMemoryPressureEvaluator&) =
+      delete;
+  TestSystemMemoryPressureEvaluator& operator=(
+      const TestSystemMemoryPressureEvaluator&) = delete;
+
+  MOCK_METHOD1(OnMemoryPressure,
+               void(base::MemoryPressureListener::MemoryPressureLevel level));
+
+  // Sets up the memory status to reflect the provided absolute memory left.
+  void SetMemoryFree(int phys_left_mb) {
+    // ullTotalPhys is set in the constructor and not modified.
+
+    // Set the amount of available memory.
+    mem_status_.available = phys_left_mb * kKiBperMiB;
+    DCHECK_LT(mem_status_.available, mem_status_.total);
+  }
+
+  void SetNone() { SetMemoryFree(moderate_threshold_mb() + 1); }
+
+  void SetModerate() { SetMemoryFree(moderate_threshold_mb() - 1); }
+
+  void SetCritical() { SetMemoryFree(critical_threshold_mb() - 1); }
+
+  bool GetSystemMemoryInfo(base::SystemMemoryInfoKB* mem_info) override {
+    *mem_info = mem_status_;
+    return true;
+  }
+
+ private:
+  base::SystemMemoryInfoKB mem_status_;
+};
+
+class LinuxSystemMemoryPressureEvaluatorTest : public testing::Test {
+ protected:
+  void CalculateCurrentMemoryPressureLevelTest(
+      TestSystemMemoryPressureEvaluator* evaluator) {
+    int mod = evaluator->moderate_threshold_mb();
+    evaluator->SetMemoryFree(mod + 1);
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
+              evaluator->CalculateCurrentPressureLevel());
+
+    evaluator->SetMemoryFree(mod);
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+              evaluator->CalculateCurrentPressureLevel());
+
+    evaluator->SetMemoryFree(mod - 1);
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+              evaluator->CalculateCurrentPressureLevel());
+
+    int crit = evaluator->critical_threshold_mb();
+    evaluator->SetMemoryFree(crit + 1);
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+              evaluator->CalculateCurrentPressureLevel());
+
+    evaluator->SetMemoryFree(crit);
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+              evaluator->CalculateCurrentPressureLevel());
+
+    evaluator->SetMemoryFree(crit - 1);
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+              evaluator->CalculateCurrentPressureLevel());
+  }
+
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
+};
+
+// Tests the fundamental direct calculation of memory pressure with manually
+// specified threshold levels.
+TEST_F(LinuxSystemMemoryPressureEvaluatorTest,
+       CalculateCurrentMemoryPressureLevelCustom) {
+  static const int kSystemMb = 512;
+  static const int kModerateMb = 256;
+  static const int kCriticalMb = 128;
+
+  TestSystemMemoryPressureEvaluator evaluator(kSystemMb, kModerateMb,
+                                              kCriticalMb);
+
+  EXPECT_EQ(kModerateMb, evaluator.moderate_threshold_mb());
+  EXPECT_EQ(kCriticalMb, evaluator.critical_threshold_mb());
+
+  ASSERT_NO_FATAL_FAILURE(CalculateCurrentMemoryPressureLevelTest(&evaluator));
+}
+
+// This test tests the various transition states from memory pressure, looking
+// for the correct behavior on event reposting as well as state updates.
+TEST_F(LinuxSystemMemoryPressureEvaluatorTest, CheckMemoryPressure) {
+  MultiSourceMemoryPressureMonitor monitor;
+  monitor.ResetSystemEvaluatorForTesting();
+
+  // Large-memory.
+  testing::StrictMock<TestSystemMemoryPressureEvaluator> evaluator(
+      true, monitor.CreateVoter());
+
+  base::MemoryPressureListener listener(
+      FROM_HERE,
+      base::BindRepeating(&TestSystemMemoryPressureEvaluator::OnMemoryPressure,
+                          base::Unretained(&evaluator)));
+
+  // Checking the memory pressure at 0% load should not produce any
+  // events.
+  evaluator.SetNone();
+  evaluator.CheckMemoryPressure();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
+            evaluator.current_vote());
+
+  // Setting the memory level to 80% should produce a moderate pressure level.
+  EXPECT_CALL(
+      evaluator,
+      OnMemoryPressure(
+          base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE));
+  evaluator.SetModerate();
+  evaluator.CheckMemoryPressure();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+            evaluator.current_vote());
+  testing::Mock::VerifyAndClearExpectations(&evaluator);
+
+  // Check that the event gets reposted after a while.
+  const int kModeratePressureCooldownCycles =
+      evaluator.kModeratePressureCooldown /
+      base::MemoryPressureMonitor::kUMAMemoryPressureLevelPeriod;
+
+  for (int i = 0; i < kModeratePressureCooldownCycles; ++i) {
+    if (i + 1 == kModeratePressureCooldownCycles) {
+      EXPECT_CALL(
+          evaluator,
+          OnMemoryPressure(
+              base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE));
+    }
+    evaluator.CheckMemoryPressure();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+              evaluator.current_vote());
+    testing::Mock::VerifyAndClearExpectations(&evaluator);
+  }
+
+  // Setting the memory usage to 99% should produce critical levels.
+  EXPECT_CALL(
+      evaluator,
+      OnMemoryPressure(
+          base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL));
+  evaluator.SetCritical();
+  evaluator.CheckMemoryPressure();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+            evaluator.current_vote());
+  testing::Mock::VerifyAndClearExpectations(&evaluator);
+
+  // Calling it again should immediately produce a second call.
+  EXPECT_CALL(
+      evaluator,
+      OnMemoryPressure(
+          base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL));
+  evaluator.CheckMemoryPressure();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL,
+            evaluator.current_vote());
+  testing::Mock::VerifyAndClearExpectations(&evaluator);
+
+  // When lowering the pressure again there should be a notification and the
+  // pressure should go back to moderate.
+  EXPECT_CALL(
+      evaluator,
+      OnMemoryPressure(
+          base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE));
+  evaluator.SetModerate();
+  evaluator.CheckMemoryPressure();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+            evaluator.current_vote());
+  testing::Mock::VerifyAndClearExpectations(&evaluator);
+
+  // Check that the event gets reposted after a while.
+  for (int i = 0; i < kModeratePressureCooldownCycles; ++i) {
+    if (i + 1 == kModeratePressureCooldownCycles) {
+      EXPECT_CALL(
+          evaluator,
+          OnMemoryPressure(
+              base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE));
+    }
+    evaluator.CheckMemoryPressure();
+    base::RunLoop().RunUntilIdle();
+    EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE,
+              evaluator.current_vote());
+    testing::Mock::VerifyAndClearExpectations(&evaluator);
+  }
+
+  // Going down to no pressure should not produce an notification.
+  evaluator.SetNone();
+  evaluator.CheckMemoryPressure();
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
+            evaluator.current_vote());
+  testing::Mock::VerifyAndClearExpectations(&evaluator);
+}
+
+}  // namespace linux
+}  // namespace util
diff --git a/build/.gitignore b/build/.gitignore
index a4a36d9..2e963395 100644
--- a/build/.gitignore
+++ b/build/.gitignore
@@ -9,6 +9,7 @@
 /cros_cache/
 /Debug
 /Debug_x64
+/fuchsia/internal/
 /goma
 /gomacc.lock
 /ipch/
diff --git a/build/config/android/rules.gni b/build/config/android/rules.gni
index 4a60106..b61dce0 100644
--- a/build/config/android/rules.gni
+++ b/build/config/android/rules.gni
@@ -4112,9 +4112,6 @@
   #       will still be verified regardless of the value of this flag.
   #   strip_resources: Whether to ignore android resources found in the .aar.
   #   custom_package: Java package for generated R.java files.
-  #   extract_assets: Whether to extract and include assets found in the .aar.
-  #       If the file contains assets, either extract_assets or ignore_assets
-  #       must be set.
   #   extract_native_libraries: Whether to extract .so files found in the .aar.
   #       If the file contains .so, either extract_native_libraries or
   #       ignore_native_libraries must be set.
@@ -4142,7 +4139,6 @@
     _unpack_target_name = "${_target_name_without_java_or_junit}__unpack_aar"
     _ignore_aidl = defined(invoker.ignore_aidl) && invoker.ignore_aidl
     _ignore_assets = defined(invoker.ignore_assets) && invoker.ignore_assets
-    _extract_assets = defined(invoker.extract_assets) && invoker.extract_assets
     _ignore_manifest =
         defined(invoker.ignore_manifest) && invoker.ignore_manifest
     _ignore_native_libraries = defined(invoker.ignore_native_libraries) &&
@@ -4176,7 +4172,7 @@
     #   rm -r out/tmp
     _scanned_files = read_file(_info_path, "scope")
 
-    _contains_scanned_assets = _scanned_files.assets != []
+    _use_scanned_assets = !_ignore_assets && _scanned_files.assets != []
 
     assert(_ignore_aidl || _scanned_files.aidl == [],
            "android_aar_prebuilt() aidl not yet supported." +
@@ -4190,11 +4186,6 @@
     assert(
         !(_ignore_native_libraries && _extract_native_libraries),
         "ignore_native_libraries and extract_native_libraries cannot both be set.")
-    assert(!_contains_scanned_assets || (_ignore_assets || _extract_assets),
-           "android_aar_prebuilt() contains asset files." +
-               " Please set ignore_assets or extract_assets.")
-    assert(!(_ignore_assets && _extract_assets),
-           "ignore_assets and extract_assets cannot both be set.")
     assert(!_scanned_files.has_native_libraries ||
            _scanned_files.native_libraries != [])
     assert(_scanned_files.has_classes_jar || _scanned_files.subjars == [])
@@ -4252,7 +4243,7 @@
                 rebase_path(_scanned_files.native_libraries, "", _output_path),
                 "abspath")
       }
-      if (_extract_assets && _contains_scanned_assets) {
+      if (_use_scanned_assets) {
         outputs +=
             get_path_info(rebase_path(_scanned_files.assets, "", _output_path),
                           "abspath")
@@ -4310,10 +4301,8 @@
       not_needed(invoker, [ "strip_drawables" ])
     }
 
-    _should_extract_assets = _extract_assets && _contains_scanned_assets
-
     # Create the android_assets target for assets
-    if (_should_extract_assets) {
+    if (_use_scanned_assets) {
       _assets_target_name = "${target_name}__assets"
       android_assets(_assets_target_name) {
         forward_variables_from(invoker, [ "testonly" ])
diff --git a/build/config/fuchsia/generate_runner_scripts.gni b/build/config/fuchsia/generate_runner_scripts.gni
index a0bae5e..af5ead3 100644
--- a/build/config/fuchsia/generate_runner_scripts.gni
+++ b/build/config/fuchsia/generate_runner_scripts.gni
@@ -104,6 +104,7 @@
     # currently always matches the |target_cpu|, rather than for the build host
     # architecture.
     data += [
+      "//third_party/fuchsia-sdk/sdk/tools/${target_cpu}/device-finder",
       "//third_party/fuchsia-sdk/sdk/tools/${target_cpu}/fvm",
       "//third_party/fuchsia-sdk/sdk/tools/${target_cpu}/merkleroot",
       "//third_party/fuchsia-sdk/sdk/tools/${target_cpu}/pm",
@@ -115,6 +116,9 @@
     } else {
       data += [
         "${qemu_root}/",
+        "//third_party/fuchsia-sdk/sdk/bin/fpave.sh",
+        "//third_party/fuchsia-sdk/sdk/bin/fuchsia-common.sh",
+        "//third_party/fuchsia-sdk/sdk/meta/manifest.json",
         "//third_party/llvm-build/Release+Asserts/bin/llvm-symbolizer",
       ]
     }
@@ -148,7 +152,7 @@
     }
 
     executable_args += [
-      "--output-directory",
+      "--output-dir",
       "@WrappedPath(.)",
       "--target-cpu",
       target_cpu,
diff --git a/build/fuchsia/aemu_target.py b/build/fuchsia/aemu_target.py
index e997541..a5f1d7e 100644
--- a/build/fuchsia/aemu_target.py
+++ b/build/fuchsia/aemu_target.py
@@ -4,6 +4,7 @@
 
 """Implements commands for running and interacting with Fuchsia on AEMU."""
 
+import emu_target
 import os
 import platform
 import qemu_target
@@ -12,14 +13,17 @@
 from common import GetEmuRootForPlatform
 
 
-class AemuTarget(qemu_target.QemuTarget):
+def GetTargetType():
+  return AemuTarget
 
-  def __init__(self, output_dir, target_cpu, system_log_file, emu_type,
-               cpu_cores, require_kvm, ram_size_mb, enable_graphics,
-               hardware_gpu):
+
+class AemuTarget(qemu_target.QemuTarget):
+  EMULATOR_NAME = 'aemu'
+
+  def __init__(self, output_dir, target_cpu, system_log_file, cpu_cores,
+               require_kvm, ram_size_mb, enable_graphics, hardware_gpu):
     super(AemuTarget, self).__init__(output_dir, target_cpu, system_log_file,
-                                     emu_type, cpu_cores, require_kvm,
-                                     ram_size_mb)
+                                     cpu_cores, require_kvm, ram_size_mb)
 
     # TODO(crbug.com/1000907): Enable AEMU for arm64.
     if platform.machine() == 'aarch64':
@@ -27,12 +31,27 @@
     self._enable_graphics = enable_graphics
     self._hardware_gpu = hardware_gpu
 
+  @staticmethod
+  def RegisterArgs(arg_parser):
+    emu_target.EmuTarget.RegisterArgs(arg_parser)
+    aemu_args = arg_parser.add_argument_group('aemu', 'AEMU Arguments')
+    aemu_args.add_argument('--enable-graphics',
+                           action='store_true',
+                           default=False,
+                           help='Start AEMU with graphics instead of '\
+                                'headless.')
+    aemu_args.add_argument('--hardware-gpu',
+                           action='store_true',
+                           default=False,
+                           help='Use local GPU hardware instead of '\
+                                'Swiftshader.')
+
   def _EnsureEmulatorExists(self, path):
     assert os.path.exists(path), \
-          'This checkout is missing %s.' % (self._emu_type)
+          'This checkout is missing %s.' % (self.EMULATOR_NAME)
 
   def _BuildCommand(self):
-    aemu_folder = GetEmuRootForPlatform(self._emu_type)
+    aemu_folder = GetEmuRootForPlatform(self.EMULATOR_NAME)
 
     self._EnsureEmulatorExists(aemu_folder)
     aemu_path = os.path.join(aemu_folder, 'emulator')
@@ -77,7 +96,7 @@
     return aemu_command
 
   def _GetVulkanIcdFile(self):
-    return os.path.join(GetEmuRootForPlatform(self._emu_type), 'lib64',
+    return os.path.join(GetEmuRootForPlatform(self.EMULATOR_NAME), 'lib64',
                         'vulkan', 'vk_swiftshader_icd.json')
 
   def _SetEnv(self):
diff --git a/build/fuchsia/common.py b/build/fuchsia/common.py
index be66d08..5eaefa2b 100644
--- a/build/fuchsia/common.py
+++ b/build/fuchsia/common.py
@@ -5,9 +5,12 @@
 import logging
 import os
 import platform
+import signal
 import socket
 import subprocess
 import sys
+import time
+import threading
 
 DIR_SOURCE_ROOT = os.path.abspath(
     os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
@@ -92,3 +95,42 @@
   port = sock.getsockname()[1]
   sock.close()
   return port
+
+
+def SubprocessCallWithTimeout(command, silent=False, timeout_secs=None):
+  """Helper function for running a command.
+
+  Args:
+    command: The command to run.
+    silent: If true, stdout and stderr of the command will not be printed.
+    timeout_secs: Maximum amount of time allowed for the command to finish.
+
+  Returns:
+    A tuple of (return code, stdout, stderr) of the command. Raises
+    an exception if the subprocess times out.
+  """
+
+  if silent:
+    devnull = open(os.devnull, 'w')
+    process = subprocess.Popen(command, stdout=devnull, stderr=devnull)
+  else:
+    process = subprocess.Popen(command,
+                               stdout=subprocess.PIPE,
+                               stderr=subprocess.PIPE)
+  timeout_timer = None
+  if timeout_secs:
+
+    def interrupt_process():
+      process.send_signal(signal.SIGKILL)
+
+    timeout_timer = threading.Timer(timeout_secs, interrupt_process)
+    timeout_timer.start()
+
+  out, err = process.communicate()
+  if timeout_timer:
+    timeout_timer.cancel()
+
+  if process.returncode == -9:
+    raise Exception('Timeout when executing \"%s\".' % ' '.join(command))
+
+  return process.returncode, out, err
diff --git a/build/fuchsia/common_args.py b/build/fuchsia/common_args.py
index 855bc11..12c9df2 100644
--- a/build/fuchsia/common_args.py
+++ b/build/fuchsia/common_args.py
@@ -2,16 +2,69 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import argparse
+import importlib
 import logging
 import os
 import sys
 
-from aemu_target import AemuTarget
-from device_target import DeviceTarget
-from qemu_target import QemuTarget
 from common import GetHostArchFromPlatform
 
 
+def _AddTargetSpecificationArgs(arg_parser):
+  """Returns a parser that handles the target type used for the test run."""
+
+  device_args = arg_parser.add_argument_group(
+      'target', 'Arguments specifying the Fuchsia target type.')
+  device_args.add_argument('--target-cpu',
+                           default=GetHostArchFromPlatform(),
+                           help='GN target_cpu setting for the build. Defaults '
+                           'to the same architecture as host cpu.')
+  device_args.add_argument('--device',
+                           default=None,
+                           choices=['aemu', 'qemu', 'device', 'ext'],
+                           help='Choose to run on aemu|qemu|device. '
+                           'By default, Fuchsia will run on AEMU on x64 '
+                           'hosts and QEMU on arm64 hosts. Alternatively, '
+                           'setting to ext will require specifying the '
+                           'subclass of Target class used via the '
+                           '--ext-device-path flag.')
+  device_args.add_argument('-d',
+                           action='store_const',
+                           dest='device',
+                           const='device',
+                           help='Run on device instead of emulator.')
+  device_args.add_argument('--ext-device-path',
+                           default=None,
+                           help='Specify path to file that contains the '
+                           'subclass of Target that will be used. Only '
+                           'needed if device specific operations such as '
+                           'paving is required.')
+
+
+def _GetTargetClass(args):
+  """Gets the target class to be used for the test run."""
+
+  if args.device == 'ext':
+    if not args.ext_device_path:
+      raise Exception('--ext-device-path flag must be set when device flag '
+                      'set to ext.')
+    target_path = args.ext_device_path
+  else:
+    if not args.device:
+      args.device = 'aemu' if args.target_cpu == 'x64' else 'qemu'
+    target_path = '%s_target' % args.device
+
+  try:
+    loaded_target = importlib.import_module(target_path)
+  except ImportError:
+    logging.error('Cannot import from %s. Make sure that --ext-device-path '
+                  'is pointing to a file containing a target '
+                  'module.' % target_path)
+    raise
+  return loaded_target.GetTargetType()
+
+
 def AddCommonArgs(arg_parser):
   """Adds command line arguments to |arg_parser| for options which are shared
   across test and executable target types.
@@ -19,6 +72,15 @@
   Args:
     arg_parser: an ArgumentParser object."""
 
+  _AddTargetSpecificationArgs(arg_parser)
+
+  # Parse the args used to specify target
+  module_args, _ = arg_parser.parse_known_args()
+
+  # Determine the target class and register target specific args.
+  target_class = _GetTargetClass(module_args)
+  target_class.RegisterArgs(arg_parser)
+
   package_args = arg_parser.add_argument_group('package', 'Fuchsia Packages')
   package_args.add_argument(
       '--package',
@@ -30,47 +92,11 @@
       help='Name of the package to execute, defined in ' + 'package metadata.')
 
   common_args = arg_parser.add_argument_group('common', 'Common arguments')
-  common_args.add_argument(
-      '--output-directory',
-      type=os.path.realpath,
-      default=None,
-      help=('Path to the directory in which build files are located. '))
-  common_args.add_argument(
-      '--target-cpu',
-      default=GetHostArchFromPlatform(),
-      help=('GN target_cpu setting for the build. Defaults to the same '
-            'architecture as host cpu.'))
   common_args.add_argument('--target-staging-path',
                            help='target path under which to stage packages '
                            'during deployment.', default='/data')
-  common_args.add_argument('--device', default=None,
-                           choices=['aemu','qemu','device'],
-                           help='Choose to run on aemu|qemu|device. ' +
-                                'By default, Fuchsia will run in QEMU.')
-  common_args.add_argument('-d', action='store_const', dest='device',
-                           const='device',
-                           help='Run on device instead of emulator.')
-  common_args.add_argument('--host', help='The IP of the target device. ' +
-                           'Optional.')
-  common_args.add_argument('--node-name',
-                           help='The node-name of the device to boot or deploy '
-                                'to. Optional, will use the first discovered '
-                                'device if omitted.')
-  common_args.add_argument('--port', '-p', type=int, default=22,
-                           help='The port of the SSH service running on the ' +
-                                'device. Optional.')
-  common_args.add_argument('--ssh-config', '-F',
-                           help='The path to the SSH configuration used for '
-                                'connecting to the target device.')
-  common_args.add_argument('--fuchsia-out-dir',
-                           help='Path to a Fuchsia build output directory. '
-                                'Equivalent to setting --ssh_config and '
-                                '---os-check=ignore')
   common_args.add_argument('--runner-logs-dir',
                            help='Directory to write test runner logs to.')
-  common_args.add_argument('--system-log-file',
-                           help='File to write system logs to. Specify - to '
-                                'log to stdout.')
   common_args.add_argument('--exclude-system-logs',
                            action='store_false',
                            dest='include_system_logs',
@@ -78,38 +104,6 @@
   common_args.add_argument('--verbose', '-v', default=False,
                            action='store_true',
                            help='Enable debug-level logging.')
-  common_args.add_argument(
-      '--qemu-cpu-cores',
-      type=int,
-      default=4,
-      help='Sets the number of CPU cores to provide if launching in a VM.')
-  common_args.add_argument('--memory', type=int, default=2048,
-                           help='Sets the RAM size (MB) if launching in a VM'),
-  common_args.add_argument(
-      '--allow-no-kvm',
-      action='store_false',
-      dest='require_kvm',
-      default=True,
-      help='Do not require KVM acceleration for emulators.')
-  common_args.add_argument(
-      '--os_check', choices=['check', 'update', 'ignore'],
-      default='update',
-      help='Sets the OS version enforcement policy. If \'check\', then the '
-           'deployment process will halt if the target\'s version doesn\'t '
-           'match. If \'update\', then the target device will automatically '
-           'be repaved. If \'ignore\', then the OS version won\'t be checked.')
-
-  aemu_args = arg_parser.add_argument_group('aemu', 'AEMU Arguments')
-  aemu_args.add_argument(
-      '--enable-graphics',
-      action='store_true',
-      default=False,
-      help='Start AEMU with graphics instead of headless.')
-  aemu_args.add_argument(
-      '--hardware-gpu',
-      action='store_true',
-      default=False,
-      help='Use local GPU hardware instead of Swiftshader.')
 
 
 def ConfigureLogging(args):
@@ -129,47 +123,29 @@
       logging.DEBUG if args.verbose else logging.WARN)
 
 
-def GetDeploymentTargetForArgs(args):
-  """Constructs a deployment target object using parameters taken from
-  command line arguments."""
-  if args.system_log_file == '-':
-    system_log_file = sys.stdout
-  elif args.system_log_file:
-    system_log_file = open(args.system_log_file, 'w')
-  else:
-    system_log_file = None
+# TODO(crbug.com/1121763): remove the need for additional_args
+def GetDeploymentTargetForArgs(additional_args=None):
+  """Constructs a deployment target object using command line arguments.
+     If needed, an additional_args dict can be used to supplement the
+     command line arguments."""
 
-  # Allow fuchsia to run on emulator if device not explicitly chosen.
-  # AEMU is the default emulator for x64 Fuchsia, and QEMU for others.
-  if not args.device:
-    if args.target_cpu == 'x64':
-      args.device = 'aemu'
-    else:
-      args.device = 'qemu'
+  # Determine target type from command line arguments.
+  device_type_parser = argparse.ArgumentParser()
+  _AddTargetSpecificationArgs(device_type_parser)
+  module_args, _ = device_type_parser.parse_known_args()
+  target_class = _GetTargetClass(module_args)
 
-  target_args = { 'output_dir':args.output_directory,
-                  'target_cpu':args.target_cpu,
-                  'system_log_file':system_log_file }
-  if args.device == 'device':
-    target_args.update({ 'host':args.host,
-                         'node_name':args.node_name,
-                         'port':args.port,
-                         'ssh_config':args.ssh_config,
-                         'fuchsia_out_dir':args.fuchsia_out_dir,
-                         'os_check':args.os_check })
-    return DeviceTarget(**target_args)
-  else:
-    target_args.update({
-        'cpu_cores': args.qemu_cpu_cores,
-        'require_kvm': args.require_kvm,
-        'emu_type': args.device,
-        'ram_size_mb': args.memory
-    })
-    if args.device == 'qemu':
-      return QemuTarget(**target_args)
-    else:
-      target_args.update({
-          'enable_graphics': args.enable_graphics,
-          'hardware_gpu': args.hardware_gpu
-      })
-      return AemuTarget(**target_args)
+  # Process command line args needed to initialize target in separate arg
+  # parser.
+  target_arg_parser = argparse.ArgumentParser()
+  target_class.RegisterArgs(target_arg_parser)
+  known_args, _ = target_arg_parser.parse_known_args()
+  target_args = vars(known_args)
+
+  # target_cpu is needed to determine target type, so we need to add
+  # it here to the args used to initialize the Target.
+  target_args.update({'target_cpu': module_args.target_cpu})
+
+  if additional_args:
+    target_args.update(additional_args)
+  return target_class(**target_args)
diff --git a/build/fuchsia/device_target.py b/build/fuchsia/device_target.py
index 4723eee..94cf8058 100644
--- a/build/fuchsia/device_target.py
+++ b/build/fuchsia/device_target.py
@@ -23,42 +23,40 @@
 
 # The maximum times to attempt mDNS resolution when connecting to a freshly
 # booted Fuchsia instance before aborting.
-_BOOT_DISCOVERY_ATTEMPTS = 30
+BOOT_DISCOVERY_ATTEMPTS = 30
 
-# Number of seconds to wait when querying a list of all devices over mDNS.
-_LIST_DEVICES_TIMEOUT_SECS = 3
-
-#Number of failed connection attempts before redirecting system logs to stdout.
+# Number of failed connection attempts before redirecting system logs to stdout.
 CONNECT_RETRY_COUNT_BEFORE_LOGGING = 10
 
 TARGET_HASH_FILE_PATH = '/data/.hash'
 
+# Number of seconds to wait when querying a list of all devices over mDNS.
+_LIST_DEVICES_TIMEOUT_SECS = 3
+
+# Time between a reboot command is issued and when connection attempts from the
+# host begin.
+_REBOOT_SLEEP_PERIOD = 20
+
+
+def GetTargetType():
+  return DeviceTarget
+
+
 class DeviceTarget(target.Target):
   """Prepares a device to be used as a deployment target. Depending on the
   command line parameters, it automatically handling a number of preparatory
-  steps relating to address resolution, device provisioning, and SDK
-  versioning.
+  steps relating to address resolution.
 
   If |_node_name| is unset:
-    If there is one running device, use it for deployment and execution. The
-    device's SDK version is checked unless --os-check=ignore is set.
-    If --os-check=update is set, then the target device is repaved if the SDK
-    version doesn't match.
+    If there is one running device, use it for deployment and execution.
 
     If there are more than one running devices, then abort and instruct the
     user to re-run the command with |_node_name|
 
-    Otherwise, if there are no running devices, then search for a device
-    running Zedboot, and pave it.
-
-
   If |_node_name| is set:
     If there is a running device with a matching nodename, then it is used
     for deployment and execution.
 
-    Otherwise, attempt to pave a device with a matching nodename, and use it
-    for deployment and execution.
-
   If |_host| is set:
     Deploy to a device at the host IP address as-is."""
 
@@ -85,7 +83,9 @@
     self._port = port if port else 22
     self._system_log_file = system_log_file
     self._host = host
-    self._fuchsia_out_dir = fuchsia_out_dir
+    self._fuchsia_out_dir = None
+    if fuchsia_out_dir:
+      self._fuchsia_out_dir = os.path.expanduser(fuchsia_out_dir)
     self._node_name = node_name
     self._os_check = os_check
     self._amber_repo = None
@@ -113,6 +113,39 @@
       boot_data.ProvisionSSH(output_dir)
       self._ssh_config_path = boot_data.GetSSHConfigPath(output_dir)
 
+  @staticmethod
+  def RegisterArgs(arg_parser):
+    target.Target.RegisterArgs(arg_parser)
+    device_args = arg_parser.add_argument_group('device', 'Device Arguments')
+    device_args.add_argument('--host',
+                             help='The IP of the target device. Optional.')
+    device_args.add_argument('--node-name',
+                             help='The node-name of the device to boot or '
+                             'deploy to. Optional, will use the first '
+                             'discovered device if omitted.')
+    device_args.add_argument('--port',
+                             '-p',
+                             type=int,
+                             default=22,
+                             help='The port of the SSH service running on the '
+                             'device. Optional.')
+    device_args.add_argument('--ssh-config',
+                             '-F',
+                             help='The path to the SSH configuration used for '
+                             'connecting to the target device.')
+    device_args.add_argument('--fuchsia-out-dir',
+                             help='Path to a Fuchsia build output directory. '
+                             'Equivalent to setting --ssh_config and '
+                             '--os-check=ignore')
+    device_args.add_argument(
+        '--os_check',
+        choices=['check', 'update', 'ignore'],
+        default='update',
+        help="Sets the OS version enforcement policy. If 'check', then the "
+        "deployment process will halt if the target\'s version doesn\'t "
+        "match. If 'update', then the target device will automatically "
+        "be repaved. If 'ignore', then the OS version won\'t be checked.")
+
   def _SDKHashMatches(self):
     """Checks if /data/.hash on the device matches SDK_ROOT/.hash.
 
@@ -127,7 +160,10 @@
 
       return filecmp.cmp(tmp.name, os.path.join(SDK_ROOT, '.hash'), False)
 
-  def __Discover(self):
+  def _ProvisionDeviceIfNecessary(self):
+    pass
+
+  def _Discover(self):
     """Queries mDNS for the IP address of a booted Fuchsia instance whose name
     matches |_node_name| on the local area network. If |_node_name| isn't
     specified, and there is only one device on the network, then returns the
@@ -186,33 +222,11 @@
   def Start(self):
     if self._host:
       self._WaitUntilReady()
-
     else:
-      should_provision = False
-
-      if self.__Discover():
-        self._WaitUntilReady()
-
-        if self._os_check != 'ignore':
-          if self._SDKHashMatches():
-            if self._os_check == 'update':
-              logging.info( 'SDK hash does not match; rebooting and repaving.')
-              self.RunCommand(['dm', 'reboot'])
-              should_provision = True
-            elif self._os_check == 'check':
-              raise Exception('Target device SDK version does not match.')
-
-      else:
-        should_provision = True
-
-      if should_provision:
-        boot_data.AssertBootImagesExist(self._GetTargetSdkArch(), 'generic')
-        self.__ProvisionDevice()
-
+      self._ProvisionDeviceIfNecessary()
       assert self._node_name
       assert self._host
 
-
   def GetAmberRepo(self):
     if not self._amber_repo:
       if self._fuchsia_out_dir:
@@ -226,39 +240,9 @@
 
     return self._amber_repo
 
-
-  def __ProvisionDevice(self):
-    """Netboots a device with Fuchsia. If |_node_name| is set, then only a
-    device with a matching node name is used.
-
-    The device is up and reachable via SSH when the function is successfully
-    completes."""
-
-    bootserver_path = GetHostToolPathFromPlatform('bootserver')
-    bootserver_command = [
-        bootserver_path,
-        '-1',
-        '--fvm',
-        EnsurePathExists(
-            boot_data.GetTargetFile('storage-sparse.blk',
-                                    self._GetTargetSdkArch(),
-                                    boot_data.TARGET_TYPE_GENERIC)),
-        EnsurePathExists(boot_data.GetBootImage(self._output_dir,
-                                                self._GetTargetSdkArch(),
-                                                boot_data.TARGET_TYPE_GENERIC))]
-
-    if self._node_name:
-      bootserver_command += ['-n', self._node_name]
-
-    bootserver_command += ['--']
-    bootserver_command += boot_data.GetKernelArgs(self._output_dir)
-
-    logging.debug(' '.join(bootserver_command))
-    stdout = subprocess.check_output(bootserver_command,
-                                     stderr=subprocess.STDOUT)
-
+  def _ParseNodename(self, output):
     # Parse the nodename from bootserver stdout.
-    m = re.search(r'.*Proceeding with nodename (?P<nodename>.*)$', stdout,
+    m = re.search(r'.*Proceeding with nodename (?P<nodename>.*)$', output,
                   re.MULTILINE)
     if not m:
       raise Exception('Couldn\'t parse nodename from bootserver output.')
@@ -286,3 +270,10 @@
 
   def _GetSshConfigPath(self):
     return self._ssh_config_path
+
+  def Restart(self):
+    """Restart the device."""
+
+    self.RunCommandPiped('dm reboot')
+    time.sleep(_REBOOT_SLEEP_PERIOD)
+    self.Start()
diff --git a/build/fuchsia/emu_target.py b/build/fuchsia/emu_target.py
index d6136d63..49a8791 100644
--- a/build/fuchsia/emu_target.py
+++ b/build/fuchsia/emu_target.py
@@ -14,17 +14,38 @@
 import target
 import tempfile
 
+
 class EmuTarget(target.Target):
   def __init__(self, output_dir, target_cpu, system_log_file):
     """output_dir: The directory which will contain the files that are
                    generated to support the emulator deployment.
     target_cpu: The emulated target CPU architecture.
                 Can be 'x64' or 'arm64'."""
+
     super(EmuTarget, self).__init__(output_dir, target_cpu)
     self._emu_process = None
     self._system_log_file = system_log_file
     self._amber_repo = None
 
+  @staticmethod
+  def RegisterArgs(arg_parser):
+    target.Target.RegisterArgs(arg_parser)
+    emu_args = arg_parser.add_argument_group('emu', 'Emulator arguments')
+    emu_args.add_argument('--cpu-cores',
+                          type=int,
+                          default=4,
+                          help='Sets the number of CPU cores to provide.')
+    emu_args.add_argument('--ram-size-mb',
+                          type=int,
+                          default=2048,
+                          help='Sets the RAM size (MB) if launching in a VM'),
+    emu_args.add_argument('--allow-no-kvm',
+                          action='store_false',
+                          dest='require_kvm',
+                          default=True,
+                          help='Do not require KVM acceleration for '
+                          'emulators.')
+
   def __enter__(self):
     return self
 
diff --git a/build/fuchsia/generic_x64_target.py b/build/fuchsia/generic_x64_target.py
new file mode 100644
index 0000000..5c56faa
--- /dev/null
+++ b/build/fuchsia/generic_x64_target.py
@@ -0,0 +1,97 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+"""Implements commands for running and interacting with Fuchsia generic
+build on devices."""
+
+import boot_data
+import device_target
+import logging
+import os
+
+from common import SDK_ROOT, EnsurePathExists, \
+                   GetHostToolPathFromPlatform, SubprocessCallWithTimeout
+
+
+def GetTargetType():
+  return GenericX64Target
+
+
+class GenericX64Target(device_target.DeviceTarget):
+  """In addition to the functionality provided by DeviceTarget, this class
+  automatically handles paving of x64 devices that use generic Fuchsia build.
+
+  If there are no running devices, then search for a device running Zedboot
+  and pave it.
+
+  If there's only one running device, or |_node_name| is set, then the
+  device's SDK version is checked unless --os-check=ignore is set.
+  If --os-check=update is set, then the target device is repaved if the SDK
+  version doesn't match."""
+
+  def _ProvisionDeviceIfNecessary(self):
+    should_provision = False
+
+    if self._Discover():
+      self._WaitUntilReady()
+
+      if self._os_check != 'ignore':
+        if self._SDKHashMatches():
+          if self._os_check == 'update':
+            logging.info('SDK hash does not match; rebooting and repaving.')
+            self.RunCommand(['dm', 'reboot'])
+            should_provision = True
+          elif self._os_check == 'check':
+            raise Exception('Target device SDK version does not match.')
+    else:
+      should_provision = True
+
+    if should_provision:
+      self._ProvisionDevice()
+
+  def _ProvisionDevice(self):
+    """Pave a device with a generic image of Fuchsia."""
+    bootserver_path = GetHostToolPathFromPlatform('bootserver')
+    bootserver_command = [
+        bootserver_path, '-1', '--fvm',
+        EnsurePathExists(
+            boot_data.GetTargetFile('storage-sparse.blk',
+                                    self._GetTargetSdkArch(),
+                                    boot_data.TARGET_TYPE_GENERIC)),
+        EnsurePathExists(
+            boot_data.GetBootImage(self._output_dir, self._GetTargetSdkArch(),
+                                   boot_data.TARGET_TYPE_GENERIC))
+    ]
+
+    if self._node_name:
+      bootserver_command += ['-n', self._node_name]
+
+    bootserver_command += ['--']
+    bootserver_command += boot_data.GetKernelArgs(self._output_dir)
+
+    logging.debug(' '.join(bootserver_command))
+    _, stdout = SubprocessCallWithTimeout(bootserver_command,
+                                          silent=False,
+                                          timeout_secs=300)
+
+    self._ParseNodename(stdout)
+
+    # Start loglistener to save system logs.
+    if self._system_log_file:
+      self._StartLoglistener()
+
+    # Repeatdly query mDNS until we find the device, or we hit
+    # BOOT_DISCOVERY_ATTEMPTS
+    logging.info('Waiting for device to join network.')
+    for _ in xrange(device_target.BOOT_DISCOVERY_ATTEMPTS):
+      if self.__Discover():
+        break
+
+    if not self._host:
+      raise Exception("Device %s couldn't be discovered via mDNS." %
+                      self._node_name)
+
+    self._WaitUntilReady()
+
+    # Update the target's hash to match the current tree's.
+    self.PutFile(os.path.join(SDK_ROOT, '.hash'), TARGET_HASH_FILE_PATH)
diff --git a/build/fuchsia/linux.sdk.sha1 b/build/fuchsia/linux.sdk.sha1
index 3af3507..f375043 100644
--- a/build/fuchsia/linux.sdk.sha1
+++ b/build/fuchsia/linux.sdk.sha1
@@ -1 +1 @@
-0.20200826.2.2
+0.20200827.3.2
diff --git a/build/fuchsia/mac.sdk.sha1 b/build/fuchsia/mac.sdk.sha1
index 3af3507..f375043 100644
--- a/build/fuchsia/mac.sdk.sha1
+++ b/build/fuchsia/mac.sdk.sha1
@@ -1 +1 @@
-0.20200826.2.2
+0.20200827.3.2
diff --git a/build/fuchsia/qemu_target.py b/build/fuchsia/qemu_target.py
index ad6f4de..895d2e0 100644
--- a/build/fuchsia/qemu_target.py
+++ b/build/fuchsia/qemu_target.py
@@ -33,19 +33,21 @@
 EXTENDED_BLOBSTORE_SIZE = 1073741824  # 1GB
 
 
+def GetTargetType():
+  return QemuTarget
+
+
 class QemuTarget(emu_target.EmuTarget):
-  def __init__(self, output_dir, target_cpu, system_log_file,
-               emu_type, cpu_cores, require_kvm, ram_size_mb):
+  EMULATOR_NAME = 'qemu'
+
+  def __init__(self, output_dir, target_cpu, system_log_file, cpu_cores,
+               require_kvm, ram_size_mb):
     super(QemuTarget, self).__init__(output_dir, target_cpu,
                                      system_log_file)
-    self._emu_type=emu_type
     self._cpu_cores=cpu_cores
     self._require_kvm=require_kvm
     self._ram_size_mb=ram_size_mb
 
-  def _GetEmulatorName(self):
-    return self._emu_type
-
   def _IsKvmEnabled(self):
     kvm_supported = sys.platform.startswith('linux') and \
                     os.access('/dev/kvm', os.R_OK | os.W_OK)
@@ -60,8 +62,8 @@
           kvm_error = 'File /dev/kvm does not exist. Please install KVM first.'
         else:
           kvm_error = 'To use KVM acceleration, add user to the kvm group '\
-                      'through editing /etc/groups. Log out and back in for '\
-                      'the change to take effect.'
+                      'with "sudo usermod -a -G kvm $USER". Log out and back '\
+                      'in for the change to take effect.'
         raise FuchsiaTargetException(kvm_error)
       else:
         raise FuchsiaTargetException('KVM unavailable when CPU architecture of'\
@@ -131,8 +133,7 @@
         kvm_command.append('host,migratable=no,+invtsc')
     else:
       logging.warning('Unable to launch %s with KVM acceleration.'
-                       % (self._emu_type) +
-                      'The guest VM will be slow.')
+                      'The guest VM will be slow.' % (self.EMULATOR_NAME))
       if self._target_cpu == 'arm64':
         kvm_command = ['-cpu', 'cortex-a53']
       else:
@@ -158,8 +159,10 @@
 
   def _BuildCommand(self):
     qemu_exec = 'qemu-system-'+self._GetTargetSdkLegacyArch()
-    qemu_command = [os.path.join(GetEmuRootForPlatform(self._emu_type), 'bin',
-                                 qemu_exec)]
+    qemu_command = [
+        os.path.join(GetEmuRootForPlatform(self.EMULATOR_NAME), 'bin',
+                     qemu_exec)
+    ]
     qemu_command.extend(self._BuildQemuConfig())
     qemu_command.append('-nographic')
     return qemu_command
diff --git a/build/fuchsia/remote_cmd.py b/build/fuchsia/remote_cmd.py
index 019c2dc9..4c950a0 100644
--- a/build/fuchsia/remote_cmd.py
+++ b/build/fuchsia/remote_cmd.py
@@ -7,6 +7,8 @@
 import subprocess
 import threading
 
+from common import SubprocessCallWithTimeout
+
 _SSH = ['ssh']
 _SCP = ['scp', '-C']  # Use gzip compression.
 _SSH_LOGGER = logging.getLogger('ssh')
@@ -54,29 +56,8 @@
 
     ssh_command = self._GetSshCommandLinePrefix() + command
     _SSH_LOGGER.debug('ssh exec: ' + ' '.join(ssh_command))
-    if silent:
-      devnull = open(os.devnull, 'w')
-      process = subprocess.Popen(ssh_command, stdout=devnull, stderr=devnull)
-    else:
-      process = subprocess.Popen(ssh_command, stdout=subprocess.PIPE,
-                                 stderr=subprocess.STDOUT)
-
-    timeout_timer = None
-    if timeout_secs:
-      timeout_timer = threading.Timer(timeout_secs, process.kill)
-      timeout_timer.start()
-
-    if not silent:
-      for line in process.stdout:
-        print(line)
-
-    process.wait()
-    if timeout_timer:
-      timeout_timer.cancel()
-    if process.returncode == -9:
-      raise Exception('Timeout when executing \"%s\".' % ' '.join(command))
-
-    return process.returncode
+    retval, _, _ = SubprocessCallWithTimeout(ssh_command, silent, timeout_secs)
+    return retval
 
 
   def RunCommandPiped(self, command, stdout, stderr, ssh_args = None, **kwargs):
diff --git a/build/fuchsia/target.py b/build/fuchsia/target.py
index 254c5fd..c030206 100644
--- a/build/fuchsia/target.py
+++ b/build/fuchsia/target.py
@@ -2,17 +2,13 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import boot_data
 import common
 import json
 import logging
 import os
 import remote_cmd
 import runner_logs
-import shutil
 import subprocess
-import sys
-import tempfile
 import time
 
 
@@ -71,6 +67,20 @@
     self._target_cpu = target_cpu
     self._command_runner = None
 
+  @staticmethod
+  def RegisterArgs(arg_parser):
+    common_args = arg_parser.add_argument_group(
+        'target', 'Arguments that apply to all targets.')
+    common_args.add_argument(
+        '--output-dir',
+        type=os.path.realpath,
+        default=os.getcwd(),
+        help=('Path to the directory in which build files are located. '
+              'Defaults to current directory.'))
+    common_args.add_argument('--system-log-file',
+                             help='File to write system logs to. Specify '
+                             '- to log to stdout.')
+
   # Functions used by the Python context manager for teardown.
   def __enter__(self):
     return self
diff --git a/build/fuchsia/test_runner.py b/build/fuchsia/test_runner.py
index 5033a245..eac7f6f 100755
--- a/build/fuchsia/test_runner.py
+++ b/build/fuchsia/test_runner.py
@@ -7,15 +7,9 @@
 """Deploys and runs a test package on a Fuchsia target."""
 
 import argparse
-import json
-import logging
 import os
 import runner_logs
-import socket
-import subprocess
 import sys
-import tempfile
-import time
 
 from common_args import AddCommonArgs, ConfigureLogging, GetDeploymentTargetForArgs
 from net_test_server import SetupTestServer
@@ -63,7 +57,7 @@
                       type=int,
                       help='Sets the limit of test batch to run in a single '
                       'process.')
-  # --test-launcher-filter-file is specified relative to --output-directory,
+  # --test-launcher-filter-file is specified relative to --output-dir,
   # so specifying type=os.path.* will break it.
   parser.add_argument('--test-launcher-filter-file',
                       default=None,
@@ -87,9 +81,9 @@
                       help='Arguments for the test process.')
   args = parser.parse_args()
 
-  # Flag output_directory is required for tests launched with this script.
-  if not args.output_directory:
-    raise ValueError("output-directory must be specified.")
+  # Flag output_dir is required for tests launched with this script.
+  if not args.output_dir:
+    raise ValueError("output-dir must be specified.")
 
   ConfigureLogging(args)
 
@@ -120,7 +114,7 @@
     if args.device == 'device':
       test_concurrency = DEFAULT_TEST_SERVER_CONCURRENCY
     else:
-      test_concurrency = args.qemu_cpu_cores
+      test_concurrency = args.cpu_cores
   if test_concurrency:
     child_args.append('--test-launcher-jobs=%d' % test_concurrency)
 
@@ -143,7 +137,7 @@
     child_args.extend(args.child_args)
 
   try:
-    with GetDeploymentTargetForArgs(args) as target, \
+    with GetDeploymentTargetForArgs() as target, \
          SystemLogReader() as system_logger, \
          RunnerLogManager(args.runner_logs_dir, BuildIdsPaths(args.package)):
       target.Start()
@@ -163,9 +157,8 @@
                                       args.package_name)
 
       run_package_args = RunPackageArgs.FromCommonArgs(args)
-      returncode = RunPackage(
-          args.output_directory, target, args.package, args.package_name,
-          child_args, run_package_args)
+      returncode = RunPackage(args.output_dir, target, args.package,
+                              args.package_name, child_args, run_package_args)
 
       if test_server:
         test_server.Stop()
diff --git a/build/linux/sysroot_scripts/libwayland-client-symbols b/build/linux/sysroot_scripts/libwayland-client-symbols
index d6bffeb..6e8a01f 100644
--- a/build/linux/sysroot_scripts/libwayland-client-symbols
+++ b/build/linux/sysroot_scripts/libwayland-client-symbols
@@ -1,74 +1,75 @@
 libwayland-client.so.0 libwayland-client0 #MINVER#
- wl_array_add@Base 1.4.0
- wl_array_copy@Base 1.4.0
- wl_array_init@Base 1.4.0
- wl_array_release@Base 1.4.0
- wl_buffer_interface@Base 1.4.0
- wl_callback_interface@Base 1.4.0
- wl_compositor_interface@Base 1.4.0
- wl_data_device_interface@Base 1.4.0
- wl_data_device_manager_interface@Base 1.4.0
- wl_data_offer_interface@Base 1.4.0
- wl_data_source_interface@Base 1.4.0
- wl_display_cancel_read@Base 1.4.0
- wl_display_connect@Base 1.4.0
- wl_display_connect_to_fd@Base 1.4.0
- wl_display_create_queue@Base 1.4.0
- wl_display_disconnect@Base 1.4.0
- wl_display_dispatch@Base 1.4.0
- wl_display_dispatch_pending@Base 1.4.0
- wl_display_dispatch_queue@Base 1.4.0
- wl_display_dispatch_queue_pending@Base 1.4.0
- wl_display_flush@Base 1.4.0
- wl_display_get_error@Base 1.4.0
- wl_display_get_fd@Base 1.4.0
- wl_display_get_protocol_error@Base 1.4.0
- wl_display_interface@Base 1.4.0
- wl_display_prepare_read@Base 1.4.0
- wl_display_prepare_read_queue@Base 1.4.0
- wl_display_read_events@Base 1.4.0
- wl_display_roundtrip@Base 1.4.0
- wl_display_roundtrip_queue@Base 1.4.0
- wl_event_queue_destroy@Base 1.4.0
- wl_keyboard_interface@Base 1.4.0
- wl_list_empty@Base 1.4.0
- wl_list_init@Base 1.4.0
- wl_list_insert@Base 1.4.0
- wl_list_insert_list@Base 1.4.0
- wl_list_length@Base 1.4.0
- wl_list_remove@Base 1.4.0
- wl_log_set_handler_client@Base 1.4.0
- wl_output_interface@Base 1.4.0
- wl_pointer_interface@Base 1.4.0
- wl_proxy_add_dispatcher@Base 1.4.0
- wl_proxy_add_listener@Base 1.4.0
- wl_proxy_create@Base 1.4.0
- wl_proxy_create_wrapper@Base 1.4.0
- wl_proxy_destroy@Base 1.4.0
- wl_proxy_get_class@Base 1.4.0
- wl_proxy_get_id@Base 1.4.0
- wl_proxy_get_listener@Base 1.4.0
- wl_proxy_get_tag@Base 1.4.0
- wl_proxy_get_user_data@Base 1.4.0
- wl_proxy_get_version@Base 1.4.0
- wl_proxy_marshal@Base 1.4.0
- wl_proxy_marshal_array@Base 1.4.0
- wl_proxy_marshal_array_constructor@Base 1.4.0
- wl_proxy_marshal_array_constructor_versioned@Base 1.4.0
- wl_proxy_marshal_constructor@Base 1.4.0
- wl_proxy_marshal_constructor_versioned@Base 1.4.0
- wl_proxy_set_queue@Base 1.4.0
- wl_proxy_set_tag@Base 1.4.0
- wl_proxy_set_user_data@Base 1.4.0
- wl_proxy_wrapper_destroy@Base 1.4.0
- wl_region_interface@Base 1.4.0
- wl_registry_interface@Base 1.4.0
- wl_seat_interface@Base 1.4.0
- wl_shell_interface@Base 1.4.0
- wl_shell_surface_interface@Base 1.4.0
- wl_shm_interface@Base 1.4.0
- wl_shm_pool_interface@Base 1.4.0
- wl_subcompositor_interface@Base 1.4.0
- wl_subsurface_interface@Base 1.4.0
- wl_surface_interface@Base 1.4.0
- wl_touch_interface@Base 1.4.0
+* Build-Depends-Package: libwayland-dev
+ wl_array_add@Base 1.0.2
+ wl_array_copy@Base 1.0.2
+ wl_array_init@Base 1.0.2
+ wl_array_release@Base 1.0.2
+ wl_buffer_interface@Base 1.0.2
+ wl_callback_interface@Base 1.0.2
+ wl_compositor_interface@Base 1.0.2
+ wl_data_device_interface@Base 1.0.2
+ wl_data_device_manager_interface@Base 1.0.2
+ wl_data_offer_interface@Base 1.0.2
+ wl_data_source_interface@Base 1.0.2
+ wl_display_cancel_read@Base 1.2.0
+ wl_display_connect@Base 1.0.2
+ wl_display_connect_to_fd@Base 1.0.2
+ wl_display_create_queue@Base 1.0.2
+ wl_display_disconnect@Base 1.0.2
+ wl_display_dispatch@Base 1.0.2
+ wl_display_dispatch_pending@Base 1.0.2
+ wl_display_dispatch_queue@Base 1.0.2
+ wl_display_dispatch_queue_pending@Base 1.0.2
+ wl_display_flush@Base 1.0.2
+ wl_display_get_error@Base 1.0.2
+ wl_display_get_fd@Base 1.0.2
+ wl_display_get_protocol_error@Base 1.5.91
+ wl_display_interface@Base 1.0.2
+ wl_display_prepare_read@Base 1.2.0
+ wl_display_prepare_read_queue@Base 1.2.0
+ wl_display_read_events@Base 1.2.0
+ wl_display_roundtrip@Base 1.0.2
+ wl_display_roundtrip_queue@Base 1.5.91
+ wl_event_queue_destroy@Base 1.0.2
+ wl_keyboard_interface@Base 1.0.2
+ wl_list_empty@Base 1.0.2
+ wl_list_init@Base 1.0.2
+ wl_list_insert@Base 1.0.2
+ wl_list_insert_list@Base 1.0.2
+ wl_list_length@Base 1.0.2
+ wl_list_remove@Base 1.0.2
+ wl_log_set_handler_client@Base 1.0.2
+ wl_output_interface@Base 1.0.2
+ wl_pointer_interface@Base 1.0.2
+ wl_proxy_add_dispatcher@Base 1.3.0
+ wl_proxy_add_listener@Base 1.0.2
+ wl_proxy_create@Base 1.0.2
+ wl_proxy_create_wrapper@Base 1.11.0
+ wl_proxy_destroy@Base 1.0.2
+ wl_proxy_get_class@Base 1.1.0
+ wl_proxy_get_id@Base 1.0.2
+ wl_proxy_get_listener@Base 1.3.0
+ wl_proxy_get_tag@Base 1.17.93
+ wl_proxy_get_user_data@Base 1.0.2
+ wl_proxy_get_version@Base 1.9.91
+ wl_proxy_marshal@Base 1.0.2
+ wl_proxy_marshal_array@Base 1.3.0
+ wl_proxy_marshal_array_constructor@Base 1.3.92
+ wl_proxy_marshal_array_constructor_versioned@Base 1.9.91
+ wl_proxy_marshal_constructor@Base 1.3.92
+ wl_proxy_marshal_constructor_versioned@Base 1.9.91
+ wl_proxy_set_queue@Base 1.0.2
+ wl_proxy_set_tag@Base 1.17.93
+ wl_proxy_set_user_data@Base 1.0.2
+ wl_proxy_wrapper_destroy@Base 1.11.0
+ wl_region_interface@Base 1.0.2
+ wl_registry_interface@Base 1.0.2
+ wl_seat_interface@Base 1.0.2
+ wl_shell_interface@Base 1.0.2
+ wl_shell_surface_interface@Base 1.0.2
+ wl_shm_interface@Base 1.0.2
+ wl_shm_pool_interface@Base 1.0.2
+ wl_subcompositor_interface@Base 1.3.92
+ wl_subsurface_interface@Base 1.3.92
+ wl_surface_interface@Base 1.0.2
+ wl_touch_interface@Base 1.0.2
diff --git a/build/linux/sysroot_scripts/sysroots.json b/build/linux/sysroot_scripts/sysroots.json
index aaa3eda..e7a9b326 100644
--- a/build/linux/sysroot_scripts/sysroots.json
+++ b/build/linux/sysroot_scripts/sysroots.json
@@ -1,36 +1,36 @@
 {
     "sid_amd64": {
-        "Sha1Sum": "cc396d2ee91286924b377c2124b1efbcad7df8f2",
+        "Sha1Sum": "5f64b417e1018dcf8fcc81dc2714e0f264b9b911",
         "SysrootDir": "debian_sid_amd64-sysroot",
         "Tarball": "debian_sid_amd64_sysroot.tar.xz"
     },
     "sid_arm": {
-        "Sha1Sum": "019d07229d7cc18fd808e210a25a307e9c52c81b",
+        "Sha1Sum": "c2e54f675b83a61301dcdb22e8e7a2b85c01d58c",
         "SysrootDir": "debian_sid_arm-sysroot",
         "Tarball": "debian_sid_arm_sysroot.tar.xz"
     },
     "sid_arm64": {
-        "Sha1Sum": "dc0edfa18ab4467282be4ca4b3b7df7d1a4ff81c",
+        "Sha1Sum": "ef67b6ca8fd6e2e51515a243d043d1ea4caf45e6",
         "SysrootDir": "debian_sid_arm64-sysroot",
         "Tarball": "debian_sid_arm64_sysroot.tar.xz"
     },
     "sid_armel": {
-        "Sha1Sum": "c76dc40268302fc585ecfb8677aebd6eb4473d41",
+        "Sha1Sum": "4ccbe0db925a5ab39ba52cbe601535f97be05372",
         "SysrootDir": "debian_sid_armel-sysroot",
         "Tarball": "debian_sid_armel_sysroot.tar.xz"
     },
     "sid_i386": {
-        "Sha1Sum": "328009b4502a9e45df905115d79582ed8cd60a0e",
+        "Sha1Sum": "d967bcef40477dbc39acef141ff22bf73f3e7cdb",
         "SysrootDir": "debian_sid_i386-sysroot",
         "Tarball": "debian_sid_i386_sysroot.tar.xz"
     },
     "sid_mips": {
-        "Sha1Sum": "d5db6c30ec4f0cd12033eeacad04f42f505ff48f",
+        "Sha1Sum": "2758d766faa0c73da18128a4d46580ade68d3eea",
         "SysrootDir": "debian_sid_mips-sysroot",
         "Tarball": "debian_sid_mips_sysroot.tar.xz"
     },
     "sid_mips64el": {
-        "Sha1Sum": "abe00d71ee077ba492a82229d87cd10d6b6a9026",
+        "Sha1Sum": "0edb979c932d207076c3986403a198387a4a8aa9",
         "SysrootDir": "debian_sid_mips64el-sysroot",
         "Tarball": "debian_sid_mips64el_sysroot.tar.xz"
     }
diff --git a/cc/input/scrollbar.h b/cc/input/scrollbar.h
index 4fc7a3e..cbfdd310 100644
--- a/cc/input/scrollbar.h
+++ b/cc/input/scrollbar.h
@@ -27,9 +27,9 @@
 
 namespace cc {
 
-enum ScrollbarOrientation { HORIZONTAL, VERTICAL };
+enum class ScrollbarOrientation { HORIZONTAL, VERTICAL };
 
-enum ScrollbarPart {
+enum class ScrollbarPart {
   THUMB,
   TRACK_BUTTONS_TICKMARKS,  // for PartNeedsRepaint() and PaintPart() only.
   BACK_BUTTON,
diff --git a/cc/input/scrollbar_animation_controller.cc b/cc/input/scrollbar_animation_controller.cc
index 39d3ec0..89861d6 100644
--- a/cc/input/scrollbar_animation_controller.cc
+++ b/cc/input/scrollbar_animation_controller.cc
@@ -50,7 +50,7 @@
       fade_duration_(fade_duration),
       need_trigger_scrollbar_fade_in_(false),
       is_animating_(false),
-      animation_change_(NONE),
+      animation_change_(AnimationChange::NONE),
       scroll_element_id_(scroll_element_id),
       opacity_(initial_opacity),
       show_scrollbars_on_scroll_gesture_(false),
@@ -70,7 +70,7 @@
       fade_duration_(fade_duration),
       need_trigger_scrollbar_fade_in_(false),
       is_animating_(false),
-      animation_change_(NONE),
+      animation_change_(AnimationChange::NONE),
       scroll_element_id_(scroll_element_id),
       opacity_(initial_opacity),
       show_scrollbars_on_scroll_gesture_(true),
@@ -102,7 +102,7 @@
 }
 
 void ScrollbarAnimationController::StartAnimation() {
-  DCHECK(animation_change_ != NONE);
+  DCHECK(animation_change_ != AnimationChange::NONE);
   delayed_scrollbar_animation_.Cancel();
   need_trigger_scrollbar_fade_in_ = false;
   is_animating_ = true;
@@ -114,7 +114,7 @@
   delayed_scrollbar_animation_.Cancel();
   need_trigger_scrollbar_fade_in_ = false;
   is_animating_ = false;
-  animation_change_ = NONE;
+  animation_change_ = AnimationChange::NONE;
 }
 
 void ScrollbarAnimationController::PostDelayedAnimation(
@@ -137,7 +137,7 @@
   }
 
   if (is_animating_) {
-    DCHECK(animation_change_ != NONE);
+    DCHECK(animation_change_ != AnimationChange::NONE);
     if (last_awaken_time_.is_null())
       last_awaken_time_ = now;
 
@@ -166,8 +166,8 @@
 void ScrollbarAnimationController::RunAnimationFrame(float progress) {
   float opacity;
 
-  DCHECK(animation_change_ != NONE);
-  if (animation_change_ == FADE_IN) {
+  DCHECK(animation_change_ != AnimationChange::NONE);
+  if (animation_change_ == AnimationChange::FADE_IN) {
     opacity = std::max(progress, opacity_);
   } else {
     opacity = std::min(1.f - progress, opacity_);
@@ -194,9 +194,9 @@
   // Overlay) and mouse is near or tickmarks show.
   if (need_thinning_animation_) {
     if (!MouseIsNearAnyScrollbar() && !tickmarks_showing_)
-      PostDelayedAnimation(FADE_OUT);
+      PostDelayedAnimation(AnimationChange::FADE_OUT);
   } else {
-    PostDelayedAnimation(FADE_OUT);
+    PostDelayedAnimation(AnimationChange::FADE_OUT);
   }
 
   if (need_thinning_animation_) {
@@ -251,7 +251,7 @@
 
   if (!Captured()) {
     if (MouseIsNearAnyScrollbar() && ScrollbarsHidden()) {
-      PostDelayedAnimation(FADE_IN);
+      PostDelayedAnimation(AnimationChange::FADE_IN);
       need_trigger_scrollbar_fade_in_ = true;
     }
     return;
@@ -261,7 +261,7 @@
   horizontal_controller_->DidMouseUp();
 
   if (!MouseIsNearAnyScrollbar() && !ScrollbarsHidden() && !tickmarks_showing_)
-    PostDelayedAnimation(FADE_OUT);
+    PostDelayedAnimation(AnimationChange::FADE_OUT);
 }
 
 void ScrollbarAnimationController::DidMouseLeave() {
@@ -277,7 +277,7 @@
   if (ScrollbarsHidden() || Captured() || tickmarks_showing_)
     return;
 
-  PostDelayedAnimation(FADE_OUT);
+  PostDelayedAnimation(AnimationChange::FADE_OUT);
 }
 
 void ScrollbarAnimationController::DidMouseMove(
@@ -304,7 +304,7 @@
     if (need_trigger_scrollbar_fade_in_before !=
         need_trigger_scrollbar_fade_in_) {
       if (need_trigger_scrollbar_fade_in_) {
-        PostDelayedAnimation(FADE_IN);
+        PostDelayedAnimation(AnimationChange::FADE_IN);
       } else {
         delayed_scrollbar_animation_.Cancel();
       }
@@ -314,7 +314,7 @@
       Show();
       StopAnimation();
     } else if (!is_animating_) {
-      PostDelayedAnimation(FADE_OUT);
+      PostDelayedAnimation(AnimationChange::FADE_OUT);
     }
   }
 }
@@ -352,8 +352,10 @@
 
 bool ScrollbarAnimationController::Captured() const {
   DCHECK(need_thinning_animation_);
-  return GetScrollbarAnimationController(VERTICAL).captured() ||
-         GetScrollbarAnimationController(HORIZONTAL).captured();
+  return GetScrollbarAnimationController(ScrollbarOrientation::VERTICAL)
+             .captured() ||
+         GetScrollbarAnimationController(ScrollbarOrientation::HORIZONTAL)
+             .captured();
 }
 
 void ScrollbarAnimationController::Show() {
diff --git a/cc/input/scrollbar_animation_controller.h b/cc/input/scrollbar_animation_controller.h
index 1f2c22a..f47fe8f 100644
--- a/cc/input/scrollbar_animation_controller.h
+++ b/cc/input/scrollbar_animation_controller.h
@@ -95,7 +95,7 @@
 
  private:
   // Describes whether the current animation should FadeIn or FadeOut.
-  enum AnimationChange { NONE, FADE_IN, FADE_OUT };
+  enum class AnimationChange { NONE, FADE_IN, FADE_OUT };
 
   ScrollbarAnimationController(ElementId scroll_element_id,
                                ScrollbarAnimationControllerClient* client,
diff --git a/cc/input/scrollbar_animation_controller_unittest.cc b/cc/input/scrollbar_animation_controller_unittest.cc
index 985f45b..53c2252 100644
--- a/cc/input/scrollbar_animation_controller_unittest.cc
+++ b/cc/input/scrollbar_animation_controller_unittest.cc
@@ -79,9 +79,11 @@
 
     scroll_layer_ = AddLayer<LayerImpl>();
     h_scrollbar_layer_ = AddLayer<SolidColorScrollbarLayerImpl>(
-        HORIZONTAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar);
+        ScrollbarOrientation::HORIZONTAL, kThumbThickness, kTrackStart,
+        kIsLeftSideVerticalScrollbar);
     v_scrollbar_layer_ = AddLayer<SolidColorScrollbarLayerImpl>(
-        VERTICAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar);
+        ScrollbarOrientation::VERTICAL, kThumbThickness, kTrackStart,
+        kIsLeftSideVerticalScrollbar);
     SetElementIdsForTesting();
 
     clip_layer_ = root_layer();
@@ -1341,7 +1343,9 @@
             base::TimeDelta::FromSeconds(3), 0.0f);
   }
 
-  virtual ScrollbarOrientation orientation() const { return HORIZONTAL; }
+  virtual ScrollbarOrientation orientation() const {
+    return ScrollbarOrientation::HORIZONTAL;
+  }
 
   std::unique_ptr<ScrollbarAnimationController> scrollbar_controller_;
   LayerImpl* scroll_layer_;
@@ -1356,7 +1360,9 @@
 class VerticalScrollbarAnimationControllerAndroidTest
     : public ScrollbarAnimationControllerAndroidTest {
  protected:
-  ScrollbarOrientation orientation() const override { return VERTICAL; }
+  ScrollbarOrientation orientation() const override {
+    return ScrollbarOrientation::VERTICAL;
+  }
 };
 
 TEST_F(ScrollbarAnimationControllerAndroidTest, HiddenInBegin) {
@@ -1403,7 +1409,7 @@
 TEST_F(ScrollbarAnimationControllerAndroidTest, HideOnResize) {
   EXPECT_EQ(gfx::Size(200, 200), scroll_layer_->bounds());
 
-  EXPECT_EQ(HORIZONTAL, scrollbar_layer_->orientation());
+  EXPECT_EQ(ScrollbarOrientation::HORIZONTAL, scrollbar_layer_->orientation());
 
   // Shrink along X axis, horizontal scrollbar should appear.
   GetScrollNode(scroll_layer_)->container_bounds = gfx::Size(100, 200);
@@ -1426,7 +1432,7 @@
 TEST_F(VerticalScrollbarAnimationControllerAndroidTest, HideOnResize) {
   EXPECT_EQ(gfx::Size(200, 200), scroll_layer_->bounds());
 
-  EXPECT_EQ(VERTICAL, scrollbar_layer_->orientation());
+  EXPECT_EQ(ScrollbarOrientation::VERTICAL, scrollbar_layer_->orientation());
 
   // Shrink along X axis, vertical scrollbar should remain invisible.
   GetScrollNode(scroll_layer_)->container_bounds = gfx::Size(100, 200);
@@ -1446,7 +1452,7 @@
 }
 
 TEST_F(ScrollbarAnimationControllerAndroidTest, HideOnUserNonScrollableHorz) {
-  EXPECT_EQ(HORIZONTAL, scrollbar_layer_->orientation());
+  EXPECT_EQ(ScrollbarOrientation::HORIZONTAL, scrollbar_layer_->orientation());
 
   GetScrollNode(scroll_layer_)->user_scrollable_horizontal = false;
   UpdateActiveTreeDrawProperties();
@@ -1456,7 +1462,7 @@
 }
 
 TEST_F(ScrollbarAnimationControllerAndroidTest, ShowOnUserNonScrollableVert) {
-  EXPECT_EQ(HORIZONTAL, scrollbar_layer_->orientation());
+  EXPECT_EQ(ScrollbarOrientation::HORIZONTAL, scrollbar_layer_->orientation());
 
   GetScrollNode(scroll_layer_)->user_scrollable_vertical = false;
   UpdateActiveTreeDrawProperties();
@@ -1467,7 +1473,7 @@
 
 TEST_F(VerticalScrollbarAnimationControllerAndroidTest,
        HideOnUserNonScrollableVert) {
-  EXPECT_EQ(VERTICAL, scrollbar_layer_->orientation());
+  EXPECT_EQ(ScrollbarOrientation::VERTICAL, scrollbar_layer_->orientation());
 
   GetScrollNode(scroll_layer_)->user_scrollable_vertical = false;
   UpdateActiveTreeDrawProperties();
@@ -1478,7 +1484,7 @@
 
 TEST_F(VerticalScrollbarAnimationControllerAndroidTest,
        ShowOnUserNonScrollableHorz) {
-  EXPECT_EQ(VERTICAL, scrollbar_layer_->orientation());
+  EXPECT_EQ(ScrollbarOrientation::VERTICAL, scrollbar_layer_->orientation());
 
   GetScrollNode(scroll_layer_)->user_scrollable_horizontal = false;
   UpdateActiveTreeDrawProperties();
diff --git a/cc/input/scrollbar_controller.h b/cc/input/scrollbar_controller.h
index 9551131..6a9ccb4 100644
--- a/cc/input/scrollbar_controller.h
+++ b/cc/input/scrollbar_controller.h
@@ -152,7 +152,7 @@
   // "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.
-  enum AutoScrollDirection { AUTOSCROLL_FORWARD, AUTOSCROLL_BACKWARD };
+  enum class AutoScrollDirection { AUTOSCROLL_FORWARD, AUTOSCROLL_BACKWARD };
 
   struct CC_EXPORT AutoScrollState {
     // Can only be either AUTOSCROLL_FORWARD or AUTOSCROLL_BACKWARD.
diff --git a/cc/input/single_scrollbar_animation_controller_thinning.cc b/cc/input/single_scrollbar_animation_controller_thinning.cc
index 9cc85ab..75a4586 100644
--- a/cc/input/single_scrollbar_animation_controller_thinning.cc
+++ b/cc/input/single_scrollbar_animation_controller_thinning.cc
@@ -61,7 +61,7 @@
       mouse_is_over_scrollbar_thumb_(false),
       mouse_is_near_scrollbar_thumb_(false),
       mouse_is_near_scrollbar_track_(false),
-      thickness_change_(NONE),
+      thickness_change_(AnimationChange::NONE),
       thinning_duration_(thinning_duration) {
   ApplyThumbThicknessScale(kIdleThicknessScale);
 }
@@ -112,7 +112,7 @@
   client_->SetNeedsRedrawForScrollbarAnimation();
   if (progress == 1.f) {
     StopAnimation();
-    thickness_change_ = NONE;
+    thickness_change_ = AnimationChange::NONE;
   }
 }
 
@@ -143,10 +143,10 @@
   StopAnimation();
 
   if (!mouse_is_near_scrollbar_thumb_) {
-    thickness_change_ = DECREASE;
+    thickness_change_ = AnimationChange::DECREASE;
     StartAnimation();
   } else {
-    thickness_change_ = NONE;
+    thickness_change_ = AnimationChange::NONE;
   }
 }
 
@@ -161,7 +161,7 @@
   if (captured_)
     return;
 
-  thickness_change_ = DECREASE;
+  thickness_change_ = AnimationChange::DECREASE;
   StartAnimation();
 }
 
@@ -188,7 +188,9 @@
 
   if (!captured_ &&
       mouse_is_near_scrollbar_thumb != mouse_is_near_scrollbar_thumb_) {
-    thickness_change_ = mouse_is_near_scrollbar_thumb ? INCREASE : DECREASE;
+    thickness_change_ = mouse_is_near_scrollbar_thumb
+                            ? AnimationChange::INCREASE
+                            : AnimationChange::DECREASE;
     StartAnimation();
   }
   mouse_is_near_scrollbar_thumb_ = mouse_is_near_scrollbar_thumb;
@@ -197,9 +199,11 @@
 
 float SingleScrollbarAnimationControllerThinning::ThumbThicknessScaleAt(
     float progress) {
-  if (thickness_change_ == NONE)
+  if (thickness_change_ == AnimationChange::NONE)
     return mouse_is_near_scrollbar_thumb_ ? 1.f : kIdleThicknessScale;
-  float factor = thickness_change_ == INCREASE ? progress : (1.f - progress);
+  float factor = thickness_change_ == AnimationChange::INCREASE
+                     ? progress
+                     : (1.f - progress);
   return ((1.f - kIdleThicknessScale) * factor) + kIdleThicknessScale;
 }
 
@@ -210,9 +214,11 @@
     float min_value,
     float max_value) {
   float result;
-  if (animation_change == INCREASE && current_value > new_value)
+  if (animation_change == AnimationChange::INCREASE &&
+      current_value > new_value)
     result = current_value;
-  else if (animation_change == DECREASE && current_value < new_value)
+  else if (animation_change == AnimationChange::DECREASE &&
+           current_value < new_value)
     result = current_value;
   else
     result = new_value;
diff --git a/cc/input/single_scrollbar_animation_controller_thinning.h b/cc/input/single_scrollbar_animation_controller_thinning.h
index 96fe980..6f9d03d 100644
--- a/cc/input/single_scrollbar_animation_controller_thinning.h
+++ b/cc/input/single_scrollbar_animation_controller_thinning.h
@@ -73,7 +73,7 @@
 
   // Describes whether the current animation should INCREASE (thicken)
   // a bar or DECREASE it (thin).
-  enum AnimationChange { NONE, INCREASE, DECREASE };
+  enum class AnimationChange { NONE, INCREASE, DECREASE };
   float ThumbThicknessScaleAt(float progress);
 
   float AdjustScale(float new_value,
diff --git a/cc/input/single_scrollbar_animation_controller_thinning_unittest.cc b/cc/input/single_scrollbar_animation_controller_thinning_unittest.cc
index a14ae213..2bf4672 100644
--- a/cc/input/single_scrollbar_animation_controller_thinning_unittest.cc
+++ b/cc/input/single_scrollbar_animation_controller_thinning_unittest.cc
@@ -71,7 +71,8 @@
     const bool kIsLeftSideVerticalScrollbar = false;
 
     scrollbar_layer_ = AddLayer<SolidColorScrollbarLayerImpl>(
-        HORIZONTAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar);
+        ScrollbarOrientation::HORIZONTAL, kThumbThickness, kTrackStart,
+        kIsLeftSideVerticalScrollbar);
 
     scrollbar_layer_->SetBounds(gfx::Size(kThumbThickness, kTrackLength));
     scrollbar_layer_->SetScrollElementId(scroll_layer->element_id());
@@ -86,7 +87,8 @@
     UpdateActiveTreeDrawProperties();
 
     scrollbar_controller_ = SingleScrollbarAnimationControllerThinning::Create(
-        scroll_layer->element_id(), HORIZONTAL, &client_, kThinningDuration);
+        scroll_layer->element_id(), ScrollbarOrientation::HORIZONTAL, &client_,
+        kThinningDuration);
   }
 
   std::unique_ptr<SingleScrollbarAnimationControllerThinning>
diff --git a/cc/layers/painted_overlay_scrollbar_layer.cc b/cc/layers/painted_overlay_scrollbar_layer.cc
index 2014160a6..a6cfc88 100644
--- a/cc/layers/painted_overlay_scrollbar_layer.cc
+++ b/cc/layers/painted_overlay_scrollbar_layer.cc
@@ -66,7 +66,7 @@
   PaintedOverlayScrollbarLayerImpl* scrollbar_layer =
       static_cast<PaintedOverlayScrollbarLayerImpl*>(layer);
 
-  if (orientation() == HORIZONTAL) {
+  if (orientation() == ScrollbarOrientation::HORIZONTAL) {
     scrollbar_layer->SetThumbThickness(thumb_size_.height());
     scrollbar_layer->SetThumbLength(thumb_size_.width());
     scrollbar_layer->SetTrackStart(track_rect_.x());
@@ -130,7 +130,7 @@
 }
 
 bool PaintedOverlayScrollbarLayer::PaintThumbIfNeeded() {
-  if (!scrollbar_->NeedsRepaintPart(THUMB) && thumb_resource_)
+  if (!scrollbar_->NeedsRepaintPart(ScrollbarPart::THUMB) && thumb_resource_)
     return false;
 
   gfx::Size paint_size = scrollbar_->NinePatchThumbCanvasSize();
@@ -142,7 +142,7 @@
   SkiaPaintCanvas canvas(skbitmap);
   canvas.clear(SK_ColorTRANSPARENT);
 
-  scrollbar_->PaintPart(&canvas, THUMB, gfx::Rect(paint_size));
+  scrollbar_->PaintPart(&canvas, ScrollbarPart::THUMB, gfx::Rect(paint_size));
   // Make sure that the pixels are no longer mutable to unavoid unnecessary
   // allocation and copying.
   skbitmap.setImmutable();
@@ -175,7 +175,7 @@
   SkiaPaintCanvas canvas(skbitmap);
   canvas.clear(SK_ColorTRANSPARENT);
 
-  scrollbar_->PaintPart(&canvas, TRACK_BUTTONS_TICKMARKS,
+  scrollbar_->PaintPart(&canvas, ScrollbarPart::TRACK_BUTTONS_TICKMARKS,
                         gfx::Rect(paint_size));
   // Make sure that the pixels are no longer mutable to unavoid unnecessary
   // allocation and copying.
diff --git a/cc/layers/painted_overlay_scrollbar_layer_impl.cc b/cc/layers/painted_overlay_scrollbar_layer_impl.cc
index ba485239..2706da80 100644
--- a/cc/layers/painted_overlay_scrollbar_layer_impl.cc
+++ b/cc/layers/painted_overlay_scrollbar_layer_impl.cc
@@ -222,7 +222,9 @@
 }
 
 float PaintedOverlayScrollbarLayerImpl::TrackLength() const {
-  return track_length_ + (orientation() == VERTICAL ? vertical_adjust() : 0);
+  return track_length_ + (orientation() == ScrollbarOrientation::VERTICAL
+                              ? vertical_adjust()
+                              : 0);
 }
 
 bool PaintedOverlayScrollbarLayerImpl::IsThumbResizable() const {
diff --git a/cc/layers/painted_overlay_scrollbar_layer_unittest.cc b/cc/layers/painted_overlay_scrollbar_layer_unittest.cc
index d7ce388..92a0d76 100644
--- a/cc/layers/painted_overlay_scrollbar_layer_unittest.cc
+++ b/cc/layers/painted_overlay_scrollbar_layer_unittest.cc
@@ -24,7 +24,7 @@
   }
 
   void PaintPart(PaintCanvas*, ScrollbarPart part, const gfx::Rect&) override {
-    if (part == TRACK_BUTTONS_TICKMARKS)
+    if (part == ScrollbarPart::TRACK_BUTTONS_TICKMARKS)
       paint_tickmarks_called_ = true;
   }
 
diff --git a/cc/layers/painted_scrollbar_layer.cc b/cc/layers/painted_scrollbar_layer.cc
index 1a3e05aa..8b682c5 100644
--- a/cc/layers/painted_scrollbar_layer.cc
+++ b/cc/layers/painted_scrollbar_layer.cc
@@ -64,7 +64,7 @@
   scrollbar_layer->SetBackButtonRect(back_button_rect_);
   scrollbar_layer->SetForwardButtonRect(forward_button_rect_);
   scrollbar_layer->SetTrackRect(track_rect_);
-  if (orientation() == HORIZONTAL) {
+  if (orientation() == ScrollbarOrientation::HORIZONTAL) {
     scrollbar_layer->SetThumbThickness(thumb_size_.height());
     scrollbar_layer->SetThumbLength(thumb_size_.width());
   } else {
@@ -176,21 +176,24 @@
   }
 
   if (!track_resource_ ||
-      scrollbar_->NeedsRepaintPart(TRACK_BUTTONS_TICKMARKS)) {
+      scrollbar_->NeedsRepaintPart(ScrollbarPart::TRACK_BUTTONS_TICKMARKS)) {
     track_resource_ = ScopedUIResource::Create(
         layer_tree_host()->GetUIResourceManager(),
-        RasterizeScrollbarPart(size, scaled_size, TRACK_BUTTONS_TICKMARKS));
+        RasterizeScrollbarPart(size, scaled_size,
+                               ScrollbarPart::TRACK_BUTTONS_TICKMARKS));
     SetNeedsPushProperties();
     updated = true;
   }
 
   gfx::Size scaled_thumb_size = LayerSizeToContentSize(thumb_size_);
   if (has_thumb_ && !scaled_thumb_size.IsEmpty()) {
-    if (!thumb_resource_ || scrollbar_->NeedsRepaintPart(THUMB) ||
+    if (!thumb_resource_ ||
+        scrollbar_->NeedsRepaintPart(ScrollbarPart::THUMB) ||
         scaled_thumb_size != thumb_resource_->GetBitmap(0, false).GetSize()) {
       thumb_resource_ = ScopedUIResource::Create(
           layer_tree_host()->GetUIResourceManager(),
-          RasterizeScrollbarPart(thumb_size_, scaled_thumb_size, THUMB));
+          RasterizeScrollbarPart(thumb_size_, scaled_thumb_size,
+                                 ScrollbarPart::THUMB));
       SetNeedsPushProperties();
       updated = true;
     }
diff --git a/cc/layers/painted_scrollbar_layer_impl.cc b/cc/layers/painted_scrollbar_layer_impl.cc
index 6d36154..b5f60854 100644
--- a/cc/layers/painted_scrollbar_layer_impl.cc
+++ b/cc/layers/painted_scrollbar_layer_impl.cc
@@ -210,7 +210,8 @@
 }
 
 int PaintedScrollbarLayerImpl::TrackStart() const {
-  return orientation() == VERTICAL ? track_rect_.y() : track_rect_.x();
+  return orientation() == ScrollbarOrientation::VERTICAL ? track_rect_.y()
+                                                         : track_rect_.x();
 }
 
 void PaintedScrollbarLayerImpl::SetBackButtonRect(gfx::Rect back_button_rect) {
@@ -240,7 +241,7 @@
   const gfx::Rect thumb_rect = ComputeThumbQuadRect();
   const int rect_x = track_rect_.x();
   const int rect_y = track_rect_.y();
-  if (orientation() == HORIZONTAL) {
+  if (orientation() == ScrollbarOrientation::HORIZONTAL) {
     int width = thumb_rect.x() - rect_x;
     int height = track_rect_.height();
     return gfx::Rect(rect_x, rect_y, width, height);
@@ -254,7 +255,7 @@
 gfx::Rect PaintedScrollbarLayerImpl::ForwardTrackRect() const {
   const gfx::Rect thumb_rect = ComputeThumbQuadRect();
   const int track_end = TrackStart() + TrackLength();
-  if (orientation() == HORIZONTAL) {
+  if (orientation() == ScrollbarOrientation::HORIZONTAL) {
     int rect_x = thumb_rect.right();
     int rect_y = track_rect_.y();
     int width = track_end - rect_x;
@@ -277,7 +278,7 @@
 }
 
 float PaintedScrollbarLayerImpl::TrackLength() const {
-  if (orientation() == VERTICAL)
+  if (orientation() == ScrollbarOrientation::VERTICAL)
     return track_rect_.height() + vertical_adjust();
   else
     return track_rect_.width();
diff --git a/cc/layers/painted_scrollbar_layer_impl_unittest.cc b/cc/layers/painted_scrollbar_layer_impl_unittest.cc
index a3805ba..11f47b40 100644
--- a/cc/layers/painted_scrollbar_layer_impl_unittest.cc
+++ b/cc/layers/painted_scrollbar_layer_impl_unittest.cc
@@ -38,7 +38,7 @@
   UIResourceBitmap track_bitmap(track_sk_bitmap);
   impl.host_impl()->CreateUIResource(track_uid, track_bitmap);
 
-  ScrollbarOrientation orientation = VERTICAL;
+  ScrollbarOrientation orientation = ScrollbarOrientation::VERTICAL;
 
   PaintedScrollbarLayerImpl* scrollbar_layer_impl =
       impl.AddLayer<PaintedScrollbarLayerImpl>(orientation, false, false);
diff --git a/cc/layers/painted_scrollbar_layer_unittest.cc b/cc/layers/painted_scrollbar_layer_unittest.cc
index 6bb0855..0777fc0 100644
--- a/cc/layers/painted_scrollbar_layer_unittest.cc
+++ b/cc/layers/painted_scrollbar_layer_unittest.cc
@@ -59,28 +59,36 @@
   // yet been initialized.
   scrollbar->set_needs_repaint_thumb(false);
   scrollbar->set_needs_repaint_track(false);
-  EXPECT_CALL(*scrollbar, PaintPart(_, THUMB, _)).Times(1);
-  EXPECT_CALL(*scrollbar, PaintPart(_, TRACK_BUTTONS_TICKMARKS, _)).Times(1);
+  EXPECT_CALL(*scrollbar, PaintPart(_, ScrollbarPart::THUMB, _)).Times(1);
+  EXPECT_CALL(*scrollbar,
+              PaintPart(_, ScrollbarPart::TRACK_BUTTONS_TICKMARKS, _))
+      .Times(1);
   scrollbar_layer->Update();
   Mock::VerifyAndClearExpectations(scrollbar.get());
 
   // The next update will paint nothing because the first update caused a paint.
-  EXPECT_CALL(*scrollbar, PaintPart(_, THUMB, _)).Times(0);
-  EXPECT_CALL(*scrollbar, PaintPart(_, TRACK_BUTTONS_TICKMARKS, _)).Times(0);
+  EXPECT_CALL(*scrollbar, PaintPart(_, ScrollbarPart::THUMB, _)).Times(0);
+  EXPECT_CALL(*scrollbar,
+              PaintPart(_, ScrollbarPart::TRACK_BUTTONS_TICKMARKS, _))
+      .Times(0);
   scrollbar_layer->Update();
   Mock::VerifyAndClearExpectations(scrollbar.get());
 
   // Enable the thumb.
-  EXPECT_CALL(*scrollbar, PaintPart(_, THUMB, _)).Times(1);
-  EXPECT_CALL(*scrollbar, PaintPart(_, TRACK_BUTTONS_TICKMARKS, _)).Times(0);
+  EXPECT_CALL(*scrollbar, PaintPart(_, ScrollbarPart::THUMB, _)).Times(1);
+  EXPECT_CALL(*scrollbar,
+              PaintPart(_, ScrollbarPart::TRACK_BUTTONS_TICKMARKS, _))
+      .Times(0);
   scrollbar->set_needs_repaint_thumb(true);
   scrollbar->set_needs_repaint_track(false);
   scrollbar_layer->Update();
   Mock::VerifyAndClearExpectations(scrollbar.get());
 
   // Enable the track.
-  EXPECT_CALL(*scrollbar, PaintPart(_, THUMB, _)).Times(0);
-  EXPECT_CALL(*scrollbar, PaintPart(_, TRACK_BUTTONS_TICKMARKS, _)).Times(1);
+  EXPECT_CALL(*scrollbar, PaintPart(_, ScrollbarPart::THUMB, _)).Times(0);
+  EXPECT_CALL(*scrollbar,
+              PaintPart(_, ScrollbarPart::TRACK_BUTTONS_TICKMARKS, _))
+      .Times(1);
   scrollbar->set_needs_repaint_thumb(false);
   scrollbar->set_needs_repaint_track(true);
   scrollbar_layer->Update();
diff --git a/cc/layers/scrollbar_layer_impl_base.cc b/cc/layers/scrollbar_layer_impl_base.cc
index 787f339..3d9e48d 100644
--- a/cc/layers/scrollbar_layer_impl_base.cc
+++ b/cc/layers/scrollbar_layer_impl_base.cc
@@ -227,7 +227,7 @@
       thumb_thickness * (1.f - thumb_thickness_scale_factor);
 
   gfx::RectF thumb_rect;
-  if (orientation_ == HORIZONTAL) {
+  if (orientation_ == ScrollbarOrientation::HORIZONTAL) {
     thumb_rect = gfx::RectF(thumb_offset,
                             vertical_adjust_ + thumb_thickness_adjustment,
                             thumb_length,
diff --git a/cc/layers/scrollbar_layer_unittest.cc b/cc/layers/scrollbar_layer_unittest.cc
index 6da184e2..ee02df5 100644
--- a/cc/layers/scrollbar_layer_unittest.cc
+++ b/cc/layers/scrollbar_layer_unittest.cc
@@ -315,7 +315,8 @@
           base::MakeRefCounted<FakeNinePatchScrollbar>());
   painted_overlay_scrollbar_layer->SetScrollElementId(layer_a->element_id());
   scoped_refptr<SolidColorScrollbarLayer> solid_color_scrollbar_layer =
-      SolidColorScrollbarLayer::Create(VERTICAL, 1, 1, false);
+      SolidColorScrollbarLayer::Create(ScrollbarOrientation::VERTICAL, 1, 1,
+                                       false);
   solid_color_scrollbar_layer->SetScrollElementId(layer_a->element_id());
 
   layer_tree_host_->SetRootLayer(layer_tree_root);
@@ -553,8 +554,9 @@
   scoped_refptr<Layer> root_layer = Layer::Create();
   // Create an overlay left side vertical scrollbar.
   scoped_refptr<FakePaintedScrollbarLayer> scrollbar_layer =
-      FakePaintedScrollbarLayer::Create(false, true, VERTICAL, true, true,
-                                        root_layer->element_id());
+      FakePaintedScrollbarLayer::Create(false, true,
+                                        ScrollbarOrientation::VERTICAL, true,
+                                        true, root_layer->element_id());
   root_layer->SetScrollable(gfx::Size(20, 50));
   root_layer->SetBounds(gfx::Size(50, 100));
 
@@ -603,8 +605,8 @@
     scoped_refptr<Layer> root = Layer::Create();
     scoped_refptr<Layer> child = Layer::Create();
     scoped_refptr<ScrollbarLayerBase> scrollbar_layer =
-        SolidColorScrollbarLayer::Create(HORIZONTAL, kThumbThickness,
-                                         kTrackStart, false);
+        SolidColorScrollbarLayer::Create(ScrollbarOrientation::HORIZONTAL,
+                                         kThumbThickness, kTrackStart, false);
     root->AddChild(child);
     root->AddChild(scrollbar_layer);
     layer_tree_host_->SetRootLayer(root);
@@ -673,7 +675,8 @@
   scoped_refptr<Layer> child1 = Layer::Create();
   const bool kIsLeftSideVerticalScrollbar = false;
   scoped_refptr<SolidColorScrollbarLayer> child2 =
-      SolidColorScrollbarLayer::Create(HORIZONTAL, kThumbThickness, kTrackStart,
+      SolidColorScrollbarLayer::Create(ScrollbarOrientation::HORIZONTAL,
+                                       kThumbThickness, kTrackStart,
                                        kIsLeftSideVerticalScrollbar);
   child2->SetScrollElementId(scroll_layer->element_id());
   scroll_layer->AddChild(child1);
@@ -728,7 +731,8 @@
   scoped_refptr<SolidColorScrollbarLayer> scrollbar_layer;
   const bool kIsLeftSideVerticalScrollbar = false;
   scrollbar_layer = SolidColorScrollbarLayer::Create(
-      HORIZONTAL, kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar);
+      ScrollbarOrientation::HORIZONTAL, kThumbThickness, kTrackStart,
+      kIsLeftSideVerticalScrollbar);
   scrollbar_layer->SetScrollElementId(scroll_layer->element_id());
   scrollbar_layer->SetElementId(ElementId(300));
   scroll_layer->AddChild(child1);
@@ -802,7 +806,8 @@
   scoped_refptr<Layer> child1 = Layer::Create();
   const bool kIsLeftSideVerticalScrollbar = false;
   scoped_refptr<SolidColorScrollbarLayer> scrollbar_layer =
-      SolidColorScrollbarLayer::Create(HORIZONTAL, kThumbThickness, kTrackStart,
+      SolidColorScrollbarLayer::Create(ScrollbarOrientation::HORIZONTAL,
+                                       kThumbThickness, kTrackStart,
                                        kIsLeftSideVerticalScrollbar);
   scrollbar_layer->SetScrollElementId(scroll_layer->element_id());
   scroll_layer->AddChild(child1);
@@ -847,9 +852,9 @@
   const bool kIsLeftSideVerticalScrollbar = false;
 
   SolidColorScrollbarLayerImpl* scrollbar_layer =
-      impl.AddLayer<SolidColorScrollbarLayerImpl>(HORIZONTAL, kThumbThickness,
-                                                  kTrackStart,
-                                                  kIsLeftSideVerticalScrollbar);
+      impl.AddLayer<SolidColorScrollbarLayerImpl>(
+          ScrollbarOrientation::HORIZONTAL, kThumbThickness, kTrackStart,
+          kIsLeftSideVerticalScrollbar);
 
   scrollbar_layer->SetScrollElementId(scroll_layer->element_id());
   scroll_layer->SetBounds(gfx::Size(980, 980));
@@ -887,9 +892,9 @@
   const int kThumbThickness = 10;
   const bool kIsLeftSideVerticalScrollbar = false;
   SolidColorScrollbarLayerImpl* scrollbar_layer =
-      impl.AddLayer<SolidColorScrollbarLayerImpl>(HORIZONTAL, kThumbThickness,
-                                                  kTrackStart,
-                                                  kIsLeftSideVerticalScrollbar);
+      impl.AddLayer<SolidColorScrollbarLayerImpl>(
+          ScrollbarOrientation::HORIZONTAL, kThumbThickness, kTrackStart,
+          kIsLeftSideVerticalScrollbar);
   scrollbar_layer->SetScrollElementId(scroll_layer->element_id());
   EXPECT_TRUE(impl.host_impl()->active_tree()->ScrollbarGeometriesNeedUpdate());
   impl.host_impl()->active_tree()->UpdateScrollbarGeometries();
@@ -991,7 +996,8 @@
   host_impl->ActivateSyncTree();
 
   scoped_refptr<SolidColorScrollbarLayer> scrollbar_layer =
-      SolidColorScrollbarLayer::Create(HORIZONTAL, kThumbThickness, kTrackStart,
+      SolidColorScrollbarLayer::Create(ScrollbarOrientation::HORIZONTAL,
+                                       kThumbThickness, kTrackStart,
                                        kIsLeftSideVerticalScrollbar);
   scrollbar_layer->SetScrollElementId(scroll_layer->element_id());
   scroll_layer->InsertChild(scrollbar_layer, 1);
@@ -1021,11 +1027,11 @@
     const bool kIsLeftSideVerticalScrollbar = false;
 
     horizontal_scrollbar_layer_ = SolidColorScrollbarLayerImpl::Create(
-        host_impl_->active_tree(), 1, HORIZONTAL, kThumbThickness, kTrackStart,
-        kIsLeftSideVerticalScrollbar);
+        host_impl_->active_tree(), 1, ScrollbarOrientation::HORIZONTAL,
+        kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar);
     vertical_scrollbar_layer_ = SolidColorScrollbarLayerImpl::Create(
-        host_impl_->active_tree(), 2, VERTICAL, kThumbThickness, kTrackStart,
-        kIsLeftSideVerticalScrollbar);
+        host_impl_->active_tree(), 2, ScrollbarOrientation::VERTICAL,
+        kThumbThickness, kTrackStart, kIsLeftSideVerticalScrollbar);
   }
 
  protected:
@@ -1116,7 +1122,7 @@
       const int kTrackStart = 0;
       const bool kIsLeftSideVerticalScrollbar = false;
       scrollbar_layer = SolidColorScrollbarLayer::Create(
-          HORIZONTAL, kThumbThickness, kTrackStart,
+          ScrollbarOrientation::HORIZONTAL, kThumbThickness, kTrackStart,
           kIsLeftSideVerticalScrollbar);
     } else {
       auto scrollbar = base::MakeRefCounted<FakeScrollbar>();
diff --git a/cc/layers/solid_color_scrollbar_layer.cc b/cc/layers/solid_color_scrollbar_layer.cc
index d446a39f..8fe21528 100644
--- a/cc/layers/solid_color_scrollbar_layer.cc
+++ b/cc/layers/solid_color_scrollbar_layer.cc
@@ -22,7 +22,8 @@
     scoped_refptr<Scrollbar> scrollbar,
     SolidColorScrollbarLayer* existing_layer) {
   DCHECK(scrollbar->IsOverlay());
-  bool is_horizontal = scrollbar->Orientation() == HORIZONTAL;
+  bool is_horizontal =
+      scrollbar->Orientation() == ScrollbarOrientation::HORIZONTAL;
   gfx::Rect thumb_rect = scrollbar->ThumbRect();
   int thumb_thickness =
       is_horizontal ? thumb_rect.height() : thumb_rect.width();
diff --git a/cc/layers/solid_color_scrollbar_layer_impl.cc b/cc/layers/solid_color_scrollbar_layer_impl.cc
index e37741c..7c86df34 100644
--- a/cc/layers/solid_color_scrollbar_layer_impl.cc
+++ b/cc/layers/solid_color_scrollbar_layer_impl.cc
@@ -58,7 +58,7 @@
   if (thumb_thickness_ != -1)
     return thumb_thickness_;
 
-  if (orientation() == HORIZONTAL)
+  if (orientation() == ScrollbarOrientation::HORIZONTAL)
     return bounds().height();
   else
     return bounds().width();
@@ -73,7 +73,7 @@
 }
 
 float SolidColorScrollbarLayerImpl::TrackLength() const {
-  if (orientation() == HORIZONTAL)
+  if (orientation() == ScrollbarOrientation::HORIZONTAL)
     return bounds().width() - TrackStart() * 2;
   else
     return bounds().height() + vertical_adjust() - TrackStart() * 2;
diff --git a/cc/layers/solid_color_scrollbar_layer_impl_unittest.cc b/cc/layers/solid_color_scrollbar_layer_impl_unittest.cc
index 8544ce17..7e87232 100644
--- a/cc/layers/solid_color_scrollbar_layer_impl_unittest.cc
+++ b/cc/layers/solid_color_scrollbar_layer_impl_unittest.cc
@@ -18,7 +18,7 @@
 
   LayerTreeImplTestBase impl;
 
-  ScrollbarOrientation orientation = VERTICAL;
+  ScrollbarOrientation orientation = ScrollbarOrientation::VERTICAL;
   int thumb_thickness = layer_size.width();
   int track_start = 0;
   bool is_left_side_vertical_scrollbar = false;
diff --git a/cc/test/fake_painted_scrollbar_layer.cc b/cc/test/fake_painted_scrollbar_layer.cc
index 8dfb65d..2ee5547d 100644
--- a/cc/test/fake_painted_scrollbar_layer.cc
+++ b/cc/test/fake_painted_scrollbar_layer.cc
@@ -13,7 +13,8 @@
     bool paint_during_update,
     bool has_thumb,
     ElementId scrolling_element_id) {
-  return Create(paint_during_update, has_thumb, HORIZONTAL, false, false,
+  return Create(paint_during_update, has_thumb,
+                ScrollbarOrientation::HORIZONTAL, false, false,
                 scrolling_element_id);
 }
 
diff --git a/cc/test/fake_scrollbar.cc b/cc/test/fake_scrollbar.cc
index 0b63a1b..9613adb5 100644
--- a/cc/test/fake_scrollbar.cc
+++ b/cc/test/fake_scrollbar.cc
@@ -62,7 +62,7 @@
 }
 
 bool FakeScrollbar::NeedsRepaintPart(ScrollbarPart part) const {
-  if (part == THUMB)
+  if (part == ScrollbarPart::THUMB)
     return needs_repaint_thumb_;
   return needs_repaint_track_;
 }
diff --git a/cc/test/fake_scrollbar.h b/cc/test/fake_scrollbar.h
index 6923618..37ddbad2 100644
--- a/cc/test/fake_scrollbar.h
+++ b/cc/test/fake_scrollbar.h
@@ -72,7 +72,7 @@
   bool should_paint_ = false;
   bool has_thumb_ = false;
   bool has_tickmarks_ = false;
-  ScrollbarOrientation orientation_ = HORIZONTAL;
+  ScrollbarOrientation orientation_ = ScrollbarOrientation::HORIZONTAL;
   bool is_left_side_vertical_scrollbar_ = false;
   bool is_solid_color_ = false;
   bool is_overlay_ = false;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index 8a6918b..790258aa 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -489,7 +489,7 @@
     squash2->SetOffsetToTransformParent(gfx::Vector2dF(220, 300));
 
     auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-        layer_tree_impl, VERTICAL, false, true);
+        layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true);
     SetupScrollbarLayer(scroll, scrollbar);
     scrollbar->SetBounds(scrollbar_size);
     scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
@@ -1595,7 +1595,7 @@
                                          content_size, scroll_content_size);
 
   auto* drawn_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      layer_tree_impl, VERTICAL, false, true);
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true);
   SetupScrollbarLayer(scroll, drawn_scrollbar);
   drawn_scrollbar->SetBounds(scrollbar_size);
   drawn_scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
@@ -3534,10 +3534,10 @@
 
   // Add scrollbars. They will always exist - even if unscrollable - but their
   // visibility will be determined by whether the content can be scrolled.
-  auto* v_scrollbar =
-      AddLayer<PaintedScrollbarLayerImpl>(active_tree, VERTICAL, false, true);
-  auto* h_scrollbar =
-      AddLayer<PaintedScrollbarLayerImpl>(active_tree, HORIZONTAL, false, true);
+  auto* v_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      active_tree, ScrollbarOrientation::VERTICAL, false, true);
+  auto* h_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      active_tree, ScrollbarOrientation::HORIZONTAL, false, true);
   SetupScrollbarLayer(scroll, v_scrollbar);
   SetupScrollbarLayer(scroll, h_scrollbar);
 
@@ -4968,7 +4968,8 @@
     host_impl_->active_tree()->PushPageScaleFromMainThread(1, 1, 4);
 
     auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-        host_impl_->active_tree(), VERTICAL, 10, 0, false);
+        host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 10, 0,
+        false);
     SetupScrollbarLayer(OuterViewportScrollLayer(), scrollbar);
 
     host_impl_->active_tree()->DidBecomeActive();
@@ -5193,7 +5194,8 @@
     LayerImpl* scroll =
         host_impl_->pending_tree()->OuterViewportScrollLayerForTesting();
     auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-        host_impl_->pending_tree(), VERTICAL, 10, 0, false);
+        host_impl_->pending_tree(), ScrollbarOrientation::VERTICAL, 10, 0,
+        false);
     SetupScrollbarLayer(scroll, scrollbar);
     scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0));
 
@@ -5297,7 +5299,7 @@
 
     // scrollbar_1 on root scroll.
     scrollbar_1_ = AddLayer<SolidColorScrollbarLayerImpl>(
-        host_impl_->active_tree(), VERTICAL, 15, 0, true);
+        host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 15, 0, true);
     SetupScrollbarLayer(root_scroll, scrollbar_1_);
     scrollbar_1_->SetBounds(scrollbar_size_1);
     TouchActionRegion touch_action_region;
@@ -5310,7 +5312,7 @@
     GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50);
 
     scrollbar_2_ = AddLayer<SolidColorScrollbarLayerImpl>(
-        host_impl_->active_tree(), VERTICAL, 15, 0, true);
+        host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 15, 0, true);
     SetupScrollbarLayer(child, scrollbar_2_);
     scrollbar_2_->SetBounds(scrollbar_size_2);
 
@@ -5429,7 +5431,7 @@
 
   // scrollbar_1 on root scroll.
   auto* scrollbar_1 = AddLayer<PaintedScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, true, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, true, true);
   SetupScrollbarLayer(root_scroll, scrollbar_1);
   scrollbar_1->SetBounds(scrollbar_size_1);
   TouchActionRegion touch_action_region;
@@ -5442,7 +5444,7 @@
 
   // scrollbar_2 on child.
   auto* scrollbar_2 = AddLayer<PaintedScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, true, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, true, true);
   SetupScrollbarLayer(child, scrollbar_2);
   scrollbar_2->SetBounds(scrollbar_size_2);
   scrollbar_2->SetOffsetToTransformParent(gfx::Vector2dF(50, 50));
@@ -5515,7 +5517,7 @@
   LayerImpl* scroll =
       host_impl_->pending_tree()->OuterViewportScrollLayerForTesting();
   auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->pending_tree(), VERTICAL, 10, 0, false);
+      host_impl_->pending_tree(), ScrollbarOrientation::VERTICAL, 10, 0, false);
   SetupScrollbarLayer(scroll, scrollbar);
   scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0));
 
@@ -5580,7 +5582,7 @@
                       outer_viewport_size, content_size);
   LayerImpl* root_scroll = OuterViewportScrollLayer();
   auto* horiz_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      host_impl_->active_tree(), HORIZONTAL, true, true);
+      host_impl_->active_tree(), ScrollbarOrientation::HORIZONTAL, true, true);
   SetupScrollbarLayer(root_scroll, horiz_scrollbar);
   LayerImpl* child = AddLayer();
   child->SetBounds(content_size);
@@ -5606,19 +5608,19 @@
   auto* container = InnerViewportScrollLayer();
   auto* root_scroll = OuterViewportScrollLayer();
   auto* vert_1_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, 5, 5, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 5, 5, true);
   CopyProperties(container, vert_1_scrollbar);
 
   auto* horiz_1_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), HORIZONTAL, 5, 5, true);
+      host_impl_->active_tree(), ScrollbarOrientation::HORIZONTAL, 5, 5, true);
   CopyProperties(container, horiz_1_scrollbar);
 
   auto* vert_2_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, 5, 5, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 5, 5, true);
   CopyProperties(container, vert_2_scrollbar);
 
   auto* horiz_2_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), HORIZONTAL, 5, 5, true);
+      host_impl_->active_tree(), ScrollbarOrientation::HORIZONTAL, 5, 5, true);
   CopyProperties(container, horiz_2_scrollbar);
 
   UpdateDrawProperties(host_impl_->active_tree());
@@ -5700,7 +5702,8 @@
 
   const int kScrollbarThickness = 5;
   auto* vert_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, kScrollbarThickness, 0, false);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL,
+      kScrollbarThickness, 0, false);
   SetupScrollbarLayer(root_scroll, vert_scrollbar);
   vert_scrollbar->SetBounds(gfx::Size(10, 200));
   vert_scrollbar->SetOffsetToTransformParent(
@@ -5721,11 +5724,13 @@
   // Move the mouse near the thumb while its at the viewport top.
   auto near_thumb_at_top = gfx::Point(295, kDistanceToTriggerThumb - 1);
   host_impl_->MouseMoveAt(near_thumb_at_top);
-  EXPECT_TRUE(scrollbar_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   // Move the mouse away from the thumb.
   host_impl_->MouseMoveAt(gfx::Point(295, kDistanceToTriggerThumb + 1));
-  EXPECT_FALSE(scrollbar_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   // Scroll the page down which moves the thumb down to the viewport bottom.
   host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, 800),
@@ -5739,7 +5744,8 @@
 
   // Move the mouse near the thumb in the top position.
   host_impl_->MouseMoveAt(near_thumb_at_top);
-  EXPECT_FALSE(scrollbar_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   // Scroll the page up which moves the thumb back up.
   host_impl_->ScrollBegin(BeginState(gfx::Point(), gfx::Vector2dF(0, -800),
@@ -5753,7 +5759,8 @@
 
   // Move the mouse near the thumb in the top position.
   host_impl_->MouseMoveAt(near_thumb_at_top);
-  EXPECT_TRUE(scrollbar_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 }
 
 void LayerTreeHostImplTest::SetupMouseMoveAtWithDeviceScale(
@@ -5773,7 +5780,7 @@
   LayerImpl* root_scroll = OuterViewportScrollLayer();
   // The scrollbar is on the left side.
   auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, 15, 0, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 15, 0, true);
   SetupScrollbarLayer(root_scroll, scrollbar);
   scrollbar->SetBounds(scrollbar_size);
   TouchActionRegion touch_action_region;
@@ -5796,34 +5803,37 @@
 
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerFadeIn, 1));
-  EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerExpand - 1, 10));
-  EXPECT_TRUE(scrollbar_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerFadeIn, 100));
-  EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   did_request_redraw_ = false;
-  EXPECT_FALSE(
-      scrollbar_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
   host_impl_->MouseMoveAt(gfx::Point(10, 10));
-  EXPECT_TRUE(
-      scrollbar_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
   host_impl_->MouseMoveAt(gfx::Point(10, 0));
-  EXPECT_TRUE(
-      scrollbar_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
   host_impl_->MouseMoveAt(gfx::Point(150, 120));
-  EXPECT_FALSE(
-      scrollbar_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 }
 
 TEST_P(ScrollUnifiedLayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1) {
@@ -6845,7 +6855,8 @@
   // Create a horizontal scrollbar.
   gfx::Size scrollbar_size(gfx::Size(50, 15));
   auto* scrollbar_layer = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), HORIZONTAL, 3, 20, false);
+      host_impl_->active_tree(), ScrollbarOrientation::HORIZONTAL, 3, 20,
+      false);
   SetupScrollbarLayer(OuterViewportScrollLayer(), scrollbar_layer);
   scrollbar_layer->SetBounds(scrollbar_size);
   TouchActionRegion touch_action_region;
@@ -13544,8 +13555,8 @@
 
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  auto* scrollbar = AddLayer<PaintedOverlayScrollbarLayerImpl>(layer_tree_impl,
-                                                               VERTICAL, false);
+  auto* scrollbar = AddLayer<PaintedOverlayScrollbarLayerImpl>(
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, false);
   SetupScrollbarLayerCommon(scroll_layer, scrollbar);
   scrollbar->SetHitTestable(true);
 
@@ -13610,8 +13621,8 @@
 
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(layer_tree_impl,
-                                                        VERTICAL, false, true);
+  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true);
   SetupScrollbarLayerCommon(scroll_layer, scrollbar);
   scrollbar->SetHitTestable(true);
 
@@ -13682,8 +13693,8 @@
 
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(layer_tree_impl,
-                                                        VERTICAL, false, true);
+  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true);
   SetupScrollbarLayerCommon(scroll_layer, scrollbar);
   scrollbar->SetHitTestable(true);
 
@@ -13732,8 +13743,8 @@
 
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(layer_tree_impl,
-                                                        VERTICAL, false, true);
+  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true);
   SetupScrollbarLayer(scroll_layer, scrollbar);
   const gfx::Size scrollbar_size = gfx::Size(15, 600);
   scrollbar->SetBounds(scrollbar_size);
@@ -13870,7 +13881,8 @@
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
   auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false,
+      layer_tree_impl, ScrollbarOrientation::VERTICAL,
+      /*is_left_side_vertical_scrollbar*/ false,
       /*is_overlay*/ false);
 
   SetupScrollbarLayer(scroll_layer, scrollbar);
@@ -13942,7 +13954,8 @@
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
   auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false,
+      layer_tree_impl, ScrollbarOrientation::VERTICAL,
+      /*is_left_side_vertical_scrollbar*/ false,
       /*is_overlay*/ false);
 
   SetupScrollbarLayer(scroll_layer, scrollbar);
@@ -14039,7 +14052,8 @@
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
   auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false,
+      layer_tree_impl, ScrollbarOrientation::VERTICAL,
+      /*is_left_side_vertical_scrollbar*/ false,
       /*is_overlay*/ false);
 
   // TODO(arakeri): crbug.com/1070063 Setting the dimensions for scrollbar parts
@@ -14148,7 +14162,8 @@
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
   auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      layer_tree_impl, VERTICAL, /*is_left_side_vertical_scrollbar*/ false,
+      layer_tree_impl, ScrollbarOrientation::VERTICAL,
+      /*is_left_side_vertical_scrollbar*/ false,
       /*is_overlay*/ false);
 
   // TODO(arakeri): crbug.com/1070063 Setting the dimensions for scrollbar parts
@@ -14248,8 +14263,8 @@
 
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(layer_tree_impl,
-                                                        VERTICAL, false, true);
+  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, false, true);
   SetupScrollbarLayer(scroll_layer, scrollbar);
   const gfx::Size scrollbar_size = gfx::Size(15, 600);
   scrollbar->SetBounds(scrollbar_size);
@@ -15548,7 +15563,7 @@
 
   // scrollbar_1 on root scroll.
   auto* scrollbar_1 = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, 15, 0, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 15, 0, true);
   SetupScrollbarLayer(root_scroll, scrollbar_1);
   scrollbar_1->SetBounds(scrollbar_size_1);
   TouchActionRegion touch_action_region;
@@ -15576,64 +15591,69 @@
   // moves back to where it was.
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerFadeIn, 0));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerExpand, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(14 + kMouseMoveDistanceToTriggerExpand, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(gfx::Point(10, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(14 + kMouseMoveDistanceToTriggerExpand, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerExpand, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(15 + kMouseMoveDistanceToTriggerFadeIn, 0));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   // scrollbar_2 on child.
   auto* scrollbar_2 = AddLayer<SolidColorScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, 15, 0, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, 15, 0, true);
   LayerImpl* child =
       AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size);
   child->SetOffsetToTransformParent(gfx::Vector2dF(50, 50));
@@ -15657,56 +15677,60 @@
   // Mouse goes over scrollbar_2, moves close to scrollbar_2, moves close to
   // scrollbar_1, goes over scrollbar_1.
   host_impl_->MouseMoveAt(gfx::Point(60, 60));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
-  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   host_impl_->MouseMoveAt(
       gfx::Point(64 + kMouseMoveDistanceToTriggerExpand, 50));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
-  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
   host_impl_->MouseMoveAt(
       gfx::Point(14 + kMouseMoveDistanceToTriggerExpand, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
   host_impl_->MouseMoveAt(gfx::Point(10, 0));
-  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_TRUE(
-      scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsNearScrollbar(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(VERTICAL));
-  EXPECT_FALSE(
-      scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_TRUE(scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsNearScrollbar(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsNearScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
+  EXPECT_FALSE(scrollbar_2_animation_controller->MouseIsOverScrollbarThumb(
+      ScrollbarOrientation::VERTICAL));
 
   // Capture scrollbar_1, then move mouse to scrollbar_2's layer);
   animation_task_.Reset();
@@ -16815,7 +16839,7 @@
                                           viewport_size, scroll_content_size);
 
   auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
-      layer_tree_impl, VERTICAL, 10, 0, false);
+      layer_tree_impl, ScrollbarOrientation::VERTICAL, 10, 0, false);
   SetupScrollbarLayer(content, scrollbar);
   scrollbar->SetBounds(scrollbar_size);
   scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
@@ -17024,7 +17048,7 @@
       content_layer, gfx::Size(185, 500), gfx::Size(185, 3800));
 
   auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
-      host_impl_->active_tree(), VERTICAL, false, true);
+      host_impl_->active_tree(), ScrollbarOrientation::VERTICAL, false, true);
   SetupScrollbarLayer(scroll_layer, scrollbar);
 
   scrollbar->SetBounds(gfx::Size(15, 500));
diff --git a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
index 6677811b..2e45a74 100644
--- a/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
+++ b/cc/trees/layer_tree_host_pixeltest_scrollbars.cc
@@ -213,7 +213,7 @@
   PaintedOverlayScrollbar() {
     set_should_paint(true);
     set_has_thumb(true);
-    set_orientation(VERTICAL);
+    set_orientation(ScrollbarOrientation::VERTICAL);
     set_is_overlay(true);
     set_thumb_size(gfx::Size(15, 50));
     set_track_rect(gfx::Rect(0, 0, 15, 400));
diff --git a/cc/trees/layer_tree_impl.cc b/cc/trees/layer_tree_impl.cc
index 0536e70..494475d 100644
--- a/cc/trees/layer_tree_impl.cc
+++ b/cc/trees/layer_tree_impl.cc
@@ -312,7 +312,7 @@
     }
 
     for (auto* scrollbar : ScrollbarsFor(scrolling_element_id)) {
-      if (scrollbar->orientation() == HORIZONTAL) {
+      if (scrollbar->orientation() == ScrollbarOrientation::HORIZONTAL) {
         scrollbar->SetCurrentPos(current_offset.x());
         scrollbar->SetClipLayerLength(bounds_size.width());
         scrollbar->SetScrollLayerLength(scrolling_size.width());
@@ -675,7 +675,7 @@
     return;
 
   for (ScrollbarLayerImplBase* scrollbar : controller->Scrollbars()) {
-    if (scrollbar->orientation() != VERTICAL)
+    if (scrollbar->orientation() != ScrollbarOrientation::VERTICAL)
       continue;
 
     // Android Overlay Scrollbar don't have FindInPage Tickmarks.
@@ -1917,9 +1917,10 @@
     return;
 
   auto* scrollbar_ids = &element_id_to_scrollbar_layer_ids_[scroll_element_id];
-  int* scrollbar_layer_id = scrollbar_layer->orientation() == HORIZONTAL
-                                ? &scrollbar_ids->horizontal
-                                : &scrollbar_ids->vertical;
+  int* scrollbar_layer_id =
+      scrollbar_layer->orientation() == ScrollbarOrientation::HORIZONTAL
+          ? &scrollbar_ids->horizontal
+          : &scrollbar_ids->vertical;
 
   // We used to DCHECK this was not the case but this can occur on Android: as
   // the visual viewport supplies scrollbars for the outer viewport, if the
@@ -1932,9 +1933,10 @@
 
     // The scrollbar_ids could have been erased above so get it again.
     scrollbar_ids = &element_id_to_scrollbar_layer_ids_[scroll_element_id];
-    scrollbar_layer_id = scrollbar_layer->orientation() == HORIZONTAL
-                             ? &scrollbar_ids->horizontal
-                             : &scrollbar_ids->vertical;
+    scrollbar_layer_id =
+        scrollbar_layer->orientation() == ScrollbarOrientation::HORIZONTAL
+            ? &scrollbar_ids->horizontal
+            : &scrollbar_ids->vertical;
   }
 
   *scrollbar_layer_id = scrollbar_layer->id();
@@ -1957,7 +1959,7 @@
     return;
 
   auto& scrollbar_ids = element_id_to_scrollbar_layer_ids_[scroll_element_id];
-  if (scrollbar_layer->orientation() == HORIZONTAL)
+  if (scrollbar_layer->orientation() == ScrollbarOrientation::HORIZONTAL)
     scrollbar_ids.horizontal = Layer::INVALID_ID;
   else
     scrollbar_ids.vertical = Layer::INVALID_ID;
diff --git a/chrome/android/chrome_public_apk_tmpl.gni b/chrome/android/chrome_public_apk_tmpl.gni
index d029559..ca09cd7c 100644
--- a/chrome/android/chrome_public_apk_tmpl.gni
+++ b/chrome/android/chrome_public_apk_tmpl.gni
@@ -168,7 +168,7 @@
     # are currently duplicated in system_webview_apk_tmpl.gni.
 
     # Used only by alert dialog on tiny screens.
-    _material_package = "com_google_android_material_material.*"
+    _material_package = "com_google_android_material.*"
     resource_exclusion_regex += "|${_material_package}values-small"
 
     # Used only by date picker (which chrome doesn't use).
@@ -182,7 +182,7 @@
     resource_exclusion_regex +=
         "|${_material_package}/drawable.*design_snackbar"
     resource_exclusion_regex += "|${_material_package}/xml.*badge_"
-    _material_package = "*com_google_android_material_material*"
+    _material_package = "*com_google_android_material*"
     resource_exclusion_exceptions += [
       # AppBarLayout
       "${_material_package}design_appbar_*",
diff --git a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
index 427db8d..1edafed8 100644
--- a/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
+++ b/chrome/android/features/start_surface/internal/javatests/src/org/chromium/chrome/features/start_surface/StartSurfaceLayoutTest.java
@@ -1077,6 +1077,7 @@
     @Test
     @MediumTest
     @CommandLineFlags.Add({BASE_PARAMS})
+    @DisabledTest(message = "https://crbug.com/1122657")
     public void testThumbnailAspectRatio_default() {
         prepareTabs(2, 0, mUrl);
         enterTabSwitcher(mActivityTestRule.getActivity());
@@ -1087,6 +1088,7 @@
     @Test
     @MediumTest
     @CommandLineFlags.Add({BASE_PARAMS + "/thumbnail_aspect_ratio/0.75"})
+    @DisabledTest(message = "https://crbug.com/1122657")
     public void testThumbnailAspectRatio_point75() {
         prepareTabs(2, 0, mUrl);
         enterTabSwitcher(mActivityTestRule.getActivity());
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
index ad4a4d5..58b5140 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStream.java
@@ -184,12 +184,12 @@
 
     @Override
     public void addOnContentChangedListener(ContentChangedListener listener) {
-        // Not longer needed.
+        mFeedStreamSurface.addContentChangedListener(listener);
     }
 
     @Override
     public void removeOnContentChangedListener(ContentChangedListener listener) {
-        // Not longer needed.
+        mFeedStreamSurface.removeContentChangedListener(listener);
     }
 
     @Override
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
index 988ce1b..c141331 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurface.java
@@ -6,6 +6,7 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.os.Handler;
 import android.view.ContextThemeWrapper;
 import android.view.View;
 import android.view.ViewParent;
@@ -14,10 +15,12 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener;
 
 import org.chromium.base.Callback;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
+import org.chromium.base.ObserverList;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -27,6 +30,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.feed.shared.ScrollTracker;
+import org.chromium.chrome.browser.feed.shared.stream.Stream.ContentChangedListener;
 import org.chromium.chrome.browser.flags.ChromeFeatureList;
 import org.chromium.chrome.browser.help.HelpAndFeedback;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
@@ -105,6 +109,10 @@
     private final NativePageNavigationDelegate mPageNavigationDelegate;
     private final HelpAndFeedback mHelpAndFeedback;
     private final ScrollReporter mScrollReporter = new ScrollReporter();
+    private final ObserverList<ContentChangedListener> mContentChangedListeners =
+            new ObserverList<ContentChangedListener>();
+    private final RecyclerViewAnimationFinishDetector mRecyclerViewAnimationFinishDetector =
+            new RecyclerViewAnimationFinishDetector();
     // True after onSurfaceOpened(), and before onSurfaceClosed().
     private boolean mOpened;
     private boolean mStreamContentVisible;
@@ -518,9 +526,12 @@
 
     private void updateContentsInPlace(
             ArrayList<FeedListContentManager.FeedContent> newContentList) {
+        boolean hasContentChange = false;
+
         // 1) Builds the hash set based on keys of new contents.
         HashSet<String> newContentKeySet = new HashSet<String>();
         for (int i = 0; i < newContentList.size(); ++i) {
+            hasContentChange = true;
             newContentKeySet.add(newContentList.get(i).getKey());
         }
 
@@ -536,6 +547,7 @@
         for (int i = mContentManager.getItemCount() - 1; i >= 0; --i) {
             String key = mContentManager.getContent(i).getKey();
             if (!newContentKeySet.contains(key)) {
+                hasContentChange = true;
                 mContentManager.removeContents(i, 1);
                 existingContentMap.remove(key);
             }
@@ -549,6 +561,7 @@
 
             // If this is an existing content, moves it to new position.
             if (existingContentMap.containsKey(content.getKey())) {
+                hasContentChange = true;
                 mContentManager.moveContent(
                         mContentManager.findContentPositionByKey(content.getKey()), i);
                 ++i;
@@ -561,8 +574,21 @@
                     && !existingContentMap.containsKey(newContentList.get(i).getKey())) {
                 ++i;
             }
+            hasContentChange = true;
             mContentManager.addContents(startIndex, newContentList.subList(startIndex, i));
         }
+
+        if (hasContentChange) {
+            mRecyclerViewAnimationFinishDetector.asyncWait();
+        }
+    }
+
+    private void notifyContentChanged() {
+        for (ContentChangedListener listener : mContentChangedListeners) {
+            // For Feed v2, we only need to report if the content has changed. All other callbacks
+            // are not used at this point.
+            listener.onContentChanged();
+        }
     }
 
     private FeedListContentManager.FeedContent createContentFromSlice(Slice slice) {
@@ -896,6 +922,7 @@
         int feedCount = mContentManager.getItemCount() - mHeaderCount;
         if (feedCount > 0) {
             mContentManager.removeContents(mHeaderCount, feedCount);
+            mRecyclerViewAnimationFinishDetector.asyncWait();
         }
 
         mScrollReporter.onUnbind();
@@ -928,6 +955,14 @@
         }
     }
 
+    public void addContentChangedListener(ContentChangedListener listener) {
+        mContentChangedListeners.addObserver(listener);
+    }
+
+    public void removeContentChangedListener(ContentChangedListener listener) {
+        mContentChangedListeners.removeObserver(listener);
+    }
+
     // Called when the stream is scrolled.
     void streamScrolled(int dx, int dy) {
         FeedStreamSurfaceJni.get().reportStreamScrollStart(
@@ -935,6 +970,51 @@
         mScrollReporter.trackScroll(dx, dy);
     }
 
+    // Detects animation finishes in RecyclerView.
+    // https://stackoverflow.com/questions/33710605/detect-animation-finish-in-androids-recyclerview
+    private class RecyclerViewAnimationFinishDetector implements ItemAnimatorFinishedListener {
+        private boolean mWaitingStarted;
+
+        /** Asynchronousy waits for the animation to finish. */
+        public void asyncWait() {
+            if (mWaitingStarted) {
+                return;
+            }
+            mWaitingStarted = true;
+
+            // The RecyclerView has not started animating yet, so post a message to the
+            // message queue that will be run after the RecyclerView has started animating.
+            new Handler().post(() -> { checkFinish(); });
+        }
+
+        private void checkFinish() {
+            if (mRootView.isAnimating()) {
+                // The RecyclerView is still animating, try again when the animation has finished.
+                mRootView.getItemAnimator().isRunning(this);
+                return;
+            }
+
+            // The RecyclerView has animated all it's views.
+            onFinished();
+        }
+
+        private void onFinished() {
+            mWaitingStarted = false;
+
+            // This works around the bug that the out-of-screen toolbar is not brought back together
+            // with the new tab page view when it slides down. This is because the RecyclerView
+            // animation may not finish when content changed event is triggered and thus the new tab
+            // page layout view may still be partially off screen.
+            notifyContentChanged();
+        }
+
+        @Override
+        public void onAnimationsFinished() {
+            // There might still be more items that will be animated after this one.
+            new Handler().post(() -> { checkFinish(); });
+        }
+    }
+
     // Ingests scroll events and reports scroll completion back to native.
     private class ScrollReporter extends ScrollTracker {
         @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java
index f0595cd..af55186 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabCoordinator.java
@@ -72,9 +72,9 @@
 
     private String mUrl;
     private int mCurrentMaxSheetHeight;
-    private Profile mProfile;
-    private boolean mOpened;
-    private boolean mFullStateLogged;
+    private boolean mPeeked;
+    private boolean mViewed; // Moved up from peek state by user
+    private boolean mFullyOpened;
 
     /**
      * Constructor.
@@ -114,7 +114,7 @@
      * Checks if the preview tab is in open (peek) state.
      */
     public boolean isOpened() {
-        return mOpened;
+        return mPeeked;
     }
 
     /**
@@ -146,25 +146,33 @@
                 public void onSheetContentChanged(BottomSheetContent newContent) {
                     if (newContent != mSheetContent) {
                         mMetrics.recordMetricsForClosed(mCloseReason);
-                        mOpened = false;
+                        mPeeked = false;
                         destroyWebContents();
                     }
                 }
 
                 @Override
+                public void onSheetOpened(@StateChangeReason int reason) {
+                    if (!mViewed) {
+                        mMetrics.recordMetricsForViewed();
+                        mViewed = true;
+                    }
+                }
+
+                @Override
                 public void onSheetStateChanged(int newState) {
                     if (mSheetContent == null) return;
                     switch (newState) {
                         case SheetState.PEEK:
-                            if (!mOpened) {
+                            if (!mPeeked) {
                                 mMetrics.recordMetricsForPeeked();
-                                mOpened = true;
+                                mPeeked = true;
                             }
                             break;
                         case SheetState.FULL:
-                            if (!mFullStateLogged) {
+                            if (!mFullyOpened) {
                                 mMetrics.recordMetricsForOpened();
-                                mFullStateLogged = true;
+                                mFullyOpened = true;
                             }
                             break;
                     }
@@ -190,8 +198,9 @@
             mLayoutView.addOnLayoutChangeListener(this);
         }
 
-        mOpened = false;
-        mFullStateLogged = false;
+        mPeeked = false;
+        mViewed = false;
+        mFullyOpened = false;
         mMediator.requestShowContent(url, title);
 
         Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabMetrics.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabMetrics.java
index e787370..66fc0cb1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabMetrics.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/ephemeraltab/EphemeralTabMetrics.java
@@ -19,7 +19,7 @@
     /** The timestamp when the panel entered the peeking state for the first time. */
     private long mPanelPeekedNanoseconds;
 
-    /** Remembers whether the panel was opened beyond the peeking state. */
+    /** Remembers whether the panel was opened fully. */
     private boolean mDidRecordFirstOpen;
 
     /** The timestamp when the panel entered the opened state for the first time. */
@@ -28,6 +28,9 @@
     /** Whether the panel is in any visible state. */
     private boolean mIsVisible;
 
+    /** Whether the panel was opened beyond peeking state. */
+    private boolean mIsViewed;
+
     /** Records metrics for the peeked panel state. */
     public void recordMetricsForPeeked() {
         mIsVisible = true;
@@ -36,6 +39,12 @@
         finishOpenTimer();
     }
 
+    /** Records metrics when the panel has gone beyond peek state. */
+    public void recordMetricsForViewed() {
+        mIsViewed = true;
+        finishPeekTimer();
+    }
+
     /** Records metrics when the panel has been fully opened. */
     public void recordMetricsForOpened() {
         mIsVisible = true;
@@ -49,6 +58,7 @@
 
         finishPeekTimer();
         finishOpenTimer();
+        RecordHistogram.recordBooleanHistogram("EphemeralTab.CtrPeek", mIsViewed);
         RecordHistogram.recordBooleanHistogram("EphemeralTab.Ctr", mDidRecordFirstOpen);
         RecordHistogram.recordEnumeratedHistogram("EphemeralTab.BottomSheet.CloseReason",
                 stateChangeReason, StateChangeReason.MAX_VALUE + 1);
@@ -73,6 +83,7 @@
         mDidRecordFirstOpen = false;
         mPanelOpenedNanoseconds = 0;
         mIsVisible = false;
+        mIsViewed = false;
     }
 
     /** Starts timing the peek state if it's not already been started. */
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java
index 03921ea..3f1aa6a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/v2/FeedStreamSurfaceTest.java
@@ -55,6 +55,7 @@
 import org.chromium.base.test.util.MetricsUtils.HistogramDelta;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.AppHooksImpl;
+import org.chromium.chrome.browser.feed.shared.stream.Stream.ContentChangedListener;
 import org.chromium.chrome.browser.help.HelpAndFeedback;
 import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
 import org.chromium.chrome.browser.ntp.NewTabPageUma;
@@ -80,6 +81,19 @@
 @RunWith(BaseRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, shadows = {ShadowPostTask.class, ShadowRecordHistogram.class})
 public class FeedStreamSurfaceTest {
+    class ContentChangeWatcher implements ContentChangedListener {
+        @Override
+        public void onContentChanged() {
+            mContentChanged = true;
+        }
+
+        @Override
+        public void onAddStarting() {}
+
+        @Override
+        public void onAddFinished() {}
+    }
+
     private static final String TEST_DATA = "test";
     private static final String TEST_URL = "https://www.chromium.org";
     private static final int LOAD_MORE_TRIGGER_LOOKAHEAD = 5;
@@ -89,6 +103,7 @@
     private LinearLayout mParent;
     private FakeLinearLayoutManager mLayoutManager;
     private FeedListContentManager mContentManager;
+    private boolean mContentChanged;
 
     @Mock
     private SnackbarManager mSnackbarManager;
@@ -146,6 +161,7 @@
         mRecyclerView = mFeedStreamSurface.mRootView;
         mLayoutManager = new FakeLinearLayoutManager(mActivity);
         mRecyclerView.setLayoutManager(mLayoutManager);
+        mFeedStreamSurface.addContentChangedListener(new ContentChangeWatcher());
 
         // Since we use a mockito spy, we need to replace the entry in sSurfaces.
         FeedStreamSurface.sSurfaces.clear();
@@ -164,6 +180,39 @@
 
     @Test
     @SmallTest
+    public void testContentChangedOnStreamUpdated() {
+        startupAndSetVisible();
+
+        // Add 1 slice.
+        StreamUpdate update = StreamUpdate.newBuilder()
+                                      .addUpdatedSlices(createSliceUpdateForNewXSurfaceSlice("a"))
+                                      .build();
+        mContentChanged = false;
+        mFeedStreamSurface.onStreamUpdated(update.toByteArray());
+        assertTrue(mContentChanged);
+        assertEquals(1, mContentManager.getItemCount());
+
+        // Remove 1 slice.
+        update = StreamUpdate.newBuilder().build();
+        mContentChanged = false;
+        mFeedStreamSurface.onStreamUpdated(update.toByteArray());
+        assertTrue(mContentChanged);
+        assertEquals(0, mContentManager.getItemCount());
+    }
+
+    @Test
+    @SmallTest
+    public void testContentChangedOnSetHeaderViews() {
+        startupAndSetVisible();
+
+        mContentChanged = false;
+        mFeedStreamSurface.setHeaderViews(Arrays.asList(new View(mActivity)));
+        assertTrue(mContentChanged);
+        assertEquals(1, mContentManager.getItemCount());
+    }
+
+    @Test
+    @SmallTest
     public void testAddSlicesOnStreamUpdated() {
         startupAndSetVisible();
         // Add 3 new slices at first.
@@ -529,7 +578,9 @@
         assertEquals(headers + 3, mContentManager.getItemCount());
 
         // Closing the surface should remove all non-header contents.
+        mContentChanged = false;
         mFeedStreamSurface.setStreamVisibility(false);
+        assertTrue(mContentChanged);
         assertEquals(headers, mContentManager.getItemCount());
         assertEquals(v0, getNativeView(0));
         assertEquals(v1, getNativeView(1));
diff --git a/chrome/android/webapk/shell_apk/BUILD.gn b/chrome/android/webapk/shell_apk/BUILD.gn
index 5c1c646..79d6fad 100644
--- a/chrome/android/webapk/shell_apk/BUILD.gn
+++ b/chrome/android/webapk/shell_apk/BUILD.gn
@@ -105,6 +105,7 @@
   _generated_res_background_dir =
       "${target_gen_dir}/${_generate_res_background_xml_target_name}/res"
   _resources_target_name = "${target_name}_resources"
+  _app_icon_resources_target_name = "${target_name}_app_icon_resources"
 
   if (defined(invoker.manifest_output)) {
     _manifest_output = invoker.manifest_output
@@ -136,6 +137,17 @@
     output = "${_generated_res_background_dir}/values/background_color.xml"
   }
 
+  android_resources(_app_icon_resources_target_name) {
+    create_srcjar = false
+    sources = [
+      "res_app_icon/mipmap-hdpi/app_icon.xml",
+      "res_app_icon/mipmap-mdpi/app_icon.xml",
+      "res_app_icon/mipmap-xhdpi/app_icon.xml",
+      "res_app_icon/mipmap-xxhdpi/app_icon.xml",
+      "res_app_icon/mipmap-xxxhdpi/app_icon.xml",
+    ]
+  }
+
   android_resources(_resources_target_name) {
     create_srcjar = false
     sources = [
@@ -173,10 +185,8 @@
       "res/layout/choose_host_browser_dialog.xml",
       "res/layout/host_browser_list_item.xml",
       "res/mipmap-anydpi-v26/ic_launcher.xml",
-      "res/mipmap-hdpi/app_icon.xml",
       "res/mipmap-hdpi/ic_launcher.xml",
       "res/mipmap-hdpi/maskable_app_icon.xml",
-      "res/mipmap-mdpi/app_icon.xml",
       "res/mipmap-mdpi/ic_launcher.xml",
       "res/mipmap-mdpi/ic_launcher_background.png",
       "res/mipmap-mdpi/ic_launcher_foreground.png",
@@ -195,13 +205,10 @@
       "res/mipmap-nodpi/maskable_splash_icon_xxxhdpi.png",
       "res/mipmap-nodpi/splash_icon_xxhdpi.png",
       "res/mipmap-nodpi/splash_icon_xxxhdpi.png",
-      "res/mipmap-xhdpi/app_icon.xml",
       "res/mipmap-xhdpi/ic_launcher.xml",
       "res/mipmap-xhdpi/maskable_app_icon.xml",
-      "res/mipmap-xxhdpi/app_icon.xml",
       "res/mipmap-xxhdpi/ic_launcher.xml",
       "res/mipmap-xxhdpi/maskable_app_icon.xml",
-      "res/mipmap-xxxhdpi/app_icon.xml",
       "res/mipmap-xxxhdpi/ic_launcher.xml",
       "res/mipmap-xxxhdpi/maskable_app_icon.xml",
       "res/values-hdpi/is_splash_icon_maskable_bool.xml",
@@ -244,7 +251,10 @@
                              "apk_name",
                              "testonly",
                            ])
-    deps = [ ":$_java_with_services_target_name" ]
+    deps = [
+      ":$_app_icon_resources_target_name",
+      ":$_java_with_services_target_name",
+    ]
 
     android_manifest = _manifest_output
     android_manifest_dep = ":$_manifest_target_name"
diff --git a/chrome/android/webapk/shell_apk/current_version/current_version.gni b/chrome/android/webapk/shell_apk/current_version/current_version.gni
index 92feb52..85d57b4 100644
--- a/chrome/android/webapk/shell_apk/current_version/current_version.gni
+++ b/chrome/android/webapk/shell_apk/current_version/current_version.gni
@@ -12,4 +12,4 @@
 # //chrome/android/webapk/shell_apk:webapk is changed. This includes
 # Java files, Android resource files and AndroidManifest.xml. Does not affect
 # Chrome.apk
-current_shell_apk_version = 131
+current_shell_apk_version = 132
diff --git a/chrome/android/webapk/shell_apk/prepare_upload_dir/BUILD.gn b/chrome/android/webapk/shell_apk/prepare_upload_dir/BUILD.gn
index a4bc1ca4..e391ed8 100644
--- a/chrome/android/webapk/shell_apk/prepare_upload_dir/BUILD.gn
+++ b/chrome/android/webapk/shell_apk/prepare_upload_dir/BUILD.gn
@@ -28,6 +28,11 @@
   destination_dir = "${upload_dir}/res"
 }
 
+copy_dir("copy_res_app_icon_to_upload_dir") {
+  source_dir = "//chrome/android/webapk/shell_apk/res_app_icon"
+  destination_dir = "${upload_dir}/res"
+}
+
 copy_dir("copy_res_template_to_upload_dir") {
   source_dir = "//chrome/android/webapk/shell_apk/res_template"
   destination_dir = "${upload_dir}/res"
@@ -63,6 +68,7 @@
   deps = [
     ":copy_extra_files_to_upload_dir",
     ":copy_libs_common_res_splash_to_upload_dir",
+    ":copy_res_app_icon_to_upload_dir",
     ":copy_res_template_to_upload_dir",
     ":copy_res_to_upload_dir",
   ]
diff --git a/chrome/android/webapk/shell_apk/res/layout/choose_host_browser_dialog.xml b/chrome/android/webapk/shell_apk/res/layout/choose_host_browser_dialog.xml
index 2f63747..b9b98994 100644
--- a/chrome/android/webapk/shell_apk/res/layout/choose_host_browser_dialog.xml
+++ b/chrome/android/webapk/shell_apk/res/layout/choose_host_browser_dialog.xml
@@ -13,8 +13,8 @@
         android:id="@+id/desc"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:textSize="@dimen/text_size_large"
-        android:textColor="@color/black_alpha_54"
+        android:textSize="@dimen/webapk_text_size_large"
+        android:textColor="@color/webapk_black_alpha_54"
         android:layout_marginBottom="12dp" />
 
     <ListView
@@ -25,4 +25,4 @@
         android:dividerHeight="0dp"
         android:layout_marginTop="4dp" />
 
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/chrome/android/webapk/shell_apk/res/layout/host_browser_list_item.xml b/chrome/android/webapk/shell_apk/res/layout/host_browser_list_item.xml
index 05f3c61..f9d0726 100644
--- a/chrome/android/webapk/shell_apk/res/layout/host_browser_list_item.xml
+++ b/chrome/android/webapk/shell_apk/res/layout/host_browser_list_item.xml
@@ -25,8 +25,8 @@
         android:layout_gravity="center"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="@color/black_alpha_87"
-        android:textSize="@dimen/text_size_large"
+        android:textColor="@color/webapk_black_alpha_87"
+        android:textSize="@dimen/webapk_text_size_large"
         android:layout_marginTop="8dp"
         android:layout_marginBottom="8dp" />
-</LinearLayout>
\ No newline at end of file
+</LinearLayout>
diff --git a/chrome/android/webapk/shell_apk/res/values/colors.xml b/chrome/android/webapk/shell_apk/res/values/colors.xml
index 6b398373..8df8d0b 100644
--- a/chrome/android/webapk/shell_apk/res/values/colors.xml
+++ b/chrome/android/webapk/shell_apk/res/values/colors.xml
@@ -5,8 +5,8 @@
 
 <resources>
     <!-- Common colors-->
-    <color name="black_alpha_38">#61000000</color>
-    <color name="black_alpha_54">#8A000000</color>
-    <color name="black_alpha_87">#DE000000</color>
+    <color name="webapk_black_alpha_38">#61000000</color>
+    <color name="webapk_black_alpha_54">#8A000000</color>
+    <color name="webapk_black_alpha_87">#DE000000</color>
     <color name="white_adaptive_ic_launcher_background">#FFFFFF</color>
 </resources>
diff --git a/chrome/android/webapk/shell_apk/res/values/dimens.xml b/chrome/android/webapk/shell_apk/res/values/dimens.xml
index 7924682..98429aa7 100644
--- a/chrome/android/webapk/shell_apk/res/values/dimens.xml
+++ b/chrome/android/webapk/shell_apk/res/values/dimens.xml
@@ -6,8 +6,8 @@
 <resources xmlns:tools="http://schemas.android.com/tools">
     <!-- Common text sizes -->
     <dimen name="headline_size_medium">20sp</dimen>
-    <dimen name="text_size_large">16sp</dimen>
-    <dimen name="text_size_medium_dense">12sp</dimen>
+    <dimen name="webapk_text_size_large">16sp</dimen>
+    <dimen name="webapk_text_size_medium_dense">12sp</dimen>
 
     <!-- Refers to https://material.googleplex.com/components/dialogs.html#dialogs-specs. -->
     <dimen name="dialog_content_padding">24dp</dimen>
diff --git a/chrome/android/webapk/shell_apk/res/mipmap-hdpi/app_icon.xml b/chrome/android/webapk/shell_apk/res_app_icon/mipmap-hdpi/app_icon.xml
similarity index 100%
rename from chrome/android/webapk/shell_apk/res/mipmap-hdpi/app_icon.xml
rename to chrome/android/webapk/shell_apk/res_app_icon/mipmap-hdpi/app_icon.xml
diff --git a/chrome/android/webapk/shell_apk/res/mipmap-mdpi/app_icon.xml b/chrome/android/webapk/shell_apk/res_app_icon/mipmap-mdpi/app_icon.xml
similarity index 100%
rename from chrome/android/webapk/shell_apk/res/mipmap-mdpi/app_icon.xml
rename to chrome/android/webapk/shell_apk/res_app_icon/mipmap-mdpi/app_icon.xml
diff --git a/chrome/android/webapk/shell_apk/res/mipmap-xhdpi/app_icon.xml b/chrome/android/webapk/shell_apk/res_app_icon/mipmap-xhdpi/app_icon.xml
similarity index 100%
rename from chrome/android/webapk/shell_apk/res/mipmap-xhdpi/app_icon.xml
rename to chrome/android/webapk/shell_apk/res_app_icon/mipmap-xhdpi/app_icon.xml
diff --git a/chrome/android/webapk/shell_apk/res/mipmap-xxhdpi/app_icon.xml b/chrome/android/webapk/shell_apk/res_app_icon/mipmap-xxhdpi/app_icon.xml
similarity index 100%
rename from chrome/android/webapk/shell_apk/res/mipmap-xxhdpi/app_icon.xml
rename to chrome/android/webapk/shell_apk/res_app_icon/mipmap-xxhdpi/app_icon.xml
diff --git a/chrome/android/webapk/shell_apk/res/mipmap-xxxhdpi/app_icon.xml b/chrome/android/webapk/shell_apk/res_app_icon/mipmap-xxxhdpi/app_icon.xml
similarity index 100%
rename from chrome/android/webapk/shell_apk/res/mipmap-xxxhdpi/app_icon.xml
rename to chrome/android/webapk/shell_apk/res_app_icon/mipmap-xxxhdpi/app_icon.xml
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/ChooseHostBrowserDialog.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/ChooseHostBrowserDialog.java
index 44226d2..f4ee46e 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/ChooseHostBrowserDialog.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/ChooseHostBrowserDialog.java
@@ -212,19 +212,20 @@
             name.setEnabled(item.enable());
             if (item.enable()) {
                 name.setText(item.getApplicationName());
-                name.setTextColor(WebApkUtils.getColor(res, R.color.black_alpha_87));
+                name.setTextColor(WebApkUtils.getColor(res, R.color.webapk_black_alpha_87));
                 icon.setAlpha(SUPPORTED_ICON_OPACITY);
             } else {
                 String text = mContext.getString(R.string.host_browser_item_not_supporting_webapks,
                         item.getApplicationName());
                 SpannableString spannableName = new SpannableString(text);
-                float descriptionProportion = res.getDimension(R.dimen.text_size_medium_dense)
-                        / res.getDimension(R.dimen.text_size_large);
+                float descriptionProportion =
+                        res.getDimension(R.dimen.webapk_text_size_medium_dense)
+                        / res.getDimension(R.dimen.webapk_text_size_large);
                 spannableName.setSpan(new RelativeSizeSpan(descriptionProportion),
                         item.getApplicationName().length() + 1, spannableName.length(), 0);
                 name.setText(spannableName);
                 name.setSingleLine(false);
-                name.setTextColor(WebApkUtils.getColor(res, R.color.black_alpha_38));
+                name.setTextColor(WebApkUtils.getColor(res, R.color.webapk_black_alpha_38));
                 icon.setAlpha(UNSUPPORTED_ICON_OPACITY);
             }
             icon.setImageDrawable(item.getApplicationIcon());
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java
index 4dd530c..155b0fb6 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/WebApkUtils.java
@@ -162,7 +162,7 @@
     public static void applyAlertDialogContentStyle(
             Context context, View contentView, TextView titleView) {
         Resources res = context.getResources();
-        titleView.setTextColor(getColor(res, R.color.black_alpha_87));
+        titleView.setTextColor(getColor(res, R.color.webapk_black_alpha_87));
         titleView.setTextSize(
                 TypedValue.COMPLEX_UNIT_PX, res.getDimension(R.dimen.headline_size_medium));
         int dialogContentPadding = res.getDimensionPixelSize(R.dimen.dialog_content_padding);
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index d09ace6b..360b7ab 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -210,6 +210,7 @@
 #endif  // OS_CHROMEOS
 
 #if defined(OS_MAC)
+#include "base/mac/mac_util.h"
 #include "chrome/browser/ui/browser_dialogs.h"
 #endif  // OS_MAC
 
@@ -1430,11 +1431,9 @@
         {features::kPromoBrowserCommandIdParam, "1"}};
 const FeatureEntry::FeatureVariation kPromoBrowserCommandsVariations[] = {
     {"- Unknown Command", kPromoBrowserCommandUnknownCommandParam,
-     base::size(kPromoBrowserCommandUnknownCommandParam),
-     "t4237555" /* variation_id */},
+     base::size(kPromoBrowserCommandUnknownCommandParam), nullptr},
     {"- Open Safety Check", kPromoBrowserCommandOpenSafetyCheckCommandParam,
-     base::size(kPromoBrowserCommandOpenSafetyCheckCommandParam),
-     "t4237555" /* variation_id */}};
+     base::size(kPromoBrowserCommandOpenSafetyCheckCommandParam), nullptr}};
 
 #if defined(OS_ANDROID)
 const FeatureEntry::FeatureParam kTranslateForceTriggerOnEnglishHeuristic[] = {
@@ -5372,7 +5371,7 @@
      FEATURE_VALUE_TYPE(features::kFormControlsRefresh)},
 
     {"color-picker-eye-dropper", flag_descriptions::kColorPickerEyeDropperName,
-     flag_descriptions::kColorPickerEyeDropperDescription, kOsWin,
+     flag_descriptions::kColorPickerEyeDropperDescription, kOsWin | kOsMac,
      FEATURE_VALUE_TYPE(features::kEyeDropper)},
 
 #if defined(OS_CHROMEOS)
@@ -6534,6 +6533,15 @@
   }
 #endif  // OS_ANDROID
 
+#if defined(OS_MAC)
+  // The Eye Dropper relies on the NSColorSampler API, which is available
+  // starting with macOS 10.15.
+  if (!strcmp("color-picker-eye-dropper", entry.internal_name) &&
+      !base::mac::IsAtLeastOS10_15()) {
+    return true;
+  }
+#endif  // OS_MAC
+
   if (flags::IsFlagExpired(storage, entry.internal_name))
     return true;
 
diff --git a/chrome/browser/apps/app_service/app_service_proxy_factory.cc b/chrome/browser/apps/app_service/app_service_proxy_factory.cc
index e1dbd0a1..4f8e086 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_factory.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_factory.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
 
+#include "base/debug/dump_without_crashing.h"
 #include "base/feature_list.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
@@ -50,7 +51,17 @@
 
 // static
 AppServiceProxy* AppServiceProxyFactory::GetForProfile(Profile* profile) {
-  DCHECK(IsAppServiceAvailableForProfile(profile));
+  // TODO(https://crbug.com/1122463): remove this and convert back to a DCHECK
+  // once we have audited and removed code paths that call here with a profile
+  // that doesn't have an App Service.
+  if (!IsAppServiceAvailableForProfile(profile)) {
+    DVLOG(1) << "Called AppServiceProxyFactory::GetForProfile() on a profile "
+                "which does not contain an AppServiceProxy. Please check "
+                "whether this is appropriate as you may be leaking information "
+                "out of this profile. Returning the AppServiceProxy attached "
+                "to the parent profile instead.";
+    base::debug::DumpWithoutCrashing();
+  }
 
   auto* proxy = static_cast<AppServiceProxy*>(
       AppServiceProxyFactory::GetInstance()->GetServiceForBrowserContext(
@@ -99,13 +110,16 @@
   }
 
   // We must have a proxy in guest mode to ensure default extension-based apps
-  // are served. Otherwise, don't create the app service for incognito profiles.
+  // are served.
   if (profile->IsGuestSession()) {
     return chrome::GetBrowserContextOwnInstanceInIncognito(context);
   }
 #endif  // OS_CHROMEOS
 
-  return BrowserContextKeyedServiceFactory::GetBrowserContextToUse(context);
+  // TODO(https://crbug.com/1122463): replace this with
+  // BrowserContextKeyedServiceFactory::GetBrowserContextToUse(context) once
+  // all non-guest incognito accesses have been removed.
+  return chrome::GetBrowserContextRedirectedInIncognito(context);
 }
 
 bool AppServiceProxyFactory::ServiceIsCreatedWithBrowserContext() const {
diff --git a/chrome/browser/apps/app_service/app_service_proxy_unittest.cc b/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
index 22cfb83..ef2c893 100644
--- a/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
+++ b/chrome/browser/apps/app_service/app_service_proxy_unittest.cc
@@ -2,11 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <memory>
 #include <utility>
 #include <vector>
 
 #include "base/callback.h"
 #include "chrome/browser/apps/app_service/app_service_proxy.h"
+#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/test/browser_task_environment.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/image/image_skia_rep.h"
@@ -81,6 +85,8 @@
   int NumOuterFinishedCallbacks() { return num_outer_finished_callbacks_; }
 
   int num_outer_finished_callbacks_ = 0;
+
+  content::BrowserTaskEnvironment task_environment_;
 };
 
 TEST_F(AppServiceProxyTest, IconCache) {
@@ -181,3 +187,30 @@
   EXPECT_EQ(3, fake.NumInnerFinishedCallbacks());
   EXPECT_EQ(6, NumOuterFinishedCallbacks());
 }
+
+TEST_F(AppServiceProxyTest, ProxyAccessPerProfile) {
+  TestingProfile::Builder profile_builder;
+
+  // We expect an App Service in a regular profile.
+  auto profile = profile_builder.Build();
+  auto* proxy = apps::AppServiceProxyFactory::GetForProfile(profile.get());
+  EXPECT_TRUE(proxy);
+
+  // We expect the same App Service in the incognito profile branched from that
+  // regular profile.
+  // TODO(https://crbug.com/1122463): this should be nullptr once we address all
+  // incognito access to the App Service.
+  TestingProfile::Builder incognito_builder;
+  auto* incognito_proxy = apps::AppServiceProxyFactory::GetForProfile(
+      incognito_builder.BuildIncognito(profile.get()));
+  EXPECT_EQ(proxy, incognito_proxy);
+
+  // We expect a different App Service in the Guest Session profile.
+  TestingProfile::Builder guest_builder;
+  guest_builder.SetGuestSession();
+  auto guest_profile = guest_builder.Build();
+  auto* guest_proxy =
+      apps::AppServiceProxyFactory::GetForProfile(guest_profile.get());
+  EXPECT_TRUE(guest_proxy);
+  EXPECT_NE(guest_proxy, proxy);
+}
diff --git a/chrome/browser/chromeos/crosapi/browser_loader.cc b/chrome/browser/chromeos/crosapi/browser_loader.cc
index efacabf6..393e8fd 100644
--- a/chrome/browser/chromeos/crosapi/browser_loader.cc
+++ b/chrome/browser/chromeos/crosapi/browser_loader.cc
@@ -15,6 +15,7 @@
 #include "base/task/thread_pool.h"
 #include "chrome/browser/chromeos/crosapi/browser_util.h"
 #include "chromeos/constants/chromeos_switches.h"
+#include "chromeos/cryptohome/system_salt_getter.h"
 
 namespace crosapi {
 
@@ -82,7 +83,7 @@
       FROM_HERE, {base::MayBlock()},
       base::BindOnce(&CheckInstalledAndMaybeRemoveUserDirectory,
                      component_manager_),
-      base::BindOnce(&BrowserLoader::UnloadAfterCleanUp,
+      base::BindOnce(&BrowserLoader::OnCheckInstalled,
                      weak_factory_.GetWeakPtr()));
 }
 
@@ -101,9 +102,22 @@
   std::move(callback).Run(success ? path : base::FilePath());
 }
 
-void BrowserLoader::UnloadAfterCleanUp(bool was_installed) {
-  if (was_installed)
-    component_manager_->Unload(kLacrosComponentName);
+void BrowserLoader::OnCheckInstalled(bool was_installed) {
+  if (!was_installed)
+    return;
+
+  // Workaround for login crash when the user un-sets the LacrosSupport flag.
+  // CrOSComponentManager::Unload() calls into code in MetadataTable that
+  // assumes that system salt is available. This isn't always true when chrome
+  // restarts to apply non-owner flags. It's hard to make MetadataTable async.
+  // Ensure salt is available before unloading. https://crbug.com/1122674
+  chromeos::SystemSaltGetter::Get()->GetSystemSalt(base::BindOnce(
+      &BrowserLoader::UnloadAfterCleanUp, weak_factory_.GetWeakPtr()));
+}
+
+void BrowserLoader::UnloadAfterCleanUp(const std::string& ignored_salt) {
+  CHECK(chromeos::SystemSaltGetter::Get()->GetRawSalt());
+  component_manager_->Unload(kLacrosComponentName);
 }
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/browser_loader.h b/chrome/browser/chromeos/crosapi/browser_loader.h
index 69c8598..d06d065 100644
--- a/chrome/browser/chromeos/crosapi/browser_loader.h
+++ b/chrome/browser/chromeos/crosapi/browser_loader.h
@@ -42,9 +42,12 @@
                       component_updater::CrOSComponentManager::Error error,
                       const base::FilePath& path);
 
-  // Unloading hops threads. This is called after possible user directory
-  // removal.
-  void UnloadAfterCleanUp(bool was_installed);
+  // Unloading hops threads. This is called after we check whether Lacros was
+  // installed and maybe clean up the user directory.
+  void OnCheckInstalled(bool was_installed);
+
+  // Unloads the component. Called after system salt is available.
+  void UnloadAfterCleanUp(const std::string& ignored_salt);
 
   // May be null in tests.
   scoped_refptr<component_updater::CrOSComponentManager> component_manager_;
diff --git a/chrome/browser/chromeos/input_method/ui/candidate_window_view.cc b/chrome/browser/chromeos/input_method/ui/candidate_window_view.cc
index d8ff2644..ed91ca6 100644
--- a/chrome/browser/chromeos/input_method/ui/candidate_window_view.cc
+++ b/chrome/browser/chromeos/input_method/ui/candidate_window_view.cc
@@ -152,6 +152,15 @@
   set_parent_window(parent);
   set_margins(gfx::Insets());
 
+  // When BubbleDialogDelegateView creates its frame view it will create a
+  // bubble border with a non-zero corner radius by default.
+  // This class replaces the frame view's bubble border later on with its own
+  // |CandidateWindowBorder| with a radius of 0.
+  // We want to disable the use of round corners here to ensure that the radius
+  // of the frame view created by the BubbleDialogDelegateView is consistent
+  // with what CandidateWindowView expects.
+  set_use_round_corners(false);
+
   SetBorder(views::CreateSolidBorder(
       1, GetNativeTheme()->GetSystemColor(
              ui::NativeTheme::kColorId_MenuBorderColor)));
diff --git a/chrome/browser/extensions/execute_script_apitest.cc b/chrome/browser/extensions/execute_script_apitest.cc
index 6526f7b..a3390896 100644
--- a/chrome/browser/extensions/execute_script_apitest.cc
+++ b/chrome/browser/extensions/execute_script_apitest.cc
@@ -32,6 +32,8 @@
                              public testing::WithParamInterface<ContextType> {
  protected:
   ExecuteScriptApiTest() {
+    // Service Workers are currently only available on certain channels, so set
+    // the channel for those tests.
     if (GetParam() == ContextType::kServiceWorker)
       current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
   }
@@ -87,14 +89,7 @@
   ASSERT_TRUE(RunTest("executescript/frame_id")) << message_;
 }
 
-// Fails often on Windows.
-// http://crbug.com/174715
-#if defined(OS_WIN)
-#define MAYBE_ExecuteScriptPermissions DISABLED_ExecuteScriptPermissions
-#else
-#define MAYBE_ExecuteScriptPermissions ExecuteScriptPermissions
-#endif  // defined(OS_WIN)
-IN_PROC_BROWSER_TEST_P(ExecuteScriptApiTest, MAYBE_ExecuteScriptPermissions) {
+IN_PROC_BROWSER_TEST_P(ExecuteScriptApiTest, ExecuteScriptPermissions) {
   // TODO(https://crbug.com/1115182): Flaky for SW-based extensions.
   if (GetParam() == ContextType::kServiceWorker)
     return;
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 93ae78b..f36faa5 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -231,6 +231,7 @@
     &net::features::kCookiesWithoutSameSiteMustBeSecure,
     &paint_preview::kPaintPreviewDemo,
     &paint_preview::kPaintPreviewShowOnStartup,
+    &language::kDetailedLanguageSettings,
     &language::kExplicitLanguageAsk,
     &ntp_snippets::kArticleSuggestionsFeature,
     &offline_pages::kOfflineIndicatorFeature,
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index abd6c601..bf47395 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -280,6 +280,7 @@
             "DarkenWebsitesCheckboxInThemesSetting";
     public static final String DECOUPLE_SYNC_FROM_ANDROID_MASTER_SYNC =
             "DecoupleSyncFromAndroidMasterSync";
+    public static final String DETAILED_LANGUAGE_SETTINGS = "DetailedLanguageSettings";
     public static final String DIRECT_ACTIONS = "DirectActions";
     public static final String DNS_OVER_HTTPS = "DnsOverHttps";
     public static final String DOWNLOAD_FILE_PROVIDER = "DownloadFileProvider";
diff --git a/chrome/browser/metrics/cros_healthd_metrics_provider.cc b/chrome/browser/metrics/cros_healthd_metrics_provider.cc
index 80ca54eb..cc76bfe0 100644
--- a/chrome/browser/metrics/cros_healthd_metrics_provider.cc
+++ b/chrome/browser/metrics/cros_healthd_metrics_provider.cc
@@ -13,6 +13,7 @@
 #include "base/strings/string_util.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "base/time/time.h"
+#include "chrome/common/chrome_features.h"
 #include "chromeos/services/cros_healthd/public/cpp/service_connection.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd.mojom.h"
 #include "chromeos/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
@@ -43,6 +44,12 @@
   init_callback_ = std::move(done_callback);
   initialized_ = false;
 
+  if (!base::FeatureList::IsEnabled(::features::kUmaStorageDimensions)) {
+    DVLOG(1) << "cros_healthd metrics provider is not enabled";
+    std::move(init_callback_).Run();
+    return;
+  }
+
   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
       FROM_HERE,
       base::BindOnce(&CrosHealthdMetricsProvider::OnProbeTimeout,
@@ -177,6 +184,8 @@
 
 void CrosHealthdMetricsProvider::ProvideSystemProfileMetrics(
     metrics::SystemProfileProto* system_profile_proto) {
+  if (!initialized_)
+    return;
   auto* mutable_hardware_proto = system_profile_proto->mutable_hardware();
   mutable_hardware_proto->clear_internal_storage_devices();
 
diff --git a/chrome/browser/metrics/cros_healthd_metrics_provider_unittest.cc b/chrome/browser/metrics/cros_healthd_metrics_provider_unittest.cc
index fe704f2..e06c42f 100644
--- a/chrome/browser/metrics/cros_healthd_metrics_provider_unittest.cc
+++ b/chrome/browser/metrics/cros_healthd_metrics_provider_unittest.cc
@@ -10,8 +10,10 @@
 
 #include "base/bind.h"
 #include "base/test/bind_test_util.h"
+#include "base/test/scoped_feature_list.h"
 #include "base/test/task_environment.h"
 #include "base/time/time.h"
+#include "chrome/common/chrome_features.h"
 #include "chromeos/dbus/cros_healthd/cros_healthd_client.h"
 #include "chromeos/dbus/cros_healthd/fake_cros_healthd_client.h"
 #include "chromeos/services/cros_healthd/public/cpp/service_connection.h"
@@ -22,6 +24,10 @@
 
 class CrosHealthdMetricsProviderTest : public testing::Test {
  public:
+  CrosHealthdMetricsProviderTest() {
+    scoped_feature_list_.InitAndEnableFeature(features::kUmaStorageDimensions);
+  }
+
   void SetUp() override { chromeos::CrosHealthdClient::InitializeFake(); }
 
   void TearDown() override {
@@ -34,6 +40,9 @@
 
   base::test::TaskEnvironment task_environment_{
       base::test::TaskEnvironment::TimeSource::MOCK_TIME};
+
+ protected:
+  base::test::ScopedFeatureList scoped_feature_list_;
 };
 
 TEST_F(CrosHealthdMetricsProviderTest, EndToEnd) {
@@ -123,3 +132,17 @@
   run_loop.Run();
   ASSERT_FALSE(provider.IsInitialized());
 }
+
+TEST_F(CrosHealthdMetricsProviderTest, EndToEndNoFeature) {
+  scoped_feature_list_.Reset();
+  scoped_feature_list_.Init();
+
+  base::RunLoop run_loop;
+  CrosHealthdMetricsProvider provider;
+  provider.AsyncInit(base::BindOnce(
+      [](base::OnceClosure callback) { std::move(callback).Run(); },
+      run_loop.QuitClosure()));
+
+  run_loop.Run();
+  ASSERT_FALSE(provider.IsInitialized());
+}
diff --git a/chrome/browser/payments/secure_payment_confirmation_browsertest.cc b/chrome/browser/payments/secure_payment_confirmation_browsertest.cc
index b1f0632..99f2e48 100644
--- a/chrome/browser/payments/secure_payment_confirmation_browsertest.cc
+++ b/chrome/browser/payments/secure_payment_confirmation_browsertest.cc
@@ -9,10 +9,13 @@
 #include <vector>
 
 #include "base/command_line.h"
+#include "base/files/file_util.h"
 #include "base/memory/ref_counted.h"
+#include "base/path_service.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string16.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/thread_restrictions.h"
 #include "build/build_config.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/web_data_service_factory.h"
@@ -46,6 +49,22 @@
   return base::StringPrintf("getStatusForMethodData(%s)", kTestMethodData);
 }
 
+std::vector<uint8_t> GetEncodedIcon(const std::string& icon_file_name) {
+  base::FilePath base_path;
+  CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &base_path));
+  std::string icon_as_string;
+  base::FilePath icon_file_path =
+      base_path.AppendASCII("components/test/data/payments")
+          .AppendASCII(icon_file_name);
+  {
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    CHECK(base::PathExists(icon_file_path));
+    CHECK(base::ReadFileToString(icon_file_path, &icon_as_string));
+  }
+
+  return std::vector<uint8_t>(icon_as_string.begin(), icon_as_string.end());
+}
+
 #if !defined(OS_ANDROID)
 std::string getPaymentCreationOptions(const std::string& icon_url) {
   return base::StrCat(
@@ -138,7 +157,7 @@
   test_controller()->SetHasAuthenticator(true);
   NavigateTo("a.com", "/payment_handler_status.html");
   std::vector<uint8_t> credential_id = {'c', 'r', 'e', 'd'};
-  std::vector<uint8_t> icon = {0, 1, 2, 3};
+  std::vector<uint8_t> icon = GetEncodedIcon("icon.png");
   WebDataServiceFactory::GetPaymentManifestWebDataForProfile(
       Profile::FromBrowserContext(GetActiveWebContents()->GetBrowserContext()),
       ServiceAccessType::EXPLICIT_ACCESS)
diff --git a/chrome/browser/pdf/pdf_extension_test.cc b/chrome/browser/pdf/pdf_extension_test.cc
index 8782ac43..07a8866 100644
--- a/chrome/browser/pdf/pdf_extension_test.cc
+++ b/chrome/browser/pdf/pdf_extension_test.cc
@@ -759,9 +759,9 @@
                          PDFExtensionLoadTest,
                          testing::Range(0, kNumberLoadTestParts));
 
-class PDFExtensionJSTest : public PDFExtensionTest {
+class PDFExtensionJSTestBase : public PDFExtensionTest {
  public:
-  ~PDFExtensionJSTest() override = default;
+  ~PDFExtensionJSTestBase() override = default;
 
  protected:
   void RunTestsInJsModule(const std::string& filename,
@@ -809,104 +809,137 @@
   }
 };
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Basic) {
+class PDFExtensionJSUpdatesDisabledTest : public PDFExtensionJSTestBase {
+ public:
+  ~PDFExtensionJSUpdatesDisabledTest() override = default;
+
+ protected:
+  const std::vector<base::Feature> GetDisabledFeatures() const override {
+    return {chrome_pdf::features::kPDFViewerUpdate};
+  }
+};
+
+// Zoom toolbar doesn't exist and the top toolbar is sticky with the new PDF
+// viewer updates, so run this test only with the updates disabled.
+IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesDisabledTest, ToolbarManager) {
+  RunTestsInJsModule("toolbar_manager_test.js", "test.pdf");
+}
+
+class PDFExtensionJSTest : public PDFExtensionJSTestBase,
+                           public testing::WithParamInterface<bool> {
+ public:
+  ~PDFExtensionJSTest() override = default;
+
+ protected:
+  const std::vector<base::Feature> GetEnabledFeatures() const override {
+    if (GetParam()) {
+      return {chrome_pdf::features::kPDFViewerUpdate};
+    }
+    return {};
+  }
+
+  const std::vector<base::Feature> GetDisabledFeatures() const override {
+    if (GetParam()) {
+      return {};
+    }
+    return {chrome_pdf::features::kPDFViewerUpdate};
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Basic) {
   RunTestsInJsModule("basic_test.js", "test.pdf");
 
   // Ensure it loaded in a PPAPI process.
   EXPECT_EQ(1, CountPDFProcesses());
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, BasicPlugin) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, BasicPlugin) {
   RunTestsInJsModule("basic_plugin_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Viewport) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Viewport) {
   RunTestsInJsModule("viewport_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Layout3) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Layout3) {
   RunTestsInJsModule("layout_test.js", "test-layout3.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Layout4) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Layout4) {
   RunTestsInJsModule("layout_test.js", "test-layout4.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Bookmark) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Bookmark) {
   RunTestsInJsModule("bookmarks_test.js", "test-bookmarks-with-zoom.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Navigator) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Navigator) {
   RunTestsInJsModule("navigator_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ParamsParser) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ParamsParser) {
   RunTestsInJsModule("params_parser_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ZoomManager) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ZoomManager) {
   RunTestsInJsModule("zoom_manager_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, GestureDetector) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, GestureDetector) {
   RunTestsInJsModule("gesture_detector_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, TouchHandling) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, TouchHandling) {
   RunTestsInJsModule("touch_handling_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Elements) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Elements) {
   // Although this test file does not require a PDF to be loaded, loading the
   // elements without loading a PDF is difficult.
   RunTestsInJsModule("material_elements_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, DownloadControls) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, DownloadControls) {
   // Although this test file does not require a PDF to be loaded, loading the
   // elements without loading a PDF is difficult.
   RunTestsInJsModule("download_controls_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ViewerPdfToolbarNew) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ViewerPdfToolbarNew) {
   // Although this test file does not require a PDF to be loaded, loading the
   // elements without loading a PDF is difficult.
   RunTestsInJsModule("viewer_pdf_toolbar_new_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ViewerPdfSidenav) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ViewerPdfSidenav) {
   // Although this test file does not require a PDF to be loaded, loading the
   // elements without loading a PDF is difficult.
   RunTestsInJsModule("viewer_pdf_sidenav_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ViewerThumbnailBar) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ViewerThumbnailBar) {
   // Although this test file does not require a PDF to be loaded, loading the
   // elements without loading a PDF is difficult.
   RunTestsInJsModule("viewer_thumbnail_bar_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ToolbarManager) {
-  RunTestsInJsModule("toolbar_manager_test.js", "test.pdf");
-}
-
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Title) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Title) {
   RunTestsInJsModule("title_test.js", "test-title.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, WhitespaceTitle) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, WhitespaceTitle) {
   RunTestsInJsModule("whitespace_title_test.js", "test-whitespace-title.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, PageChange) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, PageChange) {
   RunTestsInJsModule("page_change_test.js", "test-bookmarks.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Metrics) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Metrics) {
   RunTestsInJsModule("metrics_test.js", "test.pdf");
 }
 
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, ArrayBufferAllocator) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ArrayBufferAllocator) {
   // Run several times to see if there are issues with unloading.
   RunTestsInJsModule("beep_test.js", "array_buffer.pdf");
   RunTestsInJsModule("beep_test.js", "array_buffer.pdf");
@@ -916,12 +949,12 @@
 // Test that if the plugin tries to load a URL that redirects then it will fail
 // to load. This is to avoid the source origin of the document changing during
 // the redirect, which can have security implications. https://crbug.com/653749.
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, RedirectsFailInPlugin) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, RedirectsFailInPlugin) {
   RunTestsInJsModule("redirects_fail_test.js", "test.pdf");
 }
 
 #if defined(OS_CHROMEOS)
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, Printing) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Printing) {
   RunTestsInJsModule("printing_icon_test.js", "test.pdf");
 }
 
@@ -932,13 +965,15 @@
 #else
 #define MAYBE_AnnotationsFeatureEnabled AnnotationsFeatureEnabled
 #endif
-IN_PROC_BROWSER_TEST_F(PDFExtensionJSTest, MAYBE_AnnotationsFeatureEnabled) {
+IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, MAYBE_AnnotationsFeatureEnabled) {
   RunTestsInJsModule("annotations_feature_enabled_test.js", "test.pdf");
 }
 #endif  // defined(OS_CHROMEOS)
 
+INSTANTIATE_TEST_SUITE_P(/* no prefix */, PDFExtensionJSTest, testing::Bool());
+
 class PDFExtensionContentSettingJSTest
-    : public PDFExtensionJSTest,
+    : public PDFExtensionJSTestBase,
       public testing::WithParamInterface<bool> {
  public:
   ~PDFExtensionContentSettingJSTest() override = default;
@@ -1017,7 +1052,7 @@
 
 // Service worker tests are regression tests for
 // https://crbug.com/916514.
-class PDFExtensionServiceWorkerJSTest : public PDFExtensionJSTest {
+class PDFExtensionServiceWorkerJSTest : public PDFExtensionJSTestBase {
  public:
   ~PDFExtensionServiceWorkerJSTest() override = default;
 
diff --git a/chrome/browser/resources/chromeos/accessibility/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
index 1998283..db57d5a 100644
--- a/chrome/browser/resources/chromeos/accessibility/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/BUILD.gn
@@ -148,7 +148,6 @@
     "common/rect_util_unittest.js",
     "select_to_speak/node_utils_unittest.js",
     "select_to_speak/paragraph_utils_unittest.js",
-    "select_to_speak/rect_utils_unittest.js",
     "select_to_speak/select_to_speak_unittest.js",
     "select_to_speak/word_utils_unittest.js",
   ]
@@ -156,7 +155,6 @@
     "braille_ime/braille_ime.js",
     "common/rect_util.js",
     "select_to_speak/paragraph_utils.js",
-    "select_to_speak/rect_utils.js",
     "select_to_speak/select_to_speak.js",
     "select_to_speak/test_support.js",
     "select_to_speak/word_utils.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/common/rect_util.js b/chrome/browser/resources/chromeos/accessibility/common/rect_util.js
index ed3e63db..b3e901d 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/rect_util.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/rect_util.js
@@ -14,6 +14,24 @@
   ZERO_RECT: {top: 0, left: 0, width: 0, height: 0},
 
   /**
+   * Return the rect that encloses two points.
+   * @param {number} x1 The first x coordinate.
+   * @param {number} y1 The first y coordinate.
+   * @param {number} x2 The second x coordinate.
+   * @param {number} y2 The second x coordinate.
+   * @return {!ScreenRect}
+   */
+  rectFromPoints: (x1, y1, x2, y2) => {
+    const left = Math.min(x1, x2);
+    const right = Math.max(x1, x2);
+    const top = Math.min(y1, y2);
+    const bottom = Math.max(y1, y2);
+    const width = right - left;
+    const height = bottom - top;
+    return {left, top, width, height};
+  },
+
+  /**
    * @param {!ScreenRect} rect1
    * @param {!ScreenRect} rect2
    * @return {boolean}
@@ -92,10 +110,7 @@
       return outer;
     }
 
-    if (outer.left >= RectUtil.right(subtrahend) ||
-        RectUtil.right(outer) <= subtrahend.left ||
-        outer.top >= RectUtil.bottom(subtrahend) ||
-        RectUtil.bottom(outer) <= subtrahend.top) {
+    if (!RectUtil.overlaps(outer, subtrahend)) {
       // If the rectangles do not overlap, return the outer rect.
       return outer;
     }
@@ -241,6 +256,19 @@
   },
 
   /**
+   * Returns true if |rect1| and |rect2| overlap.
+   * @param {!ScreenRect} rect1
+   * @param {!ScreenRect} rect2
+   * @return {boolean} True if the rects overlap.
+   */
+  overlaps: (rect1, rect2) => {
+    return rect1.left < RectUtil.right(rect2) &&
+        rect2.left < RectUtil.right(rect1) &&
+        rect1.top < RectUtil.bottom(rect2) &&
+        rect2.top < RectUtil.bottom(rect1);
+  },
+
+  /**
    * Finds the right edge of a rect.
    * @param {!ScreenRect} rect
    * @return {number}
diff --git a/chrome/browser/resources/chromeos/accessibility/common/rect_util_unittest.js b/chrome/browser/resources/chromeos/accessibility/common/rect_util_unittest.js
index 1bbfa19..202a273 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/rect_util_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/rect_util_unittest.js
@@ -363,3 +363,36 @@
       RectUtil.equal(expected, RectUtil.intersection(rect2, rect1)),
       'Intersection should be symmetric');
 });
+
+TEST_F('RectUtilUnitTest', 'Overlaps', function() {
+  var rect1 = {left: 0, top: 0, width: 100, height: 100};
+  var rect2 = {left: 80, top: 0, width: 100, height: 20};
+  var rect3 = {left: 0, top: 80, width: 20, height: 100};
+
+  assertTrue(RectUtil.overlaps(rect1, rect1));
+  assertTrue(RectUtil.overlaps(rect2, rect2));
+  assertTrue(RectUtil.overlaps(rect3, rect3));
+  assertTrue(RectUtil.overlaps(rect1, rect2));
+  assertTrue(RectUtil.overlaps(rect1, rect3));
+  assertFalse(RectUtil.overlaps(rect2, rect3));
+});
+
+TEST_F('RectUtilUnitTest', 'RectFromPoints', function() {
+  var rect = {left: 10, top: 20, width: 50, height: 60};
+
+  assertNotEquals(
+      JSON.stringify(rect),
+      JSON.stringify(RectUtil.rectFromPoints(0, 0, 10, 10)));
+  assertEquals(
+      JSON.stringify(rect),
+      JSON.stringify(RectUtil.rectFromPoints(10, 20, 60, 80)));
+  assertEquals(
+      JSON.stringify(rect),
+      JSON.stringify(RectUtil.rectFromPoints(60, 20, 10, 80)));
+  assertEquals(
+      JSON.stringify(rect),
+      JSON.stringify(RectUtil.rectFromPoints(10, 80, 60, 20)));
+  assertEquals(
+      JSON.stringify(rect),
+      JSON.stringify(RectUtil.rectFromPoints(60, 80, 10, 20)));
+});
diff --git a/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler.js b/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler.js
index d60d71b..c2bb7fd9 100644
--- a/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/common/repeated_event_handler.js
@@ -52,9 +52,18 @@
     /** @private {boolean} */
     this.capture_ = options.capture || false;
 
+    /** @private {boolean} */
+    this.listening_ = false;
+
     /** @private {!function(!chrome.automation.AutomationEvent)} */
     this.handler_ = this.onEvent_.bind(this);
 
+    this.startListening();
+  }
+
+  /** Starts listening or handling events. */
+  startListening() {
+    this.listening_ = true;
     for (const node of this.nodes_) {
       node.addEventListener(this.type_, this.handler_, this.capture_);
     }
@@ -62,6 +71,7 @@
 
   /** Stops listening or handling future events. */
   stopListening() {
+    this.listening_ = false;
     for (const node of this.nodes_) {
       node.removeEventListener(this.type_, this.handler_, this.capture_);
     }
@@ -78,7 +88,7 @@
 
   /** @private */
   handleEvent_() {
-    if (this.eventStack_.length === 0) {
+    if (!this.listening_ || this.eventStack_.length === 0) {
       return;
     }
 
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
index bc257dc..f429300 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/BUILD.gn
@@ -34,7 +34,6 @@
     "options.html",
     "paragraph_utils.js",
     "prefs_manager.js",
-    "rect_utils.js",
     "select_to_speak-2x.svg",
     "select_to_speak.js",
     "select_to_speak_gdocs_script.js",
@@ -111,7 +110,6 @@
     ":node_utils",
     ":paragraph_utils",
     ":prefs_manager",
-    ":rect_utils",
     ":select_to_speak",
     ":select_to_speak_options",
     ":word_utils",
@@ -119,6 +117,7 @@
     "../common:automation_util",
     "../common:closure_shim",
     "../common:constants",
+    "../common:rect_util",
     "../common:tree_walker",
   ]
 }
@@ -130,10 +129,10 @@
     ":node_utils",
     ":paragraph_utils",
     ":prefs_manager",
-    ":rect_utils",
     ":word_utils",
     "../common:automation_util",
     "../common:constants",
+    "../common:rect_util",
   ]
   externs_list = [
     "$externs_path/accessibility_private.js",
@@ -158,7 +157,7 @@
 js_library("node_utils") {
   deps = [
     ":paragraph_utils",
-    ":rect_utils",
+    "../common:rect_util",
   ]
   externs_list = [ "$externs_path/automation.js" ]
 }
@@ -177,10 +176,7 @@
 }
 
 js_library("input_handler") {
-  deps = [ ":rect_utils" ]
-}
-
-js_library("rect_utils") {
+  deps = [ "../common:rect_util" ]
 }
 
 js_library("prefs_manager") {
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js
index 41426b4..ecb14ec 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/input_handler.js
@@ -92,7 +92,7 @@
         return false;
       }
 
-      var rect = RectUtils.rectFromPoints(
+      var rect = RectUtil.rectFromPoints(
           this.mouseStart_.x, this.mouseStart_.y, evt.screenX, evt.screenY);
       this.callbacks_.onSelectionChanged(rect);
       return false;
@@ -179,7 +179,7 @@
    * @public
    */
   getMouseRect() {
-    return RectUtils.rectFromPoints(
+    return RectUtil.rectFromPoints(
         this.mouseStart_.x, this.mouseStart_.y, this.mouseEnd_.x,
         this.mouseEnd_.y);
   }
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js
index 1d24339..17321d88 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils.js
@@ -168,7 +168,7 @@
       return false;
     }
 
-    if (RectUtils.overlaps(node.location, rect)) {
+    if (RectUtil.overlaps(node.location, rect)) {
       if (!node.children || node.children.length == 0 ||
           node.children[0].role != RoleType.INLINE_TEXT_BOX) {
         // Only add a node if it has no inlineTextBox children. If
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js
index 66d5d827..8feac02 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak/node_utils_unittest.js
@@ -13,7 +13,7 @@
   'paragraph_utils.js',
   'node_utils.js',
   'word_utils.js',
-  'rect_utils.js',
+  '../common/rect_util.js',
 ];
 
 
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/rect_utils.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/rect_utils.js
deleted file mode 100644
index 37d278c..0000000
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/rect_utils.js
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// Utilities for processing rectangles and coordinates.
-
-class RectUtils {
-  constructor() {}
-
-  /**
-   * Return the rect that encloses two points.
-   * @param {number} x1 The first x coordinate.
-   * @param {number} y1 The first y coordinate.
-   * @param {number} x2 The second x coordinate.
-   * @param {number} y2 The second x coordinate.
-   * @return {{left: number, top: number, width: number, height: number}}
-   */
-  static rectFromPoints(x1, y1, x2, y2) {
-    var left = Math.min(x1, x2);
-    var right = Math.max(x1, x2);
-    var top = Math.min(y1, y2);
-    var bottom = Math.max(y1, y2);
-    return {left, top, width: right - left, height: bottom - top};
-  }
-
-  /**
-   * Returns true if |rect1| and |rect2| overlap. The rects must define
-   * left, top, width, and height.
-   * @param {{left: number, top: number, width: number, height: number}} rect1
-   * @param {{left: number, top: number, width: number, height: number}} rect2
-   * @return {boolean} True if the rects overlap.
-   */
-  static overlaps(rect1, rect2) {
-    var l1 = rect1.left;
-    var r1 = rect1.left + rect1.width;
-    var t1 = rect1.top;
-    var b1 = rect1.top + rect1.height;
-    var l2 = rect2.left;
-    var r2 = rect2.left + rect2.width;
-    var t2 = rect2.top;
-    var b2 = rect2.top + rect2.height;
-    return (l1 < r2 && r1 > l2 && t1 < b2 && b1 > t2);
-  }
-}
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak/rect_utils_unittest.js b/chrome/browser/resources/chromeos/accessibility/select_to_speak/rect_utils_unittest.js
deleted file mode 100644
index 5ac0194..0000000
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak/rect_utils_unittest.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * Test fixture for rect_utils.js.
- */
-SelectToSpeakRectUtilsUnitTest = class extends testing.Test {};
-
-/** @override */
-SelectToSpeakRectUtilsUnitTest.prototype.extraLibraries = [
-  'rect_utils.js',
-];
-
-
-TEST_F('SelectToSpeakRectUtilsUnitTest', 'Overlaps', function() {
-  var rect1 = {left: 0, top: 0, width: 100, height: 100};
-  var rect2 = {left: 80, top: 0, width: 100, height: 20};
-  var rect3 = {left: 0, top: 80, width: 20, height: 100};
-
-  assertTrue(RectUtils.overlaps(rect1, rect1));
-  assertTrue(RectUtils.overlaps(rect2, rect2));
-  assertTrue(RectUtils.overlaps(rect3, rect3));
-  assertTrue(RectUtils.overlaps(rect1, rect2));
-  assertTrue(RectUtils.overlaps(rect1, rect3));
-  assertFalse(RectUtils.overlaps(rect2, rect3));
-});
-
-TEST_F('SelectToSpeakRectUtilsUnitTest', 'RectFromPoints', function() {
-  var rect = {left: 10, top: 20, width: 50, height: 60};
-
-  assertNotEquals(
-      JSON.stringify(rect),
-      JSON.stringify(RectUtils.rectFromPoints(0, 0, 10, 10)));
-  assertEquals(
-      JSON.stringify(rect),
-      JSON.stringify(RectUtils.rectFromPoints(10, 20, 60, 80)));
-  assertEquals(
-      JSON.stringify(rect),
-      JSON.stringify(RectUtils.rectFromPoints(60, 20, 10, 80)));
-  assertEquals(
-      JSON.stringify(rect),
-      JSON.stringify(RectUtils.rectFromPoints(10, 80, 60, 20)));
-  assertEquals(
-      JSON.stringify(rect),
-      JSON.stringify(RectUtils.rectFromPoints(60, 80, 10, 20)));
-});
diff --git a/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2 b/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
index 91b01f9..fc06737 100644
--- a/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
+++ b/chrome/browser/resources/chromeos/accessibility/select_to_speak_manifest.json.jinja2
@@ -14,6 +14,7 @@
       "common/closure_shim.js",
       "common/constants.js",
       "common/automation_predicate.js",
+      "common/rect_util.js",
       "common/tree_walker.js",
       "common/automation_util.js",
       "select_to_speak/input_handler.js",
@@ -22,7 +23,6 @@
       "select_to_speak/paragraph_utils.js",
       "select_to_speak/prefs_manager.js",
       "select_to_speak/word_utils.js",
-      "select_to_speak/rect_utils.js",
       "select_to_speak/select_to_speak.js",
       "select_to_speak/select_to_speak_main.js"
     ]
diff --git a/chrome/browser/resources/chromeos/accessibility/switch_access/background.js b/chrome/browser/resources/chromeos/accessibility/switch_access/background.js
index 90f8d65e..19854c19 100644
--- a/chrome/browser/resources/chromeos/accessibility/switch_access/background.js
+++ b/chrome/browser/resources/chromeos/accessibility/switch_access/background.js
@@ -2,4 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+// In 'split' manifest mode, the extension system runs two copies of the
+// extension. One in an incognito context; the other not. In guest mode, the
+// extension system runs only the extension in an incognito context. To prevent
+// doubling of this extension, only continue for one context.
+const manifest =
+    /** @type {{incognito: (string|undefined)}} */ (
+        chrome.runtime.getManifest());
+if (manifest.incognito == 'split' && !chrome.extension.inIncognitoContext) {
+  window.close();
+}
 SwitchAccess.initialize();
diff --git a/chrome/browser/resources/new_tab_page/BUILD.gn b/chrome/browser/resources/new_tab_page/BUILD.gn
index c83b365..1a808a6 100644
--- a/chrome/browser/resources/new_tab_page/BUILD.gn
+++ b/chrome/browser/resources/new_tab_page/BUILD.gn
@@ -48,6 +48,7 @@
   deps = [
     ":background_manager",
     ":browser_proxy",
+    ":middle_slot_promo",
     ":module_wrapper",
     ":modules",
     ":most_visited",
@@ -66,6 +67,13 @@
   ]
 }
 
+js_library("middle_slot_promo") {
+  deps = [
+    ":browser_proxy",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+}
+
 js_library("most_visited") {
   deps = [
     ":browser_proxy",
@@ -258,6 +266,7 @@
     "doodle_share_dialog.js",
     "fakebox.js",
     "logo.js",
+    "middle_slot_promo.js",
     "mini_page.js",
     "most_visited.js",
     "realbox_button.js",
diff --git a/chrome/browser/resources/new_tab_page/app.html b/chrome/browser/resources/new_tab_page/app.html
index d1f83b7..fc569cf 100644
--- a/chrome/browser/resources/new_tab_page/app.html
+++ b/chrome/browser/resources/new_tab_page/app.html
@@ -94,13 +94,9 @@
     --tile-hover-color: rgba(255, 255, 255, .1);
   }
 
-  #promo {
+  ntp-middle-slot-promo {
     bottom: 16px;
-    height: 32px;
-    left: 0;
     position: fixed;
-    right: 0;
-    width: 100%;
   }
 
   ntp-module-wrapper + ntp-module-wrapper {
@@ -280,9 +276,9 @@
   </ntp-most-visited>
   <dom-if if="[[lazyRender_]]" on-dom-change="onLazyRendered_">
     <template>
-      <ntp-iframe id="promo" hidden$="[[!promoLoaded_]]"
-          src="chrome-untrusted://new-tab-page/promo">
-      </ntp-iframe>
+      <ntp-middle-slot-promo
+          on-ntp-middle-slot-promo-loaded="onMiddleSlotPromoLoaded_">
+      </ntp-middle-slot-promo>
       <template is="dom-repeat" items="[[moduleDescriptors_]]" id="modules">
         <ntp-module-wrapper descriptor="[[item]]"></ntp-module-wrapper>
       </template>
diff --git a/chrome/browser/resources/new_tab_page/app.js b/chrome/browser/resources/new_tab_page/app.js
index e0f6989..40a36e2 100644
--- a/chrome/browser/resources/new_tab_page/app.js
+++ b/chrome/browser/resources/new_tab_page/app.js
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import './strings.m.js';
+import './middle_slot_promo.js';
 import './most_visited.js';
 import './customize_dialog.js';
 import './voice_search_overlay.js';
@@ -106,12 +107,6 @@
             lazyRender_)`,
       },
 
-      /** @private */
-      promoLoaded_: {
-        type: Boolean,
-        value: false,
-      },
-
       /** @private {!newTabPage.mojom.Theme} */
       theme_: {
         observer: 'onThemeChange_',
@@ -251,12 +246,8 @@
       if (typeof data !== 'object') {
         return;
       }
-      if ('frameType' in data) {
-        if (data.frameType === 'promo') {
-          this.handlePromoMessage_(event);
-        } else if (data.frameType === 'one-google-bar') {
-          this.handleOneGoogleBarMessage_(event);
-        }
+      if ('frameType' in data && data.frameType === 'one-google-bar') {
+        this.handleOneGoogleBarMessage_(event);
       }
     });
     this.eventTracker_.add(window, 'keydown', e => this.onWindowKeydown_(e));
@@ -728,35 +719,7 @@
       $$(this, '#oneGoogleBar').style.zIndex = '0';
     } else if (data.messageType === 'execute-browser-command') {
       this.executePromoBrowserCommand_(
-          /** @type CommandData */ (data), event.source, event.origin);
-    }
-  }
-
-  /**
-   * Handle messages from promo iframe. This shows the promo on load and sets
-   * up the show/hide logic (in case there is an overlap with most-visited
-   * tiles).
-   * @param {!MessageEvent} event
-   * @private
-   */
-  handlePromoMessage_(event) {
-    /** @type {!Object} */
-    const data = event.data;
-    if (data.messageType === 'loaded') {
-      this.promoLoaded_ = true;
-      const onResize = () => {
-        const hidePromo = this.$.mostVisited.getBoundingClientRect().bottom >=
-            $$(this, '#promo').offsetTop;
-        $$(this, '#promo').style.opacity = hidePromo ? 0 : 1;
-      };
-      this.eventTracker_.add(window, 'resize', onResize);
-      onResize();
-      this.pageHandler_.onPromoRendered(BrowserProxy.getInstance().now());
-    } else if (data.messageType === 'link-clicked') {
-      this.pageHandler_.onPromoLinkClicked();
-    } else if (data.messageType === 'execute-browser-command') {
-      this.executePromoBrowserCommand_(
-          /** @type CommandData */ (data), event.source, event.origin);
+          /** @type {!CommandData} */ (data.data), event.source, event.origin);
     }
   }
 
@@ -768,6 +731,18 @@
     }
   }
 
+  /** @private */
+  onMiddleSlotPromoLoaded_() {
+    const onResize = () => {
+      const promoElement = $$(this, 'ntp-middle-slot-promo');
+      const hidePromo = this.$.mostVisited.getBoundingClientRect().bottom >=
+          promoElement.offsetTop;
+      promoElement.style.visibility = hidePromo ? 'hidden' : 'visible';
+    };
+    this.eventTracker_.add(window, 'resize', onResize);
+    onResize();
+  }
+
   /**
    * During a shortcut drag, an iframe behind ntp-most-visited will prevent
    * 'dragover' events from firing. To workaround this, 'pointer-events: none'
diff --git a/chrome/browser/resources/new_tab_page/middle_slot_promo.html b/chrome/browser/resources/new_tab_page/middle_slot_promo.html
new file mode 100644
index 0000000..3c39838
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/middle_slot_promo.html
@@ -0,0 +1,57 @@
+<style include="cr-hidden-style">
+  :host {
+    font-size: 12px;
+    max-width: 537px;
+    white-space: pre;
+  }
+
+  #container {
+    align-items: center;
+    background-color: var(--cr-card-background-color);
+    border: 1px solid var(--ntp-border-color);
+    border-radius: 16px;
+    box-sizing: border-box;
+    color: var(--cr-primary-text-color);
+    display: flex;
+    flex-direction: row;
+    height: 32px;
+    padding: 0 16px;
+  }
+
+  a {
+    color: var(--cr-link-color);
+    cursor: pointer;
+    text-decoration: none;
+  }
+
+  a:focus {
+    border-radius: 2px;
+    box-shadow: var(--ntp-focus-shadow);
+    outline: none;
+  }
+
+  .image {
+    margin-inline-end: 8px;
+    margin-inline-start: -12px;
+  }
+
+  img {
+    border-radius: 50%;
+    height: 24px;
+    pointer-events: none;
+    width: 24px;
+  }
+
+  @media (prefers-color-scheme: dark) {
+    img {
+      background-color: var(--google-grey-200);
+    }
+  }
+
+  #container > :last-child {
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+</style>
+<!-- The #container is hidden until the promo parts are added by the JS. -->
+<div id="container" hidden></div>
diff --git a/chrome/browser/resources/new_tab_page/middle_slot_promo.js b/chrome/browser/resources/new_tab_page/middle_slot_promo.js
new file mode 100644
index 0000000..4c3c9b4d
--- /dev/null
+++ b/chrome/browser/resources/new_tab_page/middle_slot_promo.js
@@ -0,0 +1,117 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/hidden_style_css.m.js';
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+
+import {html, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {BrowserProxy} from './browser_proxy.js';
+import {PromoBrowserCommandProxy} from './promo_browser_command_proxy.js';
+
+// Element that requests and renders the middle-slot promo. The element is
+// hidden until the promo is rendered, If no promo exists or the promo is empty,
+// the element remains hidden.
+class MiddleSlotPromoElement extends PolymerElement {
+  static get is() {
+    return 'ntp-middle-slot-promo';
+  }
+
+  static get template() {
+    return html`{__html_template__}`;
+  }
+
+  constructor() {
+    super();
+    /** @private {newTabPage.mojom.PageHandlerRemote} */
+    this.pageHandler_ = BrowserProxy.getInstance().handler;
+  }
+
+  /** @override */
+  connectedCallback() {
+    super.connectedCallback();
+    this.pageHandler_.getPromo().then(({promo}) => {
+      if (!promo) {
+        return;
+      }
+      promo.middleSlotParts.forEach(({image, link, text}) => {
+        let el;
+        if (image) {
+          el = document.createElement('img');
+          el.src = image.imageUrl.url.startsWith('data:') ?
+              image.imageUrl.url :
+              `chrome://image?${image.imageUrl.url}`;
+          if (image.target) {
+            const anchor = this.createAnchor_(image.target);
+            if (anchor) {
+              anchor.appendChild(el);
+              el = anchor;
+            }
+          }
+          el.classList.add('image');
+        } else if (link) {
+          el = this.createAnchor_(link.url);
+        } else if (text) {
+          el = document.createElement('span');
+        }
+        const linkOrText = link || text;
+        if (el && linkOrText) {
+          el.innerText = linkOrText.text;
+          if (linkOrText.color) {
+            el.style.color = linkOrText.color;
+          }
+        }
+        if (el) {
+          this.$.container.appendChild(el);
+        }
+      });
+      this.$.container.hidden = false;
+      this.pageHandler_.onPromoRendered(
+          BrowserProxy.getInstance().now(), promo.logUrl || null);
+      this.dispatchEvent(new Event(
+          'ntp-middle-slot-promo-loaded', {bubbles: true, composed: true}));
+    });
+  }
+
+  /**
+   * @param {!url.mojom.Url} target
+   * @return {HTMLAnchorElement}
+   * @private
+   */
+  createAnchor_(target) {
+    const commandIdMatch = /^command:(\d+)$/.exec(target.url);
+    if (!commandIdMatch && !target.url.startsWith('https://')) {
+      return null;
+    }
+    const el = /** @type {!HTMLAnchorElement} */ (document.createElement('a'));
+    if (!commandIdMatch) {
+      el.href = target.url;
+    }
+    const onClick = event => {
+      if (commandIdMatch) {
+        let commandId = +commandIdMatch[1];
+        // Make sure we don't send unsupported commands to the browser.
+        if (!Object.values(promoBrowserCommand.mojom.Command)
+                 .includes(commandId)) {
+          commandId = promoBrowserCommand.mojom.Command.kUnknownCommand;
+        }
+        PromoBrowserCommandProxy.getInstance().handler.executeCommand(
+            commandId, {
+              middleButton: event.button === 1,
+              altKey: event.altKey,
+              ctrlKey: event.ctrlKey,
+              metaKey: event.metaKey,
+              shiftKey: event.shiftKey,
+            });
+      }
+      this.pageHandler_.onPromoLinkClicked();
+    };
+    // 'auxclick' handles the middle mouse button which does not trigger a
+    // 'click' event.
+    el.addEventListener('auxclick', onClick);
+    el.addEventListener('click', onClick);
+    return el;
+  }
+}
+
+customElements.define(MiddleSlotPromoElement.is, MiddleSlotPromoElement);
diff --git a/chrome/browser/resources/new_tab_page/new_tab_page_resources.grd b/chrome/browser/resources/new_tab_page/new_tab_page_resources.grd
index 871c434..5d1a0858 100644
--- a/chrome/browser/resources/new_tab_page/new_tab_page_resources.grd
+++ b/chrome/browser/resources/new_tab_page/new_tab_page_resources.grd
@@ -18,6 +18,9 @@
           file="${root_gen_dir}/chrome/browser/resources/new_tab_page/app.js"
           use_base_dir="false" type="BINDATA" compress="false"
           preprocess="true" />
+      <include name="IDR_NEW_TAB_PAGE_MIDDLE_SLOT_PROMO_JS"
+          file="${root_gen_dir}/chrome/browser/resources/new_tab_page/middle_slot_promo.js"
+          use_base_dir="false" type="BINDATA" compress="false" />
       <include name="IDR_NEW_TAB_PAGE_MOST_VISITED_JS"
           file="${root_gen_dir}/chrome/browser/resources/new_tab_page/most_visited.js"
           use_base_dir="false" type="BINDATA" compress="false" />
diff --git a/chrome/browser/resources/new_tab_page/new_tab_page_resources_common.grdp b/chrome/browser/resources/new_tab_page/new_tab_page_resources_common.grdp
index 8d0effd..012b9e8 100644
--- a/chrome/browser/resources/new_tab_page/new_tab_page_resources_common.grdp
+++ b/chrome/browser/resources/new_tab_page/new_tab_page_resources_common.grdp
@@ -55,10 +55,6 @@
       file="untrusted/one_google_bar.html" type="BINDATA" />
   <include name="IDR_NEW_TAB_PAGE_UNTRUSTED_ONE_GOOGLE_BAR_JS"
       file="untrusted/one_google_bar.js" type="BINDATA" />
-  <include name="IDR_NEW_TAB_PAGE_UNTRUSTED_PROMO_HTML"
-      file="untrusted/promo.html" type="BINDATA" />
-  <include name="IDR_NEW_TAB_PAGE_UNTRUSTED_PROMO_JS"
-      file="untrusted/promo.js" type="BINDATA" />
   <include name="IDR_NEW_TAB_PAGE_UNTRUSTED_IMAGE_HTML"
       file="untrusted/image.html" type="BINDATA" />
   <include name="IDR_NEW_TAB_PAGE_UNTRUSTED_BACKGROUND_IMAGE_HTML"
diff --git a/chrome/browser/resources/new_tab_page/untrusted/promo.html b/chrome/browser/resources/new_tab_page/untrusted/promo.html
deleted file mode 100644
index 04cd935..0000000
--- a/chrome/browser/resources/new_tab_page/untrusted/promo.html
+++ /dev/null
@@ -1,107 +0,0 @@
-<!doctype html>
-<html dir="$i18n{textdirection}">
-  <head>
-    <meta charset="utf-8">
-    <style>
-      html {
-        --google-blue-400-rgb: 107, 165, 237;  /* #6ba5ed */
-        --google-blue-400: rgb(var(--google-blue-400-rgb));
-        --google-blue-600-rgb: 26, 115, 232;  /* #1a73e8 */
-        --google-blue-600: rgb(var(--google-blue-600-rgb));
-        --google-grey-200-rgb: 232, 234, 237;  /* #e8eaed */
-        --google-grey-200: rgb(var(--google-grey-200-rgb));
-        --google-grey-900-rgb: 32, 33, 36;  /* #202124 */
-        --google-grey-900: rgb(var(--google-grey-900-rgb));
-        --google-blue-refresh-300-rgb: 138, 180, 248;  /* #8ab4f8 */
-        --google-blue-refresh-300: rgb(var(--google-blue-refresh-300-rgb));
-        --google-grey-refresh-300-rgb: 218, 220, 224;  /* #dadce0 */
-        --google-grey-refresh-300: rgb(var(--google-grey-refresh-300-rgb));
-        --google-grey-refresh-700-rgb: 95, 99, 104;  /* #5f6368 */
-        --google-grey-refresh-700: rgb(var(--google-grey-refresh-700-rgb));
-      }
-
-      body {
-        margin: auto;
-        overflow: hidden;
-        width: fit-content;
-      }
-
-      body * {
-        font-family: Roboto, arial, sans-serif;
-      }
-
-      body > div {
-        background-color: white;
-        border: 1px solid var(--google-grey-refresh-300);
-        border-radius: 16px;
-        box-sizing: border-box;
-        color: var(--google-grey-refresh-700);
-        display: inline-block;
-        font-size: 12px;
-        height: 32px;
-        line-height: 30px;
-        margin-bottom: 0;
-        max-width: 537px;
-        overflow: hidden;
-        padding: 0 16px;
-        pointer-events: all;
-        position: relative;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-      }
-
-      @media (prefers-color-scheme: dark) {
-        body > div {
-          background-color: var(--google-grey-900);
-          border-color: rgba(0, 0, 0, .1);
-          color: var(--google-grey-200);
-        }
-      }
-
-      body > div > a {
-        color: var(--google-blue-600);
-        text-decoration: none;
-      }
-
-      body > div > a:focus {
-        outline: solid 2px rgba(var(--google-blue-600-rgb), .4);
-      }
-
-      @media (prefers-color-scheme: dark) {
-        body > div > a {
-          color: var(--google-blue-400);
-        }
-
-        body > div > a:focus {
-          outline: solid 2px rgba(var(--google-blue-refresh-300-rgb), .5);
-        }
-      }
-
-      body > div > img {
-        border-radius: 50%;
-        height: 24px;
-        margin-bottom: 2px;
-        margin-inline-end: 8px;
-        margin-inline-start: -12px;
-        object-fit: cover;
-        vertical-align: middle;
-        width: 24px;
-      }
-
-      @media (prefers-color-scheme: dark) {
-        body > div > img {
-          background-color: var(--google-grey-200);
-        }
-      }
-    </style>
-  </head>
-  <body>
-    <!--
-        This is the promo HTML which includes a <style> and <div> tags. The
-        <style> element is removed since the styling needs to be overridden.
-        The <div> contains one or more <span>, <a> and <img> elements.
-    -->
-    $i18nRaw{data}
-    <script src="promo.js"></script>
-  </body>
-</html>
diff --git a/chrome/browser/resources/new_tab_page/untrusted/promo.js b/chrome/browser/resources/new_tab_page/untrusted/promo.js
deleted file mode 100644
index 18a6190..0000000
--- a/chrome/browser/resources/new_tab_page/untrusted/promo.js
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// TODO(crbug.com/1057147): add tests for chrome-untrusted://new-tab-page files.
-document.addEventListener('DOMContentLoaded', () => {
-  // Remove the <style> from the raw promo since we want to style the promo
-  // ourselves.
-  const styleElement = document.querySelector('body > style');
-  if (styleElement) {
-    styleElement.remove();
-  }
-  // The <a> elements need to open in the top frame since the promo is loaded
-  // in an <iframe>.
-  document.body.querySelectorAll('a').forEach(el => {
-    if (el.target !== '_blank') {
-      el.target = '_top';
-    }
-    el.addEventListener('click', (event) => {
-      const browserCommandFound =
-          event.target.getAttribute('href').match(/^command:(\d+)$/);
-      if (browserCommandFound) {
-        event.preventDefault();  // Prevent navigation attempt.
-        window.parent.postMessage(
-            {
-              frameType: 'promo',
-              messageType: 'execute-browser-command',
-              commandId: parseInt(browserCommandFound[1], 10),
-              clickInfo: {
-                middleButton: event.button === 1,
-                altKey: event.altKey,
-                ctrlKey: event.ctrlKey,
-                metaKey: event.metaKey,
-                shiftKey: event.shiftKey
-              }
-            },
-            'chrome://new-tab-page');
-      }
-
-      window.parent.postMessage(
-          {frameType: 'promo', messageType: 'link-clicked'},
-          'chrome://new-tab-page');
-    });
-  });
-  // Inform the embedder that the promo has loaded and can be displayed.
-  window.parent.postMessage(
-      {frameType: 'promo', messageType: 'loaded'}, 'chrome://new-tab-page');
-});
diff --git a/chrome/browser/resources/pdf/elements/viewer-annotations-bar.js b/chrome/browser/resources/pdf/elements/viewer-annotations-bar.js
index f502a1441..d483280 100644
--- a/chrome/browser/resources/pdf/elements/viewer-annotations-bar.js
+++ b/chrome/browser/resources/pdf/elements/viewer-annotations-bar.js
@@ -40,10 +40,16 @@
       annotationTool_: Object,
 
       /** @private */
-      canUndoAnnotation_: Boolean,
+      canUndoAnnotation_: {
+        type: Boolean,
+        value: false,
+      },
 
       /** @private */
-      canRedoAnnotation_: Boolean,
+      canRedoAnnotation_: {
+        type: Boolean,
+        value: false,
+      },
     };
   }
 
diff --git a/chrome/browser/resources/pdf/pdf_viewer.js b/chrome/browser/resources/pdf/pdf_viewer.js
index d2300291..37d7e012 100644
--- a/chrome/browser/resources/pdf/pdf_viewer.js
+++ b/chrome/browser/resources/pdf/pdf_viewer.js
@@ -540,7 +540,10 @@
    * @private
    */
   onScroll_(e) {
-    this.pluginController.updateScroll(e.target.scrollLeft, e.target.scrollTop);
+    if (this.currentController === this.pluginController) {
+      this.pluginController.updateScroll(
+          e.target.scrollLeft, e.target.scrollTop);
+    }
   }
 
   /** @override */
diff --git a/chrome/browser/resources/print_preview/data/destination.js b/chrome/browser/resources/print_preview/data/destination.js
index 5a4870f..3eae2fd 100644
--- a/chrome/browser/resources/print_preview/data/destination.js
+++ b/chrome/browser/resources/print_preview/data/destination.js
@@ -752,6 +752,11 @@
     if (this.shouldShowDeprecatedPrinterWarning) {
       return 'print-preview:printer-not-supported';
     }
+    // <if expr="chromeos">
+    if (this.id_ === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
+      return 'print-preview:save-to-drive';
+    }
+    // </if>
     if (this.id_ === Destination.GooglePromotedId.DOCS) {
       return 'print-preview:save-to-drive';
     }
diff --git a/chrome/browser/resources/print_preview/data/destination_match.js b/chrome/browser/resources/print_preview/data/destination_match.js
index e5404fe..3d71593 100644
--- a/chrome/browser/resources/print_preview/data/destination_match.js
+++ b/chrome/browser/resources/print_preview/data/destination_match.js
@@ -43,6 +43,12 @@
  * @return {!PrinterType} Map the destination to a PrinterType.
  */
 export function getPrinterTypeForDestination(destination) {
+  // <if expr="chromeos">
+  if (destination.id === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
+    return PrinterType.PDF_PRINTER;
+  }
+  // </if>
+
   if (destination.id === Destination.GooglePromotedId.SAVE_AS_PDF) {
     return PrinterType.PDF_PRINTER;
   }
@@ -123,10 +129,14 @@
    * @private
    */
   isVirtualDestination_(destination) {
-    if (destination.origin === DestinationOrigin.LOCAL) {
-      return destination.id === Destination.GooglePromotedId.SAVE_AS_PDF;
+    // <if expr="chromeos">
+    if (destination.id === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
+      return true;
     }
-    return destination.id === Destination.GooglePromotedId.DOCS;
+    // </if>
+
+    return destination.id === Destination.GooglePromotedId.DOCS ||
+        destination.id === Destination.GooglePromotedId.SAVE_AS_PDF;
   }
 
   /**
diff --git a/chrome/browser/resources/print_preview/data/destination_store.js b/chrome/browser/resources/print_preview/data/destination_store.js
index 874a1ad..93cd45ec 100644
--- a/chrome/browser/resources/print_preview/data/destination_store.js
+++ b/chrome/browser/resources/print_preview/data/destination_store.js
@@ -414,8 +414,7 @@
 
     const serializedSystemDefault = {
       id: this.systemDefaultDestinationId_,
-      origin: this.systemDefaultDestinationId_ ===
-              Destination.GooglePromotedId.SAVE_AS_PDF ?
+      origin: this.isDestinationLocal_(this.systemDefaultDestinationId_) ?
           DestinationOrigin.LOCAL :
           this.platformOrigin_,
       account: '',
@@ -436,6 +435,20 @@
         serializedSystemDefault, /*autoselect=*/ true);
   }
 
+  /**
+   * @param {?string} destinationId
+   * @return {boolean}
+   */
+  isDestinationLocal_(destinationId) {
+    // <if expr="chromeos">
+    if (destinationId === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
+      return true;
+    }
+    // </if>
+
+    return destinationId === Destination.GooglePromotedId.SAVE_AS_PDF;
+  }
+
   /** Removes all events being tracked from the tracker. */
   resetTracker() {
     this.tracker_.removeAll();
diff --git a/chrome/browser/resources/print_preview/data/model.js b/chrome/browser/resources/print_preview/data/model.js
index b0a3948..4deab39 100644
--- a/chrome/browser/resources/print_preview/data/model.js
+++ b/chrome/browser/resources/print_preview/data/model.js
@@ -11,7 +11,7 @@
 import {BackgroundGraphicsModeRestriction, Policies} from '../native_layer.js';
 
 import {Cdd, CddCapabilities, Destination, DestinationOrigin, DestinationType, RecentDestination, VendorCapability} from './destination.js';
-import {getPrinterTypeForDestination} from './destination_match.js';
+import {getPrinterTypeForDestination, PrinterType} from './destination_match.js';
 // <if expr="chromeos">
 import {ColorModeRestriction, DuplexModeRestriction, PinModeRestriction} from './destination_policies.js';
 // </if>
@@ -475,7 +475,7 @@
       value: false,
     },
 
-    /** @type {Destination} */
+    /** @type {!Destination} */
     destination: Object,
 
     /** @type {!DocumentSettings} */
@@ -708,8 +708,8 @@
 
   /** @private */
   updateSettingsAvailabilityFromDestinationAndDocumentSettings_() {
-    const isSaveAsPDF =
-        this.destination.id === Destination.GooglePromotedId.SAVE_AS_PDF;
+    const isSaveAsPDF = getPrinterTypeForDestination(this.destination) ===
+        PrinterType.PDF_PRINTER;
     const knownSizeToSaveAsPdf = isSaveAsPDF &&
         (!this.documentSettings.isModifiable ||
          this.documentSettings.hasCssMediaStyles);
@@ -1357,6 +1357,10 @@
       pageHeight: this.pageSize.height,
       showSystemDialog: showSystemDialog,
     };
+    // <if expr="chromeos">
+    ticket.printToGoogleDrive = ticket.printToGoogleDrive ||
+        destination.id === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS;
+    // </if>
 
     // Set 'cloudPrintID' only if the destination is not local.
     if (!destination.isLocal) {
diff --git a/chrome/browser/resources/print_preview/print_preview_resources.grd b/chrome/browser/resources/print_preview/print_preview_resources.grd
index 80cdb82..5e3f125 100644
--- a/chrome/browser/resources/print_preview/print_preview_resources.grd
+++ b/chrome/browser/resources/print_preview/print_preview_resources.grd
@@ -186,7 +186,8 @@
       <structure name="IDR_PRINT_PREVIEW_DATA_DESTINATION_MATCH_JS"
                  file="data/destination_match.js"
                  compress="false"
-                 type="chrome_html" />
+                 type="chrome_html"
+                 preprocess="true" />
       <if expr="chromeos">
         <structure name="IDR_PRINT_PREVIEW_DATA_DESTINATION_POLICIES_JS"
                    file="data/destination_policies.js"
diff --git a/chrome/browser/resources/print_preview/ui/app.js b/chrome/browser/resources/print_preview/ui/app.js
index b638a9d5..6f856097 100644
--- a/chrome/browser/resources/print_preview/ui/app.js
+++ b/chrome/browser/resources/print_preview/ui/app.js
@@ -19,6 +19,7 @@
 import {CloudPrintInterface, CloudPrintInterfaceErrorEventDetail, CloudPrintInterfaceEventType} from '../cloud_print_interface.js';
 import {CloudPrintInterfaceImpl} from '../cloud_print_interface_impl.js';
 import {Destination, DestinationOrigin} from '../data/destination.js';
+import {getPrinterTypeForDestination, PrinterType} from '../data/destination_match.js';
 import {DocumentSettings} from '../data/document_info.js';
 import {Margins} from '../data/margins.js';
 import {MeasurementSystem} from '../data/measurement_system.js';
@@ -442,7 +443,8 @@
       this.nativeLayer_.dialogClose(this.cancelled_);
     } else if (this.state === State.HIDDEN) {
       if (this.destination_.isLocal &&
-          this.destination_.id !== Destination.GooglePromotedId.SAVE_AS_PDF) {
+          getPrinterTypeForDestination(this.destination_) !==
+              PrinterType.PDF_PRINTER) {
         // Only hide the preview for local, non PDF destinations.
         this.nativeLayer_.hidePreview();
       }
@@ -453,8 +455,8 @@
               destination, this.openPdfInPreview_,
               this.showSystemDialogBeforePrint_));
       if (destination.isLocal) {
-        const onError =
-            destination.id === Destination.GooglePromotedId.SAVE_AS_PDF ?
+        const onError = getPrinterTypeForDestination(destination) ===
+                PrinterType.PDF_PRINTER ?
             this.onFileSelectionCancel_.bind(this) :
             this.onPrintFailed_.bind(this);
         whenPrintDone.then(this.close_.bind(this), onError);
diff --git a/chrome/browser/resources/print_preview/ui/button_strip.js b/chrome/browser/resources/print_preview/ui/button_strip.js
index 46244702..107df18 100644
--- a/chrome/browser/resources/print_preview/ui/button_strip.js
+++ b/chrome/browser/resources/print_preview/ui/button_strip.js
@@ -13,6 +13,7 @@
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination} from '../data/destination.js';
+import {getPrinterTypeForDestination, PrinterType} from '../data/destination_match.js';
 import {State} from '../data/state.js';
 
 Polymer({
@@ -83,7 +84,8 @@
    */
   isPdfOrDrive_() {
     return this.destination &&
-        (this.destination.id === Destination.GooglePromotedId.SAVE_AS_PDF ||
+        (getPrinterTypeForDestination(this.destination) ===
+             PrinterType.PDF_PRINTER ||
          this.destination.id === Destination.GooglePromotedId.DOCS);
   },
 
diff --git a/chrome/browser/resources/print_preview/ui/destination_select_cros.js b/chrome/browser/resources/print_preview/ui/destination_select_cros.js
index 0c0ccee..db1c793 100644
--- a/chrome/browser/resources/print_preview/ui/destination_select_cros.js
+++ b/chrome/browser/resources/print_preview/ui/destination_select_cros.js
@@ -174,6 +174,11 @@
 
     // Check for the Docs or Save as PDF ids first.
     const keyParams = this.selectedValue.split('/');
+    // <if expr="chromeos">
+    if (keyParams[0] === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
+      return 'print-preview:save-to-drive';
+    }
+    // </if>
     if (keyParams[0] === Destination.GooglePromotedId.DOCS) {
       return 'print-preview:save-to-drive';
     }
diff --git a/chrome/browser/resources/print_preview/ui/destination_settings.js b/chrome/browser/resources/print_preview/ui/destination_settings.js
index bdc1874c..e461abf 100644
--- a/chrome/browser/resources/print_preview/ui/destination_settings.js
+++ b/chrome/browser/resources/print_preview/ui/destination_settings.js
@@ -22,12 +22,12 @@
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
 import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
-import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
 import {beforeNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {CloudPrintInterfaceImpl} from '../cloud_print_interface_impl.js';
 import {createDestinationKey, createRecentDestinationKey, Destination, DestinationOrigin, makeRecentDestination, RecentDestination} from '../data/destination.js';
+import {getPrinterTypeForDestination, PrinterType} from '../data/destination_match.js';
 import {DestinationErrorType, DestinationStore} from '../data/destination_store.js';
 import {InvitationStore} from '../data/invitation_store.js';
 import {Error, State} from '../data/state.js';
@@ -153,17 +153,6 @@
 
     /** @private {!Array<string>} */
     users_: Array,
-
-    // <if expr="chromeos">
-    /** @private */
-    saveToDriveFlagEnabled_: {
-      type: Boolean,
-      value() {
-        return loadTimeData.getBoolean('printSaveToDrive');
-      },
-      readOnly: true,
-    },
-    // </if>
   },
 
   /** @private {string} */
@@ -384,8 +373,7 @@
    */
   destinationIsDriveOrPdf_(destination) {
     // <if expr="chromeos">
-    if (this.saveToDriveFlagEnabled_ &&
-        destination.id === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
+    if (destination.id === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
       return true;
     }
     // </if>
@@ -470,7 +458,8 @@
         this.destinationState === DestinationState.UPDATED ||
         (this.destinationState === DestinationState.SET && !!this.destination &&
          (!!this.destination.capabilities ||
-          this.destination.id === Destination.GooglePromotedId.SAVE_AS_PDF));
+          getPrinterTypeForDestination(this.destination) ===
+              PrinterType.PDF_PRINTER));
   },
 
   // <if expr="chromeos">
diff --git a/chrome/browser/resources/print_preview/ui/header.js b/chrome/browser/resources/print_preview/ui/header.js
index 8f90333a..df4c2d24 100644
--- a/chrome/browser/resources/print_preview/ui/header.js
+++ b/chrome/browser/resources/print_preview/ui/header.js
@@ -13,6 +13,7 @@
 import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 
 import {Destination} from '../data/destination.js';
+import {getPrinterTypeForDestination, PrinterType} from '../data/destination_match.js';
 import {Error, State} from '../data/state.js';
 import {SettingsBehavior} from './settings_behavior.js';
 
@@ -53,7 +54,8 @@
    */
   isPdfOrDrive_() {
     return this.destination &&
-        (this.destination.id === Destination.GooglePromotedId.SAVE_AS_PDF ||
+        (getPrinterTypeForDestination(this.destination) ===
+             PrinterType.PDF_PRINTER ||
          this.destination.id === Destination.GooglePromotedId.DOCS);
   },
 
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn b/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn
index f635bda..c1ce423 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/BUILD.gn
@@ -19,6 +19,7 @@
 js_library("android_apps_subpage") {
   deps = [
     ":android_apps_browser_proxy",
+    "..:deep_linking_behavior",
     "..:os_route",
     "../..:router",
     "../../prefs:prefs_behavior",
@@ -32,6 +33,7 @@
   deps = [
     ":android_apps_browser_proxy",
     ":android_apps_subpage",
+    "..:deep_linking_behavior",
     "..:os_route",
     "../../:router",
     "../../prefs:prefs_behavior",
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.html b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.html
index 7fcc517b..41ce64fc 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.html
@@ -9,6 +9,7 @@
 <link rel="import" href="android_apps_browser_proxy.html">
 <link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../../prefs/prefs_behavior.html">
+<link rel="import" href="../deep_linking_behavior.html">
 <link rel="import" href="../os_route.html">
 <link rel="import" href="../../router.html">
 <link rel="import" href="../../settings_shared_css.html">
@@ -19,7 +20,9 @@
 
     <template is="dom-if" if="[[androidAppsInfo.settingsAppAvailable]]" restamp>
       <cr-link-row id="manageApps" label="$i18n{androidAppsManageApps}"
-          on-click="onManageAndroidAppsTap_" external></cr-link-row>
+          on-click="onManageAndroidAppsTap_" external
+          deep-link-focus-id$="[[Setting.kManageAndroidPreferences]]">
+      </cr-link-row>
     </template>
 
     <!-- Use 'restamp' so tests can check if the row exists. -->
@@ -29,7 +32,8 @@
             $i18n{androidAppsRemove}
         </div>
         <cr-button on-click="onRemoveTap_"
-          aria-labelledby="androidRemoveLabel">
+          aria-labelledby="androidRemoveLabel"
+          deep-link-focus-id$="[[Setting.kRemovePlayStore]]">
            $i18n{androidAppsRemoveButton}
         </cr-button>
       </div>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js
index 3a78ec1..42f9b5d 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/android_apps_subpage.js
@@ -10,7 +10,12 @@
 Polymer({
   is: 'settings-android-apps-subpage',
 
-  behaviors: [I18nBehavior, PrefsBehavior],
+  behaviors: [
+    DeepLinkingBehavior,
+    I18nBehavior,
+    PrefsBehavior,
+    settings.RouteObserverBehavior,
+  ],
 
   properties: {
     /** Preferences state. */
@@ -36,7 +41,32 @@
             'androidAppsDisableDialogMessage',
             {substitutions: [], tags: ['br']});
       }
+    },
+
+    /**
+     * Used by DeepLinkingBehavior to focus this page's deep links.
+     * @type {!Set<!chromeos.settings.mojom.Setting>}
+     */
+    supportedSettingIds: {
+      type: Object,
+      value: () => new Set([
+        chromeos.settings.mojom.Setting.kManageAndroidPreferences,
+        chromeos.settings.mojom.Setting.kRemovePlayStore,
+      ]),
+    },
+  },
+
+  /**
+   * @param {!settings.Route} route
+   * @param {!settings.Route} oldRoute
+   */
+  currentRouteChanged(route, oldRoute) {
+    // Does not apply to this page.
+    if (route !== settings.routes.ANDROID_APPS_DETAILS) {
+      return;
     }
+
+    this.attemptDeepLink();
   },
 
   /** @private */
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html
index 69c36a9..86c567b 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.html
@@ -7,6 +7,7 @@
 <link rel="import" href="chrome://resources/html/i18n_behavior.html">
 <link rel="import" href="../../i18n_setup.html">
 <link rel="import" href="../../prefs/prefs_behavior.html">
+<link rel="import" href="../deep_linking_behavior.html">
 <link rel="import" href="../os_route.html">
 <link rel="import" href="../../router.html">
 <link rel="import" href="../../settings_page/settings_animated_pages.html">
@@ -66,7 +67,8 @@
                   disabled="[[isEnforced_(prefs.arc.enabled)]]"
                   on-click="onEnableAndroidAppsTap_"
                   aria-label="$i18n{androidAppsPageTitle}"
-                  aria-describedby="secondaryText">
+                  aria-describedby="secondaryText"
+                  deep-link-focus-id$="[[Setting.kTurnOnPlayStore]]">
                 $i18n{androidAppsEnable}
               </cr-button>
             </template>
@@ -74,7 +76,8 @@
         </template>
         <template is="dom-if" if="[[!havePlayStoreApp]]" restamp>
           <cr-link-row id="manageApps" label="$i18n{androidAppsManageApps}"
-              on-click="onManageAndroidAppsTap_" external>
+              on-click="onManageAndroidAppsTap_" external
+              deep-link-focus-id$="[[Setting.kManageAndroidPreferences]]">
           </cr-link-row>
         </template>
       </template>
diff --git a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
index 8d4834d..75cfec0c 100644
--- a/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
+++ b/chrome/browser/resources/settings/chromeos/os_apps_page/os_apps_page.js
@@ -12,8 +12,10 @@
 
   behaviors: [
     app_management.StoreClient,
+    DeepLinkingBehavior,
     I18nBehavior,
     PrefsBehavior,
+    settings.RouteObserverBehavior,
   ],
 
   properties: {
@@ -74,6 +76,18 @@
      * @private
      */
     app_: Object,
+
+    /**
+     * Used by DeepLinkingBehavior to focus this page's deep links.
+     * @type {!Set<!chromeos.settings.mojom.Setting>}
+     */
+    supportedSettingIds: {
+      type: Object,
+      value: () => new Set([
+        chromeos.settings.mojom.Setting.kManageAndroidPreferences,
+        chromeos.settings.mojom.Setting.kTurnOnPlayStore,
+      ]),
+    },
   },
 
   attached() {
@@ -81,6 +95,19 @@
   },
 
   /**
+   * @param {!settings.Route} route
+   * @param {!settings.Route} oldRoute
+   */
+  currentRouteChanged(route, oldRoute) {
+    // Does not apply to this page.
+    if (route !== settings.routes.APPS) {
+      return;
+    }
+
+    this.attemptDeepLink();
+  },
+
+  /**
    * @param {App} app
    * @return {string}
    * @private
diff --git a/chrome/browser/search/promos/promo_data.cc b/chrome/browser/search/promos/promo_data.cc
index 7132b318..8169aa7 100644
--- a/chrome/browser/search/promos/promo_data.cc
+++ b/chrome/browser/search/promos/promo_data.cc
@@ -14,6 +14,7 @@
 
 bool operator==(const PromoData& lhs, const PromoData& rhs) {
   return lhs.promo_html == rhs.promo_html &&
+         lhs.middle_slot_json == rhs.middle_slot_json &&
          lhs.promo_log_url == rhs.promo_log_url && lhs.promo_id == rhs.promo_id;
 }
 
diff --git a/chrome/browser/search/promos/promo_data.h b/chrome/browser/search/promos/promo_data.h
index 7d02987..ac89199 100644
--- a/chrome/browser/search/promos/promo_data.h
+++ b/chrome/browser/search/promos/promo_data.h
@@ -23,6 +23,9 @@
   // The main HTML for the promo. May be empty when nothing to show.
   std::string promo_html;
 
+  // The structured JSON data of the middle slot promo.
+  std::string middle_slot_json;
+
   // URL to ping to log a promo impression. May be invalid.
   GURL promo_log_url;
 
diff --git a/chrome/browser/search/promos/promo_service.cc b/chrome/browser/search/promos/promo_service.cc
index b435d0f..317c487 100644
--- a/chrome/browser/search/promos/promo_service.cc
+++ b/chrome/browser/search/promos/promo_service.cc
@@ -9,6 +9,7 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/feature_list.h"
+#include "base/json/json_string_value_serializer.h"
 #include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/strcat.h"
@@ -95,6 +96,13 @@
     return false;
   }
 
+  const base::Value* middle_announce_payload = promos->FindKeyOfType(
+      "middle_announce_payload", base::Value::Type::DICTIONARY);
+  if (middle_announce_payload) {
+    JSONStringValueSerializer serializer(&result.middle_slot_json);
+    serializer.Serialize(*middle_announce_payload);
+  }
+
   std::string log_url;
   // Emergency promos don't have these, so it's OK if this key is missing.
   promos->GetString("log_url", &log_url);
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java
index 16f780a..0b37fc9 100644
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/state/PersistedTabData.java
@@ -9,6 +9,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.TraceEvent;
 import org.chromium.base.UserData;
 import org.chromium.base.UserDataHost;
 import org.chromium.base.metrics.RecordHistogram;
@@ -188,7 +189,10 @@
     abstract byte[] serialize();
 
     private byte[] serializeAndLog() {
-        byte[] res = serialize();
+        byte[] res;
+        try (TraceEvent e = TraceEvent.scoped("PersistedTabData.Serialize")) {
+            res = serialize();
+        }
         RecordHistogram.recordBooleanHistogram(
                 "Tabs.PersistedTabData.Serialize." + getUmaTag(), res != null);
         return res;
@@ -202,7 +206,10 @@
     abstract boolean deserialize(@Nullable byte[] bytes);
 
     private void deserializeAndLog(@Nullable byte[] bytes) {
-        boolean success = deserialize(bytes);
+        boolean success;
+        try (TraceEvent e = TraceEvent.scoped("PersistedTabData.Deserialize")) {
+            success = deserialize(bytes);
+        }
         RecordHistogram.recordBooleanHistogram(
                 "Tabs.PersistedTabData.Deserialize." + getUmaTag(), success);
     }
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index 0fe15b66..f462bfd 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1287,6 +1287,8 @@
       "unload_controller.h",
       "views/eye_dropper/eye_dropper.cc",
       "views/eye_dropper/eye_dropper.h",
+      "views/eye_dropper/eye_dropper_view_mac.h",
+      "views/eye_dropper/eye_dropper_view_mac.mm",
       "webui/app_launcher_login_handler.cc",
       "webui/app_launcher_login_handler.h",
       "webui/app_management/app_management_page_handler.cc",
@@ -3023,8 +3025,6 @@
       "views/color_chooser_win.cc",
       "views/critical_notification_bubble_view.cc",
       "views/critical_notification_bubble_view.h",
-      "views/eye_dropper/eye_dropper_win.cc",
-      "views/eye_dropper/eye_dropper_win.h",
       "views/frame/browser_desktop_window_tree_host.h",
       "views/frame/browser_desktop_window_tree_host_win.cc",
       "views/frame/browser_desktop_window_tree_host_win.h",
@@ -3880,6 +3880,8 @@
       "views/tabs/tab_hover_card_bubble_view.h",
       "views/tabs/tab_icon.cc",
       "views/tabs/tab_icon.h",
+      "views/tabs/tab_search_button.cc",
+      "views/tabs/tab_search_button.h",
       "views/tabs/tab_slot_view.cc",
       "views/tabs/tab_slot_view.h",
       "views/tabs/tab_strip.cc",
@@ -4139,13 +4141,6 @@
     if (is_chrome_branded) {
       deps += [ "//chrome/browser/ui/media_router/internal/vector_icons" ]
     }
-
-    if (enable_tab_search) {
-      sources += [
-        "views/tabs/tab_search_button.cc",
-        "views/tabs/tab_search_button.h",
-      ]
-    }
   }
 
   if (use_aura) {
@@ -4164,6 +4159,8 @@
       "views/apps/shaped_app_window_targeter.cc",
       "views/apps/shaped_app_window_targeter.h",
       "views/dropdown_bar_host_aura.cc",
+      "views/eye_dropper/eye_dropper_view_aura.cc",
+      "views/eye_dropper/eye_dropper_view_aura.h",
       "views/renderer_context_menu/render_view_context_menu_views.cc",
       "views/renderer_context_menu/render_view_context_menu_views.h",
       "views/tab_contents/chrome_web_contents_view_delegate_views.cc",
diff --git a/chrome/browser/ui/app_list/search/omnibox_provider.cc b/chrome/browser/ui/app_list/search/omnibox_provider.cc
index 5ad1fdd9..c44c9b6 100644
--- a/chrome/browser/ui/app_list/search/omnibox_provider.cc
+++ b/chrome/browser/ui/app_list/search/omnibox_provider.cc
@@ -20,6 +20,15 @@
 #include "url/gurl.h"
 
 namespace app_list {
+namespace {
+
+bool IsDriveUrl(const GURL& url) {
+  // Returns true if the |url| points to a Drive Web host.
+  const std::string& host = url.host();
+  return host == "drive.google.com" || host == "docs.google.com";
+}
+
+}  //  namespace
 
 OmniboxProvider::OmniboxProvider(Profile* profile,
                                  AppListControllerDelegate* list_controller)
@@ -72,12 +81,22 @@
   SearchProvider::Results new_results;
   new_results.reserve(result.size());
   for (const AutocompleteMatch& match : result) {
-    if (!match.destination_url.is_valid())
+    // Do not return a match in any of these cases:
+    // - The URL is invalid.
+    // - The URL points to Drive Web. The LauncherSearchProvider surfaces Drive
+    //   results.
+    // - The URL points to a local file. The LauncherSearchProvider also handles
+    //   files results, even if they've been opened in the browser.
+    if (!match.destination_url.is_valid() ||
+        IsDriveUrl(match.destination_url) ||
+        match.destination_url.SchemeIsFile()) {
       continue;
+    }
     new_results.emplace_back(std::make_unique<OmniboxResult>(
         profile_, list_controller_, controller_.get(), match,
         is_zero_state_input_));
   }
+
   SwapResults(&new_results);
 }
 
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper.cc b/chrome/browser/ui/views/eye_dropper/eye_dropper.cc
index aa3ebb75..f0abe81b 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper.cc
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper.cc
@@ -7,11 +7,11 @@
 #include "build/build_config.h"
 #include "content/public/browser/eye_dropper.h"
 
-#if !defined(OS_WIN)
+#if !defined(USE_AURA) && !defined(OS_MAC)
 // Used for the platforms that don't support an eye dropper.
 std::unique_ptr<content::EyeDropper> ShowEyeDropper(
     content::RenderFrameHost* frame,
     content::EyeDropperListener* listener) {
   return nullptr;
 }
-#endif  // !defined(OS_WIN)
+#endif  // !defined(USE_AURA) && !defined(OS_MAC)
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_browsertest.cc b/chrome/browser/ui/views/eye_dropper/eye_dropper_browsertest.cc
index 181fbe38..93f73ec 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper_browsertest.cc
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_browsertest.cc
@@ -18,7 +18,7 @@
 #include "ui/display/display_switches.h"
 
 #if defined(OS_WIN)
-#include "chrome/browser/ui/views/eye_dropper/eye_dropper_win.h"
+#include "chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.h"
 #endif
 
 class EyeDropperBrowserTest : public UiBrowserTest,
@@ -33,9 +33,11 @@
 
   // UiBrowserTest:
   void ShowUi(const std::string& name) override {
+#if defined(OS_WIN)
     content::RenderFrameHost* parent_frame =
         browser()->tab_strip_model()->GetActiveWebContents()->GetMainFrame();
     eye_dropper_ = ShowEyeDropper(parent_frame, /*listener=*/nullptr);
+#endif
   }
 
   bool VerifyUi() override {
@@ -44,7 +46,7 @@
       return false;
 
     views::Widget* widget =
-        static_cast<EyeDropperWin*>(eye_dropper_.get())->GetWidget();
+        static_cast<EyeDropperViewAura*>(eye_dropper_.get())->GetWidget();
     auto* test_info = testing::UnitTest::GetInstance()->current_test_info();
     const std::string screenshot_name =
         base::StrCat({test_info->test_case_name(), "_", test_info->name()});
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_win.cc b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc
similarity index 80%
rename from chrome/browser/ui/views/eye_dropper/eye_dropper_win.cc
rename to chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc
index 7cbfaf4..481bedd 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper_win.cc
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/views/eye_dropper/eye_dropper_win.h"
+#include "chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.h"
 
 #include "base/time/time.h"
 #include "chrome/browser/ui/views/eye_dropper/eye_dropper.h"
@@ -19,9 +19,9 @@
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/widget/widget.h"
 
-class EyeDropperWin::PreEventDispatchHandler : public ui::EventHandler {
+class EyeDropperViewAura::PreEventDispatchHandler : public ui::EventHandler {
  public:
-  explicit PreEventDispatchHandler(aura::Window* owner, EyeDropperWin* view);
+  explicit PreEventDispatchHandler(EyeDropperViewAura* view);
   PreEventDispatchHandler(const PreEventDispatchHandler&) = delete;
   PreEventDispatchHandler& operator=(const PreEventDispatchHandler&) = delete;
   ~PreEventDispatchHandler() override;
@@ -29,35 +29,37 @@
  private:
   void OnMouseEvent(ui::MouseEvent* event) override;
 
-  aura::Window* owner_;
-  EyeDropperWin* view_;
+  EyeDropperViewAura* view_;
 };
 
-EyeDropperWin::PreEventDispatchHandler::PreEventDispatchHandler(
-    aura::Window* owner,
-    EyeDropperWin* view)
-    : owner_(owner), view_(view) {
+EyeDropperViewAura::PreEventDispatchHandler::PreEventDispatchHandler(
+    EyeDropperViewAura* view)
+    : view_(view) {
   // Ensure that this handler is called before color popup handler by using
   // a higher priority.
-  owner_->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem);
+  view->GetWidget()->GetNativeWindow()->AddPreTargetHandler(
+      this, ui::EventTarget::Priority::kSystem);
 }
 
-EyeDropperWin::PreEventDispatchHandler::~PreEventDispatchHandler() {
-  owner_->RemovePreTargetHandler(this);
+EyeDropperViewAura::PreEventDispatchHandler::~PreEventDispatchHandler() {
+  view_->GetWidget()->GetNativeWindow()->RemovePreTargetHandler(this);
 }
 
-void EyeDropperWin::PreEventDispatchHandler::OnMouseEvent(
+void EyeDropperViewAura::PreEventDispatchHandler::OnMouseEvent(
     ui::MouseEvent* event) {
   if (event->type() == ui::ET_MOUSE_PRESSED) {
     // Avoid closing the color popup when the eye dropper is clicked.
+    event->StopPropagation();
+  }
+  if (event->type() == ui::ET_MOUSE_RELEASED) {
     view_->OnColorSelected();
     event->StopPropagation();
   }
 }
 
-class EyeDropperWin::ViewPositionHandler {
+class EyeDropperViewAura::ViewPositionHandler {
  public:
-  explicit ViewPositionHandler(EyeDropperWin* owner);
+  explicit ViewPositionHandler(EyeDropperViewAura* owner);
   ViewPositionHandler(const ViewPositionHandler&) = delete;
   ViewPositionHandler& operator=(const ViewPositionHandler&) = delete;
   ~ViewPositionHandler();
@@ -68,27 +70,29 @@
   // Timer used for updating the window location.
   base::RepeatingTimer timer_;
 
-  EyeDropperWin* owner_;
+  EyeDropperViewAura* owner_;
 };
 
-EyeDropperWin::ViewPositionHandler::ViewPositionHandler(EyeDropperWin* owner)
+EyeDropperViewAura::ViewPositionHandler::ViewPositionHandler(
+    EyeDropperViewAura* owner)
     : owner_(owner) {
   // Use a value close to the refresh rate @60hz.
   // TODO(iopopesc): Use SetCapture instead of a timer when support for
   // activating the eye dropper without closing the color popup is added.
   timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(16), this,
-               &EyeDropperWin::ViewPositionHandler::UpdateViewPosition);
+               &EyeDropperViewAura::ViewPositionHandler::UpdateViewPosition);
 }
 
-EyeDropperWin::ViewPositionHandler::~ViewPositionHandler() {
+EyeDropperViewAura::ViewPositionHandler::~ViewPositionHandler() {
   timer_.AbandonAndStop();
 }
 
-void EyeDropperWin::ViewPositionHandler::UpdateViewPosition() {
+void EyeDropperViewAura::ViewPositionHandler::UpdateViewPosition() {
   owner_->UpdatePosition();
 }
 
-class EyeDropperWin::ScreenCapturer : public webrtc::DesktopCapturer::Callback {
+class EyeDropperViewAura::ScreenCapturer
+    : public webrtc::DesktopCapturer::Callback {
  public:
   ScreenCapturer();
   ScreenCapturer(const ScreenCapturer&) = delete;
@@ -106,7 +110,7 @@
   SkBitmap frame_;
 };
 
-EyeDropperWin::ScreenCapturer::ScreenCapturer() {
+EyeDropperViewAura::ScreenCapturer::ScreenCapturer() {
   // TODO(iopopesc): Update the captured frame after a period of time to match
   // latest content on screen.
   capturer_ = content::desktop_capture::CreateScreenCapturer();
@@ -114,7 +118,7 @@
   capturer_->CaptureFrame();
 }
 
-void EyeDropperWin::ScreenCapturer::OnCaptureResult(
+void EyeDropperViewAura::ScreenCapturer::OnCaptureResult(
     webrtc::DesktopCapturer::Result result,
     std::unique_ptr<webrtc::DesktopFrame> frame) {
   if (result != webrtc::DesktopCapturer::Result::SUCCESS)
@@ -126,12 +130,12 @@
   frame_.setImmutable();
 }
 
-SkBitmap EyeDropperWin::ScreenCapturer::GetBitmap() const {
+SkBitmap EyeDropperViewAura::ScreenCapturer::GetBitmap() const {
   return frame_;
 }
 
-EyeDropperWin::EyeDropperWin(content::RenderFrameHost* frame,
-                             content::EyeDropperListener* listener)
+EyeDropperViewAura::EyeDropperViewAura(content::RenderFrameHost* frame,
+                                       content::EyeDropperListener* listener)
     : render_frame_host_(frame),
       listener_(listener),
       view_position_handler_(std::make_unique<ViewPositionHandler>(this)),
@@ -154,8 +158,7 @@
   widget->Init(std::move(params));
   widget->SetContentsView(this);
 
-  pre_dispatch_handler_ = std::make_unique<PreEventDispatchHandler>(
-      widget->GetNativeWindow(), this);
+  pre_dispatch_handler_ = std::make_unique<PreEventDispatchHandler>(this);
 
   auto* cursor_client =
       aura::client::GetCursorClient(widget->GetNativeWindow()->GetRootWindow());
@@ -164,21 +167,21 @@
   widget->Show();
 }
 
-EyeDropperWin::~EyeDropperWin() {
+EyeDropperViewAura::~EyeDropperViewAura() {
   if (GetWidget())
     GetWidget()->CloseNow();
 }
 
-void EyeDropperWin::OnPaint(gfx::Canvas* view_canvas) {
+void EyeDropperViewAura::OnPaint(gfx::Canvas* view_canvas) {
   if (screen_capturer_->GetBitmap().drawsNothing())
     return;
 
   constexpr float kDiameter = 90;
   constexpr float kPixelSize = 10;
-
-  // Clip circle for magnified projection.
   const gfx::Size padding((size().width() - kDiameter) / 2,
                           (size().height() - kDiameter) / 2);
+
+  // Clip circle for magnified projection.
   SkPath clip_path;
   clip_path.addOval(SkRect::MakeXYWH(padding.width(), padding.height(),
                                      kDiameter, kDiameter));
@@ -240,23 +243,23 @@
   OnPaintBorder(view_canvas);
 }
 
-void EyeDropperWin::WindowClosing() {
+void EyeDropperViewAura::WindowClosing() {
   aura::client::GetCursorClient(GetWidget()->GetNativeWindow()->GetRootWindow())
       ->UnlockCursor();
   pre_dispatch_handler_.reset();
 }
 
-ui::ModalType EyeDropperWin::GetModalType() const {
+ui::ModalType EyeDropperViewAura::GetModalType() const {
   return ui::MODAL_TYPE_SYSTEM;
 }
 
-void EyeDropperWin::OnWidgetMove() {
+void EyeDropperViewAura::OnWidgetMove() {
   // Trigger a repaint since because the widget was moved, the content of the
   // view needs to be updated.
   SchedulePaint();
 }
 
-void EyeDropperWin::UpdatePosition() {
+void EyeDropperViewAura::UpdatePosition() {
   if (screen_capturer_->GetBitmap().drawsNothing() || !GetWidget())
     return;
 
@@ -271,7 +274,7 @@
                 size()));
 }
 
-void EyeDropperWin::OnColorSelected() {
+void EyeDropperViewAura::OnColorSelected() {
   if (!selected_color_.has_value()) {
     listener_->ColorSelectionCanceled();
     return;
@@ -284,5 +287,5 @@
 std::unique_ptr<content::EyeDropper> ShowEyeDropper(
     content::RenderFrameHost* frame,
     content::EyeDropperListener* listener) {
-  return std::make_unique<EyeDropperWin>(frame, listener);
+  return std::make_unique<EyeDropperViewAura>(frame, listener);
 }
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_win.h b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.h
similarity index 68%
rename from chrome/browser/ui/views/eye_dropper/eye_dropper_win.h
rename to chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.h
index ed32a3e4..7413262 100644
--- a/chrome/browser/ui/views/eye_dropper/eye_dropper_win.h
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_WIN_H_
-#define CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_WIN_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_AURA_H_
+#define CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_AURA_H_
 
 #include <memory>
 
@@ -15,14 +15,14 @@
 #include "ui/gfx/geometry/point.h"
 #include "ui/views/widget/widget_delegate.h"
 
-class EyeDropperWin : public content::EyeDropper,
-                      public views::WidgetDelegateView {
+class EyeDropperViewAura : public content::EyeDropper,
+                           public views::WidgetDelegateView {
  public:
-  EyeDropperWin(content::RenderFrameHost* frame,
-                content::EyeDropperListener* listener);
-  EyeDropperWin(const EyeDropperWin&) = delete;
-  EyeDropperWin& operator=(const EyeDropperWin&) = delete;
-  ~EyeDropperWin() override;
+  EyeDropperViewAura(content::RenderFrameHost* frame,
+                     content::EyeDropperListener* listener);
+  EyeDropperViewAura(const EyeDropperViewAura&) = delete;
+  EyeDropperViewAura& operator=(const EyeDropperViewAura&) = delete;
+  ~EyeDropperViewAura() override;
 
  protected:
   // views::WidgetDelegateView:
@@ -53,4 +53,4 @@
   base::Optional<SkColor> selected_color_;
 };
 
-#endif  // CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_WIN_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_AURA_H_
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h
new file mode 100644
index 0000000..bd9f82c
--- /dev/null
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h
@@ -0,0 +1,28 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_MAC_H_
+#define CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_MAC_H_
+
+#include "base/mac/scoped_nsobject.h"
+#include "content/public/browser/eye_dropper.h"
+#include "content/public/browser/eye_dropper_listener.h"
+
+@class NSColorSampler;
+
+class EyeDropperViewMac : public content::EyeDropper {
+ public:
+  EyeDropperViewMac(content::EyeDropperListener* listener);
+  EyeDropperViewMac(const EyeDropperViewMac&) = delete;
+  EyeDropperViewMac& operator=(const EyeDropperViewMac&) = delete;
+  ~EyeDropperViewMac() override;
+
+ private:
+  // Receives the color selection.
+  content::EyeDropperListener* listener_;
+
+  base::scoped_nsobject<NSColorSampler> color_sampler_;
+};
+
+#endif  // CHROME_BROWSER_UI_VIEWS_EYE_DROPPER_EYE_DROPPER_VIEW_MAC_H_
diff --git a/chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.mm b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.mm
new file mode 100644
index 0000000..a4c27352d
--- /dev/null
+++ b/chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.mm
@@ -0,0 +1,37 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h"
+
+#import <Cocoa/Cocoa.h>
+
+#include "content/public/browser/render_frame_host.h"
+#include "skia/ext/skia_utils_mac.h"
+
+EyeDropperViewMac::EyeDropperViewMac(content::EyeDropperListener* listener)
+    : listener_(listener) {
+  if (!listener_)
+    return;
+  if (@available(macOS 10.15, *)) {
+    color_sampler_.reset([[NSColorSampler alloc] init]);
+    [color_sampler_ showSamplerWithSelectionHandler:^(NSColor* selectedColor) {
+      if (!selectedColor) {
+        listener_->ColorSelectionCanceled();
+      } else {
+        listener_->ColorSelected(skia::NSSystemColorToSkColor(selectedColor));
+      }
+    }];
+  }
+}
+
+EyeDropperViewMac::~EyeDropperViewMac() {}
+
+std::unique_ptr<content::EyeDropper> ShowEyeDropper(
+    content::RenderFrameHost* frame,
+    content::EyeDropperListener* listener) {
+  if (@available(macOS 10.15, *)) {
+    return std::make_unique<EyeDropperViewMac>(listener);
+  }
+  return nullptr;
+}
diff --git a/chrome/browser/ui/views/frame/browser_frame_header_ash.cc b/chrome/browser/ui/views/frame/browser_frame_header_ash.cc
index b437f7b..07064be 100644
--- a/chrome/browser/ui/views/frame/browser_frame_header_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_frame_header_ash.cc
@@ -34,39 +34,31 @@
                       SkColor background_color,
                       const gfx::Rect& bounds,
                       int image_inset_x,
-                      int image_inset_y,
-                      int alpha) {
+                      int image_inset_y) {
   SkColor opaque_background_color =
       SkColorSetA(background_color, SK_AlphaOPAQUE);
 
-  // When no images are used, just draw a color, with the animation |alpha|
-  // applied.
+  // When no images are used, just draw a color.
   if (frame_image.isNull() && frame_overlay_image.isNull()) {
-    // We use kPlus blending mode so that between the active and inactive
-    // background colors, the result is 255 alpha (i.e. opaque).
-    canvas->DrawColor(SkColorSetA(opaque_background_color, alpha),
-                      SkBlendMode::kPlus);
+    canvas->DrawColor(opaque_background_color);
     return;
   }
 
   // This handles the case where blending is required between one or more images
   // and the background color. In this case we use a SaveLayerWithFlags() call
-  // to draw all 2-3 components into a single layer then apply the alpha to them
-  // together.
+  // to draw all 2-3 components into a single layer.
   const bool blending_required =
-      alpha < 0xFF || (!frame_image.isNull() && !frame_overlay_image.isNull());
+      !frame_image.isNull() && !frame_overlay_image.isNull();
   if (blending_required) {
     cc::PaintFlags flags;
     // We use kPlus blending mode so that between the active and inactive
     // background colors, the result is 255 alpha (i.e. opaque).
     flags.setBlendMode(SkBlendMode::kPlus);
-    flags.setAlpha(alpha);
     canvas->SaveLayerWithFlags(flags);
   }
 
   // Images can be transparent and we expect the background color to be present
-  // behind them. Here the |alpha| will be applied to the background color by
-  // the SaveLayer call, so use |opaque_background_color|.
+  // behind them.
   canvas->DrawColor(opaque_background_color);
   if (!frame_image.isNull()) {
     canvas->TileImageInt(frame_image, image_inset_x, image_inset_y, 0, 0,
@@ -89,7 +81,6 @@
                                  const gfx::Rect& bounds,
                                  int image_inset_x,
                                  int image_inset_y,
-                                 int alpha,
                                  int corner_radius) {
   const SkScalar sk_corner_radius = SkIntToScalar(corner_radius);
   const SkScalar radii[8] = {sk_corner_radius,
@@ -109,7 +100,7 @@
   canvas->ClipPath(frame_path, antialias);
 
   PaintThemedFrame(canvas, frame_image, frame_overlay_image, background_color,
-                   bounds, image_inset_x, image_inset_y, alpha);
+                   bounds, image_inset_x, image_inset_y);
 }
 
 }  // namespace
@@ -147,8 +138,7 @@
 // BrowserFrameHeaderAsh, protected:
 
 void BrowserFrameHeaderAsh::DoPaintHeader(gfx::Canvas* canvas) {
-  PaintFrameImages(canvas, false /* active */);
-  PaintFrameImages(canvas, true /* active */);
+  PaintFrameImages(canvas);
   PaintTitleBar(canvas);
 }
 
@@ -178,13 +168,8 @@
 ///////////////////////////////////////////////////////////////////////////////
 // BrowserFrameHeaderAsh, private:
 
-void BrowserFrameHeaderAsh::PaintFrameImages(gfx::Canvas* canvas, bool active) {
-  int alpha = activation_animation().CurrentValueBetween(0, 0xFF);
-  if (!active)
-    alpha = 0xFF - alpha;
-
-  if (alpha == 0)
-    return;
+void BrowserFrameHeaderAsh::PaintFrameImages(gfx::Canvas* canvas) {
+  const bool active = mode() == Mode::MODE_ACTIVE;
 
   gfx::ImageSkia frame_image =
       appearance_provider_->GetFrameHeaderImage(active);
@@ -201,5 +186,5 @@
                               appearance_provider_->GetFrameHeaderColor(active),
                               GetPaintedBounds(), GetThemeBackgroundXInset(),
                               appearance_provider_->GetFrameHeaderImageYInset(),
-                              alpha, corner_radius);
+                              corner_radius);
 }
diff --git a/chrome/browser/ui/views/frame/browser_frame_header_ash.h b/chrome/browser/ui/views/frame/browser_frame_header_ash.h
index c1ea091c..842c76d 100644
--- a/chrome/browser/ui/views/frame/browser_frame_header_ash.h
+++ b/chrome/browser/ui/views/frame/browser_frame_header_ash.h
@@ -49,9 +49,8 @@
   SkColor GetCurrentFrameColor() const override;
 
  private:
-  // Paints the frame image for the |active| state based on the current value of
-  // the activation animation.
-  void PaintFrameImages(gfx::Canvas* canvas, bool active);
+  // Paints the frame image.
+  void PaintFrameImages(gfx::Canvas* canvas);
 
   AppearanceProvider* appearance_provider_ = nullptr;
 
diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
index 8bd3e26..8bb685c8 100644
--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
+++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_ash.cc
@@ -326,11 +326,8 @@
   if (!ShouldPaint())
     return;
 
-  const ash::FrameHeader::Mode header_mode =
-      ShouldPaintAsActive() ? ash::FrameHeader::MODE_ACTIVE
-                            : ash::FrameHeader::MODE_INACTIVE;
   if (frame_header_)
-    frame_header_->PaintHeader(canvas, header_mode);
+    frame_header_->PaintHeader(canvas);
 }
 
 void BrowserNonClientFrameViewAsh::Layout() {
diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc
index 14ffedb7..471b856 100644
--- a/chrome/browser/ui/views/frame/browser_view.cc
+++ b/chrome/browser/ui/views/frame/browser_view.cc
@@ -122,6 +122,7 @@
 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/new_tab_button.h"
 #include "chrome/browser/ui/views/tabs/tab.h"
+#include "chrome/browser/ui/views/tabs/tab_search_button.h"
 #include "chrome/browser/ui/views/tabs/tab_strip.h"
 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
 #include "chrome/browser/ui/views/toolbar/browser_app_menu_button.h"
@@ -2621,6 +2622,8 @@
 }
 
 void BrowserView::CreateTabSearchBubble() {
+  // TODO(tluk): This should be triggering the TabSearchButton in the tab strip
+  // rather than creating the Tab Search bubble directly.
   TabSearchBubbleView::CreateTabSearchBubble(browser_->profile(),
                                              tabstrip_->tab_search_button());
   base::UmaHistogramEnumeration("Tabs.TabSearch.OpenAction",
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
index ba9873e..4a7ee3d27 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views.cc
@@ -2040,8 +2040,17 @@
 
 void OmniboxViewViews::OnFocusChangedInPage(
     content::FocusedNodeDetails* details) {
-  if (details->is_editable_node)
+  // Elide the URL to the simplified domain (the most security-critical
+  // information) when the user focuses a form text field, which is a key moment
+  // for making security decisions. Ignore the focus event if it didn't come
+  // from a mouse click/tap. Focus via keyboard will trigger elision from
+  // DidGetUserInteraction(), and we want to ignore focuses that aren't from an
+  // explicit user action (e.g., input fields that are autofocused on page
+  // load).
+  if (details->is_editable_node &&
+      details->focus_type == blink::mojom::FocusType::kMouse) {
     MaybeElideURLWithAnimationFromInteraction();
+  }
 }
 
 base::string16 OmniboxViewViews::GetSelectionClipboardText() const {
@@ -2267,10 +2276,11 @@
       break;
 
     case ui::VKEY_SPACE: {
-      if (!control && !alt && !shift && SelectionAtEnd()) {
-        OmniboxPopupModel* popup_model = model()->popup_model();
-        if (popup_model && popup_model->TriggerSelectionAction(
-                               popup_model->selection(), event.time_stamp())) {
+      OmniboxPopupModel* popup_model = model()->popup_model();
+      if (popup_model && !control && !alt && !shift &&
+          popup_model->selection().IsButtonFocused()) {
+        if (popup_model->TriggerSelectionAction(popup_model->selection(),
+                                                event.time_stamp())) {
           return true;
         }
       }
diff --git a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
index 889506f9..e319c804 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_view_views_unittest.cc
@@ -2232,13 +2232,33 @@
   // Focusing a non-editable node should not run the fade-out animation.
   content::FocusedNodeDetails details;
   details.is_editable_node = false;
+  details.focus_type = blink::mojom::FocusType::kMouse;
   omnibox_view()->OnFocusChangedInPage(&details);
   OmniboxViewViews::ElideAnimation* elide_animation =
       omnibox_view()->GetElideAfterInteractionAnimationForTesting();
   EXPECT_FALSE(elide_animation);
 
+  // Focusing via keypress should not run the fade-out animation.
+  details.is_editable_node = true;
+  details.focus_type = blink::mojom::FocusType::kForward;
+  omnibox_view()->OnFocusChangedInPage(&details);
+  elide_animation =
+      omnibox_view()->GetElideAfterInteractionAnimationForTesting();
+  EXPECT_FALSE(elide_animation);
+
+  // Other ways that an element can be focused, such as element.focus() in
+  // JavaScript, have a focus type of kNone and should not run the fade-out
+  // animation.
+  details.is_editable_node = true;
+  details.focus_type = blink::mojom::FocusType::kNone;
+  omnibox_view()->OnFocusChangedInPage(&details);
+  elide_animation =
+      omnibox_view()->GetElideAfterInteractionAnimationForTesting();
+  EXPECT_FALSE(elide_animation);
+
   // Focusing an editable node should run the fade-out animation.
   details.is_editable_node = true;
+  details.focus_type = blink::mojom::FocusType::kMouse;
   omnibox_view()->OnFocusChangedInPage(&details);
   elide_animation =
       omnibox_view()->GetElideAfterInteractionAnimationForTesting();
diff --git a/chrome/browser/ui/views/tabs/tab_search_button.cc b/chrome/browser/ui/views/tabs/tab_search_button.cc
index 8f80daa5..63beef1 100644
--- a/chrome/browser/ui/views/tabs/tab_search_button.cc
+++ b/chrome/browser/ui/views/tabs/tab_search_button.cc
@@ -71,6 +71,11 @@
   observed_bubble_widget_.Remove(bubble_);
   bubble_ = nullptr;
   pressed_lock_.reset();
+  tab_strip()->OnTabSearchBubbleClosed();
+}
+
+bool TabSearchButton::IsBubbleVisible() const {
+  return bubble_ && bubble_->IsVisible();
 }
 
 void TabSearchButton::PaintIcon(gfx::Canvas* canvas) {
diff --git a/chrome/browser/ui/views/tabs/tab_search_button.h b/chrome/browser/ui/views/tabs/tab_search_button.h
index aeb696c..d0f199d 100644
--- a/chrome/browser/ui/views/tabs/tab_search_button.h
+++ b/chrome/browser/ui/views/tabs/tab_search_button.h
@@ -45,6 +45,10 @@
   // views::WidgetObserver:
   void OnWidgetClosing(views::Widget* widget) override;
 
+  bool IsBubbleVisible() const;
+
+  views::Widget* bubble_for_testing() { return bubble_; }
+
  protected:
   // NewTabButton:
   void PaintIcon(gfx::Canvas* canvas) override;
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 03979210..bfb1cb5f 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -38,6 +38,7 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/view_ids.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
 #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/new_tab_button.h"
 #include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
@@ -48,6 +49,7 @@
 #include "chrome/browser/ui/views/tabs/tab_group_underline.h"
 #include "chrome/browser/ui/views/tabs/tab_group_views.h"
 #include "chrome/browser/ui/views/tabs/tab_hover_card_bubble_view.h"
+#include "chrome/browser/ui/views/tabs/tab_search_button.h"
 #include "chrome/browser/ui/views/tabs/tab_slot_view.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_layout_helper.h"
@@ -106,12 +108,6 @@
 #include "ui/aura/window.h"
 #endif
 
-#if BUILDFLAG(ENABLE_TAB_SEARCH)
-#include "chrome/browser/ui/ui_features.h"
-#include "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
-#include "chrome/browser/ui/views/tabs/tab_search_button.h"
-#endif
-
 namespace {
 
 // Distance from the next/previous stacked before before we consider the tab
@@ -1447,6 +1443,11 @@
   ShiftGroupRelative(group, 1);
 }
 
+void TabStrip::OnTabSearchBubbleClosed() {
+  UpdateIdealBounds();
+  AnimateToIdealBounds();
+}
+
 bool TabStrip::ShouldTabBeVisible(const Tab* tab) const {
   // Detached tabs should always be invisible (as they close).
   if (tab->detached())
@@ -2333,12 +2334,13 @@
 }
 
 gfx::Size TabStrip::CalculatePreferredSize() const {
-  if (tab_count() == 0) {
-    return gfx::Size(GetRightSideReservedWidth(),
-                     GetLayoutConstant(TAB_HEIGHT));
+  // The tabs might be out of order due to an active animation or drag, so
+  // we have to check all of them to find the visually trailing-most one.
+  int max_x = 0;
+  for (auto* tab : layout_helper_->GetTabs()) {
+    max_x = std::max(max_x, tab->bounds().right());
   }
-  return gfx::Size(layout_helper_->GetTabs().back()->bounds().right() +
-                       GetRightSideReservedWidth(),
+  return gfx::Size(max_x + GetRightSideReservedWidth(),
                    GetLayoutConstant(TAB_HEIGHT));
 }
 
@@ -2451,7 +2453,6 @@
   new_tab_button_ =
       tab_controls_container_->AddChildView(std::move(new_tab_button));
 
-#if BUILDFLAG(ENABLE_TAB_SEARCH)
   if (base::FeatureList::IsEnabled(features::kTabSearch) &&
       !controller_->GetProfile()->IsIncognitoProfile()) {
     auto tab_search_button = std::make_unique<TabSearchButton>(this, this);
@@ -2466,7 +2467,6 @@
     tab_search_button_ =
         tab_controls_container_->AddChildView(std::move(tab_search_button));
   }
-#endif
 
   UpdateNewTabButtonBorder();
   tab_controls_container_ideal_bounds_.set_size(
@@ -3373,9 +3373,13 @@
         base::FeatureList::IsEnabled(features::kScrollableTabStrip)
             ? trailing_x
             : std::min(available_width_for_tabs, trailing_x);
-
-    tab_controls_container_ideal_bounds_.set_origin(
-        gfx::Point(ntb_x_offset + TabToNewTabButtonSpacing(), 0));
+    // We want to lock the |tab_controls_container_| in place if the Tab Search
+    // bubble is open to prevent the UI sliding across the screen as the tab
+    // strip changes.
+    if (!tab_search_button_ || !tab_search_button_->IsBubbleVisible()) {
+      tab_controls_container_ideal_bounds_.set_origin(
+          gfx::Point(ntb_x_offset + TabToNewTabButtonSpacing(), 0));
+    }
   }
 }
 
diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h
index 83c438c..466d62b 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.h
+++ b/chrome/browser/ui/views/tabs/tab_strip.h
@@ -45,6 +45,7 @@
 class StackedTabStripLayout;
 class Tab;
 class TabHoverCardBubbleView;
+class TabSearchButton;
 class TabStripController;
 class TabStripObserver;
 class TabStripLayoutHelper;
@@ -201,6 +202,12 @@
   // Attempts to move the specified group to the right.
   void ShiftGroupRight(const tab_groups::TabGroupId& group);
 
+  // While the Tab Search bubble is open, the |tab_controls_container_| is kept
+  // in a fixed position in the tab strip. This is called when the bubble is
+  // closed to allow the |tab_controls_container_| to animate to the correct
+  // position.
+  void OnTabSearchBubbleClosed();
+
   // Returns true if the tab is not partly or fully clipped (due to overflow),
   // and the tab couldn't become partly clipped due to changing the selected tab
   // (for example, if currently the strip has the last tab selected, and
@@ -238,7 +245,7 @@
   NewTabButton* new_tab_button() { return new_tab_button_; }
 
   // Returns the TabSearchButton.
-  NewTabButton* tab_search_button() { return tab_search_button_; }
+  TabSearchButton* tab_search_button() { return tab_search_button_; }
 
   // Returns the index of the specified view in the model coordinate system, or
   // -1 if view is closing or not a tab.
@@ -700,7 +707,7 @@
   NewTabButton* new_tab_button_ = nullptr;
   // |tab_search_button_| will be null if features::kTabSearch is disabled or if
   // the current profile is an incognito profile.
-  NewTabButton* tab_search_button_ = nullptr;
+  TabSearchButton* tab_search_button_ = nullptr;
 
   // Ideal bounds of container holding the tab controls.
   gfx::Rect tab_controls_container_ideal_bounds_;
diff --git a/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc b/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc
index b207e9b1..ad3b16af 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_browsertest.cc
@@ -6,17 +6,27 @@
 
 #include <vector>
 
+#include "base/test/scoped_feature_list.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/tabs/tab_group_model.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/ui_features.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/tabs/tab_search_button.h"
 #include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "content/public/test/browser_test.h"
 #include "url/gurl.h"
 
+namespace {
+ui::MouseEvent GetDummyEvent() {
+  return ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
+                        base::TimeTicks::Now(), 0, 0);
+}
+}  // namespace
+
 // Integration tests for interactions between TabStripModel and TabStrip.
 class TabStripBrowsertest : public InProcessBrowserTest {
  public:
@@ -741,10 +751,7 @@
   AppendTab();
 
   tab_groups::TabGroupId group = AddTabToNewGroup(0);
-  ui::MouseEvent dummy_event =
-      ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
-                     base::TimeTicks::Now(), 0, 0);
-  tab_strip()->SelectTab(tab_strip()->tab_at(0), dummy_event);
+  tab_strip()->SelectTab(tab_strip()->tab_at(0), GetDummyEvent());
   ASSERT_EQ(0, tab_strip()->controller()->GetActiveIndex());
   ASSERT_FALSE(tab_strip()->controller()->IsGroupCollapsed(group));
   tab_strip()->controller()->ToggleTabGroupCollapsedState(group);
@@ -758,10 +765,7 @@
   AppendTab();
 
   tab_groups::TabGroupId group = AddTabToNewGroup(1);
-  ui::MouseEvent dummy_event =
-      ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
-                     base::TimeTicks::Now(), 0, 0);
-  tab_strip()->SelectTab(tab_strip()->tab_at(1), dummy_event);
+  tab_strip()->SelectTab(tab_strip()->tab_at(1), GetDummyEvent());
   ASSERT_EQ(1, tab_strip()->controller()->GetActiveIndex());
   ASSERT_FALSE(tab_strip()->controller()->IsGroupCollapsed(group));
   tab_strip()->controller()->ToggleTabGroupCollapsedState(group);
@@ -776,10 +780,7 @@
   AppendTab();
 
   tab_groups::TabGroupId group = AddTabToNewGroup(0);
-  ui::MouseEvent dummy_event =
-      ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
-                     base::TimeTicks::Now(), 0, 0);
-  tab_strip()->SelectTab(tab_strip()->tab_at(1), dummy_event);
+  tab_strip()->SelectTab(tab_strip()->tab_at(1), GetDummyEvent());
   ASSERT_EQ(1, tab_strip()->controller()->GetActiveIndex());
   ASSERT_FALSE(tab_strip()->controller()->IsGroupCollapsed(group));
   tab_strip()->controller()->ToggleTabGroupCollapsedState(group);
@@ -809,9 +810,31 @@
   ASSERT_TRUE(tab_strip()->controller()->IsGroupCollapsed(group));
   ASSERT_EQ(1, tab_strip()->controller()->GetActiveIndex());
 
-  ui::MouseEvent dummy_event =
-      ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::PointF(), gfx::PointF(),
-                     base::TimeTicks::Now(), 0, 0);
-  tab_strip()->SelectTab(tab_strip()->tab_at(0), dummy_event);
+  tab_strip()->SelectTab(tab_strip()->tab_at(0), GetDummyEvent());
   EXPECT_FALSE(tab_strip()->controller()->IsGroupCollapsed(group));
 }
+
+class TabSearchButtonTest : public TabStripBrowsertest {
+ public:
+  void SetUp() override {
+    scoped_feature_list_.InitWithFeatureState(features::kTabSearch, true);
+    TabStripBrowsertest::SetUp();
+  }
+
+ private:
+  base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(TabSearchButtonTest, TabSearchBubble_CreateAndClose) {
+  TabSearchButton* tab_search_button = tab_strip()->tab_search_button();
+
+  DCHECK_EQ(nullptr, tab_search_button->bubble_for_testing());
+  tab_search_button->ButtonPressed(tab_search_button, GetDummyEvent());
+  DCHECK_NE(nullptr, tab_search_button->bubble_for_testing());
+
+  // Close the tab search bubble widget, the bubble should be cleared from the
+  // TabSearchButton.
+  tab_search_button->bubble_for_testing()->CloseWithReason(
+      views::Widget::ClosedReason::kUnspecified);
+  DCHECK_EQ(nullptr, tab_search_button->bubble_for_testing());
+}
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom b/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom
index a28efd40..4e2f01cb 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom
@@ -4,12 +4,13 @@
 
 module new_tab_page.mojom;
 
+import "chrome/common/search/omnibox.mojom";
+import "mojo/public/mojom/base/string16.mojom";
 import "mojo/public/mojom/base/text_direction.mojom";
 import "mojo/public/mojom/base/time.mojom";
 import "skia/public/mojom/skcolor.mojom";
 import "url/mojom/url.mojom";
-import "mojo/public/mojom/base/string16.mojom";
-import "chrome/common/search/omnibox.mojom";
+
 
 struct OneGoogleBarParts {
   string bar_html;
@@ -251,6 +252,38 @@
   kLinkCopy,
 };
 
+struct PromoImagePart {
+  url.mojom.Url image_url;
+  url.mojom.Url target;
+};
+
+struct PromoLinkPart {
+  string? color;
+  string text;
+  url.mojom.Url url;
+};
+
+struct PromoTextPart {
+  string? color;
+  string text;
+};
+
+union PromoPart {
+  PromoImagePart image;
+  PromoLinkPart link;
+  PromoTextPart text;
+};
+
+// Homepage promo used to display a message with a link on the NTP.
+struct Promo {
+  // A unique identifier for this promo.
+  string? id;
+  // URL to ping to log a promo impression. May be invalid.
+  url.mojom.Url? log_url;
+  // Middle slot promo data.
+  array<PromoPart> middle_slot_parts;
+};
+
 // Action the user performed while using the customize dialog. Used for metrics
 // logging only. Actions correspond to items in NTPLoggingEventType.
 enum CustomizeDialogAction {
@@ -360,6 +393,8 @@
   // parameters to the endpoint that serves the OneGoogleBar parts with
   // |query_params|.
   GetOneGoogleBarParts(string query_params) => (OneGoogleBarParts? parts);
+  // Get the middle slot promo if it exists.
+  GetPromo() => (Promo? promo);
 
   // ======= METRICS =======
   // Logs that |tiles| were displayed / updated at |time|. The first instance of
@@ -368,8 +403,9 @@
   // Logs that the One Google Bar was added to the DOM / loaded in an iframe at
   // |time|.
   OnOneGoogleBarRendered(double time);
-  // Logs that the promo iframe was loaded at |time|.
-  OnPromoRendered(double time);
+  // Logs that the promo iframe was loaded at |time| and pings |log_url| for
+  // promo metrics logging.
+  OnPromoRendered(double time, url.mojom.Url? log_url);
   // Logs that |tile| at position |index| was triggered to navigate to that
   // most visited entry.
   OnMostVisitedTileNavigation(MostVisitedTile tile, uint32 index);
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
index 8c872e06..f030459 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc
@@ -10,6 +10,8 @@
 #include "base/files/file_path.h"
 #include "base/i18n/rtl.h"
 #include "base/json/json_reader.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/metrics/user_metrics.h"
@@ -22,6 +24,7 @@
 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service_factory.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
+#include "chrome/browser/browser_features.h"
 #include "chrome/browser/favicon/favicon_service_factory.h"
 #include "chrome/browser/history/history_service_factory.h"
 #include "chrome/browser/predictors/autocomplete_action_predictor.h"
@@ -34,6 +37,7 @@
 #include "chrome/browser/search/chrome_colors/chrome_colors_service.h"
 #include "chrome/browser/search/instant_service.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_service_factory.h"
+#include "chrome/browser/search/promos/promo_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/search_provider_logos/logo_service_factory.h"
 #include "chrome/browser/ui/bookmarks/bookmark_stats.h"
@@ -287,6 +291,76 @@
   return doodle;
 }
 
+new_tab_page::mojom::PromoPtr MakePromo(const PromoData& data) {
+  // |data.middle_slot_json| is safe to be decoded here. The JSON string is part
+  // of a larger JSON initially decoded using the data decoder utility in the
+  // PromoService to base::Value. The middle-slot promo part is then reencoded
+  // from base::Value to a JSON string stored in |data.middle_slot_json|.
+  auto middle_slot = base::JSONReader::Read(data.middle_slot_json);
+  if (!middle_slot.has_value() ||
+      middle_slot.value().FindBoolPath("hidden").value_or(false)) {
+    return nullptr;
+  }
+  auto promo = new_tab_page::mojom::Promo::New();
+  promo->id = data.promo_id;
+  if (middle_slot.has_value()) {
+    auto* parts = middle_slot.value().FindListPath("part");
+    if (parts) {
+      std::vector<new_tab_page::mojom::PromoPartPtr> mojom_parts;
+      for (const base::Value& part : parts->GetList()) {
+        if (part.FindKey("image")) {
+          auto mojom_image = new_tab_page::mojom::PromoImagePart::New();
+          auto* image_url = part.FindStringPath("image.image_url");
+          if (!image_url || image_url->empty()) {
+            continue;
+          }
+          mojom_image->image_url = GURL(*image_url);
+          auto* target = part.FindStringPath("image.target");
+          if (target && !target->empty()) {
+            mojom_image->target = GURL(*target);
+          }
+          mojom_parts.push_back(
+              new_tab_page::mojom::PromoPart::NewImage(std::move(mojom_image)));
+        } else if (part.FindKey("link")) {
+          auto mojom_link = new_tab_page::mojom::PromoLinkPart::New();
+          auto* url = part.FindStringPath("link.url");
+          if (!url || url->empty()) {
+            continue;
+          }
+          mojom_link->url = GURL(*url);
+          auto* text = part.FindStringPath("link.text");
+          if (!text || text->empty()) {
+            continue;
+          }
+          mojom_link->text = *text;
+          auto* color = part.FindStringPath("link.color");
+          if (color && !color->empty()) {
+            mojom_link->color = *color;
+          }
+          mojom_parts.push_back(
+              new_tab_page::mojom::PromoPart::NewLink(std::move(mojom_link)));
+        } else if (part.FindKey("text")) {
+          auto mojom_text = new_tab_page::mojom::PromoTextPart::New();
+          auto* text = part.FindStringPath("text.text");
+          if (!text || text->empty()) {
+            continue;
+          }
+          mojom_text->text = *text;
+          auto* color = part.FindStringPath("text.color");
+          if (color && !color->empty()) {
+            mojom_text->color = *color;
+          }
+          mojom_parts.push_back(
+              new_tab_page::mojom::PromoPart::NewText(std::move(mojom_text)));
+        }
+      }
+      promo->middle_slot_parts = std::move(mojom_parts);
+    }
+  }
+  promo->log_url = data.promo_log_url;
+  return promo;
+}
+
 }  // namespace
 
 NewTabPageHandler::NewTabPageHandler(
@@ -318,11 +392,14 @@
       web_contents_(web_contents),
       ntp_navigation_start_time_(ntp_navigation_start_time),
       logger_(logger),
+      promo_service_(PromoServiceFactory::GetForProfile(profile)),
       page_{std::move(pending_page)},
       receiver_{this, std::move(pending_page_handler)} {
   CHECK(instant_service_);
   CHECK(ntp_background_service_);
   CHECK(logo_service_);
+  CHECK(one_google_bar_service_);
+  CHECK(promo_service_);
   CHECK(web_contents_);
   CHECK(logger_);
   instant_service_->AddObserver(this);
@@ -330,11 +407,8 @@
   instant_service_->UpdateNtpTheme();
   OmniboxTabHelper::CreateForWebContents(web_contents);
   OmniboxTabHelper::FromWebContents(web_contents_)->AddObserver(this);
-  // |one_google_bar_service_| is null in incognito, or when the feature is
-  // disabled.
-  if (one_google_bar_service_) {
-    one_google_bar_service_observer_.Add(one_google_bar_service_);
-  }
+  promo_service_observer_.Add(promo_service_);
+  one_google_bar_service_observer_.Add(one_google_bar_service_);
 }
 
 NewTabPageHandler::~NewTabPageHandler() {
@@ -613,6 +687,79 @@
   one_google_bar_service_->Refresh();
 }
 
+void NewTabPageHandler::GetPromo(GetPromoCallback callback) {
+  // Replace the promo URL with "command:<id>" if such a command ID is set
+  // via the feature params.
+  const std::string command_id = base::GetFieldTrialParamValueByFeature(
+      features::kPromoBrowserCommands, features::kPromoBrowserCommandIdParam);
+  if (!command_id.empty()) {
+    auto promo = new_tab_page::mojom::Promo::New();
+    std::vector<new_tab_page::mojom::PromoPartPtr> parts;
+    auto image = new_tab_page::mojom::PromoImagePart::New();
+    // Warning symbol used as the test image.
+    image->image_url = GURL(
+        "data:image/"
+        "svg+xml;base64,"
+        "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9Ii01IC"
+        "01IDU4IDU4IiBmaWxsPSIjZmRkNjMzIj48cGF0aCBkPSJNMiA0Mmg0NEwyNCA0IDIgNDJ6"
+        "bTI0LTZoLTR2LTRoNHY0em0wLThoLTR2LThoNHY4eiIvPjwvc3ZnPg==");
+    image->target = GURL("command:" + command_id);
+    parts.push_back(new_tab_page::mojom::PromoPart::NewImage(std::move(image)));
+    auto link = new_tab_page::mojom::PromoLinkPart::New();
+    link->url = GURL("command:" + command_id);
+    link->text = "Test command: " + command_id;
+    parts.push_back(new_tab_page::mojom::PromoPart::NewLink(std::move(link)));
+    promo->middle_slot_parts = std::move(parts);
+    std::move(callback).Run(std::move(promo));
+    return;
+  }
+
+  promo_callbacks_.push_back(std::move(callback));
+  if (promo_service_->promo_data().has_value()) {
+    OnPromoDataUpdated();
+  }
+  promo_load_start_time_ = base::TimeTicks::Now();
+  promo_service_->Refresh();
+}
+
+void NewTabPageHandler::OnPromoDataUpdated() {
+  if (promo_load_start_time_.has_value()) {
+    base::TimeDelta duration = base::TimeTicks::Now() - *promo_load_start_time_;
+    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency2", duration);
+    if (promo_service_->promo_status() == PromoService::Status::OK_WITH_PROMO) {
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.Promos.RequestLatency2.SuccessWithPromo", duration);
+    } else if (promo_service_->promo_status() ==
+               PromoService::Status::OK_BUT_BLOCKED) {
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.Promos.RequestLatency2.SuccessButBlocked", duration);
+    } else if (promo_service_->promo_status() ==
+               PromoService::Status::OK_WITHOUT_PROMO) {
+      UMA_HISTOGRAM_MEDIUM_TIMES(
+          "NewTabPage.Promos.RequestLatency2.SuccessWithoutPromo", duration);
+    } else {
+      UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency2.Failure",
+                                 duration);
+    }
+    promo_load_start_time_ = base::nullopt;
+  }
+
+  const auto& data = promo_service_->promo_data();
+  for (auto& callback : promo_callbacks_) {
+    if (data.has_value() && !data->promo_html.empty()) {
+      std::move(callback).Run(MakePromo(data.value()));
+    } else {
+      std::move(callback).Run(nullptr);
+    }
+  }
+  promo_callbacks_.clear();
+}
+
+void NewTabPageHandler::OnPromoServiceShuttingDown() {
+  promo_service_observer_.RemoveAll();
+  promo_service_ = nullptr;
+}
+
 void NewTabPageHandler::OnMostVisitedTilesRendered(
     std::vector<new_tab_page::mojom::MostVisitedTilePtr> tiles,
     double time) {
@@ -630,9 +777,13 @@
                     base::Time::FromJsTime(time) - ntp_navigation_start_time_);
 }
 
-void NewTabPageHandler::OnPromoRendered(double time) {
+void NewTabPageHandler::OnPromoRendered(double time,
+                                        const base::Optional<GURL>& log_url) {
   logger_->LogEvent(NTP_MIDDLE_SLOT_PROMO_SHOWN,
                     base::Time::FromJsTime(time) - ntp_navigation_start_time_);
+  if (log_url.has_value() && log_url->is_valid()) {
+    Fetch(*log_url, base::BindOnce([](bool, std::unique_ptr<std::string>) {}));
+  }
 }
 
 void NewTabPageHandler::OnMostVisitedTileNavigation(
@@ -1342,13 +1493,13 @@
       net::DefineNetworkTrafficAnnotation("new_tab_page_handler", R"(
         semantics {
           sender: "New Tab Page"
-          description: "Logs impression and interaction with the doodle."
+          description: "Logs impression and interaction with doodle or promo."
           trigger:
-            "Showing or clicking on the doodle on the New Tab Page. Desktop "
-            "only."
+            "Showing or clicking on the doodle or promo on the New Tab Page. "
+            "Desktop only."
           data:
-            "String identifiying todays doodle and token identifying a single "
-            "doodle interaction session. Data does not contain PII."
+            "String identifiying todays doodle or promo and token identifying "
+            "a single interaction session. Data does not contain PII."
           destination: GOOGLE_OWNED_SERVICE
         }
         policy {
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h
index 21c0258..bc4c231 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h
@@ -6,9 +6,11 @@
 #define CHROME_BROWSER_UI_WEBUI_NEW_TAB_PAGE_NEW_TAB_PAGE_HANDLER_H_
 
 #include <unordered_map>
+#include <vector>
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
+#include "base/optional.h"
 #include "base/scoped_observer.h"
 #include "base/time/time.h"
 #include "chrome/browser/bitmap_fetcher/bitmap_fetcher_service.h"
@@ -16,6 +18,8 @@
 #include "chrome/browser/search/instant_service_observer.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_service.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_service_observer.h"
+#include "chrome/browser/search/promos/promo_service.h"
+#include "chrome/browser/search/promos/promo_service_observer.h"
 #include "chrome/browser/ui/omnibox/omnibox_tab_helper.h"
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page.mojom.h"
 #include "chrome/common/search/instant_types.h"
@@ -54,7 +58,8 @@
                           public OmniboxTabHelper::Observer,
                           public OneGoogleBarServiceObserver,
                           public ui::SelectFileDialog::Listener,
-                          public AutocompleteController::Observer {
+                          public AutocompleteController::Observer,
+                          public PromoServiceObserver {
  public:
   NewTabPageHandler(mojo::PendingReceiver<new_tab_page::mojom::PageHandler>
                         pending_page_handler,
@@ -103,11 +108,13 @@
       ChooseLocalCustomBackgroundCallback callback) override;
   void GetOneGoogleBarParts(const std::string& ogdeb_value,
                             GetOneGoogleBarPartsCallback callback) override;
+  void GetPromo(GetPromoCallback callback) override;
   void OnMostVisitedTilesRendered(
       std::vector<new_tab_page::mojom::MostVisitedTilePtr> tiles,
       double time) override;
   void OnOneGoogleBarRendered(double time) override;
-  void OnPromoRendered(double time) override;
+  void OnPromoRendered(double time,
+                       const base::Optional<GURL>& log_url) override;
   void OnMostVisitedTileNavigation(new_tab_page::mojom::MostVisitedTilePtr tile,
                                    uint32_t index) override;
   void OnCustomizeDialogAction(
@@ -161,6 +168,10 @@
   void OnOneGoogleBarDataUpdated() override;
   void OnOneGoogleBarServiceShuttingDown() override;
 
+  // PromoServiceObserver:
+  void OnPromoDataUpdated() override;
+  void OnPromoServiceShuttingDown() override;
+
   // SelectFileDialog::Listener:
   void FileSelected(const base::FilePath& path,
                     int index,
@@ -225,6 +236,11 @@
   std::unordered_map<const network::SimpleURLLoader*,
                      std::unique_ptr<network::SimpleURLLoader>>
       loader_map_;
+  std::vector<GetPromoCallback> promo_callbacks_;
+  PromoService* promo_service_;
+  ScopedObserver<PromoService, PromoServiceObserver> promo_service_observer_{
+      this};
+  base::Optional<base::TimeTicks> promo_load_start_time_;
 
   // These are located at the end of the list of member variables to ensure the
   // WebUI page is disconnected before other members are destroyed.
diff --git a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
index e0f74b9..6eb933e 100644
--- a/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
+++ b/chrome/browser/ui/webui/new_tab_page/new_tab_page_ui.cc
@@ -22,6 +22,7 @@
 #include "chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.h"
 #include "chrome/browser/ui/webui/new_tab_page/promo_browser_command/promo_browser_command_handler.h"
 #include "chrome/browser/ui/webui/new_tab_page/untrusted_source.h"
+#include "chrome/browser/ui/webui/sanitized_image_source.h"
 #include "chrome/browser/ui/webui/theme_source.h"
 #include "chrome/browser/ui/webui/webui_util.h"
 #include "chrome/common/url_constants.h"
@@ -269,6 +270,8 @@
                      instant_service_->IsCustomBackgroundDisabledByPolicy());
   content::WebUIDataSource::Add(profile_, source);
 
+  content::URLDataSource::Add(profile_,
+                              std::make_unique<SanitizedImageSource>(profile_));
   content::URLDataSource::Add(
       profile_, std::make_unique<FaviconSource>(
                     profile_, chrome::FaviconUrlFormat::kFavicon2));
diff --git a/chrome/browser/ui/webui/new_tab_page/untrusted_source.cc b/chrome/browser/ui/webui/new_tab_page/untrusted_source.cc
index c583b9c..e868748e 100644
--- a/chrome/browser/ui/webui/new_tab_page/untrusted_source.cc
+++ b/chrome/browser/ui/webui/new_tab_page/untrusted_source.cc
@@ -12,7 +12,6 @@
 #include "base/i18n/rtl.h"
 #include "base/memory/ref_counted_memory.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/metrics/field_trial_params.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/optional.h"
 #include "base/strings/string_piece.h"
@@ -20,13 +19,10 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
-#include "chrome/browser/browser_features.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/search/ntp_features.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_data.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_service_factory.h"
-#include "chrome/browser/search/promos/promo_data.h"
-#include "chrome/browser/search/promos/promo_service_factory.h"
 #include "chrome/browser/ui/search/ntp_user_data_logger.h"
 #include "chrome/common/url_constants.h"
 #include "chrome/grit/new_tab_page_resources.h"
@@ -71,14 +67,7 @@
 UntrustedSource::UntrustedSource(Profile* profile)
     : one_google_bar_service_(
           OneGoogleBarServiceFactory::GetForProfile(profile)),
-      profile_(profile),
-      promo_service_(PromoServiceFactory::GetForProfile(profile)) {
-  // |promo_service_| is null in incognito, or when the feature is
-  // disabled.
-  if (promo_service_) {
-    promo_service_observer_.Add(promo_service_);
-  }
-
+      profile_(profile) {
   // |one_google_bar_service_| is null in incognito, or when the feature is
   // disabled.
   if (one_google_bar_service_) {
@@ -147,21 +136,6 @@
         bundle.LoadDataResourceBytes(IDR_NEW_TAB_PAGE_ONE_GOOGLE_BAR_API_JS));
     return;
   }
-  if (path == "promo" && promo_service_) {
-    promo_callbacks_.push_back(std::move(callback));
-    if (promo_service_->promo_data().has_value()) {
-      OnPromoDataUpdated();
-    }
-    promo_load_start_time_ = base::TimeTicks::Now();
-    promo_service_->Refresh();
-    return;
-  }
-  if (path == "promo.js") {
-    ui::ResourceBundle& bundle = ui::ResourceBundle::GetSharedInstance();
-    std::move(callback).Run(
-        bundle.LoadDataResourceBytes(IDR_NEW_TAB_PAGE_UNTRUSTED_PROMO_JS));
-    return;
-  }
   if (path == "image" && url_param.is_valid() &&
       (url_param.SchemeIs(url::kHttpsScheme) ||
        url_param.SchemeIs(content::kChromeUIUntrustedScheme))) {
@@ -255,10 +229,9 @@
   }
   const std::string path = url.path().substr(1);
   return path == "one-google-bar" || path == "one_google_bar.js" ||
-         path == "promo" || path == "promo.js" || path == "image" ||
-         path == "background_image" || path == "custom_background_image" ||
-         path == "background_image.js" || path == "background.jpg" ||
-         path == "one_google_bar_api.js";
+         path == "image" || path == "background_image" ||
+         path == "custom_background_image" || path == "background_image.js" ||
+         path == "background.jpg" || path == "one_google_bar_api.js";
 }
 
 void UntrustedSource::OnOneGoogleBarDataUpdated() {
@@ -300,59 +273,6 @@
   one_google_bar_service_ = nullptr;
 }
 
-void UntrustedSource::OnPromoDataUpdated() {
-  if (promo_load_start_time_.has_value()) {
-    base::TimeDelta duration = base::TimeTicks::Now() - *promo_load_start_time_;
-    UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency2", duration);
-    if (promo_service_->promo_status() == PromoService::Status::OK_WITH_PROMO) {
-      UMA_HISTOGRAM_MEDIUM_TIMES(
-          "NewTabPage.Promos.RequestLatency2.SuccessWithPromo", duration);
-    } else if (promo_service_->promo_status() ==
-               PromoService::Status::OK_BUT_BLOCKED) {
-      UMA_HISTOGRAM_MEDIUM_TIMES(
-          "NewTabPage.Promos.RequestLatency2.SuccessButBlocked", duration);
-    } else if (promo_service_->promo_status() ==
-               PromoService::Status::OK_WITHOUT_PROMO) {
-      UMA_HISTOGRAM_MEDIUM_TIMES(
-          "NewTabPage.Promos.RequestLatency2.SuccessWithoutPromo", duration);
-    } else {
-      UMA_HISTOGRAM_MEDIUM_TIMES("NewTabPage.Promos.RequestLatency2.Failure",
-                                 duration);
-    }
-    promo_load_start_time_ = base::nullopt;
-  }
-
-  const auto& data = promo_service_->promo_data();
-  std::string html;
-  if (data.has_value() && !data->promo_html.empty()) {
-    std::string promo_html = data->promo_html;
-
-    // Replace the promo URL with "command:<id>" if such a command ID is set
-    // via the feature params.
-    const std::string command_id = base::GetFieldTrialParamValueByFeature(
-        features::kPromoBrowserCommands, features::kPromoBrowserCommandIdParam);
-    if (!command_id.empty()) {
-      re2::RE2::GlobalReplace(&promo_html, re2::RE2("href=\"([^\\s]+)\""),
-                              "href=\"command:" + command_id + "\"");
-    }
-
-    ui::TemplateReplacements replacements;
-    replacements["textdirection"] = base::i18n::IsRTL() ? "rtl" : "ltr";
-    replacements["data"] = promo_html;
-
-    html = FormatTemplate(IDR_NEW_TAB_PAGE_UNTRUSTED_PROMO_HTML, replacements);
-  }
-  for (auto& callback : promo_callbacks_) {
-    std::move(callback).Run(base::RefCountedString::TakeString(&html));
-  }
-  promo_callbacks_.clear();
-}
-
-void UntrustedSource::OnPromoServiceShuttingDown() {
-  promo_service_observer_.RemoveAll();
-  promo_service_ = nullptr;
-}
-
 void UntrustedSource::ServeBackgroundImage(
     const GURL& url,
     const GURL& url_2x,
diff --git a/chrome/browser/ui/webui/new_tab_page/untrusted_source.h b/chrome/browser/ui/webui/new_tab_page/untrusted_source.h
index 1caf16a..420a55c 100644
--- a/chrome/browser/ui/webui/new_tab_page/untrusted_source.h
+++ b/chrome/browser/ui/webui/new_tab_page/untrusted_source.h
@@ -11,8 +11,6 @@
 #include "base/scoped_observer.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_service.h"
 #include "chrome/browser/search/one_google_bar/one_google_bar_service_observer.h"
-#include "chrome/browser/search/promos/promo_service.h"
-#include "chrome/browser/search/promos/promo_service_observer.h"
 #include "content/public/browser/url_data_source.h"
 
 class Profile;
@@ -41,8 +39,7 @@
 //         * positionY: (optional) CSS background-position-y property.
 //   Each of those helpers only accept URLs with HTTPS or chrome-untrusted:.
 class UntrustedSource : public content::URLDataSource,
-                        public OneGoogleBarServiceObserver,
-                        public PromoServiceObserver {
+                        public OneGoogleBarServiceObserver {
  public:
   explicit UntrustedSource(Profile* profile);
   ~UntrustedSource() override;
@@ -70,10 +67,6 @@
   void OnOneGoogleBarDataUpdated() override;
   void OnOneGoogleBarServiceShuttingDown() override;
 
-  // PromoServiceObserver:
-  void OnPromoDataUpdated() override;
-  void OnPromoServiceShuttingDown() override;
-
   void ServeBackgroundImage(const GURL& url,
                             const GURL& url_2x,
                             const std::string& size,
@@ -90,11 +83,6 @@
       one_google_bar_service_observer_{this};
   base::Optional<base::TimeTicks> one_google_bar_load_start_time_;
   Profile* profile_;
-  std::vector<content::URLDataSource::GotDataCallback> promo_callbacks_;
-  PromoService* promo_service_;
-  ScopedObserver<PromoService, PromoServiceObserver> promo_service_observer_{
-      this};
-  base::Optional<base::TimeTicks> promo_load_start_time_;
 };
 
 #endif  // CHROME_BROWSER_UI_WEBUI_NEW_TAB_PAGE_UNTRUSTED_SOURCE_H_
diff --git a/chrome/test/data/pdf/annotations_feature_enabled_test.js b/chrome/test/data/pdf/annotations_feature_enabled_test.js
index d5a422d..52dad0c9 100644
--- a/chrome/test/data/pdf/annotations_feature_enabled_test.js
+++ b/chrome/test/data/pdf/annotations_feature_enabled_test.js
@@ -59,11 +59,21 @@
       viewer.viewport.setZoom(2);
       chrome.test.assertEq(2, cameras.length);
 
-      window.scrollTo(100, 100);
+      const updateEnabled =
+          document.documentElement.hasAttribute('pdf-viewer-update-enabled');
+      const scrollingContainer =
+          updateEnabled ? viewer.shadowRoot.querySelector('#main') : window;
+      scrollingContainer.scrollTo(100, 100);
       await animationFrame();
 
       chrome.test.assertEq(3, cameras.length);
 
+      if (updateEnabled) {
+        // TODO (https://crbug.com/1120279): Determine what the expectations
+        // below should be for the new UI and fix if needed to meet them.
+        return;
+      }
+
       const expectations = [
         {top: 44.25, left: -106.5, right: 718.5, bottom: -448.5},
         {top: 23.25, left: -3.75, right: 408.75, bottom: -223.125},
@@ -87,8 +97,7 @@
       inkHost.ink_.setAnnotationTool = value => tool = value;
 
       // Pen defaults.
-      const viewerPdfToolbar =
-          viewer.shadowRoot.querySelector('viewer-pdf-toolbar');
+      const viewerPdfToolbar = viewer.shadowRoot.querySelector('#toolbar');
       const viewerAnnotationsBar =
           viewerPdfToolbar.shadowRoot.querySelector('viewer-annotations-bar');
       const pen = viewerAnnotationsBar.shadowRoot.querySelector('#pen');
@@ -148,8 +157,7 @@
   function testStrokeUndoRedo() {
     testAsync(async () => {
       const inkHost = contentElement();
-      const viewerPdfToolbar =
-          viewer.shadowRoot.querySelector('viewer-pdf-toolbar');
+      const viewerPdfToolbar = viewer.shadowRoot.querySelector('#toolbar');
       const viewerAnnotationsBar =
           viewerPdfToolbar.shadowRoot.querySelector('viewer-annotations-bar');
       const undo = viewerAnnotationsBar.shadowRoot.querySelector('#undo');
diff --git a/chrome/test/data/pdf/basic_plugin_test.js b/chrome/test/data/pdf/basic_plugin_test.js
index 3910002..92d19d1 100644
--- a/chrome/test/data/pdf/basic_plugin_test.js
+++ b/chrome/test/data/pdf/basic_plugin_test.js
@@ -18,6 +18,14 @@
     // Verify that the initial zoom is less than or equal to 100%.
     chrome.test.assertTrue(viewer.viewport.getZoom() <= 1);
 
+    // TODO (https://crbug.com/1120279): Currently, calling setZoom() on the
+    // viewport with the new UI enabled triggers a crash in Blink. Fix this
+    // issue and remove the lines below.
+    if (document.documentElement.hasAttribute('pdf-viewer-update-enabled')) {
+      chrome.test.succeed();
+      return;
+    }
+
     viewer.viewport.setZoom(1);
     const sizer = viewer.shadowRoot.querySelector('#sizer');
     chrome.test.assertEq(826, sizer.offsetWidth);
diff --git a/chrome/test/data/pdf/basic_test.js b/chrome/test/data/pdf/basic_test.js
index e803f137..b415fc7 100644
--- a/chrome/test/data/pdf/basic_test.js
+++ b/chrome/test/data/pdf/basic_test.js
@@ -14,12 +14,11 @@
   function testHasElements() {
     const viewer = /** @type {!PDFViewerElement} */ (
         document.body.querySelector('pdf-viewer'));
-    const elementNames = [
-      'viewer-pdf-toolbar',
-      'viewer-zoom-toolbar',
-      'viewer-password-screen',
-      'viewer-error-screen',
-    ];
+    const commonElements = ['viewer-password-screen', 'viewer-error-screen'];
+    const elementNames =
+        document.documentElement.hasAttribute('pdf-viewer-update-enabled') ?
+        ['viewer-pdf-toolbar-new', 'viewer-pdf-sidenav', ...commonElements] :
+        ['viewer-pdf-toolbar', 'viewer-zoom-toolbar', ...commonElements];
     for (let i = 0; i < elementNames.length; i++) {
       const elements = viewer.shadowRoot.querySelectorAll(elementNames[i]);
       chrome.test.assertEq(1, elements.length);
@@ -52,11 +51,17 @@
         document.body.querySelector('pdf-viewer'));
     const toolbar = /** @type {!ViewerPdfToolbarElement} */ (
         viewer.shadowRoot.querySelector('#toolbar'));
-    toolbar.$$('#pageselector').pageSelector.focus();
+    toolbar.shadowRoot.querySelector('viewer-page-selector')
+        .pageSelector.focus();
     chrome.test.assertTrue(shouldIgnoreKeyEvents(toolbar));
 
     // Test case where the active element has a shadow root of its own.
-    toolbar.$['rotate-right'].focus();
+    const rotateButton =
+        document.documentElement.hasAttribute('pdf-viewer-update-enabled') ?
+        toolbar.shadowRoot.querySelector(
+            'cr-icon-button[iron-icon=\'pdf:rotate-left\']') :
+        toolbar.$['rotate-right'];
+    rotateButton.focus();
     chrome.test.assertFalse(shouldIgnoreKeyEvents(toolbar));
 
     chrome.test.assertFalse(
@@ -70,6 +75,13 @@
    * pressing escape.
    */
   function testOpenCloseBookmarks() {
+    // Test is not relevant for the new viewer, as bookmarks are no longer in a
+    // dropdown.
+    if (document.documentElement.hasAttribute('pdf-viewer-update-enabled')) {
+      chrome.test.succeed();
+      return;
+    }
+
     const viewer = /** @type {!PDFViewerElement} */ (
         document.body.querySelector('pdf-viewer'));
     const toolbar = /** @type {!ViewerPdfToolbarElement} */ (
diff --git a/chrome/test/data/pdf/page_change_test.js b/chrome/test/data/pdf/page_change_test.js
index 0473456d..59d2935 100644
--- a/chrome/test/data/pdf/page_change_test.js
+++ b/chrome/test/data/pdf/page_change_test.js
@@ -21,7 +21,6 @@
 function resetDocument() {
   const viewer = getViewer();
   viewer.viewport.goToPage(0);
-  viewer.viewport.setZoom(1);
   simulateFormFocusChange(false);
 }
 
diff --git a/chrome/test/data/webui/new_tab_page/app_test.js b/chrome/test/data/webui/new_tab_page/app_test.js
index e3efe75..6d27da31 100644
--- a/chrome/test/data/webui/new_tab_page/app_test.js
+++ b/chrome/test/data/webui/new_tab_page/app_test.js
@@ -51,6 +51,7 @@
     testProxy.handler.setResultFor('getOneGoogleBarParts', Promise.resolve({
       parts: null,
     }));
+    testProxy.handler.setResultFor('getPromo', Promise.resolve({promo: null}));
     testProxy.setResultMapperFor('matchMedia', () => ({
                                                  addListener() {},
                                                  removeListener() {},
@@ -411,8 +412,10 @@
       data: {
         frameType: 'one-google-bar',
         messageType: 'execute-browser-command',
-        commandId,
-        clickInfo,
+        data: {
+          commandId,
+          clickInfo,
+        },
       },
       source: window,
       origin: window.origin,
diff --git a/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.js b/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.js
new file mode 100644
index 0000000..3998639
--- /dev/null
+++ b/chrome/test/data/webui/new_tab_page/middle_slot_promo_test.js
@@ -0,0 +1,120 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {BrowserProxy, PromoBrowserCommandProxy} from 'chrome://new-tab-page/new_tab_page.js';
+import {createTestProxy} from 'chrome://test/new_tab_page/test_support.js';
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+import {eventToPromise, flushTasks} from 'chrome://test/test_util.m.js';
+
+suite('NewTabPageMiddleSlotPromoTest', () => {
+  /** @type {!MiddleSlotPromoElement} */
+  let middleSlotPromo;
+
+  /**
+   * @implements {BrowserProxy}
+   * @extends {TestBrowserProxy}
+   */
+  let testProxy;
+
+  setup(async () => {
+    PolymerTest.clearBody();
+    testProxy = createTestProxy();
+    testProxy.handler.setResultFor('getPromo', Promise.resolve({
+      promo: {
+        middleSlotParts: [
+          {image: {imageUrl: {url: 'https://image'}}},
+          {
+            image: {
+              imageUrl: {url: 'https://image'},
+              target: {url: 'https://link'},
+            }
+          },
+          {
+            image: {
+              imageUrl: {url: 'https://image'},
+              target: {url: 'command:123'},
+            }
+          },
+          {text: {text: 'text', color: 'red'}},
+          {
+            link: {
+              url: {url: 'https://link'},
+              text: 'link',
+              color: 'green',
+            }
+          },
+          {
+            link: {
+              url: {url: 'command:123'},
+              text: 'command',
+              color: 'blue',
+            }
+          },
+        ],
+      },
+    }));
+    BrowserProxy.instance_ = testProxy;
+    const loaded =
+        eventToPromise('ntp-middle-slot-promo-loaded', document.body);
+    middleSlotPromo = document.createElement('ntp-middle-slot-promo');
+    document.body.appendChild(middleSlotPromo);
+    await loaded;
+  });
+
+  test('render', () => {
+    const parts = middleSlotPromo.$.container.children;
+    assertEquals(6, parts.length);
+    const [image, imageWithLink, imageWithCommand, text, link, command] = parts;
+
+    assertEquals('chrome://image/?https://image', image.src);
+
+    assertEquals('https://link/', imageWithLink.href);
+    assertEquals(
+        'chrome://image/?https://image', imageWithLink.children[0].src);
+
+    assertEquals('', imageWithCommand.href);
+    assertEquals(
+        'chrome://image/?https://image', imageWithCommand.children[0].src);
+
+    assertEquals('text', text.innerText);
+    assertEquals('red', text.style.color);
+
+    assertEquals('https://link/', link.href);
+    assertEquals('link', link.innerText);
+    assertEquals('green', link.style.color);
+
+    assertEquals('', command.href);
+    assertEquals('command', command.text);
+    assertEquals('blue', command.style.color);
+  });
+
+  test('clicking on command', async () => {
+    const testProxy = PromoBrowserCommandProxy.getInstance();
+    testProxy.handler = TestBrowserProxy.fromClass(
+        promoBrowserCommand.mojom.CommandHandlerRemote);
+    testProxy.handler.setResultFor('executeCommand', Promise.resolve());
+    const imageWithCommand = middleSlotPromo.$.container.children[2];
+    const command = middleSlotPromo.$.container.children[5];
+    await Promise.all([imageWithCommand, command].map(async el => {
+      testProxy.handler.reset();
+      el.click();
+      // Make sure the command and click information are sent to the browser.
+      const [expectedCommand, expectedClickInfo] =
+          await testProxy.handler.whenCalled('executeCommand');
+      // Unsupported commands get resolved to the default command before being
+      // sent to the browser.
+      assertEquals(
+          promoBrowserCommand.mojom.Command.kUnknownCommand, expectedCommand);
+      assertDeepEquals(
+          {
+            middleButton: false,
+            altKey: false,
+            ctrlKey: false,
+            metaKey: false,
+            shiftKey: false,
+          },
+          expectedClickInfo);
+    }));
+  });
+});
diff --git a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
index 807cb8e..7b1e4b3 100644
--- a/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
+++ b/chrome/test/data/webui/new_tab_page/new_tab_page_browsertest.js
@@ -226,3 +226,15 @@
 TEST_F('NewTabPageModulesDummyModuleTest', 'All', function() {
   mocha.run();
 });
+
+// eslint-disable-next-line no-var
+var NewTabPageMiddleSlotPromoTest = class extends NewTabPageBrowserTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://new-tab-page/test_loader.html?module=new_tab_page/middle_slot_promo_test.js';
+  }
+};
+
+TEST_F('NewTabPageMiddleSlotPromoTest', 'All', function() {
+  mocha.run();
+});
diff --git a/chrome/test/data/webui/print_preview/destination_select_test_cros.js b/chrome/test/data/webui/print_preview/destination_select_test_cros.js
index 519a6093..4e785c3 100644
--- a/chrome/test/data/webui/print_preview/destination_select_test_cros.js
+++ b/chrome/test/data/webui/print_preview/destination_select_test_cros.js
@@ -415,7 +415,11 @@
           compareIcon(selectEl, 'print');
           destinationSelect.driveDestinationKey = driveKey;
 
-          return selectOption(destinationSelect, driveKey);
+          return selectOption(
+              destinationSelect,
+              loadTimeData.getBoolean('printSaveToDrive') ?
+                  SAVE_TO_DRIVE_CROS_DESTINATION_KEY :
+                  driveKey);
         })
         .then(() => {
           // Icon updates early based on the ID.
diff --git a/chrome/test/data/webui/print_preview/model_test.js b/chrome/test/data/webui/print_preview/model_test.js
index 6cc4ae1..af81b39 100644
--- a/chrome/test/data/webui/print_preview/model_test.js
+++ b/chrome/test/data/webui/print_preview/model_test.js
@@ -17,7 +17,8 @@
   SetPolicySettings: 'set policy settings',
   GetPrintTicket: 'get print ticket',
   GetCloudPrintTicket: 'get cloud print ticket',
-  ChangeDestination: 'change destination'
+  ChangeDestination: 'change destination',
+  PrintToGoogleDriveCros: 'print to google drive cros',
 };
 
 suite(model_test.suiteName, function() {
@@ -525,4 +526,17 @@
     assertEquals(400, model.getSettingValue('dpi').horizontal_dpi);
     assertEquals(false, model.getSettingValue('duplex'));
   });
+
+  // Tests that printToGoogleDrive is set correctly on the print ticket for Save
+  // to Drive CrOS.
+  test(assert(model_test.TestNames.PrintToGoogleDriveCros), function() {
+    const driveDestination = new Destination(
+        Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS, DestinationType.LOCAL,
+        DestinationOrigin.LOCAL, 'Save to Google Drive',
+        DestinationConnectionStatus.ONLINE);
+    initializeModel();
+    model.destination = driveDestination;
+    const ticket = model.createPrintTicket(driveDestination, false, false);
+    assertTrue(JSON.parse(ticket).printToGoogleDrive);
+  });
 });
diff --git a/chrome/test/data/webui/print_preview/native_layer_stub.js b/chrome/test/data/webui/print_preview/native_layer_stub.js
index 00a7b0c..674e6b3 100644
--- a/chrome/test/data/webui/print_preview/native_layer_stub.js
+++ b/chrome/test/data/webui/print_preview/native_layer_stub.js
@@ -187,7 +187,8 @@
         this.multipleCapabilitiesPromise_ = null;
       }
     }
-    if (printerId === Destination.GooglePromotedId.SAVE_AS_PDF) {
+    if (printerId === Destination.GooglePromotedId.SAVE_AS_PDF ||
+        printerId === Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS) {
       return Promise.resolve(getPdfPrinter());
     }
     if (type !== PrinterType.LOCAL_PRINTER) {
diff --git a/chrome/test/data/webui/print_preview/print_button_test.js b/chrome/test/data/webui/print_preview/print_button_test.js
index 2da30e3..35c492797 100644
--- a/chrome/test/data/webui/print_preview/print_button_test.js
+++ b/chrome/test/data/webui/print_preview/print_button_test.js
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import {NativeLayer, NativeLayerImpl, PluginProxyImpl} from 'chrome://print/print_preview.js';
+import {Destination, NativeLayer, NativeLayerImpl, PluginProxyImpl} from 'chrome://print/print_preview.js';
 import {assert} from 'chrome://resources/js/assert.m.js';
 import {NativeLayerStub} from 'chrome://test/print_preview/native_layer_stub.js';
 import {getDefaultInitialSettings} from 'chrome://test/print_preview/print_preview_test_utils.js';
@@ -14,6 +14,7 @@
 print_button_test.TestNames = {
   LocalPrintHidePreview: 'local print hide preview',
   PDFPrintVisiblePreview: 'pdf print visible preview',
+  SaveToDriveVisiblePreviewCros: 'save to drive visible preview cros',
 };
 
 suite(print_button_test.suiteName, function() {
@@ -131,4 +132,45 @@
           return nativeLayer.whenCalled('dialogClose');
         });
   });
+
+  // Tests that hidePreview() is not called if Save to Drive is selected on
+  // Chrome OS and the user clicks print while the preview is loading because
+  // when the flag |kPrintSaveToDrive| is enabled, Save to Drive needs to be
+  // treated like Save as PDF.
+  test(
+      assert(print_button_test.TestNames.SaveToDriveVisiblePreviewCros),
+      function() {
+        printBeforePreviewReady = false;
+
+        return waitForInitialPreview()
+            .then(function() {
+              nativeLayer.reset();
+              // Setup to print before the preview loads.
+              printBeforePreviewReady = true;
+
+              // Select Save as PDF destination
+              const destinationSettings =
+                  page.$$('print-preview-sidebar')
+                      .$$('print-preview-destination-settings');
+              const driveDestination =
+                  destinationSettings.destinationStore_.destinations().find(
+                      d => d.id ===
+                          Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS);
+              assertTrue(!!driveDestination);
+              destinationSettings.destinationStore_.selectDestination(
+                  driveDestination);
+
+              // Reload preview and wait for print.
+              return nativeLayer.whenCalled('print');
+            })
+            .then(function(printTicket) {
+              assertFalse(previewHidden);
+
+              // Verify that the printer name is correct.
+              assertEquals(
+                  Destination.GooglePromotedId.SAVE_TO_DRIVE_CROS,
+                  JSON.parse(printTicket).deviceName);
+              return nativeLayer.whenCalled('dialogClose');
+            });
+      });
 });
diff --git a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
index 9ef5f4a1..c7ac4fd8 100644
--- a/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/print_preview_ui_browsertest.js
@@ -293,6 +293,35 @@
   this.runMochaTest(model_test.TestNames.ChangeDestination);
 });
 
+GEN('#if defined(OS_CHROMEOS)');
+// eslint-disable-next-line no-var
+var PrintPreviewModelTestCros = class extends PrintPreviewTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://print/test_loader.html?module=print_preview/model_test.js';
+  }
+
+  /** @override */
+  get suiteName() {
+    return model_test.suiteName;
+  }
+
+  /** @override */
+  get featureList() {
+    const kPrintSaveToDrive = ['chromeos::features::kPrintSaveToDrive'];
+    const featureList = super.featureList || [];
+    featureList.enabled = featureList.enabled ?
+        featureList.enabled.concat(kPrintSaveToDrive) :
+        kPrintSaveToDrive;
+    return featureList;
+  }
+};
+
+TEST_F('PrintPreviewModelTestCros', 'PrintToGoogleDriveCros', function() {
+  this.runMochaTest(model_test.TestNames.PrintToGoogleDriveCros);
+});
+GEN('#endif');
+
 // eslint-disable-next-line no-var
 var PrintPreviewModelSettingsAvailabilityTest = class extends PrintPreviewTest {
   /** @override */
@@ -1076,6 +1105,38 @@
   this.runMochaTest(print_button_test.TestNames.PDFPrintVisiblePreview);
 });
 
+GEN('#if defined(OS_CHROMEOS)');
+// eslint-disable-next-line no-var
+var PrintPreviewPrintButtonTestCros = class extends PrintPreviewTest {
+  /** @override */
+  get browsePreload() {
+    return 'chrome://print/test_loader.html?module=print_preview/print_button_test.js';
+  }
+
+  /** @override */
+  get suiteName() {
+    return print_button_test.suiteName;
+  }
+
+  /** @override */
+  get featureList() {
+    const kPrintSaveToDrive = ['chromeos::features::kPrintSaveToDrive'];
+    const featureList = super.featureList || [];
+    featureList.enabled = featureList.enabled ?
+        featureList.enabled.concat(kPrintSaveToDrive) :
+        kPrintSaveToDrive;
+    return featureList;
+  }
+};
+
+TEST_F(
+    'PrintPreviewPrintButtonTestCros', 'SaveToDriveVisiblePreviewCros',
+    function() {
+      this.runMochaTest(
+          print_button_test.TestNames.SaveToDriveVisiblePreviewCros);
+    });
+GEN('#endif');
+
 // eslint-disable-next-line no-var
 var PrintPreviewKeyEventTest = class extends PrintPreviewTest {
   /** @override */
@@ -1219,6 +1280,19 @@
           destination_select_test_cros.TestNames.SelectDriveDestination);
     });
 
+TEST_F(
+    'PrintPreviewDestinationSelectTestCrOSSaveToDriveEnabled', 'ChangeIcon',
+    function() {
+      this.runMochaTest(destination_select_test_cros.TestNames.ChangeIcon);
+    });
+
+TEST_F(
+    'PrintPreviewDestinationSelectTestCrOSSaveToDriveEnabled',
+    'ChangeIconDeprecationWarnings', function() {
+      this.runMochaTest(
+          destination_select_test_cros.TestNames.ChangeIconDeprecationWarnings);
+    });
+
 // eslint-disable-next-line no-var
 var PrintPreviewPrinterStatusTestCros = class extends PrintPreviewTest {
   /** @override */
diff --git a/chrome/test/data/webui/settings/chromeos/apps_page_test.js b/chrome/test/data/webui/settings/chromeos/apps_page_test.js
index 4c1c15b..19b8e72c 100644
--- a/chrome/test/data/webui/settings/chromeos/apps_page_test.js
+++ b/chrome/test/data/webui/settings/chromeos/apps_page_test.js
@@ -21,6 +21,7 @@
   teardown(function() {
     appsPage.remove();
     appsPage = null;
+    settings.Router.getInstance().resetRouteForTesting();
   });
 
   suite('Page Combinations', function() {
@@ -78,6 +79,41 @@
       assertTrue(!!appsPage.$$('.subpage-arrow'));
     });
 
+    test('Deep link to manage android prefs', async () => {
+      loadTimeData.overrideValues({
+        isDeepLinkingEnabled: true,
+      });
+
+      appsPage.havePlayStoreApp = false;
+      Polymer.dom.flush();
+
+      const params = new URLSearchParams;
+      params.append('settingId', '700');
+      settings.Router.getInstance().navigateTo(settings.routes.APPS, params);
+
+      const deepLinkElement = appsPage.$$('#manageApps').$$('cr-icon-button');
+      await test_util.waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, getDeepActiveElement(),
+          'Manage android prefs button should be focused for settingId=700.');
+    });
+
+    test('Deep link to turn on Play Store', async () => {
+      loadTimeData.overrideValues({
+        isDeepLinkingEnabled: true,
+      });
+
+      const params = new URLSearchParams;
+      params.append('settingId', '702');
+      settings.Router.getInstance().navigateTo(settings.routes.APPS, params);
+
+      const deepLinkElement = appsPage.$$('#enable');
+      await test_util.waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, getDeepActiveElement(),
+          'Turn on play store button should be focused for settingId=702.');
+    });
+
     // TODO(crbug.com/1006662): Test that setting playStoreEnabled to false
     // navigates back to the main apps section.
   });
@@ -93,6 +129,13 @@
       document.body.appendChild(subpage);
       testing.Test.disableAnimationsAndTransitions();
 
+      // Because we can't simulate the loadTimeData value androidAppsVisible,
+      // this route doesn't exist for tests. Add it in for testing.
+      if (!settings.routes.ANDROID_APPS_DETAILS) {
+        settings.routes.ANDROID_APPS_DETAILS = settings.routes.APPS.createChild(
+            '/' + chromeos.settings.mojom.GOOGLE_PLAY_STORE_SUBPAGE_PATH);
+      }
+
       subpage.prefs = {arc: {enabled: {value: true}}};
       subpage.androidAppsInfo = {
         playStoreEnabled: true,
@@ -192,5 +235,45 @@
       Polymer.dom.flush();
       return promise;
     });
+
+    test('Deep link to manage android prefs - subpage', async () => {
+      loadTimeData.overrideValues({
+        isDeepLinkingEnabled: true,
+      });
+
+      subpage.androidAppsInfo = {
+        playStoreEnabled: false,
+        settingsAppAvailable: true,
+      };
+      Polymer.dom.flush();
+
+      const params = new URLSearchParams;
+      params.append('settingId', '700');
+      settings.Router.getInstance().navigateTo(
+          settings.routes.ANDROID_APPS_DETAILS, params);
+
+      const deepLinkElement = subpage.$$('#manageApps').$$('cr-icon-button');
+      await test_util.waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, getDeepActiveElement(),
+          'Manage android prefs button should be focused for settingId=700.');
+    });
+
+    test('Deep link to remove play store', async () => {
+      loadTimeData.overrideValues({
+        isDeepLinkingEnabled: true,
+      });
+
+      const params = new URLSearchParams;
+      params.append('settingId', '701');
+      settings.Router.getInstance().navigateTo(
+          settings.routes.ANDROID_APPS_DETAILS, params);
+
+      const deepLinkElement = subpage.$$('#remove cr-button');
+      await test_util.waitAfterNextRender(deepLinkElement);
+      assertEquals(
+          deepLinkElement, getDeepActiveElement(),
+          'Remove play store button should be focused for settingId=701.');
+    });
   });
 });
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index 9075336c..7543ea1 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -302,6 +302,7 @@
     return super.extraLibraries.concat([
       '//ui/webui/resources/js/promise_resolver.js',
       BROWSER_SETTINGS_PATH + '../test_browser_proxy.js',
+      BROWSER_SETTINGS_PATH + '../test_util.js',
       BROWSER_SETTINGS_PATH + 'chromeos/test_android_apps_browser_proxy.js',
       'apps_page_test.js',
     ]);
diff --git a/chrome/updater/BUILD.gn b/chrome/updater/BUILD.gn
index fd043250..5ffcecf 100644
--- a/chrome/updater/BUILD.gn
+++ b/chrome/updater/BUILD.gn
@@ -104,6 +104,8 @@
       "installer.cc",
       "installer.h",
       "lib_util.h",
+      "policy_service.cc",
+      "policy_service.h",
       "prefs.cc",
       "prefs.h",
       "prefs_impl.h",
@@ -183,6 +185,7 @@
     if (is_mac) {
       deps += [
         "//chrome/updater/app/server/mac:protocol",
+        "//chrome/updater/mac:enterprise",
         "//chrome/updater/mac:installer_sources",
         "//chrome/updater/mac:network_fetcher_sources",
         "//chrome/updater/mac:updater_setup_sources",
@@ -290,6 +293,7 @@
       "lib_util_unittest.cc",
       "persisted_data_unittest.cc",
       "policy_manager_unittest.cc",
+      "policy_service_unittest.cc",
       "prefs_unittest.cc",
       "run_all_unittests.cc",
       "service_scope_unittest.cc",
diff --git a/chrome/updater/device_management/dm_policy_manager.cc b/chrome/updater/device_management/dm_policy_manager.cc
index be2d4fa..4fac804 100644
--- a/chrome/updater/device_management/dm_policy_manager.cc
+++ b/chrome/updater/device_management/dm_policy_manager.cc
@@ -202,6 +202,16 @@
   return true;
 }
 
+bool DMPolicyManager::GetTargetChannel(const std::string& app_id,
+                                       std::string* channel) const {
+  const auto* app_settings = GetAppSettings(app_id);
+  if (!app_settings || !app_settings->has_target_channel())
+    return false;
+
+  *channel = app_settings->target_channel();
+  return true;
+}
+
 bool DMPolicyManager::IsRollbackToTargetVersionAllowed(
     const std::string& app_id,
     bool* rollback_allowed) const {
diff --git a/chrome/updater/device_management/dm_policy_manager.h b/chrome/updater/device_management/dm_policy_manager.h
index 62f4b9e..e6a69e3 100644
--- a/chrome/updater/device_management/dm_policy_manager.h
+++ b/chrome/updater/device_management/dm_policy_manager.h
@@ -47,6 +47,8 @@
   bool GetTargetVersionPrefix(
       const std::string& app_id,
       std::string* target_version_prefix) const override;
+  bool GetTargetChannel(const std::string& app_id,
+                        std::string* channel) const override;
   bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
                                         bool* rollback_allowed) const override;
 
diff --git a/chrome/updater/installer.cc b/chrome/updater/installer.cc
index 2794f6d..b029c74 100644
--- a/chrome/updater/installer.cc
+++ b/chrome/updater/installer.cc
@@ -17,6 +17,7 @@
 #include "build/build_config.h"
 #include "chrome/updater/action_handler.h"
 #include "chrome/updater/constants.h"
+#include "chrome/updater/policy_service.h"
 #include "chrome/updater/util.h"
 #include "components/crx_file/crx_verifier.h"
 #include "components/update_client/update_client_errors.h"
@@ -78,6 +79,13 @@
   component.name = app_id_;
   component.version = pv_;
   component.fingerprint = fingerprint_;
+
+  // In case we fail at getting the target channel, make sure that
+  // |component.channel| is an empty string. Possible failure cases are if the
+  // machine is not managed, the policy was not set or any other unexpected
+  // error.
+  if (!GetUpdaterPolicyService()->GetTargetChannel(app_id_, &component.channel))
+    component.channel.clear();
   return component;
 }
 
diff --git a/chrome/updater/mac/managed_preference_policy_manager.mm b/chrome/updater/mac/managed_preference_policy_manager.mm
index bb8d78b6..ffe9169 100644
--- a/chrome/updater/mac/managed_preference_policy_manager.mm
+++ b/chrome/updater/mac/managed_preference_policy_manager.mm
@@ -52,6 +52,8 @@
   bool GetProxyMode(std::string* proxy_mode) const override;
   bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
   bool GetProxyServer(std::string* proxy_server) const override;
+  bool GetTargetChannel(const std::string& app_id,
+                        std::string* channel) const override;
 
  private:
   base::scoped_nsobject<CRUManagedPreferencePolicyManager> impl_;
@@ -188,6 +190,18 @@
   return false;
 }
 
+bool ManagedPreferencePolicyManager::GetTargetChannel(
+    const std::string& app_id,
+    std::string* channel) const {
+  NSString* value = [impl_ targetChannel:base::SysUTF8ToNSString(app_id)];
+  if (value) {
+    *channel = base::SysNSStringToUTF8(value);
+    return true;
+  }
+
+  return false;
+}
+
 NSDictionary* ReadManagedPreferencePolicyDictionary() {
   base::ScopedCFTypeRef<CFPropertyListRef> policies(CFPreferencesCopyAppValue(
       (__bridge CFStringRef)kManagedPreferencesUpdatePolicies,
diff --git a/chrome/updater/mac/managed_preference_policy_manager_impl.h b/chrome/updater/mac/managed_preference_policy_manager_impl.h
index f0a57cb..8626a8c 100644
--- a/chrome/updater/mac/managed_preference_policy_manager_impl.h
+++ b/chrome/updater/mac/managed_preference_policy_manager_impl.h
@@ -67,6 +67,7 @@
 
 // App-level policies.
 - (int)appUpdatePolicy:(nonnull NSString*)appid;
+- (nullable NSString*)targetChannel:(nonnull NSString*)appid;
 - (nullable NSString*)targetVersionPrefix:(nonnull NSString*)appid;
 - (int)rollbackToTargetVersion:(nonnull NSString*)appid;
 
diff --git a/chrome/updater/mac/managed_preference_policy_manager_impl.mm b/chrome/updater/mac/managed_preference_policy_manager_impl.mm
index 2e5cbaa3..ad70f97 100644
--- a/chrome/updater/mac/managed_preference_policy_manager_impl.mm
+++ b/chrome/updater/mac/managed_preference_policy_manager_impl.mm
@@ -16,6 +16,7 @@
     @"UpdatesSuppressedStartMin";
 static NSString* kUpdatesSuppressedDurationMinuteKey =
     @"UpdatesSuppressedDurationMin";
+static NSString* kTargetChannelKey = @"TargetChannel";
 static NSString* kTargetVersionPrefixKey = @"TargetVersionPrefix";
 static NSString* kRollbackToTargetVersionKey = @"RollbackToTargetVersion";
 
@@ -119,11 +120,13 @@
 
 /// Class that manages policies for a single App.
 @interface CRUManagedPreferenceAppPolicySettings : NSObject {
+  base::scoped_nsobject<NSString> _targetChannel;
   base::scoped_nsobject<NSString> _targetVersionPrefix;
 }
 
 @property(nonatomic, readonly) int updatePolicy;
 @property(nonatomic, readonly) int rollbackToTargetVersion;
+@property(nonatomic, readonly, nullable) NSString* targetChannel;
 @property(nonatomic, readonly, nullable) NSString* targetVersionPrefix;
 
 @end
@@ -137,6 +140,8 @@
   if (([super init])) {
     _updatePolicy =
         updater::ReadPolicyInteger([policyDict objectForKey:kUpdateDefaultKey]);
+    _targetChannel =
+        updater::ReadPolicyString([policyDict objectForKey:kTargetChannelKey]);
     _targetVersionPrefix = updater::ReadPolicyString(
         [policyDict objectForKey:kTargetVersionPrefixKey]);
     _rollbackToTargetVersion = updater::ReadPolicyInteger(
@@ -146,6 +151,14 @@
   return self;
 }
 
+- (NSString*)targetChannel {
+  if (_targetChannel) {
+    return [NSString stringWithString:_targetChannel];
+  } else {
+    return nil;
+  }
+}
+
 - (NSString*)targetVersionPrefix {
   if (_targetVersionPrefix) {
     return [NSString stringWithString:_targetVersionPrefix];
@@ -233,6 +246,11 @@
   return [_appPolicies objectForKey:appid].updatePolicy;
 }
 
+- (NSString*)targetChannel:(NSString*)appid {
+  appid = appid.lowercaseString;
+  return [_appPolicies objectForKey:appid].targetChannel;
+}
+
 - (NSString*)targetVersionPrefix:(NSString*)appid {
   appid = appid.lowercaseString;
   return [_appPolicies objectForKey:appid].targetVersionPrefix;
diff --git a/chrome/updater/policy_manager.cc b/chrome/updater/policy_manager.cc
index 04ff5626f..3038ca4 100644
--- a/chrome/updater/policy_manager.cc
+++ b/chrome/updater/policy_manager.cc
@@ -44,6 +44,8 @@
   bool GetProxyMode(std::string* proxy_mode) const override;
   bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
   bool GetProxyServer(std::string* proxy_server) const override;
+  bool GetTargetChannel(const std::string& app_id,
+                        std::string* channel) const override;
 };
 
 DefaultPolicyManager::DefaultPolicyManager() = default;
@@ -119,6 +121,11 @@
   return false;
 }
 
+bool DefaultPolicyManager::GetTargetChannel(const std::string& app_id,
+                                            std::string* channel) const {
+  return false;
+}
+
 std::unique_ptr<PolicyManagerInterface> GetPolicyManager() {
   return std::make_unique<DefaultPolicyManager>();
 }
diff --git a/chrome/updater/policy_manager.h b/chrome/updater/policy_manager.h
index 39f275e..cc5fa27c 100644
--- a/chrome/updater/policy_manager.h
+++ b/chrome/updater/policy_manager.h
@@ -84,6 +84,10 @@
 
   // Returns a proxy server.
   virtual bool GetProxyServer(std::string* proxy_server) const = 0;
+
+  // Returns a channel, for example {stable|beta|dev}.
+  virtual bool GetTargetChannel(const std::string& app_id,
+                                std::string* channel) const = 0;
 };
 
 std::unique_ptr<PolicyManagerInterface> GetPolicyManager();
diff --git a/chrome/updater/policy_service.cc b/chrome/updater/policy_service.cc
new file mode 100644
index 0000000..c26b77b
--- /dev/null
+++ b/chrome/updater/policy_service.cc
@@ -0,0 +1,183 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/updater/policy_service.h"
+
+#include "base/check.h"
+#include "build/build_config.h"
+
+#if defined(OS_WIN)
+#include "chrome/updater/win/group_policy_manager.h"
+#elif defined(OS_MAC)
+#include "chrome/updater/mac/managed_preference_policy_manager.h"
+#endif
+
+namespace updater {
+
+// Only policy manager that are enterprise managed are used by the policy
+// service.
+PolicyService::PolicyService() : default_policy_manager_(GetPolicyManager()) {
+#if defined(OS_WIN)
+  auto group_policy_manager = std::make_unique<GroupPolicyManager>();
+  if (group_policy_manager->IsManaged())
+    policy_managers_.emplace_back(std::move(group_policy_manager));
+#endif
+    // TODO (crbug/1122118): Inject the DMPolicyManager here.
+#if defined(OS_MAC)
+  auto mac_policy_manager = CreateManagedPreferencePolicyManager();
+  if (mac_policy_manager->IsManaged())
+    policy_managers_.emplace_back(std::move(mac_policy_manager));
+#endif
+  UpdateActivePolicyManager();
+}
+
+PolicyService::~PolicyService() = default;
+
+void PolicyService::SetPolicyManagersForTesting(
+    std::vector<std::unique_ptr<PolicyManagerInterface>> managers) {
+  policy_managers_ = std::move(managers);
+  UpdateActivePolicyManager();
+}
+
+std::string PolicyService::source() const {
+  return active_policy_manager_->source();
+}
+
+bool PolicyService::IsManaged() const {
+  return active_policy_manager_->IsManaged();
+}
+
+bool PolicyService::GetLastCheckPeriodMinutes(int* minutes) const {
+  return active_policy_manager_->GetLastCheckPeriodMinutes(minutes) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetLastCheckPeriodMinutes(minutes));
+}
+
+bool PolicyService::GetUpdatesSuppressedTimes(int* start_hour,
+                                              int* start_min,
+                                              int* duration_min) const {
+  return active_policy_manager_->GetUpdatesSuppressedTimes(
+             start_hour, start_min, duration_min) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetUpdatesSuppressedTimes(
+              start_hour, start_min, duration_min));
+}
+
+bool PolicyService::GetDownloadPreferenceGroupPolicy(
+    std::string* download_preference) const {
+  return active_policy_manager_->GetDownloadPreferenceGroupPolicy(
+             download_preference) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetDownloadPreferenceGroupPolicy(
+              download_preference));
+}
+
+bool PolicyService::GetPackageCacheSizeLimitMBytes(
+    int* cache_size_limit) const {
+  return active_policy_manager_->GetPackageCacheSizeLimitMBytes(
+             cache_size_limit) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetPackageCacheSizeLimitMBytes(
+              cache_size_limit));
+}
+
+bool PolicyService::GetPackageCacheExpirationTimeDays(
+    int* cache_life_limit) const {
+  return active_policy_manager_->GetPackageCacheExpirationTimeDays(
+             cache_life_limit) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetPackageCacheExpirationTimeDays(
+              cache_life_limit));
+}
+
+bool PolicyService::GetEffectivePolicyForAppInstalls(
+    const std::string& app_id,
+    int* install_policy) const {
+  return active_policy_manager_->GetEffectivePolicyForAppInstalls(
+             app_id, install_policy) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetEffectivePolicyForAppInstalls(
+              app_id, install_policy));
+}
+
+bool PolicyService::GetEffectivePolicyForAppUpdates(const std::string& app_id,
+                                                    int* update_policy) const {
+  return active_policy_manager_->GetEffectivePolicyForAppUpdates(
+             app_id, update_policy) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetEffectivePolicyForAppUpdates(
+              app_id, update_policy));
+}
+
+bool PolicyService::GetTargetChannel(const std::string& app_id,
+                                     std::string* channel) const {
+  return active_policy_manager_->GetTargetChannel(app_id, channel) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetTargetChannel(app_id, channel));
+}
+
+bool PolicyService::GetTargetVersionPrefix(
+    const std::string& app_id,
+    std::string* target_version_prefix) const {
+  return active_policy_manager_->GetTargetVersionPrefix(
+             app_id, target_version_prefix) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetTargetVersionPrefix(
+              app_id, target_version_prefix));
+}
+
+bool PolicyService::IsRollbackToTargetVersionAllowed(
+    const std::string& app_id,
+    bool* rollback_allowed) const {
+  return active_policy_manager_->IsRollbackToTargetVersionAllowed(
+             app_id, rollback_allowed) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->IsRollbackToTargetVersionAllowed(
+              app_id, rollback_allowed));
+}
+
+bool PolicyService::GetProxyMode(std::string* proxy_mode) const {
+  return active_policy_manager_->GetProxyMode(proxy_mode) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetProxyMode(proxy_mode));
+}
+
+bool PolicyService::GetProxyPacUrl(std::string* proxy_pac_url) const {
+  return active_policy_manager_->GetProxyPacUrl(proxy_pac_url) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetProxyPacUrl(proxy_pac_url));
+}
+
+bool PolicyService::GetProxyServer(std::string* proxy_server) const {
+  return active_policy_manager_->GetProxyServer(proxy_server) ||
+         (ShouldFallbackToDefaultManager() &&
+          default_policy_manager_->GetProxyServer(proxy_server));
+}
+
+const PolicyManagerInterface& PolicyService::GetActivePolicyManager() {
+  DCHECK(active_policy_manager_);
+  return *active_policy_manager_;
+}
+
+bool PolicyService::ShouldFallbackToDefaultManager() const {
+  return active_policy_manager_ != default_policy_manager_.get();
+}
+
+void PolicyService::UpdateActivePolicyManager() {
+  // The active policy manager is either the default policy manager or the
+  // manager with the highest level that is managed.
+  active_policy_manager_ = default_policy_manager_.get();
+  for (const auto& manager : policy_managers_) {
+    if (manager->IsManaged()) {
+      active_policy_manager_ = manager.get();
+      return;
+    }
+  }
+}
+
+std::unique_ptr<PolicyService> GetUpdaterPolicyService() {
+  return std::make_unique<PolicyService>();
+}
+
+}  // namespace updater
diff --git a/chrome/updater/policy_service.h b/chrome/updater/policy_service.h
new file mode 100644
index 0000000..055c62b
--- /dev/null
+++ b/chrome/updater/policy_service.h
@@ -0,0 +1,81 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_UPDATER_POLICY_SERVICE_H_
+#define CHROME_UPDATER_POLICY_SERVICE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "chrome/updater/policy_manager.h"
+
+namespace updater {
+
+// The PolicyService returns policies for enterprise managed machines from the
+// source with the highest priority where the policy available.
+class PolicyService : public PolicyManagerInterface {
+ public:
+  PolicyService();
+  PolicyService(const PolicyService&) = delete;
+  PolicyService& operator=(const PolicyService&) = delete;
+  ~PolicyService() override;
+
+  // Overrides for PolicyManagerInterface.
+  std::string source() const override;
+
+  bool IsManaged() const override;
+
+  bool GetLastCheckPeriodMinutes(int* minutes) const override;
+  bool GetUpdatesSuppressedTimes(int* start_hour,
+                                 int* start_min,
+                                 int* duration_min) const override;
+  bool GetDownloadPreferenceGroupPolicy(
+      std::string* download_preference) const override;
+  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override;
+  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override;
+
+  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
+                                        int* install_policy) const override;
+  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
+                                       int* update_policy) const override;
+  bool GetTargetChannel(const std::string& app_id,
+                        std::string* channel) const override;
+  bool GetTargetVersionPrefix(
+      const std::string& app_id,
+      std::string* target_version_prefix) const override;
+  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
+                                        bool* rollback_allowed) const override;
+  bool GetProxyMode(std::string* proxy_mode) const override;
+  bool GetProxyPacUrl(std::string* proxy_pac_url) const override;
+  bool GetProxyServer(std::string* proxy_server) const override;
+
+  const std::vector<std::unique_ptr<PolicyManagerInterface>>&
+  policy_managers() {
+    return policy_managers_;
+  }
+
+  void SetPolicyManagersForTesting(
+      std::vector<std::unique_ptr<PolicyManagerInterface>> managers);
+  const PolicyManagerInterface& GetActivePolicyManager();
+
+ private:
+  bool ShouldFallbackToDefaultManager() const;
+
+  // Sets the policy manager that is managed and has the highest priority as the
+  // active policy manager. If no manager is managed, use the default policy
+  // manager as the active one.
+  void UpdateActivePolicyManager();
+  // List of policy managers in descending order of priority. The first policy
+  // manager's policies takes precedence over the following.
+  std::vector<std::unique_ptr<PolicyManagerInterface>> policy_managers_;
+  std::unique_ptr<PolicyManagerInterface> default_policy_manager_;
+  const PolicyManagerInterface* active_policy_manager_;
+};
+
+std::unique_ptr<PolicyService> GetUpdaterPolicyService();
+
+}  // namespace updater
+
+#endif  // CHROME_UPDATER_POLICY_SERVICE_H_
diff --git a/chrome/updater/policy_service_unittest.cc b/chrome/updater/policy_service_unittest.cc
new file mode 100644
index 0000000..50a8d547
--- /dev/null
+++ b/chrome/updater/policy_service_unittest.cc
@@ -0,0 +1,138 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "chrome/updater/policy_manager.h"
+#include "chrome/updater/policy_service.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace updater {
+
+// The Policy Manager Interface is implemented by policy managers such as Group
+// Policy and Device Management.
+class FakePolicyManager : public PolicyManagerInterface {
+ public:
+  ~FakePolicyManager() override = default;
+
+  std::string source() const override { return source_; }
+  bool IsManaged() const override { return managed_; }
+  bool GetLastCheckPeriodMinutes(int* minutes) const override { return false; }
+  bool GetUpdatesSuppressedTimes(int* start_hour,
+                                 int* start_min,
+                                 int* duration_min) const override {
+    return false;
+  }
+  bool GetDownloadPreferenceGroupPolicy(
+      std::string* download_preference) const override {
+    return false;
+  }
+  bool GetPackageCacheSizeLimitMBytes(int* cache_size_limit) const override {
+    return false;
+  }
+  bool GetPackageCacheExpirationTimeDays(int* cache_life_limit) const override {
+    return false;
+  }
+  bool GetEffectivePolicyForAppInstalls(const std::string& app_id,
+                                        int* install_policy) const override {
+    return false;
+  }
+  bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
+                                       int* update_policy) const override {
+    return false;
+  }
+  bool GetTargetVersionPrefix(
+      const std::string& app_id,
+      std::string* target_version_prefix) const override {
+    return false;
+  }
+  bool IsRollbackToTargetVersionAllowed(const std::string& app_id,
+                                        bool* rollback_allowed) const override {
+    return false;
+  }
+  bool GetProxyMode(std::string* proxy_mode) const override { return false; }
+  bool GetProxyPacUrl(std::string* proxy_pac_url) const override {
+    return false;
+  }
+  bool GetProxyServer(std::string* proxy_server) const override {
+    return false;
+  }
+  bool GetTargetChannel(const std::string& app_id,
+                        std::string* channel) const override {
+    auto value = channels_.find(app_id);
+    if (value == channels_.end())
+      return false;
+    *channel = value->second;
+    return true;
+  }
+  void SetChannel(const std::string& app_id, std::string channel) {
+    channels_[app_id] = std::move(channel);
+  }
+
+  static std::unique_ptr<FakePolicyManager> GetTestingPolicyManager(
+      std::string source,
+      bool managed) {
+    auto manager = std::make_unique<FakePolicyManager>();
+    manager->source_ = std::move(source);
+    manager->managed_ = managed;
+    return manager;
+  }
+
+ private:
+  std::string source_;
+  std::map<std::string, std::string> channels_;
+  bool managed_;
+};
+
+TEST(PolicyService, ReturnsHighestPriorityManagedPolicyManager) {
+  std::unique_ptr<PolicyService> policy_service(GetUpdaterPolicyService());
+  std::vector<std::unique_ptr<PolicyManagerInterface>> managers;
+  managers.emplace_back(
+      FakePolicyManager::GetTestingPolicyManager("highest_unmanaged", false));
+  managers.emplace_back(
+      FakePolicyManager::GetTestingPolicyManager("highest_managed", true));
+  managers.emplace_back(
+      FakePolicyManager::GetTestingPolicyManager("managed", true));
+  managers.emplace_back(
+      FakePolicyManager::GetTestingPolicyManager("lowest_managed", true));
+  managers.emplace_back(
+      FakePolicyManager::GetTestingPolicyManager("lowest_unmanaged", false));
+  policy_service->SetPolicyManagersForTesting(std::move(managers));
+  ASSERT_EQ("highest_managed",
+            policy_service->GetActivePolicyManager().source());
+}
+
+TEST(PolicyService, ReturnsDefaultPolicyManager) {
+  std::unique_ptr<PolicyService> policy_service(GetUpdaterPolicyService());
+  policy_service->SetPolicyManagersForTesting({});
+  ASSERT_EQ("default", policy_service->GetActivePolicyManager().source());
+}
+
+TEST(PolicyService, TargetChannelUnmanagedSource) {
+  std::unique_ptr<PolicyService> policy_service(GetUpdaterPolicyService());
+  auto manager = FakePolicyManager::GetTestingPolicyManager("unmanaged", false);
+  manager->SetChannel("", "channel");
+  std::vector<std::unique_ptr<PolicyManagerInterface>> managers;
+  managers.emplace_back(std::move(manager));
+  policy_service->SetPolicyManagersForTesting(std::move(managers));
+  std::string channel;
+  policy_service->GetTargetChannel("", &channel);
+  ASSERT_TRUE(channel.empty());
+}
+
+TEST(PolicyService, TargetChannelManagedSource) {
+  std::unique_ptr<PolicyService> policy_service(GetUpdaterPolicyService());
+  auto manager = FakePolicyManager::GetTestingPolicyManager("managed", true);
+  manager->SetChannel("", "channel");
+  std::vector<std::unique_ptr<PolicyManagerInterface>> managers;
+  managers.emplace_back(std::move(manager));
+  policy_service->SetPolicyManagersForTesting(std::move(managers));
+  std::string channel;
+  policy_service->GetTargetChannel("", &channel);
+  ASSERT_EQ(channel, "channel");
+}
+
+}  // namespace updater
diff --git a/chrome/updater/protos/omaha_settings.proto b/chrome/updater/protos/omaha_settings.proto
index 5a1c49e..178383e 100644
--- a/chrome/updater/protos/omaha_settings.proto
+++ b/chrome/updater/protos/omaha_settings.proto
@@ -129,6 +129,24 @@
   // specified by "Target version prefix override" will be downgraded to the
   // highest available version that matches the target version.
   optional RollbackToTargetVersionValue rollback_to_target_version = 6;
+
+  // Gcpw specific application setting which contains a list of domains from
+  // which the user is allowed to login.
+  optional GcpwSpecificApplicationSettings gcpw_application_settings = 7;
+
+  // Target Channel
+  //
+  // Specifies the channel to which <app> should be updated.
+  //
+  // When this policy is set, the binaries returned by Google Update will be the
+  // binaries for the specified channel. If this policy is not set, the default
+  // channel will be used.
+  optional string target_channel = 8;
+}
+
+message GcpwSpecificApplicationSettings {
+  // List of domains from which the user is allowed to login.
+  repeated string domains_allowed_to_login = 1;
 }
 
 message OmahaSettingsClientProto {
diff --git a/chrome/updater/win/group_policy_manager.cc b/chrome/updater/win/group_policy_manager.cc
index b761eac..ab9e934e 100644
--- a/chrome/updater/win/group_policy_manager.cc
+++ b/chrome/updater/win/group_policy_manager.cc
@@ -48,6 +48,7 @@
 const base::char16 kRegValueUpdateAppsDefault[] = L"UpdateDefault";
 const base::char16 kRegValueUpdateAppPrefix[] = L"Update";
 const base::char16 kRegValueTargetVersionPrefix[] = L"TargetVersionPrefix";
+const base::char16 kRegValueTargetChannel[] = L"TargetChannel";
 const base::char16 kRegValueRollbackToTargetVersion[] =
     L"RollbackToTargetVersion";
 
@@ -112,6 +113,13 @@
              : ReadValueDW(kRegValueUpdateAppsDefault, update_policy);
 }
 
+bool GroupPolicyManager::GetTargetChannel(const std::string& app_id,
+                                          std::string* channel) const {
+  base::string16 app_value_name(kRegValueTargetChannel);
+  app_value_name.append(base::SysUTF8ToWide(app_id));
+  return ReadValue(app_value_name.c_str(), channel);
+}
+
 bool GroupPolicyManager::GetTargetVersionPrefix(
     const std::string& app_id,
     std::string* target_version_prefix) const {
diff --git a/chrome/updater/win/group_policy_manager.h b/chrome/updater/win/group_policy_manager.h
index 18a5192..5249199 100644
--- a/chrome/updater/win/group_policy_manager.h
+++ b/chrome/updater/win/group_policy_manager.h
@@ -39,6 +39,8 @@
                                         int* install_policy) const override;
   bool GetEffectivePolicyForAppUpdates(const std::string& app_id,
                                        int* update_policy) const override;
+  bool GetTargetChannel(const std::string& app_id,
+                        std::string* channel) const override;
   bool GetTargetVersionPrefix(
       const std::string& app_id,
       std::string* target_version_prefix) const override;
diff --git a/chromeos/components/sync_wifi/synced_network_metrics_logger.cc b/chromeos/components/sync_wifi/synced_network_metrics_logger.cc
index 3f4423c..9fffb3b 100644
--- a/chromeos/components/sync_wifi/synced_network_metrics_logger.cc
+++ b/chromeos/components/sync_wifi/synced_network_metrics_logger.cc
@@ -166,9 +166,19 @@
 }
 
 bool SyncedNetworkMetricsLogger::IsEligible(const NetworkState* network) {
-  return network &&
-         NetworkHandler::Get()->network_metadata_store()->GetIsConfiguredBySync(
-             network->guid());
+  // Only non-tether Wi-Fi networks are eligible for logging.
+  if (!network || network->type() != shill::kTypeWifi ||
+      !network->tether_guid().empty()) {
+    return false;
+  }
+
+  NetworkMetadataStore* metadata_store =
+      NetworkHandler::Get()->network_metadata_store();
+  if (!metadata_store) {
+    return false;
+  }
+
+  return metadata_store->GetIsConfiguredBySync(network->guid());
 }
 
 void SyncedNetworkMetricsLogger::OnConnectErrorGetProperties(
diff --git a/chromeos/components/sync_wifi/synced_network_metrics_logger_unittest.cc b/chromeos/components/sync_wifi/synced_network_metrics_logger_unittest.cc
index 6cfbe88..033992fcf 100644
--- a/chromeos/components/sync_wifi/synced_network_metrics_logger_unittest.cc
+++ b/chromeos/components/sync_wifi/synced_network_metrics_logger_unittest.cc
@@ -235,6 +235,18 @@
               testing::ElementsAre(base::Bucket(/*min=*/10, /*count=*/1)));
 }
 
+TEST_F(SyncedNetworkMetricsLoggerTest, NetworkStatusChange_DuringLogout) {
+  base::HistogramTester histogram_tester;
+  const NetworkState* network = CreateNetwork(/*from_sync=*/true);
+  NetworkHandler::Get()->ShutdownPrefServices();
+  synced_network_metrics_logger()->NetworkConnectionStateChanged(network);
+  base::RunLoop().RunUntilIdle();
+
+  // Expect that there is no crash, and no Wi-Fi sync histograms recorded.
+  EXPECT_THAT(histogram_tester.GetTotalCountsForPrefix("Network.Wifi.Synced"),
+              testing::ContainerEq(base::HistogramTester::CountsMap()));
+}
+
 }  // namespace sync_wifi
 
 }  // namespace chromeos
diff --git a/chromeos/cryptohome/system_salt_getter.h b/chromeos/cryptohome/system_salt_getter.h
index 4e22729..f5013980 100644
--- a/chromeos/cryptohome/system_salt_getter.h
+++ b/chromeos/cryptohome/system_salt_getter.h
@@ -44,6 +44,8 @@
 
   // Returns pointer to binary system salt if it is already known.
   // Returns nullptr if system salt is not known.
+  // WARNING: This pointer is null early in startup. Do not assume it is valid.
+  // Prefer GetSystemSalt() above. https://crbug.com/1122674
   const RawSalt* GetRawSalt() const;
 
   // This is for browser tests API.
diff --git a/chromeos/network/onc/variable_expander.cc b/chromeos/network/onc/variable_expander.cc
index 92ff57a..1df4464 100644
--- a/chromeos/network/onc/variable_expander.cc
+++ b/chromeos/network/onc/variable_expander.cc
@@ -4,6 +4,8 @@
 
 #include "chromeos/network/onc/variable_expander.h"
 
+#include <algorithm>
+
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_split.h"
@@ -86,8 +88,8 @@
       }
     }
 
-    const base::StringPiece replacement_part =
-        replacement.substr(replacement_start, replacement_count);
+    const base::StringPiece replacement_part = replacement.substr(
+        std::min(replacement_start, replacement.size()), replacement_count);
     // Don't use ReplaceSubstringsAfterOffset here, it can lead to a doubling
     // of tokens, see VariableExpanderTest.DoesNotRecurse test.
     base::ReplaceFirstSubstringAfterOffset(str, token_start, full_token,
diff --git a/components/exo/README b/components/exo/README
deleted file mode 100644
index 81ae61f..0000000
--- a/components/exo/README
+++ /dev/null
@@ -1,2 +0,0 @@
-This directory contains an implementation of a display server on top of
-the Aura Shell.
diff --git a/components/exo/README.md b/components/exo/README.md
new file mode 100644
index 0000000..def122078
--- /dev/null
+++ b/components/exo/README.md
@@ -0,0 +1,31 @@
+Exo implements a display server on top of the Aura Shell. It uses the
+[Wayland protocol](https://wayland.freedesktop.org/docs/html/)
+to communicate with clients. For a general introduction to Wayland see
+https://wayland-book.com/.
+
+Current clients of Exo include:
+
+* ARC++ (Android apps on Chrome OS)
+* [Chromecast](https://chromium.googlesource.com/chromium/src/+/master/chromecast/README.md)
+* Crostini (Linux apps on Chrome OS)
+* [Lacros](https://chromium.googlesource.com/chromium/src/+/master/docs/lacros.md)
+* PluginVM
+
+In addition to the core Wayland protocol, Exo supports a number of protocol
+extensions. Some are third-party; see
+[//third_party/wayland-protocols/README.chromium](https://chromium.googlesource.com/chromium/src/+/master/third_party/wayland-protocols/README.chromium).
+Others are Chromium-specific.
+
+A few noteworthy extensions (this list is not at all exhaustive):
+
+* zaura_shell
+  * A Chromium-specific protocol used by all Exo clients. See
+    [//components/exo/wayland/protocol/aura-shell.xml](wayland/protocol/aura-shell.xml)
+    and [//components/exo/wayland/zaura_shell.h](wayland/zaura_shell.h)
+* zcr_remote_shell
+  * A Chromium-specific protocol used exclusively by ARC++. See
+    [//components/exo/wayland/zcr_remote_shell.h](wayland/zcr_remote_shell.h) and
+    [//components/exo/client_controlled_shell_surface.h](client_controlled_shell_surface.h)
+* zwp_fullscreen_shell
+  * A third-party protocol, used in Chromium only by Chromecast. See
+    [//components/exo/wayland/zwp_fullscreen_shell.h](wayland/zwp_fullscreen_shell.h)
diff --git a/components/language/core/common/language_experiments.cc b/components/language/core/common/language_experiments.cc
index 7ea587f..ad4827b 100644
--- a/components/language/core/common/language_experiments.cc
+++ b/components/language/core/common/language_experiments.cc
@@ -30,6 +30,8 @@
 };
 const base::Feature kNotifySyncOnLanguageDetermined{
     "NotifySyncOnLanguageDetermined", base::FEATURE_ENABLED_BY_DEFAULT};
+const base::Feature kDetailedLanguageSettings{
+    "DetailedLanguageSettings", base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Params:
 const char kBackoffThresholdKey[] = "backoff_threshold";
diff --git a/components/language/core/common/language_experiments.h b/components/language/core/common/language_experiments.h
index 6332de1..283be88 100644
--- a/components/language/core/common/language_experiments.h
+++ b/components/language/core/common/language_experiments.h
@@ -36,6 +36,9 @@
 // This feature uses the existing UI for translate bubble.
 extern const base::Feature kUseButtonTranslateBubbleUi;
 
+// This feature enables setting the application language on Android.
+extern const base::Feature kDetailedLanguageSettings;
+
 enum class OverrideLanguageModel {
   DEFAULT,
   FLUENT,
diff --git a/components/payments/content/BUILD.gn b/components/payments/content/BUILD.gn
index 77b0cd9..3581775 100644
--- a/components/payments/content/BUILD.gn
+++ b/components/payments/content/BUILD.gn
@@ -64,6 +64,7 @@
     "//components/webdata/common",
     "//content/public/browser",
     "//device/fido",
+    "//services/data_decoder/public/cpp",
     "//third_party/blink/public:blink_headers",
     "//url",
   ]
diff --git a/components/payments/content/secure_payment_confirmation_app_factory.cc b/components/payments/content/secure_payment_confirmation_app_factory.cc
index bf182e55bc..8dcd0a6 100644
--- a/components/payments/content/secure_payment_confirmation_app_factory.cc
+++ b/components/payments/content/secure_payment_confirmation_app_factory.cc
@@ -23,6 +23,7 @@
 #include "components/payments/core/secure_payment_confirmation_instrument.h"
 #include "components/webdata/common/web_data_results.h"
 #include "components/webdata/common/web_data_service_base.h"
+#include "services/data_decoder/public/cpp/decode_image.h"
 #include "third_party/blink/public/mojom/payments/payment_request.mojom.h"
 #include "url/origin.h"
 
@@ -187,10 +188,23 @@
   std::unique_ptr<SecurePaymentConfirmationInstrument> instrument =
       std::move(instruments.front());
 
-  // TODO(https://crbug.com/1110324): Decode `instrument->icon` from
-  // std::unique_ptr<std::vector<uint8_t>> into std::unique_ptr<SkBitmap> and
-  // check the icon validity.
-  auto icon = std::make_unique<SkBitmap>();
+  auto* instrument_ptr = instrument.get();
+  // Decode the icon in a sandboxed process off the main thread.
+  data_decoder::DecodeImageIsolated(
+      instrument_ptr->icon, data_decoder::mojom::ImageCodec::DEFAULT,
+      /*shrink_to_fit=*/false, data_decoder::kDefaultMaxSizeInBytes,
+      /*desired_image_frame_size=*/gfx::Size(),
+      base::BindOnce(&SecurePaymentConfirmationAppFactory::OnAppIconDecoded,
+                     weak_ptr_factory_.GetWeakPtr(), std::move(instrument),
+                     std::move(request)));
+}
+
+void SecurePaymentConfirmationAppFactory::OnAppIconDecoded(
+    std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+    std::unique_ptr<Request> request,
+    const SkBitmap& decoded_icon) {
+  DCHECK(!decoded_icon.drawsNothing());
+  auto icon = std::make_unique<SkBitmap>(decoded_icon);
 
   request->delegate->OnPaymentAppCreated(
       std::make_unique<SecurePaymentConfirmationApp>(
diff --git a/components/payments/content/secure_payment_confirmation_app_factory.h b/components/payments/content/secure_payment_confirmation_app_factory.h
index fd058bc..3eab505 100644
--- a/components/payments/content/secure_payment_confirmation_app_factory.h
+++ b/components/payments/content/secure_payment_confirmation_app_factory.h
@@ -14,6 +14,8 @@
 
 namespace payments {
 
+struct SecurePaymentConfirmationInstrument;
+
 class SecurePaymentConfirmationAppFactory : public PaymentAppFactory,
                                             public WebDataServiceConsumer {
  public:
@@ -42,6 +44,11 @@
       std::unique_ptr<autofill::InternalAuthenticator> authenticator,
       bool is_available);
 
+  void OnAppIconDecoded(
+      std::unique_ptr<SecurePaymentConfirmationInstrument> instrument,
+      std::unique_ptr<Request> request,
+      const SkBitmap& decoded_image);
+
   std::map<WebDataServiceBase::Handle, std::unique_ptr<Request>> requests_;
   base::WeakPtrFactory<SecurePaymentConfirmationAppFactory> weak_ptr_factory_{
       this};
diff --git a/components/services/heap_profiling/public/cpp/profiling_client.cc b/components/services/heap_profiling/public/cpp/profiling_client.cc
index cfdfe5d8..ff58a17 100644
--- a/components/services/heap_profiling/public/cpp/profiling_client.cc
+++ b/components/services/heap_profiling/public/cpp/profiling_client.cc
@@ -8,7 +8,6 @@
 #include <unordered_set>
 #include <vector>
 
-#include "base/allocator/allocator_interception_mac.h"
 #include "base/bind.h"
 #include "base/debug/stack_trace.h"
 #include "base/lazy_instance.h"
@@ -25,6 +24,10 @@
 #include "base/trace_event/cfi_backtrace_android.h"
 #endif
 
+#if defined(OS_APPLE)
+#include "base/allocator/allocator_interception_mac.h"
+#endif
+
 namespace heap_profiling {
 
 ProfilingClient::ProfilingClient() = default;
diff --git a/components/update_client/protocol_definition.h b/components/update_client/protocol_definition.h
index aaacc76..f82015d 100644
--- a/components/update_client/protocol_definition.h
+++ b/components/update_client/protocol_definition.h
@@ -103,6 +103,8 @@
   std::string cohort_hint;  // Server may use to move the app to a new cohort.
   std::string cohort_name;  // Human-readable interpretation of the cohort.
 
+  std::string release_channel;
+
   base::Optional<bool> enabled;
   base::Optional<std::vector<int>> disabled_reasons;
 
diff --git a/components/update_client/protocol_serializer.cc b/components/update_client/protocol_serializer.cc
index 1074c93..1bec4106 100644
--- a/components/update_client/protocol_serializer.cc
+++ b/components/update_client/protocol_serializer.cc
@@ -193,6 +193,7 @@
     const std::string& cohort,
     const std::string& cohort_hint,
     const std::string& cohort_name,
+    const std::string& release_channel,
     const std::vector<int>& disabled_reasons,
     base::Optional<protocol_request::UpdateCheck> update_check,
     base::Optional<protocol_request::Ping> ping) {
@@ -205,6 +206,7 @@
   app.cohort = cohort;
   app.cohort_hint = cohort_hint;
   app.cohort_name = cohort_name;
+  app.release_channel = release_channel;
   app.enabled = disabled_reasons.empty();
   app.disabled_reasons = disabled_reasons;
   app.update_check = std::move(update_check);
diff --git a/components/update_client/protocol_serializer.h b/components/update_client/protocol_serializer.h
index 47c212a4..5ebe196 100644
--- a/components/update_client/protocol_serializer.h
+++ b/components/update_client/protocol_serializer.h
@@ -60,6 +60,7 @@
     const std::string& cohort,
     const std::string& cohort_hint,
     const std::string& cohort_name,
+    const std::string& release_channel,
     const std::vector<int>& disabled_reasons,
     base::Optional<protocol_request::UpdateCheck> update_check,
     base::Optional<protocol_request::Ping> ping);
diff --git a/components/update_client/protocol_serializer_json.cc b/components/update_client/protocol_serializer_json.cc
index 8e7aee8..ea7f220 100644
--- a/components/update_client/protocol_serializer_json.cc
+++ b/components/update_client/protocol_serializer_json.cc
@@ -97,6 +97,10 @@
       app_node.SetKey("installsource", Value(app.install_source));
     if (!app.install_location.empty())
       app_node.SetKey("installedby", Value(app.install_location));
+    // TODO(crbug/1120685): Test that this is never sent to the server if the
+    // machine is not enterprise managed.
+    if (!app.release_channel.empty())
+      app_node.SetKey("release_channel", Value(app.release_channel));
     if (!app.cohort.empty())
       app_node.SetKey("cohort", Value(app.cohort));
     if (!app.cohort_name.empty())
diff --git a/components/update_client/protocol_serializer_json_unittest.cc b/components/update_client/protocol_serializer_json_unittest.cc
index eb9fca0..48d7879 100644
--- a/components/update_client/protocol_serializer_json_unittest.cc
+++ b/components/update_client/protocol_serializer_json_unittest.cc
@@ -47,7 +47,7 @@
   std::vector<protocol_request::App> apps;
   apps.push_back(MakeProtocolApp(
       "id1", base::Version("1.0"), "brand1", "source1", "location1", "fp1",
-      {{"attr1", "1"}, {"attr2", "2"}}, "c1", "ch1", "cn1", {0, 1},
+      {{"attr1", "1"}, {"attr2", "2"}}, "c1", "ch1", "cn1", "test", {0, 1},
       MakeProtocolUpdateCheck(true), MakeProtocolPing("id1", metadata.get())));
   apps.push_back(
       MakeProtocolApp("id2", base::Version("2.0"), std::move(events)));
@@ -65,6 +65,7 @@
       R"("installedby":"location1","installsource":"source1",)"
       R"("packages":{"package":\[{"fp":"fp1"}]},)"
       R"("ping":{"ping_freshness":"{[-\w]{36}}","rd":1234},)"
+      R"("release_channel":"test",)"
       R"("updatecheck":{"updatedisabled":true},"version":"1.0"},)"
       R"({"appid":"id2","event":\[{"a":1,"b":"2"},{"error":0}],)"
       R"("version":"2.0"}],"arch":"\w+","dedup":"cr","dlpref":"cacheable",)"
@@ -74,7 +75,7 @@
       R"("prodversion":"1.0","protocol":"3.1","requestid":"{[-\w]{36}}",)"
       R"("sessionid":"{[-\w]{36}}","updaterchannel":"channel",)"
       R"("updaterversion":"1.0"(,"wow64":true)?}})";
-  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+  EXPECT_TRUE(RE2::FullMatch(request, regex)) << request << "\n VS \n" << regex;
 }
 
 TEST(SerializeRequestJSON, DownloadPreference) {
diff --git a/components/update_client/update_checker.cc b/components/update_client/update_checker.cc
index 9eea680..facdea48 100644
--- a/components/update_client/update_checker.cc
+++ b/components/update_client/update_checker.cc
@@ -200,7 +200,8 @@
         crx_component->fingerprint,
         SanitizeInstallerAttributes(crx_component->installer_attributes),
         metadata_->GetCohort(app_id), metadata_->GetCohortName(app_id),
-        metadata_->GetCohortHint(app_id), crx_component->disabled_reasons,
+        metadata_->GetCohortHint(app_id), crx_component->channel,
+        crx_component->disabled_reasons,
         MakeProtocolUpdateCheck(is_update_disabled),
         MakeProtocolPing(app_id, metadata_)));
   }
diff --git a/components/update_client/update_client.h b/components/update_client/update_client.h
index 45bc2150..be4f702 100644
--- a/components/update_client/update_client.h
+++ b/components/update_client/update_client.h
@@ -315,6 +315,11 @@
   // For extensions, this information is inferred from the extension
   // registry.
   std::string install_location;
+
+  // Information about the channel to send to the update server when updating
+  // the component. This optional field is typically populated by policy and is
+  // only populated on managed devices.
+  std::string channel;
 };
 
 // Called when a non-blocking call of UpdateClient completes.
diff --git a/components/viz/common/gl_i420_converter.cc b/components/viz/common/gl_i420_converter.cc
index 3bfc042..c00e202 100644
--- a/components/viz/common/gl_i420_converter.cc
+++ b/components/viz/common/gl_i420_converter.cc
@@ -10,16 +10,17 @@
 
 namespace viz {
 
-GLI420Converter::GLI420Converter(
-    scoped_refptr<ContextProvider> context_provider)
-    : GLI420Converter(std::move(context_provider), true) {}
+GLI420Converter::GLI420Converter(ContextProvider* context_provider)
+    : GLI420Converter(context_provider, true) {
+  DCHECK(context_provider_);
+}
 
-GLI420Converter::GLI420Converter(
-    scoped_refptr<ContextProvider> context_provider,
-    bool allow_mrt_path)
-    : context_provider_(std::move(context_provider)),
+GLI420Converter::GLI420Converter(ContextProvider* context_provider,
+                                 bool allow_mrt_path)
+    : context_provider_(context_provider),
       step1_(context_provider_),
       step2_(context_provider_) {
+  DCHECK(context_provider_);
   context_provider_->AddObserver(this);
   if (!allow_mrt_path || step1_.GetMaxDrawBuffersSupported() < 2) {
     step3_ = std::make_unique<GLScaler>(context_provider_);
diff --git a/components/viz/common/gl_i420_converter.h b/components/viz/common/gl_i420_converter.h
index 779445a..b5be44c 100644
--- a/components/viz/common/gl_i420_converter.h
+++ b/components/viz/common/gl_i420_converter.h
@@ -76,7 +76,7 @@
   // GLI420Converter uses the exact same parameters as GLScaler.
   using Parameters = GLScaler::Parameters;
 
-  explicit GLI420Converter(scoped_refptr<ContextProvider> context_provider);
+  explicit GLI420Converter(ContextProvider* context_provider);
   ~GLI420Converter() final;
 
   // Returns true if the GL context provides the necessary support for enabling
@@ -145,8 +145,7 @@
  private:
   friend class GLI420ConverterPixelTest;
 
-  GLI420Converter(scoped_refptr<ContextProvider> context_provider,
-                  bool allow_mrt_path);
+  GLI420Converter(ContextProvider* context_provider, bool allow_mrt_path);
 
   bool is_using_mrt_path() const { return !step3_; }
 
@@ -159,7 +158,7 @@
 
   // The provider of the GL context. This is non-null while the GL context is
   // valid and GLI420Converter is observing for context loss.
-  scoped_refptr<ContextProvider> context_provider_;
+  ContextProvider* context_provider_;
 
   // Scales the source content and produces either:
   //   * MRT path: NV61-format output in two textures.
diff --git a/components/viz/common/gl_scaler.cc b/components/viz/common/gl_scaler.cc
index c75e71f3..129fc12 100644
--- a/components/viz/common/gl_scaler.cc
+++ b/components/viz/common/gl_scaler.cc
@@ -42,8 +42,8 @@
 
 }  // namespace
 
-GLScaler::GLScaler(scoped_refptr<ContextProvider> context_provider)
-    : context_provider_(std::move(context_provider)) {
+GLScaler::GLScaler(ContextProvider* context_provider)
+    : context_provider_(context_provider) {
   if (context_provider_) {
     DCHECK(context_provider_->ContextGL());
     context_provider_->AddObserver(this);
diff --git a/components/viz/common/gl_scaler.h b/components/viz/common/gl_scaler.h
index dd1dd7b3..9304ba2 100644
--- a/components/viz/common/gl_scaler.h
+++ b/components/viz/common/gl_scaler.h
@@ -191,7 +191,7 @@
     ~Parameters();
   };
 
-  explicit GLScaler(scoped_refptr<ContextProvider> context_provider);
+  explicit GLScaler(ContextProvider* context_provider);
 
   ~GLScaler() final;
 
@@ -474,7 +474,7 @@
 
   // The provider of the GL context. This is non-null while the GL context is
   // valid and GLScaler is observing for context loss.
-  scoped_refptr<ContextProvider> context_provider_;
+  ContextProvider* context_provider_;
 
   // Set by Configure() to the resolved set of Parameters.
   Parameters params_;
diff --git a/components/viz/common/gl_scaler_unittest.cc b/components/viz/common/gl_scaler_unittest.cc
index 69e9c6d..3f6c393b7 100644
--- a/components/viz/common/gl_scaler_unittest.cc
+++ b/components/viz/common/gl_scaler_unittest.cc
@@ -99,7 +99,7 @@
       .WillOnce(SaveArg<0>(&registered_observer));
   EXPECT_CALL(provider, RemoveObserver(Eq(ByRef(registered_observer))))
       .InSequence(s);
-  GLScaler scaler(base::WrapRefCounted(&provider));
+  GLScaler scaler(&provider);
 }
 
 TEST_F(GLScalerTest, RemovesObserverWhenContextIsLost) {
@@ -111,7 +111,7 @@
       .WillOnce(SaveArg<0>(&registered_observer));
   EXPECT_CALL(provider, RemoveObserver(Eq(ByRef(registered_observer))))
       .InSequence(s);
-  GLScaler scaler(base::WrapRefCounted(&provider));
+  GLScaler scaler(&provider);
   static_cast<ContextLostObserver&>(scaler).OnContextLost();
   // Verify RemoveObserver() was called before |scaler| goes out-of-scope.
   Mock::VerifyAndClearExpectations(&provider);
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
index 8d50154..c725e7c2 100644
--- a/components/viz/service/display/gl_renderer.cc
+++ b/components/viz/service/display/gl_renderer.cc
@@ -2092,11 +2092,35 @@
   SetupQuadForClippingAndAntialiasing(device_transform, quad, aa_quad,
                                       clip_region, &local_quad, edge);
 
-  gfx::ColorSpace quad_color_space = gfx::ColorSpace::CreateSRGB();
   SetUseProgram(ProgramKey::SolidColor(use_aa ? USE_AA : NO_AA,
                                        tint_gl_composited_content_,
                                        ShouldApplyRoundedCorner(quad)),
-                quad_color_space, CurrentRenderPassColorSpace());
+                CurrentRenderPassColorSpace(), CurrentRenderPassColorSpace());
+
+  gfx::ColorSpace quad_color_space = gfx::ColorSpace::CreateSRGB();
+  SkColor4f color_f = SkColor4f::FromColor(color);
+
+  // Apply color transform if the color space or source and target do not match.
+  if (quad_color_space != CurrentRenderPassColorSpace()) {
+    const gfx::ColorTransform* color_transform =
+        GetColorTransform(quad_color_space, CurrentRenderPassColorSpace());
+    gfx::ColorTransform::TriStim col(color_f.fR, color_f.fG, color_f.fB);
+    color_transform->Transform(&col, 1);
+    color_f.fR = col.x();
+    color_f.fG = col.y();
+    color_f.fB = col.z();
+    color = color_f.toSkColor();
+  }
+
+  // Apply any color matrix that may be present.
+  if (HasOutputColorMatrix()) {
+    const SkMatrix44& output_color_matrix = output_surface_->color_matrix();
+    const SkVector4 color_v(color_f.fR, color_f.fG, color_f.fB, color_f.fA);
+    const SkVector4 result = output_color_matrix * color_v;
+    std::copy(result.fData, result.fData + 4, color_f.vec());
+    color = color_f.toSkColor();
+  }
+
   SetShaderColor(color, opacity);
   if (current_program_->rounded_corner_rect_location() != -1) {
     SetShaderRoundedCorner(
@@ -3537,12 +3561,9 @@
       GetColorTransform(adjusted_src_color_space, dst_color_space);
   program_key.SetColorTransform(color_transform);
 
-  const bool is_root_render_pass =
-      current_frame()->current_render_pass == current_frame()->root_render_pass;
-  const SkMatrix44& output_color_matrix = output_surface_->color_matrix();
-  const bool has_output_color_matrix =
-      is_root_render_pass && !output_color_matrix.isIdentity();
-
+  bool has_output_color_matrix = false;
+  if (program_key.type() != ProgramType::PROGRAM_TYPE_SOLID_COLOR)
+    has_output_color_matrix = HasOutputColorMatrix();
   program_key.set_has_output_color_matrix(has_output_color_matrix);
 
   // Create and set the program if needed.
@@ -3577,7 +3598,7 @@
   if (has_output_color_matrix) {
     DCHECK_NE(current_program_->output_color_matrix_location(), -1);
     float matrix[16];
-    output_color_matrix.asColMajorf(matrix);
+    output_surface_->color_matrix().asColMajorf(matrix);
     gl_->UniformMatrix4fv(current_program_->output_color_matrix_location(), 1,
                           false, matrix);
   }
@@ -4217,6 +4238,13 @@
   return PlatformColor::BestSupportedTextureFormat(caps);
 }
 
+bool GLRenderer::HasOutputColorMatrix() const {
+  const bool is_root_render_pass =
+      current_frame()->current_render_pass == current_frame()->root_render_pass;
+  const SkMatrix44& output_color_matrix = output_surface_->color_matrix();
+  return is_root_render_pass && !output_color_matrix.isIdentity();
+}
+
 void GLRenderer::AllocateRenderPassResourceIfNeeded(
     const AggregatedRenderPassId& render_pass_id,
     const RenderPassRequirements& requirements) {
diff --git a/components/viz/service/display/gl_renderer.h b/components/viz/service/display/gl_renderer.h
index 81901243..e668d91 100644
--- a/components/viz/service/display/gl_renderer.h
+++ b/components/viz/service/display/gl_renderer.h
@@ -370,6 +370,8 @@
 
   ResourceFormat CurrentRenderPassResourceFormat() const;
 
+  bool HasOutputColorMatrix() const;
+
   // A map from RenderPass id to the texture used to draw the RenderPass from.
   base::flat_map<AggregatedRenderPassId, ScopedRenderPassTexture>
       render_pass_textures_;
diff --git a/components/viz/service/display/gl_renderer_copier.cc b/components/viz/service/display/gl_renderer_copier.cc
index cb8fc93..115f0535 100644
--- a/components/viz/service/display/gl_renderer_copier.cc
+++ b/components/viz/service/display/gl_renderer_copier.cc
@@ -28,8 +28,6 @@
 #include "third_party/libyuv/include/libyuv/planar_functions.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 #include "third_party/skia/include/core/SkImageInfo.h"
-#include "ui/gfx/color_space.h"
-#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
 // Syntactic sugar to DCHECK that two sizes are equal.
@@ -108,11 +106,9 @@
 
 }  // namespace
 
-GLRendererCopier::GLRendererCopier(
-    scoped_refptr<ContextProvider> context_provider,
-    TextureDeleter* texture_deleter)
-    : context_provider_(std::move(context_provider)),
-      texture_deleter_(texture_deleter) {}
+GLRendererCopier::GLRendererCopier(ContextProvider* context_provider,
+                                   TextureDeleter* texture_deleter)
+    : context_provider_(context_provider), texture_deleter_(texture_deleter) {}
 
 GLRendererCopier::~GLRendererCopier() {
   for (auto& entry : cache_)
@@ -373,27 +369,43 @@
 
 namespace {
 
+// This is the type of CopyOutputResult we send for RGBA readback. The
+// constructor is called during on GLRendererCopier::FinishReadPixelsWorkflow(),
+// thus it always have access to the GLContext. The ReadRGBAPlane and destructor
+// are called asynchronously, and thus might not have access to the GLContext if
+// it has been destroyed in the meantime. We use the WeakPtr to the
+// GLRendererCopier as an indicator that the GLContext is still alive. If the
+// access to the GLContext is lost, we treat the copy output as failed.
 class GLPixelBufferRGBAResult : public CopyOutputResult {
  public:
   GLPixelBufferRGBAResult(const gfx::Rect& result_rect,
                           const gfx::ColorSpace& color_space,
-                          scoped_refptr<ContextProvider> context_provider,
+                          base::WeakPtr<GLRendererCopier> copier_weak_ptr,
+                          ContextProvider* context_provider,
                           GLuint transfer_buffer,
                           bool is_upside_down,
                           bool swap_red_and_blue)
       : CopyOutputResult(CopyOutputResult::Format::RGBA_BITMAP, result_rect),
         color_space_(color_space),
+        copier_weak_ptr_(std::move(copier_weak_ptr)),
         context_provider_(std::move(context_provider)),
         transfer_buffer_(transfer_buffer),
         is_upside_down_(is_upside_down),
         swap_red_and_blue_(swap_red_and_blue) {}
 
   ~GLPixelBufferRGBAResult() final {
-    if (transfer_buffer_)
+    if (transfer_buffer_ && copier_weak_ptr_) {
       context_provider_->ContextGL()->DeleteBuffers(1, &transfer_buffer_);
+    }
   }
 
   bool ReadRGBAPlane(uint8_t* dest, int stride) const final {
+    // If the GLRendererCopier is gone, this implies the display compositor
+    // which contains the GLContext is gone. Regard this copy output readback as
+    // failed.
+    if (!copier_weak_ptr_)
+      return false;
+
     const int src_bytes_per_row = size().width() * kRGBABytesPerPixel;
     DCHECK_GE(stride, src_bytes_per_row);
 
@@ -434,6 +446,10 @@
 
   gfx::ColorSpace GetRGBAColorSpace() const final { return color_space_; }
 
+  // This method is always called on the same sequence as the GLRendererCopier.
+  // This method will be inside Viz and has access to the WeakPtr of the
+  // GLRendererCopier to check whether we still have the access to an alive
+  // GLContext.
   const SkBitmap& AsSkBitmap() const final {
     if (rect().IsEmpty())
       return *cached_bitmap();  // Return "null" bitmap for empty result.
@@ -441,7 +457,9 @@
     if (cached_bitmap()->readyToDraw())
       return *cached_bitmap();
 
-    DCHECK(context_provider_);
+    if (!copier_weak_ptr_)
+      return *cached_bitmap();
+
     SkBitmap result_bitmap;
     // size() was clamped to render pass or framebuffer size. If we can't
     // allocate it then OOM.
@@ -458,114 +476,86 @@
     context_provider_->ContextGL()->DeleteBuffers(1, &transfer_buffer_);
     transfer_buffer_ = 0;
 
-    // We don't need context provider anymore. If these CopyOutputResults will
-    // be sent to different thread we might end holding last reference to
-    // context provider, so drop it now.
-    context_provider_.reset();
-
     return *cached_bitmap();
   }
 
  private:
   const gfx::ColorSpace color_space_;
-  mutable scoped_refptr<ContextProvider> context_provider_;
+  base::WeakPtr<GLRendererCopier> copier_weak_ptr_;
+  ContextProvider* context_provider_;
   mutable GLuint transfer_buffer_;
   const bool is_upside_down_;
   const bool swap_red_and_blue_;
 };
-
-// Manages the execution of one asynchronous framebuffer readback and contains
-// all the relevant state needed to complete a copy request. The constructor
-// initiates the operation, and then at some later point either: 1) the Finish()
-// method is invoked; or 2) the instance will be destroyed (cancelled) because
-// the GL context is going away. Either way, the GL objects created for this
-// workflow are properly cleaned-up.
-//
-// Motivation: In case #2, it's possible GLRendererCopier will have been
-// destroyed before Finish(). However, since there are no dependencies on
-// GLRendererCopier to finish the copy request, there's no reason to mess around
-// with a complex WeakPtr-to-GLRendererCopier scheme.
-class ReadPixelsWorkflow {
- public:
-  // Saves all revelant state and initiates the GL asynchronous read-pixels
-  // workflow.
-  ReadPixelsWorkflow(std::unique_ptr<CopyOutputRequest> copy_request,
-                     const gfx::Vector2d& readback_offset,
-                     bool flipped_source,
-                     bool swap_red_and_blue,
-                     const gfx::Rect& result_rect,
-                     const gfx::ColorSpace& color_space,
-                     scoped_refptr<ContextProvider> context_provider,
-                     GLenum readback_format)
-      : copy_request_(std::move(copy_request)),
-        flipped_source_(flipped_source),
-        swap_red_and_blue_(swap_red_and_blue),
-        result_rect_(result_rect),
-        color_space_(color_space),
-        context_provider_(std::move(context_provider)) {
-    DCHECK(readback_format == GL_RGBA || readback_format == GL_BGRA_EXT);
-
-    auto* const gl = context_provider_->ContextGL();
-
-    // Create a buffer for the pixel transfer.
-    gl->GenBuffers(1, &transfer_buffer_);
-    gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
-    gl->BufferData(
-        GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
-        (result_rect.size().GetCheckedArea() * kRGBABytesPerPixel).ValueOrDie(),
-        nullptr, GL_STREAM_READ);
-
-    // Execute an asynchronous read-pixels operation, with a query that triggers
-    // when Finish() should be run.
-    gl->GenQueriesEXT(1, &query_);
-    gl->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, query_);
-    gl->ReadPixels(readback_offset.x(), readback_offset.y(),
-                   result_rect.width(), result_rect.height(), readback_format,
-                   GL_UNSIGNED_BYTE, nullptr);
-    gl->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
-    gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
-  }
-
-  // The destructor is called when the callback that owns this instance is
-  // destroyed. That will happen either with or without a call to Finish(),
-  // but either way everything will be clean-up appropriately.
-  ~ReadPixelsWorkflow() {
-    auto* const gl = context_provider_->ContextGL();
-    gl->DeleteQueriesEXT(1, &query_);
-    if (transfer_buffer_)
-      gl->DeleteBuffers(1, &transfer_buffer_);
-  }
-
-  GLuint query() const { return query_; }
-
-  // Callback for the asynchronous glReadPixels(). The pixels are read from the
-  // transfer buffer, and a CopyOutputResult is sent to the requestor.
-  void Finish() {
-    auto result = std::make_unique<GLPixelBufferRGBAResult>(
-        result_rect_, color_space_, context_provider_, transfer_buffer_,
-        flipped_source_, swap_red_and_blue_);
-    transfer_buffer_ = 0;  // Ownerhip was transferred to the result.
-    if (!copy_request_->SendsResultsInCurrentSequence()) {
-      // Force readback into a SkBitmap now, because after PostTask we don't
-      // have access to |context_provider_|.
-      result->AsSkBitmap();
-    }
-    copy_request_->SendResult(std::move(result));
-  }
-
- private:
-  const std::unique_ptr<CopyOutputRequest> copy_request_;
-  const bool flipped_source_;
-  const bool swap_red_and_blue_;
-  const gfx::Rect result_rect_;
-  const gfx::ColorSpace color_space_;
-  const scoped_refptr<ContextProvider> context_provider_;
-  GLuint transfer_buffer_ = 0;
-  GLuint query_ = 0;
-};
-
 }  // namespace
 
+GLRendererCopier::ReadPixelsWorkflow::ReadPixelsWorkflow(
+    std::unique_ptr<CopyOutputRequest> copy_request,
+    const gfx::Vector2d& readback_offset,
+    bool flipped_source,
+    bool swap_red_and_blue,
+    const gfx::Rect& result_rect,
+    const gfx::ColorSpace& color_space,
+    ContextProvider* context_provider,
+    GLenum readback_format)
+    : copy_request(std::move(copy_request)),
+      flipped_source(flipped_source),
+      swap_red_and_blue(swap_red_and_blue),
+      result_rect(result_rect),
+      color_space(color_space),
+      context_provider_(context_provider) {
+  DCHECK(readback_format == GL_RGBA || readback_format == GL_BGRA_EXT);
+  DCHECK(context_provider_);
+  auto* const gl = context_provider_->ContextGL();
+
+  // Create a buffer for the pixel transfer.
+  gl->GenBuffers(1, &transfer_buffer);
+  gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer);
+  gl->BufferData(
+      GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+      (result_rect.size().GetCheckedArea() * kRGBABytesPerPixel).ValueOrDie(),
+      nullptr, GL_STREAM_READ);
+
+  // Execute an asynchronous read-pixels operation, with a query that triggers
+  // when Finish() should be run.
+  gl->GenQueriesEXT(1, &query_);
+  gl->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, query_);
+  gl->ReadPixels(readback_offset.x(), readback_offset.y(), result_rect.width(),
+                 result_rect.height(), readback_format, GL_UNSIGNED_BYTE,
+                 nullptr);
+  gl->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
+  gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
+}
+
+GLRendererCopier::ReadPixelsWorkflow::~ReadPixelsWorkflow() {
+  auto* const gl = context_provider_->ContextGL();
+  gl->DeleteQueriesEXT(1, &query_);
+  if (transfer_buffer)
+    gl->DeleteBuffers(1, &transfer_buffer);
+}
+
+// Callback for the asynchronous glReadPixels(). The pixels are read from the
+// transfer buffer, and a CopyOutputResult is sent to the requestor. This would
+// mark this workflow as finished, and the workflow will be cleared later.
+void GLRendererCopier::FinishReadPixelsWorkflow(ReadPixelsWorkflow* workflow) {
+  auto result = std::make_unique<GLPixelBufferRGBAResult>(
+      workflow->result_rect, workflow->color_space, weak_factory_.GetWeakPtr(),
+      context_provider_, workflow->transfer_buffer, workflow->flipped_source,
+      workflow->swap_red_and_blue);
+  workflow->transfer_buffer = 0;  // Ownerhip was transferred to the result.
+  if (!workflow->copy_request->SendsResultsInCurrentSequence()) {
+    // Force readback into a SkBitmap now, because after PostTask we don't
+    // have access to |context_provider_|.
+    result->AsSkBitmap();
+  }
+  workflow->copy_request->SendResult(std::move(result));
+  const auto it =
+      std::find_if(read_pixels_workflows_.begin(), read_pixels_workflows_.end(),
+                   [workflow](auto& ptr) { return ptr.get() == workflow; });
+  DCHECK(it != read_pixels_workflows_.end());
+  read_pixels_workflows_.erase(it);
+}
+
 void GLRendererCopier::StartReadbackFromFramebuffer(
     std::unique_ptr<CopyOutputRequest> request,
     const gfx::Vector2d& readback_offset,
@@ -575,13 +565,15 @@
     const gfx::ColorSpace& color_space) {
   DCHECK_EQ(request->result_format(), ResultFormat::RGBA_BITMAP);
 
-  auto workflow = std::make_unique<ReadPixelsWorkflow>(
+  read_pixels_workflows_.emplace_back(std::make_unique<ReadPixelsWorkflow>(
       std::move(request), readback_offset, flipped_source,
       ShouldSwapRedAndBlueForBitmapReadback() != swapped_red_and_blue,
-      result_rect, color_space, context_provider_, GetOptimalReadbackFormat());
-  const GLuint query = workflow->query();
+      result_rect, color_space, context_provider_, GetOptimalReadbackFormat()));
   context_provider_->ContextSupport()->SignalQuery(
-      query, base::BindOnce(&ReadPixelsWorkflow::Finish, std::move(workflow)));
+      read_pixels_workflows_.back()->query(),
+      base::BindOnce(&GLRendererCopier::FinishReadPixelsWorkflow,
+                     weak_factory_.GetWeakPtr(),
+                     read_pixels_workflows_.back().get()));
 }
 
 void GLRendererCopier::RenderAndSendTextureResult(
@@ -636,14 +628,15 @@
  public:
   // |aligned_rect| identifies the region of result pixels in the pixel buffer,
   // while the |result_rect| is the subregion that is exposed to the client.
-  GLPixelBufferI420Result(
-      const gfx::Rect& aligned_rect,
-      const gfx::Rect& result_rect,
-      scoped_refptr<ContextProvider> context_provider,
-      GLuint transfer_buffer)
+  GLPixelBufferI420Result(const gfx::Rect& aligned_rect,
+                          const gfx::Rect& result_rect,
+                          base::WeakPtr<GLRendererCopier> copier_weak_ptr,
+                          ContextProvider* context_provider,
+                          GLuint transfer_buffer)
       : CopyOutputResult(CopyOutputResult::Format::I420_PLANES, result_rect),
         aligned_rect_(aligned_rect),
-        context_provider_(std::move(context_provider)),
+        copier_weak_ptr_(copier_weak_ptr),
+        context_provider_(context_provider),
         transfer_buffer_(transfer_buffer) {
     auto* const gl = context_provider_->ContextGL();
     gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
@@ -653,11 +646,13 @@
   }
 
   ~GLPixelBufferI420Result() final {
-    auto* const gl = context_provider_->ContextGL();
-    gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
-    gl->UnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
-    gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
-    gl->DeleteBuffers(1, &transfer_buffer_);
+    if (copier_weak_ptr_) {
+      auto* const gl = context_provider_->ContextGL();
+      gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
+      gl->UnmapBufferCHROMIUM(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM);
+      gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
+      gl->DeleteBuffers(1, &transfer_buffer_);
+    }
   }
 
   bool ReadI420Planes(uint8_t* y_out,
@@ -670,6 +665,8 @@
     const int chroma_row_bytes = (size().width() + 1) / 2;
     DCHECK_GE(u_out_stride, chroma_row_bytes);
     DCHECK_GE(v_out_stride, chroma_row_bytes);
+    if (!copier_weak_ptr_)
+      return false;
 
     uint8_t* pixels = pixels_;
     if (pixels) {
@@ -696,131 +693,98 @@
 
  private:
   const gfx::Rect aligned_rect_;
-  const scoped_refptr<ContextProvider> context_provider_;
+  base::WeakPtr<GLRendererCopier> copier_weak_ptr_;
+  ContextProvider* const context_provider_;
   const GLuint transfer_buffer_;
   uint8_t* pixels_;
 };
-
-// Like the ReadPixelsWorkflow, except for I420 planes readback. Because there
-// are three separate glReadPixels operations that may complete in any order, a
-// ReadI420PlanesWorkflow will receive notifications from three separate "GL
-// query" callbacks. It is only after all three operations have completed that a
-// fully-assembled CopyOutputResult can be sent.
-//
-// Please see class comments for ReadPixelsWorkflow for discussion about how GL
-// context loss is handled during the workflow.
-//
-// Also, see class comments for GLI420Converter for an explanation of how planar
-// data is packed into RGBA textures.
-class ReadI420PlanesWorkflow
-    : public base::RefCountedThreadSafe<ReadI420PlanesWorkflow> {
- public:
-  ReadI420PlanesWorkflow(
-      std::unique_ptr<CopyOutputRequest> copy_request,
-      const gfx::Rect& aligned_rect,
-      const gfx::Rect& result_rect,
-      scoped_refptr<ContextProvider> context_provider)
-      : copy_request_(std::move(copy_request)),
-        aligned_rect_(aligned_rect),
-        result_rect_(result_rect),
-        context_provider_(std::move(context_provider)) {
-    // Create a buffer for the pixel transfer: A single buffer is used and will
-    // contain the Y plane, then the U plane, then the V plane.
-    auto* const gl = context_provider_->ContextGL();
-    gl->GenBuffers(1, &transfer_buffer_);
-    gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
-    base::CheckedNumeric<int> y_plane_bytes =
-        y_texture_size().GetCheckedArea() * kRGBABytesPerPixel;
-    base::CheckedNumeric<int> chroma_plane_bytes =
-        chroma_texture_size().GetCheckedArea() * kRGBABytesPerPixel;
-    gl->BufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
-                   (y_plane_bytes + chroma_plane_bytes * 2).ValueOrDie(),
-                   nullptr, GL_STREAM_READ);
-    data_offsets_ = {0, y_plane_bytes.ValueOrDie(),
-                     (y_plane_bytes + chroma_plane_bytes).ValueOrDie()};
-    gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
-
-    // Generate the three queries used for determining when each of the plane
-    // readbacks has completed.
-    gl->GenQueriesEXT(3, queries_.data());
-  }
-
-  void BindTransferBuffer() {
-    DCHECK_NE(transfer_buffer_, 0u);
-    context_provider_->ContextGL()->BindBuffer(
-        GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer_);
-  }
-
-  void StartPlaneReadback(int plane, GLenum readback_format) {
-    DCHECK_NE(queries_[plane], 0u);
-    auto* const gl = context_provider_->ContextGL();
-    gl->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, queries_[plane]);
-    const gfx::Size& size =
-        plane == 0 ? y_texture_size() : chroma_texture_size();
-    // Note: While a PIXEL_PACK_BUFFER is bound, OpenGL interprets the last
-    // argument to ReadPixels() as a byte offset within the buffer instead of
-    // an actual pointer in system memory.
-    uint8_t* offset_in_buffer = 0;
-    offset_in_buffer += data_offsets_[plane];
-    gl->ReadPixels(0, 0, size.width(), size.height(), readback_format,
-                   GL_UNSIGNED_BYTE, offset_in_buffer);
-    gl->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
-    context_provider_->ContextSupport()->SignalQuery(
-        queries_[plane],
-        base::BindOnce(&ReadI420PlanesWorkflow::OnFinishedPlane, this, plane));
-  }
-
-  void UnbindTransferBuffer() {
-    context_provider_->ContextGL()->BindBuffer(
-        GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
-  }
-
- private:
-  friend class base::RefCountedThreadSafe<ReadI420PlanesWorkflow>;
-
-  ~ReadI420PlanesWorkflow() {
-    auto* const gl = context_provider_->ContextGL();
-    if (transfer_buffer_ != 0)
-      gl->DeleteBuffers(1, &transfer_buffer_);
-    for (GLuint& query : queries_) {
-      if (query != 0)
-        gl->DeleteQueriesEXT(1, &query);
-    }
-  }
-
-  gfx::Size y_texture_size() const {
-    return gfx::Size(aligned_rect_.width() / kRGBABytesPerPixel,
-                     aligned_rect_.height());
-  }
-
-  gfx::Size chroma_texture_size() const {
-    return gfx::Size(aligned_rect_.width() / kRGBABytesPerPixel / 2,
-                     aligned_rect_.height() / 2);
-  }
-
-  void OnFinishedPlane(int plane) {
-    context_provider_->ContextGL()->DeleteQueriesEXT(1, &queries_[plane]);
-    queries_[plane] = 0;
-
-    // If all three readbacks have completed, send the result.
-    if (queries_ == std::array<GLuint, 3>{{0, 0, 0}}) {
-      copy_request_->SendResult(std::make_unique<GLPixelBufferI420Result>(
-          aligned_rect_, result_rect_, context_provider_, transfer_buffer_));
-      transfer_buffer_ = 0;  // Ownership was transferred to the result.
-    }
-  }
-
-  const std::unique_ptr<CopyOutputRequest> copy_request_;
-  const gfx::Rect aligned_rect_;
-  const gfx::Rect result_rect_;
-  const scoped_refptr<ContextProvider> context_provider_;
-  GLuint transfer_buffer_;
-  std::array<int, 3> data_offsets_;
-  std::array<GLuint, 3> queries_;
-};
-
 }  // namespace
 
+GLRendererCopier::ReadI420PlanesWorkflow::ReadI420PlanesWorkflow(
+    std::unique_ptr<CopyOutputRequest> copy_request,
+    const gfx::Rect& aligned_rect,
+    const gfx::Rect& result_rect,
+    base::WeakPtr<GLRendererCopier> copier_weak_ptr,
+    ContextProvider* context_provider)
+    : copy_request(std::move(copy_request)),
+      aligned_rect(aligned_rect),
+      result_rect(result_rect),
+      copier_weak_ptr_(copier_weak_ptr),
+      context_provider_(context_provider) {
+  // Create a buffer for the pixel transfer: A single buffer is used and will
+  // contain the Y plane, then the U plane, then the V plane.
+  auto* const gl = context_provider_->ContextGL();
+  gl->GenBuffers(1, &transfer_buffer);
+  gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer);
+  base::CheckedNumeric<int> y_plane_bytes =
+      y_texture_size().GetCheckedArea() * kRGBABytesPerPixel;
+  base::CheckedNumeric<int> chroma_plane_bytes =
+      chroma_texture_size().GetCheckedArea() * kRGBABytesPerPixel;
+  gl->BufferData(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM,
+                 (y_plane_bytes + chroma_plane_bytes * 2).ValueOrDie(), nullptr,
+                 GL_STREAM_READ);
+  data_offsets_ = {0, y_plane_bytes.ValueOrDie(),
+                   (y_plane_bytes + chroma_plane_bytes).ValueOrDie()};
+  gl->BindBuffer(GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
+
+  // Generate the three queries used for determining when each of the plane
+  // readbacks has completed.
+  gl->GenQueriesEXT(3, queries.data());
+}
+
+void GLRendererCopier::ReadI420PlanesWorkflow::BindTransferBuffer() {
+  DCHECK_NE(transfer_buffer, 0u);
+  context_provider_->ContextGL()->BindBuffer(
+      GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, transfer_buffer);
+}
+
+void GLRendererCopier::ReadI420PlanesWorkflow::StartPlaneReadback(
+    int plane,
+    GLenum readback_format) {
+  DCHECK_NE(queries[plane], 0u);
+  auto* const gl = context_provider_->ContextGL();
+  gl->BeginQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM, queries[plane]);
+  const gfx::Size& size = plane == 0 ? y_texture_size() : chroma_texture_size();
+  // Note: While a PIXEL_PACK_BUFFER is bound, OpenGL interprets the last
+  // argument to ReadPixels() as a byte offset within the buffer instead of
+  // an actual pointer in system memory.
+  uint8_t* offset_in_buffer = reinterpret_cast<uint8_t*>(/* byte_offset = */ 0);
+  offset_in_buffer += data_offsets_[plane];
+  gl->ReadPixels(0, 0, size.width(), size.height(), readback_format,
+                 GL_UNSIGNED_BYTE, offset_in_buffer);
+  gl->EndQueryEXT(GL_ASYNC_PIXEL_PACK_COMPLETED_CHROMIUM);
+  context_provider_->ContextSupport()->SignalQuery(
+      queries[plane],
+      base::BindOnce(&GLRendererCopier::FinishReadI420PlanesWorkflow,
+                     copier_weak_ptr_, this, plane));
+}
+
+void GLRendererCopier::ReadI420PlanesWorkflow::UnbindTransferBuffer() {
+  context_provider_->ContextGL()->BindBuffer(
+      GL_PIXEL_PACK_TRANSFER_BUFFER_CHROMIUM, 0);
+}
+
+GLRendererCopier::ReadI420PlanesWorkflow::~ReadI420PlanesWorkflow() {
+  auto* const gl = context_provider_->ContextGL();
+  if (transfer_buffer != 0)
+    gl->DeleteBuffers(1, &transfer_buffer);
+  for (GLuint& query : queries) {
+    if (query != 0)
+      gl->DeleteQueriesEXT(1, &query);
+  }
+}
+
+gfx::Size GLRendererCopier::ReadI420PlanesWorkflow::y_texture_size() const {
+  return gfx::Size(aligned_rect.width() / kRGBABytesPerPixel,
+                   aligned_rect.height());
+}
+
+gfx::Size GLRendererCopier::ReadI420PlanesWorkflow::chroma_texture_size()
+    const {
+  return gfx::Size(aligned_rect.width() / kRGBABytesPerPixel / 2,
+                   aligned_rect.height() / 2);
+}
+
 void GLRendererCopier::StartI420ReadbackFromTextures(
     std::unique_ptr<CopyOutputRequest> request,
     const gfx::Rect& aligned_rect,
@@ -835,8 +799,10 @@
   // Execute three asynchronous read-pixels operations, one for each plane. The
   // CopyOutputRequest is passed to the ReadI420PlanesWorkflow, which will send
   // the CopyOutputResult once all readback operations are complete.
-  const auto workflow = base::MakeRefCounted<ReadI420PlanesWorkflow>(
-      std::move(request), aligned_rect, result_rect, context_provider_);
+  read_i420_workflows_.emplace_back(std::make_unique<ReadI420PlanesWorkflow>(
+      std::move(request), aligned_rect, result_rect, weak_factory_.GetWeakPtr(),
+      context_provider_));
+  ReadI420PlanesWorkflow* workflow = read_i420_workflows_.back().get();
   workflow->BindTransferBuffer();
   for (int plane = 0; plane < 3; ++plane) {
     gl->BindFramebuffer(GL_FRAMEBUFFER,
@@ -848,6 +814,29 @@
   workflow->UnbindTransferBuffer();
 }
 
+void GLRendererCopier::FinishReadI420PlanesWorkflow(
+    ReadI420PlanesWorkflow* workflow,
+    int plane) {
+  context_provider_->ContextGL()->DeleteQueriesEXT(1,
+                                                   &workflow->queries[plane]);
+  workflow->queries[plane] = 0;
+
+  // If all three readbacks have completed, send the result.
+  if (workflow->queries == std::array<GLuint, 3>{{0, 0, 0}}) {
+    workflow->copy_request->SendResult(
+        std::make_unique<GLPixelBufferI420Result>(
+            workflow->aligned_rect, workflow->result_rect,
+            weak_factory_.GetWeakPtr(), context_provider_,
+            workflow->transfer_buffer));
+    workflow->transfer_buffer = 0;  // Ownership was transferred to the result.
+    const auto it =
+        std::find_if(read_i420_workflows_.begin(), read_i420_workflows_.end(),
+                     [workflow](auto& ptr) { return ptr.get() == workflow; });
+    DCHECK(it != read_i420_workflows_.end());
+    read_i420_workflows_.erase(it);
+  }
+}
+
 std::unique_ptr<GLRendererCopier::ReusableThings>
 GLRendererCopier::TakeReusableThingsOrCreate(
     const base::UnguessableToken& requester) {
diff --git a/components/viz/service/display/gl_renderer_copier.h b/components/viz/service/display/gl_renderer_copier.h
index 57eba45..0f9a904 100644
--- a/components/viz/service/display/gl_renderer_copier.h
+++ b/components/viz/service/display/gl_renderer_copier.h
@@ -9,6 +9,7 @@
 
 #include <array>
 #include <memory>
+#include <vector>
 
 #include "base/callback.h"
 #include "base/containers/flat_map.h"
@@ -17,6 +18,8 @@
 #include "base/task_runner.h"
 #include "base/unguessable_token.h"
 #include "components/viz/service/viz_service_export.h"
+#include "ui/gfx/color_space.h"
+#include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 
 namespace gfx {
@@ -66,8 +69,8 @@
   using GLuint = unsigned int;
   using GLenum = unsigned int;
 
-  // |texture_deleter| must outlive this instance.
-  GLRendererCopier(scoped_refptr<ContextProvider> context_provider,
+  // |context_provider| and |texture_deleter| must outlive this instance.
+  GLRendererCopier(ContextProvider* context_provider,
                    TextureDeleter* texture_deleter);
 
   ~GLRendererCopier();
@@ -141,6 +144,44 @@
     DISALLOW_COPY_AND_ASSIGN(ReusableThings);
   };
 
+  // Manages the execution of one asynchronous framebuffer readback and contains
+  // all the relevant state needed to complete a copy request. The constructor
+  // initiates the operation, and the destructor cleans up all the GL objects
+  // created for this workflow. This class is owned by the GLRendererCopier, and
+  // GLRendererCopier is responsible for deleting this either after the workflow
+  // is finished, or when the GLRendererCopier is being destroyed.
+  struct ReadPixelsWorkflow {
+   public:
+    // Saves all revelant state and initiates the GL asynchronous read-pixels
+    // workflow.
+    ReadPixelsWorkflow(std::unique_ptr<CopyOutputRequest> copy_request,
+                       const gfx::Vector2d& readback_offset,
+                       bool flipped_source,
+                       bool swap_red_and_blue,
+                       const gfx::Rect& result_rect,
+                       const gfx::ColorSpace& color_space,
+                       ContextProvider* context_provider,
+                       GLenum readback_format);
+    ReadPixelsWorkflow(const ReadPixelsWorkflow&) = delete;
+
+    // The destructor is by the GLRendererCopier, either called after the
+    // workflow is finished or when GLRendererCopier is being destoryed.
+    ~ReadPixelsWorkflow();
+
+    GLuint query() const { return query_; }
+
+    const std::unique_ptr<CopyOutputRequest> copy_request;
+    const bool flipped_source;
+    const bool swap_red_and_blue;
+    const gfx::Rect result_rect;
+    const gfx::ColorSpace color_space;
+    GLuint transfer_buffer = 0;
+
+   private:
+    ContextProvider* const context_provider_;
+    GLuint query_ = 0;
+  };
+
   // Renders a scaled/transformed copy of a source texture according to the
   // |request| parameters and other source characteristics. |result_texture|
   // must be allocated/sized by the caller. For RGBA_BITMAP requests, the image
@@ -157,6 +198,43 @@
                            GLuint result_texture,
                            ReusableThings* things);
 
+  // Like the ReadPixelsWorkflow, except for I420 planes readback. Because there
+  // are three separate glReadPixels operations that may complete in any order,
+  // a ReadI420PlanesWorkflow will receive notifications from three separate "GL
+  // query" callbacks. It is only after all three operations have completed that
+  // a fully-assembled CopyOutputResult can be sent.
+  //
+  // See class comments for GLI420Converter for an explanation of how
+  // planar data is packed into RGBA textures.
+  struct ReadI420PlanesWorkflow {
+   public:
+    ReadI420PlanesWorkflow(std::unique_ptr<CopyOutputRequest> copy_request,
+                           const gfx::Rect& aligned_rect,
+                           const gfx::Rect& result_rect,
+                           base::WeakPtr<GLRendererCopier> copier_weak_ptr,
+                           ContextProvider* context_provider);
+
+    void BindTransferBuffer();
+    void StartPlaneReadback(int plane, GLenum readback_format);
+    void UnbindTransferBuffer();
+
+    ~ReadI420PlanesWorkflow();
+
+    const std::unique_ptr<CopyOutputRequest> copy_request;
+    const gfx::Rect aligned_rect;
+    const gfx::Rect result_rect;
+    GLuint transfer_buffer;
+    std::array<GLuint, 3> queries;
+
+   private:
+    gfx::Size y_texture_size() const;
+    gfx::Size chroma_texture_size() const;
+
+    base::WeakPtr<GLRendererCopier> copier_weak_ptr_;
+    ContextProvider* const context_provider_;
+    std::array<int, 3> data_offsets_;
+  };
+
   // Similar to RenderResultTexture(), except also transform the image into I420
   // format (a popular video format). Three textures, representing each of the
   // Y/U/V planes (as described in GLI420Converter), are populated and their GL
@@ -251,8 +329,11 @@
   // swap does not need to happen on the CPU (non-negligible cost).
   bool ShouldSwapRedAndBlueForBitmapReadback();
 
+  void FinishReadPixelsWorkflow(ReadPixelsWorkflow*);
+  void FinishReadI420PlanesWorkflow(ReadI420PlanesWorkflow*, int plane);
+
   // Injected dependencies.
-  const scoped_refptr<ContextProvider> context_provider_;
+  ContextProvider* const context_provider_;
   TextureDeleter* const texture_deleter_;
 
   // This increments by one for every call to FreeUnusedCachedResources(). It
@@ -279,6 +360,12 @@
   // things to be auto-purged after approx. 1-2 seconds of not being used.
   static constexpr int kKeepalivePeriod = 60;
 
+  std::vector<std::unique_ptr<ReadPixelsWorkflow>> read_pixels_workflows_;
+  std::vector<std::unique_ptr<ReadI420PlanesWorkflow>> read_i420_workflows_;
+
+  // Weak ptr to this class.
+  base::WeakPtrFactory<GLRendererCopier> weak_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(GLRendererCopier);
 };
 
diff --git a/components/viz/service/display/gl_renderer_copier_perftest.cc b/components/viz/service/display/gl_renderer_copier_perftest.cc
index 37069121..6e2edbb 100644
--- a/components/viz/service/display/gl_renderer_copier_perftest.cc
+++ b/components/viz/service/display/gl_renderer_copier_perftest.cc
@@ -61,15 +61,15 @@
 class GLRendererCopierPerfTest : public testing::Test {
  public:
   GLRendererCopierPerfTest() {
-    auto context_provider = base::MakeRefCounted<TestInProcessContextProvider>(
+    context_provider_ = base::MakeRefCounted<TestInProcessContextProvider>(
         /*enable_gpu_rasterization=*/false,
         /*enable_oop_rasterization=*/false, /*support_locking=*/false);
-    gpu::ContextResult result = context_provider->BindToCurrentThread();
+    gpu::ContextResult result = context_provider_->BindToCurrentThread();
     DCHECK_EQ(result, gpu::ContextResult::kSuccess);
-    gl_ = context_provider->ContextGL();
+    gl_ = context_provider_->ContextGL();
     texture_deleter_ =
         std::make_unique<TextureDeleter>(base::ThreadTaskRunnerHandle::Get());
-    copier_ = std::make_unique<GLRendererCopier>(std::move(context_provider),
+    copier_ = std::make_unique<GLRendererCopier>(context_provider_.get(),
                                                  texture_deleter_.get());
   }
 
@@ -259,6 +259,7 @@
 
  private:
   gpu::gles2::GLES2Interface* gl_ = nullptr;
+  scoped_refptr<TestInProcessContextProvider> context_provider_;
   std::unique_ptr<TextureDeleter> texture_deleter_;
   std::unique_ptr<GLRendererCopier> copier_;
   GLuint source_texture_ = 0;
diff --git a/components/viz/service/display/gl_renderer_copier_unittest.cc b/components/viz/service/display/gl_renderer_copier_unittest.cc
index fb3452b..13fdca6 100644
--- a/components/viz/service/display/gl_renderer_copier_unittest.cc
+++ b/components/viz/service/display/gl_renderer_copier_unittest.cc
@@ -62,11 +62,11 @@
   using ReusableThings = GLRendererCopier::ReusableThings;
 
   void SetUp() override {
-    auto context_provider = TestContextProvider::Create(
+    context_provider_ = TestContextProvider::Create(
         std::make_unique<CopierTestGLES2Interface>());
-    context_provider->BindToCurrentThread();
-    copier_ = std::make_unique<GLRendererCopier>(std::move(context_provider),
-                                                 nullptr);
+    context_provider_->BindToCurrentThread();
+    copier_ =
+        std::make_unique<GLRendererCopier>(context_provider_.get(), nullptr);
   }
 
   void TearDown() override { copier_.reset(); }
@@ -102,6 +102,7 @@
   static constexpr int kKeepalivePeriod = GLRendererCopier::kKeepalivePeriod;
 
  private:
+  scoped_refptr<ContextProvider> context_provider_;
   std::unique_ptr<GLRendererCopier> copier_;
 };
 
diff --git a/components/viz/service/display/gl_renderer_unittest.cc b/components/viz/service/display/gl_renderer_unittest.cc
index c2d68df..ad04f2a 100644
--- a/components/viz/service/display/gl_renderer_unittest.cc
+++ b/components/viz/service/display/gl_renderer_unittest.cc
@@ -230,6 +230,7 @@
           auto color_transform = gfx::ColorTransform::NewColorTransform(
               adjusted_color_space, dst_color_space,
               gfx::ColorTransform::Intent::INTENT_PERCEPTUAL);
+
           ASSERT_EQ(color_transform->GetShaderSource(),
                     renderer()
                         ->current_program_->color_transform_for_testing()
@@ -237,8 +238,15 @@
         }
 
         if (validate_output_color_matrix) {
-          ASSERT_NE(
-              -1, renderer()->current_program_->output_color_matrix_location());
+          if (program_key.type() == ProgramType::PROGRAM_TYPE_SOLID_COLOR) {
+            ASSERT_EQ(
+                -1,
+                renderer()->current_program_->output_color_matrix_location());
+          } else {
+            ASSERT_NE(
+                -1,
+                renderer()->current_program_->output_color_matrix_location());
+          }
         }
       }
     }
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
index 2772299..35b009a 100644
--- a/components/viz/service/display/output_surface.h
+++ b/components/viz/service/display/output_surface.h
@@ -117,13 +117,9 @@
     // enforced if zero.
     int max_render_target_size = 0;
 
-    // The SkColorType and GrBackendFormat for non-HDR and HDR.
-    // TODO(penghuang): remove SkColorType and GrBackendFormat when
-    // OutputSurface uses the |format| passed to Reshape().
-    SkColorType sk_color_type = kUnknown_SkColorType;
-    GrBackendFormat gr_backend_format;
-    SkColorType sk_color_type_for_hdr = kUnknown_SkColorType;
-    GrBackendFormat gr_backend_format_for_hdr;
+    // SkColorType for all supported buffer formats.
+    SkColorType sk_color_types[static_cast<int>(gfx::BufferFormat::LAST) + 1] =
+        {};
   };
 
   // Constructor for skia-based compositing.
diff --git a/components/viz/service/display/program_binding.h b/components/viz/service/display/program_binding.h
index bba6169e..04b05dd3 100644
--- a/components/viz/service/display/program_binding.h
+++ b/components/viz/service/display/program_binding.h
@@ -126,6 +126,8 @@
   }
   TexCoordPrecision tex_coord_precision() const { return precision_; }
 
+  ProgramType type() const { return type_; }
+
  private:
   friend struct ProgramKeyHash;
   friend class Program;
diff --git a/components/viz/service/display_embedder/output_presenter_fuchsia.cc b/components/viz/service/display_embedder/output_presenter_fuchsia.cc
index e1e15de..9d37ed16 100644
--- a/components/viz/service/display_embedder/output_presenter_fuchsia.cc
+++ b/components/viz/service/display_embedder/output_presenter_fuchsia.cc
@@ -188,10 +188,10 @@
   capabilities->supports_post_sub_buffer = false;
   capabilities->supports_commit_overlay_planes = false;
 
-  capabilities->sk_color_type = kRGBA_8888_SkColorType;
-  capabilities->gr_backend_format =
-      dependency_->GetSharedContextState()->gr_context()->defaultBackendFormat(
-          capabilities->sk_color_type, GrRenderable::kYes);
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      kRGBA_8888_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      kRGBA_8888_SkColorType;
 }
 
 bool OutputPresenterFuchsia::Reshape(const gfx::Size& size,
diff --git a/components/viz/service/display_embedder/output_presenter_gl.cc b/components/viz/service/display_embedder/output_presenter_gl.cc
index d0e262d..b74a3f2 100644
--- a/components/viz/service/display_embedder/output_presenter_gl.cc
+++ b/components/viz/service/display_embedder/output_presenter_gl.cc
@@ -214,23 +214,6 @@
   // GL is origin is at bottom left normally, all Surfaceless implementations
   // are flipped.
   DCHECK_EQ(gl_surface_->GetOrigin(), gfx::SurfaceOrigin::kTopLeft);
-
-  // TODO(https://crbug.com/958166): The initial |image_format_| should not be
-  // used, and the gfx::BufferFormat specified in Reshape should be used
-  // instead, because it may be updated to reflect changes in the content being
-  // displayed (e.g, HDR content appearing on-screen).
-#if defined(OS_APPLE)
-  image_format_ = BGRA_8888;
-#else
-#if defined(USE_OZONE)
-  if (features::IsUsingOzonePlatform()) {
-    image_format_ =
-        GetResourceFormat(display::DisplaySnapshot::PrimaryFormat());
-    return;
-  }
-#endif
-  image_format_ = RGBA_8888;
-#endif
 }
 
 OutputPresenterGL::~OutputPresenterGL() = default;
@@ -247,13 +230,28 @@
   // We expect origin of buffers is at top left.
   capabilities->output_surface_origin = gfx::SurfaceOrigin::kTopLeft;
 
-  // TODO(penghuang): Use defaultBackendFormat() in shared image implementation
-  // to make sure backend format is consistent.
-  capabilities->sk_color_type = ResourceFormatToClosestSkColorType(
-      true /* gpu_compositing */, image_format_);
-  capabilities->gr_backend_format =
-      dependency_->GetSharedContextState()->gr_context()->defaultBackendFormat(
-          capabilities->sk_color_type, GrRenderable::kYes);
+  // TODO(https://crbug.com/1108406): only add supported formats base on
+  // platform, driver, etc.
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::BGR_565)] =
+      kRGB_565_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_4444)] =
+      kARGB_4444_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBX_8888)] =
+      kRGB_888x_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      kRGBA_8888_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::BGRX_8888)] =
+      kBGRA_8888_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      kBGRA_8888_SkColorType;
+  capabilities
+      ->sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_1010102)] =
+      kBGRA_1010102_SkColorType;
+  capabilities
+      ->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_1010102)] =
+      kRGBA_1010102_SkColorType;
+  capabilities->sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_F16)] =
+      kRGBA_F16_SkColorType;
 }
 
 bool OutputPresenterGL::Reshape(const gfx::Size& size,
@@ -261,6 +259,7 @@
                                 const gfx::ColorSpace& color_space,
                                 gfx::BufferFormat format,
                                 gfx::OverlayTransform transform) {
+  image_format_ = GetResourceFormat(format);
   return gl_surface_->Resize(size, device_scale_factor, color_space,
                              gfx::AlphaBitsForBufferFormat(format));
 }
diff --git a/components/viz/service/display_embedder/output_presenter_gl.h b/components/viz/service/display_embedder/output_presenter_gl.h
index bfbf5199..66cc8ce9 100644
--- a/components/viz/service/display_embedder/output_presenter_gl.h
+++ b/components/viz/service/display_embedder/output_presenter_gl.h
@@ -65,7 +65,7 @@
   SkiaOutputSurfaceDependency* dependency_;
   const bool supports_async_swap_;
 
-  ResourceFormat image_format_;
+  ResourceFormat image_format_ = RGBA_8888;
 
   // Shared Image factories
   gpu::SharedImageFactory shared_image_factory_;
diff --git a/components/viz/service/display_embedder/skia_output_device.cc b/components/viz/service/display_embedder/skia_output_device.cc
index ecb8359..fa40e44 100644
--- a/components/viz/service/display_embedder/skia_output_device.cc
+++ b/components/viz/service/display_embedder/skia_output_device.cc
@@ -120,6 +120,12 @@
 void SkiaOutputDevice::EnsureBackbuffer() {}
 void SkiaOutputDevice::DiscardBackbuffer() {}
 
+void SkiaOutputDevice::SetDrawTimings(base::TimeTicks submitted,
+                                      base::TimeTicks started) {
+  viz_scheduled_draw_ = submitted;
+  gpu_started_draw_ = started;
+}
+
 void SkiaOutputDevice::StartSwapBuffers(BufferPresentedCallback feedback) {
   DCHECK_LT(static_cast<int>(pending_swaps_.size()),
             capabilities_.max_frames_pending);
@@ -158,12 +164,6 @@
   pending_swaps_.pop();
 }
 
-void SkiaOutputDevice::SetDrawTimings(base::TimeTicks submitted,
-                                      base::TimeTicks started) {
-  viz_scheduled_draw_ = submitted;
-  gpu_started_draw_ = started;
-}
-
 SkiaOutputDevice::SwapInfo::SwapInfo(
     uint64_t swap_id,
     SkiaOutputDevice::BufferPresentedCallback feedback,
diff --git a/components/viz/service/display_embedder/skia_output_device_dawn.cc b/components/viz/service/display_embedder/skia_output_device_dawn.cc
index 2d7317f..084d0f5 100644
--- a/components/viz/service/display_embedder/skia_output_device_dawn.cc
+++ b/components/viz/service/display_embedder/skia_output_device_dawn.cc
@@ -44,11 +44,10 @@
   capabilities_.uses_default_gl_framebuffer = false;
   capabilities_.supports_post_sub_buffer = false;
 
-  capabilities_.sk_color_type = kSurfaceColorType;
-  capabilities_.gr_backend_format =
-      context_provider_->GetGrContext()->defaultBackendFormat(
-          kSurfaceColorType, GrRenderable::kYes);
-
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      kSurfaceColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      kSurfaceColorType;
   vsync_provider_ = std::make_unique<gl::VSyncProviderWin>(widget);
   child_window_.Initialize();
 }
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.cc b/components/viz/service/display_embedder/skia_output_device_gl.cc
index 1de042f..436d206 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.cc
+++ b/components/viz/service/display_embedder/skia_output_device_gl.cc
@@ -105,15 +105,20 @@
     glGetIntegerv(GL_ALPHA_BITS, &alpha_bits);
   }
   CHECK_GL_ERROR();
-  supports_alpha_ = alpha_bits > 0;
 
-  capabilities_.sk_color_type =
-      supports_alpha_ ? kRGBA_8888_SkColorType : kRGB_888x_SkColorType;
-  capabilities_.gr_backend_format = gr_context->defaultBackendFormat(
-      capabilities_.sk_color_type, GrRenderable::kYes);
-  capabilities_.sk_color_type_for_hdr = kRGBA_F16_SkColorType;
-  capabilities_.gr_backend_format_for_hdr = gr_context->defaultBackendFormat(
-      capabilities_.sk_color_type_for_hdr, GrRenderable::kYes);
+  auto color_type =
+      (alpha_bits > 0) ? kRGBA_8888_SkColorType : kRGB_888x_SkColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      color_type;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBX_8888)] =
+      color_type;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      color_type;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRX_8888)] =
+      color_type;
+
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_F16)] =
+      kRGBA_F16_SkColorType;
 }
 
 SkiaOutputDeviceGL::~SkiaOutputDeviceGL() {
@@ -140,22 +145,21 @@
   framebuffer_info.fFBOID = 0;
   DCHECK_EQ(gl_surface_->GetBackingFramebufferObject(), 0u);
 
-  SkColorType color_type;
-  // TODO(https://crbug.com/1049334): The pixel format should be determined by
-  // |buffer_format|, not |color_space|, and not |supports_alpha_|.
-  if (color_space.IsHDR()) {
-    color_type = capabilities_.sk_color_type_for_hdr;
-    framebuffer_info.fFormat = GL_RGBA16F;
-    DCHECK_EQ(capabilities_.gr_backend_format_for_hdr.asGLFormat(),
-              GrGLFormat::kRGBA16F);
-  } else if (supports_alpha_) {
-    color_type = capabilities_.sk_color_type;
-    framebuffer_info.fFormat = GL_RGBA8;
-    DCHECK_EQ(capabilities_.gr_backend_format.asGLFormat(), GrGLFormat::kRGBA8);
-  } else {
-    color_type = capabilities_.sk_color_type;
-    framebuffer_info.fFormat = GL_RGB8;
-    DCHECK_EQ(capabilities_.gr_backend_format.asGLFormat(), GrGLFormat::kRGB8);
+  const auto format_index = static_cast<int>(buffer_format);
+  SkColorType color_type = capabilities_.sk_color_types[format_index];
+  switch (color_type) {
+    case kRGBA_8888_SkColorType:
+      framebuffer_info.fFormat = GL_RGBA8;
+      break;
+    case kRGB_888x_SkColorType:
+      framebuffer_info.fFormat = GL_RGB8;
+      break;
+    case kRGBA_F16_SkColorType:
+      framebuffer_info.fFormat = GL_RGBA16F;
+      break;
+    default:
+      NOTREACHED() << "color_type: " << color_type
+                   << " buffer_format: " << format_index;
   }
   // TODO(kylechar): We might need to support RGB10A2 for HDR10. HDR10 was only
   // used with Windows updated RS3 (2017) as a workaround for a DWM bug so it
diff --git a/components/viz/service/display_embedder/skia_output_device_gl.h b/components/viz/service/display_embedder/skia_output_device_gl.h
index c237b00..db933d9e 100644
--- a/components/viz/service/display_embedder/skia_output_device_gl.h
+++ b/components/viz/service/display_embedder/skia_output_device_gl.h
@@ -81,7 +81,6 @@
 
   sk_sp<SkSurface> sk_surface_;
 
-  bool supports_alpha_ = false;
   uint64_t backbuffer_estimated_size_ = 0;
 
   base::WeakPtrFactory<SkiaOutputDeviceGL> weak_ptr_factory_{this};
diff --git a/components/viz/service/display_embedder/skia_output_device_offscreen.cc b/components/viz/service/display_embedder/skia_output_device_offscreen.cc
index 37066fc..a6948f6 100644
--- a/components/viz/service/display_embedder/skia_output_device_offscreen.cc
+++ b/components/viz/service/display_embedder/skia_output_device_offscreen.cc
@@ -34,10 +34,16 @@
   capabilities_.output_surface_origin = origin;
   capabilities_.supports_post_sub_buffer = true;
 
-  capabilities_.sk_color_type = kSurfaceColorType;
-  capabilities_.gr_backend_format =
-      context_state_->gr_context()->defaultBackendFormat(kSurfaceColorType,
-                                                         GrRenderable::kYes);
+  // TODO(https://crbug.com/1108406): use the right color types base on GPU
+  // capabilities.
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      kSurfaceColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBX_8888)] =
+      kSurfaceColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      kSurfaceColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRX_8888)] =
+      kSurfaceColorType;
 }
 
 SkiaOutputDeviceOffscreen::~SkiaOutputDeviceOffscreen() {
@@ -53,6 +59,7 @@
 
   DiscardBackbuffer();
   size_ = size;
+  format_ = format;
   sk_color_space_ = color_space.ToSkColorSpace();
   EnsureBackbuffer();
   return true;
@@ -82,15 +89,20 @@
   if (size_.IsEmpty())
     return;
 
+  auto format_index = static_cast<int>(format_);
+  const auto& sk_color_type = capabilities_.sk_color_types[format_index];
+  DCHECK(sk_color_type != kUnknown_SkColorType)
+      << "SkColorType is invalid for format: " << format_index;
+
   if (has_alpha_) {
     backend_texture_ = context_state_->gr_context()->createBackendTexture(
-        size_.width(), size_.height(), kSurfaceColorType, GrMipMapped::kNo,
+        size_.width(), size_.height(), sk_color_type, GrMipMapped::kNo,
         GrRenderable::kYes);
   } else {
     is_emulated_rgbx_ = true;
     // Initialize alpha channel to opaque.
     backend_texture_ = context_state_->gr_context()->createBackendTexture(
-        size_.width(), size_.height(), kSurfaceColorType, SkColors::kBlack,
+        size_.width(), size_.height(), sk_color_type, SkColors::kBlack,
         GrMipMapped::kNo, GrRenderable::kYes);
   }
   DCHECK(backend_texture_.isValid());
diff --git a/components/viz/service/display_embedder/skia_output_device_offscreen.h b/components/viz/service/display_embedder/skia_output_device_offscreen.h
index 1acc7044..9457d65 100644
--- a/components/viz/service/display_embedder/skia_output_device_offscreen.h
+++ b/components/viz/service/display_embedder/skia_output_device_offscreen.h
@@ -51,6 +51,7 @@
 
  private:
   gfx::Size size_;
+  gfx::BufferFormat format_ = gfx::BufferFormat::RGBA_8888;
   uint64_t backbuffer_estimated_size_ = 0;
   sk_sp<SkColorSpace> sk_color_space_;
 
diff --git a/components/viz/service/display_embedder/skia_output_device_vulkan.cc b/components/viz/service/display_embedder/skia_output_device_vulkan.cc
index 5febb78..d68844e 100644
--- a/components/viz/service/display_embedder/skia_output_device_vulkan.cc
+++ b/components/viz/service/display_embedder/skia_output_device_vulkan.cc
@@ -318,10 +318,14 @@
   const auto surface_format = vulkan_surface_->surface_format().format;
   DCHECK(surface_format == VK_FORMAT_B8G8R8A8_UNORM ||
          surface_format == VK_FORMAT_R8G8B8A8_UNORM);
-  capabilities_.sk_color_type = surface_format == VK_FORMAT_R8G8B8A8_UNORM
-                                    ? kRGBA_8888_SkColorType
-                                    : kBGRA_8888_SkColorType;
-  capabilities_.gr_backend_format = GrBackendFormat::MakeVk(surface_format);
+
+  auto sk_color_type = surface_format == VK_FORMAT_R8G8B8A8_UNORM
+                           ? kRGBA_8888_SkColorType
+                           : kBGRA_8888_SkColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      sk_color_type;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      sk_color_type;
   return true;
 }
 
diff --git a/components/viz/service/display_embedder/skia_output_device_webview.cc b/components/viz/service/display_embedder/skia_output_device_webview.cc
index e2c50ed..74f8c33 100644
--- a/components/viz/service/display_embedder/skia_output_device_webview.cc
+++ b/components/viz/service/display_embedder/skia_output_device_webview.cc
@@ -16,6 +16,10 @@
 
 namespace viz {
 
+namespace {
+constexpr auto kSurfaceColorType = kRGBA_8888_SkColorType;
+}
+
 SkiaOutputDeviceWebView::SkiaOutputDeviceWebView(
     gpu::SharedContextState* context_state,
     scoped_refptr<gl::GLSurface> gl_surface,
@@ -36,10 +40,10 @@
   DCHECK(context_state_->gr_context());
   DCHECK(context_state_->context());
 
-  capabilities_.sk_color_type = kRGBA_8888_SkColorType;
-  capabilities_.gr_backend_format =
-      context_state_->gr_context()->defaultBackendFormat(
-          capabilities_.sk_color_type, GrRenderable::kYes);
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::RGBA_8888)] =
+      kSurfaceColorType;
+  capabilities_.sk_color_types[static_cast<int>(gfx::BufferFormat::BGRA_8888)] =
+      kSurfaceColorType;
 }
 
 SkiaOutputDeviceWebView::~SkiaOutputDeviceWebView() = default;
@@ -100,8 +104,7 @@
   GrGLFramebufferInfo framebuffer_info;
   framebuffer_info.fFBOID = fbo;
   framebuffer_info.fFormat = GL_RGBA8;
-  DCHECK_EQ(capabilities_.gr_backend_format.asGLFormat(), GrGLFormat::kRGBA8);
-  SkColorType color_type = capabilities_.sk_color_type;
+  SkColorType color_type = kSurfaceColorType;
 
   GrBackendRenderTarget render_target(size_.width(), size_.height(),
                                       /*sampleCnt=*/0,
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.cc b/components/viz/service/display_embedder/skia_output_surface_impl.cc
index bb4eb79..594dc14 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.cc
@@ -236,9 +236,10 @@
   color_space_ = color_space;
   is_hdr_ = color_space_.IsHDR();
   size_ = size;
+  format_ = format;
   characterization_ = CreateSkSurfaceCharacterization(
-      size, GetResourceFormat(format), false /* mipmap */,
-      color_space_.ToSkColorSpace(), true /* is_root_render_pass */);
+      size, format, false /* mipmap */, color_space_.ToSkColorSpace(),
+      true /* is_root_render_pass */);
   RecreateRootRecorder();
 }
 
@@ -289,8 +290,8 @@
   nway_canvas_->addCanvas(current_paint_->recorder()->getCanvas());
 
   SkSurfaceCharacterization characterization = CreateSkSurfaceCharacterization(
-      gfx::Size(characterization_.width(), characterization_.height()),
-      BGRA_8888, false /* mipmap */, characterization_.refColorSpace(),
+      gfx::Size(characterization_.width(), characterization_.height()), format_,
+      false /* mipmap */, characterization_.refColorSpace(),
       false /* is_root_render_pass */);
   if (characterization.isValid()) {
     overdraw_surface_recorder_.emplace(characterization);
@@ -493,7 +494,7 @@
   DCHECK(resource_sync_tokens_.empty());
 
   SkSurfaceCharacterization characterization = CreateSkSurfaceCharacterization(
-      surface_size, format, mipmap, std::move(color_space),
+      surface_size, BufferFormat(format), mipmap, std::move(color_space),
       false /* is_root_render_pass */);
   if (!characterization.isValid())
     return nullptr;
@@ -779,7 +780,7 @@
 SkSurfaceCharacterization
 SkiaOutputSurfaceImpl::CreateSkSurfaceCharacterization(
     const gfx::Size& surface_size,
-    ResourceFormat format,
+    gfx::BufferFormat format,
     bool mipmap,
     sk_sp<SkColorSpace> color_space,
     bool is_root_render_pass) {
@@ -791,15 +792,15 @@
   SkSurfaceProps surface_props(0 /*flags */,
                                SkSurfaceProps::kLegacyFontHost_InitType);
   if (is_root_render_pass) {
-    auto color_type =
-        is_hdr_ && capabilities_.sk_color_type_for_hdr != kUnknown_SkColorType
-            ? capabilities_.sk_color_type_for_hdr
-            : capabilities_.sk_color_type;
-
-    const auto& backend_format =
-        is_hdr_ && capabilities_.gr_backend_format_for_hdr.isValid()
-            ? capabilities_.gr_backend_format_for_hdr
-            : capabilities_.gr_backend_format;
+    const auto format_index = static_cast<int>(format);
+    const auto& color_type = capabilities_.sk_color_types[format_index];
+    const auto backend_format = gr_context_thread_safe_->defaultBackendFormat(
+        color_type, GrRenderable::kYes);
+    DCHECK(color_type != kUnknown_SkColorType)
+        << "SkColorType is invalid for buffer format_index: " << format_index;
+    DCHECK(backend_format.isValid())
+        << "GrBackendFormat is invalid for buffer format_index: "
+        << format_index;
     auto surface_origin =
         capabilities_.output_surface_origin == gfx::SurfaceOrigin::kBottomLeft
             ? kBottomLeft_GrSurfaceOrigin
@@ -838,8 +839,9 @@
     return characterization;
   }
 
-  auto color_type =
-      ResourceFormatToClosestSkColorType(true /* gpu_compositing */, format);
+  auto resource_format = GetResourceFormat(format);
+  auto color_type = ResourceFormatToClosestSkColorType(
+      true /* gpu_compositing */, resource_format);
   auto backend_format = gr_context_thread_safe_->defaultBackendFormat(
       color_type, GrRenderable::kYes);
   DCHECK(backend_format.isValid());
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl.h b/components/viz/service/display_embedder/skia_output_surface_impl.h
index f2abcd6..cff99942 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl.h
@@ -159,7 +159,7 @@
                              bool* result);
   SkSurfaceCharacterization CreateSkSurfaceCharacterization(
       const gfx::Size& surface_size,
-      ResourceFormat format,
+      gfx::BufferFormat format,
       bool mipmap,
       sk_sp<SkColorSpace> color_space,
       bool is_root_render_pass);
@@ -202,6 +202,7 @@
 
   gfx::Size size_;
   gfx::ColorSpace color_space_;
+  gfx::BufferFormat format_;
   bool is_hdr_ = false;
   SkSurfaceCharacterization characterization_;
   base::Optional<SkDeferredDisplayListRecorder> root_recorder_;
diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc
index 1635479f..40e29b8 100644
--- a/content/browser/browser_main_loop.cc
+++ b/content/browser/browser_main_loop.cc
@@ -365,7 +365,8 @@
 #if defined(OS_CHROMEOS)
   if (chromeos::switches::MemoryPressureHandlingEnabled())
     monitor = std::make_unique<util::MultiSourceMemoryPressureMonitor>();
-#elif defined(OS_MAC) || defined(OS_WIN) || defined(OS_FUCHSIA)
+#elif defined(OS_MAC) || defined(OS_WIN) || defined(OS_FUCHSIA) || \
+    (defined(OS_LINUX) && !defined(OS_CHROMEOS))
   monitor = std::make_unique<util::MultiSourceMemoryPressureMonitor>();
 #endif
   // No memory monitor on other platforms...
diff --git a/content/browser/frame_host/render_frame_host_delegate.h b/content/browser/frame_host/render_frame_host_delegate.h
index c934841..32682547 100644
--- a/content/browser/frame_host/render_frame_host_delegate.h
+++ b/content/browser/frame_host/render_frame_host_delegate.h
@@ -359,7 +359,8 @@
   // coordinate space.
   virtual void OnFocusedElementChangedInFrame(
       RenderFrameHostImpl* frame,
-      const gfx::Rect& bounds_in_root_view) {}
+      const gfx::Rect& bounds_in_root_view,
+      blink::mojom::FocusType focus_type) {}
 
   // The page is trying to open a new page (e.g. a popup window). The window
   // should be created associated the process of |opener|, but it should not
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 7836afd..3936083 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -4510,16 +4510,19 @@
 
 void RenderFrameHostImpl::FocusedElementChanged(
     bool is_editable_element,
-    const gfx::Rect& bounds_in_frame_widget) {
+    const gfx::Rect& bounds_in_frame_widget,
+    blink::mojom::FocusType focus_type) {
   if (!GetView())
     return;
 
   has_focused_editable_element_ = is_editable_element;
   // First convert the bounds to root view.
   delegate_->OnFocusedElementChangedInFrame(
-      this, gfx::Rect(GetView()->TransformPointToRootCoordSpace(
-                          bounds_in_frame_widget.origin()),
-                      bounds_in_frame_widget.size()));
+      this,
+      gfx::Rect(GetView()->TransformPointToRootCoordSpace(
+                    bounds_in_frame_widget.origin()),
+                bounds_in_frame_widget.size()),
+      focus_type);
 }
 
 void RenderFrameHostImpl::DidReceiveFirstUserActivation() {
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 88ef3e7..4b90c78 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -1619,7 +1619,8 @@
       std::vector<blink::mojom::FaviconURLPtr> favicon_urls) override;
   void DownloadURL(blink::mojom::DownloadURLParamsPtr params) override;
   void FocusedElementChanged(bool is_editable_element,
-                             const gfx::Rect& bounds_in_frame_widget) override;
+                             const gfx::Rect& bounds_in_frame_widget,
+                             blink::mojom::FocusType focus_type) override;
   void ShowPopupMenu(
       mojo::PendingRemote<blink::mojom::PopupMenuClient> popup_client,
       const gfx::Rect& bounds,
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index f9cb56fd..ecc7332 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -6957,7 +6957,8 @@
 
 void WebContentsImpl::OnFocusedElementChangedInFrame(
     RenderFrameHostImpl* frame,
-    const gfx::Rect& bounds_in_root_view) {
+    const gfx::Rect& bounds_in_root_view,
+    blink::mojom::FocusType focus_type) {
   RenderWidgetHostViewBase* root_view =
       static_cast<RenderWidgetHostViewBase*>(GetRenderWidgetHostView());
   if (!root_view || !frame->GetView())
@@ -6972,7 +6973,7 @@
                                 bounds_in_screen);
 
   FocusedNodeDetails details = {frame->has_focused_editable_element(),
-                                bounds_in_screen};
+                                bounds_in_screen, focus_type};
 
   // TODO(ekaramad): We should replace this with an observer notification
   // (https://crbug.com/675975).
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 85985f23..e2a8d8c8 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -644,7 +644,8 @@
   RenderFrameHost* GetFocusedFrameIncludingInnerWebContents() override;
   void OnFocusedElementChangedInFrame(
       RenderFrameHostImpl* frame,
-      const gfx::Rect& bounds_in_root_view) override;
+      const gfx::Rect& bounds_in_root_view,
+      blink::mojom::FocusType focus_type) override;
   void OnAdvanceFocus(RenderFrameHostImpl* source_rfh) override;
   RenderFrameHostDelegate* CreateNewWindow(
       RenderFrameHost* opener,
diff --git a/content/browser/web_contents/web_contents_observer_browsertest.cc b/content/browser/web_contents/web_contents_observer_browsertest.cc
index cf382d66..643c68e 100644
--- a/content/browser/web_contents/web_contents_observer_browsertest.cc
+++ b/content/browser/web_contents/web_contents_observer_browsertest.cc
@@ -8,6 +8,7 @@
 #include "content/browser/service_worker/embedded_worker_status.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/public/browser/allow_service_worker_result.h"
+#include "content/public/browser/focused_node_details.h"
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/content_client.h"
@@ -610,4 +611,41 @@
   cookie_tracker.cookie_accesses().clear();
 }
 
+namespace {
+
+class FocusedNodeObserver : public WebContentsObserver {
+ public:
+  explicit FocusedNodeObserver(WebContentsImpl* web_contents)
+      : WebContentsObserver(web_contents) {}
+
+  blink::mojom::FocusType last_focus_type() const { return last_focus_type_; }
+
+  void WaitForFocusChangedInPage() { run_loop_.Run(); }
+
+  // WebContentsObserver:
+  void OnFocusChangedInPage(FocusedNodeDetails* details) override {
+    last_focus_type_ = details->focus_type;
+    run_loop_.Quit();
+  }
+
+ private:
+  base::RunLoop run_loop_;
+  blink::mojom::FocusType last_focus_type_;
+};
+
+// Tests that the focus type is reported correctly in FocusedNodeDetails when
+// WebContentsObserver::OnFocusChangedInPage() is called.
+IN_PROC_BROWSER_TEST_F(WebContentsObserverBrowserTest,
+                       OnFocusChangedInPageFocusType) {
+  FocusedNodeObserver observer(web_contents());
+  GURL url(embedded_test_server()->GetURL("/form_that_posts_cross_site.html"));
+
+  EXPECT_TRUE(NavigateToURL(web_contents(), url));
+  SimulateMouseClickOrTapElementWithId(web_contents(), "text");
+  observer.WaitForFocusChangedInPage();
+  EXPECT_EQ(blink::mojom::FocusType::kMouse, observer.last_focus_type());
+}
+
+}  // namespace
+
 }  // namespace content
diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc
index f5e7b729..f47393b 100644
--- a/content/child/runtime_features.cc
+++ b/content/child/runtime_features.cc
@@ -217,6 +217,7 @@
     {wf::EnableWebXRHitTest, features::kWebXrHitTest, kUseFeatureState},
     {wf::EnableWebXRAnchors, features::kWebXrIncubations, kEnableOnly},
     {wf::EnableWebXRCameraAccess, features::kWebXrIncubations, kEnableOnly},
+    {wf::EnableWebXRDepth, features::kWebXrIncubations, kEnableOnly},
     {wf::EnableWebXRLightEstimation, features::kWebXrIncubations, kEnableOnly},
     {wf::EnableWebXRPlaneDetection, features::kWebXrIncubations, kEnableOnly},
     {wf::EnableWebXRReflectionEstimation, features::kWebXrIncubations,
@@ -604,6 +605,8 @@
   // these features.
   if (enable_experimental_web_platform_features) {
     WebRuntimeFeatures::EnableNetInfoDownlinkMax(true);
+    WebRuntimeFeatures::EnableSignedExchangePrefetchCacheForNavigations(true);
+    WebRuntimeFeatures::EnableSignedExchangeSubresourcePrefetch(true);
   }
 
   if (enable_blink_test_features) {
diff --git a/content/public/browser/focused_node_details.h b/content/public/browser/focused_node_details.h
index 6870bc4..0d5a5a2 100644
--- a/content/public/browser/focused_node_details.h
+++ b/content/public/browser/focused_node_details.h
@@ -5,6 +5,7 @@
 #ifndef CONTENT_PUBLIC_BROWSER_FOCUSED_NODE_DETAILS_H_
 #define CONTENT_PUBLIC_BROWSER_FOCUSED_NODE_DETAILS_H_
 
+#include "third_party/blink/public/mojom/input/focus_type.mojom.h"
 #include "ui/gfx/geometry/rect.h"
 
 namespace content {
@@ -12,6 +13,7 @@
 struct FocusedNodeDetails {
   bool is_editable_node;
   gfx::Rect node_bounds_in_screen;
+  blink::mojom::FocusType focus_type;
 };
 
 }  // namespace content
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
index 6fdee39..e7efa714 100644
--- a/content/public/test/browser_test_utils.cc
+++ b/content/public/test/browser_test_utils.cc
@@ -841,6 +841,36 @@
                                                             ui::LatencyInfo());
 }
 
+void SimulateMouseClickOrTapElementWithId(content::WebContents* web_contents,
+                                          const std::string& id) {
+  // Get the center coordinates of the DOM element.
+  const int x =
+      content::EvalJs(
+          web_contents,
+          content::JsReplace("const bounds = "
+                             "document.getElementById($1)."
+                             "getBoundingClientRect();"
+                             "Math.floor(bounds.left + bounds.width / 2)",
+                             id))
+          .ExtractInt();
+  const int y =
+      content::EvalJs(
+          web_contents,
+          content::JsReplace("const bounds = "
+                             "document.getElementById($1)."
+                             "getBoundingClientRect();"
+                             "Math.floor(bounds.top + bounds.height / 2)",
+                             id))
+          .ExtractInt();
+#if defined(OS_ANDROID)
+  content::SimulateTapDownAt(web_contents, gfx::Point(x, y));
+  content::SimulateTapAt(web_contents, gfx::Point(x, y));
+#else
+  content::SimulateMouseClickAt(
+      web_contents, 0, blink::WebMouseEvent::Button::kLeft, gfx::Point(x, y));
+#endif  // defined(OS_ANDROID)
+}
+
 void SendMouseDownToWidget(RenderWidgetHost* target,
                            int modifiers,
                            blink::WebMouseEvent::Button button) {
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
index 486af7f..eeaf18a 100644
--- a/content/public/test/browser_test_utils.h
+++ b/content/public/test/browser_test_utils.h
@@ -216,6 +216,11 @@
                           blink::WebMouseEvent::Button button,
                           const gfx::Point& point);
 
+// Retrieves the center coordinates of the element with id |id| and simulates a
+// mouse click there using SimulateMouseClickAt().
+void SimulateMouseClickOrTapElementWithId(content::WebContents* web_contents,
+                                          const std::string& id);
+
 // Simulates MouseDown at the center of the given RenderWidgetHost's area.
 // This does not send a corresponding MouseUp.
 void SendMouseDownToWidget(RenderWidgetHost* target,
diff --git a/content/test/data/form_that_posts_cross_site.html b/content/test/data/form_that_posts_cross_site.html
index 44c3bd18..36663ed 100644
--- a/content/test/data/form_that_posts_cross_site.html
+++ b/content/test/data/form_that_posts_cross_site.html
@@ -1,6 +1,7 @@
 <!DOCTYPE html>
 <html>
   <head></head>
+  <meta name="viewport" content="width=device-width">
   <body>
     <form id="text-form" method="POST" action="/cross-site-307/i.com/cross-site-307/x.com/echoall">
       <input type="text" id="text" name="text" value="value">
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 53e2fab..3bdd18e7 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
@@ -133,6 +133,7 @@
 crbug.com/angleproject/4417 [ win d3d11 ] conformance2/rendering/framebuffer-render-to-layer-angle-issue.html [ Failure ]
 
 # Intel flaky issues
+crbug.com/1122744 [ win d3d11 intel ] conformance/textures/misc/texparameter-test.html [ RetryOnFailure ]
 crbug.com/912579 [ opengl win passthrough intel ] conformance2/rendering/out-of-bounds-index-buffers-after-copying.html [ RetryOnFailure ]
 crbug.com/1114780 [ win intel ] deqp/functional/gles3/multisample.html [ RetryOnFailure ]
 # Flakily times out on driver 26.20.100.8141.
diff --git a/content/test/gpu/run_gpu_integration_test_fuchsia.py b/content/test/gpu/run_gpu_integration_test_fuchsia.py
index 972ef55..a7efe4d 100755
--- a/content/test/gpu/run_gpu_integration_test_fuchsia.py
+++ b/content/test/gpu/run_gpu_integration_test_fuchsia.py
@@ -27,10 +27,12 @@
   args, gpu_test_args = parser.parse_known_args()
   ConfigureLogging(args)
 
-  # If output directory is not set, assume the script is being launched
+  additional_target_args = {}
+
+  # If output_dir is not set, assume the script is being launched
   # from the output directory.
-  if not args.output_directory:
-    args.output_directory = os.getcwd()
+  if not args.output_dir:
+    args.output_dir = os.getcwd()
 
   # Create a temporary log file that Telemetry will look to use to build
   # an artifact when tests fail.
@@ -38,10 +40,10 @@
   if not args.system_log_file:
     args.system_log_file = os.path.join(tempfile.mkdtemp(), 'system-log')
     temp_log_file = True
+    additional_target_args['system_log_file'] = args.system_log_file
 
   package_names = ['web_engine', 'web_engine_shell']
-  web_engine_dir = os.path.join(args.output_directory, 'gen', 'fuchsia',
-                                'engine')
+  web_engine_dir = os.path.join(args.output_dir, 'gen', 'fuchsia', 'engine')
   gpu_script = [
       os.path.join(path_util.GetChromiumSrcDir(), 'content', 'test', 'gpu',
                    'run_gpu_integration_test.py')
@@ -50,10 +52,10 @@
   # Pass all other arguments to the gpu integration tests.
   gpu_script.extend(gpu_test_args)
   try:
-    with GetDeploymentTargetForArgs(args) as target:
+    with GetDeploymentTargetForArgs(additional_target_args) as target:
       target.Start()
       _, fuchsia_ssh_port = target._GetEndpoint()
-      gpu_script.extend(['--fuchsia-ssh-config-dir', args.output_directory])
+      gpu_script.extend(['--fuchsia-ssh-config-dir', args.output_dir])
       gpu_script.extend(['--fuchsia-ssh-port', str(fuchsia_ssh_port)])
       gpu_script.extend(['--fuchsia-system-log-file', args.system_log_file])
       if args.verbose:
diff --git a/extensions/common/api/printer_provider.idl b/extensions/common/api/printer_provider.idl
index 3de58fa0..aec9d79 100644
--- a/extensions/common/api/printer_provider.idl
+++ b/extensions/common/api/printer_provider.idl
@@ -37,7 +37,7 @@
   };
 
   // Printing request parameters. Passed to $(ref:onPrintRequested) event.
-  dictionary PrintJob {
+  [noinline_doc] dictionary PrintJob {
     // ID of the printer which should handle the job.
     DOMString printerId;
 
diff --git a/fuchsia/engine/test/web_engine_test_launcher.cc b/fuchsia/engine/test/web_engine_test_launcher.cc
index 6cc9cea..18a8543 100644
--- a/fuchsia/engine/test/web_engine_test_launcher.cc
+++ b/fuchsia/engine/test/web_engine_test_launcher.cc
@@ -50,7 +50,7 @@
 int main(int argc, char** argv) {
   base::CommandLine::Init(argc, argv);
   auto* command_line = base::CommandLine::ForCurrentProcess();
-  command_line->AppendSwitchASCII(switches::kOzonePlatform, "headless");
+  command_line->AppendSwitchASCII(switches::kOzonePlatform, "scenic");
   command_line->AppendSwitchASCII(switches::kEnableLogging, "stderr");
   command_line->AppendSwitch(switches::kDisableGpu);
 
diff --git a/google_apis/gaia/oauth_multilogin_result.cc b/google_apis/gaia/oauth_multilogin_result.cc
index 84b0511..ea734b5 100644
--- a/google_apis/gaia/oauth_multilogin_result.cc
+++ b/google_apis/gaia/oauth_multilogin_result.cc
@@ -4,6 +4,8 @@
 
 #include "google_apis/gaia/oauth_multilogin_result.h"
 
+#include <algorithm>
+
 #include "base/compiler_specific.h"
 #include "base/json/json_reader.h"
 #include "base/logging.h"
@@ -59,7 +61,7 @@
 base::StringPiece OAuthMultiloginResult::StripXSSICharacters(
     const std::string& raw_data) {
   base::StringPiece body(raw_data);
-  return body.substr(body.find('\n'));
+  return body.substr(std::min(body.find('\n'), body.size()));
 }
 
 void OAuthMultiloginResult::TryParseFailedAccountsFromValue(
diff --git a/gpu/command_buffer/service/shared_image_backing_gl_image.cc b/gpu/command_buffer/service/shared_image_backing_gl_image.cc
index c34decc5..6f8ff6e 100644
--- a/gpu/command_buffer/service/shared_image_backing_gl_image.cc
+++ b/gpu/command_buffer/service/shared_image_backing_gl_image.cc
@@ -17,6 +17,8 @@
 #include "ui/gl/gl_context.h"
 #include "ui/gl/gl_fence.h"
 #include "ui/gl/gl_gl_api_implementation.h"
+#include "ui/gl/gl_implementation.h"
+#include "ui/gl/scoped_binders.h"
 #include "ui/gl/trace_util.h"
 
 #if defined(OS_MAC)
@@ -569,6 +571,22 @@
     gl::GLApi* api = gl::g_current_gl_context;
     api->glFlushFn();
   }
+
+  // When SwANGLE is used as the GL implementation, we have to call
+  // ReleaseTexImage to signal an UnlockIOSurface call to sync the surface
+  // between the CPU and GPU. The next time this texture is accessed we will
+  // call BindTexImage to signal a LockIOSurface call before rendering to it via
+  // the CPU.
+  if (IsPassthrough() &&
+      gl::GetANGLEImplementation() == gl::ANGLEImplementation::kSwiftShader &&
+      image_->ShouldBindOrCopy() == gl::GLImage::BIND) {
+    const GLenum target = GetGLTarget();
+    gl::ScopedTextureBinder binder(target, passthrough_texture_->service_id());
+    if (!passthrough_texture_->is_bind_pending()) {
+      image_->ReleaseTexImage(target);
+      image_bind_or_copy_needed_ = true;
+    }
+  }
 #endif
 }
 
diff --git a/infra/config/generated/commit-queue.cfg b/infra/config/generated/commit-queue.cfg
index d40b431..178ad42 100644
--- a/infra/config/generated/commit-queue.cfg
+++ b/infra/config/generated/commit-queue.cfg
@@ -1257,10 +1257,7 @@
       }
       builders {
         name: "chromium/try/mac-coverage-rel"
-        experiment_percentage: 5
-        location_regexp: ".*"
-        location_regexp_exclude: ".+/[+]/docs/.+"
-        location_regexp_exclude: ".+/[+]/infra/config/.+"
+        includable_only: true
       }
       builders {
         name: "chromium/try/mac-dawn-rel"
diff --git a/infra/config/generated/cq-builders.md b/infra/config/generated/cq-builders.md
index ad30554..172aedf 100644
--- a/infra/config/generated/cq-builders.md
+++ b/infra/config/generated/cq-builders.md
@@ -366,6 +366,3 @@
   * [`//content/browser/tracing/.+`](https://cs.chromium.org/chromium/src/content/browser/tracing/)
   * [`//services/tracing/.+`](https://cs.chromium.org/chromium/src/services/tracing/)
 
-* [mac-coverage-rel](https://ci.chromium.org/p/chromium/builders/try/mac-coverage-rel) ([definition](https://cs.chromium.org/search?q=package:%5Echromium$+file:/cq.star$+-file:/beta/+-file:/stable/+mac-coverage-rel)) ([matching builders](https://cs.chromium.org/search?q=+file:trybots.py+mac-coverage-rel))
-  * Experiment percentage: 5
-
diff --git a/infra/config/generated/cr-buildbucket.cfg b/infra/config/generated/cr-buildbucket.cfg
index ee19eda5..9492ace 100644
--- a/infra/config/generated/cr-buildbucket.cfg
+++ b/infra/config/generated/cr-buildbucket.cfg
@@ -5388,7 +5388,7 @@
       swarming_tags: "vpython:native-python-wrapper"
       dimensions: "builder:Mac deterministic (dbg)"
       dimensions: "cpu:x86-64"
-      dimensions: "os:Mac-10.13"
+      dimensions: "os:Mac-10.15"
       dimensions: "pool:luci.chromium.ci"
       exe {
         cipd_package: "infra/recipe_bundles/chromium.googlesource.com/chromium/tools/build"
diff --git a/infra/config/subprojects/chromium/ci.star b/infra/config/subprojects/chromium/ci.star
index 561ec521..a18563e3 100644
--- a/infra/config/subprojects/chromium/ci.star
+++ b/infra/config/subprojects/chromium/ci.star
@@ -2899,6 +2899,7 @@
     cores = None,
     executable = "recipe:swarming/deterministic_build",
     execution_timeout = 6 * time.hour,
+    os = os.MAC_10_15,
 )
 
 ci.fyi_mac_builder(
diff --git a/infra/config/subprojects/chromium/try.star b/infra/config/subprojects/chromium/try.star
index 2ceb669f..297d169 100644
--- a/infra/config/subprojects/chromium/try.star
+++ b/infra/config/subprojects/chromium/try.star
@@ -1098,7 +1098,6 @@
     name = "mac-coverage-rel",
     use_clang_coverage = True,
     goma_jobs = goma.jobs.J150,
-    tryjob = try_.job(experiment_percentage = 5),
 )
 
 try_.chromium_mac_builder(
diff --git a/ios/chrome/browser/BUILD.gn b/ios/chrome/browser/BUILD.gn
index 5fda2cc..eb7a9672 100644
--- a/ios/chrome/browser/BUILD.gn
+++ b/ios/chrome/browser/BUILD.gn
@@ -101,7 +101,6 @@
     "//ios/chrome/browser/complex_tasks",
     "//ios/chrome/browser/download",
     "//ios/chrome/browser/itunes_urls",
-    "//ios/chrome/browser/ssl:feature_flags",
     "//ios/chrome/browser/sync/glue",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/coordinators:chrome_coordinators",
diff --git a/ios/chrome/browser/ssl/BUILD.gn b/ios/chrome/browser/ssl/BUILD.gn
index 2c95a685..81f0a1aa 100644
--- a/ios/chrome/browser/ssl/BUILD.gn
+++ b/ios/chrome/browser/ssl/BUILD.gn
@@ -9,8 +9,6 @@
     "captive_portal_detector_tab_helper.mm",
     "captive_portal_detector_tab_helper_delegate.h",
     "captive_portal_metrics.h",
-    "captive_portal_metrics_tab_helper.h",
-    "captive_portal_metrics_tab_helper.mm",
     "ios_captive_portal_blocking_page.h",
     "ios_captive_portal_blocking_page.mm",
     "ios_ssl_blocking_page.h",
@@ -19,7 +17,6 @@
     "ios_ssl_error_handler.mm",
   ]
   deps = [
-    ":feature_flags",
     "//base",
     "//base:i18n",
     "//components/autofill/ios/form_util",
@@ -46,15 +43,6 @@
   ]
 }
 
-source_set("feature_flags") {
-  configs += [ "//build/config/compiler:enable_arc" ]
-  sources = [
-    "captive_portal_features.cc",
-    "captive_portal_features.h",
-  ]
-  deps = [ "//base" ]
-}
-
 source_set("unit_tests") {
   configs += [ "//build/config/compiler:enable_arc" ]
   testonly = true
@@ -63,7 +51,6 @@
     "ios_ssl_error_handler_unittest.mm",
   ]
   deps = [
-    ":feature_flags",
     ":ssl",
     "//base/test:test_support",
     "//components/security_interstitials/core",
diff --git a/ios/chrome/browser/ssl/README.md b/ios/chrome/browser/ssl/README.md
new file mode 100644
index 0000000..d745dd9
--- /dev/null
+++ b/ios/chrome/browser/ssl/README.md
@@ -0,0 +1,6 @@
+## Captive Portal Metrics Tab Helper
+
+A tab helper was in place previously to collect metrics regarding current
+captive portal issues. It was removed in [crrev.com/c/2372901][cl].
+
+[cl]: https://chromium-review.googlesource.com/2372901
diff --git a/ios/chrome/browser/ssl/captive_portal_features.cc b/ios/chrome/browser/ssl/captive_portal_features.cc
deleted file mode 100644
index e9209ef6..0000000
--- a/ios/chrome/browser/ssl/captive_portal_features.cc
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "ios/chrome/browser/ssl/captive_portal_features.h"
-
-const base::Feature kCaptivePortalMetrics{"CaptivePortalMetrics",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/ios/chrome/browser/ssl/captive_portal_features.h b/ios/chrome/browser/ssl/captive_portal_features.h
deleted file mode 100644
index ca06f80a..0000000
--- a/ios/chrome/browser/ssl/captive_portal_features.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_SSL_CAPTIVE_PORTAL_FEATURES_H_
-#define IOS_CHROME_BROWSER_SSL_CAPTIVE_PORTAL_FEATURES_H_
-
-#include "base/feature_list.h"
-
-// Used to control the state of logging Captive Portal Metrics.
-extern const base::Feature kCaptivePortalMetrics;
-
-#endif  // IOS_CHROME_BROWSER_SSL_CAPTIVE_PORTAL_FEATURES_H_
diff --git a/ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.h b/ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.h
deleted file mode 100644
index 625dd94c..0000000
--- a/ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.h
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_SSL_CAPTIVE_PORTAL_METRICS_TAB_HELPER_H_
-#define IOS_CHROME_BROWSER_SSL_CAPTIVE_PORTAL_METRICS_TAB_HELPER_H_
-
-#include "base/macros.h"
-#include "base/timer/timer.h"
-#include "components/captive_portal/core/captive_portal_detector.h"
-#include "ios/chrome/browser/ssl/captive_portal_metrics.h"
-#include "ios/web/public/web_state_observer.h"
-#import "ios/web/public/web_state_user_data.h"
-
-namespace web {
-class WebState;
-}
-
-// Logs the result of a captive portal detector when users enter error states
-// which could be caused by being on a Captive Portal network. These metrics
-// will be used to identify how many users incorrectly see these error cases
-// because they are on captive portal networks without a network connection. In
-// the future, these users should be shown the captive portal login interstitial
-// if these metrics show that enough users are affected.
-// 1. When a -1200 error occurs.
-// 2. If a request is taking a long time to complete.
-class CaptivePortalMetricsTabHelper
-    : public web::WebStateObserver,
-      public web::WebStateUserData<CaptivePortalMetricsTabHelper> {
- public:
-  ~CaptivePortalMetricsTabHelper() override;
-
- private:
-  explicit CaptivePortalMetricsTabHelper(web::WebState* web_state);
-  friend class web::WebStateUserData<CaptivePortalMetricsTabHelper>;
-
-  // Tests if network access is currently blocked by a captive portal.
-  void TestForCaptivePortal(
-      captive_portal::CaptivePortalDetector::DetectionCallback callback);
-
-  // Tests for a captive portal and logs the resulting metric.
-  void TimerTriggered();
-
-  // Returns the associated CaptivePortalStatus value for logging to UMA metrics
-  // based on detection |results|.
-  static CaptivePortalStatus CaptivePortalStatusFromDetectionResult(
-      const captive_portal::CaptivePortalDetector::Results& results);
-
-  // Logs the |results| of the captive portal detector to the UMA metric for a
-  // failed secure connection.
-  static void HandleSecureConnectionFailedCaptivePortalDetectionResult(
-      const captive_portal::CaptivePortalDetector::Results& results);
-
-  // Logs the |results| of the captive portal detector to the UMA metric for a
-  // potential request timeout.
-  static void HandleTimeoutCaptivePortalDetectionResult(
-      const captive_portal::CaptivePortalDetector::Results& results);
-
-  // web::WebStateObserver implementation.
-  void DidStartNavigation(web::WebState* web_state,
-                          web::NavigationContext* navigation_context) override;
-  void DidFinishNavigation(web::WebState* web_state,
-                           web::NavigationContext* navigation_context) override;
-  void WebStateDestroyed(web::WebState* web_state) override;
-
-  // A timer to test for a captive portal blocking an ongoing request.
-  base::OneShotTimer timer_;
-  // The web state associated with this tab helper.
-  web::WebState* web_state_;
-
-  WEB_STATE_USER_DATA_KEY_DECL();
-
-  DISALLOW_COPY_AND_ASSIGN(CaptivePortalMetricsTabHelper);
-};
-
-#endif  // IOS_CHROME_BROWSER_SSL_CAPTIVE_PORTAL_METRICS_TAB_HELPER_H_
diff --git a/ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.mm b/ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.mm
deleted file mode 100644
index 917c053..0000000
--- a/ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.mm
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.h"
-
-#include "base/bind.h"
-#include "base/metrics/histogram_macros.h"
-#include "components/captive_portal/core/captive_portal_detector.h"
-#import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
-#import "ios/web/public/navigation/navigation_context.h"
-#import "ios/web/public/web_state.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-// Result of captive portal network check after a request takes longer than
-// |kCaptivePortalTestDelayInMilliseconds|.
-const char kCaptivePortalCausingTimeoutHistogram[] =
-    "CaptivePortal.Session.TimeoutDetectionResult";
-
-// Result of captive portal network check after a request fails with
-// NSURLErrorSecureConnectionFailed.
-const char kCaptivePortalSecureConnectionFailedHistogram[] =
-    "CaptivePortal.Session.SecureConnectionFailed";
-
-// Time in milliseconds of still ongoing request before testing if the user is
-// behind a captive portal.
-const int64_t kCaptivePortalTestDelayInMilliseconds = 8000;
-
-CaptivePortalMetricsTabHelper::CaptivePortalMetricsTabHelper(
-    web::WebState* web_state)
-    : web_state_(web_state) {
-  web_state_->AddObserver(this);
-}
-
-CaptivePortalMetricsTabHelper::~CaptivePortalMetricsTabHelper() = default;
-
-void CaptivePortalMetricsTabHelper::TimerTriggered() {
-  TestForCaptivePortal(
-      base::BindOnce(&HandleTimeoutCaptivePortalDetectionResult));
-}
-
-void CaptivePortalMetricsTabHelper::TestForCaptivePortal(
-    captive_portal::CaptivePortalDetector::DetectionCallback callback) {
-  CaptivePortalDetectorTabHelper* tab_helper =
-      CaptivePortalDetectorTabHelper::FromWebState(web_state_);
-  // TODO(crbug.com/760873): replace test with DCHECK when this method is only
-  // called on WebStates attached to tabs.
-  if (tab_helper) {
-    tab_helper->detector()->DetectCaptivePortal(
-        GURL(captive_portal::CaptivePortalDetector::kDefaultURL),
-        std::move(callback), NO_TRAFFIC_ANNOTATION_YET);
-  }
-}
-
-// static
-CaptivePortalStatus
-CaptivePortalMetricsTabHelper::CaptivePortalStatusFromDetectionResult(
-    const captive_portal::CaptivePortalDetector::Results& results) {
-  CaptivePortalStatus status;
-  switch (results.result) {
-    case captive_portal::RESULT_INTERNET_CONNECTED:
-      status = CaptivePortalStatus::ONLINE;
-      break;
-    case captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL:
-      status = CaptivePortalStatus::PORTAL;
-      break;
-    default:
-      status = CaptivePortalStatus::UNKNOWN;
-      break;
-  }
-  return status;
-}
-
-// static
-void CaptivePortalMetricsTabHelper::
-    HandleSecureConnectionFailedCaptivePortalDetectionResult(
-        const captive_portal::CaptivePortalDetector::Results& results) {
-  CaptivePortalStatus status =
-      CaptivePortalMetricsTabHelper::CaptivePortalStatusFromDetectionResult(
-          results);
-  UMA_HISTOGRAM_ENUMERATION(kCaptivePortalSecureConnectionFailedHistogram,
-                            static_cast<int>(status),
-                            static_cast<int>(CaptivePortalStatus::COUNT));
-}
-
-// static
-void CaptivePortalMetricsTabHelper::HandleTimeoutCaptivePortalDetectionResult(
-    const captive_portal::CaptivePortalDetector::Results& results) {
-  CaptivePortalStatus status =
-      CaptivePortalMetricsTabHelper::CaptivePortalStatusFromDetectionResult(
-          results);
-  UMA_HISTOGRAM_ENUMERATION(kCaptivePortalCausingTimeoutHistogram,
-                            static_cast<int>(status),
-                            static_cast<int>(CaptivePortalStatus::COUNT));
-}
-
-// WebStateObserver
-void CaptivePortalMetricsTabHelper::DidStartNavigation(
-    web::WebState* web_state,
-    web::NavigationContext* navigation_context) {
-  timer_.Stop();
-
-  timer_.Start(
-      FROM_HERE,
-      base::TimeDelta::FromMilliseconds(kCaptivePortalTestDelayInMilliseconds),
-      this, &CaptivePortalMetricsTabHelper::TimerTriggered);
-}
-
-void CaptivePortalMetricsTabHelper::DidFinishNavigation(
-    web::WebState* web_state,
-    web::NavigationContext* navigation_context) {
-  timer_.Stop();
-
-  NSError* error = navigation_context->GetError();
-  if ([error.domain isEqualToString:NSURLErrorDomain] &&
-      error.code == NSURLErrorSecureConnectionFailed) {
-    TestForCaptivePortal(
-        base::Bind(&HandleSecureConnectionFailedCaptivePortalDetectionResult));
-  } else if ([error.domain isEqualToString:NSURLErrorDomain] &&
-             error.code == NSURLErrorTimedOut) {
-    TestForCaptivePortal(
-        base::Bind(&HandleTimeoutCaptivePortalDetectionResult));
-  }
-}
-
-void CaptivePortalMetricsTabHelper::WebStateDestroyed(
-    web::WebState* web_state) {
-  DCHECK_EQ(web_state_, web_state);
-
-  timer_.Stop();
-
-  // Ensure the captive portal detection is canceled if it never completed.
-  CaptivePortalDetectorTabHelper* tab_helper =
-      CaptivePortalDetectorTabHelper::FromWebState(web_state_);
-  // TODO(crbug.com/760873): replace test with DCHECK when this method is only
-  // called on WebStates attached to tabs.
-  if (tab_helper) {
-    tab_helper->detector()->Cancel();
-  }
-
-  web_state_->RemoveObserver(this);
-  web_state_ = nullptr;
-}
-
-WEB_STATE_USER_DATA_KEY_IMPL(CaptivePortalMetricsTabHelper)
diff --git a/ios/chrome/browser/ssl/ios_ssl_error_handler.mm b/ios/chrome/browser/ssl/ios_ssl_error_handler.mm
index 095683d..99df7a81 100644
--- a/ios/chrome/browser/ssl/ios_ssl_error_handler.mm
+++ b/ios/chrome/browser/ssl/ios_ssl_error_handler.mm
@@ -16,7 +16,6 @@
 #include "components/security_interstitials/core/ssl_error_ui.h"
 #include "ios/chrome/browser/application_context.h"
 #include "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
-#include "ios/chrome/browser/ssl/captive_portal_features.h"
 #include "ios/chrome/browser/ssl/captive_portal_metrics.h"
 #include "ios/chrome/browser/ssl/ios_captive_portal_blocking_page.h"
 #include "ios/chrome/browser/ssl/ios_ssl_blocking_page.h"
diff --git a/ios/chrome/browser/tabs/BUILD.gn b/ios/chrome/browser/tabs/BUILD.gn
index c235acf2..f12e080 100644
--- a/ios/chrome/browser/tabs/BUILD.gn
+++ b/ios/chrome/browser/tabs/BUILD.gn
@@ -96,7 +96,6 @@
     "//ios/chrome/browser/sessions:session_service",
     "//ios/chrome/browser/snapshots",
     "//ios/chrome/browser/ssl",
-    "//ios/chrome/browser/ssl:feature_flags",
     "//ios/chrome/browser/store_kit",
     "//ios/chrome/browser/sync",
     "//ios/chrome/browser/translate",
diff --git a/ios/chrome/browser/tabs/tab_helper_util.mm b/ios/chrome/browser/tabs/tab_helper_util.mm
index adec82a..12dce93d 100644
--- a/ios/chrome/browser/tabs/tab_helper_util.mm
+++ b/ios/chrome/browser/tabs/tab_helper_util.mm
@@ -57,8 +57,6 @@
 #import "ios/chrome/browser/search_engines/search_engine_tab_helper.h"
 #import "ios/chrome/browser/sessions/ios_chrome_session_tab_helper.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
-#include "ios/chrome/browser/ssl/captive_portal_features.h"
-#import "ios/chrome/browser/ssl/captive_portal_metrics_tab_helper.h"
 #import "ios/chrome/browser/store_kit/store_kit_tab_helper.h"
 #import "ios/chrome/browser/sync/ios_chrome_synced_tab_delegate.h"
 #import "ios/chrome/browser/translate/chrome_ios_translate_client.h"
@@ -131,10 +129,6 @@
     TranslateOverlayTabHelper::CreateForWebState(web_state);
   }
 
-  if (base::FeatureList::IsEnabled(kCaptivePortalMetrics)) {
-    CaptivePortalMetricsTabHelper::CreateForWebState(web_state);
-  }
-
   if (base::FeatureList::IsEnabled(web::kWebPageTextAccessibility)) {
     FontSizeTabHelper::CreateForWebState(web_state);
   }
diff --git a/ios/chrome/test/app/signin_test_util.mm b/ios/chrome/test/app/signin_test_util.mm
index 3d3569a..d85392a 100644
--- a/ios/chrome/test/app/signin_test_util.mm
+++ b/ios/chrome/test/app/signin_test_util.mm
@@ -125,6 +125,7 @@
   prefs->SetBoolean(prefs::kIosBookmarkPromoAlreadySeen, false);
   prefs->SetInteger(prefs::kIosSettingsSigninPromoDisplayedCount, 0);
   prefs->SetBoolean(prefs::kIosSettingsPromoAlreadySeen, false);
+  prefs->SetBoolean(prefs::kSigninShouldPromptForSigninAgain, false);
 }
 
 }  // namespace chrome_test_util
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index d6edd7a..eef3f5e8 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -453,3 +453,9 @@
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_start_peer_migration_earlier,
           false)
+
+// If true, neuter initial packet in the coalescer when discarding initial keys.
+QUIC_FLAG(
+    bool,
+    FLAGS_quic_reloadable_flag_quic_neuter_initial_packet_in_coalescer_with_initial_key_discarded,
+    true)
diff --git a/sandbox/linux/syscall_broker/broker_client.cc b/sandbox/linux/syscall_broker/broker_client.cc
index fb897eb..71114e7 100644
--- a/sandbox/linux/syscall_broker/broker_client.cc
+++ b/sandbox/linux/syscall_broker/broker_client.cc
@@ -112,11 +112,14 @@
     return -ENOMEM;
   if (return_length < 0)
     return -ENOMEM;
+  // Sanity check that our broker is behaving correctly.
+  RAW_CHECK(return_length == static_cast<size_t>(return_value));
 
-  if (static_cast<size_t>(return_length) > bufsize)
-    return -ENAMETOOLONG;
+  if (return_length > bufsize) {
+    return_length = bufsize;
+  }
   memcpy(buf, return_data, return_length);
-  return return_value;
+  return return_length;
 }
 
 int BrokerClient::Rename(const char* oldpath, const char* newpath) const {
diff --git a/sandbox/linux/syscall_broker/broker_process_unittest.cc b/sandbox/linux/syscall_broker/broker_process_unittest.cc
index e4a8b04..6cd2fde8 100644
--- a/sandbox/linux/syscall_broker/broker_process_unittest.cc
+++ b/sandbox/linux/syscall_broker/broker_process_unittest.cc
@@ -1094,7 +1094,7 @@
     BrokerProcess open_broker(kFakeErrnoSentinel, command_set, permissions,
                               fast_check_in_client);
     ASSERT_TRUE(open_broker.Init(base::BindOnce(&NoOpCallback)));
-    EXPECT_EQ(-ENAMETOOLONG, open_broker.Readlink(newpath_name, buf, 4));
+    EXPECT_EQ(4, open_broker.Readlink(newpath_name, buf, 4));
   }
 
   // Cleanup both paths.
diff --git a/testing/buildbot/chromium.chromiumos.json b/testing/buildbot/chromium.chromiumos.json
index 3609335..8408a2c 100644
--- a/testing/buildbot/chromium.chromiumos.json
+++ b/testing/buildbot/chromium.chromiumos.json
@@ -4548,7 +4548,7 @@
       },
       {
         "args": [
-          "--gtest_filter=-OutOfProcessPPAPITest.TrueTypeFont"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/lacros.content_browsertests.filter"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/chromium.ci.json b/testing/buildbot/chromium.ci.json
index 8d14896..30183c00 100644
--- a/testing/buildbot/chromium.ci.json
+++ b/testing/buildbot/chromium.ci.json
@@ -243684,7 +243684,7 @@
       },
       {
         "args": [
-          "--gtest_filter=-OutOfProcessPPAPITest.TrueTypeFont"
+          "--test-launcher-filter-file=../../testing/buildbot/filters/lacros.content_browsertests.filter"
         ],
         "merge": {
           "args": [],
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index 578f0fc..2e700ea5 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -89,6 +89,7 @@
     "//testing/buildbot/filters/android.emulator_p.content_browsertests.filter",
     "//testing/buildbot/filters/cast-linux.content_browsertests.filter",
     "//testing/buildbot/filters/gpu.skiarenderer_vulkan_content_browsertests.filter",
+    "//testing/buildbot/filters/lacros.content_browsertests.filter",
     "//testing/buildbot/filters/site_isolation_android.content_browsertests.filter",
     "//testing/buildbot/filters/skia_renderer.content_browsertests.filter",
     "//testing/buildbot/filters/vulkan.content_browsertests.filter",
diff --git a/testing/buildbot/filters/lacros.content_browsertests.filter b/testing/buildbot/filters/lacros.content_browsertests.filter
new file mode 100644
index 0000000..bf84e3e4
--- /dev/null
+++ b/testing/buildbot/filters/lacros.content_browsertests.filter
@@ -0,0 +1,6 @@
+

+# TODO(crbug.com/1111979) Enable all tests on lacros.

+-OutOfProcessPPAPITest.TrueTypeFont

+

+# Following tests are flaky.

+-All/WebContentsVideoCaptureDeviceBrowserTestP.CapturesContentChanges*

diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 80179d18..f30ab3a 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -46,8 +46,11 @@
 #  : the test is a python script; the path to the script is specified in
 #    the "script" field.
 #  "generated_script"
-#  : the test is a script generated at build time; the path to the script
-#    *relative to the output directory* is specified in the "script" field.
+#  : the test is a script generated at build time; the script *should* be
+#    in output_dir/bin/run_$target (or output_dir\bin\run_$target.bat on
+#    Windows), but an alternative path *may* be specified,
+#    in the "script" field, as *relative to the output directory*.
+#    TODO(crbug.com/816629): remove support for "script".
 #  "unknown"
 #  : (the default), which indicates that we don't know what the command line
 #    needs to be (this is a fatal error).
@@ -58,9 +61,10 @@
 # On Windows, ".exe" will be automatically appended if need be, so
 # the executable name (and target name) should not contain an ".exe".
 #
-# The optional "args" field can be used to append extra command line
-# args onto the command line determined by the "type". If not specified,
-# it defaults to an empty list (no extra args).
+# The optional "args" field may be specified for "windowed_test_launcher",
+# "console_test_launcher", and "script"-type tests, and can be used to
+# append extra command line args onto the command line determined by the
+# "type". If not specified, it defaults to an empty list (no extra args).
 #
 # The optional "label_type" field can be used in conjunction with
 # "type" == "console_test_launcher" or "type" == "windowed_test_launcher"
@@ -487,7 +491,6 @@
   "chrome_sizes": {
     "label": "//chrome/test:chrome_sizes",
     "type": "generated_script",
-    "script": "bin/run_chrome_sizes",
   },
   "chromedriver": {
     "label": "//chrome/test/chromedriver:chromedriver",
@@ -647,7 +650,6 @@
   "cronet_sizes": {
     "label": "//components/cronet/android:cronet_sizes",
     "type": "generated_script",
-    "script": "bin/run_cronet_sizes",
   },
   "cronet_smoketests_missing_native_library_instrumentation_apk": {
     "label": "//components/cronet/android:cronet_smoketests_missing_native_library_instrumentation_apk",
@@ -660,7 +662,6 @@
   "cronet_test": {
     "label": "//components/cronet/ios/test:cronet_test",
     "type": "generated_script",
-    "script": "bin/run_cronet_test"
   },
   "cronet_test_instrumentation_apk": {
     "label": "//components/cronet/android:cronet_test_instrumentation_apk",
@@ -896,21 +897,15 @@
     "type": "windowed_test_launcher",
   },
   "ios_chrome_bookmarks_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_bookmarks_egtests",
-    "script": "bin/run_ios_chrome_bookmarks_egtests",
     "type": "generated_script",
   },
   "ios_chrome_integration_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_integration_egtests",
-    "script": "bin/run_ios_chrome_integration_egtests",
     "type": "generated_script",
   },
   "ios_chrome_reading_list_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_reading_list_egtests",
-    "script": "bin/run_ios_chrome_reading_list_egtests",
     "type": "generated_script",
   },
   "ios_chrome_settings_egtests": {
@@ -918,162 +913,110 @@
       "--enable-features=ClearSyncedData",
     ],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_settings_egtests",
-    "script": "bin/run_ios_chrome_settings_egtests",
     "type": "generated_script",
   },
   "ios_chrome_signin_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_signin_egtests",
-    "script": "bin/run_ios_chrome_signin_egtests",
     "type": "generated_script",
   },
   "ios_chrome_translate_egtests": {
     "label": "//ios/chrome/test/earl_grey:ios_chrome_translate_egtests",
-    "script": "bin/run_ios_chrome_translate_egtests",
     "type": "generated_script",
   },
   "ios_chrome_smoke_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_smoke_egtests",
-    "script": "bin/run_ios_chrome_smoke_egtests",
     "type": "generated_script",
   },
   "ios_chrome_ui_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_ui_egtests",
-    "script": "bin/run_ios_chrome_ui_egtests",
     "type": "generated_script",
   },
   "ios_chrome_unittests": {
-    "args": [],
     "label": "//ios/chrome/test:ios_chrome_unittests",
-    "script": "bin/run_ios_chrome_unittests",
     "type": "generated_script",
   },
   "ios_chrome_web_egtests": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey:ios_chrome_web_egtests",
-    "script": "bin/run_ios_chrome_web_egtests",
     "type": "generated_script",
   },
   "ios_chrome_bookmarks_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_bookmarks_eg2tests_module",
-    "script": "bin/run_ios_chrome_bookmarks_eg2tests_module",
     "type": "generated_script",
   },
   "ios_chrome_integration_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_integration_eg2tests_module",
-    "script": "bin/run_ios_chrome_integration_eg2tests_module",
     "type": "generated_script",
   },
   "ios_chrome_settings_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_settings_eg2tests_module",
-    "script": "bin/run_ios_chrome_settings_eg2tests_module",
     "type": "generated_script",
   },
   "ios_chrome_signin_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_signin_eg2tests_module",
-    "script": "bin/run_ios_chrome_signin_eg2tests_module",
     "type": "generated_script",
   },
   "ios_chrome_smoke_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_smoke_eg2tests_module",
-    "script": "bin/run_ios_chrome_smoke_eg2tests_module",
     "type": "generated_script",
   },
   "ios_chrome_ui_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_ui_eg2tests_module",
-    "script": "bin/run_ios_chrome_ui_eg2tests_module",
     "type": "generated_script",
   },
   "ios_chrome_web_eg2tests_module": {
-    "args": [],
     "label": "//ios/chrome/test/earl_grey2:ios_chrome_web_eg2tests_module",
-    "script": "bin/run_ios_chrome_web_eg2tests_module",
     "type": "generated_script",
   },
   "ios_crash_xcuitests_module": {
-    "args": [],
     "label": "//third_party/crashpad/crashpad/test/ios:ios_crash_xcuitests_module",
-    "script": "bin/run_ios_crash_xcuitests_module",
     "type": "generated_script",
   },
   "ios_components_unittests": {
-    "args": [],
     "label": "//ios/components:ios_components_unittests",
-    "script": "bin/run_ios_components_unittests",
     "type": "generated_script",
   },
   "ios_net_unittests": {
-    "args": [],
     "label": "//ios/net:ios_net_unittests",
-    "script": "bin/run_ios_net_unittests",
     "type": "generated_script",
   },
   "ios_remoting_unittests": {
-    "args": [],
     "label": "//remoting/ios:ios_remoting_unittests",
-    "script": "bin/run_ios_remoting_unittests",
     "type": "generated_script",
   },
   "ios_showcase_egtests": {
-    "args": [],
     "label": "//ios/showcase:ios_showcase_egtests",
-    "script": "bin/run_ios_showcase_egtests",
     "type": "generated_script",
   },
   "ios_showcase_eg2tests_module": {
-    "args":[],
     "label": "//ios/showcase:ios_showcase_eg2tests_module",
-    "script": "bin/run_ios_showcase_eg2tests_module",
     "type": "generated_script",
   },
   "ios_testing_unittests": {
-    "args": [],
     "label": "//ios/testing:ios_testing_unittests",
-    "script": "bin/run_ios_testing_unittests",
     "type": "generated_script",
   },
   "ios_web_inttests": {
-    "args": [],
     "label": "//ios/web:ios_web_inttests",
-    "script": "bin/run_ios_web_inttests",
     "type": "generated_script",
   },
   "ios_web_shell_eg2tests_module": {
-    "args": [],
     "label": "//ios/web/shell/test:ios_web_shell_eg2tests_module",
-    "script": "bin/run_ios_web_shell_eg2tests_module",
     "type": "generated_script",
   },
   "ios_web_shell_egtests": {
-    "args": [],
     "label": "//ios/web/shell/test:ios_web_shell_egtests",
-    "script": "bin/run_ios_web_shell_egtests",
     "type": "generated_script",
   },
   "ios_web_unittests": {
-    "args": [],
     "label": "//ios/web:ios_web_unittests",
-    "script": "bin/run_ios_web_unittests",
     "type": "generated_script",
   },
   "ios_web_view_inttests": {
-    "args": [],
     "label": "//ios/web_view:ios_web_view_inttests",
-    "script": "bin/run_ios_web_view_inttests",
     "type": "generated_script",
   },
   "ios_web_view_unittests": {
-    "args": [],
     "label": "//ios/web_view:ios_web_view_unittests",
-    "script": "bin/run_ios_web_view_unittests",
     "type": "generated_script",
   },
   "ipc_tests": {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index b6b2c16a..0ae31cee 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -993,7 +993,7 @@
       # https://crbug.com/1111979,
       'linux-lacros-tester-rel': {
         'args': [
-          '--gtest_filter=-OutOfProcessPPAPITest.TrueTypeFont',
+          '--test-launcher-filter-file=../../testing/buildbot/filters/lacros.content_browsertests.filter',
         ],
       },
     },
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index 73763f5..23a586c 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -527,15 +527,6 @@
     ":androidx_fragment_fragment_java",
     ":androidx_recyclerview_recyclerview_java",
   ]
-  deps += [ "//third_party/android_deps/local_modifications/androidx_preference_preference:androidx_preference_preference_prebuilt_java" ]
-
-  # Omit these files since we use our own copy from AndroidX master, included above.
-  # We can remove this once we migrate to AndroidX master for all libraries.
-  jar_excluded_patterns = [
-    "androidx/preference/PreferenceDialogFragmentCompat*",
-    "androidx/preference/PreferenceFragmentCompat*",
-  ]
-
   ignore_proguard_configs = true
 }
 
diff --git a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index 18e62be..286f875e 100644
--- a/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/third_party/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -433,17 +433,6 @@
                 sb.append('  jar_excluded_patterns = [ "*xmlpull*" ]\n')
                 break
             case 'androidx_preference_preference':
-                sb.append("""\
-                |  deps += [ "//third_party/android_deps/local_modifications/androidx_preference_preference:androidx_preference_preference_prebuilt_java" ]
-                |  # Omit these files since we use our own copy from AndroidX master, included above.
-                |  # We can remove this once we migrate to AndroidX master for all libraries.
-                |  jar_excluded_patterns = [
-                |    "androidx/preference/PreferenceDialogFragmentCompat*",
-                |    "androidx/preference/PreferenceFragmentCompat*",
-                |  ]
-                |
-                |""".stripMargin())
-                // fallthrough
             case 'com_android_support_preference_v7':
                 // Replace broad library -keep rules with a more limited set in
                 // chrome/android/java/proguard.flags instead.
diff --git a/third_party/android_deps/local_modifications/androidx_preference_preference/BUILD.gn b/third_party/android_deps/local_modifications/androidx_preference_preference/BUILD.gn
deleted file mode 100644
index 15afb59..0000000
--- a/third_party/android_deps/local_modifications/androidx_preference_preference/BUILD.gn
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright 2020 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-import("//build/config/android/rules.gni")
-
-# This rule contains the jar file produced by building
-# :androidx_preference_preference_partial_java. After building the
-# aforementioned target, the jar file can be found in:
-# out/<dir>/lib.java/third_party/android_deps/local_modifications/androidx_preference_preference/androidx_preference_preference_partial_java.jar
-android_java_prebuilt("androidx_preference_preference_prebuilt_java") {
-  jar_path = "androidx_preference_preference_java.jar"
-
-  # Normally this would depend on //third_party/android_deps:androidx_preference_preference_java,
-  # but the dependency is reversed to keep the .class file customization
-  # transparent to dependents. Disable bytecode checks, which would otherwise
-  # complain about this.
-  enable_bytecode_checks = false
-}
-
-android_library("androidx_preference_preference_partial_java") {
-  sources = [
-    "java/androidx/preference/PreferenceDialogFragmentCompat.java",
-    "java/androidx/preference/PreferenceFragmentCompat.java",
-  ]
-  deps = [
-    "//third_party/android_deps:androidx_annotation_annotation_java",
-    "//third_party/android_deps:androidx_appcompat_appcompat_java",
-    "//third_party/android_deps:androidx_fragment_fragment_java",
-    "//third_party/android_deps:androidx_preference_preference_java",
-    "//third_party/android_deps:androidx_recyclerview_recyclerview_java",
-  ]
-}
diff --git a/third_party/android_deps/local_modifications/androidx_preference_preference/README b/third_party/android_deps/local_modifications/androidx_preference_preference/README
deleted file mode 100644
index f4fb51c..0000000
--- a/third_party/android_deps/local_modifications/androidx_preference_preference/README
+++ /dev/null
@@ -1,10 +0,0 @@
-This directory contains PreferenceFragmentCompat.java and
-PreferenceDialogFragmentCompat.java, copied without changes from the AndroidX
-preference library at commit beeb6fb. These files contain two changes (commits
-72c0381 and beeb6fb) that we want in Chromium, but are not yet in an official
-preference library release.
-
-To pull in these changes, we exclude PreferenceFragmentCompat and
-PreferenceDialogFragmentCompat from the androidx_preference_preference library
-in CIPD (which is from an official release), and add a dependency from that
-library to a prebuilt jar file containing the two classes we want to update.
diff --git a/third_party/android_deps/local_modifications/androidx_preference_preference/androidx_preference_preference_java.jar b/third_party/android_deps/local_modifications/androidx_preference_preference/androidx_preference_preference_java.jar
deleted file mode 100644
index 201cd50..0000000
--- a/third_party/android_deps/local_modifications/androidx_preference_preference/androidx_preference_preference_java.jar
+++ /dev/null
Binary files differ
diff --git a/third_party/android_deps/local_modifications/androidx_preference_preference/java/androidx/preference/PreferenceDialogFragmentCompat.java b/third_party/android_deps/local_modifications/androidx_preference_preference/java/androidx/preference/PreferenceDialogFragmentCompat.java
deleted file mode 100644
index fbf4b72..0000000
--- a/third_party/android_deps/local_modifications/androidx_preference_preference/java/androidx/preference/PreferenceDialogFragmentCompat.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.preference;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.Window;
-import android.view.WindowManager;
-import android.widget.TextView;
-
-import androidx.annotation.LayoutRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-
-/**
- * Abstract base class which presents a dialog associated with a {@link DialogPreference}. Since
- * the preference object may not be available during fragment re-creation, the necessary
- * information for displaying the dialog is read once during the initial call to
- * {@link #onCreate(Bundle)} and saved/restored in the saved instance state. Custom subclasses
- * should also follow this pattern.
- */
-public abstract class PreferenceDialogFragmentCompat extends DialogFragment implements
-        DialogInterface.OnClickListener {
-
-    protected static final String ARG_KEY = "key";
-
-    private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
-    private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
-    private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
-    private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
-    private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
-    private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
-
-    private DialogPreference mPreference;
-
-    private CharSequence mDialogTitle;
-    private CharSequence mPositiveButtonText;
-    private CharSequence mNegativeButtonText;
-    private CharSequence mDialogMessage;
-    private @LayoutRes int mDialogLayoutRes;
-
-    private BitmapDrawable mDialogIcon;
-
-    /** Which button was clicked. */
-    private int mWhichButtonClicked;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final Fragment rawFragment = getTargetFragment();
-        if (!(rawFragment instanceof DialogPreference.TargetFragment)) {
-            throw new IllegalStateException("Target fragment must implement TargetFragment" +
-                    " interface");
-        }
-
-        final DialogPreference.TargetFragment fragment =
-                (DialogPreference.TargetFragment) rawFragment;
-
-        final String key = getArguments().getString(ARG_KEY);
-        if (savedInstanceState == null) {
-            mPreference = fragment.findPreference(key);
-            mDialogTitle = mPreference.getDialogTitle();
-            mPositiveButtonText = mPreference.getPositiveButtonText();
-            mNegativeButtonText = mPreference.getNegativeButtonText();
-            mDialogMessage = mPreference.getDialogMessage();
-            mDialogLayoutRes = mPreference.getDialogLayoutResource();
-
-            final Drawable icon = mPreference.getDialogIcon();
-            if (icon == null || icon instanceof BitmapDrawable) {
-                mDialogIcon = (BitmapDrawable) icon;
-            } else {
-                final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
-                        icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
-                final Canvas canvas = new Canvas(bitmap);
-                icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
-                icon.draw(canvas);
-                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
-            }
-        } else {
-            mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
-            mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
-            mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
-            mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
-            mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
-            final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
-            if (bitmap != null) {
-                mDialogIcon = new BitmapDrawable(getResources(), bitmap);
-            }
-        }
-    }
-
-    @Override
-    public void onSaveInstanceState(@NonNull Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
-        outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
-        outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
-        outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
-        outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
-        if (mDialogIcon != null) {
-            outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
-        }
-    }
-
-    @Override
-    public @NonNull
-    Dialog onCreateDialog(Bundle savedInstanceState) {
-        mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
-
-        final AlertDialog.Builder builder = new AlertDialog.Builder(getContext())
-                .setTitle(mDialogTitle)
-                .setIcon(mDialogIcon)
-                .setPositiveButton(mPositiveButtonText, this)
-                .setNegativeButton(mNegativeButtonText, this);
-
-        View contentView = onCreateDialogView(getContext());
-        if (contentView != null) {
-            onBindDialogView(contentView);
-            builder.setView(contentView);
-        } else {
-            builder.setMessage(mDialogMessage);
-        }
-
-        onPrepareDialogBuilder(builder);
-
-        // Create the dialog
-        final Dialog dialog = builder.create();
-        if (needInputMethod()) {
-            requestInputMethod(dialog);
-        }
-
-        return dialog;
-    }
-
-    /**
-     * Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
-     * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
-     *
-     * @return The {@link DialogPreference} associated with this dialog
-     */
-    public DialogPreference getPreference() {
-        if (mPreference == null) {
-            final String key = getArguments().getString(ARG_KEY);
-            final DialogPreference.TargetFragment fragment =
-                    (DialogPreference.TargetFragment) getTargetFragment();
-            mPreference = fragment.findPreference(key);
-        }
-        return mPreference;
-    }
-
-    /**
-     * Prepares the dialog builder to be shown when the preference is clicked.
-     * Use this to set custom properties on the dialog.
-     *
-     * <p>Do not {@link AlertDialog.Builder#create()} or {@link AlertDialog.Builder#show()}.
-     */
-    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {}
-
-    /**
-     * Returns whether the preference needs to display a soft input method when the dialog is
-     * displayed. Default is false. Subclasses should override this method if they need the soft
-     * input method brought up automatically.
-     *
-     * <p>Note: If your application targets P or above, ensure your subclass manually requests
-     * focus (ideally in {@link #onBindDialogView(View)}) for the input field in order to
-     * correctly attach the input method to the field.
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY)
-    protected boolean needInputMethod() {
-        return false;
-    }
-
-    /**
-     * Sets the required flags on the dialog window to enable input method window to show up.
-     */
-    private void requestInputMethod(Dialog dialog) {
-        Window window = dialog.getWindow();
-        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-    }
-
-    /**
-     * Creates the content view for the dialog (if a custom content view is required).
-     * By default, it inflates the dialog layout resource if it is set.
-     *
-     * @return The content view for the dialog
-     * @see DialogPreference#setLayoutResource(int)
-     */
-    protected View onCreateDialogView(Context context) {
-        final int resId = mDialogLayoutRes;
-        if (resId == 0) {
-            return null;
-        }
-
-        return getLayoutInflater().inflate(resId, null);
-    }
-
-    /**
-     * Binds views in the content view of the dialog to data.
-     *
-     * <p>Make sure to call through to the superclass implementation.
-     *
-     * @param view The content view of the dialog, if it is custom
-     */
-    protected void onBindDialogView(View view) {
-        View dialogMessageView = view.findViewById(android.R.id.message);
-
-        if (dialogMessageView != null) {
-            final CharSequence message = mDialogMessage;
-            int newVisibility = View.GONE;
-
-            if (!TextUtils.isEmpty(message)) {
-                if (dialogMessageView instanceof TextView) {
-                    ((TextView) dialogMessageView).setText(message);
-                }
-
-                newVisibility = View.VISIBLE;
-            }
-
-            if (dialogMessageView.getVisibility() != newVisibility) {
-                dialogMessageView.setVisibility(newVisibility);
-            }
-        }
-    }
-
-    @Override
-    public void onClick(DialogInterface dialog, int which) {
-        mWhichButtonClicked = which;
-    }
-
-    @Override
-    public void onDismiss(@NonNull DialogInterface dialog) {
-        super.onDismiss(dialog);
-        onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON_POSITIVE);
-    }
-
-    public abstract void onDialogClosed(boolean positiveResult);
-}
diff --git a/third_party/android_deps/local_modifications/androidx_preference_preference/java/androidx/preference/PreferenceFragmentCompat.java b/third_party/android_deps/local_modifications/androidx_preference_preference/java/androidx/preference/PreferenceFragmentCompat.java
deleted file mode 100644
index 1fa26698..0000000
--- a/third_party/android_deps/local_modifications/androidx_preference_preference/java/androidx/preference/PreferenceFragmentCompat.java
+++ /dev/null
@@ -1,872 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.preference;
-
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
-import androidx.annotation.XmlRes;
-import androidx.fragment.app.DialogFragment;
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-
-/**
- * A PreferenceFragmentCompat is the entry point to using the Preference library. This
- * {@link Fragment} displays a hierarchy of {@link Preference} objects to the user. It also
- * handles persisting values to the device. To retrieve an instance of
- * {@link android.content.SharedPreferences} that the preference hierarchy in this fragment will
- * use by default, call
- * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)} with a context
- * in the same package as this fragment.
- *
- * <p>You can define a preference hierarchy as an XML resource, or you can build a hierarchy in
- * code. In both cases you need to use a {@link PreferenceScreen} as the root component in your
- * hierarchy.
- *
- * <p>To inflate from XML, use the {@link #setPreferencesFromResource(int, String)}. An example
- * example XML resource is shown further down.
- *
- * <p>To build a hierarchy from code, use
- * {@link PreferenceManager#createPreferenceScreen(Context)} to create the root
- * {@link PreferenceScreen}. Once you have added other {@link Preference}s to this root scree
- * with {@link PreferenceScreen#addPreference(Preference)}, you then need to set the screen as
- * the root screen in your hierarchy with {@link #setPreferenceScreen(PreferenceScreen)}.
- *
- * <p>As a convenience, this fragment implements a click listener for any preference in the
- * current hierarchy, see {@link #onPreferenceTreeClick(Preference)}.
- *
- * <div class="special reference"> <h3>Developer Guides</h3> <p>For more information about
- * building a settings screen using the AndroidX Preference library, see
- * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.</p> </div>
- *
- * <a name="SampleCode"></a>
- * <h3>Sample Code</h3>
- *
- * <p>The following sample code shows a simple settings screen using an XML resource. The XML
- * resource is as follows:</p>
- *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml preferences}
- *
- * <p>The fragment that loads the XML resource is as follows:</p>
- *
- * {@sample frameworks/support/samples/SupportPreferenceDemos/src/main/java/com/example/androidx/preference/Preferences.java preferences}
- *
- * @see Preference
- * @see PreferenceScreen
- */
-public abstract class PreferenceFragmentCompat extends Fragment implements
-        PreferenceManager.OnPreferenceTreeClickListener,
-        PreferenceManager.OnDisplayPreferenceDialogListener,
-        PreferenceManager.OnNavigateToScreenListener,
-        DialogPreference.TargetFragment {
-
-    private static final String TAG = "PreferenceFragment";
-
-    /**
-     * Fragment argument used to specify the tag of the desired root {@link PreferenceScreen}
-     * object.
-     */
-    public static final String ARG_PREFERENCE_ROOT =
-            "androidx.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
-
-    private static final String PREFERENCES_TAG = "android:preferences";
-
-    private static final String DIALOG_FRAGMENT_TAG =
-            "androidx.preference.PreferenceFragment.DIALOG";
-
-    private static final int MSG_BIND_PREFERENCES = 1;
-
-    private final DividerDecoration mDividerDecoration = new DividerDecoration();
-    private PreferenceManager mPreferenceManager;
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    RecyclerView mList;
-    private boolean mHavePrefs;
-    private boolean mInitDone;
-    private int mLayoutResId = R.layout.preference_list_fragment;
-    private Runnable mSelectPreferenceRunnable;
-
-    @SuppressWarnings("deprecation")
-    private Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_BIND_PREFERENCES:
-                    bindPreferences();
-                    break;
-            }
-        }
-    };
-
-    final private Runnable mRequestFocus = new Runnable() {
-        @Override
-        public void run() {
-            mList.focusableViewAvailable(mList);
-        }
-    };
-
-    @Override
-    public void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        final TypedValue tv = new TypedValue();
-        getContext().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
-        int theme = tv.resourceId;
-        if (theme == 0) {
-            // Fallback to default theme.
-            theme = R.style.PreferenceThemeOverlay;
-        }
-        getContext().getTheme().applyStyle(theme, false);
-
-        mPreferenceManager = new PreferenceManager(getContext());
-        mPreferenceManager.setOnNavigateToScreenListener(this);
-        final Bundle args = getArguments();
-        final String rootKey;
-        if (args != null) {
-            rootKey = getArguments().getString(ARG_PREFERENCE_ROOT);
-        } else {
-            rootKey = null;
-        }
-        onCreatePreferences(savedInstanceState, rootKey);
-    }
-
-    /**
-     * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment.
-     * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either
-     * directly or via helper methods such as {@link #addPreferencesFromResource(int)}.
-     *
-     * @param savedInstanceState If the fragment is being re-created from a previous saved state,
-     *                           this is the state.
-     * @param rootKey            If non-null, this preference fragment should be rooted at the
-     *                           {@link PreferenceScreen} with this key.
-     */
-    public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey);
-
-    @Override
-    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
-            @Nullable Bundle savedInstanceState) {
-
-        TypedArray a = getContext().obtainStyledAttributes(null,
-                R.styleable.PreferenceFragmentCompat,
-                R.attr.preferenceFragmentCompatStyle,
-                0);
-
-        mLayoutResId = a.getResourceId(R.styleable.PreferenceFragmentCompat_android_layout,
-                mLayoutResId);
-
-        final Drawable divider = a.getDrawable(
-                R.styleable.PreferenceFragmentCompat_android_divider);
-        final int dividerHeight = a.getDimensionPixelSize(
-                R.styleable.PreferenceFragmentCompat_android_dividerHeight, -1);
-        final boolean allowDividerAfterLastItem = a.getBoolean(
-                R.styleable.PreferenceFragmentCompat_allowDividerAfterLastItem, true);
-
-        a.recycle();
-
-        final LayoutInflater themedInflater = inflater.cloneInContext(getContext());
-
-        final View view = themedInflater.inflate(mLayoutResId, container, false);
-
-        final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
-        if (!(rawListContainer instanceof ViewGroup)) {
-            throw new IllegalStateException("Content has view with id attribute "
-                    + "'android.R.id.list_container' that is not a ViewGroup class");
-        }
-
-        final ViewGroup listContainer = (ViewGroup) rawListContainer;
-
-        final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer,
-                savedInstanceState);
-        if (listView == null) {
-            throw new RuntimeException("Could not create RecyclerView");
-        }
-
-        mList = listView;
-
-        listView.addItemDecoration(mDividerDecoration);
-        setDivider(divider);
-        if (dividerHeight != -1) {
-            setDividerHeight(dividerHeight);
-        }
-        mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem);
-
-        // If mList isn't present in the view hierarchy, add it. mList is automatically inflated
-        // on an Auto device so don't need to add it.
-        if (mList.getParent() == null) {
-            listContainer.addView(mList);
-        }
-        mHandler.post(mRequestFocus);
-
-        return view;
-    }
-
-    /**
-     * Sets the {@link Drawable} that will be drawn between each item in the list.
-     *
-     * <p><strong>Note:</strong> If the drawable does not have an intrinsic height, you should also
-     * call {@link #setDividerHeight(int)}.
-     *
-     * @param divider The drawable to use
-     * {@link android.R.attr#divider}
-     */
-    public void setDivider(Drawable divider) {
-        mDividerDecoration.setDivider(divider);
-    }
-
-    /**
-     * Sets the height of the divider that will be drawn between each item in the list. Calling
-     * this will override the intrinsic height as set by {@link #setDivider(Drawable)}.
-     *
-     * @param height The new height of the divider in pixels
-     * {@link android.R.attr#dividerHeight}
-     */
-    public void setDividerHeight(int height) {
-        mDividerDecoration.setDividerHeight(height);
-    }
-
-    @Override
-    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        if (savedInstanceState != null) {
-            Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
-            if (container != null) {
-                final PreferenceScreen preferenceScreen = getPreferenceScreen();
-                if (preferenceScreen != null) {
-                    preferenceScreen.restoreHierarchyState(container);
-                }
-            }
-        }
-
-        if (mHavePrefs) {
-            bindPreferences();
-            if (mSelectPreferenceRunnable != null) {
-                mSelectPreferenceRunnable.run();
-                mSelectPreferenceRunnable = null;
-            }
-        }
-
-        mInitDone = true;
-    }
-
-    @Override
-    public void onStart() {
-        super.onStart();
-        mPreferenceManager.setOnPreferenceTreeClickListener(this);
-        mPreferenceManager.setOnDisplayPreferenceDialogListener(this);
-    }
-
-    @Override
-    public void onStop() {
-        super.onStop();
-        mPreferenceManager.setOnPreferenceTreeClickListener(null);
-        mPreferenceManager.setOnDisplayPreferenceDialogListener(null);
-    }
-
-    @Override
-    public void onDestroyView() {
-        mHandler.removeCallbacks(mRequestFocus);
-        mHandler.removeMessages(MSG_BIND_PREFERENCES);
-        if (mHavePrefs) {
-            unbindPreferences();
-        }
-        mList = null;
-        super.onDestroyView();
-    }
-
-    @Override
-    public void onSaveInstanceState(@NonNull Bundle outState) {
-        super.onSaveInstanceState(outState);
-
-        final PreferenceScreen preferenceScreen = getPreferenceScreen();
-        if (preferenceScreen != null) {
-            Bundle container = new Bundle();
-            preferenceScreen.saveHierarchyState(container);
-            outState.putBundle(PREFERENCES_TAG, container);
-        }
-    }
-
-    /**
-     * Returns the {@link PreferenceManager} used by this fragment.
-     *
-     * @return The {@link PreferenceManager} used by this fragment
-     */
-    public PreferenceManager getPreferenceManager() {
-        return mPreferenceManager;
-    }
-
-    /**
-     * Gets the root of the preference hierarchy that this fragment is showing.
-     *
-     * @return The {@link PreferenceScreen} that is the root of the preference hierarchy
-     */
-    public PreferenceScreen getPreferenceScreen() {
-        return mPreferenceManager.getPreferenceScreen();
-    }
-
-    /**
-     * Sets the root of the preference hierarchy that this fragment is showing.
-     *
-     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy
-     */
-    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
-        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
-            onUnbindPreferences();
-            mHavePrefs = true;
-            if (mInitDone) {
-                postBindPreferences();
-            }
-        }
-    }
-
-    /**
-     * Inflates the given XML resource and adds the preference hierarchy to the current
-     * preference hierarchy.
-     *
-     * @param preferencesResId The XML resource ID to inflate
-     */
-    public void addPreferencesFromResource(@XmlRes int preferencesResId) {
-        requirePreferenceManager();
-
-        setPreferenceScreen(mPreferenceManager.inflateFromResource(getContext(),
-                preferencesResId, getPreferenceScreen()));
-    }
-
-    /**
-     * Inflates the given XML resource and replaces the current preference hierarchy (if any) with
-     * the preference hierarchy rooted at {@code key}.
-     *
-     * @param preferencesResId The XML resource ID to inflate
-     * @param key              The preference key of the {@link PreferenceScreen} to use as the
-     *                         root of the preference hierarchy, or {@code null} to use the root
-     *                         {@link PreferenceScreen}.
-     */
-    public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) {
-        requirePreferenceManager();
-
-        final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(getContext(),
-                preferencesResId, null);
-
-        final Preference root;
-        if (key != null) {
-            root = xmlRoot.findPreference(key);
-            if (!(root instanceof PreferenceScreen)) {
-                throw new IllegalArgumentException("Preference object with key " + key
-                        + " is not a PreferenceScreen");
-            }
-        } else {
-            root = xmlRoot;
-        }
-
-        setPreferenceScreen((PreferenceScreen) root);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean onPreferenceTreeClick(Preference preference) {
-        if (preference.getFragment() != null) {
-            boolean handled = false;
-            if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) {
-                handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
-                        .onPreferenceStartFragment(this, preference);
-            }
-            if (!handled && getContext() instanceof OnPreferenceStartFragmentCallback) {
-                handled = ((OnPreferenceStartFragmentCallback) getContext())
-                        .onPreferenceStartFragment(this, preference);
-            }
-            // Check the Activity as well in case getContext was overridden to return something
-            // other than the Activity.
-            if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback) {
-                handled = ((OnPreferenceStartFragmentCallback) getActivity())
-                        .onPreferenceStartFragment(this, preference);
-            }
-            if (!handled) {
-                Log.w(TAG,
-                        "onPreferenceStartFragment is not implemented in the parent activity - "
-                                + "attempting to use a fallback implementation. You should "
-                                + "implement this method so that you can configure the new "
-                                + "fragment that will be displayed, and set a transition between "
-                                + "the fragments.");
-                final FragmentManager fragmentManager = requireActivity()
-                        .getSupportFragmentManager();
-                final Bundle args = preference.getExtras();
-                final Fragment fragment = fragmentManager.getFragmentFactory().instantiate(
-                        requireActivity().getClassLoader(), preference.getFragment());
-                fragment.setArguments(args);
-                fragment.setTargetFragment(this, 0);
-                fragmentManager.beginTransaction()
-                        // Attempt to replace this fragment in its root view - developers should
-                        // implement onPreferenceStartFragment in their activity so that they can
-                        // customize this behaviour and handle any transitions between fragments
-                        .replace(((View) getView().getParent()).getId(), fragment)
-                        .addToBackStack(null)
-                        .commit();
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Called by {@link PreferenceScreen#onClick()} in order to navigate to a new screen of
-     * preferences. Calls
-     * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback#onPreferenceStartScreen}
-     * if the target fragment or containing activity implements
-     * {@link PreferenceFragmentCompat.OnPreferenceStartScreenCallback}.
-     *
-     * @param preferenceScreen The {@link PreferenceScreen} to navigate to
-     */
-    @Override
-    public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
-        boolean handled = false;
-        if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) {
-            handled = ((OnPreferenceStartScreenCallback) getCallbackFragment())
-                    .onPreferenceStartScreen(this, preferenceScreen);
-        }
-        if (!handled && getContext() instanceof OnPreferenceStartScreenCallback) {
-            ((OnPreferenceStartScreenCallback) getContext())
-                    .onPreferenceStartScreen(this, preferenceScreen);
-        }
-        // Check the Activity as well in case getContext was overridden to return something other
-        // than the Activity.
-        if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) {
-            ((OnPreferenceStartScreenCallback) getActivity())
-                    .onPreferenceStartScreen(this, preferenceScreen);
-        }
-    }
-
-    @Override
-    @SuppressWarnings("TypeParameterUnusedInFormals")
-    @Nullable
-    public <T extends Preference> T findPreference(@NonNull CharSequence key) {
-        if (mPreferenceManager == null) {
-            return null;
-        }
-        return mPreferenceManager.findPreference(key);
-    }
-
-    private void requirePreferenceManager() {
-        if (mPreferenceManager == null) {
-            throw new RuntimeException("This should be called after super.onCreate.");
-        }
-    }
-
-    private void postBindPreferences() {
-        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
-        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
-    }
-
-    @SuppressWarnings("WeakerAccess") /* synthetic access */
-    void bindPreferences() {
-        final PreferenceScreen preferenceScreen = getPreferenceScreen();
-        if (preferenceScreen != null) {
-            getListView().setAdapter(onCreateAdapter(preferenceScreen));
-            preferenceScreen.onAttached();
-        }
-        onBindPreferences();
-    }
-
-    private void unbindPreferences() {
-        getListView().setAdapter(null);
-        final PreferenceScreen preferenceScreen = getPreferenceScreen();
-        if (preferenceScreen != null) {
-            preferenceScreen.onDetached();
-        }
-        onUnbindPreferences();
-    }
-
-    /**
-     * Used by Settings.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    protected void onBindPreferences() {}
-
-    /**
-     * Used by Settings.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    protected void onUnbindPreferences() {}
-
-    public final RecyclerView getListView() {
-        return mList;
-    }
-
-    /**
-     * Creates the {@link RecyclerView} used to display the preferences.
-     * Subclasses may override this to return a customized {@link RecyclerView}.
-     *
-     * @param inflater           The LayoutInflater object that can be used to inflate the
-     *                           {@link RecyclerView}.
-     * @param parent             The parent {@link ViewGroup} that the RecyclerView will be attached
-     *                           to. This method should not add the view itself, but this can be
-     *                           used to generate the layout params of the view.
-     * @param savedInstanceState If non-null, this view is being re-constructed from a previous
-     *                           saved state as given here.
-     * @return A new {@link RecyclerView} object to be placed into the view hierarchy
-     */
-    @SuppressWarnings("deprecation")
-    public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
-            Bundle savedInstanceState) {
-        // If device detected is Auto, use Auto's custom layout that contains a custom ViewGroup
-        // wrapping a RecyclerView
-        if (getContext().getPackageManager().hasSystemFeature(PackageManager
-                .FEATURE_AUTOMOTIVE)) {
-            RecyclerView recyclerView = parent.findViewById(R.id.recycler_view);
-            if (recyclerView != null) {
-                return recyclerView;
-            }
-        }
-        RecyclerView recyclerView = (RecyclerView) inflater
-                .inflate(R.layout.preference_recyclerview, parent, false);
-
-        recyclerView.setLayoutManager(onCreateLayoutManager());
-        recyclerView.setAccessibilityDelegateCompat(
-                new PreferenceRecyclerViewAccessibilityDelegate(recyclerView));
-
-        return recyclerView;
-    }
-
-    /**
-     * Called from {@link #onCreateRecyclerView} to create the {@link RecyclerView.LayoutManager}
-     * for the created {@link RecyclerView}.
-     *
-     * @return A new {@link RecyclerView.LayoutManager} instance
-     */
-    public RecyclerView.LayoutManager onCreateLayoutManager() {
-        return new LinearLayoutManager(getContext());
-    }
-
-    /**
-     * Creates the root adapter.
-     *
-     * @param preferenceScreen The {@link PreferenceScreen} object to create the adapter for
-     * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}
-     */
-    protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
-        return new PreferenceGroupAdapter(preferenceScreen);
-    }
-
-    /**
-     * Called when a preference in the tree requests to display a dialog. Subclasses should
-     * override this method to display custom dialogs or to handle dialogs for custom preference
-     * classes.
-     *
-     * @param preference The {@link Preference} object requesting the dialog
-     */
-    @Override
-    public void onDisplayPreferenceDialog(Preference preference) {
-
-        boolean handled = false;
-        if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) {
-            handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment())
-                    .onPreferenceDisplayDialog(this, preference);
-        }
-        if (!handled && getContext() instanceof OnPreferenceDisplayDialogCallback) {
-            handled = ((OnPreferenceDisplayDialogCallback) getContext())
-                    .onPreferenceDisplayDialog(this, preference);
-        }
-        // Check the Activity as well in case getContext was overridden to return something other
-        // than the Activity.
-        if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) {
-            handled = ((OnPreferenceDisplayDialogCallback) getActivity())
-                    .onPreferenceDisplayDialog(this, preference);
-        }
-
-        if (handled) {
-            return;
-        }
-
-        // check if dialog is already showing
-        if (getParentFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
-            return;
-        }
-
-        final DialogFragment f;
-        if (preference instanceof EditTextPreference) {
-            f = EditTextPreferenceDialogFragmentCompat.newInstance(preference.getKey());
-        } else if (preference instanceof ListPreference) {
-            f = ListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
-        } else if (preference instanceof MultiSelectListPreference) {
-            f = MultiSelectListPreferenceDialogFragmentCompat.newInstance(preference.getKey());
-        } else {
-            throw new IllegalArgumentException(
-                    "Cannot display dialog for an unknown Preference type: "
-                            + preference.getClass().getSimpleName()
-                            + ". Make sure to implement onPreferenceDisplayDialog() to handle "
-                            + "displaying a custom dialog for this Preference.");
-        }
-        f.setTargetFragment(this, 0);
-        f.show(getParentFragmentManager(), DIALOG_FRAGMENT_TAG);
-    }
-
-    /**
-     * A wrapper for getParentFragment which is v17+. Used by the leanback preference lib.
-     *
-     * @return The {@link Fragment} to possibly use as a callback
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    public Fragment getCallbackFragment() {
-        return null;
-    }
-
-    public void scrollToPreference(final String key) {
-        scrollToPreferenceInternal(null, key);
-    }
-
-    public void scrollToPreference(final Preference preference) {
-        scrollToPreferenceInternal(preference, null);
-    }
-
-    private void scrollToPreferenceInternal(final Preference preference, final String key) {
-        final Runnable r = new Runnable() {
-            @Override
-            public void run() {
-                final RecyclerView.Adapter adapter = mList.getAdapter();
-                if (!(adapter instanceof
-                        PreferenceGroup.PreferencePositionCallback)) {
-                    if (adapter != null) {
-                        throw new IllegalStateException("Adapter must implement "
-                                + "PreferencePositionCallback");
-                    } else {
-                        // Adapter was set to null, so don't scroll
-                        return;
-                    }
-                }
-                final int position;
-                if (preference != null) {
-                    position = ((PreferenceGroup.PreferencePositionCallback) adapter)
-                            .getPreferenceAdapterPosition(preference);
-                } else {
-                    position = ((PreferenceGroup.PreferencePositionCallback) adapter)
-                            .getPreferenceAdapterPosition(key);
-                }
-                if (position != RecyclerView.NO_POSITION) {
-                    mList.scrollToPosition(position);
-                } else {
-                    // Item not found, wait for an update and try again
-                    adapter.registerAdapterDataObserver(
-                            new ScrollToPreferenceObserver(adapter, mList, preference, key));
-                }
-            }
-        };
-        if (mList == null) {
-            mSelectPreferenceRunnable = r;
-        } else {
-            r.run();
-        }
-    }
-
-    /**
-     * Interface that the fragment's containing activity should implement to be able to process
-     * preference items that wish to switch to a specified fragment.
-     */
-    public interface OnPreferenceStartFragmentCallback {
-        /**
-         * Called when the user has clicked on a preference that has a fragment class name
-         * associated with it. The implementation should instantiate and switch to an instance
-         * of the given fragment.
-         *
-         * @param caller The fragment requesting navigation
-         * @param pref   The preference requesting the fragment
-         * @return {@code true} if the fragment creation has been handled
-         */
-        boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref);
-    }
-
-    /**
-     * Interface that the fragment's containing activity should implement to be able to process
-     * preference items that wish to switch to a new screen of preferences.
-     */
-    public interface OnPreferenceStartScreenCallback {
-        /**
-         * Called when the user has clicked on a {@link PreferenceScreen} in order to navigate to
-         * a new screen of preferences.
-         *
-         * @param caller The fragment requesting navigation
-         * @param pref   The preference screen to navigate to
-         * @return {@code true} if the screen navigation has been handled
-         */
-        boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref);
-    }
-
-    /**
-     * Interface that the fragment's containing activity should implement to be able to process
-     * preference items that wish to display a dialog.
-     */
-    public interface OnPreferenceDisplayDialogCallback {
-        /**
-         * @param caller The fragment containing the preference requesting the dialog
-         * @param pref   The preference requesting the dialog
-         * @return {@code true} if the dialog creation has been handled
-         */
-        boolean onPreferenceDisplayDialog(@NonNull PreferenceFragmentCompat caller,
-                Preference pref);
-    }
-
-    private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver {
-        private final RecyclerView.Adapter mAdapter;
-        private final RecyclerView mList;
-        private final Preference mPreference;
-        private final String mKey;
-
-        public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list,
-                Preference preference, String key) {
-            mAdapter = adapter;
-            mList = list;
-            mPreference = preference;
-            mKey = key;
-        }
-
-        private void scrollToPreference() {
-            mAdapter.unregisterAdapterDataObserver(this);
-            final int position;
-            if (mPreference != null) {
-                position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
-                        .getPreferenceAdapterPosition(mPreference);
-            } else {
-                position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
-                        .getPreferenceAdapterPosition(mKey);
-            }
-            if (position != RecyclerView.NO_POSITION) {
-                mList.scrollToPosition(position);
-            }
-        }
-
-        @Override
-        public void onChanged() {
-            scrollToPreference();
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount) {
-            scrollToPreference();
-        }
-
-        @Override
-        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
-            scrollToPreference();
-        }
-
-        @Override
-        public void onItemRangeInserted(int positionStart, int itemCount) {
-            scrollToPreference();
-        }
-
-        @Override
-        public void onItemRangeRemoved(int positionStart, int itemCount) {
-            scrollToPreference();
-        }
-
-        @Override
-        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-            scrollToPreference();
-        }
-    }
-
-    private class DividerDecoration extends RecyclerView.ItemDecoration {
-
-        private Drawable mDivider;
-        private int mDividerHeight;
-        private boolean mAllowDividerAfterLastItem = true;
-
-        DividerDecoration() {}
-
-        @Override
-        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
-            if (mDivider == null) {
-                return;
-            }
-            final int childCount = parent.getChildCount();
-            final int width = parent.getWidth();
-            for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
-                final View view = parent.getChildAt(childViewIndex);
-                if (shouldDrawDividerBelow(view, parent)) {
-                    int top = (int) view.getY() + view.getHeight();
-                    mDivider.setBounds(0, top, width, top + mDividerHeight);
-                    mDivider.draw(c);
-                }
-            }
-        }
-
-        @Override
-        public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
-                RecyclerView.State state) {
-            if (shouldDrawDividerBelow(view, parent)) {
-                outRect.bottom = mDividerHeight;
-            }
-        }
-
-        private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
-            final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
-            final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
-                    && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
-            if (!dividerAllowedBelow) {
-                return false;
-            }
-            boolean nextAllowed = mAllowDividerAfterLastItem;
-            int index = parent.indexOfChild(view);
-            if (index < parent.getChildCount() - 1) {
-                final View nextView = parent.getChildAt(index + 1);
-                final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
-                nextAllowed = nextHolder instanceof PreferenceViewHolder
-                        && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
-            }
-            return nextAllowed;
-        }
-
-        public void setDivider(Drawable divider) {
-            if (divider != null) {
-                mDividerHeight = divider.getIntrinsicHeight();
-            } else {
-                mDividerHeight = 0;
-            }
-            mDivider = divider;
-            mList.invalidateItemDecorations();
-        }
-
-        public void setDividerHeight(int dividerHeight) {
-            mDividerHeight = dividerHeight;
-            mList.invalidateItemDecorations();
-        }
-
-        public void setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem) {
-            mAllowDividerAfterLastItem = allowDividerAfterLastItem;
-        }
-    }
-}
diff --git a/third_party/apple_apsl/README.chromium b/third_party/apple_apsl/README.chromium
index e4b86f3..361d5fa 100644
--- a/third_party/apple_apsl/README.chromium
+++ b/third_party/apple_apsl/README.chromium
@@ -7,11 +7,12 @@
 Four files are excerpted here:
 
 malloc.h from:
-http://www.opensource.apple.com/source/Libc/Libc-763.11/include/malloc/malloc.h
+https://opensource.apple.com/source/libmalloc/libmalloc-283/include/malloc/malloc.h
 
 Modifications:
 - Modified #ifdef guards.
 - Removed everything but the definition of malloc_zone_t.
+- Added a header for the definition of boolean_t.
 - Renamed _malloc_zone_t to ChromeMallocZone to avoid possible name conflicts.
 
 CFRuntime.h from:
diff --git a/third_party/apple_apsl/malloc.h b/third_party/apple_apsl/malloc.h
index 9fd1d5d..1af648d 100644
--- a/third_party/apple_apsl/malloc.h
+++ b/third_party/apple_apsl/malloc.h
@@ -24,6 +24,8 @@
 #ifndef THIRD_PARTY_APPLE_APSL_MALLOC_H_
 #define THIRD_PARTY_APPLE_APSL_MALLOC_H_
 
+#include <mach/boolean.h>
+
 typedef struct _ChromeMallocZone {
     /* Only zone implementors should depend on the layout of this structure;
     Regular callers should use the access functions below */
@@ -53,6 +55,13 @@
 
     /* Empty out caches in the face of memory pressure. The callback may be NULL. Present in version >= 8. */
     size_t 	(*pressure_relief)(struct _malloc_zone_t *zone, size_t goal);
+
+    /*
+     * Checks whether an address might belong to the zone. May be NULL. Present in version >= 10.
+     * False positives are allowed (e.g. the pointer was freed, or it's in zone space that has
+     * not yet been allocated. False negatives are not allowed.
+     */
+    boolean_t (*claimed_address)(struct _malloc_zone_t *zone, void *ptr);
 } ChromeMallocZone;
 
 #endif  // THIRD_PARTY_APPLE_APSL_MALLOC_H_
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index e48968a..a5bdf4e 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -370,7 +370,7 @@
   // local root's view, and it will be an empty bounds if there is no focused
   // element.
   FocusedElementChanged(bool is_editable_element,
-                        gfx.mojom.Rect bounds_in_frame_widget);
+                        gfx.mojom.Rect bounds_in_frame_widget, blink.mojom.FocusType focus_type);
 
   // Show a popup menu using native controls on Mac or Android.
   // The popup menu is hidden when the mojo channel is closed.
diff --git a/third_party/blink/public/platform/web_runtime_features.h b/third_party/blink/public/platform/web_runtime_features.h
index aad09090..f435cf7 100644
--- a/third_party/blink/public/platform/web_runtime_features.h
+++ b/third_party/blink/public/platform/web_runtime_features.h
@@ -189,6 +189,7 @@
   BLINK_PLATFORM_EXPORT static void EnableWebXRAnchors(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRARModule(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRCameraAccess(bool);
+  BLINK_PLATFORM_EXPORT static void EnableWebXRDepth(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRHitTest(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRLightEstimation(bool);
   BLINK_PLATFORM_EXPORT static void EnableWebXRPlaneDetection(bool);
diff --git a/third_party/blink/renderer/bindings/generated_in_modules.gni b/third_party/blink/renderer/bindings/generated_in_modules.gni
index 8fd82a0..51f4c2c 100644
--- a/third_party/blink/renderer/bindings/generated_in_modules.gni
+++ b/third_party/blink/renderer/bindings/generated_in_modules.gni
@@ -165,8 +165,6 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_dynamics_compressor_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.cc",
-  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_event_source_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_event_source_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_extendable_cookie_change_event_init.cc",
@@ -689,6 +687,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_usb_device_request_options.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_configuration.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_configuration.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_config.cc",
@@ -1062,6 +1062,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_user_verification_requirement.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_acceleration_preference.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_acceleration_preference.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_pixel_format.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_video_pixel_format.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_wake_lock_type.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_wake_lock_type.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_webgl_power_preference.cc",
@@ -2065,6 +2067,8 @@
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_anchor_set.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_bounded_reference_space.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_bounded_reference_space.h",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_depth_information.cc",
+  "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_depth_information.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_state.cc",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_dom_overlay_state.h",
   "$root_gen_dir/third_party/blink/renderer/bindings/modules/v8/v8_xr_frame.cc",
diff --git a/third_party/blink/renderer/bindings/idl_in_modules.gni b/third_party/blink/renderer/bindings/idl_in_modules.gni
index 0672667..b31585e6 100644
--- a/third_party/blink/renderer/bindings/idl_in_modules.gni
+++ b/third_party/blink/renderer/bindings/idl_in_modules.gni
@@ -747,7 +747,6 @@
           "//third_party/blink/renderer/modules/webcodecs/encoded_audio_config.idl",
           "//third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.idl",
           "//third_party/blink/renderer/modules/webcodecs/encoded_video_chunk_init.idl",
-          "//third_party/blink/renderer/modules/webcodecs/encoded_video_config.idl",
           "//third_party/blink/renderer/modules/webcodecs/image_decoder.idl",
           "//third_party/blink/renderer/modules/webcodecs/image_decoder_init.idl",
           "//third_party/blink/renderer/modules/webcodecs/image_frame.idl",
@@ -755,6 +754,7 @@
           "//third_party/blink/renderer/modules/webcodecs/plane.idl",
           "//third_party/blink/renderer/modules/webcodecs/plane_init.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_decoder.idl",
+          "//third_party/blink/renderer/modules/webcodecs/video_decoder_config.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_decoder_init.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_encoder.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_encoder_config.idl",
@@ -764,6 +764,7 @@
           "//third_party/blink/renderer/modules/webcodecs/video_frame.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_frame_init.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_frame_output_callback.idl",
+          "//third_party/blink/renderer/modules/webcodecs/video_pixel_format.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_track_reader.idl",
           "//third_party/blink/renderer/modules/webcodecs/video_track_writer_parameters.idl",
           "//third_party/blink/renderer/modules/webcodecs/web_codecs_error_callback.idl",
@@ -975,6 +976,7 @@
           "//third_party/blink/renderer/modules/xr/xr_anchor.idl",
           "//third_party/blink/renderer/modules/xr/xr_anchor_set.idl",
           "//third_party/blink/renderer/modules/xr/xr_bounded_reference_space.idl",
+          "//third_party/blink/renderer/modules/xr/xr_depth_information.idl",
           "//third_party/blink/renderer/modules/xr/xr_dom_overlay_init.idl",
           "//third_party/blink/renderer/modules/xr/xr_dom_overlay_state.idl",
           "//third_party/blink/renderer/modules/xr/xr_frame.idl",
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 0f00c3c..74a9dac4 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -3050,7 +3050,7 @@
       default_value: "nullptr",
       type_name: "QuotesData",
       converter: "ConvertQuotes",
-      keywords: ["none"],
+      keywords: ["auto", "none"],
       typedom_types: ["Keyword"]
     },
     {
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index aaeadfa9..9903598 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -4917,8 +4917,11 @@
 const CSSValue* Quotes::ParseSingleValue(CSSParserTokenRange& range,
                                          const CSSParserContext& context,
                                          const CSSParserLocalContext&) const {
-  if (range.Peek().Id() == CSSValueID::kNone)
-    return css_parsing_utils::ConsumeIdent(range);
+  if (auto* value =
+          css_parsing_utils::ConsumeIdent<CSSValueID::kAuto, CSSValueID::kNone>(
+              range)) {
+    return value;
+  }
   CSSValueList* values = CSSValueList::CreateSpaceSeparated();
   while (!range.AtEnd()) {
     CSSStringValue* parsed_value = css_parsing_utils::ConsumeString(range);
@@ -4936,11 +4939,8 @@
     const SVGComputedStyle&,
     const LayoutObject*,
     bool allow_visited_style) const {
-  if (!style.Quotes()) {
-    // TODO(ramya.v): We should return the quote values that we're actually
-    // using.
-    return nullptr;
-  }
+  if (!style.Quotes())
+    return CSSIdentifierValue::Create(CSSValueID::kAuto);
   if (style.Quotes()->size()) {
     CSSValueList* list = CSSValueList::CreateSpaceSeparated();
     for (int i = 0; i < style.Quotes()->size(); i++) {
diff --git a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
index 47be63f..68eaa37 100644
--- a/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_builder_converter.cc
@@ -1384,8 +1384,10 @@
     }
     return quotes;
   }
-  DCHECK_EQ(To<CSSIdentifierValue>(value).GetValueID(), CSSValueID::kNone);
-  return QuotesData::Create();
+  if (To<CSSIdentifierValue>(value).GetValueID() == CSSValueID::kNone)
+    return QuotesData::Create();
+  DCHECK_EQ(To<CSSIdentifierValue>(value).GetValueID(), CSSValueID::kAuto);
+  return nullptr;
 }
 
 LengthSize StyleBuilderConverter::ConvertRadius(StyleResolverState& state,
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 2654a31b..d6056a1 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -3157,7 +3157,8 @@
   if (focused_element_.Get()) {
     Element* old_focused_element = focused_element_;
     focused_element_ = nullptr;
-    NotifyFocusedElementChanged(old_focused_element, nullptr);
+    NotifyFocusedElementChanged(old_focused_element, nullptr,
+                                mojom::blink::FocusType::kNone);
   }
   sequential_focus_navigation_starting_point_ = nullptr;
 
@@ -5199,8 +5200,10 @@
     }
   }
 
-  if (!focus_change_blocked)
-    NotifyFocusedElementChanged(old_focused_element, focused_element_.Get());
+  if (!focus_change_blocked) {
+    NotifyFocusedElementChanged(old_focused_element, focused_element_.Get(),
+                                params.type);
+  }
 
   UpdateStyleAndLayoutTree();
   if (LocalFrame* frame = GetFrame())
@@ -5214,7 +5217,8 @@
                                 mojom::blink::FocusType::kNone, nullptr));
 }
 
-void Document::SendFocusNotification(Element* new_focused_element) {
+void Document::SendFocusNotification(Element* new_focused_element,
+                                     mojom::blink::FocusType focus_type) {
   if (!GetPage())
     return;
 
@@ -5244,12 +5248,13 @@
     element_bounds = gfx::Rect(rect);
   }
 
-  GetFrame()->GetLocalFrameHostRemote().FocusedElementChanged(is_editable,
-                                                              element_bounds);
+  GetFrame()->GetLocalFrameHostRemote().FocusedElementChanged(
+      is_editable, element_bounds, focus_type);
 }
 
 void Document::NotifyFocusedElementChanged(Element* old_focused_element,
-                                           Element* new_focused_element) {
+                                           Element* new_focused_element,
+                                           mojom::blink::FocusType focus_type) {
   // |old_focused_element| may not belong to this document by invoking
   // adoptNode in event handlers during moving the focus to the new element.
   DCHECK(!new_focused_element || new_focused_element->GetDocument() == this);
@@ -5263,7 +5268,7 @@
     GetPage()->GetValidationMessageClient().DidChangeFocusTo(
         new_focused_element);
 
-    SendFocusNotification(new_focused_element);
+    SendFocusNotification(new_focused_element, focus_type);
 
     Document* old_document =
         old_focused_element ? &old_focused_element->GetDocument() : nullptr;
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 9f98651..fe69959 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1167,7 +1167,7 @@
   // updating focus appearance.
   bool WillUpdateFocusAppearance() const;
 
-  void SendFocusNotification(Element*);
+  void SendFocusNotification(Element*, mojom::blink::FocusType);
 
   bool IsDNSPrefetchEnabled() const { return is_dns_prefetch_enabled_; }
   void ParseDNSPrefetchControlHeader(const String&);
@@ -1782,7 +1782,8 @@
   }
 
   void NotifyFocusedElementChanged(Element* old_focused_element,
-                                   Element* new_focused_element);
+                                   Element* new_focused_element,
+                                   mojom::blink::FocusType focus_type);
   void DisplayNoneChangedForFrame();
 
   // Handles a connection error to |has_trust_tokens_answerer_| by rejecting all
diff --git a/third_party/blink/renderer/core/exported/web_frame_test.cc b/third_party/blink/renderer/core/exported/web_frame_test.cc
index 2352e586..b064a97 100644
--- a/third_party/blink/renderer/core/exported/web_frame_test.cc
+++ b/third_party/blink/renderer/core/exported/web_frame_test.cc
@@ -13584,7 +13584,8 @@
 
   // FakeLocalFrameHost:
   void FocusedElementChanged(bool is_editable_element,
-                             const gfx::Rect& bounds_in_frame_widget) override {
+                             const gfx::Rect& bounds_in_frame_widget,
+                             blink::mojom::FocusType focus_type) override {
     did_notify_ = true;
   }
 
diff --git a/third_party/blink/renderer/core/frame/root_frame_viewport.h b/third_party/blink/renderer/core/frame/root_frame_viewport.h
index b2c9e412..89baa70 100644
--- a/third_party/blink/renderer/core/frame/root_frame_viewport.h
+++ b/third_party/blink/renderer/core/frame/root_frame_viewport.h
@@ -9,7 +9,7 @@
 #include "third_party/blink/public/mojom/scroll/scroll_into_view_params.mojom-blink-forward.h"
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
@@ -95,12 +95,10 @@
   cc::Layer* LayerForHorizontalScrollbar() const override;
   cc::Layer* LayerForVerticalScrollbar() const override;
   cc::Layer* LayerForScrollCorner() const override;
-  int HorizontalScrollbarHeight(
-      OverlayScrollbarClipBehavior =
-          kIgnorePlatformOverlayScrollbarSize) const override;
-  int VerticalScrollbarWidth(
-      OverlayScrollbarClipBehavior =
-          kIgnorePlatformOverlayScrollbarSize) const override;
+  int HorizontalScrollbarHeight(OverlayScrollbarClipBehavior =
+                                    kIgnoreOverlayScrollbarSize) const override;
+  int VerticalScrollbarWidth(OverlayScrollbarClipBehavior =
+                                 kIgnoreOverlayScrollbarSize) const override;
   ScrollResult UserScroll(ScrollGranularity,
                           const FloatSize&,
                           ScrollableArea::ScrollCallback on_finish) override;
diff --git a/third_party/blink/renderer/core/frame/visual_viewport.cc b/third_party/blink/renderer/core/frame/visual_viewport.cc
index 15d463d6..f424381 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport.cc
@@ -632,7 +632,9 @@
     int thumb_thickness = theme.ThumbThickness(scale);
     int scrollbar_margin = theme.ScrollbarMargin(scale);
     cc::ScrollbarOrientation cc_orientation =
-        orientation == kHorizontalScrollbar ? cc::HORIZONTAL : cc::VERTICAL;
+        orientation == kHorizontalScrollbar
+            ? cc::ScrollbarOrientation::HORIZONTAL
+            : cc::ScrollbarOrientation::VERTICAL;
     scrollbar_layer = cc::SolidColorScrollbarLayer::Create(
         cc_orientation, thumb_thickness, scrollbar_margin,
         /*is_left_side_vertical_scrollbar*/ false);
diff --git a/third_party/blink/renderer/core/frame/visual_viewport_test.cc b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
index e8f8b43..ff13dfd 100644
--- a/third_party/blink/renderer/core/frame/visual_viewport_test.cc
+++ b/third_party/blink/renderer/core/frame/visual_viewport_test.cc
@@ -2603,7 +2603,7 @@
     EXPECT_FALSE(scrollbar->HitTestable());
     EXPECT_TRUE(scrollbar->IsScrollbarLayerForTesting());
     EXPECT_EQ(
-        cc::VERTICAL,
+        cc::ScrollbarOrientation::VERTICAL,
         static_cast<const cc::ScrollbarLayerBase*>(scrollbar)->orientation());
     EXPECT_EQ(gfx::Size(7, 393), scrollbar->bounds());
     EXPECT_EQ(gfx::Vector2dF(393, 0), scrollbar->offset_to_transform_parent());
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index f93494ee..1bbde6b3 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -491,7 +491,8 @@
 void HTMLVideoElement::DidEnterFullscreen() {
   UpdateControlsVisibility();
 
-  if (DisplayType() == WebMediaPlayer::DisplayType::kPictureInPicture) {
+  if (DisplayType() == WebMediaPlayer::DisplayType::kPictureInPicture &&
+      !IsInAutoPIP()) {
     PictureInPictureController::From(GetDocument())
         .ExitPictureInPicture(this, nullptr);
   }
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 d18d9a18..37ec61c 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
@@ -191,4 +191,17 @@
   EXPECT_TRUE(video()->HasAvailableVideoFrame());
 }
 
+TEST_F(HTMLVideoElementTest, AutoPIPExitPIPTest) {
+  video()->SetSrc("http://example.com/foo.mp4");
+  test::RunPendingTasks();
+
+  // Set in auto PIP.
+  video()->OnBecamePersistentVideo(true);
+
+  // Shouldn't get to PictureInPictureController::ExitPictureInPicture
+  // and fail the DCHECK.
+  EXPECT_NO_FATAL_FAILURE(video()->DidEnterFullscreen());
+  test::RunPendingTasks();
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index 070bb2e5..1b6d318 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -30,7 +30,7 @@
 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
 #include "third_party/blink/renderer/core/layout/min_max_sizes.h"
 #include "third_party/blink/renderer/core/layout/overflow_model.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 
 namespace blink {
 
@@ -1325,14 +1325,14 @@
   // Returns the intersection of all overflow clips which apply.
   virtual PhysicalRect OverflowClipRect(
       const PhysicalOffset& location,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize) const;
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize) const;
   PhysicalRect ClipRect(const PhysicalOffset& location) const;
 
   // This version is for legacy code that has not switched to the new physical
   // geometry yet.
   LayoutRect OverflowClipRect(const LayoutPoint& location,
                               OverlayScrollbarClipBehavior behavior =
-                                  kIgnorePlatformOverlayScrollbarSize) const {
+                                  kIgnoreOverlayScrollbarSize) const {
     return OverflowClipRect(PhysicalOffset(location), behavior).ToLayoutRect();
   }
 
@@ -1801,7 +1801,7 @@
 
   void ExcludeScrollbars(
       PhysicalRect&,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize) const;
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize) const;
 
   LayoutUnit ContainingBlockLogicalWidthForPositioned(
       const LayoutBoxModelObject* containing_block,
diff --git a/third_party/blink/renderer/core/layout/layout_file_upload_control.h b/third_party/blink/renderer/core/layout/layout_file_upload_control.h
index 372714a..a9869961 100644
--- a/third_party/blink/renderer/core/layout/layout_file_upload_control.h
+++ b/third_party/blink/renderer/core/layout/layout_file_upload_control.h
@@ -23,7 +23,7 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 
 namespace blink {
 
diff --git a/third_party/blink/renderer/core/layout/layout_object.cc b/third_party/blink/renderer/core/layout/layout_object.cc
index 6c34d774..08f804d 100644
--- a/third_party/blink/renderer/core/layout/layout_object.cc
+++ b/third_party/blink/renderer/core/layout/layout_object.cc
@@ -1752,7 +1752,7 @@
     FloatClipRect clip_rect((FloatRect(rect)));
     intersects = GeometryMapper::LocalToAncestorVisualRect(
         container_properties, ancestor->FirstFragment().ContentsProperties(),
-        clip_rect, kIgnorePlatformOverlayScrollbarSize,
+        clip_rect, kIgnoreOverlayScrollbarSize,
         (visual_rect_flags & kEdgeInclusive) ? kInclusiveIntersect
                                              : kNonInclusiveIntersect);
     rect = PhysicalRect::EnclosingRect(clip_rect.Rect());
diff --git a/third_party/blink/renderer/core/layout/layout_table.h b/third_party/blink/renderer/core/layout/layout_table.h
index 6c57542..1f99679 100644
--- a/third_party/blink/renderer/core/layout/layout_table.h
+++ b/third_party/blink/renderer/core/layout/layout_table.h
@@ -31,7 +31,7 @@
 #include "third_party/blink/renderer/core/css/css_property_names.h"
 #include "third_party/blink/renderer/core/layout/layout_block.h"
 #include "third_party/blink/renderer/core/layout/ng/table/layout_ng_table_interface.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
 namespace blink {
@@ -472,10 +472,9 @@
   LayoutUnit ConvertStyleLogicalHeightToComputedHeight(
       const Length& style_logical_height) const;
 
-  PhysicalRect OverflowClipRect(
-      const PhysicalOffset& location,
-      OverlayScrollbarClipBehavior =
-          kIgnorePlatformOverlayScrollbarSize) const override;
+  PhysicalRect OverflowClipRect(const PhysicalOffset& location,
+                                OverlayScrollbarClipBehavior =
+                                    kIgnoreOverlayScrollbarSize) const override;
 
   void ComputeVisualOverflow(bool recompute_floats) final;
 
diff --git a/third_party/blink/renderer/core/layout/layout_view.h b/third_party/blink/renderer/core/layout/layout_view.h
index 0fb838d..d44e74b 100644
--- a/third_party/blink/renderer/core/layout/layout_view.h
+++ b/third_party/blink/renderer/core/layout/layout_view.h
@@ -30,7 +30,7 @@
 #include "third_party/blink/renderer/core/layout/layout_block_flow.h"
 #include "third_party/blink/renderer/core/layout/layout_state.h"
 #include "third_party/blink/renderer/core/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
@@ -161,10 +161,9 @@
                      MapCoordinatesFlags mode = 0) const override;
 
   PhysicalRect ViewRect() const override;
-  PhysicalRect OverflowClipRect(
-      const PhysicalOffset& location,
-      OverlayScrollbarClipBehavior =
-          kIgnorePlatformOverlayScrollbarSize) const override;
+  PhysicalRect OverflowClipRect(const PhysicalOffset& location,
+                                OverlayScrollbarClipBehavior =
+                                    kIgnoreOverlayScrollbarSize) const override;
 
   // If either direction has a non-auto mode, the other must as well.
   void SetAutosizeScrollbarModes(mojom::blink::ScrollbarMode h_mode,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
index e55f7e8..d1ad96b0 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h
@@ -13,7 +13,7 @@
 #include "third_party/blink/renderer/core/layout/ng/ng_physical_container_fragment.h"
 #include "third_party/blink/renderer/core/layout/ng/table/ng_table_borders.h"
 #include "third_party/blink/renderer/core/layout/ng/table/ng_table_fragment_data.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
@@ -167,7 +167,7 @@
   // and change them to use NG geometry types once LayoutNG supports overflow.
   PhysicalRect OverflowClipRect(
       const PhysicalOffset& location,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize) const;
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize) const;
   LayoutSize PixelSnappedScrolledContentOffset() const;
   PhysicalSize ScrollSize() const;
 
diff --git a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
index 1572481..279a5d9 100644
--- a/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/scrolling_test.cc
@@ -998,8 +998,10 @@
   EXPECT_TRUE(scroll_node->user_scrollable_horizontal);
   EXPECT_TRUE(scroll_node->user_scrollable_vertical);
 
-  EXPECT_TRUE(ScrollbarLayerForScrollNode(scroll_node, cc::HORIZONTAL));
-  EXPECT_TRUE(ScrollbarLayerForScrollNode(scroll_node, cc::VERTICAL));
+  EXPECT_TRUE(ScrollbarLayerForScrollNode(
+      scroll_node, cc::ScrollbarOrientation::HORIZONTAL));
+  EXPECT_TRUE(ScrollbarLayerForScrollNode(scroll_node,
+                                          cc::ScrollbarOrientation::VERTICAL));
 }
 
 TEST_P(ScrollingTest, overflowHidden) {
@@ -1045,8 +1047,10 @@
   const auto* scroll_node =
       ScrollNodeForScrollableArea(inner_frame_view->LayoutViewport());
   ASSERT_TRUE(scroll_node);
-  EXPECT_TRUE(ScrollbarLayerForScrollNode(scroll_node, cc::HORIZONTAL));
-  EXPECT_TRUE(ScrollbarLayerForScrollNode(scroll_node, cc::VERTICAL));
+  EXPECT_TRUE(ScrollbarLayerForScrollNode(
+      scroll_node, cc::ScrollbarOrientation::HORIZONTAL));
+  EXPECT_TRUE(ScrollbarLayerForScrollNode(scroll_node,
+                                          cc::ScrollbarOrientation::VERTICAL));
 }
 
 TEST_P(ScrollingTest, rtlIframe) {
@@ -1111,8 +1115,8 @@
       ScrollNodeForScrollableArea(frame_view->LayoutViewport());
   ASSERT_TRUE(scroll_node);
 
-  const auto* horizontal_scrollbar_layer =
-      ScrollbarLayerForScrollNode(scroll_node, cc::HORIZONTAL);
+  const auto* horizontal_scrollbar_layer = ScrollbarLayerForScrollNode(
+      scroll_node, cc::ScrollbarOrientation::HORIZONTAL);
   ASSERT_TRUE(horizontal_scrollbar_layer);
   // TODO(crbug.com/1029620): CAP needs more accurate contents_opaque.
   if (!RuntimeEnabledFeatures::CompositeAfterPaintEnabled()) {
@@ -1122,7 +1126,8 @@
               horizontal_scrollbar_layer->contents_opaque());
   }
 
-  EXPECT_FALSE(ScrollbarLayerForScrollNode(scroll_node, cc::VERTICAL));
+  EXPECT_FALSE(ScrollbarLayerForScrollNode(scroll_node,
+                                           cc::ScrollbarOrientation::VERTICAL));
 }
 
 TEST_P(ScrollingTest, NestedIFramesMainThreadScrollingRegion) {
diff --git a/third_party/blink/renderer/core/paint/clip_rects_cache.h b/third_party/blink/renderer/core/paint/clip_rects_cache.h
index 6730fa1c..2b82d78 100644
--- a/third_party/blink/renderer/core/paint/clip_rects_cache.h
+++ b/third_party/blink/renderer/core/paint/clip_rects_cache.h
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/core/paint/clip_rects.h"
 
 #if DCHECK_IS_ON()
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"  // For OverlayScrollbarClipBehavior.
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #endif
 
 namespace blink {
@@ -37,7 +37,7 @@
         : root(nullptr)
 #if DCHECK_IS_ON()
           ,
-          overlay_scrollbar_clip_behavior(kIgnorePlatformOverlayScrollbarSize)
+          overlay_scrollbar_clip_behavior(kIgnoreOverlayScrollbarSize)
 #endif
     {
     }
diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
index 7a778f85e..feb341a 100644
--- a/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
+++ b/third_party/blink/renderer/core/paint/compositing/compositing_inputs_updater.cc
@@ -481,7 +481,7 @@
         .CalculateBackgroundClipRect(
             ClipRectsContext(root_layer_,
                              &root_layer_->GetLayoutObject().FirstFragment(),
-                             cache_slot, kIgnorePlatformOverlayScrollbarSize,
+                             cache_slot, kIgnoreOverlayScrollbarSize,
                              kIgnoreOverflowClipAndScroll),
             clip_rect);
     IntRect snapped_clip_rect = PixelSnappedIntRect(clip_rect.Rect());
diff --git a/third_party/blink/renderer/core/paint/paint_layer.h b/third_party/blink/renderer/core/paint/paint_layer.h
index b909fe7..ad423db 100644
--- a/third_party/blink/renderer/core/paint/paint_layer.h
+++ b/third_party/blink/renderer/core/paint/paint_layer.h
@@ -58,8 +58,8 @@
 #include "third_party/blink/renderer/core/paint/paint_layer_stacking_node.h"
 #include "third_party/blink/renderer/core/paint/paint_result.h"
 #include "third_party/blink/renderer/platform/graphics/compositing_reasons.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
 #include "third_party/blink/renderer/platform/graphics/squashing_disallowed_reasons.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
@@ -986,7 +986,7 @@
       PaintLayerFragments&,
       const PaintLayer* root_layer,
       const CullRect* cull_rect,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize,
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize,
       ShouldRespectOverflowClipType = kRespectOverflowClip,
       const PhysicalOffset* offset_from_root = nullptr,
       const PhysicalOffset& sub_pixel_accumulation = PhysicalOffset()) const;
@@ -995,7 +995,7 @@
       PaintLayerFragments&,
       const PaintLayer* root_layer,
       const CullRect* cull_rect,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize,
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize,
       ShouldRespectOverflowClipType = kRespectOverflowClip,
       const PhysicalOffset* offset_from_root = nullptr,
       const PhysicalOffset& sub_pixel_accumulation = PhysicalOffset()) const;
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper.cc b/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
index dd2996d1..28b216d 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper.cc
@@ -475,7 +475,7 @@
         context.overlay_scrollbar_clip_behavior;
 
     if (is_clipping_root)
-      clip_behavior = kIgnorePlatformOverlayScrollbarSize;
+      clip_behavior = kIgnoreOverlayScrollbarSize;
 
     FloatClipRect clip_rect(FloatRect(LocalVisualRect(context)));
     clip_rect.MoveBy(FloatPoint(fragment_data.PaintOffset()));
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper.h b/third_party/blink/renderer/core/paint/paint_layer_clipper.h
index a298bcd..a627998c 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper.h
@@ -47,8 +47,8 @@
 
 #include "third_party/blink/renderer/core/core_export.h"
 #include "third_party/blink/renderer/core/paint/clip_rects_cache.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/graphics/paint/cull_rect.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 
 namespace blink {
@@ -71,7 +71,7 @@
       const FragmentData* fragment,
       ClipRectsCacheSlot slot,
       OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior =
-          kIgnorePlatformOverlayScrollbarSize,
+          kIgnoreOverlayScrollbarSize,
       ShouldRespectOverflowClipType root_layer_clip_behavior =
           kRespectOverflowClip,
       const PhysicalOffset& sub_pixel_accumulation = PhysicalOffset())
diff --git a/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc b/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc
index ae447df..b5c72c8 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_clipper_test.cc
@@ -31,11 +31,11 @@
   Element* target = GetDocument().getElementById("target");
   PaintLayer* target_paint_layer =
       ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
-      PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip,
+                           PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
 
   ClipRect background_rect_gm;
   target_paint_layer
@@ -65,11 +65,11 @@
   Element* target = GetDocument().getElementById("target");
   PaintLayer* target_paint_layer =
       ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
-      PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip,
+                           PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
 
   ClipRect background_rect_gm;
   target_paint_layer
@@ -99,11 +99,11 @@
   Element* target = GetDocument().getElementById("target");
   PaintLayer* target_paint_layer =
       ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
-      PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip,
+                           PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
 
   ClipRect background_rect_gm;
   target_paint_layer
@@ -135,11 +135,11 @@
       ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
   // When RLS is enabled, the LayoutView will have a composited scrolling layer,
   // so don't apply an overflow clip.
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip,
-      PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip,
+                           PhysicalOffset(LayoutUnit(0.25), LayoutUnit(0.35)));
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
 
@@ -171,10 +171,10 @@
       ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
   // When RLS is enabled, the LayoutView will have a composited scrolling layer,
   // so don't apply an overflow clip.
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
 
@@ -202,10 +202,10 @@
   Element* target = GetDocument().getElementById("target");
   PaintLayer* target_paint_layer =
       ToLayoutBoxModelObject(target->GetLayoutObject())->Layer();
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
 
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
@@ -276,10 +276,10 @@
   )HTML");
   LayoutBox* target = ToLayoutBox(GetLayoutObjectByElementId("target"));
   PaintLayer* target_paint_layer = target->Layer();
-  ClipRectsContext context(
-      GetDocument().GetLayoutView()->Layer(),
-      &GetDocument().GetLayoutView()->FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(GetDocument().GetLayoutView()->Layer(),
+                           &GetDocument().GetLayoutView()->FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
 
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
@@ -335,9 +335,9 @@
 
   PaintLayer* layer =
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
-  ClipRectsContext context(
-      layer, &layer->GetLayoutObject().FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(layer, &layer->GetLayoutObject().FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
 
@@ -373,8 +373,7 @@
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
   ClipRectsContext context(
       layer->Parent(), &layer->Parent()->GetLayoutObject().FirstFragment(),
-      kUncachedClipRects, kIgnorePlatformOverlayScrollbarSize,
-      kIgnoreOverflowClip);
+      kUncachedClipRects, kIgnoreOverlayScrollbarSize, kIgnoreOverflowClip);
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
 
@@ -622,9 +621,9 @@
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("root"))->Layer();
   PaintLayer* target =
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
-  ClipRectsContext context(
-      root, &root->GetLayoutObject().FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(root, &root->GetLayoutObject().FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
   PhysicalRect infinite_rect(LayoutRect::InfiniteIntRect());
   PhysicalRect layer_bounds(infinite_rect);
   ClipRect background_rect(infinite_rect);
@@ -657,9 +656,9 @@
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("root"))->Layer();
   PaintLayer* target =
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
-  ClipRectsContext context(
-      root, &root->GetLayoutObject().FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(root, &root->GetLayoutObject().FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
   PhysicalRect infinite_rect(LayoutRect::InfiniteIntRect());
   PhysicalRect layer_bounds(infinite_rect);
   ClipRect background_rect(infinite_rect);
@@ -693,9 +692,9 @@
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("root"))->Layer();
   PaintLayer* target =
       ToLayoutBoxModelObject(GetLayoutObjectByElementId("target"))->Layer();
-  ClipRectsContext context(
-      root, &root->GetLayoutObject().FirstFragment(), kUncachedClipRects,
-      kIgnorePlatformOverlayScrollbarSize, kIgnoreOverflowClip);
+  ClipRectsContext context(root, &root->GetLayoutObject().FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize,
+                           kIgnoreOverflowClip);
   PhysicalRect infinite_rect(LayoutRect::InfiniteIntRect());
   PhysicalRect layer_bounds(infinite_rect);
   ClipRect background_rect(infinite_rect);
@@ -722,9 +721,9 @@
   Element* root = GetDocument().getElementById("root");
   PaintLayer* root_paint_layer =
       ToLayoutBoxModelObject(root->GetLayoutObject())->Layer();
-  ClipRectsContext context(
-      root_paint_layer, &root_paint_layer->GetLayoutObject().FirstFragment(),
-      kUncachedClipRects, kIgnorePlatformOverlayScrollbarSize);
+  ClipRectsContext context(root_paint_layer,
+                           &root_paint_layer->GetLayoutObject().FirstFragment(),
+                           kUncachedClipRects, kIgnoreOverlayScrollbarSize);
   PhysicalRect layer_bounds;
   ClipRect background_rect, foreground_rect;
 
@@ -912,7 +911,7 @@
             ClipRectsContext(
                 target_layer.GetLayoutObject().View()->Layer(),
                 &target_layer.GetLayoutObject().View()->FirstFragment(),
-                kUncachedClipRects, kIgnorePlatformOverlayScrollbarSize,
+                kUncachedClipRects, kIgnoreOverlayScrollbarSize,
                 kIgnoreOverflowClipAndScroll),
             clip_rect);
     EXPECT_EQ(expected_rect, clip_rect.Rect());
@@ -925,7 +924,7 @@
             ClipRectsContext(
                 target_layer.GetLayoutObject().View()->Layer(),
                 &target_layer.GetLayoutObject().View()->FirstFragment(),
-                kUncachedClipRects, kIgnorePlatformOverlayScrollbarSize,
+                kUncachedClipRects, kIgnoreOverlayScrollbarSize,
                 kIgnoreOverflowClipAndScroll),
             clip_rect);
     EXPECT_EQ(expected_rect, clip_rect.Rect());
diff --git a/third_party/blink/renderer/core/paint/paint_layer_painter.cc b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
index 0a54dce..502721d9 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_painter.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_painter.cc
@@ -442,7 +442,7 @@
     // offsets for each layer fragment.
     paint_layer_.CollectFragments(
         layer_fragments, local_painting_info.root_layer,
-        &local_painting_info.cull_rect, kIgnorePlatformOverlayScrollbarSize,
+        &local_painting_info.cull_rect, kIgnoreOverlayScrollbarSize,
         respect_overflow_clip, &offset_from_root,
         local_painting_info.sub_pixel_accumulation);
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
index 7cc46df..5c425ca1 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.cc
@@ -703,11 +703,10 @@
   if (this == controller.RootScrollerArea()) {
     visible_size = controller.RootScrollerVisibleArea();
   } else {
-    visible_size =
-        PixelSnappedIntRect(GetLayoutBox()->OverflowClipRect(
-                                GetLayoutBox()->Location(),
-                                kIgnorePlatformAndCSSOverlayScrollbarSize))
-            .Size();
+    visible_size = PixelSnappedIntRect(GetLayoutBox()->OverflowClipRect(
+                                           GetLayoutBox()->Location(),
+                                           kIgnoreOverlayScrollbarSize))
+                       .Size();
   }
 
   // TODO(skobes): We should really ASSERT that contentSize >= visibleSize
@@ -839,9 +838,9 @@
 }
 
 void PaintLayerScrollableArea::ScrollbarFrameRectChanged() {
-  // Size of non-overlay scrollbar affects overflow clip rect.
-  if (!HasOverlayScrollbars())
-    GetLayoutBox()->SetNeedsPaintPropertyUpdate();
+  // Size of non-overlay scrollbar affects overflow clip rect. Size of overlay
+  // scrollbar effects hit testing rect excluding overlay scrollbars.
+  GetLayoutBox()->SetNeedsPaintPropertyUpdate();
 }
 
 bool PaintLayerScrollableArea::ScrollbarsCanBeActive() const {
@@ -1255,9 +1254,8 @@
   // converse problem seems to happen much less frequently in practice, so we
   // bias the logic towards preventing unwanted horizontal scrollbars, which
   // are more common and annoying.
-  LayoutUnit client_width =
-      LayoutContentRect(kIncludeScrollbars).Width() -
-      VerticalScrollbarWidth(kIgnorePlatformAndCSSOverlayScrollbarSize);
+  LayoutUnit client_width = LayoutContentRect(kIncludeScrollbars).Width() -
+                            VerticalScrollbarWidth(kIgnoreOverlayScrollbarSize);
   if (NeedsRelayout() && !HadVerticalScrollbarBeforeRelayout())
     client_width += VerticalScrollbarWidth();
   LayoutUnit scroll_width(ScrollWidth());
@@ -1269,7 +1267,7 @@
 bool PaintLayerScrollableArea::HasVerticalOverflow() const {
   LayoutUnit client_height =
       LayoutContentRect(kIncludeScrollbars).Height() -
-      HorizontalScrollbarHeight(kIgnorePlatformAndCSSOverlayScrollbarSize);
+      HorizontalScrollbarHeight(kIgnoreOverlayScrollbarSize);
   LayoutUnit scroll_height(ScrollHeight());
   LayoutUnit box_y = GetLayoutBox()->Location().Y();
   return SnapSizeToPixel(scroll_height, box_y) >
@@ -1747,14 +1745,11 @@
     OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
   if (!HasVerticalScrollbar())
     return 0;
-  if (overlay_scrollbar_clip_behavior ==
-          kIgnorePlatformAndCSSOverlayScrollbarSize &&
+  if (overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize &&
       GetLayoutBox()->StyleRef().OverflowY() == EOverflow::kOverlay) {
     return 0;
   }
-  if ((overlay_scrollbar_clip_behavior == kIgnorePlatformOverlayScrollbarSize ||
-       overlay_scrollbar_clip_behavior ==
-           kIgnorePlatformAndCSSOverlayScrollbarSize ||
+  if ((overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize ||
        !VerticalScrollbar()->ShouldParticipateInHitTesting()) &&
       VerticalScrollbar()->IsOverlayScrollbar()) {
     return 0;
@@ -1766,14 +1761,11 @@
     OverlayScrollbarClipBehavior overlay_scrollbar_clip_behavior) const {
   if (!HasHorizontalScrollbar())
     return 0;
-  if (overlay_scrollbar_clip_behavior ==
-          kIgnorePlatformAndCSSOverlayScrollbarSize &&
+  if (overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize &&
       GetLayoutBox()->StyleRef().OverflowX() == EOverflow::kOverlay) {
     return 0;
   }
-  if ((overlay_scrollbar_clip_behavior == kIgnorePlatformOverlayScrollbarSize ||
-       overlay_scrollbar_clip_behavior ==
-           kIgnorePlatformAndCSSOverlayScrollbarSize ||
+  if ((overlay_scrollbar_clip_behavior == kIgnoreOverlayScrollbarSize ||
        !HorizontalScrollbar()->ShouldParticipateInHitTesting()) &&
       HorizontalScrollbar()->IsOverlayScrollbar()) {
     return 0;
@@ -1857,11 +1849,20 @@
 }
 
 bool PaintLayerScrollableArea::HasOverlayOverflowControls() const {
-  return HasOverlayScrollbars() ||
-         (!HasScrollbar() && GetLayoutBox()->CanResize());
+  if (HasOverlayScrollbars())
+    return true;
+  if (!HasScrollbar() && GetLayoutBox()->CanResize())
+    return true;
+  if (GetLayoutBox()->StyleRef().OverflowX() == EOverflow::kOverlay ||
+      GetLayoutBox()->StyleRef().OverflowY() == EOverflow::kOverlay)
+    return true;
+  return false;
 }
 
-bool PaintLayerScrollableArea::HasNonOverlayOverflowControls() const {
+bool PaintLayerScrollableArea::NeedsScrollCorner() const {
+  // This is one of the differences between platform overlay scrollbars and
+  // overflow:overlay scrollbars: the former don't need scroll corner, while
+  // the latter do. HasOverlayScrollbars doesn't include overflow:overlay.
   return HasScrollbar() && !HasOverlayScrollbars();
 }
 
@@ -1910,7 +1911,7 @@
 }
 
 void PaintLayerScrollableArea::UpdateScrollCornerStyle() {
-  if (!HasNonOverlayOverflowControls()) {
+  if (!NeedsScrollCorner()) {
     if (scroll_corner_) {
       scroll_corner_->Destroy();
       scroll_corner_ = nullptr;
@@ -2891,8 +2892,9 @@
 void PaintLayerScrollableArea::DidAddScrollbar(
     Scrollbar& scrollbar,
     ScrollbarOrientation orientation) {
-  // Z-order of reparented scrollbar is updated along with the z-order lists.
-  if (scrollbar.IsOverlayScrollbar())
+  // Z-order of recordered overflow controls is updated along with the z-order
+  // lists.
+  if (HasOverlayOverflowControls())
     layer_->DirtyStackingContextZOrderLists();
 
   ScrollableArea::DidAddScrollbar(scrollbar, orientation);
@@ -2902,8 +2904,8 @@
     Scrollbar& scrollbar,
     ScrollbarOrientation orientation) {
   if (layer_->NeedsReorderOverlayOverflowControls()) {
-    // Z-order of reparented scrollbar is updated along with the z-order lists.
-    DCHECK(scrollbar.IsOverlayScrollbar());
+    // Z-order of recordered overflow controls is updated along with the z-order
+    // lists.
     layer_->DirtyStackingContextZOrderLists();
   }
 
diff --git a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
index 1537ad5..fc41101b 100644
--- a/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
+++ b/third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h
@@ -51,7 +51,7 @@
 #include "third_party/blink/renderer/core/page/scrolling/sticky_position_scrolling_constraints.h"
 #include "third_party/blink/renderer/core/paint/paint_layer_fragment.h"
 #include "third_party/blink/renderer/core/scroll/scrollable_area.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/heap/persistent.h"
 
@@ -399,7 +399,7 @@
   bool HasOverflowControls() const;
 
   bool HasOverlayOverflowControls() const;
-  bool HasNonOverlayOverflowControls() const;
+  bool NeedsScrollCorner() const;
 
   bool HasOverflow() const {
     return HasHorizontalOverflow() || HasVerticalOverflow();
@@ -418,12 +418,10 @@
   LayoutUnit ScrollWidth() const;
   LayoutUnit ScrollHeight() const;
 
-  int VerticalScrollbarWidth(
-      OverlayScrollbarClipBehavior =
-          kIgnorePlatformOverlayScrollbarSize) const override;
-  int HorizontalScrollbarHeight(
-      OverlayScrollbarClipBehavior =
-          kIgnorePlatformOverlayScrollbarSize) const override;
+  int VerticalScrollbarWidth(OverlayScrollbarClipBehavior =
+                                 kIgnoreOverlayScrollbarSize) const override;
+  int HorizontalScrollbarHeight(OverlayScrollbarClipBehavior =
+                                    kIgnoreOverlayScrollbarSize) const override;
 
   DoubleSize AdjustedScrollOffset() const {
     return ToDoubleSize(DoublePoint(ScrollOrigin()) + scroll_offset_);
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 9f1ce1b..036d07f 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1637,19 +1637,6 @@
     context_.current.clip = border_radius_clip;
 }
 
-static PhysicalRect OverflowClipRect(const LayoutBox& box,
-                                     const PhysicalOffset& offset) {
-  // TODO(pdr): We should ignore CSS overlay scrollbars for non-root scrollers
-  // but cannot due to compositing bugs (crbug.com/984167). This special-case is
-  // here instead of LayoutBox::OverflowClipRect because the layout size of the
-  // scrolling content is still affected by overlay scrollbar behavior, just not
-  // the clip.
-  auto behavior = IsA<LayoutView>(box)
-                      ? kIgnorePlatformAndCSSOverlayScrollbarSize
-                      : kIgnorePlatformOverlayScrollbarSize;
-  return box.OverflowClipRect(offset, behavior);
-}
-
 static bool CanOmitOverflowClip(const LayoutObject& object) {
   DCHECK(NeedsOverflowClip(object));
 
@@ -1672,7 +1659,7 @@
 
   // We need OverflowClip for hit-testing if the clip rect excluding overlay
   // scrollbars is different from the normal clip rect.
-  auto clip_rect = OverflowClipRect(*block, PhysicalOffset());
+  auto clip_rect = block->OverflowClipRect(PhysicalOffset());
   auto clip_rect_excluding_overlay_scrollbars = block->OverflowClipRect(
       PhysicalOffset(), kExcludeOverlayScrollbarSizeForHitTesting);
   if (clip_rect != clip_rect_excluding_overlay_scrollbars)
@@ -1735,8 +1722,8 @@
           state.SetClipRect(adjusted_clip_rect, adjusted_clip_rect);
         }
       } else if (object_.IsBox()) {
-        const auto& clip_rect = OverflowClipRect(ToLayoutBox(object_),
-                                                 context_.current.paint_offset);
+        const auto& clip_rect = ToLayoutBox(object_).OverflowClipRect(
+            context_.current.paint_offset);
         state.SetClipRect(FloatRoundedRect(FloatRect(clip_rect)),
                           ToSnappedClipRect(clip_rect));
 
diff --git a/third_party/blink/renderer/core/paint/scrollable_area_painter.cc b/third_party/blink/renderer/core/paint/scrollable_area_painter.cc
index ebc20b3f..87b27b8 100644
--- a/third_party/blink/renderer/core/paint/scrollable_area_painter.cc
+++ b/third_party/blink/renderer/core/paint/scrollable_area_painter.cc
@@ -54,7 +54,7 @@
 
   // Draw a frame around the resizer (1px grey line) if there are any scrollbars
   // present.  Clipping will exclude the right and bottom edges of this frame.
-  if (GetScrollableArea().HasNonOverlayOverflowControls()) {
+  if (GetScrollableArea().NeedsScrollCorner()) {
     GraphicsContextStateSaver state_saver(context);
     context.Clip(visual_rect);
     IntRect larger_corner = visual_rect;
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.cc b/third_party/blink/renderer/core/scroll/scrollable_area.cc
index 8b1def6..b5c9797 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.cc
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.cc
@@ -827,7 +827,7 @@
 
 int ScrollableArea::VerticalScrollbarWidth(
     OverlayScrollbarClipBehavior behavior) const {
-  DCHECK_EQ(behavior, kIgnorePlatformOverlayScrollbarSize);
+  DCHECK_EQ(behavior, kIgnoreOverlayScrollbarSize);
   if (Scrollbar* vertical_bar = VerticalScrollbar())
     return !vertical_bar->IsOverlayScrollbar() ? vertical_bar->Width() : 0;
   return 0;
@@ -835,7 +835,7 @@
 
 int ScrollableArea::HorizontalScrollbarHeight(
     OverlayScrollbarClipBehavior behavior) const {
-  DCHECK_EQ(behavior, kIgnorePlatformOverlayScrollbarSize);
+  DCHECK_EQ(behavior, kIgnoreOverlayScrollbarSize);
   if (Scrollbar* horizontal_bar = HorizontalScrollbar())
     return !horizontal_bar->IsOverlayScrollbar() ? horizontal_bar->Height() : 0;
   return 0;
diff --git a/third_party/blink/renderer/core/scroll/scrollable_area.h b/third_party/blink/renderer/core/scroll/scrollable_area.h
index 578ad26c9..8598821 100644
--- a/third_party/blink/renderer/core/scroll/scrollable_area.h
+++ b/third_party/blink/renderer/core/scroll/scrollable_area.h
@@ -37,7 +37,7 @@
 #include "third_party/blink/renderer/platform/geometry/float_quad.h"
 #include "third_party/blink/renderer/platform/graphics/color.h"
 #include "third_party/blink/renderer/platform/graphics/compositor_element_id.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/heap/handle.h"
 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
@@ -215,6 +215,10 @@
 
   virtual void ContentsResized();
 
+  // This is for platform overlay scrollbars only, doesn't include
+  // overflow:overlay scrollbars. Probably this should be renamed to
+  // HasPlatformOverlayScrollbars() but we don't bother it because
+  // overflow:overlay might be deprecated soon.
   bool HasOverlayScrollbars() const;
   void SetScrollbarOverlayColorTheme(ScrollbarOverlayColorTheme);
   void RecalculateScrollbarOverlayColorTheme(Color);
@@ -465,9 +469,9 @@
   IntSize ExcludeScrollbars(const IntSize&) const;
 
   virtual int VerticalScrollbarWidth(
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize) const;
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize) const;
   virtual int HorizontalScrollbarHeight(
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize) const;
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize) const;
 
   virtual LayoutBox* GetLayoutBox() const { return nullptr; }
 
diff --git a/third_party/blink/renderer/core/scroll/scrollbar.h b/third_party/blink/renderer/core/scroll/scrollbar.h
index d116250..66fd17d 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar.h
+++ b/third_party/blink/renderer/core/scroll/scrollbar.h
@@ -118,7 +118,13 @@
   void Paint(GraphicsContext&, const IntPoint& paint_offset) const;
 
   virtual bool IsSolidColor() const;
+
+  // Returns true if the scrollbar is a overlay scrollbar. This doesn't include
+  // overflow:overlay scrollbars. Probably this should be renamed to
+  // IsPlatformOverlayScrollbar() but we don't bother it because
+  // overflow:overlay might be deprecated soon.
   virtual bool IsOverlayScrollbar() const;
+
   bool ShouldParticipateInHitTesting();
 
   bool IsWindowActive() const;
diff --git a/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc b/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc
index e9332965..25d451ef 100644
--- a/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc
+++ b/third_party/blink/renderer/core/scroll/scrollbar_layer_delegate.cc
@@ -52,8 +52,8 @@
 
 cc::ScrollbarOrientation ScrollbarLayerDelegate::Orientation() const {
   if (scrollbar_->Orientation() == kHorizontalScrollbar)
-    return cc::HORIZONTAL;
-  return cc::VERTICAL;
+    return cc::ScrollbarOrientation::HORIZONTAL;
+  return cc::ScrollbarOrientation::VERTICAL;
 }
 
 bool ScrollbarLayerDelegate::IsLeftSideVerticalScrollbar() const {
@@ -112,7 +112,7 @@
 }
 
 bool ScrollbarLayerDelegate::NeedsRepaintPart(cc::ScrollbarPart part) const {
-  if (part == cc::THUMB)
+  if (part == cc::ScrollbarPart::THUMB)
     return scrollbar_->ThumbNeedsRepaint();
   return scrollbar_->TrackNeedsRepaint();
 }
@@ -161,11 +161,11 @@
   ScopedScrollbarPainter painter(*canvas, device_scale_factor_);
   // The canvas coordinate space is relative to the part's origin.
   switch (part) {
-    case cc::THUMB:
+    case cc::ScrollbarPart::THUMB:
       theme.PaintThumb(painter.Context(), *scrollbar_, IntRect(rect));
       scrollbar_->ClearThumbNeedsRepaint();
       break;
-    case cc::TRACK_BUTTONS_TICKMARKS: {
+    case cc::ScrollbarPart::TRACK_BUTTONS_TICKMARKS: {
       DCHECK_EQ(IntSize(rect.size()), scrollbar_->FrameRect().Size());
       IntPoint offset(IntPoint(rect.origin()) -
                       scrollbar_->FrameRect().Location());
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
index d1b341a..5168f1f1 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.cc
@@ -164,7 +164,8 @@
 
 void FakeLocalFrameHost::FocusedElementChanged(
     bool is_editable_element,
-    const gfx::Rect& bounds_in_frame_widget) {}
+    const gfx::Rect& bounds_in_frame_widget,
+    blink::mojom::FocusType focus_type) {}
 
 void FakeLocalFrameHost::ShowPopupMenu(
     mojo::PendingRemote<mojom::blink::PopupMenuClient> popup_client,
diff --git a/third_party/blink/renderer/core/testing/fake_local_frame_host.h b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
index b10f036b..8eaaaaa3 100644
--- a/third_party/blink/renderer/core/testing/fake_local_frame_host.h
+++ b/third_party/blink/renderer/core/testing/fake_local_frame_host.h
@@ -98,7 +98,8 @@
       WTF::Vector<blink::mojom::blink::FaviconURLPtr> favicon_urls) override;
   void DownloadURL(mojom::blink::DownloadURLParamsPtr params) override;
   void FocusedElementChanged(bool is_editable_element,
-                             const gfx::Rect& bounds_in_frame_widget) override;
+                             const gfx::Rect& bounds_in_frame_widget,
+                             blink::mojom::FocusType focus_type) override;
   void ShowPopupMenu(
       mojo::PendingRemote<mojom::blink::PopupMenuClient> popup_client,
       const gfx::Rect& bounds,
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
index ac228be3..9b40356 100644
--- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
+++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
@@ -242,6 +242,7 @@
   if (!EnsureService())
     return;
 
+  DCHECK(picture_in_picture_session_.is_bound());
   picture_in_picture_session_->Stop(
       WTF::Bind(&PictureInPictureControllerImpl::OnExitedPictureInPicture,
                 WrapPersistent(this), WrapPersistent(resolver)));
diff --git a/third_party/blink/renderer/modules/webcodecs/decoder_template.cc b/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
index 9762fa5..47772f51 100644
--- a/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
+++ b/third_party/blink/renderer/modules/webcodecs/decoder_template.cc
@@ -18,7 +18,7 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_chunk.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
diff --git a/third_party/blink/renderer/modules/webcodecs/encoded_video_config.idl b/third_party/blink/renderer/modules/webcodecs/encoded_video_config.idl
deleted file mode 100644
index 7af04a6..0000000
--- a/third_party/blink/renderer/modules/webcodecs/encoded_video_config.idl
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2020 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-// https://github.com/WICG/web-codecs
-
-dictionary EncodedVideoConfig {
-  // Codec string, eg. "avc1.42001e" or "vp09.00.10.08".
-  // TODO(sandersd): Accept "avc1" if |description| is provided?
-  required DOMString codec;
-
-  // avcC, vpcC, or etc.
-  BufferSource description;
-
-  // If provided, these override in-band configuration.
-  double sampleAspect;
-  // TODO(sandersd): color space.
-
-  // TODO(sandersd): Constraints (sequential access) and requirements
-  // (imagebitmap, colorspace conversion).
-};
diff --git a/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc b/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc
index b958cbd..c9c72b2 100644
--- a/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc
+++ b/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.cc
@@ -10,7 +10,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_image_bitmap_options.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_init.h"
@@ -45,9 +45,9 @@
   return ScriptValue();
 }
 
-EncodedVideoConfig* MakeVideoDecoderConfig(
+VideoDecoderConfig* MakeVideoDecoderConfig(
     const wc_fuzzer::ConfigureVideoDecoder& proto) {
-  auto* config = EncodedVideoConfig::Create();
+  auto* config = VideoDecoderConfig::Create();
   config->setCodec(proto.codec().c_str());
   DOMArrayBuffer* data_copy = DOMArrayBuffer::Create(
       proto.description().data(), proto.description().size());
diff --git a/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.h b/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.h
index ef538132b..60780639 100644
--- a/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.h
+++ b/third_party/blink/renderer/modules/webcodecs/fuzzer_utils.h
@@ -8,7 +8,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
 #include "third_party/blink/renderer/bindings/core/v8/script_value.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_audio_config.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_encode_options.h"
@@ -37,7 +37,7 @@
   const std::string name_;
 };
 
-EncodedVideoConfig* MakeVideoDecoderConfig(
+VideoDecoderConfig* MakeVideoDecoderConfig(
     const wc_fuzzer::ConfigureVideoDecoder& proto);
 
 EncodedAudioConfig* MakeAudioDecoderConfig(
diff --git a/third_party/blink/renderer/modules/webcodecs/idls.gni b/third_party/blink/renderer/modules/webcodecs/idls.gni
index b97d8fa..0dbbb21 100644
--- a/third_party/blink/renderer/modules/webcodecs/idls.gni
+++ b/third_party/blink/renderer/modules/webcodecs/idls.gni
@@ -24,7 +24,6 @@
 
 modules_dictionary_idl_files = [
   "audio_decoder_init.idl",
-  "encoded_video_config.idl",
   "encoded_video_chunk_init.idl",
   "encoded_audio_config.idl",
   "encoded_audio_chunk_init.idl",
@@ -32,6 +31,7 @@
   "image_frame.idl",
   "image_track.idl",
   "plane_init.idl",
+  "video_decoder_config.idl",
   "video_decoder_init.idl",
   "video_encoder_config.idl",
   "video_encoder_init.idl",
@@ -41,7 +41,10 @@
   "video_track_writer_parameters.idl",
 ]
 
-modules_typedefs_enums_only_idl_files = [ "codec_state.idl" ]
+modules_typedefs_enums_only_idl_files = [
+  "codec_state.idl",
+  "video_pixel_format.idl",
+]
 
 # IDL files that either define partial interfaces or target (right side of)
 # `includes`.
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder.cc
index 96ba5052..aa951212 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder.cc
@@ -16,7 +16,7 @@
 #include "media/media_buildflags.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_chunk.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/core/dom/dom_exception.h"
 #include "third_party/blink/renderer/modules/webcodecs/codec_config_eval.h"
 #include "third_party/blink/renderer/modules/webcodecs/encoded_video_chunk.h"
@@ -150,9 +150,9 @@
   }
 #endif  // BUILDFLAG(USE_PROPRIETARY_CODECS)
 
-  // TODO(sandersd): Either remove sizes from VideoDecoderConfig (replace with
-  // sample aspect) or parse the AvcC here to get the actual size.
-  // For the moment, hard-code 720p to prefer hardware decoders.
+  // TODO(sandersd): Use size information from the VideoDecoderConfig when it is
+  // provided, and figure out how to combine it with the avcC. Update fuzzer to
+  // match.
   gfx::Size size = gfx::Size(1280, 720);
 
   out_media_config->Initialize(codec, profile,
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder.h b/third_party/blink/renderer/modules/webcodecs/video_decoder.h
index 3213109..11b19b99 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder.h
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder.h
@@ -44,8 +44,8 @@
 namespace blink {
 
 class EncodedVideoChunk;
-class EncodedVideoConfig;
 class ExceptionState;
+class VideoDecoderConfig;
 class VideoDecoderInit;
 class VideoFrame;
 class V8VideoFrameOutputCallback;
@@ -57,7 +57,7 @@
   using MediaOutputType = media::VideoFrame;
   using MediaDecoderType = media::VideoDecoder;
   using OutputCallbackType = V8VideoFrameOutputCallback;
-  using ConfigType = EncodedVideoConfig;
+  using ConfigType = VideoDecoderConfig;
   using MediaConfigType = media::VideoDecoderConfig;
   using InputType = EncodedVideoChunk;
 
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder.idl b/third_party/blink/renderer/modules/webcodecs/video_decoder.idl
index cb50995fb..b385993 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder.idl
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder.idl
@@ -39,7 +39,7 @@
   // The next decode request must be for a keyframe.
   //
   // TODO(sandersd): Move the keyframe rule into the bytestream registry.
-  [RaisesException] void configure(EncodedVideoConfig config);
+  [RaisesException] void configure(VideoDecoderConfig config);
 
   // Request decoding of an input chunk.
   //
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_config.idl b/third_party/blink/renderer/modules/webcodecs/video_decoder_config.idl
new file mode 100644
index 0000000..f770f2d
--- /dev/null
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_config.idl
@@ -0,0 +1,35 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://github.com/WICG/web-codecs
+
+dictionary VideoDecoderConfig {
+  // Codec string, eg. "avc1.42001e" or "vp09.00.10.08".
+  // TODO(sandersd): Should we accept "avc1" when |description| is provided?
+  required DOMString codec;
+
+  // avcC, vpcC, or etc.
+  // TODO(sandersd): Define what happens if the parsed description differs from
+  // the metadata below.
+  BufferSource description;
+
+  // Hint about the encoded size of the content.
+  // TODO(sandersd): Draft spec marks these as required.
+  unsigned long codedWidth;
+  unsigned long codedHeight;
+
+  // Hint about the visible region of the content.
+  unsigned long cropLeft;
+  unsigned long cropTop;
+  unsigned long cropWidth;
+  unsigned long cropHeight;
+
+  // Hint about the pixel aspect ratio of the content.
+  unsigned long displayWidth;
+  unsigned long displayHeight;
+
+  // TODO(sandersd): color space.
+  // TODO(sandersd): Constraints (sequential access) and requirements
+  // (imagebitmap, colorspace conversion).
+};
diff --git a/third_party/blink/renderer/modules/webcodecs/video_decoder_fuzzer.cc b/third_party/blink/renderer/modules/webcodecs/video_decoder_fuzzer.cc
index d27c510..73f1b31 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_decoder_fuzzer.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_decoder_fuzzer.cc
@@ -5,7 +5,7 @@
 #include "base/run_loop.h"
 #include "testing/libfuzzer/proto/lpm_interface.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_output_callback.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_web_codecs_error_callback.h"
diff --git a/third_party/blink/renderer/modules/webcodecs/video_encoder_fuzzer.cc b/third_party/blink/renderer/modules/webcodecs/video_encoder_fuzzer.cc
index be28c3d..5b74d84a 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_encoder_fuzzer.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_encoder_fuzzer.cc
@@ -6,7 +6,7 @@
 #include "base/run_loop.h"
 #include "testing/libfuzzer/proto/lpm_interface.h"
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
-#include "third_party/blink/renderer/bindings/modules/v8/v8_encoded_video_config.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_decoder_config.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_init.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_encoder_output_callback.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_web_codecs_error_callback.h"
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.cc b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
index 3c613b1..fd90c0f 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.cc
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.cc
@@ -16,6 +16,7 @@
 #include "media/renderers/video_frame_yuv_converter.h"
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/renderer/bindings/modules/v8/v8_video_frame_init.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_video_pixel_format.h"
 #include "third_party/blink/renderer/core/html/canvas/image_data.h"
 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap.h"
 #include "third_party/blink/renderer/core/imagebitmap/image_bitmap_factories.h"
@@ -184,7 +185,7 @@
 
   switch (local_frame->format()) {
     case media::PIXEL_FORMAT_I420:
-      return "I420";
+      return V8VideoPixelFormat(V8VideoPixelFormat::Enum::kI420);
 
     default:
       NOTREACHED();
diff --git a/third_party/blink/renderer/modules/webcodecs/video_frame.idl b/third_party/blink/renderer/modules/webcodecs/video_frame.idl
index 66dc371..028602b 100644
--- a/third_party/blink/renderer/modules/webcodecs/video_frame.idl
+++ b/third_party/blink/renderer/modules/webcodecs/video_frame.idl
@@ -4,11 +4,6 @@
 
 // https://github.com/WICG/web-codecs
 
-enum VideoPixelFormat {
-    // 4:2:0 subsampled planar YUV
-    "I420"
-};
-
 [
     Exposed=(Window,Worker),
     Serializable,
diff --git a/third_party/blink/renderer/modules/webcodecs/video_pixel_format.idl b/third_party/blink/renderer/modules/webcodecs/video_pixel_format.idl
new file mode 100644
index 0000000..490e6fa
--- /dev/null
+++ b/third_party/blink/renderer/modules/webcodecs/video_pixel_format.idl
@@ -0,0 +1,10 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// https://github.com/WICG/web-codecs
+
+enum VideoPixelFormat {
+    // 4:2:0 subsampled planar YUV
+    "I420"
+};
diff --git a/third_party/blink/renderer/modules/xr/BUILD.gn b/third_party/blink/renderer/modules/xr/BUILD.gn
index e6144d5..7ce764a1 100644
--- a/third_party/blink/renderer/modules/xr/BUILD.gn
+++ b/third_party/blink/renderer/modules/xr/BUILD.gn
@@ -21,6 +21,8 @@
     "xr_canvas_input_provider.h",
     "xr_cube_map.cc",
     "xr_cube_map.h",
+    "xr_depth_information.cc",
+    "xr_depth_information.h",
     "xr_dom_overlay_state.cc",
     "xr_dom_overlay_state.h",
     "xr_frame.cc",
diff --git a/third_party/blink/renderer/modules/xr/idls.gni b/third_party/blink/renderer/modules/xr/idls.gni
index 50897f7d..0346ec6 100644
--- a/third_party/blink/renderer/modules/xr/idls.gni
+++ b/third_party/blink/renderer/modules/xr/idls.gni
@@ -8,6 +8,7 @@
   "xr_anchor.idl",
   "xr_anchor_set.idl",
   "xr_bounded_reference_space.idl",
+  "xr_depth_information.idl",
   "xr_dom_overlay_state.idl",
   "xr_frame.idl",
   "xr_input_source.idl",
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_information.cc b/third_party/blink/renderer/modules/xr/xr_depth_information.cc
new file mode 100644
index 0000000..7a0a14cb
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_depth_information.cc
@@ -0,0 +1,25 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/xr/xr_depth_information.h"
+
+namespace blink {
+
+DOMUint16Array* XRDepthInformation::data() const {
+  return nullptr;
+}
+
+uint32_t XRDepthInformation::width() const {
+  return 0;
+}
+
+uint32_t XRDepthInformation::height() const {
+  return 0;
+}
+
+float XRDepthInformation::getDepth(uint32_t col, uint32_t row) const {
+  return 0.0;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_information.h b/third_party/blink/renderer/modules/xr/xr_depth_information.h
new file mode 100644
index 0000000..954c21c
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_depth_information.h
@@ -0,0 +1,31 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_DEPTH_INFORMATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_DEPTH_INFORMATION_H_
+
+#include "third_party/blink/renderer/core/typed_arrays/dom_typed_array.h"
+#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
+#include "third_party/blink/renderer/platform/wtf/forward.h"
+
+namespace blink {
+
+class XRDepthInformation final : public ScriptWrappable {
+  DEFINE_WRAPPERTYPEINFO();
+
+ public:
+  DOMUint16Array* data() const;
+
+  uint32_t width() const;
+
+  uint32_t height() const;
+
+  float getDepth(uint32_t col, uint32_t row) const;
+
+ private:
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_MODULES_XR_XR_DEPTH_INFORMATION_H_
diff --git a/third_party/blink/renderer/modules/xr/xr_depth_information.idl b/third_party/blink/renderer/modules/xr/xr_depth_information.idl
new file mode 100644
index 0000000..433254d
--- /dev/null
+++ b/third_party/blink/renderer/modules/xr/xr_depth_information.idl
@@ -0,0 +1,16 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+    SecureContext,
+    Exposed=Window,
+    RuntimeEnabled=WebXRDepth
+] interface XRDepthInformation {
+  readonly attribute Uint16Array data;
+
+  readonly attribute unsigned long width;
+  readonly attribute unsigned long height;
+
+  float getDepth(unsigned long column, unsigned long row);
+};
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.cc b/third_party/blink/renderer/modules/xr/xr_frame.cc
index 5b1de602..8e6da76 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.cc
+++ b/third_party/blink/renderer/modules/xr/xr_frame.cc
@@ -128,6 +128,10 @@
   return light_probe->getLightEstimate();
 }
 
+XRDepthInformation* XRFrame::getDepthInformation(XRView* view) const {
+  return nullptr;
+}
+
 // Return an XRPose that has a transform of basespace_from_space, while
 // accounting for the base pose matrix of this frame. If computing a transform
 // isn't possible, return nullptr.
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.h b/third_party/blink/renderer/modules/xr/xr_frame.h
index b19ca82..1e63c4a 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.h
+++ b/third_party/blink/renderer/modules/xr/xr_frame.h
@@ -19,6 +19,7 @@
 
 class ExceptionState;
 class XRAnchorSet;
+class XRDepthInformation;
 class XRHitTestResult;
 class XRHitTestSource;
 class XRInputSource;
@@ -31,6 +32,7 @@
 class XRSpace;
 class XRTransientInputHitTestResult;
 class XRTransientInputHitTestSource;
+class XRView;
 class XRViewerPose;
 class XRWorldInformation;
 
@@ -47,6 +49,7 @@
   XRWorldInformation* worldInformation() const { return world_information_; }
   XRAnchorSet* trackedAnchors() const;
   XRLightEstimate* getLightEstimate(XRLightProbe*, ExceptionState&) const;
+  XRDepthInformation* getDepthInformation(XRView* view) const;
 
   void Trace(Visitor*) const override;
 
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.idl b/third_party/blink/renderer/modules/xr/xr_frame.idl
index 17bc688c4..5a0b55ec5 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.idl
+++ b/third_party/blink/renderer/modules/xr/xr_frame.idl
@@ -29,4 +29,7 @@
 
   [RuntimeEnabled=WebXRLightEstimation, RaisesException]
   XRLightEstimate? getLightEstimate(XRLightProbe lightProbe);
+
+  [RuntimeEnabled=WebXRDepth]
+  XRDepthInformation getDepthInformation(XRView view);
 };
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 08e980ba..7bf42f2bd 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1020,6 +1020,7 @@
     "graphics/mutator_client.h",
     "graphics/offscreen_canvas_placeholder.cc",
     "graphics/offscreen_canvas_placeholder.h",
+    "graphics/overlay_scrollbar_clip_behavior.h",
     "graphics/paint/clip_paint_property_node.cc",
     "graphics/paint/clip_paint_property_node.h",
     "graphics/paint/cull_rect.cc",
@@ -1117,7 +1118,6 @@
     "graphics/replaying_canvas.cc",
     "graphics/replaying_canvas.h",
     "graphics/scoped_interpolation_quality.h",
-    "graphics/scroll_types.h",
     "graphics/scrollbar_theme_settings.cc",
     "graphics/scrollbar_theme_settings.h",
     "graphics/skia/image_pixel_locker.cc",
diff --git a/third_party/blink/renderer/platform/exported/web_runtime_features.cc b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
index becdaed..e688360 100644
--- a/third_party/blink/renderer/platform/exported/web_runtime_features.cc
+++ b/third_party/blink/renderer/platform/exported/web_runtime_features.cc
@@ -471,6 +471,10 @@
   RuntimeEnabledFeatures::SetWebXRCameraAccessEnabled(enable);
 }
 
+void WebRuntimeFeatures::EnableWebXRDepth(bool enable) {
+  RuntimeEnabledFeatures::SetWebXRDepthEnabled(enable);
+}
+
 void WebRuntimeFeatures::EnableWebXRHitTest(bool enable) {
   RuntimeEnabledFeatures::SetWebXRHitTestEnabled(enable);
 }
diff --git a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
index bf3c19f..ce59ac7 100644
--- a/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
+++ b/third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.cc
@@ -518,7 +518,7 @@
   FloatClipRect visual_rect(bounds);
   GeometryMapper::LocalToAncestorVisualRect(
       property_tree_state, PropertyTreeState::Root(), visual_rect,
-      kIgnorePlatformOverlayScrollbarSize, kNonInclusiveIntersect,
+      kIgnoreOverlayScrollbarSize, kNonInclusiveIntersect,
       kExpandVisualRectForAnimation);
   return visual_rect.Rect();
 }
diff --git a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
index 910fc54..51ceeb1 100644
--- a/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
+++ b/third_party/blink/renderer/platform/graphics/dark_mode_color_filter.cc
@@ -60,18 +60,16 @@
   }
 
   SkColor InvertColor(SkColor color) const override {
-    blink::FloatPoint3D rgb = {SkColorGetR(color) / 255.0f,
-                               SkColorGetG(color) / 255.0f,
-                               SkColorGetB(color) / 255.0f};
-    blink::FloatPoint3D lab = transformer_.sRGBToLab(rgb);
-    float invertedL = std::min(110.0f - lab.X(), 100.0f);
-    lab.SetX(invertedL);
+    SkV3 rgb = {SkColorGetR(color) / 255.0f, SkColorGetG(color) / 255.0f,
+                SkColorGetB(color) / 255.0f};
+    SkV3 lab = transformer_.sRGBToLab(rgb);
+    lab.x = std::min(110.0f - lab.x, 100.0f);
     rgb = transformer_.LabToSRGB(lab);
 
     SkColor inverted_color = SkColorSetARGB(
-        SkColorGetA(color), static_cast<unsigned int>(rgb.X() * 255 + 0.5),
-        static_cast<unsigned int>(rgb.Y() * 255 + 0.5),
-        static_cast<unsigned int>(rgb.Z() * 255 + 0.5));
+        SkColorGetA(color), static_cast<unsigned int>(rgb.x * 255 + 0.5),
+        static_cast<unsigned int>(rgb.y * 255 + 0.5),
+        static_cast<unsigned int>(rgb.z * 255 + 0.5));
     return AdjustGray(inverted_color);
   }
 
diff --git a/third_party/blink/renderer/platform/graphics/image_data_buffer.cc b/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
index 9abb2b2..7cffc61 100644
--- a/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/image_data_buffer.cc
@@ -51,30 +51,36 @@
 ImageDataBuffer::ImageDataBuffer(scoped_refptr<StaticBitmapImage> image) {
   if (!image)
     return;
-  retained_image_ = image->PaintImageForCurrentFrame().GetSkImage();
-  if (!retained_image_)
+  PaintImage paint_image = image->PaintImageForCurrentFrame();
+  if (!paint_image || paint_image.IsPaintWorklet())
     return;
+
+  SkImageInfo paint_image_info = paint_image.GetSkImageInfo();
+  if (paint_image_info.isEmpty())
+    return;
+
 #if defined(MEMORY_SANITIZER)
-  // Test if retained_image has an initialized pixmap.
+  // Test if software SKImage has an initialized pixmap.
   SkPixmap pixmap;
-  if (retained_image_->peekPixels(&pixmap))
+  if (!paint_image.IsTextureBacked() &&
+      paint_image.GetSwSkImage()->peekPixels(&pixmap)) {
     MSAN_CHECK_MEM_IS_INITIALIZED(pixmap.addr(), pixmap.computeByteSize());
+  }
 #endif
 
-  if (retained_image_->isTextureBacked() ||
-      retained_image_->isLazyGenerated() ||
-      retained_image_->alphaType() != kUnpremul_SkAlphaType) {
+  if (paint_image.IsTextureBacked() || paint_image.IsLazyGenerated() ||
+      paint_image_info.alphaType() != kUnpremul_SkAlphaType) {
     // Unpremul is handled upfront, using readPixels, which will correctly clamp
     // premul color values that would otherwise cause overflows in the skia
     // encoder unpremul logic.
-    SkColorType colorType = retained_image_->colorType();
+    SkColorType colorType = paint_image.GetColorType();
     if (colorType == kRGBA_8888_SkColorType ||
         colorType == kBGRA_8888_SkColorType)
       colorType = kN32_SkColorType;  // Work around for bug with JPEG encoder
     const SkImageInfo info =
-        SkImageInfo::Make(retained_image_->width(), retained_image_->height(),
-                          retained_image_->colorType(), kUnpremul_SkAlphaType,
-                          retained_image_->refColorSpace());
+        SkImageInfo::Make(paint_image_info.width(), paint_image_info.height(),
+                          paint_image_info.colorType(), kUnpremul_SkAlphaType,
+                          paint_image_info.refColorSpace());
     const size_t rowBytes = info.minRowBytes();
     size_t size = info.computeByteSize(rowBytes);
     if (SkImageInfo::ByteSizeOverflowed(size))
@@ -82,13 +88,15 @@
 
     sk_sp<SkData> data = SkData::MakeUninitialized(size);
     pixmap_ = {info, data->writable_data(), info.minRowBytes()};
-    if (!retained_image_->readPixels(pixmap_, 0, 0)) {
+    if (!paint_image.readPixels(info, pixmap_.writable_addr(), rowBytes, 0,
+                                0)) {
       pixmap_.reset();
       return;
     }
     MSAN_CHECK_MEM_IS_INITIALIZED(pixmap_.addr(), pixmap_.computeByteSize());
     retained_image_ = SkImage::MakeRasterData(info, std::move(data), rowBytes);
   } else {
+    retained_image_ = paint_image.GetSwSkImage();
     if (!retained_image_->peekPixels(&pixmap_))
       return;
     MSAN_CHECK_MEM_IS_INITIALIZED(pixmap_.addr(), pixmap_.computeByteSize());
diff --git a/third_party/blink/renderer/platform/graphics/lab_color_space.h b/third_party/blink/renderer/platform/graphics/lab_color_space.h
index e002057..931c0633 100644
--- a/third_party/blink/renderer/platform/graphics/lab_color_space.h
+++ b/third_party/blink/renderer/platform/graphics/lab_color_space.h
@@ -3,167 +3,176 @@
 
 #include <algorithm>
 #include <cmath>
-#include <initializer_list>
-#include <iterator>
 
-#include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
+#include "base/check.h"
+#include "third_party/skia/include/core/SkM44.h"
 
 // Class to handle color transformation between RGB and CIE L*a*b* color spaces.
 namespace LabColorSpace {
 
-using blink::FloatPoint3D;
-using blink::TransformationMatrix;
-
-static constexpr FloatPoint3D kIlluminantD50 =
-    FloatPoint3D(0.964212f, 1.0f, 0.825188f);
-static constexpr FloatPoint3D kIlluminantD65 =
-    FloatPoint3D(0.95042855f, 1.0f, 1.0889004f);
+static constexpr SkV3 kIlluminantD50 = {0.964212f, 1.0f, 0.825188f};
+static constexpr SkV3 kIlluminantD65 = {0.95042855f, 1.0f, 1.0889004f};
 
 // All matrices here are 3x3 matrices.
-// They are stored in blink::TransformationMatrix which is 4x4 matrix in the
-// following form.
+// They are stored in SkM44 which is 4x4 matrix in the following form.
 // |a b c 0|
 // |d e f 0|
 // |g h i 0|
 // |0 0 0 1|
 
-inline TransformationMatrix mul3x3Diag(const FloatPoint3D& lhs,
-                                       const TransformationMatrix& rhs) {
-  return TransformationMatrix(
-      lhs.X() * rhs.M11(), lhs.Y() * rhs.M12(), lhs.Z() * rhs.M13(), 0.0f,
-      lhs.X() * rhs.M21(), lhs.Y() * rhs.M22(), lhs.Z() * rhs.M23(), 0.0f,
-      lhs.X() * rhs.M31(), lhs.Y() * rhs.M32(), lhs.Z() * rhs.M33(), 0.0f,
-      0.0f, 0.0f, 0.0f, 1.0f);
-}
-
 template <typename T>
-inline constexpr T clamp(T x, T min, T max) {
+inline constexpr T Clamp(T x, T min, T max) {
   return x < min ? min : x > max ? max : x;
 }
 
 // See https://en.wikipedia.org/wiki/Chromatic_adaptation#Von_Kries_transform.
-inline TransformationMatrix chromaticAdaptation(
-    const TransformationMatrix& matrix,
-    const FloatPoint3D& srcWhitePoint,
-    const FloatPoint3D& dstWhitePoint) {
-  FloatPoint3D srcLMS = matrix.MapPoint(srcWhitePoint);
-  FloatPoint3D dstLMS = matrix.MapPoint(dstWhitePoint);
-  // LMS is a diagonal matrix stored as a float[3]
-  FloatPoint3D LMS = {dstLMS.X() / srcLMS.X(), dstLMS.Y() / srcLMS.Y(),
-                      dstLMS.Z() / srcLMS.Z()};
-  return matrix.Inverse() * mul3x3Diag(LMS, matrix);
+inline SkM44 ChromaticAdaptation(const SkM44& matrix,
+                                 const SkV3& src_white_point,
+                                 const SkV3& dst_white_point) {
+  SkV3 src_lms = matrix * src_white_point;
+  SkV3 dst_lms = matrix * dst_white_point;
+  // |lms| is a diagonal matrix stored as a float[3].
+  SkV3 lms = {dst_lms.x / src_lms.x, dst_lms.y / src_lms.y,
+              dst_lms.z / src_lms.z};
+  SkM44 inverse;
+  bool success = matrix.invert(&inverse);
+  DCHECK(success);
+  return inverse * (SkM44::Scale(lms.x, lms.y, lms.z) * matrix);
 }
 
 class sRGBColorSpace {
  public:
-  FloatPoint3D toLinear(const FloatPoint3D& v) const {
-    auto EOTF = [](float u) {
-      return u < 0.04045f
-                 ? clamp(u / 12.92f, .0f, 1.0f)
-                 : clamp(std::pow((u + 0.055f) / 1.055f, 2.4f), .0f, 1.0f);
-    };
-    return {EOTF(v.X()), EOTF(v.Y()), EOTF(v.Z())};
+  sRGBColorSpace() {
+    bool success = transform_.invert(&inverseTransform_);
+    DCHECK(success);
   }
 
-  FloatPoint3D fromLinear(const FloatPoint3D& v) const {
+  SkV3 ToLinear(const SkV3& v) const {
+    auto EOTF = [](float u) {
+      return u < 0.04045f
+                 ? Clamp(u / 12.92f, .0f, 1.0f)
+                 : Clamp(std::pow((u + 0.055f) / 1.055f, 2.4f), .0f, 1.0f);
+    };
+    return {EOTF(v.x), EOTF(v.y), EOTF(v.z)};
+  }
+
+  SkV3 FromLinear(const SkV3& v) const {
     auto OETF = [](float u) {
       return (u < 0.0031308f
-                  ? clamp(12.92 * u, .0, 1.0)
-                  : clamp(1.055 * std::pow(u, 1.0 / 2.4) - 0.055, .0, 1.0));
+                  ? Clamp(12.92 * u, .0, 1.0)
+                  : Clamp(1.055 * std::pow(u, 1.0 / 2.4) - 0.055, .0, 1.0));
     };
-    return {OETF(v.X()), OETF(v.Y()), OETF(v.Z())};
+    return {OETF(v.x), OETF(v.y), OETF(v.z)};
   }
 
   // See https://en.wikipedia.org/wiki/SRGB#The_reverse_transformation.
-  FloatPoint3D toXYZ(const FloatPoint3D& rgb) const {
-    return transform_.MapPoint(toLinear(rgb));
-  }
+  SkV3 ToXYZ(const SkV3& rgb) const { return transform_ * ToLinear(rgb); }
 
   // See
   // https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB).
-  FloatPoint3D fromXYZ(const FloatPoint3D& xyz) const {
-    return fromLinear(inverseTransform_.MapPoint(xyz));
+  SkV3 FromXYZ(const SkV3& xyz) const {
+    return FromLinear(inverseTransform_ * xyz);
   }
 
  private:
-  TransformationMatrix kBradford = TransformationMatrix(
-       0.8951f, -0.7502f,  0.0389f, 0.0f,
-       0.2664f,  1.7135f, -0.0685f, 0.0f,
-      -0.1614f,  0.0367f,  1.0296f, 0.0f,
-       0.0f,     0.0f,     0.0f,    1.0f);
+  SkM44 kBradford = SkM44(0.8951f,
+                          0.2664f,
+                          -0.1614f,
+                          0.0f,
+                          -0.7502f,
+                          1.7135f,
+                          0.0367f,
+                          0.0f,
+                          0.0389f,
+                          0.0685f,
+                          1.0296f,
+                          0.0f,
+                          0.0f,
+                          0.0f,
+                          0.0f,
+                          1.0f);
 
-  TransformationMatrix xyzTransform = TransformationMatrix(
-      0.41238642f, 0.21263677f, 0.019330615f, 0.0f,
-      0.3575915f,  0.715183f,   0.11919712f,  0.0f,
-      0.18045056f, 0.07218022f, 0.95037293f,  0.0f,
-      0.0f,        0.0f,        0.0f,         1.0f);
+  SkM44 xyzTransform = SkM44(0.41238642f,
+                             0.3575915f,
+                             0.18045056f,
+                             0.0f,
+                             0.21263677f,
+                             0.715183f,
+                             0.07218022f,
+                             0.0f,
+                             0.019330615f,
+                             0.11919712f,
+                             0.95037293f,
+                             0.0f,
+                             0.0f,
+                             0.0f,
+                             0.0f,
+                             1.0f);
 
-  TransformationMatrix transform_ =
-      chromaticAdaptation(kBradford, kIlluminantD65, kIlluminantD50) *
+  SkM44 transform_ =
+      ChromaticAdaptation(kBradford, kIlluminantD65, kIlluminantD50) *
       xyzTransform;
-  TransformationMatrix inverseTransform_ = transform_.Inverse();
+  SkM44 inverseTransform_;
 };
 
 class LABColorSpace {
  public:
   // See
   // https://en.wikipedia.org/wiki/CIELAB_color_space#Reverse_transformation.
-  FloatPoint3D fromXYZ(const FloatPoint3D& v) const {
+  SkV3 FromXYZ(const SkV3& v) const {
     auto f = [](float x) {
       return x > kSigma3 ? pow(x, 1.0f / 3.0f)
                          : x / (3 * kSigma2) + 4.0f / 29.0f;
     };
 
-    float fx = f(v.X() / kIlluminantD50.X());
-    float fy = f(v.Y() / kIlluminantD50.Y());
-    float fz = f(v.Z() / kIlluminantD50.Z());
+    float fx = f(v.x / kIlluminantD50.x);
+    float fy = f(v.y / kIlluminantD50.y);
+    float fz = f(v.z / kIlluminantD50.z);
 
     float L = 116.0f * fy - 16.0f;
     float a = 500.0f * (fx - fy);
     float b = 200.0f * (fy - fz);
 
-    return {clamp(L, 0.0f, 100.0f), clamp(a, -128.0f, 128.0f),
-            clamp(b, -128.0f, 128.0f)};
+    return {Clamp(L, 0.0f, 100.0f), Clamp(a, -128.0f, 128.0f),
+            Clamp(b, -128.0f, 128.0f)};
   }
 
   // See
   // https://en.wikipedia.org/wiki/CIELAB_color_space#Forward_transformation.
-  FloatPoint3D toXYZ(const FloatPoint3D& lab) const {
+  SkV3 ToXYZ(const SkV3& lab) const {
     auto invf = [](float x) {
       return x > kSigma ? pow(x, 3) : 3 * kSigma2 * (x - 4.0f / 29.0f);
     };
 
-    FloatPoint3D v = {clamp(lab.X(), 0.0f, 100.0f),
-                      clamp(lab.Y(), -128.0f, 128.0f),
-                      clamp(lab.Z(), -128.0f, 128.0f)};
+    SkV3 v = {Clamp(lab.x, 0.0f, 100.0f), Clamp(lab.y, -128.0f, 128.0f),
+              Clamp(lab.z, -128.0f, 128.0f)};
 
-    return {
-        invf((v.X() + 16.0f) / 116.0f + (v.Y() * 0.002f)) * kIlluminantD50.X(),
-        invf((v.X() + 16.0f) / 116.0f) * kIlluminantD50.Y(),
-        invf((v.X() + 16.0f) / 116.0f - (v.Z() * 0.005f)) * kIlluminantD50.Z()};
+    return {invf((v.x + 16.0f) / 116.0f + (v.y * 0.002f)) * kIlluminantD50.x,
+            invf((v.x + 16.0f) / 116.0f) * kIlluminantD50.y,
+            invf((v.x + 16.0f) / 116.0f - (v.z * 0.005f)) * kIlluminantD50.z};
   }
 
  private:
-  static constexpr float kSigma = 6.0f / 29.0f;
-  static constexpr float kSigma2 = 36.0f / 841.0f;
-  static constexpr float kSigma3 = 216.0f / 24389.0f;
+  static const constexpr float kSigma = 6.0f / 29.0f;
+  static const constexpr float kSigma2 = 36.0f / 841.0f;
+  static const constexpr float kSigma3 = 216.0f / 24389.0f;
 };
 
 class RGBLABTransformer {
  public:
-  FloatPoint3D sRGBToLab(const FloatPoint3D& rgb) const {
-    FloatPoint3D xyz = rcs.toXYZ(rgb);
-    return lcs.fromXYZ(xyz);
+  SkV3 sRGBToLab(const SkV3& rgb) const {
+    SkV3 xyz = rgb_space_.ToXYZ(rgb);
+    return lab_space_.FromXYZ(xyz);
   }
 
-  FloatPoint3D LabToSRGB(const FloatPoint3D& lab) const {
-    FloatPoint3D xyz = lcs.toXYZ(lab);
-    return rcs.fromXYZ(xyz);
+  SkV3 LabToSRGB(const SkV3& lab) const {
+    SkV3 xyz = lab_space_.ToXYZ(lab);
+    return rgb_space_.FromXYZ(xyz);
   }
 
  private:
-  sRGBColorSpace rcs = sRGBColorSpace();
-  LABColorSpace lcs = LABColorSpace();
+  sRGBColorSpace rgb_space_ = sRGBColorSpace();
+  LABColorSpace lab_space_ = LABColorSpace();
 };
 
 }  // namespace LabColorSpace
diff --git a/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc b/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc
index cacdb7ee..3c1af5c 100644
--- a/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc
+++ b/third_party/blink/renderer/platform/graphics/lab_color_space_test.cc
@@ -3,21 +3,16 @@
 
 namespace LabColorSpace {
 
-using blink::FloatPoint3D;
-
-static constexpr FloatPoint3D rgbReferenceWhite =
-    FloatPoint3D(1.0f, 1.0f, 1.0f);
-static constexpr FloatPoint3D labReferenceWhite =
-    FloatPoint3D(100.0f, 0.0f, 0.0f);
+static constexpr SkV3 rgbReferenceWhite = {1.0f, 1.0f, 1.0f};
+static constexpr SkV3 labReferenceWhite = {100.0f, 0.0f, 0.0f};
 static constexpr float epsilon = 0.0001;
 
 class LabColorSpaceTest : public testing::Test {
  public:
-  void AssertColorsEqual(const FloatPoint3D& color1,
-                         const FloatPoint3D& color2) {
-    EXPECT_NEAR(color1.X(), color2.X(), epsilon);
-    EXPECT_NEAR(color1.Y(), color2.Y(), epsilon);
-    EXPECT_NEAR(color1.Z(), color2.Z(), epsilon);
+  void AssertColorsEqual(const SkV3& color1, const SkV3& color2) {
+    EXPECT_NEAR(color1.x, color2.x, epsilon);
+    EXPECT_NEAR(color1.y, color2.y, epsilon);
+    EXPECT_NEAR(color1.z, color2.z, epsilon);
   }
 };
 
@@ -25,10 +20,10 @@
   sRGBColorSpace colorSpace = sRGBColorSpace();
 
   // Check whether white transformation is correct
-  FloatPoint3D xyzWhite = colorSpace.toXYZ(rgbReferenceWhite);
+  SkV3 xyzWhite = colorSpace.ToXYZ(rgbReferenceWhite);
   AssertColorsEqual(xyzWhite, kIlluminantD50);
 
-  FloatPoint3D rgbWhite = colorSpace.fromXYZ(kIlluminantD50);
+  SkV3 rgbWhite = colorSpace.FromXYZ(kIlluminantD50);
   AssertColorsEqual(rgbWhite, rgbReferenceWhite);
 
   // Check whether transforming sRGB to XYZ and back gives the same RGB values
@@ -36,9 +31,9 @@
   for (unsigned r = 0; r <= 255; r += 40) {
     for (unsigned g = 0; r <= 255; r += 50) {
       for (unsigned b = 0; r <= 255; r += 60) {
-        FloatPoint3D rgb = FloatPoint3D(r / 255.0f, g / 255.0f, b / 255.0f);
-        FloatPoint3D xyz = colorSpace.toXYZ(rgb);
-        AssertColorsEqual(rgb, colorSpace.fromXYZ(xyz));
+        SkV3 rgb = {r / 255.0f, g / 255.0f, b / 255.0f};
+        SkV3 xyz = colorSpace.ToXYZ(rgb);
+        AssertColorsEqual(rgb, colorSpace.FromXYZ(xyz));
       }
     }
   }
@@ -48,10 +43,10 @@
   RGBLABTransformer transformer = RGBLABTransformer();
 
   // Check whether white transformation is correct
-  FloatPoint3D labWhite = transformer.sRGBToLab(rgbReferenceWhite);
+  SkV3 labWhite = transformer.sRGBToLab(rgbReferenceWhite);
   AssertColorsEqual(labWhite, labReferenceWhite);
 
-  FloatPoint3D rgbWhite = transformer.LabToSRGB(labReferenceWhite);
+  SkV3 rgbWhite = transformer.LabToSRGB(labReferenceWhite);
   AssertColorsEqual(rgbWhite, rgbReferenceWhite);
 
   // Check whether transforming sRGB to Lab and back gives the same RGB values
@@ -59,8 +54,8 @@
   for (unsigned r = 0; r <= 255; r += 40) {
     for (unsigned g = 0; r <= 255; r += 50) {
       for (unsigned b = 0; r <= 255; r += 60) {
-        FloatPoint3D rgb = FloatPoint3D(r / 255.0f, g / 255.0f, b / 255.0f);
-        FloatPoint3D lab = transformer.sRGBToLab(rgb);
+        SkV3 rgb = {r / 255.0f, g / 255.0f, b / 255.0f};
+        SkV3 lab = transformer.sRGBToLab(rgb);
         AssertColorsEqual(rgb, transformer.LabToSRGB(lab));
       }
     }
diff --git a/third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h b/third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h
new file mode 100644
index 0000000..ecbfe890
--- /dev/null
+++ b/third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_OVERLAY_SCROLLBAR_CLIP_BEHAVIOR_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_OVERLAY_SCROLLBAR_CLIP_BEHAVIOR_H_
+
+namespace blink {
+
+enum OverlayScrollbarClipBehavior {
+  kIgnoreOverlayScrollbarSize,
+  kExcludeOverlayScrollbarSizeForHitTesting
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_OVERLAY_SCROLLBAR_CLIP_BEHAVIOR_H_
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
index 6a831abb..17b90c3 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper.h
@@ -6,9 +6,9 @@
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_GEOMETRY_MAPPER_H_
 
 #include "base/optional.h"
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/graphics/paint/float_clip_rect.h"
 #include "third_party/blink/renderer/platform/graphics/paint/property_tree_state.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
 #include "third_party/blink/renderer/platform/transforms/transformation_matrix.h"
 #include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
 #include "third_party/blink/renderer/platform/wtf/hash_map.h"
@@ -211,15 +211,14 @@
   static FloatClipRect LocalToAncestorClipRect(
       const PropertyTreeStateOrAlias& local_state,
       const PropertyTreeStateOrAlias& ancestor_state,
-      OverlayScrollbarClipBehavior behavior =
-          kIgnorePlatformOverlayScrollbarSize) {
+      OverlayScrollbarClipBehavior behavior = kIgnoreOverlayScrollbarSize) {
     return LocalToAncestorClipRect(local_state.Unalias(),
                                    ancestor_state.Unalias(), behavior);
   }
   static FloatClipRect LocalToAncestorClipRect(
       const PropertyTreeState& local_state,
       const PropertyTreeState& ancestor_state,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize);
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize);
 
   // Maps from a rect in |local_state| to its visual rect in |ancestor_state|.
   // If there is no effect node between |local_state| (included) and
@@ -260,7 +259,7 @@
       const PropertyTreeStateOrAlias& local_state,
       const PropertyTreeStateOrAlias& ancestor_state,
       FloatClipRect& mapping_rect,
-      OverlayScrollbarClipBehavior clip = kIgnorePlatformOverlayScrollbarSize,
+      OverlayScrollbarClipBehavior clip = kIgnoreOverlayScrollbarSize,
       InclusiveIntersectOrNot intersect = kNonInclusiveIntersect,
       ExpandVisualRectForAnimationOrNot animation =
           kDontExpandVisualRectForAnimation) {
@@ -272,7 +271,7 @@
       const PropertyTreeState& local_state,
       const PropertyTreeState& ancestor_state,
       FloatClipRect& mapping_rect,
-      OverlayScrollbarClipBehavior = kIgnorePlatformOverlayScrollbarSize,
+      OverlayScrollbarClipBehavior = kIgnoreOverlayScrollbarSize,
       InclusiveIntersectOrNot = kNonInclusiveIntersect,
       ExpandVisualRectForAnimationOrNot = kDontExpandVisualRectForAnimation);
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h
index 820475a..daebdde0 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_clip_cache.h
@@ -5,8 +5,8 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_GEOMETRY_MAPPER_CLIP_CACHE_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_PAINT_GEOMETRY_MAPPER_CLIP_CACHE_H_
 
+#include "third_party/blink/renderer/platform/graphics/overlay_scrollbar_clip_behavior.h"
 #include "third_party/blink/renderer/platform/graphics/paint/float_clip_rect.h"
-#include "third_party/blink/renderer/platform/graphics/scroll_types.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/vector.h"
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
index da4cb060..00df6010 100644
--- a/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/geometry_mapper_test.cc
@@ -25,8 +25,7 @@
       const PropertyTreeState& ancestor_property_tree_state) {
     GeometryMapperClipCache::ClipAndTransform clip_and_transform(
         &ancestor_property_tree_state.Clip(),
-        &ancestor_property_tree_state.Transform(),
-        kIgnorePlatformOverlayScrollbarSize);
+        &ancestor_property_tree_state.Transform(), kIgnoreOverlayScrollbarSize);
     return descendant_clip.GetClipCache().GetCachedClip(clip_and_transform);
   }
 
@@ -36,9 +35,8 @@
       FloatClipRect& mapping_rect,
       bool& success) {
     GeometryMapper::LocalToAncestorVisualRectInternal(
-        local_state, ancestor_state, mapping_rect,
-        kIgnorePlatformOverlayScrollbarSize, kNonInclusiveIntersect,
-        kDontExpandVisualRectForAnimation, success);
+        local_state, ancestor_state, mapping_rect, kIgnoreOverlayScrollbarSize,
+        kNonInclusiveIntersect, kDontExpandVisualRectForAnimation, success);
   }
 
   void CheckMappings();
@@ -95,7 +93,7 @@
   actual_visual_rect = FloatClipRect(input_rect);
   GeometryMapper::LocalToAncestorVisualRect(
       local_state, ancestor_state, actual_visual_rect,
-      kIgnorePlatformOverlayScrollbarSize, kNonInclusiveIntersect,
+      kIgnoreOverlayScrollbarSize, kNonInclusiveIntersect,
       kExpandVisualRectForAnimation);
   EXPECT_CLIP_RECT_EQ(expected_visual_rect_expanded_for_animation
                           ? *expected_visual_rect_expanded_for_animation
@@ -382,9 +380,9 @@
   // Check that not passing kExcludeOverlayScrollbarSizeForHitTesting gives
   // a different result.
   actual_visual_rect = FloatClipRect(input_rect);
-  GeometryMapper::LocalToAncestorVisualRect(
-      local_state, ancestor_state, actual_visual_rect,
-      kIgnorePlatformOverlayScrollbarSize);
+  GeometryMapper::LocalToAncestorVisualRect(local_state, ancestor_state,
+                                            actual_visual_rect,
+                                            kIgnoreOverlayScrollbarSize);
   EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(10, 10, 50, 50)),
                       actual_visual_rect);
 
@@ -396,7 +394,7 @@
   // Check that not passing kExcludeOverlayScrollbarSizeForHitTesting gives
   // a different result.
   actual_clip_rect = GeometryMapper::LocalToAncestorClipRect(
-      local_state, ancestor_state, kIgnorePlatformOverlayScrollbarSize);
+      local_state, ancestor_state, kIgnoreOverlayScrollbarSize);
   EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(10, 10, 50, 50)),
                       actual_clip_rect);
 }
@@ -408,7 +406,7 @@
   FloatClipRect actual_clip_rect(FloatRect(60, 10, 10, 10));
   GeometryMapper::LocalToAncestorVisualRect(
       local_state, ancestor_state, actual_clip_rect,
-      kIgnorePlatformOverlayScrollbarSize, kInclusiveIntersect);
+      kIgnoreOverlayScrollbarSize, kInclusiveIntersect);
   EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect(60, 10, 0, 10)),
                       actual_clip_rect);
 
@@ -417,7 +415,7 @@
   actual_clip_rect.SetRect(FloatRect(60, 10, 10, 10));
   GeometryMapper::LocalToAncestorVisualRect(
       local_state, ancestor_state, actual_clip_rect,
-      kIgnorePlatformOverlayScrollbarSize, kNonInclusiveIntersect);
+      kIgnoreOverlayScrollbarSize, kNonInclusiveIntersect);
   EXPECT_CLIP_RECT_EQ(FloatClipRect(FloatRect()), actual_clip_rect);
 }
 
@@ -446,7 +444,7 @@
   FloatClipRect actual_clip_rect(FloatRect(10, 10, 10, 0));
   auto intersects = GeometryMapper::LocalToAncestorVisualRect(
       local_state, ancestor_state, actual_clip_rect,
-      kIgnorePlatformOverlayScrollbarSize, kInclusiveIntersect);
+      kIgnoreOverlayScrollbarSize, kInclusiveIntersect);
 
   EXPECT_TRUE(actual_clip_rect.Rect().IsEmpty());
   EXPECT_TRUE(intersects);
diff --git a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
index 129b5446..2bc052e 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item.cc
@@ -38,8 +38,9 @@
 
 sk_sp<const PaintRecord> ScrollbarDisplayItem::Paint() const {
   if (record_) {
-    DCHECK(!scrollbar_->NeedsRepaintPart(cc::TRACK_BUTTONS_TICKMARKS));
-    DCHECK(!scrollbar_->NeedsRepaintPart(cc::THUMB));
+    DCHECK(!scrollbar_->NeedsRepaintPart(
+        cc::ScrollbarPart::TRACK_BUTTONS_TICKMARKS));
+    DCHECK(!scrollbar_->NeedsRepaintPart(cc::ScrollbarPart::THUMB));
     return record_;
   }
 
@@ -47,10 +48,11 @@
   const IntRect& rect = VisualRect();
   recorder.beginRecording(rect);
   auto* canvas = recorder.getRecordingCanvas();
-  scrollbar_->PaintPart(canvas, cc::TRACK_BUTTONS_TICKMARKS, rect);
+  scrollbar_->PaintPart(canvas, cc::ScrollbarPart::TRACK_BUTTONS_TICKMARKS,
+                        rect);
   gfx::Rect thumb_rect = scrollbar_->ThumbRect();
   thumb_rect.Offset(rect.X(), rect.Y());
-  scrollbar_->PaintPart(canvas, cc::THUMB, thumb_rect);
+  scrollbar_->PaintPart(canvas, cc::ScrollbarPart::THUMB, thumb_rect);
 
   record_ = recorder.finishRecordingAsPicture();
   return record_;
@@ -76,8 +78,8 @@
       gfx::Vector2dF(FloatPoint(VisualRect().Location())));
   layer->SetBounds(gfx::Size(VisualRect().Size()));
 
-  if (scrollbar_->NeedsRepaintPart(cc::THUMB) ||
-      scrollbar_->NeedsRepaintPart(cc::TRACK_BUTTONS_TICKMARKS))
+  if (scrollbar_->NeedsRepaintPart(cc::ScrollbarPart::THUMB) ||
+      scrollbar_->NeedsRepaintPart(cc::ScrollbarPart::TRACK_BUTTONS_TICKMARKS))
     layer->SetNeedsDisplay();
   return layer;
 }
diff --git a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item_test.cc b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item_test.cc
index c3b9e47..3a98a9b 100644
--- a/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item_test.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/scrollbar_display_item_test.cc
@@ -17,7 +17,7 @@
 
 CompositorElementId ScrollbarElementId(const cc::Scrollbar& scrollbar) {
   return CompositorElementIdFromUniqueObjectId(
-      13579, scrollbar.Orientation() == cc::HORIZONTAL
+      13579, scrollbar.Orientation() == cc::ScrollbarOrientation::HORIZONTAL
                  ? CompositorElementIdNamespace::kHorizontalScrollbar
                  : CompositorElementIdNamespace::kVerticalScrollbar);
 }
@@ -38,7 +38,7 @@
 
 TEST(ScrollbarDisplayItemTest, HorizontalSolidColorScrollbar) {
   auto scrollbar = base::MakeRefCounted<cc::FakeScrollbar>();
-  scrollbar->set_orientation(cc::HORIZONTAL);
+  scrollbar->set_orientation(cc::ScrollbarOrientation::HORIZONTAL);
   scrollbar->set_is_solid_color(true);
   scrollbar->set_is_overlay(true);
   scrollbar->set_track_rect(gfx::Rect(2, 90, 96, 10));
@@ -58,7 +58,8 @@
 
   auto* scrollbar_layer =
       static_cast<cc::SolidColorScrollbarLayer*>(layer.get());
-  EXPECT_EQ(cc::HORIZONTAL, scrollbar_layer->orientation());
+  EXPECT_EQ(cc::ScrollbarOrientation::HORIZONTAL,
+            scrollbar_layer->orientation());
   EXPECT_EQ(7, scrollbar_layer->thumb_thickness());
   EXPECT_EQ(2, scrollbar_layer->track_start());
   EXPECT_EQ(element_id, scrollbar_layer->element_id());
@@ -69,7 +70,7 @@
 
 TEST(ScrollbarDisplayItemTest, VerticalSolidColorScrollbar) {
   auto scrollbar = base::MakeRefCounted<cc::FakeScrollbar>();
-  scrollbar->set_orientation(cc::VERTICAL);
+  scrollbar->set_orientation(cc::ScrollbarOrientation::VERTICAL);
   scrollbar->set_is_solid_color(true);
   scrollbar->set_is_overlay(true);
   scrollbar->set_track_rect(gfx::Rect(90, 2, 10, 96));
@@ -89,7 +90,7 @@
 
   auto* scrollbar_layer =
       static_cast<cc::SolidColorScrollbarLayer*>(layer.get());
-  EXPECT_EQ(cc::VERTICAL, scrollbar_layer->orientation());
+  EXPECT_EQ(cc::ScrollbarOrientation::VERTICAL, scrollbar_layer->orientation());
   EXPECT_EQ(7, scrollbar_layer->thumb_thickness());
   EXPECT_EQ(2, scrollbar_layer->track_start());
   EXPECT_EQ(element_id, scrollbar_layer->element_id());
diff --git a/third_party/blink/renderer/platform/graphics/scroll_types.h b/third_party/blink/renderer/platform/graphics/scroll_types.h
deleted file mode 100644
index 1f00a31..0000000
--- a/third_party/blink/renderer/platform/graphics/scroll_types.h
+++ /dev/null
@@ -1,21 +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 THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_SCROLL_TYPES_H_
-#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_SCROLL_TYPES_H_
-
-namespace blink {
-
-// Platform overlay scrollbars are controlled and painted by the operating
-// system (e.g., OSX and Android).  CSS overlay scrollbars are created by
-// setting overflow:overlay, and they are painted by chromium.
-enum OverlayScrollbarClipBehavior {
-  kIgnorePlatformOverlayScrollbarSize,
-  kIgnorePlatformAndCSSOverlayScrollbarSize,
-  kExcludeOverlayScrollbarSizeForHitTesting
-};
-
-}  // namespace blink
-
-#endif
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index 357a201..cd061b9 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -2124,6 +2124,11 @@
       status: "experimental",
     },
     {
+      name: "WebXRDepth",
+      depends_on: ["WebXRARModule"],
+      status: "experimental",
+    },
+    {
       name: "WebXRHitTest",
       depends_on: ["WebXRARModule"],
       status: "stable",
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/compositor_priority_experiments.cc b/third_party/blink/renderer/platform/scheduler/main_thread/compositor_priority_experiments.cc
index f755622..8c5b26f 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/compositor_priority_experiments.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/compositor_priority_experiments.cc
@@ -112,6 +112,7 @@
     case Experiment::kVeryHighPriorityForCompositingAlways:
       return;
     case Experiment::kVeryHighPriorityForCompositingWhenFast:
+      scheduler_->OnCompositorPriorityExperimentUpdateCompositorPriority();
       return;
     case Experiment::kVeryHighPriorityForCompositingAlternating:
       // Deprioritize the compositor if it has just run a task. Prioritize the
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
index 7c9b16f5..ddec847 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.cc
@@ -446,7 +446,11 @@
           &main_thread_scheduler_impl->tracing_controller_,
           YesNoStateToString),
       compositor_priority_experiments(main_thread_scheduler_impl),
-      main_thread_compositing_is_fast(false) {}
+      main_thread_compositing_is_fast(false),
+      compositor_priority(TaskQueue::QueuePriority::kNormalPriority,
+                          "Scheduler.CompositorPriority",
+                          &main_thread_scheduler_impl->tracing_controller_,
+                          TaskQueue::PriorityToString) {}
 
 MainThreadSchedulerImpl::MainThreadOnly::~MainThreadOnly() = default;
 
@@ -1506,8 +1510,6 @@
 
   new_policy.should_disable_throttling() = main_thread_only().use_virtual_time;
 
-  new_policy.compositor_priority() = ComputeCompositorPriority();
-
   new_policy.find_in_page_priority() =
       find_in_page_budget_pool_controller_->CurrentTaskPriority();
 
@@ -1547,6 +1549,8 @@
   Policy old_policy = main_thread_only().current_policy;
   main_thread_only().current_policy = new_policy;
 
+  UpdateCompositorTaskQueuePriority();
+
   UpdateStateForAllTaskQueues(old_policy);
 }
 
@@ -2083,8 +2087,6 @@
       should_disable_throttling_(false),
       frozen_when_backgrounded_(false),
       should_prioritize_loading_with_compositing_(false),
-      compositor_priority_(
-          base::sequence_manager::TaskQueue::QueuePriority::kNormalPriority),
       find_in_page_priority_(FindInPageBudgetPoolController::
                                  kFindInPageBudgetNotExhaustedPriority),
       use_case_(UseCase::kNone) {}
@@ -2104,8 +2106,6 @@
   state->EndDictionary();
 
   state->SetString("rail_mode", RAILModeToString(rail_mode()));
-  state->SetString("compositor_priority",
-                   TaskQueue::PriorityToString(compositor_priority()));
   state->SetString("use_case", UseCaseToString(use_case()));
 
   state->SetBoolean("should_disable_throttling", should_disable_throttling());
@@ -2551,8 +2551,7 @@
   RecordTaskUkm(queue.get(), task, *task_timing);
 
   main_thread_only().compositor_priority_experiments.OnTaskCompleted(
-      queue.get(), main_thread_only().current_policy.compositor_priority(),
-      task_timing);
+      queue.get(), main_thread_only().compositor_priority, task_timing);
 
   find_in_page_budget_pool_controller_->OnTaskCompleted(queue.get(),
                                                         task_timing);
@@ -2671,7 +2670,7 @@
 
   if (task_queue->GetPrioritisationType() ==
       MainThreadTaskQueue::QueueTraits::PrioritisationType::kCompositor) {
-    return main_thread_only().current_policy.compositor_priority();
+    return main_thread_only().compositor_priority;
   }
 
   // Default priority.
@@ -2735,8 +2734,6 @@
     Policy old_policy) const {
   return old_policy.use_case() !=
              main_thread_only().current_policy.use_case() ||
-         old_policy.compositor_priority() !=
-             main_thread_only().current_policy.compositor_priority() ||
          old_policy.find_in_page_priority() !=
              main_thread_only().current_policy.find_in_page_priority();
 }
@@ -2758,12 +2755,12 @@
   }
   main_thread_only().prioritize_compositing_after_input =
       prioritize_compositing_after_input;
-  UpdateCompositorPolicy();
+  UpdateCompositorTaskQueuePriority();
 }
 
 void MainThreadSchedulerImpl::
     OnCompositorPriorityExperimentUpdateCompositorPriority() {
-  UpdateCompositorPolicy();
+  UpdateCompositorTaskQueuePriority();
 }
 
 TaskQueue::QueuePriority MainThreadSchedulerImpl::ComputeCompositorPriority()
@@ -2788,9 +2785,8 @@
   return TaskQueue::QueuePriority::kNormalPriority;
 }
 
-void MainThreadSchedulerImpl::UpdateCompositorPolicy() {
-  main_thread_only().current_policy.compositor_priority() =
-      ComputeCompositorPriority();
+void MainThreadSchedulerImpl::UpdateCompositorTaskQueuePriority() {
+  main_thread_only().compositor_priority = ComputeCompositorPriority();
   CompositorTaskQueue()->SetQueuePriority(
       ComputePriority(CompositorTaskQueue().get()));
 }
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
index e15972a5..757fa17c 100644
--- a/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/main_thread_scheduler_impl.h
@@ -405,7 +405,7 @@
   base::WeakPtr<MainThreadSchedulerImpl> GetWeakPtr();
 
   base::sequence_manager::TaskQueue::QueuePriority compositor_priority() const {
-    return main_thread_only().current_policy.compositor_priority();
+    return main_thread_only().compositor_priority;
   }
 
   bool should_prioritize_loading_with_compositing() const {
@@ -565,14 +565,6 @@
       return should_freeze_compositor_task_queue_;
     }
 
-    base::sequence_manager::TaskQueue::QueuePriority& compositor_priority() {
-      return compositor_priority_;
-    }
-    base::sequence_manager::TaskQueue::QueuePriority compositor_priority()
-        const {
-      return compositor_priority_;
-    }
-
     base::sequence_manager::TaskQueue::QueuePriority& find_in_page_priority() {
       return find_in_page_priority_;
     }
@@ -592,7 +584,6 @@
                  other.should_prioritize_loading_with_compositing_ &&
              should_freeze_compositor_task_queue_ ==
                  other.should_freeze_compositor_task_queue_ &&
-             compositor_priority_ == other.compositor_priority_ &&
              find_in_page_priority_ == other.find_in_page_priority_ &&
              use_case_ == other.use_case_;
     }
@@ -606,10 +597,6 @@
     bool should_prioritize_loading_with_compositing_;
     bool should_freeze_compositor_task_queue_{false};
 
-    // Priority of task queues belonging to the compositor class (Check
-    // MainThread::QueueClass).
-    base::sequence_manager::TaskQueue::QueuePriority compositor_priority_;
-
     base::sequence_manager::TaskQueue::QueuePriority find_in_page_priority_;
 
     UseCase use_case_;
@@ -770,9 +757,8 @@
   // the use case. Defaults to kNormalPriority.
   TaskQueue::QueuePriority ComputeCompositorPriority() const;
 
-  // Used to update the compositor policy on the main thread when there is a
-  // change in the compositor priority.
-  void UpdateCompositorPolicy();
+  // Used to update the compositor priority on the main thread.
+  void UpdateCompositorTaskQueuePriority();
 
   // Computes the priority for compositing based on the current use case.
   // Returns nullopt if the use case does not need to set the priority.
@@ -973,6 +959,11 @@
     CompositorPriorityExperiments compositor_priority_experiments;
 
     bool main_thread_compositing_is_fast;
+
+    // Priority given to the main thread's compositor task queue. Defaults to
+    // kNormalPriority and is updated via UpdateCompositorTaskQueuePriority().
+    TraceableState<TaskQueue::QueuePriority, TracingCategoryName::kDefault>
+        compositor_priority;
   };
 
   struct AnyThread {
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
index 547eaef7..2f24847 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/fuchsia.py
@@ -135,7 +135,6 @@
                 'system_log_file': None,
                 'cpu_cores': CPU_CORES,
                 'require_kvm': True,
-                'emu_type': target_device,
                 'ram_size_mb': 8192
             }
             if target_device == 'qemu':
diff --git a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=ForceSynchronousHTMLParsing b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=ForceSynchronousHTMLParsing
index 8f2e187..5763dd9 100644
--- a/third_party/blink/web_tests/FlagExpectations/enable-blink-features=ForceSynchronousHTMLParsing
+++ b/third_party/blink/web_tests/FlagExpectations/enable-blink-features=ForceSynchronousHTMLParsing
@@ -38,8 +38,8 @@
 ### virtual/composite-after-paint/scrollingcoordinator/
 crbug.com/901056 virtual/composite-after-paint/scrollingcoordinator/donot-compute-non-fast-scrollable-region-for-hidden-frames.html [ Timeout ]
 
-### virtual/sxg-subresource/external/wpt/signed-exchange/subresource/
-crbug.com/901056 virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource.tentative.html [ Timeout ]
+### external/wpt/signed-exchange/subresource/
+crbug.com/901056 external/wpt/signed-exchange/subresource/sxg-subresource.tentative.html [ Timeout ]
 
 ### http/tests/devtools/sources/debugger/
 crbug.com/1063051 http/tests/devtools/sources/debugger/debug-inlined-scripts-fragment-id.js [ Timeout ]
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 681552a7..ce8c8fc 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -2148,6 +2148,10 @@
 crbug.com/1082020 http/tests/loading/wbn/origin-trial/* [ Skip ]
 virtual/subresource-web-bundles-disabled/http/tests/loading/wbn/origin-trial/* [ Pass ]
 
+# This test requires sxg-subresource-disabled.
+http/tests/loading/sxg/sxg-subresource-origin-trial.https.html [ Skip ]
+virtual/sxg-subresource-disabled/http/tests/loading/sxg/sxg-subresource-origin-trial.https.html [ Pass ]
+
 # No good way to automate this test yet
 external/wpt/native-file-system/showSaveFilePicker-manual.https.html [ Skip ]
 
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index d687419..4b71364a 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -718,6 +718,7 @@
 crbug.com/1081802 external/wpt/css/css-flexbox/overflow-area-002.html [ Failure ]
 crbug.com/807497 external/wpt/css/css-flexbox/anonymous-flex-item-005.html [ Failure ]
 crbug.com/1111708 external/wpt/css/css-flexbox/flexbox_justifycontent-center-overflow.html [ Failure ]
+crbug.com/1111128 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-min-height-auto-002b.html [ Failure ]
 
 # These have some incorrect expectations.
 crbug.com/704294 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-intrinsic-ratio-003.html [ Failure ]
@@ -733,11 +734,7 @@
 crbug.com/249112 external/wpt/css/css-flexbox/flex-minimum-width-flex-items-005.xht [ Failure ]
 crbug.com/249112 external/wpt/css/css-flexbox/flex-minimum-width-flex-items-007.xht [ Failure ]
 
-# Not implemented yet
-crbug.com/336604 external/wpt/css/css-flexbox/flexbox_visibility-collapse-line-wrapping.html [ Failure ]
-crbug.com/336604 external/wpt/css/css-flexbox/flexbox_visibility-collapse.html [ Failure ]
-
-# These require justify-content:right and align-content:end
+# These require css-align-3 positional keywords that Blink doesn't yet support for flexbox.
 crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001a.xhtml [ Failure ]
 crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-horiz-001b.xhtml [ Failure ]
 crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-vert-001a.xhtml [ Failure ]
@@ -749,6 +746,7 @@
 crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-justify-content-horiz-003.xhtml [ Failure ]
 crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-justify-content-horiz-005.xhtml [ Failure ]
 crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-justify-content-horiz-006.xhtml [ Failure ]
+crbug.com/1011718 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-002.xhtml [ Failure ]
 
 # We don't support requesting flex line breaks and it is not clear that we should.
 # See https://lists.w3.org/Archives/Public/www-style/2015May/0065.html
@@ -762,9 +760,8 @@
 crbug.com/336604 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-collapsed-item-horiz-001.html [ Failure ]
 crbug.com/336604 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-collapsed-item-horiz-002.html [ Failure ]
 crbug.com/336604 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-collapsed-item-horiz-003.html [ Failure ]
-
-# We don't correctly implement aspect ratios for images in Flexbox
-crbug.com/1111128 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-min-height-auto-002b.html [ Failure ]
+crbug.com/336604 external/wpt/css/css-flexbox/flexbox_visibility-collapse-line-wrapping.html [ Failure ]
+crbug.com/336604 external/wpt/css/css-flexbox/flexbox_visibility-collapse.html [ Failure ]
 
 # These tests are incorrect, as Firefox has a bug in this area. https://bugzilla.mozilla.org/show_bug.cgi?id=1136312
 crbug.com/553838 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-min-height-auto-002a.html [ Failure ]
@@ -775,21 +772,24 @@
 # We paint in an incorrect order when layers are present. Blocked on composite-after-paint.
 crbug.com/370604 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-paint-ordering-002.xhtml [ Failure ]
 
+# We haven't implemented last baseline alignment.
+crbug.com/886585 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001a.xhtml [ Failure ]
+crbug.com/886585 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001b.xhtml [ Failure ]
+crbug.com/886585 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-006.xhtml [ Failure ]
+crbug.com/886585 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-007.xhtml [ Failure ]
+crbug.com/886585 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-008.xhtml [ Failure ]
+
+# We haven't implemented flex-basis: content yet.
+crbug.com/470421 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001a.html [ Failure ]
+crbug.com/470421 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001b.html [ Failure ]
+crbug.com/470421 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002a.html [ Failure ]
+crbug.com/470421 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002b.html [ Failure ]
+crbug.com/470421 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003a.html [ Failure ]
+crbug.com/470421 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004a.html [ Failure ]
+
 # Untriaged flex failures
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-content-wmvert-001.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001a.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-001b.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-006.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-007.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-baseline-horiz-008.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-002.xhtml [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-align-self-vert-rtl-005.xhtml [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001a.html [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-001b.html [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002a.html [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-002b.html [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-003a.html [ Failure ]
-crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-flex-basis-content-004a.html [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-justify-content-vert-006.xhtml [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-justify-content-wmvert-001.xhtml [ Failure ]
 crbug.com/626703 external/wpt/css/vendor-imports/mozilla/mozilla-central-reftests/flexbox/flexbox-safe-overflow-position-001.html [ Failure ]
@@ -1349,7 +1349,6 @@
 crbug.com/753671 [ Mac10.15 ] external/wpt/css/css-content/quotes-020.html [ Failure ]
 crbug.com/753671 external/wpt/css/css-content/quotes-021.html [ Failure ]
 crbug.com/753671 external/wpt/css/css-content/quotes-022.html [ Failure ]
-crbug.com/753671 external/wpt/css/css-content/quotes-033.html [ Failure ]
 crbug.com/989123 external/wpt/css/css-typed-om/the-stylepropertymap/computed/get-auto-min-size.html [ Failure ]
 
 crbug.com/995106 external/wpt/css/css-lists/inline-block-list.html [ Failure ]
@@ -4182,7 +4181,7 @@
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/flex-and-content-sized-resolution-columns.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/flex-and-intrinsic-sizes.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/flex-content-sized-column-use-available-width.html [ Failure ]
-crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/flex-content-sized-columns-resize.html [ Failure ]
+crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/flex-content-sized-columns-resize.html [ Failure Timeout ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/floating-empty-grids.html [ Failure ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-auto-columns-rows-auto-flow-resolution.html [ Failure Crash ]
 crbug.com/1045599 virtual/layout-ng-grid/fast/css-grid-layout/grid-auto-columns-rows-get-set.html [ Failure ]
@@ -6642,3 +6641,4 @@
 
 # Sheriff 2020-08-26
 crbug.com/1122106 external/wpt/webrtc/protocol/crypto-suite.https.html [ Pass Timeout ]
+crbug.com/1122742 http/tests/devtools/sources/debugger/source-frame-inline-breakpoint-decorations.js [ Pass Timeout ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 43972cd3..d9cf9b8 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -377,9 +377,9 @@
     "args": ["--autoplay-policy=document-user-activation-required"]
   },
   {
-    "prefix": "sxg-subresource",
-    "bases": ["external/wpt/signed-exchange"],
-    "args": ["--enable-features=SignedExchangeSubresourcePrefetch"]
+    "prefix": "sxg-subresource-disabled",
+    "bases": ["http/tests/loading/sxg/sxg-subresource-origin-trial.https.html"],
+    "args": ["--disable-blink-features=SignedExchangeSubresourcePrefetch"]
   },
   {
     "prefix": "wbn-from-network",
diff --git a/third_party/blink/web_tests/external/wpt/css/css-content/inheritance-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-content/inheritance-expected.txt
index a75fca40..e271532d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-content/inheritance-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-content/inheritance-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-FAIL Property quotes has initial value auto assert_equals: expected "auto" but got ""
+PASS Property quotes has initial value auto
 PASS Property quotes inherits
 FAIL Property bookmark-level has initial value none assert_true: bookmark-level doesn't seem to be supported in the computed style expected true got false
 FAIL Property bookmark-level does not inherit assert_true: expected true got false
diff --git a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/quotes-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/quotes-expected.txt
deleted file mode 100644
index 608583f..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-typed-om/the-stylepropertymap/properties/quotes-expected.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-This is a testharness.js-based test.
-FAIL Can set 'quotes' to CSS-wide keywords assert_not_equals: Computed value must not be null got disallowed value null
-FAIL Can set 'quotes' to var() references assert_not_equals: Computed value must not be null got disallowed value null
-PASS Can set 'quotes' to the 'none' keyword
-PASS Setting 'quotes' to a length throws TypeError
-PASS Setting 'quotes' to a percent throws TypeError
-PASS Setting 'quotes' to a time throws TypeError
-PASS Setting 'quotes' to an angle throws TypeError
-PASS Setting 'quotes' to a flexible length throws TypeError
-PASS Setting 'quotes' to a number throws TypeError
-PASS Setting 'quotes' to a position throws TypeError
-PASS Setting 'quotes' to a URL throws TypeError
-PASS Setting 'quotes' to a transform throws TypeError
-PASS 'quotes' does not supported '"<<" ">>" "<" ">"'
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative-expected.txt
deleted file mode 100644
index 1cd5040..0000000
--- a/third_party/blink/web_tests/external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Subresource signed exchange prefetch. assert_equals: expected "from server" but got "sxg-subresource-script.js should not be prefetched"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/signed-exchange/subresource/sxg-subresource.tentative-expected.txt b/third_party/blink/web_tests/external/wpt/signed-exchange/subresource/sxg-subresource.tentative-expected.txt
deleted file mode 100644
index ee54a8c..0000000
--- a/third_party/blink/web_tests/external/wpt/signed-exchange/subresource/sxg-subresource.tentative-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-FAIL Subresource signed exchange prefetch. assert_equals: expected "from signed exchange" but got "sxg-subresource-script.js should not be prefetched"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/position/layout-state-only-positioned-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/position/layout-state-only-positioned-expected.txt
similarity index 93%
rename from third_party/blink/web_tests/platform/mac/paint/invalidation/position/layout-state-only-positioned-expected.txt
rename to third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/position/layout-state-only-positioned-expected.txt
index 1761aae..31c2dba7 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/position/layout-state-only-positioned-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/paint/invalidation/position/layout-state-only-positioned-expected.txt
@@ -10,7 +10,7 @@
       ]
     },
     {
-      "name": "LayoutNGBlockFlow (positioned) DIV",
+      "name": "LayoutBlockFlow (positioned) DIV",
       "bounds": [106, 106],
       "invalidations": [
         [0, 0, 106, 106]
diff --git a/third_party/blink/web_tests/flag-specific/disable-layout-ng/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt b/third_party/blink/web_tests/flag-specific/disable-layout-ng/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt
index 919414f..610f7cd 100644
--- a/third_party/blink/web_tests/flag-specific/disable-layout-ng/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt
+++ b/third_party/blink/web_tests/flag-specific/disable-layout-ng/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt
@@ -12,6 +12,12 @@
       "transform": 1
     },
     {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1000, 1000],
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
+    },
+    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -28,12 +34,6 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
-    },
-    {
-      "name": "Scrolling Contents Layer",
-      "bounds": [1000, 1000],
-      "backgroundColor": "#C0C0C0",
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/paint/invalidation/position/layout-state-only-positioned-expected.txt b/third_party/blink/web_tests/paint/invalidation/position/layout-state-only-positioned-expected.txt
index 8f64356..1761aae 100644
--- a/third_party/blink/web_tests/paint/invalidation/position/layout-state-only-positioned-expected.txt
+++ b/third_party/blink/web_tests/paint/invalidation/position/layout-state-only-positioned-expected.txt
@@ -8,6 +8,37 @@
       "invalidations": [
         [0, 50, 106, 106]
       ]
+    },
+    {
+      "name": "LayoutNGBlockFlow (positioned) DIV",
+      "bounds": [106, 106],
+      "invalidations": [
+        [0, 0, 106, 106]
+      ],
+      "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [3, 3],
+      "bounds": [100, 200],
+      "transform": 1
+    },
+    {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [88, 3],
+      "bounds": [15, 100],
+      "transform": 1
+    }
+  ],
+  "transforms": [
+    {
+      "id": 1,
+      "transform": [
+        [1, 0, 0, 0],
+        [0, 1, 0, 0],
+        [0, 0, 1, 0],
+        [0, 50, 0, 1]
+      ]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/platform/mac-mac10.13/paint/invalidation/position/layout-state-only-positioned-expected.txt b/third_party/blink/web_tests/platform/mac-mac10.13/paint/invalidation/position/layout-state-only-positioned-expected.txt
deleted file mode 100644
index 8f64356..0000000
--- a/third_party/blink/web_tests/platform/mac-mac10.13/paint/invalidation/position/layout-state-only-positioned-expected.txt
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "layers": [
-    {
-      "name": "Scrolling Contents Layer",
-      "bounds": [800, 600],
-      "contentsOpaque": true,
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [0, 50, 106, 106]
-      ]
-    }
-  ]
-}
-
diff --git a/third_party/blink/web_tests/platform/mac/compositing/layer-creation/scroll-partial-update-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/layer-creation/scroll-partial-update-expected.txt
index 7b2227ab..9d75ad9 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/layer-creation/scroll-partial-update-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/layer-creation/scroll-partial-update-expected.txt
@@ -25,16 +25,16 @@
       "transform": 2
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [185, 400],
-      "backgroundColor": "#FFFF0080",
-      "transform": 3
-    },
-    {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [185, 0],
       "bounds": [15, 200],
       "transform": 2
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [185, 400],
+      "backgroundColor": "#FFFF0080",
+      "transform": 3
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-expected.txt
index 4c12a3a..ded1393 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-expected.txt
@@ -12,12 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [1000, 1000],
-      "backgroundColor": "#C0C0C0",
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -34,6 +28,12 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1000, 1000],
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
index 4c12a3a..ded1393 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/overflow/overflow-auto-with-touch-toggle-expected.txt
@@ -12,12 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [1000, 1000],
-      "backgroundColor": "#C0C0C0",
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -34,6 +28,12 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1000, 1000],
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt b/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
index 4374f06b..8fb4d11 100644
--- a/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/compositing/squashing/invalidations-with-large-negative-margin-inline-content-expected.txt
@@ -12,14 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [585, 500],
-      "invalidations": [
-        [400, 100, 20, 20]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 185],
       "bounds": [585, 15],
@@ -36,6 +28,14 @@
       "position": [585, 185],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [585, 500],
+      "invalidations": [
+        [400, 100, 20, 20]
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt
index b07ea9c..8b83b6df 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/invalidations-with-large-negative-margin-expected.txt
@@ -12,15 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [585, 400],
-      "invalidations": [
-        [400, 0, 50, 50],
-        [0, 0, 50, 50]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 185],
       "bounds": [585, 15],
@@ -37,6 +28,15 @@
       "position": [585, 185],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [585, 400],
+      "invalidations": [
+        [400, 0, 50, 50],
+        [0, 0, 50, 50]
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt
index dc35a27..ca83f1b 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-color-change-expected.txt
@@ -12,14 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [185, 615],
-      "invalidations": [
-        [0, 0, 47, 615]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 185],
       "bounds": [185, 15],
@@ -36,6 +28,14 @@
       "position": [185, 185],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [185, 615],
+      "invalidations": [
+        [0, 0, 47, 615]
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt
index 71ccc50..069d36da 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/text-match-highlight-expected.txt
@@ -15,20 +15,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [785, 1340],
-      "invalidations": [
-        [10, 72, 227, 18],
-        [20, 160, 200, 72],
-        [10, 126, 139, 12],
-        [268, 0, 46, 18],
-        [90, 0, 46, 18],
-        [224, 0, 45, 18],
-        [52, 18, 45, 18]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 485],
       "bounds": [785, 15],
@@ -45,6 +31,20 @@
       "position": [785, 485],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [785, 1340],
+      "invalidations": [
+        [10, 72, 227, 18],
+        [20, 160, 200, 72],
+        [10, 126, 139, 12],
+        [268, 0, 46, 18],
+        [90, 0, 46, 18],
+        [224, 0, 45, 18],
+        [52, 18, 45, 18]
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt
index d3f2950b..8766c26 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-and-content-expected.txt
@@ -12,6 +12,24 @@
       "transform": 1
     },
     {
+      "name": "ContentsLayer for Horizontal Scrollbar Layer",
+      "position": [0, 185],
+      "bounds": [185, 15],
+      "transform": 1
+    },
+    {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [185, 0],
+      "bounds": [15, 185],
+      "transform": 1
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [185, 185],
+      "bounds": [15, 15],
+      "transform": 1
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [185, 234],
       "invalidations": [
@@ -30,24 +48,6 @@
         [0, 0, 75, 18]
       ],
       "transform": 2
-    },
-    {
-      "name": "ContentsLayer for Horizontal Scrollbar Layer",
-      "position": [0, 185],
-      "bounds": [185, 15],
-      "transform": 1
-    },
-    {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [185, 0],
-      "bounds": [15, 185],
-      "transform": 1
-    },
-    {
-      "name": "Scroll Corner Layer",
-      "position": [185, 185],
-      "bounds": [15, 15],
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt
index 98ea9d6..67aadb1 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/compositing/updating-scrolling-container-expected.txt
@@ -15,12 +15,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [5, 5],
-      "bounds": [400, 400],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [5, 190],
       "bounds": [185, 15],
@@ -37,6 +31,12 @@
       "position": [190, 190],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [5, 5],
+      "bounds": [400, 400],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/flexbox/scrollbars-changed-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/flexbox/scrollbars-changed-expected.txt
index 54a7084c..c56fde6 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/flexbox/scrollbars-changed-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/flexbox/scrollbars-changed-expected.txt
@@ -11,17 +11,17 @@
       "bounds": [200, 100]
     },
     {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [185, 0],
+      "bounds": [15, 100]
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [185, 210],
       "backgroundColor": "#80808080",
       "invalidations": [
         [0, 5, 15, 15]
       ]
-    },
-    {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [185, 0],
-      "bounds": [15, 100]
     }
   ]
 }
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt
index 5c83202..668454d 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/float-offscreen-expected.txt
@@ -12,11 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [784, 1027],
-      "transform": 1
-    },
-    {
       "name": "Horizontal Scrollbar Layer",
       "position": [0, 300],
       "bounds": [784, 0],
@@ -27,6 +22,11 @@
       "position": [784, 0],
       "bounds": [0, 300],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [784, 1027],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/forms/textarea-caret-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/forms/textarea-caret-expected.txt
index 3692673a..34f1fe8 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/forms/textarea-caret-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/forms/textarea-caret-expected.txt
@@ -20,16 +20,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [1, 1],
-      "bounds": [470, 19],
-      "backgroundColor": "#FFFFFF",
-      "invalidations": [
-        [0, 0, 470, 19]
-      ],
-      "transform": 2
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [1, 20],
       "bounds": [164, 15],
@@ -45,6 +35,16 @@
       "transform": 1
     },
     {
+      "name": "Scrolling Contents Layer",
+      "position": [1, 1],
+      "bounds": [470, 19],
+      "backgroundColor": "#FFFFFF",
+      "invalidations": [
+        [0, 0, 470, 19]
+      ],
+      "transform": 2
+    },
+    {
       "name": "Decoration Layer",
       "position": [-2, -2],
       "bounds": [185, 40],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt
index bcdd642..7a56b4e 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-child-expected.txt
@@ -12,15 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [1, 1],
-      "bounds": [2100, 185],
-      "invalidations": [
-        [2000, 0, 100, 100]
-      ],
-      "transform": 2
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [1, 186],
       "bounds": [285, 15],
@@ -37,6 +28,15 @@
       "position": [286, 186],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [1, 1],
+      "bounds": [2100, 185],
+      "invalidations": [
+        [2000, 0, 100, 100]
+      ],
+      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt
index 450ee51..89532e5 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-parent-expected.txt
@@ -12,15 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [1, 1],
-      "bounds": [2100, 185],
-      "invalidations": [
-        [0, 0, 100, 100]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [1, 186],
       "bounds": [285, 15],
@@ -37,6 +28,15 @@
       "position": [286, 186],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [1, 1],
+      "bounds": [2100, 185],
+      "invalidations": [
+        [0, 0, 100, 100]
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt
index c59b6fb..db66808 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/overflow/vertical-overflow-same-expected.txt
@@ -12,15 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [1, 1],
-      "bounds": [2100, 185],
-      "invalidations": [
-        [0, 0, 100, 100]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [1, 186],
       "bounds": [285, 15],
@@ -37,6 +28,15 @@
       "position": [286, 186],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [1, 1],
+      "bounds": [2100, 185],
+      "invalidations": [
+        [0, 0, 100, 100]
+      ],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-child-of-transformed-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-child-of-transformed-scrolled-expected.txt
index ed0997d..da52e03 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-child-of-transformed-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-child-of-transformed-scrolled-expected.txt
@@ -12,14 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [1000, 1000],
-      "invalidations": [
-        [100, 150, 100, 100]
-      ],
-      "transform": 2
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -36,6 +28,14 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1000, 1000],
+      "invalidations": [
+        [100, 150, 100, 100]
+      ],
+      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt
index 38ca408..ecf0b357 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/fixed-descendant-of-transformed-scrolled-expected.txt
@@ -12,14 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [1050, 1050],
-      "invalidations": [
-        [100, 150, 100, 100]
-      ],
-      "transform": 2
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -36,6 +28,14 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1050, 1050],
+      "invalidations": [
+        [100, 150, 100, 100]
+      ],
+      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/outline-change-in-scrollers-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/outline-change-in-scrollers-expected.txt
index a4d3ea6..d434177 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/outline-change-in-scrollers-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/outline-change-in-scrollers-expected.txt
@@ -15,15 +15,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [10, 10],
-      "bounds": [150, 150],
-      "invalidations": [
-        [0, 0, 75, 75]
-      ],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [10, 105],
       "bounds": [95, 15],
@@ -42,11 +33,38 @@
       "transform": 1
     },
     {
+      "name": "Scrolling Contents Layer",
+      "position": [10, 10],
+      "bounds": [150, 150],
+      "invalidations": [
+        [0, 0, 75, 75]
+      ],
+      "transform": 1
+    },
+    {
       "name": "LayoutNGBlockFlow (relative positioned) DIV class='scroll'",
       "bounds": [130, 130],
       "transform": 2
     },
     {
+      "name": "ContentsLayer for Horizontal Scrollbar Layer",
+      "position": [10, 105],
+      "bounds": [95, 15],
+      "transform": 2
+    },
+    {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [105, 10],
+      "bounds": [15, 95],
+      "transform": 2
+    },
+    {
+      "name": "Scroll Corner Layer",
+      "position": [105, 105],
+      "bounds": [15, 15],
+      "transform": 2
+    },
+    {
       "name": "Scrolling Contents Layer",
       "position": [10, 10],
       "bounds": [95, 150],
@@ -56,26 +74,26 @@
       "transform": 2
     },
     {
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='scroll'",
+      "bounds": [130, 130],
+      "transform": 3
+    },
+    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [10, 105],
       "bounds": [95, 15],
-      "transform": 2
+      "transform": 3
     },
     {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [105, 10],
       "bounds": [15, 95],
-      "transform": 2
+      "transform": 3
     },
     {
       "name": "Scroll Corner Layer",
       "position": [105, 105],
       "bounds": [15, 15],
-      "transform": 2
-    },
-    {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV class='scroll'",
-      "bounds": [130, 130],
       "transform": 3
     },
     {
@@ -88,38 +106,11 @@
       "transform": 3
     },
     {
-      "name": "ContentsLayer for Horizontal Scrollbar Layer",
-      "position": [10, 105],
-      "bounds": [95, 15],
-      "transform": 3
-    },
-    {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [105, 10],
-      "bounds": [15, 95],
-      "transform": 3
-    },
-    {
-      "name": "Scroll Corner Layer",
-      "position": [105, 105],
-      "bounds": [15, 15],
-      "transform": 3
-    },
-    {
       "name": "LayoutNGBlockFlow (relative positioned) DIV class='scroll'",
       "bounds": [130, 130],
       "transform": 4
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [25, 10],
-      "bounds": [95, 150],
-      "invalidations": [
-        [20, 0, 75, 75]
-      ],
-      "transform": 4
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [25, 105],
       "bounds": [95, 15],
@@ -138,17 +129,17 @@
       "transform": 4
     },
     {
-      "name": "LayoutNGBlockFlow (relative positioned) DIV class='scroll'",
-      "bounds": [130, 130],
-      "transform": 5
+      "name": "Scrolling Contents Layer",
+      "position": [25, 10],
+      "bounds": [95, 150],
+      "invalidations": [
+        [20, 0, 75, 75]
+      ],
+      "transform": 4
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [10, 10],
-      "bounds": [150, 95],
-      "invalidations": [
-        [0, 20, 75, 75]
-      ],
+      "name": "LayoutNGBlockFlow (relative positioned) DIV class='scroll'",
+      "bounds": [130, 130],
       "transform": 5
     },
     {
@@ -168,6 +159,15 @@
       "position": [105, 105],
       "bounds": [15, 15],
       "transform": 5
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [10, 10],
+      "bounds": [150, 95],
+      "invalidations": [
+        [0, 20, 75, 75]
+      ],
+      "transform": 5
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt
index a872fb78..43733428 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-auto-in-overflow-auto-scrolled-expected.txt
@@ -12,6 +12,12 @@
       "transform": 1
     },
     {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "transform": 1
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [769, 700],
       "transform": 2
@@ -22,21 +28,15 @@
       "transform": 3
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [754, 800],
-      "transform": 4
-    },
-    {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [754, 0],
       "bounds": [15, 400],
       "transform": 3
     },
     {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "transform": 1
+      "name": "Scrolling Contents Layer",
+      "bounds": [754, 800],
+      "transform": 4
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt
index 03f2afb..57920cc5 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-delete-expected.txt
@@ -12,18 +12,18 @@
       "transform": 1
     },
     {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [65, 0],
+      "bounds": [15, 69],
+      "transform": 1
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [65, 198],
       "invalidations": [
         [0, 162, 44, 36]
       ],
       "transform": 2
-    },
-    {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [65, 0],
-      "bounds": [15, 69],
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt
index a872fb78..43733428 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/overflow-scroll-in-overflow-scroll-scrolled-expected.txt
@@ -12,6 +12,12 @@
       "transform": 1
     },
     {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "transform": 1
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [769, 700],
       "transform": 2
@@ -22,21 +28,15 @@
       "transform": 3
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [754, 800],
-      "transform": 4
-    },
-    {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [754, 0],
       "bounds": [15, 400],
       "transform": 3
     },
     {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "transform": 1
+      "name": "Scrolling Contents Layer",
+      "bounds": [754, 800],
+      "transform": 4
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt
index 19abd96..654754a2 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/repaint-composited-child-in-scrolled-container-expected.txt
@@ -12,12 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [650, 600],
-      "backgroundColor": "#0000FF80",
-      "transform": 2
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -36,6 +30,12 @@
       "transform": 1
     },
     {
+      "name": "Scrolling Contents Layer",
+      "bounds": [650, 600],
+      "backgroundColor": "#0000FF80",
+      "transform": 2
+    },
+    {
       "name": "LayoutNGBlockFlow (positioned) DIV id='container'",
       "bounds": [600, 600],
       "contentsOpaque": true,
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt
index 725212e6..e393107 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-ancestor-clip-change-expected.txt
@@ -12,11 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [400, 400],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 185],
       "bounds": [85, 15],
@@ -33,6 +28,11 @@
       "position": [85, 185],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [400, 400],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt
index 8260b99..39510ed 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-damage-and-full-viewport-repaint-expected.txt
@@ -16,12 +16,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [1, 1],
-      "bounds": [2000, 2000],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [1, 186],
       "bounds": [185, 15],
@@ -38,6 +32,12 @@
       "position": [186, 186],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [1, 1],
+      "bounds": [2000, 2000],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt
index f880b7b..fbbb74c 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/scrollbar-parts-expected.txt
@@ -12,11 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [150, 300],
-      "transform": 1
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 85],
       "bounds": [85, 15],
@@ -33,6 +28,11 @@
       "position": [85, 85],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "bounds": [150, 300],
+      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt
index b5042ab..85952dfc 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/scroll/sticky/invalidate-after-composited-scroll-with-sticky-expected.txt
@@ -12,17 +12,17 @@
       "transform": 2
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [345, 2018],
-      "transform": 3
-    },
-    {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [345, 0],
       "bounds": [15, 640],
       "transform": 2
     },
     {
+      "name": "Scrolling Contents Layer",
+      "bounds": [345, 2018],
+      "transform": 3
+    },
+    {
       "name": "LayoutNGBlockFlow (sticky positioned) DIV id='sticky'",
       "bounds": [345, 18],
       "transform": 4
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt
index 2d9e7eb..1393129 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-inside-table-cell-expected.txt
@@ -12,12 +12,6 @@
       "transform": 1
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [2, 2],
-      "bounds": [950, 450],
-      "transform": 2
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [2, 452],
       "bounds": [435, 15],
@@ -34,6 +28,12 @@
       "position": [437, 452],
       "bounds": [15, 15],
       "transform": 1
+    },
+    {
+      "name": "Scrolling Contents Layer",
+      "position": [2, 2],
+      "bounds": [950, 450],
+      "transform": 2
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt
index b0b8a8c..9412cc0 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/scroll-relative-table-inside-table-cell-expected.txt
@@ -13,12 +13,6 @@
       "transform": 2
     },
     {
-      "name": "Scrolling Contents Layer",
-      "position": [2, 2],
-      "bounds": [950, 450],
-      "transform": 3
-    },
-    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [2, 452],
       "bounds": [435, 15],
@@ -37,6 +31,12 @@
       "transform": 2
     },
     {
+      "name": "Scrolling Contents Layer",
+      "position": [2, 2],
+      "bounds": [950, 450],
+      "transform": 3
+    },
+    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 585],
       "bounds": [785, 15],
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt
index 757afe5..0cdafde 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-auto-in-overflow-auto-scrolled-expected.txt
@@ -12,6 +12,12 @@
       "transform": 1
     },
     {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "transform": 1
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [769, 700],
       "transform": 2
@@ -22,21 +28,15 @@
       "transform": 3
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [754, 810],
-      "transform": 4
-    },
-    {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [754, 0],
       "bounds": [15, 400],
       "transform": 3
     },
     {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "transform": 1
+      "name": "Scrolling Contents Layer",
+      "bounds": [754, 810],
+      "transform": 4
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt
index 757afe5..0cdafde 100644
--- a/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt
+++ b/third_party/blink/web_tests/platform/mac/paint/invalidation/table/table-overflow-scroll-in-overflow-scroll-scrolled-expected.txt
@@ -12,6 +12,12 @@
       "transform": 1
     },
     {
+      "name": "ContentsLayer for Vertical Scrollbar Layer",
+      "position": [769, 0],
+      "bounds": [15, 300],
+      "transform": 1
+    },
+    {
       "name": "Scrolling Contents Layer",
       "bounds": [769, 700],
       "transform": 2
@@ -22,21 +28,15 @@
       "transform": 3
     },
     {
-      "name": "Scrolling Contents Layer",
-      "bounds": [754, 810],
-      "transform": 4
-    },
-    {
       "name": "ContentsLayer for Vertical Scrollbar Layer",
       "position": [754, 0],
       "bounds": [15, 400],
       "transform": 3
     },
     {
-      "name": "ContentsLayer for Vertical Scrollbar Layer",
-      "position": [769, 0],
-      "bounds": [15, 300],
-      "transform": 1
+      "name": "Scrolling Contents Layer",
+      "bounds": [754, 810],
+      "transform": 4
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-stacked-children-expected.html b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-stacked-children-expected.html
new file mode 100644
index 0000000..684de0c
--- /dev/null
+++ b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-stacked-children-expected.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<div style="width: 100px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-stacked-children.html b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-stacked-children.html
new file mode 100644
index 0000000..a55e6f0
--- /dev/null
+++ b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-stacked-children.html
@@ -0,0 +1,34 @@
+<!doctype html>
+<style>
+#scroller::-webkit-scrollbar {
+  background: green;
+  width: 100px;
+}
+#scroller {
+  width: 100px;
+  height: 100px;
+  overflow-y: overlay;
+  overflow-x: hidden;
+  position: relative;
+}
+#notransform {
+  width: 100px;
+  height: 50px;
+  background: red;
+  position: relative;
+  z-index: 1;
+}
+#transform {
+  width: 100px;
+  height: 50px;
+  background: red;
+  position: absolute;
+  will-change: transform;
+  z-index: 2;
+}
+</style>
+<div id="scroller">
+  <div id="notransform"></div>
+  <div id="transform"></div>
+  <div id="forcescroll" style="height: 200px; width: 10px;"></div>
+</div>
diff --git a/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-transparency-expected.html b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-transparency-expected.html
new file mode 100644
index 0000000..684de0c
--- /dev/null
+++ b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-transparency-expected.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<div style="width: 100px; height: 100px; background: green"></div>
diff --git a/third_party/blink/web_tests/scrollbars/overlay-scrollbar-with-transparency.html b/third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-transparency.html
similarity index 100%
rename from third_party/blink/web_tests/scrollbars/overlay-scrollbar-with-transparency.html
rename to third_party/blink/web_tests/scrollbars/overflow-overlay-scrollbar-with-transparency.html
diff --git a/third_party/blink/web_tests/scrollbars/overlay-scrollbar-with-transparency-expected.png b/third_party/blink/web_tests/scrollbars/overlay-scrollbar-with-transparency-expected.png
deleted file mode 100644
index b5daa85..0000000
--- a/third_party/blink/web_tests/scrollbars/overlay-scrollbar-with-transparency-expected.png
+++ /dev/null
Binary files differ
diff --git a/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt b/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt
index ded1393..4c12a3a 100644
--- a/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt
+++ b/third_party/blink/web_tests/virtual/prefer_compositing_to_lcd_text/compositing/overflow/overflow-overlay-with-touch-expected.txt
@@ -12,6 +12,12 @@
       "transform": 1
     },
     {
+      "name": "Scrolling Contents Layer",
+      "bounds": [1000, 1000],
+      "backgroundColor": "#C0C0C0",
+      "transform": 1
+    },
+    {
       "name": "ContentsLayer for Horizontal Scrollbar Layer",
       "position": [0, 285],
       "bounds": [285, 15],
@@ -28,12 +34,6 @@
       "position": [285, 285],
       "bounds": [15, 15],
       "transform": 1
-    },
-    {
-      "name": "Scrolling Contents Layer",
-      "bounds": [1000, 1000],
-      "backgroundColor": "#C0C0C0",
-      "transform": 1
     }
   ],
   "transforms": [
diff --git a/third_party/blink/web_tests/virtual/sxg-subresource-disabled/README.md b/third_party/blink/web_tests/virtual/sxg-subresource-disabled/README.md
new file mode 100644
index 0000000..3c932a3
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/sxg-subresource-disabled/README.md
@@ -0,0 +1 @@
+With --disable-features=SignedExchangeSubresourcePrefetch
diff --git a/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/README.txt b/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/README.txt
deleted file mode 100644
index d01256d..0000000
--- a/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/README.txt
+++ /dev/null
@@ -1 +0,0 @@
-This directory is for testing the SignedExchangeSubresourcePrefetch feature.
diff --git a/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative-expected.txt b/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative-expected.txt
deleted file mode 100644
index 943287c..0000000
--- a/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource-header-integrity-mismatch.tentative-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-PASS Subresource signed exchange prefetch.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource.tentative-expected.txt b/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource.tentative-expected.txt
deleted file mode 100644
index 943287c..0000000
--- a/third_party/blink/web_tests/virtual/sxg-subresource/external/wpt/signed-exchange/subresource/sxg-subresource.tentative-expected.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-This is a testharness.js-based test.
-PASS Subresource signed exchange prefetch.
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt b/third_party/blink/web_tests/webexposed/global-interface-listing-expected.txt
index f87c815a..ec626bef 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
@@ -11371,6 +11371,13 @@
     attribute @@toStringTag
     getter type
     method constructor
+interface XRDepthInformation
+    attribute @@toStringTag
+    getter data
+    getter height
+    getter width
+    method constructor
+    method getDepth
 interface XRFrame
     attribute @@toStringTag
     getter session
@@ -11378,6 +11385,7 @@
     getter worldInformation
     method constructor
     method createAnchor
+    method getDepthInformation
     method getHitTestResults
     method getHitTestResultsForTransientInput
     method getLightEstimate
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index b433965..7a983bc5 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -1388,12 +1388,11 @@
       cmdline = []
 
     if test_type == 'generated_script' or is_ios or is_lacros:
-      script = isolate_map[target].get('script', 'bin/run_{}'.format(target))
-      # TODO(crbug.com/816629): the 'script' in gn_isolate_map.pyl
-      # has a unix-style command line that won't work on win. We need to
-      # get rid of it.
       if is_win:
-        script = script.replace("bin/", "bin\\") + ".bat"
+        default_script = 'bin\\run_{}.bat'.format(target)
+      else:
+        default_script = 'bin/run_{}'.format(target)
+      script = isolate_map[target].get('script', default_script)
       cmdline += [script]
       return cmdline, []
 
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 2805531..de4861d 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -1188,7 +1188,8 @@
     ],
 
     'android_cronet_release_trybot_arm64': [
-      'android', 'cronet_android', 'release_trybot', 'arm64', 'strip_debug_info',
+      'android', 'cronet_android', 'official_optimize', 'release_trybot', 'arm64',
+      'strip_debug_info',
     ],
 
     'android_debug_bot': [
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index b8d0ae0..00e98fe 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -25470,6 +25470,9 @@
 
 <histogram name="CaptivePortal.Session.SecureConnectionFailed"
     enum="CaptivePortalStatus" expires_after="M84">
+  <obsolete>
+    Retired in M86.
+  </obsolete>
   <owner>michaeldo@chromium.org</owner>
   <owner>cros-networking@google.com</owner>
   <summary>
@@ -25481,6 +25484,9 @@
 
 <histogram name="CaptivePortal.Session.TimeoutDetectionResult"
     enum="CaptivePortalStatus" expires_after="M84">
+  <obsolete>
+    Retired in M86.
+  </obsolete>
   <owner>michaeldo@chromium.org</owner>
   <owner>cros-networking@google.com</owner>
   <summary>
@@ -51121,8 +51127,17 @@
   <owner>donnd@chromium.org</owner>
   <owner>jinsukkim@chromium.org</owner>
   <summary>
-    Records whether the user opened the Ephemeral Tab panel when it was shown.
-    Recorded when the UX is hidden. Implemented for Android.
+    Records whether the user fully opened the Ephemeral Tab panel when it was
+    shown. Recorded when the UX is hidden. Implemented for Android.
+  </summary>
+</histogram>
+
+<histogram name="EphemeralTab.CtrPeek" enum="BooleanOpened" expires_after="M88">
+  <owner>donnd@chromium.org</owner>
+  <owner>jinsukkim@chromium.org</owner>
+  <summary>
+    Records whether the user opened the Ephemeral Tab panel beyond peeking
+    state. Recorded when the UX is hidden. Implemented for Android.
   </summary>
 </histogram>
 
diff --git a/tools/perf/core/perfetto_binary_roller/binary_deps.json b/tools/perf/core/perfetto_binary_roller/binary_deps.json
index b304735..d24446c 100644
--- a/tools/perf/core/perfetto_binary_roller/binary_deps.json
+++ b/tools/perf/core/perfetto_binary_roller/binary_deps.json
@@ -5,12 +5,12 @@
             "remote_path": "perfetto_binaries/trace_processor_shell/win/ef0fb4c2707e07b2cdff4186893bceea3ab1153c/trace_processor_shell.exe"
         },
         "mac": {
-            "hash": "4a318e434590be8c4a5d7bb2fea5ce2411353104",
-            "remote_path": "perfetto_binaries/trace_processor_shell/mac/11944f7122cf99f1aaa82eb2bed364d5edf0f85f/trace_processor_shell"
+            "hash": "f946b367c73e6bcbcd585ba616226febf2836477",
+            "remote_path": "perfetto_binaries/trace_processor_shell/mac/ed2e4739280f8f0971c39966ac6319295b6598e4/trace_processor_shell"
         },
         "linux": {
-            "hash": "a20c495e1e27fab6701fbb22250239da65afc8e5",
-            "remote_path": "perfetto_binaries/trace_processor_shell/linux/8e6246dafe6190ad560958a549f11f973a573aa1/trace_processor_shell"
+            "hash": "a936e4844a2c42e3384d6e7fc96b00782ff6ef56",
+            "remote_path": "perfetto_binaries/trace_processor_shell/linux/ed2e4739280f8f0971c39966ac6319295b6598e4/trace_processor_shell"
         }
     },
     "power_profile.sql": {
diff --git a/tools/perf/page_sets/data/rendering_desktop.json b/tools/perf/page_sets/data/rendering_desktop.json
index 599106b..0cb4044 100644
--- a/tools/perf/page_sets/data/rendering_desktop.json
+++ b/tools/perf/page_sets/data/rendering_desktop.json
@@ -21,6 +21,15 @@
         "animometer_webgl_attrib_arrays": {
             "DEFAULT": "rendering_desktop_3a59b064d2.wprgo"
         },
+        "animometer_webgl_indexed": {
+            "DEFAULT": "rendering_desktop_e66a172ea7.wprgo"
+        },
+        "animometer_webgl_indexed_multi_draw": {
+            "DEFAULT": "rendering_desktop_e66a172ea7.wprgo"
+        },
+        "animometer_webgl_indexed_multi_draw_base_vertex_base_instance": {
+            "DEFAULT": "rendering_desktop_e66a172ea7.wprgo"
+        },
         "animometer_webgl_multi_draw": {
             "DEFAULT": "rendering_desktop_a8987d91b9.wprgo"
         },
diff --git a/tools/perf/page_sets/data/rendering_desktop_e66a172ea7.wprgo.sha1 b/tools/perf/page_sets/data/rendering_desktop_e66a172ea7.wprgo.sha1
new file mode 100644
index 0000000..6c48c78
--- /dev/null
+++ b/tools/perf/page_sets/data/rendering_desktop_e66a172ea7.wprgo.sha1
@@ -0,0 +1 @@
+e66a172ea7810cf48688342f41f025d340bcdc2b
\ No newline at end of file
diff --git a/tools/perf/page_sets/rendering/tough_webgl_cases.py b/tools/perf/page_sets/rendering/tough_webgl_cases.py
index ad511ac..0676c5d6 100644
--- a/tools/perf/page_sets/rendering/tough_webgl_cases.py
+++ b/tools/perf/page_sets/rendering/tough_webgl_cases.py
@@ -104,6 +104,25 @@
   # pylint: disable=line-too-long
   URL = 'http://kenrussell.github.io/webgl-animometer/Animometer/tests/3d/webgl.html?webgl_version=2&use_ubos=1&use_multi_draw=1'
 
+
+class AnimometerWebGLIndexed(ToughWebglPage):
+  BASE_NAME = 'animometer_webgl_indexed'
+  # pylint: disable=line-too-long
+  URL = 'http://kenrussell.github.io/webgl-animometer/Animometer/tests/3d/webgl-indexed-instanced.html?webgl_version=2&use_attributes=1&num_geometries=120000'
+
+
+class AnimometerWebGLIndexedMultiDraw(ToughWebglPage):
+  BASE_NAME = 'animometer_webgl_indexed_multi_draw'
+  # pylint: disable=line-too-long
+  URL = 'http://kenrussell.github.io/webgl-animometer/Animometer/tests/3d/webgl-indexed-instanced.html?webgl_version=2&use_attributes=1&use_multi_draw=1&num_geometries=120000'
+
+
+class AnimometerWebGLIndexedBaseVertexBaseInstancePage(ToughWebglPage):
+  BASE_NAME = 'animometer_webgl_indexed_multi_draw_base_vertex_base_instance'
+  # pylint: disable=line-too-long
+  URL = 'http://kenrussell.github.io/webgl-animometer/Animometer/tests/3d/webgl-indexed-instanced.html?webgl_version=2&use_attributes=1&use_multi_draw=1&use_base_vertex_base_instance=1&num_geometries=120000'
+
+
 class AnimometerWebGLAttribArraysPage(ToughWebglPage):
   BASE_NAME = 'animometer_webgl_attrib_arrays'
   # pylint: disable=line-too-long
diff --git a/tools/polymer/polymer.py b/tools/polymer/polymer.py
index a92fbb5..75d78fd 100644
--- a/tools/polymer/polymer.py
+++ b/tools/polymer/polymer.py
@@ -179,24 +179,26 @@
 
 def _generate_js_imports(html_file):
   output = []
+  imports_start_offset = -1
   imports_end_index = -1
   imports_found = False
   with io.open(html_file, encoding='utf-8', mode='r') as f:
     lines = f.readlines()
-    deps = []
     for i, line in enumerate(lines):
       match = re.search(r'\s*<link rel="import" href="(.*)"', line)
       if match:
         if not imports_found:
           imports_found = True
+          imports_start_offset = i
           # Include the previous line if it is an opening <if> tag.
           if (i > 0):
             previous_line = lines[i - 1]
             if re.search(r'^\s*<if', previous_line):
+              imports_start_offset -= 1
               previous_line = '// ' + previous_line
               output.append(previous_line.rstrip('\n'))
 
-        imports_end_index = i
+        imports_end_index = i - imports_start_offset
 
         # Convert HTML import URL to equivalent JS import URL.
         dep = Dependency(html_file, match.group(1))
diff --git a/tools/polymer/polymer_test.py b/tools/polymer/polymer_test.py
index f48d594..b5d96a5 100755
--- a/tools/polymer/polymer_test.py
+++ b/tools/polymer/polymer_test.py
@@ -110,6 +110,13 @@
                    'dom_module.js', 'dom_module.m.js',
                    'dom_module_with_if_expr_expected.js')
 
+  # Test case where HTML has some comment before the first <link rel="import"> \
+  # and also uses <if expr> for imports.
+  def testDomModuleImportsWithCopyrightPrefix(self):
+    self._run_test('dom-module', 'dom_module_with_copyright.html',
+                   'dom_module.js', 'dom_module.m.js',
+                   'dom_module_with_if_expr_expected.js')
+
   # Test case where HTML is extracted from a Polymer2 style module.
   def testStyleModule(self):
     self._run_test(
diff --git a/tools/polymer/tests/dom_module_with_copyright.html b/tools/polymer/tests/dom_module_with_copyright.html
new file mode 100644
index 0000000..9aee84cc
--- /dev/null
+++ b/tools/polymer/tests/dom_module_with_copyright.html
@@ -0,0 +1,28 @@
+<!-- Copyright 2020 The Chromium Authors. All rights reserved.
+  -- Use of this source code is governed by a BSD-style license that can be
+  -- found in the LICENSE file.
+-->
+
+<if expr="chromeos">
+<link rel="import" href="../shared_vars_chromeos_css.html">
+</if>
+<link rel="import" href="../../../ui/webui/resources/html/polymer.html">
+
+<link rel="import" href="chrome://resources/polymer/v1_0/paper-behaviors/paper-ripple-behavior.html">
+<link rel="import" href="../shared_vars_css.html">
+<link rel="import" href="foo.html">
+<if expr="chromeos">
+<link rel="import" href="bar.html">
+</if>
+
+<dom-module id="cr-test-foo">
+  <template>
+    <style>
+      div {
+        font-size: 2rem;
+      }
+    </style>
+    <div>Hello world</div>
+  </template>
+  <script src="dom_module.js"></script>
+</dom-module>
diff --git a/tools/traffic_annotation/summary/annotations.xml b/tools/traffic_annotation/summary/annotations.xml
index 4e4326c..636e32714 100644
--- a/tools/traffic_annotation/summary/annotations.xml
+++ b/tools/traffic_annotation/summary/annotations.xml
@@ -186,7 +186,7 @@
  <item id="network_location_provider" hash_code="23472048" type="1" second_id="96590038" content_hash_code="41087976" os_list="linux,windows" semantics_fields="1" policy_fields="3,4" file_path="services/device/geolocation/network_location_provider.cc"/>
  <item id="network_location_request" hash_code="96590038" type="2" content_hash_code="80741011" os_list="linux,windows" semantics_fields="2,3,4,5" policy_fields="-1" file_path="services/device/geolocation/network_location_request.cc"/>
  <item id="network_time_component" hash_code="46188932" type="0" content_hash_code="28051857" os_list="linux,windows" file_path="components/network_time/network_time_tracker.cc"/>
- <item id="new_tab_page_handler" hash_code="48673144" type="0" content_hash_code="100789845" os_list="linux,windows" file_path="chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc"/>
+ <item id="new_tab_page_handler" hash_code="48673144" type="0" content_hash_code="49174657" os_list="linux,windows" file_path="chrome/browser/ui/webui/new_tab_page/new_tab_page_handler.cc"/>
  <item id="notification_client" hash_code="78479125" type="0" content_hash_code="129874070" os_list="linux,windows" file_path="remoting/client/notification/notification_client.cc"/>
  <item id="ntp_contextual_suggestions_fetch" hash_code="95711309" type="0" deprecated="2019-04-18" content_hash_code="107035434" file_path=""/>
  <item id="ntp_custom_background" hash_code="92125886" type="0" content_hash_code="61176452" os_list="linux,windows" file_path="chrome/browser/search/instant_service.cc"/>
diff --git a/tools/win/OWNERS b/tools/win/OWNERS
index 0d31244e..327cc33 100644
--- a/tools/win/OWNERS
+++ b/tools/win/OWNERS
@@ -1,3 +1,4 @@
 ajgo@chromium.org
 brucedawson@chromium.org
+jessemckenna@google.com
 scottmg@chromium.org
diff --git a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
index a4a9ebd9..173c21a 100644
--- a/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
+++ b/ui/views/widget/desktop_aura/desktop_drag_drop_client_win.cc
@@ -52,6 +52,11 @@
                        MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_ABSOLUTE);
     ui::SendMouseEvent(screen_point, MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE);
     desktop_host_->SetInTouchDrag(true);
+    // Gesture state gets left in a state where you can't start
+    // another drag, unless it's cleaned up. Cleaning it up before starting
+    // drag drop also fixes an issue with getting two kGestureScrollBegin events
+    // in a row. See crbug.com/1120809.
+    source_window->CleanupGestureState();
   }
   base::WeakPtr<DesktopDragDropClientWin> alive(weak_factory_.GetWeakPtr());
 
@@ -69,9 +74,6 @@
       ui::DragDropTypes::DragOperationToDropEffect(operation), &effect);
   if (alive && source == ui::mojom::DragEventSource::kTouch) {
     desktop_host_->SetInTouchDrag(false);
-    // Gesture state gets left in a state where you can't start
-    // another drag, unless it's cleaned up.
-    source_window->CleanupGestureState();
   }
   drag_source_copy->set_data(nullptr);
 
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_icon.js b/ui/webui/resources/cr_components/chromeos/network/network_icon.js
index e01e0d9..e2a74ac 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_icon.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_icon.js
@@ -216,7 +216,12 @@
    * @private
    */
   showTechnology_() {
-    return this.getTechnology_() !== '' && this.showTechnologyBadge;
+    if (!this.networkState) {
+      return false;
+    }
+    return OncMojo.connectionStateIsConnected(
+               this.networkState.connectionState) &&
+        this.getTechnology_() !== '' && this.showTechnologyBadge;
   },
 
   /**
diff --git a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
index 5827515..deaf4e6 100644
--- a/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
+++ b/weblayer/browser/android/javatests/src/org/chromium/weblayer/test/NavigationTest.java
@@ -827,4 +827,32 @@
         mActivityTestRule.navigateAndWait(URL2);
         assertFalse(mCallback.onStartedCallback.isPageInitiated());
     }
+
+    /**
+     * This test verifies calling destroyTab() from within onNavigationFailed doesn't crash.
+     */
+    @Test
+    @SmallTest
+    @MinWebLayerVersion(87)
+    public void testDestroyTabInNavigationFailed() throws Throwable {
+        InstrumentationActivity activity = mActivityTestRule.launchShellWithUrl(null);
+        CallbackHelper callbackHelper = new CallbackHelper();
+        runOnUiThreadBlocking(() -> {
+            NavigationController navigationController = activity.getTab().getNavigationController();
+            navigationController.registerNavigationCallback(new NavigationCallback() {
+                @Override
+                public void onNavigationFailed(Navigation navigation) {
+                    navigationController.unregisterNavigationCallback(this);
+                    Tab tab = activity.getTab();
+                    tab.getBrowser().destroyTab(tab);
+                    callbackHelper.notifyCalled();
+                }
+            });
+        });
+        TestThreadUtils.runOnUiThreadBlocking(() -> {
+            activity.getTab().getNavigationController().navigate(
+                    Uri.parse("http://localhost:7/non_existent"));
+        });
+        callbackHelper.waitForFirst();
+    }
 }
diff --git a/weblayer/browser/browser_impl.cc b/weblayer/browser/browser_impl.cc
index 61b6bcfb..04c775fb 100644
--- a/weblayer/browser/browser_impl.cc
+++ b/weblayer/browser/browser_impl.cc
@@ -271,6 +271,12 @@
 #endif
 }
 
+#if defined(OS_ANDROID)
+void BrowserImpl::DestroyTabFromJava(Tab* tab) {
+  RemoveTab(tab);
+}
+#endif
+
 void BrowserImpl::AddTab(Tab* tab) {
   DCHECK(tab);
   TabImpl* tab_impl = static_cast<TabImpl*>(tab);
@@ -283,7 +289,12 @@
 }
 
 void BrowserImpl::DestroyTab(Tab* tab) {
+#if defined(OS_ANDROID)
+  Java_BrowserImpl_destroyTabImpl(AttachCurrentThread(), java_impl_,
+                                  static_cast<TabImpl*>(tab)->GetJavaTab());
+#else
   RemoveTab(tab);
+#endif
 }
 
 void BrowserImpl::SetActiveTab(Tab* tab) {
diff --git a/weblayer/browser/browser_impl.h b/weblayer/browser/browser_impl.h
index f5487af1..02363c1 100644
--- a/weblayer/browser/browser_impl.h
+++ b/weblayer/browser/browser_impl.h
@@ -97,6 +97,14 @@
   bool GetPasswordEchoEnabled();
   void SetWebPreferences(content::WebPreferences* prefs);
 
+#if defined(OS_ANDROID)
+  // On Android the Java Tab class owns the C++ Tab. DestroyTab() calls to the
+  // Java Tab class to initiate deletion. This function is called from the Java
+  // side, and must not call DestroyTab(), otherwise we get stuck in infinite
+  // recursion.
+  void DestroyTabFromJava(Tab* tab);
+#endif
+
   // Browser:
   void AddTab(Tab* tab) override;
   void DestroyTab(Tab* tab) override;
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
index dfd34f0..1fc0c5bf 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/BrowserImpl.java
@@ -434,6 +434,7 @@
         destroyTabImpl((TabImpl) iTab);
     }
 
+    @CalledByNative
     private void destroyTabImpl(TabImpl tab) {
         tab.destroy();
     }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java b/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java
index 072df06..4621ed6 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/SiteSettingsFragmentImpl.java
@@ -24,6 +24,7 @@
 import androidx.preference.PreferenceFragmentCompat;
 
 import org.chromium.components.browser_ui.settings.SettingsUtils;
+import org.chromium.components.browser_ui.site_settings.AllSiteSettings;
 import org.chromium.components.browser_ui.site_settings.SingleCategorySettings;
 import org.chromium.components.browser_ui.site_settings.SingleWebsiteSettings;
 import org.chromium.components.browser_ui.site_settings.SiteSettings;
@@ -150,6 +151,11 @@
                         mFragmentImpl.getEmbedderContext(), mFragmentImpl.getProfile().getName(),
                         newFragmentArgs.getString(SingleCategorySettings.EXTRA_CATEGORY),
                         newFragmentArgs.getString(SingleCategorySettings.EXTRA_TITLE));
+            } else if (newFragmentClassName.equals(AllSiteSettings.class.getName())) {
+                intent = SiteSettingsIntentHelper.createIntentForAllSites(
+                        mFragmentImpl.getEmbedderContext(), mFragmentImpl.getProfile().getName(),
+                        newFragmentArgs.getString(AllSiteSettings.EXTRA_CATEGORY),
+                        newFragmentArgs.getString(AllSiteSettings.EXTRA_TITLE));
             } else if (newFragmentClassName.equals(SingleWebsiteSettings.class.getName())) {
                 WebsiteAddress address;
                 if (newFragmentArgs.containsKey(SingleWebsiteSettings.EXTRA_SITE)) {
@@ -217,6 +223,14 @@
         // implementation fragments expect.
         Bundle fragmentArgs = intentExtras.getBundle(SiteSettingsFragmentArgs.FRAGMENT_ARGUMENTS);
         switch (intentExtras.getString(SiteSettingsFragmentArgs.FRAGMENT_NAME)) {
+            case SiteSettingsFragmentArgs.ALL_SITES:
+                mFragmentClass = AllSiteSettings.class;
+                mFragmentArguments = new Bundle();
+                mFragmentArguments.putString(AllSiteSettings.EXTRA_TITLE,
+                        fragmentArgs.getString(SiteSettingsFragmentArgs.ALL_SITES_TITLE));
+                mFragmentArguments.putString(AllSiteSettings.EXTRA_CATEGORY,
+                        fragmentArgs.getString(SiteSettingsFragmentArgs.ALL_SITES_TYPE));
+                break;
             case SiteSettingsFragmentArgs.CATEGORY_LIST:
                 mFragmentClass = SiteSettings.class;
                 mFragmentArguments = null;
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsFragmentArgs.java b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsFragmentArgs.java
index a983e34..6278b6d 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsFragmentArgs.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsFragmentArgs.java
@@ -14,6 +14,7 @@
     String FRAGMENT_ARGUMENTS = "fragment_arguments";
 
     // FRAGMENT_NAME values
+    String ALL_SITES = "all_sites";
     String CATEGORY_LIST = "category_list";
     String SINGLE_CATEGORY = "single_category";
     String SINGLE_WEBSITE = "single_website";
@@ -24,4 +25,8 @@
     // SINGLE_CATEGORY argument names
     String SINGLE_CATEGORY_TITLE = "title";
     String SINGLE_CATEGORY_TYPE = "type";
+
+    // ALL_SITES argument names
+    String ALL_SITES_TITLE = "title";
+    String ALL_SITES_TYPE = "type";
 }
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsIntentHelper.java b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsIntentHelper.java
index 4b1b882..34a7b79c 100644
--- a/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsIntentHelper.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/interfaces/SiteSettingsIntentHelper.java
@@ -50,6 +50,21 @@
         return createIntentWithExtras(context, extras);
     }
 
+    /** Creates an Intent that launches the all sites settings UI. */
+    public static Intent createIntentForAllSites(
+            Context context, String profileName, String type, String title) {
+        Bundle extras = new Bundle();
+        extras.putString(SiteSettingsFragmentArgs.PROFILE_NAME, profileName);
+        extras.putString(
+                SiteSettingsFragmentArgs.FRAGMENT_NAME, SiteSettingsFragmentArgs.ALL_SITES);
+
+        Bundle fragmentArgs = new Bundle();
+        fragmentArgs.putString(SiteSettingsFragmentArgs.ALL_SITES_TITLE, title);
+        fragmentArgs.putString(SiteSettingsFragmentArgs.ALL_SITES_TYPE, type);
+        extras.putBundle(SiteSettingsFragmentArgs.FRAGMENT_ARGUMENTS, fragmentArgs);
+        return createIntentWithExtras(context, extras);
+    }
+
     private static Intent createIntentWithExtras(Context context, Bundle extras) {
         Intent intent = new Intent();
         intent.setClassName(context, SiteSettingsFragmentArgs.ACTIVITY_CLASS_NAME);
diff --git a/weblayer/browser/navigation_browsertest.cc b/weblayer/browser/navigation_browsertest.cc
index c869c59..ba8caf7c 100644
--- a/weblayer/browser/navigation_browsertest.cc
+++ b/weblayer/browser/navigation_browsertest.cc
@@ -14,6 +14,7 @@
 #include "net/test/embedded_test_server/controllable_http_response.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "net/test/embedded_test_server/http_response.h"
+#include "weblayer/public/browser.h"
 #include "weblayer/public/navigation.h"
 #include "weblayer/public/navigation_controller.h"
 #include "weblayer/public/navigation_observer.h"
@@ -68,8 +69,11 @@
       completed_callback_.Run(navigation);
   }
   void NavigationFailed(Navigation* navigation) override {
-    if (failed_callback_)
-      failed_callback_.Run(navigation);
+    // As |this| may be deleted when running the callback, the callback must be
+    // copied before running. To do otherwise results in use-after-free.
+    auto callback = failed_callback_;
+    if (callback)
+      callback.Run(navigation);
   }
 
  private:
@@ -259,6 +263,25 @@
   run_loop.Run();
 }
 
+IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, DestroyTabInNavigation) {
+  ASSERT_TRUE(embedded_test_server()->Start());
+  Tab* new_tab = shell()->browser()->CreateTab();
+  base::RunLoop run_loop;
+  std::unique_ptr<NavigationObserverImpl> observer =
+      std::make_unique<NavigationObserverImpl>(
+          new_tab->GetNavigationController());
+  observer->SetFailedCallback(
+      base::BindLambdaForTesting([&](Navigation* navigation) {
+        observer.reset();
+        shell()->browser()->DestroyTab(new_tab);
+        run_loop.Quit();
+      }));
+  new_tab->GetNavigationController()->Navigate(
+      embedded_test_server()->GetURL("/simple_pageX.html"));
+
+  run_loop.Run();
+}
+
 IN_PROC_BROWSER_TEST_F(NavigationBrowserTest, StopInOnRedirect) {
   ASSERT_TRUE(embedded_test_server()->Start());
   base::RunLoop run_loop;
diff --git a/weblayer/browser/navigation_controller_impl.cc b/weblayer/browser/navigation_controller_impl.cc
index b7788f47..f931bf64 100644
--- a/weblayer/browser/navigation_controller_impl.cc
+++ b/weblayer/browser/navigation_controller_impl.cc
@@ -31,6 +31,29 @@
 
 namespace weblayer {
 
+class NavigationControllerImpl::DelayDeletionHelper {
+ public:
+  explicit DelayDeletionHelper(NavigationControllerImpl* controller)
+      : controller_(controller->weak_ptr_factory_.GetWeakPtr()) {
+    // This should never be called reentrantly.
+    DCHECK(!controller->should_delay_web_contents_deletion_);
+    controller->should_delay_web_contents_deletion_ = true;
+  }
+
+  DelayDeletionHelper(const DelayDeletionHelper&) = delete;
+  DelayDeletionHelper& operator=(const DelayDeletionHelper&) = delete;
+
+  ~DelayDeletionHelper() {
+    if (controller_)
+      controller_->should_delay_web_contents_deletion_ = false;
+  }
+
+  bool WasControllerDeleted() { return controller_.get() == nullptr; }
+
+ private:
+  base::WeakPtr<NavigationControllerImpl> controller_;
+};
+
 // NavigationThrottle implementation responsible for delaying certain
 // operations and performing them when safe. This is necessary as content
 // does allow certain operations to be called at certain times. For example,
@@ -343,6 +366,7 @@
   if (!navigation_handle->IsInMainFrame())
     return;
 
+  DelayDeletionHelper deletion_helper(this);
   DCHECK(navigation_map_.find(navigation_handle) != navigation_map_.end());
   auto* navigation = navigation_map_[navigation_handle].get();
   if (navigation_handle->GetNetErrorCode() == net::OK &&
@@ -354,10 +378,15 @@
       Java_NavigationControllerImpl_navigationCompleted(
           AttachCurrentThread(), java_controller_,
           navigation->java_navigation());
+      if (deletion_helper.WasControllerDeleted())
+        return;
     }
 #endif
-    for (auto& observer : observers_)
+    for (auto& observer : observers_) {
       observer.NavigationCompleted(navigation);
+      if (deletion_helper.WasControllerDeleted())
+        return;
+    }
   } else {
 #if defined(OS_ANDROID)
     if (java_controller_) {
@@ -366,10 +395,15 @@
       Java_NavigationControllerImpl_navigationFailed(
           AttachCurrentThread(), java_controller_,
           navigation->java_navigation());
+      if (deletion_helper.WasControllerDeleted())
+        return;
     }
 #endif
-    for (auto& observer : observers_)
+    for (auto& observer : observers_) {
       observer.NavigationFailed(navigation);
+      if (deletion_helper.WasControllerDeleted())
+        return;
+    }
   }
 
   // Note InsertVisualStateCallback currently does not take into account
diff --git a/weblayer/browser/navigation_controller_impl.h b/weblayer/browser/navigation_controller_impl.h
index d7e24bd..240573b 100644
--- a/weblayer/browser/navigation_controller_impl.h
+++ b/weblayer/browser/navigation_controller_impl.h
@@ -73,7 +73,13 @@
   bool IsNavigationEntrySkippable(JNIEnv* env, int index);
 #endif
 
+  bool should_delay_web_contents_deletion() {
+    return should_delay_web_contents_deletion_;
+  }
+
  private:
+  class DelayDeletionHelper;
+
   class NavigationThrottleImpl;
 
   // Called from NavigationControllerImpl::WillRedirectRequest(). See
@@ -134,6 +140,11 @@
   base::android::ScopedJavaGlobalRef<jobject> java_controller_;
 #endif
 
+  // Set to true while processing an observer/callback and it's unsafe to
+  // delete the WebContents. This is not used for all callbacks, just the
+  // ones that we need to allow deletion from (such as completed/failed).
+  bool should_delay_web_contents_deletion_ = false;
+
   base::WeakPtrFactory<NavigationControllerImpl> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(NavigationControllerImpl);
diff --git a/weblayer/browser/persistence/browser_persister_browsertest.cc b/weblayer/browser/persistence/browser_persister_browsertest.cc
index aa23fc94..a8eeec2 100644
--- a/weblayer/browser/persistence/browser_persister_browsertest.cc
+++ b/weblayer/browser/persistence/browser_persister_browsertest.cc
@@ -47,50 +47,6 @@
 namespace {
 using testing::UnorderedElementsAre;
 
-class OneShotNavigationObserver : public NavigationObserver {
- public:
-  explicit OneShotNavigationObserver(Shell* shell) : tab_(shell->tab()) {
-    tab_->GetNavigationController()->AddObserver(this);
-  }
-
-  ~OneShotNavigationObserver() override {
-    tab_->GetNavigationController()->RemoveObserver(this);
-  }
-
-  void WaitForNavigation() { run_loop_.Run(); }
-
-  bool completed() { return completed_; }
-  bool is_error_page() { return is_error_page_; }
-  Navigation::LoadError load_error() { return load_error_; }
-  int http_status_code() { return http_status_code_; }
-  NavigationState navigation_state() { return navigation_state_; }
-
- private:
-  // NavigationObserver implementation:
-  void NavigationCompleted(Navigation* navigation) override {
-    completed_ = true;
-    Finish(navigation);
-  }
-
-  void NavigationFailed(Navigation* navigation) override { Finish(navigation); }
-
-  void Finish(Navigation* navigation) {
-    is_error_page_ = navigation->IsErrorPage();
-    load_error_ = navigation->GetLoadError();
-    http_status_code_ = navigation->GetHttpStatusCode();
-    navigation_state_ = navigation->GetState();
-    run_loop_.Quit();
-  }
-
-  base::RunLoop run_loop_;
-  Tab* tab_;
-  bool completed_ = false;
-  bool is_error_page_ = false;
-  Navigation::LoadError load_error_ = Navigation::kNoError;
-  int http_status_code_ = 0;
-  NavigationState navigation_state_ = NavigationState::kWaitingResponse;
-};
-
 class BrowserObserverImpl : public BrowserObserver {
  public:
   static void WaitForNewTab(Browser* browser) {
diff --git a/weblayer/browser/profile_impl.cc b/weblayer/browser/profile_impl.cc
index 2ccc51e6..4889094 100644
--- a/weblayer/browser/profile_impl.cc
+++ b/weblayer/browser/profile_impl.cc
@@ -28,6 +28,7 @@
 #include "content/public/browser/download_manager.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/storage_partition.h"
+#include "content/public/browser/web_contents.h"
 #include "services/network/public/mojom/network_context.mojom.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
@@ -232,6 +233,10 @@
 }
 
 ProfileImpl::~ProfileImpl() {
+  // Destroy any scheduled WebContents. These implicitly refer to the
+  // BrowserContext and must be destroyed before the BrowserContext.
+  web_contents_to_delete_.clear();
+
   if (browser_context_) {
     BrowserContextDependencyManager::GetInstance()
         ->DestroyBrowserContextServices(browser_context_.get());
@@ -260,6 +265,16 @@
   GetObservers().RemoveObserver(observer);
 }
 
+void ProfileImpl::DeleteWebContentsSoon(
+    std::unique_ptr<content::WebContents> web_contents) {
+  if (web_contents_to_delete_.empty()) {
+    base::SequencedTaskRunnerHandle::Get()->PostTask(
+        FROM_HERE, base::BindOnce(&ProfileImpl::DeleteScheduleWebContents,
+                                  weak_ptr_factory_.GetWeakPtr()));
+  }
+  web_contents_to_delete_.push_back(std::move(web_contents));
+}
+
 BrowserContextImpl* ProfileImpl::GetBrowserContext() {
   if (browser_context_)
     return browser_context_.get();
@@ -667,4 +682,8 @@
                        [this](BrowserImpl* b) { return b->profile() == this; });
 }
 
+void ProfileImpl::DeleteScheduleWebContents() {
+  web_contents_to_delete_.clear();
+}
+
 }  // namespace weblayer
diff --git a/weblayer/browser/profile_impl.h b/weblayer/browser/profile_impl.h
index 75c3162..5707d85 100644
--- a/weblayer/browser/profile_impl.h
+++ b/weblayer/browser/profile_impl.h
@@ -6,6 +6,7 @@
 #define WEBLAYER_BROWSER_PROFILE_IMPL_H_
 
 #include <set>
+#include <vector>
 
 #include "base/files/file_path.h"
 #include "base/macros.h"
@@ -23,6 +24,7 @@
 
 namespace content {
 class BrowserContext;
+class WebContents;
 }
 
 namespace weblayer {
@@ -64,6 +66,11 @@
   static void AddProfileObserver(ProfileObserver* observer);
   static void RemoveProfileObserver(ProfileObserver* observer);
 
+  // Deletes |web_contents| after a delay. This is used if the owning Tab is
+  // deleted and it's not safe to delete the WebContents.
+  void DeleteWebContentsSoon(
+      std::unique_ptr<content::WebContents> web_contents);
+
   BrowserContextImpl* GetBrowserContext();
 
   // Called when the download subsystem has finished initializing. By this point
@@ -156,6 +163,8 @@
   // Returns the number of Browsers with this profile.
   int GetNumberOfBrowsers();
 
+  void DeleteScheduleWebContents();
+
   ProfileInfo info_;
 
   std::unique_ptr<BrowserContextImpl> browser_context_;
@@ -178,6 +187,10 @@
   // CancelableTaskTracker is owned by Profile.
   base::CancelableTaskTracker cancelable_task_tracker_;
 
+  std::vector<std::unique_ptr<content::WebContents>> web_contents_to_delete_;
+
+  base::WeakPtrFactory<ProfileImpl> weak_ptr_factory_{this};
+
   DISALLOW_COPY_AND_ASSIGN(ProfileImpl);
 };
 
diff --git a/weblayer/browser/tab_impl.cc b/weblayer/browser/tab_impl.cc
index 29d8e3f..2a95b92 100644
--- a/weblayer/browser/tab_impl.cc
+++ b/weblayer/browser/tab_impl.cc
@@ -301,8 +301,7 @@
 
   sessions::SessionTabHelper::CreateForWebContents(
       web_contents_.get(),
-      base::BindRepeating(&TabImpl::GetSessionServiceTabHelperDelegate,
-                          base::Unretained(this)));
+      base::BindRepeating(&TabImpl::GetSessionServiceTabHelperDelegate));
 
   permissions::PermissionRequestManager::CreateForWebContents(
       web_contents_.get());
@@ -361,6 +360,14 @@
 #endif
   Observe(nullptr);
   web_contents_->SetDelegate(nullptr);
+  if (navigation_controller_->should_delay_web_contents_deletion()) {
+    // Remove the linkage between this and the WebContents.
+    web_contents_->RemoveUserData(&kWebContentsUserDataKey);
+    // Have Profile handle the task posting to ensure the WebContents is
+    // deleted before Profile. To do otherwise means it would be possible for
+    // the Profile to outlive the WebContents, which is problematic (crash).
+    profile_->DeleteWebContentsSoon(std::move(web_contents_));
+  }
   web_contents_.reset();
   GetTabs().erase(this);
 }
@@ -568,7 +575,8 @@
   TabImpl* tab_impl = reinterpret_cast<TabImpl*>(tab);
   DCHECK(tab_impl);
   DCHECK(tab_impl->browser());
-  tab_impl->browser()->DestroyTab(tab_impl);
+  // Don't call Browser::DestroyTab() as it calls back to the java side.
+  tab_impl->browser()->DestroyTabFromJava(tab_impl);
 }
 
 ScopedJavaLocalRef<jobject> TabImpl::GetWebContents(JNIEnv* env) {
@@ -1246,10 +1254,11 @@
   return find_in_page::FindTabHelper::FromWebContents(web_contents_.get());
 }
 
+// static
 sessions::SessionTabHelperDelegate* TabImpl::GetSessionServiceTabHelperDelegate(
     content::WebContents* web_contents) {
-  DCHECK_EQ(web_contents, web_contents_.get());
-  return browser_ ? browser_->browser_persister() : nullptr;
+  TabImpl* tab = FromWebContents(web_contents);
+  return (tab && tab->browser_) ? tab->browser_->browser_persister() : nullptr;
 }
 
 bool TabImpl::SetDataInternal(const std::map<std::string, std::string>& data) {
diff --git a/weblayer/browser/tab_impl.h b/weblayer/browser/tab_impl.h
index 4c56808..4476276 100644
--- a/weblayer/browser/tab_impl.h
+++ b/weblayer/browser/tab_impl.h
@@ -345,7 +345,7 @@
   // Returns the FindTabHelper for the page, or null if none exists.
   find_in_page::FindTabHelper* GetFindTabHelper();
 
-  sessions::SessionTabHelperDelegate* GetSessionServiceTabHelperDelegate(
+  static sessions::SessionTabHelperDelegate* GetSessionServiceTabHelperDelegate(
       content::WebContents* web_contents);
 
 #if defined(OS_ANDROID)
diff --git a/weblayer/weblayer_resource_exclusions.gni b/weblayer/weblayer_resource_exclusions.gni
index 169673e..f5498829 100644
--- a/weblayer/weblayer_resource_exclusions.gni
+++ b/weblayer/weblayer_resource_exclusions.gni
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-_material_package = "*com_google_android_material_material*"
+_material_package = "*com_google_android_material*"
 
 weblayer_resource_exclusion_exceptions = [
   # TextInputLayout (used for HTTP Auth dialog)
@@ -37,7 +37,7 @@
   "${_material_package}:[Tt]oolbarLayout",
 ]
 
-_material_package = "com_google_android_material_material.*"
+_material_package = "com_google_android_material.*"
 
 # Used only by alert dialog on tiny screens.
 weblayer_resource_exclusion_regex = "${_material_package}values-small"