diff --git a/DEPS b/DEPS
index 3399c89..eac50e7b 100644
--- a/DEPS
+++ b/DEPS
@@ -162,7 +162,7 @@
   # 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': '6d1c0d4196f19537cc64f74bacc7d123de3be454',
+  'skia_revision': 'c30f1a936d84d476bb08bd9d0a6e56eddb6fe984',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
@@ -174,15 +174,15 @@
   # 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': 'e64cea302acd9f92791d30c9c7344e9fac18de36',
+  'angle_revision': '32d6006bf2ef9ea1cc5a705df3493ed7cca16821',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling SwiftShader
   # and whatever else without interference from each other.
-  'swiftshader_revision': '6726fcfdd4aa4baacdbf296688509e28c7c873e5',
+  'swiftshader_revision': '374b99bd6607afd7fa200a7f205d3cc937329d32',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling PDFium
   # and whatever else without interference from each other.
-  'pdfium_revision': 'a6b07058f9525c42b021f6f01d5e0da3215f46ed',
+  'pdfium_revision': 'fd9b7e654b0586ffc67748b81ee6eff0fc5d3957',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
@@ -225,7 +225,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': '52c5d30be8af069e61f9a5049544b9fa27079da5',
+  'catapult_revision': '19f3c21a61d3895dd16d836b6856c77ddedb4480',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -293,7 +293,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.
-  'shaderc_revision': '0597d39ec6378dc1c872686af1fa17988faf26c8',
+  'shaderc_revision': '9cb02b61806990ce2667d9721ef8c4d6bbf7cdc2',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
@@ -842,7 +842,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'e2cd0bf51938c380a00720edf621f7418bb7b836',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'a588508b2b95dd897de627502379c43ff05ae346',
       'condition': 'checkout_linux',
   },
 
@@ -867,7 +867,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'b3b46a268903d2cb09430d29957bf13b0cb06e4c',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '4ebfe4643bf06794bbf2620e4d2d8dda21f62987',
 
   'src/third_party/devtools-node-modules':
     Var('chromium_git') + '/external/github.com/ChromeDevTools/devtools-node-modules' + '@' + Var('devtools_node_modules_revision'),
@@ -1246,7 +1246,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' + 'debe648a37fc10b3b4b527d7e5c50906aac3daa6',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '9bf9b907280b4b10447bed4725bc604a363ded6b',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + '6f3e5028eb65d0b4c5fdd792106ac4c84eee1eb3',
@@ -1414,7 +1414,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'abaae129d9a0c6e1e092067e0b105475df43352e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '30323e2fb25af33bec3a6ab7f4d081bfbff56fde',
+    Var('webrtc_git') + '/src.git' + '@' + 'b64d65e67bff3c9a0acdc56d9398c5aad49d3117',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1455,7 +1455,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@8b1435392e02e34f3a70abbd6bfcfc00bdfa6afa',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@b6cad5f3d3380916d65eb4ea66e26559b264ccc1',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/android_webview/tools/run_cts.pydeps b/android_webview/tools/run_cts.pydeps
index 3cd52bb..b21bd7d5 100644
--- a/android_webview/tools/run_cts.pydeps
+++ b/android_webview/tools/run_cts.pydeps
@@ -43,6 +43,7 @@
 //third_party/catapult/devil/devil/android/sdk/aapt.py
 //third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
 //third_party/catapult/devil/devil/android/sdk/build_tools.py
+//third_party/catapult/devil/devil/android/sdk/bundletool.py
 //third_party/catapult/devil/devil/android/sdk/intent.py
 //third_party/catapult/devil/devil/android/sdk/keyevent.py
 //third_party/catapult/devil/devil/android/sdk/split_select.py
diff --git a/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests.xml b/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests.xml
index 5104b35..2f89bc0 100644
--- a/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests.xml
+++ b/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests.xml
@@ -13,7 +13,8 @@
       android:layout_alignParentStart="true"
       android:layout_marginStart="0dp"
       />
-  <RelativeLayout
+  <LinearLayout
+      android:orientation="vertical"
       android:visibility="invisible"
       android:background="@android:color/black"
       android:layout_width="match_parent"
@@ -25,11 +26,9 @@
         android:layout_width="match_parent"
         android:padding="0dp"
         android:layout_height="wrap_content"
-        android:layout_alignParentStart="true"
         android:layout_marginStart="0dp"
         android:layout_marginTop="0dp"
-        android:layout_alignParentTop="true"
-        android:id="@+id/childLinearLayout">
+        android:id="@+id/childTopControls">
       <Button
           android:text="@string/close_button"
           android:layout_width="wrap_content"
@@ -53,14 +52,5 @@
           android:textStyle="bold"
           />
     </LinearLayout>
-    <RelativeLayout
-        android:layout_below="@id/childLinearLayout"
-        android:background="@android:color/holo_blue_dark"
-        android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:id="@+id/mainBrowserLayout"
-        android:weightSum="1">
-    </RelativeLayout>
-  </RelativeLayout>
+  </LinearLayout>
 </RelativeLayout>
diff --git a/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebPlatformTestsActivity.java b/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebPlatformTestsActivity.java
index 4542e53..8063a42 100644
--- a/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebPlatformTestsActivity.java
+++ b/android_webview/tools/system_webview_shell/apk/src/org/chromium/webview_shell/WebPlatformTestsActivity.java
@@ -14,9 +14,10 @@
 import android.webkit.WebView;
 import android.webkit.WebViewClient;
 import android.widget.Button;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import org.chromium.base.Log;
 import org.chromium.base.VisibleForTesting;
 
 /**
@@ -30,6 +31,8 @@
  *                         children, which is not supported for now.
  */
 public class WebPlatformTestsActivity extends Activity {
+    private static final String TAG = "WPTActivity";
+
     /**
      * A callback for testing.
      */
@@ -43,8 +46,7 @@
 
     private WebView mWebView;
     private WebView mChildWebView;
-    private RelativeLayout mChildLayout;
-    private RelativeLayout mBrowserLayout;
+    private LinearLayout mChildLayout;
     private Button mChildCloseButton;
     private TextView mChildTitleText;
     private TestCallback mTestCallback;
@@ -73,14 +75,15 @@
                 }
             });
             // Add the new WebView to the layout
-            mChildWebView.setLayoutParams(new RelativeLayout.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+            mChildWebView.setLayoutParams(
+                    new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
+                            LinearLayout.LayoutParams.MATCH_PARENT));
             // Tell the transport about the new view
             WebView.WebViewTransport transport = (WebView.WebViewTransport) resultMsg.obj;
             transport.setWebView(mChildWebView);
             resultMsg.sendToTarget();
 
-            mBrowserLayout.addView(mChildWebView);
+            mChildLayout.addView(mChildWebView);
             // Make the child webview's layout visible
             mChildLayout.setVisibility(View.VISIBLE);
             if (mTestCallback != null) mTestCallback.onChildLayoutVisible();
@@ -102,18 +105,30 @@
         mChildWebView = null;
     }
 
+    private String getUrlFromIntent() {
+        if (getIntent() == null) return null;
+        return getIntent().getDataString();
+    }
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         WebView.setWebContentsDebuggingEnabled(true);
         setContentView(R.layout.activity_web_platform_tests);
         setUpWidgets();
-        // This is equivalent to Chrome's WPT setup.
-        setUpWebView("about:blank");
+        String url = getUrlFromIntent();
+        if (url == null) {
+            // This is equivalent to Chrome's WPT setup.
+            setUpWebView("about:blank");
+        } else {
+            Log.w(TAG,
+                    "Handling a non-empty intent. This should only be used for testing. URL: "
+                            + url);
+            setUpWebView(url);
+        }
     }
 
     private void setUpWidgets() {
-        mBrowserLayout = findViewById(R.id.mainBrowserLayout);
         mChildLayout = findViewById(R.id.childLayout);
         mChildTitleText = findViewById(R.id.childTitleText);
         mChildCloseButton = findViewById(R.id.childCloseButton);
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 73d0a7e7..8e8c2c1 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1631,6 +1631,7 @@
     "accessibility/touch_accessibility_enabler_unittest.cc",
     "accessibility/touch_exploration_controller_unittest.cc",
     "accessibility/touch_exploration_manager_unittest.cc",
+    "ambient/model/photo_model_unittest.cc",
     "ambient/ui/ambient_container_view_unittest.cc",
     "app_list/app_list_controller_impl_unittest.cc",
     "app_list/app_list_metrics_unittest.cc",
diff --git a/ash/ambient/ambient_controller.cc b/ash/ambient/ambient_controller.cc
index c4053da..56c58aae 100644
--- a/ash/ambient/ambient_controller.cc
+++ b/ash/ambient/ambient_controller.cc
@@ -82,17 +82,43 @@
   if (!PhotoController::Get())
     return;
 
-  PhotoController::Get()->GetNextImage(base::BindOnce(
-      &AmbientController::OnPhotoDownloaded, weak_factory_.GetWeakPtr()));
+  if (model_.ShouldFetchImmediately()) {
+    // TODO(b/140032139): Defer downloading image if it is animating.
+    constexpr base::TimeDelta kAnimationDuration =
+        base::TimeDelta::FromMilliseconds(250);
+    base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
+        FROM_HERE,
+        base::BindOnce(&AmbientController::GetNextImage,
+                       weak_factory_.GetWeakPtr()),
+        kAnimationDuration);
+  } else {
+    model_.ShowNextImage();
+    ScheduleRefreshImage();
+  }
+}
 
-  constexpr base::TimeDelta kTimeOut = base::TimeDelta::FromMilliseconds(1000);
+void AmbientController::ScheduleRefreshImage() {
+  base::TimeDelta refresh_interval;
+  if (!model_.ShouldFetchImmediately()) {
+    // TODO(b/139953713): Change to a correct time interval.
+    refresh_interval = base::TimeDelta::FromSeconds(5);
+  }
+
   refresh_timer_.Start(
-      FROM_HERE, kTimeOut,
+      FROM_HERE, refresh_interval,
       base::BindOnce(&AmbientController::RefreshImage, base::Unretained(this)));
 }
 
+void AmbientController::GetNextImage() {
+  PhotoController::Get()->GetNextImage(base::BindOnce(
+      &AmbientController::OnPhotoDownloaded, weak_factory_.GetWeakPtr()));
+}
+
 void AmbientController::OnPhotoDownloaded(const gfx::ImageSkia& image) {
-  model_.AddNextImage(image);
+  if (!image.isNull())
+    model_.AddNextImage(image);
+
+  ScheduleRefreshImage();
 }
 
 AmbientContainerView* AmbientController::GetAmbientContainerViewForTesting() {
diff --git a/ash/ambient/ambient_controller.h b/ash/ambient/ambient_controller.h
index 6c0c3ebc..8896608 100644
--- a/ash/ambient/ambient_controller.h
+++ b/ash/ambient/ambient_controller.h
@@ -31,8 +31,13 @@
   void OnWidgetDestroying(views::Widget* widget) override;
 
   void Toggle();
+
   void AddPhotoModelObserver(PhotoModelObserver* observer);
+
   void RemovePhotoModelObserver(PhotoModelObserver* observer);
+
+  const PhotoModel& model() const { return model_; }
+
   AmbientContainerView* GetAmbientContainerViewForTesting();
 
  private:
@@ -41,6 +46,8 @@
   void CreateContainerView();
   void DestroyContainerView();
   void RefreshImage();
+  void ScheduleRefreshImage();
+  void GetNextImage();
   void OnPhotoDownloaded(const gfx::ImageSkia& image);
 
   AmbientContainerView* container_view_ = nullptr;
diff --git a/ash/ambient/model/photo_model.cc b/ash/ambient/model/photo_model.cc
index ab4d99c..497aa8d5 100644
--- a/ash/ambient/model/photo_model.cc
+++ b/ash/ambient/model/photo_model.cc
@@ -8,6 +8,15 @@
 
 namespace ash {
 
+namespace {
+
+// This class has a local in memory cache of downloaded photos. This is the
+// desired max number of photos stored in cache. If this is an even number,
+// the max number could be one larger.
+constexpr int kImageBufferLength = 5;
+
+}  // namespace
+
 PhotoModel::PhotoModel() = default;
 
 PhotoModel::~PhotoModel() = default;
@@ -20,13 +29,70 @@
   observers_.RemoveObserver(observer);
 }
 
-void PhotoModel::AddNextImage(const gfx::ImageSkia& image) {
-  NotifyImageAvailable(image);
+bool PhotoModel::ShouldFetchImmediately() const {
+  // If currently shown image is close to the end of images cache, we prefetch
+  // more image.
+  const int next_load_image_index = GetImageBufferLength() / 2;
+  return images_.empty() ||
+         current_image_index_ >
+             static_cast<int>(images_.size() - 1 - next_load_image_index);
 }
 
-void PhotoModel::NotifyImageAvailable(const gfx::ImageSkia& image) {
+void PhotoModel::ShowNextImage() {
+  // Do not show next if have not downloaded enough images.
+  if (ShouldFetchImmediately())
+    return;
+
+  const int max_current_image_index = GetImageBufferLength() / 2;
+  if (current_image_index_ >= max_current_image_index) {
+    // Pop the first image and keep |current_image_index_| unchanged, will be
+    // equivalent to show next image.
+    images_.pop_front();
+  } else {
+    ++current_image_index_;
+  }
+  NotifyImagesChanged();
+}
+
+void PhotoModel::AddNextImage(const gfx::ImageSkia& image) {
+  images_.emplace_back(image);
+
+  // Update the first image.
+  if (images_.size() == 1)
+    NotifyImagesChanged();
+}
+
+gfx::ImageSkia PhotoModel::GetPrevImage() const {
+  if (current_image_index_ == 0)
+    return gfx::ImageSkia();
+
+  return images_[current_image_index_ - 1];
+}
+
+gfx::ImageSkia PhotoModel::GetCurrImage() const {
+  if (images_.empty())
+    return gfx::ImageSkia();
+
+  return images_[current_image_index_];
+}
+
+gfx::ImageSkia PhotoModel::GetNextImage() const {
+  if (images_.empty() ||
+      static_cast<int>(images_.size() - current_image_index_) == 1) {
+    return gfx::ImageSkia();
+  }
+
+  return images_[current_image_index_ + 1];
+}
+
+void PhotoModel::NotifyImagesChanged() {
   for (auto& observer : observers_)
-    observer.OnImageAvailable(image);
+    observer.OnImagesChanged();
+}
+
+int PhotoModel::GetImageBufferLength() const {
+  return buffer_length_for_testing_ == -1 ? kImageBufferLength
+                                          : buffer_length_for_testing_;
 }
 
 }  // namespace ash
diff --git a/ash/ambient/model/photo_model.h b/ash/ambient/model/photo_model.h
index 1713b3c..54b3aca 100644
--- a/ash/ambient/model/photo_model.h
+++ b/ash/ambient/model/photo_model.h
@@ -5,12 +5,11 @@
 #ifndef ASH_AMBIENT_MODEL_PHOTO_MODEL_H_
 #define ASH_AMBIENT_MODEL_PHOTO_MODEL_H_
 
+#include "ash/ash_export.h"
+#include "base/containers/circular_deque.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
-
-namespace gfx {
-class ImageSkia;
-}  // namespace gfx
+#include "ui/gfx/image/image_skia.h"
 
 namespace ash {
 
@@ -18,7 +17,7 @@
 
 // The model belonging to AmbientController which tracks photo state and
 // notifies a pool of observers.
-class PhotoModel {
+class ASH_EXPORT PhotoModel {
  public:
   PhotoModel();
   ~PhotoModel();
@@ -26,10 +25,36 @@
   void AddObserver(PhotoModelObserver* observer);
   void RemoveObserver(PhotoModelObserver* observer);
 
+  // Prefetch one more image for ShowNextImage animations.
+  bool ShouldFetchImmediately() const;
+
+  // Show the next downloaded image.
+  void ShowNextImage();
+
+  // Add image to local storage.
   void AddNextImage(const gfx::ImageSkia& image);
 
+  // Get images from local storage. Could be null image.
+  gfx::ImageSkia GetPrevImage() const;
+  gfx::ImageSkia GetCurrImage() const;
+  gfx::ImageSkia GetNextImage() const;
+
+  void set_buffer_length_for_testing(int length) {
+    buffer_length_for_testing_ = length;
+  }
+
  private:
-  void NotifyImageAvailable(const gfx::ImageSkia& image);
+  void NotifyImagesChanged();
+  int GetImageBufferLength() const;
+
+  // A local cache for downloaded images. This buffer is split into two equal
+  // length of kImageBufferLength / 2 for previous seen and next unseen images.
+  base::circular_deque<gfx::ImageSkia> images_;
+
+  // The index of currently shown image.
+  int current_image_index_ = 0;
+
+  int buffer_length_for_testing_ = -1;
 
   base::ObserverList<ash::PhotoModelObserver> observers_;
 
diff --git a/ash/ambient/model/photo_model_observer.h b/ash/ambient/model/photo_model_observer.h
index a3060da..a4a2da6c 100644
--- a/ash/ambient/model/photo_model_observer.h
+++ b/ash/ambient/model/photo_model_observer.h
@@ -8,18 +8,14 @@
 #include "ash/public/cpp/ash_public_export.h"
 #include "base/observer_list_types.h"
 
-namespace gfx {
-class ImageSkia;
-}  // namespace gfx
-
 namespace ash {
 
 // A checked observer which receives notification of changes to the PhotoModel
 // in ambient mode.
 class ASH_PUBLIC_EXPORT PhotoModelObserver : public base::CheckedObserver {
  public:
-  // Invoked when the requested image is available to show.
-  virtual void OnImageAvailable(const gfx::ImageSkia& image) = 0;
+  // Invoked when prev/current/next images changed.
+  virtual void OnImagesChanged() = 0;
 
  protected:
   ~PhotoModelObserver() override = default;
diff --git a/ash/ambient/model/photo_model_unittest.cc b/ash/ambient/model/photo_model_unittest.cc
new file mode 100644
index 0000000..b718db44
--- /dev/null
+++ b/ash/ambient/model/photo_model_unittest.cc
@@ -0,0 +1,113 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/ambient/model/photo_model.h"
+
+#include "ash/ambient/model/photo_model_observer.h"
+#include "ash/test/ash_test_base.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/gfx/image/image_unittest_util.h"
+#include "ui/views/controls/image_view.h"
+
+namespace ash {
+
+namespace {
+
+// This class has a local in memory cache of downloaded photos. This is the max
+// number of photos before and after currently shown image.
+constexpr int kImageBufferLength = 3;
+
+}  // namespace
+
+class PhotoModelTest : public AshTestBase {
+ public:
+  PhotoModelTest() = default;
+  ~PhotoModelTest() override = default;
+
+  void SetUp() override {
+    AshTestBase::SetUp();
+    model_ = std::make_unique<PhotoModel>();
+    model_->set_buffer_length_for_testing(kImageBufferLength);
+  }
+
+  void TearDown() override {
+    model_.reset();
+    AshTestBase::TearDown();
+  }
+
+ protected:
+  std::unique_ptr<PhotoModel> model_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PhotoModelTest);
+};
+
+// Test adding the first image.
+TEST_F(PhotoModelTest, AddFirstImage) {
+  gfx::ImageSkia first_image =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+  model_->AddNextImage(first_image);
+  EXPECT_TRUE(model_->GetPrevImage().isNull());
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(first_image));
+  EXPECT_TRUE(model_->GetNextImage().isNull());
+}
+
+// Test adding the second image.
+TEST_F(PhotoModelTest, AddSecondImage) {
+  gfx::ImageSkia first_image =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+  gfx::ImageSkia second_image =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+
+  // First |AddNextImage| will set |current_image_index_| to 0.
+  model_->AddNextImage(first_image);
+  model_->AddNextImage(second_image);
+  EXPECT_TRUE(model_->GetPrevImage().isNull());
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(first_image));
+  EXPECT_TRUE(model_->GetNextImage().BackedBySameObjectAs(second_image));
+
+  // Increment the |current_image_index_| to 1.
+  model_->ShowNextImage();
+  EXPECT_TRUE(model_->GetPrevImage().BackedBySameObjectAs(first_image));
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(second_image));
+  EXPECT_TRUE(model_->GetNextImage().isNull());
+}
+
+// Test adding the third image.
+TEST_F(PhotoModelTest, AddThirdImage) {
+  gfx::ImageSkia first_image =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+  gfx::ImageSkia second_image =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+  gfx::ImageSkia third_image =
+      gfx::test::CreateImageSkia(/*width=*/10, /*height=*/10);
+
+  // The default |current_image_index_| is 0.
+  model_->AddNextImage(first_image);
+  model_->AddNextImage(second_image);
+  model_->AddNextImage(third_image);
+  EXPECT_TRUE(model_->GetPrevImage().isNull());
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(first_image));
+  EXPECT_TRUE(model_->GetNextImage().BackedBySameObjectAs(second_image));
+
+  // Increment the |current_image_index_| to 1.
+  model_->ShowNextImage();
+  EXPECT_TRUE(model_->GetPrevImage().BackedBySameObjectAs(first_image));
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(second_image));
+  EXPECT_TRUE(model_->GetNextImage().BackedBySameObjectAs(third_image));
+
+  // Pop the |images_| front and keep the |current_image_index_| to 1.
+  model_->ShowNextImage();
+  EXPECT_TRUE(model_->GetPrevImage().BackedBySameObjectAs(second_image));
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(third_image));
+  EXPECT_TRUE(model_->GetNextImage().isNull());
+
+  // ShowNextImage() will early return.
+  model_->ShowNextImage();
+  EXPECT_TRUE(model_->GetPrevImage().BackedBySameObjectAs(second_image));
+  EXPECT_TRUE(model_->GetCurrImage().BackedBySameObjectAs(third_image));
+  EXPECT_TRUE(model_->GetNextImage().isNull());
+}
+
+}  // namespace ash
diff --git a/ash/ambient/ui/ambient_container_view.cc b/ash/ambient/ui/ambient_container_view.cc
index 3e3e639..e522649 100644
--- a/ash/ambient/ui/ambient_container_view.cc
+++ b/ash/ambient/ui/ambient_container_view.cc
@@ -12,7 +12,7 @@
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
 #include "ui/aura/window.h"
-#include "ui/views/layout/fill_layout.h"
+#include "ui/views/background.h"
 #include "ui/views/view.h"
 #include "ui/views/widget/widget.h"
 
@@ -56,7 +56,7 @@
 }
 
 gfx::Size AmbientContainerView::CalculatePreferredSize() const {
-  // TODO(wutao): Handle multiple displays.
+  // TODO(b/139953389): Handle multiple displays.
   return GetWidget()->GetNativeWindow()->GetRootWindow()->bounds().size();
 }
 
@@ -76,7 +76,9 @@
 
 void AmbientContainerView::Init() {
   CreateWidget(this);
-  SetLayoutManager(std::make_unique<views::FillLayout>());
+  // TODO(b/139954108): Choose a better dark mode theme color.
+  SetBackground(views::CreateSolidBackground(SK_ColorBLACK));
+
   photo_view_ = new PhotoView(ambient_controller_);
   AddChildView(photo_view_);
 }
diff --git a/ash/ambient/ui/photo_view.cc b/ash/ambient/ui/photo_view.cc
index 820cb1c..45c1c2f1 100644
--- a/ash/ambient/ui/photo_view.cc
+++ b/ash/ambient/ui/photo_view.cc
@@ -4,9 +4,16 @@
 
 #include "ash/ambient/ui/photo_view.h"
 
+#include <algorithm>
+
 #include "ash/ambient/ambient_controller.h"
+#include "ui/aura/window.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/gfx/image/image_skia_operations.h"
 #include "ui/views/controls/image_view.h"
+#include "ui/views/layout/box_layout.h"
+#include "ui/views/widget/widget.h"
 
 namespace ash {
 
@@ -24,22 +31,75 @@
   return "PhotoView";
 }
 
-void PhotoView::OnBoundsChanged(const gfx::Rect& prev_bounds) {
-  image_view_->SetBoundsRect(GetLocalBounds());
+void PhotoView::AddedToWidget() {
+  // Set the bounds to show |image_view_curr_| for the first time.
+  // TODO(b/140066694): Handle display configuration changes, e.g. resolution,
+  // rotation, etc.
+  const gfx::Size widget_size = GetWidget()->GetRootView()->size();
+  image_view_prev_->SetImageSize(widget_size);
+  image_view_curr_->SetImageSize(widget_size);
+  image_view_next_->SetImageSize(widget_size);
+  gfx::Rect view_bounds = gfx::Rect(GetPreferredSize());
+  const int width = widget_size.width();
+  view_bounds.set_x(-width);
+  SetBoundsRect(view_bounds);
 }
 
-void PhotoView::OnImageAvailable(const gfx::ImageSkia& image) {
-  // TODO(wutao): Options to choose how to layout image, such as STRETCH,
-  // CENTER, CENTER_CROPPED etc.
-  image_view_->SetImage(image);
+void PhotoView::OnImagesChanged() {
+  UpdateImages();
+  StartSlideAnimation();
 }
 
 void PhotoView::Init() {
-  image_view_ = new views::ImageView();
-  AddChildView(image_view_);
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+
+  auto* layout = SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::Orientation::kHorizontal));
+  layout->set_cross_axis_alignment(
+      views::BoxLayout::CrossAxisAlignment::kStart);
+
+  image_view_prev_ = AddChildView(std::make_unique<views::ImageView>());
+  image_view_curr_ = AddChildView(std::make_unique<views::ImageView>());
+  image_view_next_ = AddChildView(std::make_unique<views::ImageView>());
 
   // |ambient_controller_| outlives this view.
   ambient_controller_->AddPhotoModelObserver(this);
 }
 
+void PhotoView::UpdateImages() {
+  // TODO(b/140193766): Investigate a more efficient way to update images and do
+  // layer animation.
+  auto& model = ambient_controller_->model();
+  image_view_prev_->SetImage(model.GetPrevImage());
+  image_view_curr_->SetImage(model.GetCurrImage());
+  image_view_next_->SetImage(model.GetNextImage());
+}
+
+void PhotoView::StartSlideAnimation() {
+  if (!CanAnimate())
+    return;
+
+  ui::Layer* layer = this->layer();
+  const int x_offset = image_view_prev_->GetPreferredSize().width();
+  gfx::Transform transform;
+  transform.Translate(x_offset, 0);
+  layer->SetTransform(transform);
+  {
+    constexpr base::TimeDelta kDuration =
+        base::TimeDelta::FromMilliseconds(250);
+    ui::ScopedLayerAnimationSettings animation(layer->GetAnimator());
+    animation.SetTransitionDuration(kDuration);
+    animation.SetTweenType(gfx::Tween::EASE_OUT);
+    animation.SetPreemptionStrategy(
+        ui::LayerAnimator::IMMEDIATELY_SET_NEW_TARGET);
+    layer->SetTransform(gfx::Transform());
+  }
+}
+
+bool PhotoView::CanAnimate() const {
+  // Cannot do slide animation from previous to current image.
+  return !image_view_prev_->GetImage().isNull();
+}
+
 }  // namespace ash
diff --git a/ash/ambient/ui/photo_view.h b/ash/ambient/ui/photo_view.h
index 6bb717e4..970a2b3 100644
--- a/ash/ambient/ui/photo_view.h
+++ b/ash/ambient/ui/photo_view.h
@@ -26,16 +26,23 @@
 
   // views::View:
   const char* GetClassName() const override;
-  void OnBoundsChanged(const gfx::Rect& prev_bounds) override;
+  void AddedToWidget() override;
 
   // PhotoModelObserver:
-  void OnImageAvailable(const gfx::ImageSkia& image) override;
+  void OnImagesChanged() override;
 
  private:
   void Init();
+  void UpdateImages();
+  void StartSlideAnimation();
+  bool CanAnimate() const;
 
   AmbientController* ambient_controller_ = nullptr;
-  views::ImageView* image_view_;  // Owned by view hierarchy.
+
+  // Image containers. Used for layer animation.
+  views::ImageView* image_view_prev_ = nullptr;  // Owned by view hierarchy.
+  views::ImageView* image_view_curr_ = nullptr;  // Owned by view hierarchy.
+  views::ImageView* image_view_next_ = nullptr;  // Owned by view hierarchy.
 
   DISALLOW_COPY_AND_ASSIGN(PhotoView);
 };
diff --git a/ash/app_list/app_list_presenter_delegate_unittest.cc b/ash/app_list/app_list_presenter_delegate_unittest.cc
index 1a1d946..6f83d6e 100644
--- a/ash/app_list/app_list_presenter_delegate_unittest.cc
+++ b/ash/app_list/app_list_presenter_delegate_unittest.cc
@@ -26,6 +26,7 @@
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_switches.h"
 #include "ash/public/cpp/keyboard/keyboard_switches.h"
 #include "ash/public/cpp/shelf_item_delegate.h"
@@ -1820,8 +1821,8 @@
     : public AppListPresenterDelegateTest {
  public:
   AppListPresenterDelegateHomeLauncherTest() {
-    scoped_feature_list_.InitWithFeatures(
-        {app_list_features::kEnableBackgroundBlur}, {});
+    scoped_feature_list_.InitWithFeatures({features::kEnableBackgroundBlur},
+                                          {});
   }
   ~AppListPresenterDelegateHomeLauncherTest() override = default;
 
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index e4fd404..f9995bc9 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -22,6 +22,7 @@
 #include "ash/public/cpp/app_list/app_list_config_provider.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/wallpaper_types.h"
 #include "base/macros.h"
@@ -554,7 +555,7 @@
     : delegate_(delegate),
       model_(delegate->GetModel()),
       search_model_(delegate->GetSearchModel()),
-      is_background_blur_enabled_(app_list_features::IsBackgroundBlurEnabled()),
+      is_background_blur_enabled_(ash::features::IsBackgroundBlurEnabled()),
       bounds_animation_observer_(
           std::make_unique<BoundsAnimationObserver>(this)),
       state_animation_metrics_reporter_(
diff --git a/ash/app_list/views/apps_grid_view.cc b/ash/app_list/views/apps_grid_view.cc
index 31d5bf8..79620f60 100644
--- a/ash/app_list/views/apps_grid_view.cc
+++ b/ash/app_list/views/apps_grid_view.cc
@@ -30,6 +30,7 @@
 #include "ash/public/cpp/app_list/app_list_config.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_switches.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/pagination/pagination_controller.h"
 #include "base/guid.h"
 #include "base/macros.h"
@@ -996,7 +997,7 @@
 
 void AppsGridView::UpdateControlVisibility(ash::AppListViewState app_list_state,
                                            bool is_in_drag) {
-  if (!folder_delegate_ && app_list_features::IsBackgroundBlurEnabled()) {
+  if (!folder_delegate_ && ash::features::IsBackgroundBlurEnabled()) {
     if (is_in_drag) {
       layer()->SetMaskLayer(nullptr);
     } else {
diff --git a/ash/app_list/views/assistant/assistant_main_stage.cc b/ash/app_list/views/assistant/assistant_main_stage.cc
index 2b1a7ebe..615b700c 100644
--- a/ash/app_list/views/assistant/assistant_main_stage.cc
+++ b/ash/app_list/views/assistant/assistant_main_stage.cc
@@ -20,7 +20,6 @@
 #include "base/bind.h"
 #include "base/time/time.h"
 #include "ui/base/l10n/l10n_util.h"
-#include "ui/compositor/callback_layer_animation_observer.h"
 #include "ui/compositor/layer_animation_element.h"
 #include "ui/compositor/layer_animator.h"
 #include "ui/gfx/canvas.h"
@@ -39,19 +38,6 @@
 constexpr int kSeparatorThicknessDip = 1;
 constexpr int kSeparatorWidthDip = 64;
 
-// Footer animation.
-constexpr int kFooterAnimationTranslationDip = 22;
-constexpr base::TimeDelta kFooterAnimationTranslationDelay =
-    base::TimeDelta::FromMilliseconds(66);
-constexpr base::TimeDelta kFooterAnimationTranslationDuration =
-    base::TimeDelta::FromMilliseconds(416);
-constexpr base::TimeDelta kFooterAnimationFadeInDelay =
-    base::TimeDelta::FromMilliseconds(149);
-constexpr base::TimeDelta kFooterAnimationFadeInDuration =
-    base::TimeDelta::FromMilliseconds(250);
-constexpr base::TimeDelta kFooterAnimationFadeOutDuration =
-    base::TimeDelta::FromMilliseconds(100);
-
 // Footer entry animation.
 constexpr base::TimeDelta kFooterEntryAnimationFadeInDelay =
     base::TimeDelta::FromMilliseconds(283);
@@ -111,15 +97,7 @@
 // AssistantMainStage ----------------------------------------------------------
 
 AssistantMainStage::AssistantMainStage(ash::AssistantViewDelegate* delegate)
-    : delegate_(delegate),
-      footer_animation_observer_(
-          std::make_unique<ui::CallbackLayerAnimationObserver>(
-              /*animation_started_callback=*/base::BindRepeating(
-                  &AssistantMainStage::OnFooterAnimationStarted,
-                  base::Unretained(this)),
-              /*animation_ended_callback=*/base::BindRepeating(
-                  &AssistantMainStage::OnFooterAnimationEnded,
-                  base::Unretained(this)))) {
+    : delegate_(delegate) {
   InitLayout();
 
   // The view hierarchy will be destructed before AssistantController in Shell,
@@ -327,7 +305,6 @@
     const ash::AssistantQuery& query) {
   // Update the view.
   query_view_->SetQuery(query);
-  UpdateFooter(/*visible=*/false);
 
   if (!IsLayerVisible(greeting_label_))
     return;
@@ -357,12 +334,6 @@
   // reseting the query here will have no visible effect. If the interaction was
   // cancelled, we set the query here to restore the previously committed query.
   query_view_->SetQuery(delegate_->GetInteractionModel()->committed_query());
-
-  // If the query was committed, we wait for |OnResponseChanged()| to update the
-  // footer. If the interaction was cancelled, we restore the previous
-  // suggestion chips.
-  if (!due_to_commit)
-    UpdateFooter(/*visible=*/true);
 }
 
 void AssistantMainStage::OnResponseChanged(
@@ -387,8 +358,6 @@
   progress_indicator_->layer()->GetAnimator()->StartAnimation(
       CreateLayerAnimationSequence(ash::assistant::util::CreateOpacityElement(
           0.f, kDividerAnimationFadeOutDuration)));
-
-  UpdateFooter(/*visible=*/true);
 }
 
 void AssistantMainStage::OnUiVisibilityChanged(
@@ -473,75 +442,4 @@
           CreateOpacityElement(0.f, kGreetingAnimationFadeOutDuration)));
 }
 
-void AssistantMainStage::UpdateFooter(bool visible) {
-  using ash::assistant::util::CreateLayerAnimationSequence;
-  using ash::assistant::util::CreateOpacityElement;
-  using ash::assistant::util::CreateTransformElement;
-  using ash::assistant::util::StartLayerAnimationSequence;
-  using ash::assistant::util::StartLayerAnimationSequencesTogether;
-
-  if (visible == IsLayerVisible(footer_))
-    return;
-
-  // Reset visibility to enable animation.
-  footer_->SetVisible(true);
-
-  if (visible) {
-    // The footer will animate up into position so we need to set an initial
-    // offset transformation from which to animate.
-    gfx::Transform transform;
-    transform.Translate(0, kFooterAnimationTranslationDip);
-    footer_->layer()->SetTransform(transform);
-
-    // Animate the entry of the footer.
-    StartLayerAnimationSequencesTogether(
-        footer_->layer()->GetAnimator(),
-        {// Animate the translation with delay.
-         CreateLayerAnimationSequence(
-             ui::LayerAnimationElement::CreatePauseElement(
-                 ui::LayerAnimationElement::AnimatableProperty::TRANSFORM,
-                 kFooterAnimationTranslationDelay),
-             CreateTransformElement(gfx::Transform(),
-                                    kFooterAnimationTranslationDuration,
-                                    gfx::Tween::Type::FAST_OUT_SLOW_IN_2)),
-         // Animate the fade in with delay.
-         CreateLayerAnimationSequence(
-             ui::LayerAnimationElement::CreatePauseElement(
-                 ui::LayerAnimationElement::AnimatableProperty::OPACITY,
-                 kFooterAnimationFadeInDelay),
-             CreateOpacityElement(1.f, kFooterAnimationFadeInDuration))},
-        // Observer animation start/end events.
-        footer_animation_observer_.get());
-  } else {
-    // Animate the exit of the footer.
-    StartLayerAnimationSequence(
-        footer_->layer()->GetAnimator(),
-        // Animate fade out.
-        CreateLayerAnimationSequence(
-            CreateOpacityElement(0.f, kFooterAnimationFadeOutDuration)),
-        // Observe animation start/end events.
-        footer_animation_observer_.get());
-  }
-
-  // Set the observer to active so that we'll receive start/end events.
-  footer_animation_observer_->SetActive();
-}
-
-void AssistantMainStage::OnFooterAnimationStarted(
-    const ui::CallbackLayerAnimationObserver& observer) {
-  // The footer should not process events while animating.
-  footer_->set_can_process_events_within_subtree(/*can_process=*/false);
-}
-
-bool AssistantMainStage::OnFooterAnimationEnded(
-    const ui::CallbackLayerAnimationObserver& observer) {
-  // The footer should only process events when visible.
-  const bool visible = IsLayerVisible(footer_);
-  footer_->set_can_process_events_within_subtree(visible);
-  footer_->SetVisible(visible);
-
-  // Return false so that the observer does not destroy itself.
-  return false;
-}
-
 }  // namespace app_list
diff --git a/ash/app_list/views/assistant/assistant_main_stage.h b/ash/app_list/views/assistant/assistant_main_stage.h
index ad5c5cb..8e66a15 100644
--- a/ash/app_list/views/assistant/assistant_main_stage.h
+++ b/ash/app_list/views/assistant/assistant_main_stage.h
@@ -22,10 +22,6 @@
 class UiElementContainerView;
 }  // namespace ash
 
-namespace ui {
-class CallbackLayerAnimationObserver;
-}  // namespace ui
-
 namespace views {
 class Label;
 }  // namespace views
@@ -75,14 +71,6 @@
 
   void MaybeHideGreetingLabel();
 
-  // Update footer to |visible| with animations.
-  void UpdateFooter(bool visible);
-
-  void OnFooterAnimationStarted(
-      const ui::CallbackLayerAnimationObserver& observer);
-  bool OnFooterAnimationEnded(
-      const ui::CallbackLayerAnimationObserver& observer);
-
   ash::AssistantViewDelegate* const delegate_;  // Owned by Shell.
 
   // Owned by view hierarchy.
@@ -93,9 +81,6 @@
   views::Label* greeting_label_;
   ash::AssistantFooterView* footer_;
 
-  std::unique_ptr<ui::CallbackLayerAnimationObserver>
-      footer_animation_observer_;
-
   DISALLOW_COPY_AND_ASSIGN(AssistantMainStage);
 };
 
diff --git a/ash/assistant/ui/BUILD.gn b/ash/assistant/ui/BUILD.gn
index 0ac7663..88ef2fb 100644
--- a/ash/assistant/ui/BUILD.gn
+++ b/ash/assistant/ui/BUILD.gn
@@ -66,6 +66,8 @@
     "dialog_plate/mic_view.h",
     "logo_view/logo_view.cc",
     "logo_view/logo_view.h",
+    "main_stage/animated_container_view.cc",
+    "main_stage/animated_container_view.h",
     "main_stage/assistant_card_element_view.cc",
     "main_stage/assistant_card_element_view.h",
     "main_stage/assistant_footer_view.cc",
@@ -82,6 +84,8 @@
     "main_stage/assistant_query_view.h",
     "main_stage/assistant_text_element_view.cc",
     "main_stage/assistant_text_element_view.h",
+    "main_stage/element_animator.cc",
+    "main_stage/element_animator.h",
     "main_stage/suggestion_chip_view.cc",
     "main_stage/suggestion_chip_view.h",
     "main_stage/suggestion_container_view.cc",
diff --git a/ash/assistant/ui/main_stage/animated_container_view.cc b/ash/assistant/ui/main_stage/animated_container_view.cc
new file mode 100644
index 0000000..f57b2f6
--- /dev/null
+++ b/ash/assistant/ui/main_stage/animated_container_view.cc
@@ -0,0 +1,274 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/ui/main_stage/animated_container_view.h"
+
+#include "ash/assistant/model/assistant_interaction_model_observer.h"
+#include "ash/assistant/model/assistant_response.h"
+#include "ash/assistant/ui/assistant_view_delegate.h"
+#include "ash/assistant/ui/main_stage/element_animator.h"
+#include "ui/compositor/callback_layer_animation_observer.h"
+
+namespace ash {
+
+AnimatedContainerView::AnimatedContainerView(AssistantViewDelegate* delegate)
+    : delegate_(delegate) {
+  // The AssistantViewDelegate should outlive AnimatedContainerView.
+  delegate_->AddInteractionModelObserver(this);
+}
+
+AnimatedContainerView::~AnimatedContainerView() {
+  delegate_->RemoveInteractionModelObserver(this);
+}
+
+void AnimatedContainerView::AddElementAnimator(
+    std::unique_ptr<ElementAnimator> view_animator) {
+  DCHECK_EQ(view_animator->view()->parent(), content_view());
+  animators_.push_back(std::move(view_animator));
+}
+
+void AnimatedContainerView::PreferredSizeChanged() {
+  // Because views are added/removed in batches, we attempt to prevent
+  // over-propagation of the PreferredSizeChanged event during batched view
+  // hierarchy add/remove operations. This helps to reduce layout passes.
+  if (propagate_preferred_size_changed_)
+    AssistantScrollView::PreferredSizeChanged();
+}
+
+void AnimatedContainerView::OnChildViewRemoved(View* observed_view,
+                                               View* child) {
+  for (auto it = animators_.begin(); it != animators_.end(); ++it) {
+    if (it->get()->view() == child) {
+      animators_.erase(it);
+      return;
+    }
+  }
+}
+
+void AnimatedContainerView::OnCommittedQueryChanged(
+    const AssistantQuery& query) {
+  FadeOutViews();
+}
+
+void AnimatedContainerView::OnResponseChanged(
+    const scoped_refptr<AssistantResponse>& response) {
+  ChangeResponse(response);
+}
+
+void AnimatedContainerView::OnResponseCleared() {
+  RemoveAllViews();
+}
+
+void AnimatedContainerView::RemoveAllViews() {
+  // We explicitly abort all in progress animations here because we will remove
+  // their views immediately and we want to ensure that any animation observers
+  // will be notified of an abort, not an animation completion.  Otherwise there
+  // is potential to enter into a bad state (see crbug/952996).
+  for (const auto& animator : animators_)
+    animator->AbortAnimation();
+
+  // We can prevent over-propagation of the PreferredSizeChanged event by
+  // stopping propagation during batched view hierarchy add/remove operations.
+  SetPropagatePreferredSizeChanged(false);
+  animators_.clear();
+  content_view()->RemoveAllChildViews(/*delete_children=*/true);
+  SetPropagatePreferredSizeChanged(true);
+
+  // We inform our derived class all views have been removed.
+  OnAllViewsRemoved();
+
+  // Once the response has been cleared from the stage, we are free to release
+  // our shared pointer. This allows resources associated with the underlying
+  // views to be freed, provided there are no other usages.
+  response_.reset();
+}
+
+void AnimatedContainerView::AnimateIn() {
+  // We don't allow processing of events while animating.
+  set_can_process_events_within_subtree(false);
+
+  auto* animation_observer = new ui::CallbackLayerAnimationObserver(
+      /*animation_ended_callback=*/base::BindRepeating(
+          AnimatedContainerView::AnimateInObserverCallback,
+          weak_factory_.GetWeakPtr()));
+
+  for (const auto& animator : animators_)
+    animator->AnimateIn(animation_observer);
+
+  // Set the observer to active so that we receive callback events.
+  animation_observer->SetActive();
+}
+
+void AnimatedContainerView::SetPropagatePreferredSizeChanged(bool propagate) {
+  if (propagate == propagate_preferred_size_changed_)
+    return;
+
+  propagate_preferred_size_changed_ = propagate;
+
+  // When we are no longer stopping propagation of PreferredSizeChanged events,
+  // we fire an event off to ensure the view hierarchy is properly laid out.
+  if (propagate_preferred_size_changed_)
+    PreferredSizeChanged();
+}
+
+void AnimatedContainerView::FadeOutViews() {
+  // We don't allow processing of events while waiting for the next query
+  // response. The contents will be faded out, so it should not be interactive.
+  set_can_process_events_within_subtree(false);
+
+  fade_out_in_progress_ = true;
+
+  auto* animation_observer = new ui::CallbackLayerAnimationObserver(
+      /*animation_ended_callback=*/base::BindRepeating(
+          AnimatedContainerView::FadeOutObserverCallback,
+          weak_factory_.GetWeakPtr()));
+
+  for (const auto& animator : animators_)
+    animator->FadeOut(animation_observer);
+
+  // Set the observer to active so that we receive callback events.
+  animation_observer->SetActive();
+}
+
+void AnimatedContainerView::ChangeResponse(
+    const scoped_refptr<const AssistantResponse>& response) {
+  // We may have to pend the response while we animate the previous response off
+  // stage. We use a shared pointer to ensure that any views we add to the view
+  // hierarchy can be removed before the underlying views are destroyed.
+  pending_response_ = response;
+
+  // If we are currently fading out the old content, don't interrupt it.
+  // When the fading out is completed, it will detect we've got a pending
+  // response and animate it in.
+  if (fade_out_in_progress_)
+    return;
+
+  // If we don't have any pre-existing content, there is nothing to animate off
+  // stage so we can proceed to add the new response.
+  if (content_view()->children().empty()) {
+    AddResponse(std::move(pending_response_));
+    return;
+  }
+
+  // There is a previous response on stage, so we'll animate it off before
+  // adding the new response. The new response will be added upon invocation
+  // of the exit animation ended callback.
+  auto* animation_observer = new ui::CallbackLayerAnimationObserver(
+      /*animation_ended_callback=*/base::BindRepeating(
+          AnimatedContainerView::AnimateOutObserverCallback,
+          weak_factory_.GetWeakPtr()));
+
+  for (const auto& animator : animators_)
+    animator->AnimateOut(animation_observer);
+
+  // Set the observer to active so that we receive callback events.
+  animation_observer->SetActive();
+}
+
+void AnimatedContainerView::AddResponse(
+    scoped_refptr<const AssistantResponse> response) {
+  // The response should be fully processed before it is presented.
+  DCHECK_EQ(AssistantResponse::ProcessingState::kProcessed,
+            response->processing_state());
+  // All children should be animated out and removed before the new response is
+  // added.
+  DCHECK(content_view()->children().empty());
+
+  // We cache a reference to the |response| to ensure that the instance is not
+  // destroyed before we have removed associated views from the view hierarchy.
+  response_ = std::move(response);
+
+  // Because the views for the response are animated in together, we can stop
+  // propagation of PreferredSizeChanged events until all views have been added
+  // to the view hierarchy to reduce layout passes.
+  SetPropagatePreferredSizeChanged(false);
+
+  HandleResponse(*response_);
+
+  // Now that the response for the current query has been added to the view
+  // hierarchy, we can restart propagation of PreferredSizeChanged events since
+  // all views have been added to the view hierarchy. Note we do not re-enable
+  // processing of events yet, as that will happen once the enter animations
+  // have completed.
+  SetPropagatePreferredSizeChanged(true);
+
+  // Now that we've received and added all views for the current query response,
+  // we can animate them in.
+  AnimateIn();
+}
+
+bool AnimatedContainerView::AnimateInObserverCallback(
+    const base::WeakPtr<AnimatedContainerView>& weak_ptr,
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // If the AnimatedContainerView is destroyed we just return true to delete our
+  // observer. No further action is needed.
+  if (!weak_ptr)
+    return true;
+
+  // If the animation was aborted, we just return true to delete our observer.
+  // No further action is needed.
+  if (observer.aborted_count())
+    return true;
+
+  // Now that all views have been animated in we make it interactive.
+  weak_ptr->set_can_process_events_within_subtree(true);
+
+  // Inform our derived class all views have been animated in.
+  weak_ptr->OnAllViewsAnimatedIn();
+
+  // We return true to delete our observer.
+  return true;
+}
+
+bool AnimatedContainerView::AnimateOutObserverCallback(
+    const base::WeakPtr<AnimatedContainerView>& weak_ptr,
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // If the AnimatedContainerView is destroyed we just return true to delete our
+  // observer. No further action is needed.
+  if (!weak_ptr)
+    return true;
+
+  // If the exit animation was aborted, we just return true to delete our
+  // observer. No further action is needed.
+  if (observer.aborted_count())
+    return true;
+
+  // All views have finished their exit animations so it's safe to perform
+  // clearing of their views and managed resources.
+  weak_ptr->RemoveAllViews();
+
+  // It is safe to add our pending response, if one exists, to the view
+  // hierarchy now that we've cleared the previous response from the stage.
+  if (weak_ptr->pending_response_)
+    weak_ptr->AddResponse(std::move(weak_ptr->pending_response_));
+
+  // We return true to delete our observer.
+  return true;
+}
+
+bool AnimatedContainerView::FadeOutObserverCallback(
+    const base::WeakPtr<AnimatedContainerView>& weak_ptr,
+    const ui::CallbackLayerAnimationObserver& observer) {
+  // If the AnimatedContainerView is destroyed we just return true to delete our
+  // observer. No further action is needed.
+  if (!weak_ptr)
+    return true;
+
+  weak_ptr->fade_out_in_progress_ = false;
+
+  // If the exit animation was aborted, we just return true to delete our
+  // observer. No further action is needed.
+  if (observer.aborted_count())
+    return true;
+
+  // If the new response arrived while the fade-out was in progress, we will
+  // start handling it now.
+  if (weak_ptr->pending_response_)
+    weak_ptr->ChangeResponse(std::move(weak_ptr->pending_response_));
+
+  // We return true to delete our observer.
+  return true;
+}
+
+}  // namespace ash
diff --git a/ash/assistant/ui/main_stage/animated_container_view.h b/ash/assistant/ui/main_stage/animated_container_view.h
new file mode 100644
index 0000000..9da4dc77d
--- /dev/null
+++ b/ash/assistant/ui/main_stage/animated_container_view.h
@@ -0,0 +1,144 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ASSISTANT_UI_MAIN_STAGE_ANIMATED_CONTAINER_VIEW_H_
+#define ASH_ASSISTANT_UI_MAIN_STAGE_ANIMATED_CONTAINER_VIEW_H_
+
+#include <memory>
+
+#include "ash/assistant/model/assistant_interaction_model_observer.h"
+#include "ash/assistant/ui/base/assistant_scroll_view.h"
+
+namespace ui {
+class CallbackLayerAnimationObserver;
+}  // namespace ui
+
+namespace ash {
+
+class AssistantViewDelegate;
+class AssistantResponse;
+class ElementAnimator;
+
+// A view that will observe the |AssistantResponse| and which will use
+// |ElementAnimator| to animate each child view.
+//
+// To use this you must implement |HandleResponse| and in there
+//    - Add the new child views for the given |AssistantResponse|.
+//    - Add animators for the new view by calling |AddElementAnimator|.
+//
+// More in detail, this is what will happen:
+//    1) When |AssistantInteractionModelObserver::OnCommittedQueryChanged| is
+//       observed, |FadeOut| is called on all |ElementAnimator| instances.
+//       Furthermore all views will stop processing events (like click).
+//    2) When |AssistantInteractionModelObserver::OnResponseChanged| is
+//       observed, we wait until the |FadeOut| animations are complete.
+//    3) Next |AnimateOut| is invoked on all |ElementAnimator| instances.
+//    4) When these animations are complete, all child views are removed.
+//    5) |AnimatedContainerView::OnAllViewsRemoved| is invoked to inform
+//       the derived class all child views are removed.
+//    6) Next |AnimatedContainerView::HandleResponse| is called with the new
+//       |AssistantResponse|.
+//       In here the derived class should add the child views for the new
+//       response, as well as adding |ElementAnimator| instances by calling
+//       |AnimatedContainerView::AddElementAnimator|.
+//    7) When all new child views have been added, |AnimateIn| is invoked on
+//       all |ElementAnimator| instances.
+//    8) Finally when this animation is complete the derived class is informed
+//       through |AnimatedContainerView::OnAllViewsAnimatedIn|.
+class AnimatedContainerView : public AssistantScrollView,
+                              public AssistantInteractionModelObserver {
+ public:
+  explicit AnimatedContainerView(AssistantViewDelegate* delegate);
+  ~AnimatedContainerView() override;
+
+  // Add an animator for a view that is displayed in this content view.
+  // Should be called for each view that needs to be animated, and is usually
+  // called from inside the |HandleResponse| callback.
+  void AddElementAnimator(std::unique_ptr<ElementAnimator> view_animator);
+
+  // AssistantScrollView:
+  void PreferredSizeChanged() override;
+  void OnChildViewRemoved(View* observed_view, View* child) override;
+
+  // AssistantInteractionModelObserver:
+  void OnCommittedQueryChanged(const AssistantQuery& query) override;
+  void OnResponseChanged(
+      const scoped_refptr<AssistantResponse>& response) override;
+  void OnResponseCleared() override;
+
+  // Remove all current responses/views.
+  // This will abort all in progress animations, and remove all the child views
+  // and their animators.
+  void RemoveAllViews();
+
+  // Manually trigger the animate-in animation.
+  // Should only be used if you call |AddElementAnimator| outside of the
+  // |HandleResponse| callback.
+  void AnimateIn();
+
+ protected:
+  // Callback called when all (new) views have been added.
+  // This is called when the animate-in animations are done.
+  virtual void OnAllViewsAnimatedIn() {}
+
+  // Callback called when all (old) views have been removed.
+  // This is called when the exit animations are done.
+  virtual void OnAllViewsRemoved() {}
+
+  // Callback called to create the new views.
+  // For each new views it should
+  //    - Create and add the |views::View|.
+  //    - Call |AddElementAnimator| and pass it the |ElementAnimator| to
+  //      animate the view.
+  virtual void HandleResponse(const AssistantResponse& response) = 0;
+
+  AssistantViewDelegate* delegate() { return delegate_; }
+
+ private:
+  void SetPropagatePreferredSizeChanged(bool propagate);
+
+  void FadeOutViews();
+  void ChangeResponse(const scoped_refptr<const AssistantResponse>& response);
+  void AddResponse(scoped_refptr<const AssistantResponse> response);
+
+  static bool AnimateInObserverCallback(
+      const base::WeakPtr<AnimatedContainerView>& weak_ptr,
+      const ui::CallbackLayerAnimationObserver& observer);
+  static bool AnimateOutObserverCallback(
+      const base::WeakPtr<AnimatedContainerView>& weak_ptr,
+      const ui::CallbackLayerAnimationObserver& observer);
+  static bool FadeOutObserverCallback(
+      const base::WeakPtr<AnimatedContainerView>& weak_ptr,
+      const ui::CallbackLayerAnimationObserver& observer);
+
+  AssistantViewDelegate* const delegate_;  // Owned by AssistantController.
+
+  // The animators used to trigger the animations of the individual views.
+  std::vector<std::unique_ptr<ElementAnimator>> animators_;
+
+  // Whether we should allow propagation of PreferredSizeChanged events.
+  // Because we only animate views in/out in batches, we can prevent
+  // over-propagation of PreferredSizeChanged events by waiting until the
+  // entirety of a response has been added/removed before propagating. This
+  // reduces layout passes.
+  bool propagate_preferred_size_changed_ = true;
+
+  // Whether the fade-out animation is in progress.
+  bool fade_out_in_progress_ = false;
+
+  // Shared pointers to the response that is currently on stage as well as the
+  // pending response to be presented following the former's animated exit. We
+  // use shared pointers to ensure that underlying views are not destroyed
+  // before we have an opportunity to remove their associated views.
+  scoped_refptr<const AssistantResponse> response_;
+  scoped_refptr<const AssistantResponse> pending_response_;
+
+  base::WeakPtrFactory<AnimatedContainerView> weak_factory_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(AnimatedContainerView);
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_UI_MAIN_STAGE_ANIMATED_CONTAINER_VIEW_H_
diff --git a/ash/assistant/ui/main_stage/element_animator.cc b/ash/assistant/ui/main_stage/element_animator.cc
new file mode 100644
index 0000000..e5817be
--- /dev/null
+++ b/ash/assistant/ui/main_stage/element_animator.cc
@@ -0,0 +1,43 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/assistant/ui/main_stage/element_animator.h"
+
+#include "ash/assistant/util/animation_util.h"
+#include "base/time/time.h"
+#include "ui/compositor/callback_layer_animation_observer.h"
+#include "ui/compositor/layer.h"
+#include "ui/compositor/layer_animator.h"
+#include "ui/views/view.h"
+
+namespace ash {
+
+constexpr float ElementAnimator::kFadeOutOpacity;
+constexpr base::TimeDelta ElementAnimator::kFadeOutDuration;
+constexpr float ElementAnimator::kMinimumAnimateOutOpacity;
+
+ElementAnimator::ElementAnimator(views::View* view) : view_(view) {}
+
+void ElementAnimator::FadeOut(ui::CallbackLayerAnimationObserver* observer) {
+  assistant::util::StartLayerAnimationSequence(
+      layer()->GetAnimator(),
+      assistant::util::CreateLayerAnimationSequence(
+          assistant::util::CreateOpacityElement(kFadeOutOpacity,
+                                                kFadeOutDuration)),
+      observer);
+}
+
+void ElementAnimator::AbortAnimation() {
+  layer()->GetAnimator()->AbortAllAnimations();
+}
+
+views::View* ElementAnimator::view() const {
+  return view_;
+}
+
+ui::Layer* ElementAnimator::layer() const {
+  return view()->layer();
+}
+
+}  // namespace ash
diff --git a/ash/assistant/ui/main_stage/element_animator.h b/ash/assistant/ui/main_stage/element_animator.h
new file mode 100644
index 0000000..364b9ce6
--- /dev/null
+++ b/ash/assistant/ui/main_stage/element_animator.h
@@ -0,0 +1,72 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_ASSISTANT_UI_MAIN_STAGE_ELEMENT_ANIMATOR_H_
+#define ASH_ASSISTANT_UI_MAIN_STAGE_ELEMENT_ANIMATOR_H_
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace ui {
+class CallbackLayerAnimationObserver;
+class Layer;
+}  // namespace ui
+
+namespace views {
+class View;
+}  // namespace views
+
+namespace ash {
+
+// Defines all the animations for the associated UI element.
+class ElementAnimator {
+ public:
+  // Fade out duration used in the default implementation of |FadeOut|.
+  constexpr static base::TimeDelta kFadeOutDuration =
+      base::TimeDelta::FromMilliseconds(150);
+  // Fade out opacity used in the default implementation of |FadeOut|.
+  constexpr static float kFadeOutOpacity = 0.26f;
+  // Minimum allowed opacity as a target when fading out.
+  // Note that we approximate 0% by actually using 0.01%. We do this to
+  // workaround a DCHECK that requires aura::Windows to have a target opacity >
+  // 0% when shown. Because our window will be removed after it reaches this
+  // value, it should be safe to circumnavigate this DCHECK.
+  constexpr static float kMinimumAnimateOutOpacity = 0.0001f;
+
+  ElementAnimator(views::View* animated_view);
+  virtual ~ElementAnimator() = default;
+
+  // Fade out the current element, meaning it will still be visible but
+  // partially opaque.
+  virtual void FadeOut(ui::CallbackLayerAnimationObserver* observer);
+
+  // Start the animation to remove the element.
+  virtual void AnimateOut(ui::CallbackLayerAnimationObserver* observer) = 0;
+
+  // Start the animation to add the element.
+  virtual void AnimateIn(ui::CallbackLayerAnimationObserver* observer) = 0;
+
+  // Abort whatever animation is currently in progress.
+  virtual void AbortAnimation();
+
+  // The view that is being animated.
+  virtual views::View* view() const;
+
+ protected:
+  // The layer that needs to be animated.
+  // Used by the default implementations for |FadeOut| and |AbortAnimation|.
+  // Defaults to |view()->layer()|.
+  virtual ui::Layer* layer() const;
+
+ private:
+  // The parent |AnimatedContainerView| owns both |view_| and |this| and will
+  // delete |this| when |view_| is removed.
+  views::View* const view_;
+
+  DISALLOW_COPY_AND_ASSIGN(ElementAnimator);
+};
+
+}  // namespace ash
+
+#endif  // ASH_ASSISTANT_UI_MAIN_STAGE_ELEMENT_ANIMATOR_H_
diff --git a/ash/assistant/ui/main_stage/suggestion_container_view.cc b/ash/assistant/ui/main_stage/suggestion_container_view.cc
index 8f7b38a8..2666a7a 100644
--- a/ash/assistant/ui/main_stage/suggestion_container_view.cc
+++ b/ash/assistant/ui/main_stage/suggestion_container_view.cc
@@ -11,38 +11,130 @@
 #include "ash/assistant/model/assistant_response.h"
 #include "ash/assistant/ui/assistant_ui_constants.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
+#include "ash/assistant/ui/main_stage/animated_container_view.h"
+#include "ash/assistant/ui/main_stage/element_animator.h"
+#include "ash/assistant/util/animation_util.h"
 #include "ash/assistant/util/assistant_util.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "base/bind.h"
 #include "base/strings/utf_string_conversions.h"
+#include "ui/compositor/callback_layer_animation_observer.h"
+#include "ui/compositor/layer_animation_element.h"
 #include "ui/views/layout/box_layout.h"
 
 namespace ash {
 
 namespace {
 
+using assistant::util::CreateLayerAnimationSequence;
+using assistant::util::CreateOpacityElement;
+using assistant::util::CreateTransformElement;
+using assistant::util::StartLayerAnimationSequence;
+using assistant::util::StartLayerAnimationSequencesTogether;
+
+// Animation.
+constexpr int kChipMoveUpDistanceDip = 24;
+constexpr base::TimeDelta kSelectedChipAnimateInDelay =
+    base::TimeDelta::FromMilliseconds(150);
+constexpr base::TimeDelta kChipFadeInDuration =
+    base::TimeDelta::FromMilliseconds(250);
+constexpr base::TimeDelta kChipMoveUpDuration =
+    base::TimeDelta::FromMilliseconds(250);
+constexpr base::TimeDelta kChipFadeOutDuration =
+    base::TimeDelta::FromMilliseconds(200);
+
 // Appearance.
 constexpr int kPreferredHeightDip = 48;
 
 }  // namespace
 
+// SuggestionChipAnimator -----------------------------------------------------
+
+class SuggestionChipAnimator : public ElementAnimator {
+ public:
+  SuggestionChipAnimator(SuggestionChipView* chip,
+                         const SuggestionContainerView* parent)
+      : ElementAnimator(chip), parent_(parent) {}
+  ~SuggestionChipAnimator() override = default;
+
+  void AnimateIn(ui::CallbackLayerAnimationObserver* observer) override {
+    // As part of the animation we will move up the chip from the bottom
+    // so we need to start by moving it down.
+    MoveDown();
+    layer()->SetOpacity(0.f);
+
+    StartLayerAnimationSequencesTogether(layer()->GetAnimator(),
+                                         {
+                                             CreateFadeInAnimation(),
+                                             CreateMoveUpAnimation(),
+                                         },
+                                         observer);
+  }
+
+  void AnimateOut(ui::CallbackLayerAnimationObserver* observer) override {
+    StartLayerAnimationSequence(layer()->GetAnimator(),
+                                CreateAnimateOutAnimation(), observer);
+  }
+
+  void FadeOut(ui::CallbackLayerAnimationObserver* observer) override {
+    // If the user pressed a chip we do not fade it out.
+    if (!IsSelectedChip())
+      ElementAnimator::FadeOut(observer);
+  }
+
+ private:
+  bool IsSelectedChip() const { return view() == parent_->selected_chip(); }
+
+  void MoveDown() const {
+    gfx::Transform transform;
+    transform.Translate(0, kChipMoveUpDistanceDip);
+    layer()->SetTransform(transform);
+  }
+
+  ui::LayerAnimationSequence* CreateFadeInAnimation() const {
+    return CreateLayerAnimationSequence(
+        ui::LayerAnimationElement::CreatePauseElement(
+            ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+            kSelectedChipAnimateInDelay),
+        CreateOpacityElement(1.f, kChipFadeInDuration,
+                             gfx::Tween::Type::FAST_OUT_SLOW_IN));
+  }
+
+  ui::LayerAnimationSequence* CreateMoveUpAnimation() const {
+    return CreateLayerAnimationSequence(
+        ui::LayerAnimationElement::CreatePauseElement(
+            ui::LayerAnimationElement::AnimatableProperty::TRANSFORM,
+            kSelectedChipAnimateInDelay),
+        CreateTransformElement(gfx::Transform(), kChipMoveUpDuration,
+                               gfx::Tween::Type::FAST_OUT_SLOW_IN));
+  }
+
+  ui::LayerAnimationSequence* CreateAnimateOutAnimation() const {
+    return CreateLayerAnimationSequence(CreateOpacityElement(
+        0.f, kChipFadeOutDuration, gfx::Tween::Type::FAST_OUT_SLOW_IN));
+  }
+
+  const SuggestionContainerView* const parent_;  // |parent_| owns |this|.
+
+  DISALLOW_COPY_AND_ASSIGN(SuggestionChipAnimator);
+};
+
 // SuggestionContainerView -----------------------------------------------------
 
 SuggestionContainerView::SuggestionContainerView(
     AssistantViewDelegate* delegate)
-    : delegate_(delegate) {
+    : AnimatedContainerView(delegate) {
   InitLayout();
 
   // The AssistantViewDelegate should outlive SuggestionContainerView.
-  delegate_->AddInteractionModelObserver(this);
-  delegate_->AddSuggestionsModelObserver(this);
-  delegate_->AddUiModelObserver(this);
+  delegate->AddSuggestionsModelObserver(this);
+  delegate->AddUiModelObserver(this);
 }
 
 SuggestionContainerView::~SuggestionContainerView() {
-  delegate_->RemoveUiModelObserver(this);
-  delegate_->RemoveSuggestionsModelObserver(this);
-  delegate_->RemoveInteractionModelObserver(this);
+  delegate()->RemoveUiModelObserver(this);
+  delegate()->RemoveSuggestionsModelObserver(this);
+  delegate()->RemoveInteractionModelObserver(this);
 }
 
 const char* SuggestionContainerView::GetClassName() const {
@@ -82,82 +174,91 @@
       views::BoxLayout::MainAxisAlignment::kCenter);
 }
 
-void SuggestionContainerView::OnResponseChanged(
-    const scoped_refptr<AssistantResponse>& response) {
-  has_received_response_ = true;
+void SuggestionContainerView::OnConversationStartersChanged(
+    const std::map<int, const AssistantSuggestion*>& conversation_starters) {
+  RemoveAllViews();
+  OnSuggestionsChanged(conversation_starters);
+  AnimateIn();
+}
 
-  OnSuggestionsCleared();
+void SuggestionContainerView::HandleResponse(
+    const AssistantResponse& response) {
+  has_received_response_ = true;
 
   // When no longer showing conversation starters, we start align our content.
   layout_manager_->set_main_axis_alignment(
       views::BoxLayout::MainAxisAlignment::kStart);
 
-  OnSuggestionsChanged(response->GetSuggestions());
+  OnSuggestionsChanged(response.GetSuggestions());
 }
 
-void SuggestionContainerView::OnResponseCleared() {
+void SuggestionContainerView::OnAllViewsRemoved() {
+  // Abort any download requests in progress.
+  download_request_weak_factory_.InvalidateWeakPtrs();
+
+  // Clear our view cache.
+  suggestion_chip_views_.clear();
+
+  // Clear the selected button.
+  selected_chip_ = nullptr;
+
   // Note that we don't reset |has_received_response_| here because that refers
   // to whether we've received a response during the current Assistant session,
   // not whether we are currently displaying a response.
-  OnSuggestionsCleared();
-}
-
-void SuggestionContainerView::OnConversationStartersChanged(
-    const std::map<int, const AssistantSuggestion*>& conversation_starters) {
-  OnSuggestionsCleared();
-  OnSuggestionsChanged(conversation_starters);
 }
 
 void SuggestionContainerView::OnSuggestionsChanged(
     const std::map<int, const AssistantSuggestion*>& suggestions) {
-  using AssistantSuggestion = chromeos::assistant::mojom::AssistantSuggestion;
-  for (const std::pair<int, const AssistantSuggestion*>& suggestion :
-       suggestions) {
+  for (const auto& suggestion : suggestions) {
     // We will use the same identifier by which the Assistant interaction model
     // uniquely identifies a suggestion to uniquely identify its corresponding
     // suggestion chip view.
-    const int id = suggestion.first;
-
-    SuggestionChipView::Params params;
-    params.text = base::UTF8ToUTF16(suggestion.second->text);
-
-    if (!suggestion.second->icon_url.is_empty()) {
-      // Initiate a request to download the image for the suggestion chip icon.
-      // Note that the request is identified by the suggestion id.
-      delegate_->DownloadImage(
-          suggestion.second->icon_url,
-          base::BindOnce(
-              &SuggestionContainerView::OnSuggestionChipIconDownloaded,
-              download_request_weak_factory_.GetWeakPtr(), id));
-
-      // To reserve layout space until the actual icon can be downloaded, we
-      // supply an empty placeholder image to the suggestion chip view.
-      params.icon = gfx::ImageSkia();
-    }
-
-    SuggestionChipView* suggestion_chip_view =
-        new SuggestionChipView(params, /*listener=*/this);
-    suggestion_chip_view->SetAccessibleName(params.text);
-
-    // Given a suggestion chip view, we need to be able to look up the id of
-    // the underlying suggestion. This is used for handling press events.
-    suggestion_chip_view->SetID(id);
-
-    // Given an id, we also want to be able to look up the corresponding
-    // suggestion chip view. This is used for handling icon download events.
-    suggestion_chip_views_[id] = suggestion_chip_view;
-
-    content_view()->AddChildView(suggestion_chip_view);
+    AddSuggestionChip(/*suggestion=*/*suggestion.second,
+                      /*id=*/suggestion.first);
   }
 }
 
-void SuggestionContainerView::OnSuggestionsCleared() {
-  // Abort any download requests in progress.
-  download_request_weak_factory_.InvalidateWeakPtrs();
+void SuggestionContainerView::AddSuggestionChip(
+    const AssistantSuggestion& suggestion,
+    int id) {
+  SuggestionChipView::Params params;
+  params.text = base::UTF8ToUTF16(suggestion.text);
 
-  // When modifying the view hierarchy, make sure we keep our view cache synced.
-  content_view()->RemoveAllChildViews(/*delete_children=*/true);
-  suggestion_chip_views_.clear();
+  if (!suggestion.icon_url.is_empty()) {
+    // Initiate a request to download the image for the suggestion chip icon.
+    // Note that the request is identified by the suggestion id.
+    delegate()->DownloadImage(
+        suggestion.icon_url,
+        base::BindOnce(&SuggestionContainerView::OnSuggestionChipIconDownloaded,
+                       download_request_weak_factory_.GetWeakPtr(), id));
+
+    // To reserve layout space until the actual icon can be downloaded, we
+    // supply an empty placeholder image to the suggestion chip view.
+    params.icon = gfx::ImageSkia();
+  }
+
+  SuggestionChipView* suggestion_chip_view =
+      new SuggestionChipView(params, /*listener=*/this);
+
+  suggestion_chip_view->SetAccessibleName(params.text);
+
+  // Given a suggestion chip view, we need to be able to look up the id of
+  // the underlying suggestion. This is used for handling press events.
+  suggestion_chip_view->SetID(id);
+
+  // The chip will be animated on its own layer.
+  suggestion_chip_view->SetPaintToLayer();
+  suggestion_chip_view->layer()->SetFillsBoundsOpaquely(false);
+
+  // Given an id, we also want to be able to look up the corresponding
+  // suggestion chip view. This is used for handling icon download events.
+  suggestion_chip_views_[id] = suggestion_chip_view;
+
+  content_view()->AddChildView(suggestion_chip_view);
+
+  // Set the animations for the suggestion chip.
+  AddElementAnimator(
+      std::make_unique<SuggestionChipAnimator>(suggestion_chip_view, this));
 }
 
 void SuggestionContainerView::OnSuggestionChipIconDownloaded(
@@ -169,21 +270,24 @@
 
 void SuggestionContainerView::ButtonPressed(views::Button* sender,
                                             const ui::Event& event) {
+  // Remember which chip was selected, so we can give it a special animation.
+  selected_chip_ = suggestion_chip_views_[sender->GetID()];
+
   const AssistantSuggestion* suggestion = nullptr;
 
   // If we haven't yet received a query response, the suggestion chip that was
   // pressed was a conversation starter.
   if (!has_received_response_) {
-    suggestion = delegate_->GetSuggestionsModel()->GetConversationStarterById(
+    suggestion = delegate()->GetSuggestionsModel()->GetConversationStarterById(
         sender->GetID());
   } else {
     // Otherwise, the suggestion chip belonged to the interaction response.
     suggestion =
-        delegate_->GetInteractionModel()->response()->GetSuggestionById(
+        delegate()->GetInteractionModel()->response()->GetSuggestionById(
             sender->GetID());
   }
 
-  delegate_->OnSuggestionChipPressed(suggestion);
+  delegate()->OnSuggestionChipPressed(suggestion);
 }
 
 void SuggestionContainerView::OnUiVisibilityChanged(
@@ -196,7 +300,7 @@
     // Show conversation starters at the start of a new Assistant session except
     // when the user already started a query in Launcher quick search box (QSB).
     OnConversationStartersChanged(
-        delegate_->GetSuggestionsModel()->GetConversationStarters());
+        delegate()->GetSuggestionsModel()->GetConversationStarters());
     return;
   }
 
diff --git a/ash/assistant/ui/main_stage/suggestion_container_view.h b/ash/assistant/ui/main_stage/suggestion_container_view.h
index 210a4ea..2adc14e2 100644
--- a/ash/assistant/ui/main_stage/suggestion_container_view.h
+++ b/ash/assistant/ui/main_stage/suggestion_container_view.h
@@ -8,10 +8,9 @@
 #include <map>
 #include <memory>
 
-#include "ash/assistant/model/assistant_interaction_model_observer.h"
 #include "ash/assistant/model/assistant_suggestions_model_observer.h"
 #include "ash/assistant/model/assistant_ui_model_observer.h"
-#include "ash/assistant/ui/base/assistant_scroll_view.h"
+#include "ash/assistant/ui/main_stage/animated_container_view.h"
 #include "ash/assistant/ui/main_stage/suggestion_chip_view.h"
 #include "base/component_export.h"
 #include "base/macros.h"
@@ -31,8 +30,7 @@
 // laying out SuggestionChipViews in response to Assistant interaction model
 // suggestion events.
 class COMPONENT_EXPORT(ASSISTANT_UI) SuggestionContainerView
-    : public AssistantScrollView,
-      public AssistantInteractionModelObserver,
+    : public AnimatedContainerView,
       public AssistantSuggestionsModelObserver,
       public AssistantUiModelObserver,
       public views::ButtonListener {
@@ -44,17 +42,12 @@
   explicit SuggestionContainerView(AssistantViewDelegate* delegate);
   ~SuggestionContainerView() override;
 
-  // AssistantScrollView:
+  // AnimatedContainerView:
   const char* GetClassName() const override;
   gfx::Size CalculatePreferredSize() const override;
   int GetHeightForWidth(int width) const override;
   void OnContentsPreferredSizeChanged(views::View* content_view) override;
 
-  // AssistantInteractionModelObserver:
-  void OnResponseChanged(
-      const scoped_refptr<AssistantResponse>& response) override;
-  void OnResponseCleared() override;
-
   // AssistantSuggestionsModelObserver:
   void OnConversationStartersChanged(
       const std::map<int, const AssistantSuggestion*>& conversation_starters)
@@ -70,18 +63,24 @@
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
+  // The suggestion chip that was pressed by the user, or nullptr if no chip was
+  // pressed.
+  const SuggestionChipView* selected_chip() const { return selected_chip_; }
+
  private:
   void InitLayout();
 
+  // AnimatedContainerView:
+  void HandleResponse(const AssistantResponse& response) override;
+  void OnAllViewsRemoved() override;
+
   void OnSuggestionsChanged(
       const std::map<int, const AssistantSuggestion*>& suggestions);
-  void OnSuggestionsCleared();
+  void AddSuggestionChip(const AssistantSuggestion& suggestion, int id);
 
   // Invoked on suggestion chip icon downloaded event.
   void OnSuggestionChipIconDownloaded(int id, const gfx::ImageSkia& icon);
 
-  AssistantViewDelegate* const delegate_;  // Owned by Shell.
-
   views::BoxLayout* layout_manager_;  // Owned by view hierarchy.
 
   // Cache of suggestion chip views owned by the view hierarchy. The key for the
@@ -93,6 +92,8 @@
   // false otherwise.
   bool has_received_response_ = false;
 
+  const SuggestionChipView* selected_chip_ = nullptr;
+
   // Weak pointer factory used for image downloading requests.
   base::WeakPtrFactory<SuggestionContainerView> download_request_weak_factory_{
       this};
diff --git a/ash/assistant/ui/main_stage/ui_element_container_view.cc b/ash/assistant/ui/main_stage/ui_element_container_view.cc
index 9b268a4..5179e3f 100644
--- a/ash/assistant/ui/main_stage/ui_element_container_view.cc
+++ b/ash/assistant/ui/main_stage/ui_element_container_view.cc
@@ -6,12 +6,15 @@
 
 #include <string>
 
+#include "ash/assistant/model/assistant_interaction_model_observer.h"
 #include "ash/assistant/model/assistant_response.h"
 #include "ash/assistant/model/assistant_ui_element.h"
 #include "ash/assistant/ui/assistant_ui_constants.h"
 #include "ash/assistant/ui/assistant_view_delegate.h"
+#include "ash/assistant/ui/main_stage/animated_container_view.h"
 #include "ash/assistant/ui/main_stage/assistant_card_element_view.h"
 #include "ash/assistant/ui/main_stage/assistant_text_element_view.h"
+#include "ash/assistant/ui/main_stage/element_animator.h"
 #include "ash/assistant/util/animation_util.h"
 #include "ash/public/cpp/app_list/app_list_features.h"
 #include "base/callback.h"
@@ -27,26 +30,36 @@
 
 namespace {
 
+using assistant::util::CreateLayerAnimationSequence;
+using assistant::util::CreateOpacityElement;
+using assistant::util::CreateTransformElement;
+using assistant::util::StartLayerAnimationSequence;
+
 // Appearance.
 constexpr int kEmbeddedUiFirstCardMarginTopDip = 8;
 constexpr int kEmbeddedUiPaddingBottomDip = 8;
 constexpr int kMainUiFirstCardMarginTopDip = 40;
 constexpr int kMainUiPaddingBottomDip = 24;
 
-// Card element animation.
-constexpr float kCardElementAnimationFadeOutOpacity = 0.26f;
-
-// Text element animation.
-constexpr float kEmbeddedUiTextElementAnimationFadeOutOpacity = 0.26f;
+// Main UI element animation.
+constexpr base::TimeDelta kMainUiElementAnimationFadeInDelay =
+    base::TimeDelta::FromMilliseconds(83);
+constexpr base::TimeDelta kMainUiElementAnimationFadeInDuration =
+    base::TimeDelta::FromMilliseconds(250);
+constexpr base::TimeDelta kMainUiElementAnimationFadeOutDuration =
+    base::TimeDelta::FromMilliseconds(167);
+// Text elements must fade out to 0 as the thinking dots will appear in the
+// location of the first text element.
 constexpr float kMainUiTextElementAnimationFadeOutOpacity = 0.f;
 
-// UI element animation.
-constexpr base::TimeDelta kUiElementAnimationFadeInDelay =
-    base::TimeDelta::FromMilliseconds(83);
-constexpr base::TimeDelta kUiElementAnimationFadeInDuration =
+// Embedded UI element animation.
+constexpr base::TimeDelta kEmbeddedUiElementAnimationFadeInDuration =
     base::TimeDelta::FromMilliseconds(250);
-constexpr base::TimeDelta kUiElementAnimationFadeOutDuration =
-    base::TimeDelta::FromMilliseconds(167);
+constexpr base::TimeDelta kEmbeddedUiElementAnimationMoveUpDuration =
+    base::TimeDelta::FromMilliseconds(250);
+constexpr base::TimeDelta kEmbeddedUiElementAnimationFadeOutDuration =
+    base::TimeDelta::FromMilliseconds(200);
+constexpr int kEmbeddedUiElementAnimationMoveUpDistanceDip = 32;
 
 // Helpers ---------------------------------------------------------------------
 
@@ -62,10 +75,157 @@
              : kMainUiPaddingBottomDip;
 }
 
-float GetTextElementAnimationFadeOutOpacity() {
-  return app_list_features::IsEmbeddedAssistantUIEnabled()
-             ? kEmbeddedUiTextElementAnimationFadeOutOpacity
-             : kMainUiTextElementAnimationFadeOutOpacity;
+// Animator for elements in the main (non-embedded) UI.
+class MainUiAnimator : public ElementAnimator {
+ public:
+  using ElementAnimator::ElementAnimator;
+  ~MainUiAnimator() override = default;
+
+  // ElementAnimator:
+  void AnimateOut(ui::CallbackLayerAnimationObserver* observer) override {
+    StartLayerAnimationSequence(
+        layer()->GetAnimator(),
+        CreateLayerAnimationSequence(CreateOpacityElement(
+            kMinimumAnimateOutOpacity, kMainUiElementAnimationFadeOutDuration,
+            gfx::Tween::Type::FAST_OUT_SLOW_IN)),
+        observer);
+  }
+
+  void AnimateIn(ui::CallbackLayerAnimationObserver* observer) override {
+    // We fade in the views to full opacity after a slight delay.
+    assistant::util::StartLayerAnimationSequence(
+        layer()->GetAnimator(),
+        CreateLayerAnimationSequence(
+            ui::LayerAnimationElement::CreatePauseElement(
+                ui::LayerAnimationElement::AnimatableProperty::OPACITY,
+                kMainUiElementAnimationFadeInDelay),
+            CreateOpacityElement(1.f, kMainUiElementAnimationFadeInDuration)),
+        observer);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MainUiAnimator);
+};
+
+// Animator used for card elements in the main (non-embedded) UI.
+class MainUiCardAnimator : public MainUiAnimator {
+ public:
+  // Constructor used for card elements.
+  explicit MainUiCardAnimator(AssistantCardElementView* element)
+      : MainUiAnimator(element), element_(element) {}
+
+  ui::Layer* layer() const override { return element_->native_view()->layer(); }
+
+ private:
+  AssistantCardElementView* const element_;
+
+  DISALLOW_COPY_AND_ASSIGN(MainUiCardAnimator);
+};
+
+// Animator used for text elements in the main (non-embedded) UI.
+class MainUiTextAnimator : public MainUiAnimator {
+ public:
+  // Constructor used for text elements.
+  explicit MainUiTextAnimator(AssistantTextElementView* element)
+      : MainUiAnimator(element) {}
+
+  void FadeOut(ui::CallbackLayerAnimationObserver* observer) override {
+    assistant::util::StartLayerAnimationSequence(
+        layer()->GetAnimator(),
+        assistant::util::CreateLayerAnimationSequence(
+            assistant::util::CreateOpacityElement(
+                kMainUiTextElementAnimationFadeOutOpacity, kFadeOutDuration)),
+        observer);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MainUiTextAnimator);
+};
+
+// Animator for elements in the embedded UI.
+class EmbeddedUiAnimator : public ElementAnimator {
+ public:
+  using ElementAnimator::ElementAnimator;
+  ~EmbeddedUiAnimator() override = default;
+
+  // ElementAnimator:
+  void AnimateOut(ui::CallbackLayerAnimationObserver* observer) override {
+    StartLayerAnimationSequence(
+        layer()->GetAnimator(),
+        CreateLayerAnimationSequence(
+            CreateOpacityElement(kMinimumAnimateOutOpacity,
+                                 kEmbeddedUiElementAnimationFadeOutDuration)),
+        observer);
+  }
+
+  void AnimateIn(ui::CallbackLayerAnimationObserver* observer) override {
+    // As part of the animation we will move up the element from the bottom
+    // so we need to start by moving it down.
+    MoveElementDown();
+
+    assistant::util::StartLayerAnimationSequencesTogether(
+        layer()->GetAnimator(),
+        {
+            CreateFadeInAnimation(),
+            CreateMoveUpAnimation(),
+        },
+        observer);
+  }
+
+ private:
+  void MoveElementDown() const {
+    gfx::Transform transform;
+    transform.Translate(0, kEmbeddedUiElementAnimationMoveUpDistanceDip);
+    layer()->SetTransform(transform);
+  }
+
+  ui::LayerAnimationSequence* CreateFadeInAnimation() const {
+    return CreateLayerAnimationSequence(
+        CreateOpacityElement(1.f, kEmbeddedUiElementAnimationFadeInDuration,
+                             gfx::Tween::Type::FAST_OUT_SLOW_IN));
+  }
+
+  ui::LayerAnimationSequence* CreateMoveUpAnimation() const {
+    return CreateLayerAnimationSequence(CreateTransformElement(
+        gfx::Transform(), kEmbeddedUiElementAnimationMoveUpDuration,
+        gfx::Tween::Type::FAST_OUT_SLOW_IN));
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(EmbeddedUiAnimator);
+};
+
+// Animator for card elements in the embedded UI.
+class EmbeddedUiCardAnimator : public EmbeddedUiAnimator {
+ public:
+  // Constructor used for card elements.
+  explicit EmbeddedUiCardAnimator(AssistantCardElementView* element)
+      : EmbeddedUiAnimator(element), element_(element) {}
+
+  ui::Layer* layer() const override { return element_->native_view()->layer(); }
+
+ private:
+  AssistantCardElementView* const element_;
+
+  DISALLOW_COPY_AND_ASSIGN(EmbeddedUiCardAnimator);
+};
+
+// Animator for text elements in the embedded UI.
+using EmbeddedUiTextAnimator = EmbeddedUiAnimator;
+
+std::unique_ptr<ElementAnimator> CreateCardAnimator(
+    AssistantCardElementView* card_element) {
+  if (app_list_features::IsEmbeddedAssistantUIEnabled())
+    return std::make_unique<EmbeddedUiCardAnimator>(card_element);
+  else
+    return std::make_unique<MainUiCardAnimator>(card_element);
+}
+
+std::unique_ptr<ElementAnimator> CreateTextAnimator(
+    AssistantTextElementView* text_element) {
+  if (app_list_features::IsEmbeddedAssistantUIEnabled())
+    return std::make_unique<EmbeddedUiTextAnimator>(text_element);
+  else
+    return std::make_unique<MainUiTextAnimator>(text_element);
 }
 
 }  // namespace
@@ -73,15 +233,12 @@
 // UiElementContainerView ------------------------------------------------------
 
 UiElementContainerView::UiElementContainerView(AssistantViewDelegate* delegate)
-    : delegate_(delegate) {
+    : AnimatedContainerView(delegate) {
   InitLayout();
-
-  // The AssistantViewDelegate should outlive UiElementContainerView.
-  delegate_->AddInteractionModelObserver(this);
 }
 
 UiElementContainerView::~UiElementContainerView() {
-  delegate_->RemoveInteractionModelObserver(this);
+  delegate()->RemoveInteractionModelObserver(this);
 }
 
 const char* UiElementContainerView::GetClassName() const {
@@ -115,14 +272,6 @@
   content_view->SetSize(gfx::Size(width(), preferred_height));
 }
 
-void UiElementContainerView::PreferredSizeChanged() {
-  // Because views are added/removed in batches, we attempt to prevent over-
-  // propagation of the PreferredSizeChanged event during batched view hierarchy
-  // add/remove operations. This helps to reduce layout passes.
-  if (propagate_preferred_size_changed_)
-    AssistantScrollView::PreferredSizeChanged();
-}
-
 void UiElementContainerView::InitLayout() {
   content_view()->SetLayoutManager(std::make_unique<views::BoxLayout>(
       views::BoxLayout::Orientation::kVertical,
@@ -133,133 +282,14 @@
 
 void UiElementContainerView::OnCommittedQueryChanged(
     const AssistantQuery& query) {
-  using assistant::util::CreateLayerAnimationSequence;
-  using assistant::util::CreateOpacityElement;
-
-  // We don't allow processing of events while waiting for the next query
-  // response. The contents will be faded out, so it should not be interactive.
-  // We also scroll to the top to play nice with the transition animation.
-  set_can_process_events_within_subtree(false);
+  // Scroll to the top to play nice with the transition animation.
   ScrollToPosition(vertical_scroll_bar(), 0);
 
-  // When a query is committed, we fade out the views for the previous response
-  // until the next Assistant response has been received.
-  for (const std::pair<ui::LayerOwner*, float>& pair : ui_element_views_) {
-    pair.first->layer()->GetAnimator()->StartAnimation(
-        CreateLayerAnimationSequence(CreateOpacityElement(
-            /*opacity=*/pair.second, kUiElementAnimationFadeOutDuration)));
-  }
+  AnimatedContainerView::OnCommittedQueryChanged(query);
 }
 
-void UiElementContainerView::OnResponseChanged(
-    const scoped_refptr<AssistantResponse>& response) {
-  // We may have to pend the response while we animate the previous response off
-  // stage. We use a shared pointer to ensure that any views we add to the view
-  // hierarchy can be removed before the underlying UI elements are destroyed.
-  pending_response_ = response;
-
-  // If we don't have any pre-existing content, there is nothing to animate off
-  // stage so we we can proceed to add the new response.
-  if (content_view()->children().empty()) {
-    OnResponseAdded(std::move(pending_response_));
-    return;
-  }
-
-  using assistant::util::CreateLayerAnimationSequence;
-  using assistant::util::CreateOpacityElement;
-  using assistant::util::StartLayerAnimationSequence;
-
-  // There is a previous response on stage, so we'll animate it off before
-  // adding the new response. The new response will be added upon invocation of
-  // the exit animation ended callback.
-  auto* exit_animation_observer = new ui::CallbackLayerAnimationObserver(
-      /*animation_ended_callback=*/base::BindRepeating(
-          [](const base::WeakPtr<UiElementContainerView>& weak_ptr,
-             const ui::CallbackLayerAnimationObserver& observer) {
-            // If the UiElementContainerView is destroyed we just return true
-            // to delete our observer. No futher action is needed.
-            if (!weak_ptr)
-              return true;
-
-            // If the exit animation was aborted, we just return true to delete
-            // our observer. No further action is needed.
-            if (observer.aborted_count())
-              return true;
-
-            // All UI elements have finished their exit animations so it's safe
-            // to perform clearing of their views and managed resources.
-            weak_ptr->OnResponseCleared();
-
-            // It is safe to add our pending response, if one exists, to the
-            // view hierarchy now that we've cleared the previous response
-            // from the stage.
-            if (weak_ptr->pending_response_)
-              weak_ptr->OnResponseAdded(std::move(weak_ptr->pending_response_));
-
-            // We return true to delete our observer.
-            return true;
-          },
-          weak_factory_.GetWeakPtr()));
-
-  // Animate the layer belonging to each view in the previous response.
-  for (const std::pair<ui::LayerOwner*, float>& pair : ui_element_views_) {
-    StartLayerAnimationSequence(
-        pair.first->layer()->GetAnimator(),
-        // Fade out the opacity to 0%. Note that we approximate 0% by actually
-        // using 0.01%. We do this to workaround a DCHECK that requires
-        // aura::Windows to have a target opacity > 0% when shown. Because our
-        // window will be removed after it reaches this value, it should be safe
-        // to circumnavigate this DCHECK.
-        CreateLayerAnimationSequence(
-            CreateOpacityElement(0.0001f, kUiElementAnimationFadeOutDuration)),
-        // Observe the animation.
-        exit_animation_observer);
-  }
-
-  // Set the observer to active so that we receive callback events.
-  exit_animation_observer->SetActive();
-}
-
-void UiElementContainerView::OnResponseCleared() {
-  // We explicitly abort all in progress animations here because we will remove
-  // their views immediately and we want to ensure that any animation observers
-  // will be notified of an abort, not an animation completion. Otherwise there
-  // is potential to enter into a bad state (see crbug/952996).
-  for (auto& ui_element_view : ui_element_views_)
-    ui_element_view.first->layer()->GetAnimator()->AbortAllAnimations();
-
-  // We can prevent over-propagation of the PreferredSizeChanged event by
-  // stopping propagation during batched view hierarchy add/remove operations.
-  SetPropagatePreferredSizeChanged(false);
-  content_view()->RemoveAllChildViews(/*delete_children=*/true);
-  ui_element_views_.clear();
-  SetPropagatePreferredSizeChanged(true);
-
-  // Once the response has been cleared from the stage, we can are free to
-  // release our shared pointer. This allows resources associated with the
-  // underlying UI elements to be freed, provided there are no other usages.
-  response_.reset();
-
-  // Reset state for the next response.
-  is_first_card_ = true;
-}
-
-void UiElementContainerView::OnResponseAdded(
-    scoped_refptr<const AssistantResponse> response) {
-  // The response should be fully processed before it is presented.
-  DCHECK_EQ(AssistantResponse::ProcessingState::kProcessed,
-            response->processing_state());
-
-  // We cache a reference to the |response| to ensure that the instance is not
-  // destroyed before we have removed associated views from the view hierarchy.
-  response_ = std::move(response);
-
-  // Because the views for the response are animated in together, we can stop
-  // propagation of PreferredSizeChanged events until all views have been added
-  // to the view hierarchy to reduce layout passes.
-  SetPropagatePreferredSizeChanged(false);
-
-  for (const auto& ui_element : response_->GetUiElements()) {
+void UiElementContainerView::HandleResponse(const AssistantResponse& response) {
+  for (const auto& ui_element : response.GetUiElements()) {
     switch (ui_element->GetType()) {
       case AssistantUiElementType::kCard:
         OnCardElementAdded(
@@ -271,8 +301,6 @@
         break;
     }
   }
-
-  OnAllUiElementsAdded();
 }
 
 void UiElementContainerView::OnCardElementAdded(
@@ -282,7 +310,7 @@
     return;
 
   auto* card_element_view =
-      new AssistantCardElementView(delegate_, card_element);
+      new AssistantCardElementView(delegate(), card_element);
   if (is_first_card_) {
     is_first_card_ = false;
 
@@ -300,23 +328,17 @@
   content_view()->AddChildView(card_element_view);
 
   // The view will be animated on its own layer, so we need to do some initial
-  // layer setup. We're going to fade the view in, so hide it. Note that we
-  // approximate 0% opacity by actually using 0.01%. We do this to workaround
-  // a DCHECK that requires aura::Windows to have a target opacity > 0% when
-  // shown. Because our window will be animated to full opacity from this
-  // value, it should be safe to circumnavigate this DCHECK.
+  // layer setup. We're going to fade the view in, so hide it.
   card_element_view->native_view()->layer()->SetFillsBoundsOpaquely(false);
-  card_element_view->native_view()->layer()->SetOpacity(0.0001f);
+  card_element_view->native_view()->layer()->SetOpacity(0.f);
 
-  // We cache the native view for use during animations and its desired
-  // opacity that we'll animate to while processing the next query response.
-  ui_element_views_.push_back(std::pair<ui::LayerOwner*, float>(
-      card_element_view->native_view(), kCardElementAnimationFadeOutOpacity));
+  // We set the animator to handle all animations for this view.
+  AddElementAnimator(CreateCardAnimator(card_element_view));
 }
 
 void UiElementContainerView::OnTextElementAdded(
     const AssistantTextElement* text_element) {
-  views::View* text_element_view = new AssistantTextElementView(text_element);
+  auto* text_element_view = new AssistantTextElementView(text_element);
 
   // The view will be animated on its own layer, so we need to do some initial
   // layer setup. We're going to fade the view in, so hide it.
@@ -324,56 +346,25 @@
   text_element_view->layer()->SetFillsBoundsOpaquely(false);
   text_element_view->layer()->SetOpacity(0.f);
 
-  // We cache the view for use during animations and its desired opacity that
-  // we'll animate to while processing the next query response.
-  ui_element_views_.push_back(std::pair<ui::LayerOwner*, float>(
-      text_element_view, GetTextElementAnimationFadeOutOpacity()));
-
   content_view()->AddChildView(text_element_view);
+
+  // We set the animator to handle all animations for this view.
+  AddElementAnimator(CreateTextAnimator(text_element_view));
 }
 
-void UiElementContainerView::OnAllUiElementsAdded() {
-  using assistant::util::CreateLayerAnimationSequence;
-  using assistant::util::CreateOpacityElement;
+void UiElementContainerView::OnAllViewsRemoved() {
+  // Reset state for the next response.
+  is_first_card_ = true;
+}
 
-  // Now that the response for the current query has been added to the view
-  // hierarchy, we can re-enable processing of events. We can also restart
-  // propagation of PreferredSizeChanged events since all views have been added
-  // to the view hierarchy.
-  set_can_process_events_within_subtree(true);
-  SetPropagatePreferredSizeChanged(true);
-
-  // Now that we've received and added all UI elements for the current query
-  // response, we can animate them in.
-  for (const std::pair<ui::LayerOwner*, float>& pair : ui_element_views_) {
-    // We fade in the views to full opacity after a slight delay.
-    pair.first->layer()->GetAnimator()->StartAnimation(
-        CreateLayerAnimationSequence(
-            ui::LayerAnimationElement::CreatePauseElement(
-                ui::LayerAnimationElement::AnimatableProperty::OPACITY,
-                kUiElementAnimationFadeInDelay),
-            CreateOpacityElement(1.f, kUiElementAnimationFadeInDuration)));
-  }
-
+void UiElementContainerView::OnAllViewsAnimatedIn() {
   // Let screen reader read the query result. This includes the text response
   // and the card fallback text, but webview result is not included.
   // We don't read when there is TTS to avoid speaking over the server response.
   const AssistantResponse* response =
-      delegate_->GetInteractionModel()->response();
+      delegate()->GetInteractionModel()->response();
   if (!response->has_tts())
     NotifyAccessibilityEvent(ax::mojom::Event::kAlert, true);
 }
 
-void UiElementContainerView::SetPropagatePreferredSizeChanged(bool propagate) {
-  if (propagate == propagate_preferred_size_changed_)
-    return;
-
-  propagate_preferred_size_changed_ = propagate;
-
-  // When we are no longer stopping propagation of PreferredSizeChanged events,
-  // we fire an event off to ensure the view hierarchy is properly laid out.
-  if (propagate_preferred_size_changed_)
-    PreferredSizeChanged();
-}
-
 }  // namespace ash
diff --git a/ash/assistant/ui/main_stage/ui_element_container_view.h b/ash/assistant/ui/main_stage/ui_element_container_view.h
index 18a2c31..2e098a15 100644
--- a/ash/assistant/ui/main_stage/ui_element_container_view.h
+++ b/ash/assistant/ui/main_stage/ui_element_container_view.h
@@ -10,8 +10,7 @@
 #include <utility>
 #include <vector>
 
-#include "ash/assistant/model/assistant_interaction_model_observer.h"
-#include "ash/assistant/ui/base/assistant_scroll_view.h"
+#include "ash/assistant/ui/main_stage/animated_container_view.h"
 #include "base/component_export.h"
 #include "base/macros.h"
 #include "base/memory/scoped_refptr.h"
@@ -28,63 +27,34 @@
 // laying out text views and embedded card views in response to Assistant
 // interaction model UI element events.
 class COMPONENT_EXPORT(ASSISTANT_UI) UiElementContainerView
-    : public AssistantScrollView,
-      public AssistantInteractionModelObserver {
+    : public AnimatedContainerView {
  public:
   explicit UiElementContainerView(AssistantViewDelegate* delegate);
   ~UiElementContainerView() override;
 
-  // AssistantScrollView:
+  // AnimatedContainerView:
   const char* GetClassName() const override;
   gfx::Size CalculatePreferredSize() const override;
   int GetHeightForWidth(int width) const override;
   gfx::Size GetMinimumSize() const override;
   void OnContentsPreferredSizeChanged(views::View* content_view) override;
-  void PreferredSizeChanged() override;
-
-  // AssistantInteractionModelObserver:
   void OnCommittedQueryChanged(const AssistantQuery& query) override;
-  void OnResponseChanged(
-      const scoped_refptr<AssistantResponse>& response) override;
-  void OnResponseCleared() override;
 
  private:
   void InitLayout();
 
-  void OnResponseAdded(scoped_refptr<const AssistantResponse> response);
+  // AnimatedContainerView:
+  void HandleResponse(const AssistantResponse& response) override;
+  void OnAllViewsRemoved() override;
+  void OnAllViewsAnimatedIn() override;
+
   void OnCardElementAdded(const AssistantCardElement* card_element);
   void OnTextElementAdded(const AssistantTextElement* text_element);
-  void OnAllUiElementsAdded();
-
-  // Sets whether or not PreferredSizeChanged events should be propagated.
-  void SetPropagatePreferredSizeChanged(bool propagate);
-
-  AssistantViewDelegate* const delegate_;
-
-  // Shared pointers to the response that is currently on stage as well as the
-  // pending response to be presented following the former's animated exit. We
-  // use shared pointers to ensure that underlying UI elements are not destroyed
-  // before we have an opportunity to remove their associated views.
-  scoped_refptr<const AssistantResponse> response_;
-  scoped_refptr<const AssistantResponse> pending_response_;
-
-  // Whether we should allow propagation of PreferredSizeChanged events. Because
-  // we only animate views in/out in batches, we can prevent over-propagation of
-  // PreferredSizeChanged events by waiting until the entirety of a response has
-  // been added/removed before propagating. This reduces layout passes.
-  bool propagate_preferred_size_changed_ = true;
-
-  // UI elements will be animated on their own layers. We track the desired
-  // opacity to which each layer should be animated when processing the next
-  // query response.
-  std::vector<std::pair<ui::LayerOwner*, float>> ui_element_views_;
 
   // Whether or not the card we are adding is the first card for the current
   // Assistant response. The first card requires the addition of a top margin.
   bool is_first_card_ = true;
 
-  base::WeakPtrFactory<UiElementContainerView> weak_factory_{this};
-
   DISALLOW_COPY_AND_ASSIGN(UiElementContainerView);
 };
 
diff --git a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
index b512e60..29d21aa 100644
--- a/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
+++ b/ash/components/shortcut_viewer/views/keyboard_shortcut_view.cc
@@ -16,6 +16,7 @@
 #include "ash/components/strings/grit/ash_components_strings.h"
 #include "ash/public/cpp/app_list/internal_app_id_constants.h"
 #include "ash/public/cpp/app_types.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/resources/grit/ash_public_unscaled_resources.h"
 #include "ash/public/cpp/shelf_item.h"
 #include "ash/public/cpp/window_properties.h"
@@ -132,6 +133,22 @@
   }
 }
 
+// Returns true if the given |item| should be excluded from the view, since
+// certain shortcuts can be associated with a disabled feature behind a flag.
+bool ShouldExcludeItem(const KeyboardShortcutItem& item) {
+  switch (item.description_message_id) {
+    case IDS_KSV_DESCRIPTION_DESKS_NEW_DESK:
+    case IDS_KSV_DESCRIPTION_DESKS_REMOVE_CURRENT_DESK:
+    case IDS_KSV_DESCRIPTION_DESKS_ACTIVATE_LEFT_DESK:
+    case IDS_KSV_DESCRIPTION_DESKS_ACTIVATE_RIGHT_DESK:
+    case IDS_KSV_DESCRIPTION_DESKS_MOVE_ACTIVE_ITEM_LEFT_DESK:
+    case IDS_KSV_DESCRIPTION_DESKS_MOVE_ACTIVE_ITEM_RIGHT_DESK:
+      return !ash::features::IsVirtualDesksEnabled();
+  }
+
+  return false;
+}
+
 }  // namespace
 
 KeyboardShortcutView::~KeyboardShortcutView() {
@@ -356,6 +373,9 @@
   // clear the cache.
   KeyboardShortcutItemView::ClearKeycodeToString16Cache();
   for (const auto& item : GetKeyboardShortcutItemList()) {
+    if (ShouldExcludeItem(item))
+      continue;
+
     for (auto category : item.categories) {
       shortcut_views_.emplace_back(
           std::make_unique<KeyboardShortcutItemView>(item, category));
diff --git a/ash/components/shortcut_viewer_strings.grdp b/ash/components/shortcut_viewer_strings.grdp
index 7ee54f2..a2ba2901 100644
--- a/ash/components/shortcut_viewer_strings.grdp
+++ b/ash/components/shortcut_viewer_strings.grdp
@@ -634,16 +634,16 @@
     <ph name="alt">$1<ex>Alt</ex></ph><ph name="separator">$2<ex>+</ex></ph><ph name="e">$3<ex>e</ex></ph> or <ph name="f">$4<ex>f</ex></ph>
   </message>
   <message name="IDS_KSV_DESCRIPTION_DESKS_NEW_DESK" desc="Description of the command in keyboard shortcut viewer.">
-    Create a new virtual desk
+    Create a new desk
   </message>
   <message name="IDS_KSV_DESCRIPTION_DESKS_REMOVE_CURRENT_DESK" desc="Description of the command in keyboard shortcut viewer.">
-    Remove the current virtual desk
+    Remove the current desk
   </message>
   <message name="IDS_KSV_DESCRIPTION_DESKS_ACTIVATE_LEFT_DESK" desc="Description of the command in keyboard shortcut viewer.">
-    Activate the virtual desk on the left
+    Activate the desk on the left
   </message>
   <message name="IDS_KSV_DESCRIPTION_DESKS_ACTIVATE_RIGHT_DESK" desc="Description of the command in keyboard shortcut viewer.">
-    Activate the virtual desk on the right
+    Activate the desk on the right
   </message>
   <message name="IDS_KSV_DESCRIPTION_DESKS_MOVE_ACTIVE_ITEM_LEFT_DESK" desc="Description of the command in keyboard shortcut viewer.">
     Move active window to the desk on the left
diff --git a/ash/magnifier/docked_magnifier_controller_impl.cc b/ash/magnifier/docked_magnifier_controller_impl.cc
index 115b16f..aa252d2 100644
--- a/ash/magnifier/docked_magnifier_controller_impl.cc
+++ b/ash/magnifier/docked_magnifier_controller_impl.cc
@@ -507,18 +507,17 @@
   is_minimum_point_of_interest_height_valid_ = false;
 
   if (old_root_window) {
-    if (separator_layer_) {
-      GetViewportParentContainerForRoot(old_root_window)
-          ->layer()
-          ->Remove(separator_layer_.get());
-    }
-    if (update_old_root_workarea)
-      SetViewportHeightInWorkArea(old_root_window, 0);
-
+    // Order here matters. We should stop observing caret bounds changes before
+    // updating the work area bounds of the old root window. Otherwise, work
+    // area bounds changes will lead to caret bounds changes that recurses back
+    // here unnecessarily. https://crbug.com/1000903.
     if (input_method_)
       input_method_->RemoveObserver(this);
     input_method_ = nullptr;
 
+    if (update_old_root_workarea)
+      SetViewportHeightInWorkArea(old_root_window, 0);
+
     // Reset mouse cursor confinement to default.
     RootWindowController::ForWindow(old_root_window)
         ->ash_host()
diff --git a/ash/magnifier/docked_magnifier_controller_impl_unittest.cc b/ash/magnifier/docked_magnifier_controller_impl_unittest.cc
index 7594dc0..5baed06 100644
--- a/ash/magnifier/docked_magnifier_controller_impl_unittest.cc
+++ b/ash/magnifier/docked_magnifier_controller_impl_unittest.cc
@@ -652,6 +652,37 @@
   TestMagnifierLayerTransform(new_caret_center, root_windows[0]);
 }
 
+// Tests that there are no crashes observed when the docked magnifier switches
+// displays, moving away from a display with a maximized window that has a
+// focused text input field. Changing the old display's work area bounds should
+// not cause recursive caret bounds change notifications into the docked
+// magnifier. https://crbug.com/1000903.
+TEST_P(DockedMagnifierTest, NoCrashDueToRecursion) {
+  UpdateDisplay("600x900,800x600");
+  const auto roots = Shell::GetAllRootWindows();
+  ASSERT_EQ(2u, roots.size());
+
+  MagnifierTextInputTestHelper text_input_helper;
+  text_input_helper.CreateAndShowTextInputViewInRoot(gfx::Rect(0, 0, 600, 900),
+                                                     roots[0]);
+  text_input_helper.MaximizeWidget();
+
+  // Enable the docked magnifier.
+  controller()->SetEnabled(true);
+  const float scale1 = 2.0f;
+  controller()->SetScale(scale1);
+  EXPECT_TRUE(controller()->GetEnabled());
+  EXPECT_FLOAT_EQ(scale1, controller()->GetScale());
+
+  // Focus on the text input field.
+  text_input_helper.FocusOnTextInputView();
+  gfx::Point caret_center(text_input_helper.GetCaretBounds().CenterPoint());
+  TestMagnifierLayerTransform(caret_center, roots[0]);
+
+  // Move the mouse to the second display and expect no crashes.
+  GetEventGenerator()->MoveMouseTo(1000, 300);
+}
+
 TEST_P(DockedMagnifierTest, FocusChangeEvents) {
   UpdateDisplay("600x900");
   const auto root_windows = Shell::GetAllRootWindows();
diff --git a/ash/magnifier/magnifier_test_utils.cc b/ash/magnifier/magnifier_test_utils.cc
index 486b71f..a93e418 100644
--- a/ash/magnifier/magnifier_test_utils.cc
+++ b/ash/magnifier/magnifier_test_utils.cc
@@ -179,6 +179,11 @@
   text_input_view_->FocusOnTextInput();
 }
 
+void MagnifierTextInputTestHelper::MaximizeWidget() {
+  DCHECK(text_input_view_);
+  text_input_view_->GetWidget()->Maximize();
+}
+
 ui::InputMethod* MagnifierTextInputTestHelper::GetInputMethod() {
   DCHECK(text_input_view_);
   return text_input_view_->GetWidget()->GetInputMethod();
diff --git a/ash/magnifier/magnifier_test_utils.h b/ash/magnifier/magnifier_test_utils.h
index 4284fc2..28b3b8a 100644
--- a/ash/magnifier/magnifier_test_utils.h
+++ b/ash/magnifier/magnifier_test_utils.h
@@ -78,6 +78,9 @@
 
   void FocusOnTextInputView();
 
+  // Maximizes the widget of |text_input_view_|.
+  void MaximizeWidget();
+
  private:
   ui::InputMethod* GetInputMethod();
 
diff --git a/ash/media/media_notification_controller_impl.h b/ash/media/media_notification_controller_impl.h
index 1b5a93b6..3cf05f4 100644
--- a/ash/media/media_notification_controller_impl.h
+++ b/ash/media/media_notification_controller_impl.h
@@ -55,6 +55,7 @@
   void HideNotification(const std::string& id) override;
   void RemoveItem(const std::string& id) override;
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override;
+  void LogMediaSessionActionButtonPressed(const std::string& id) override {}
 
   std::unique_ptr<MediaNotificationContainerImpl> CreateMediaNotification(
       const message_center::Notification& notification);
diff --git a/ash/public/cpp/app_list/app_list_features.cc b/ash/public/cpp/app_list/app_list_features.cc
index 0d19a3c..dc08f3ce 100644
--- a/ash/public/cpp/app_list/app_list_features.cc
+++ b/ash/public/cpp/app_list/app_list_features.cc
@@ -13,8 +13,6 @@
 
 const base::Feature kEnableAnswerCard{"EnableAnswerCard",
                                       base::FEATURE_ENABLED_BY_DEFAULT};
-const base::Feature kEnableBackgroundBlur{"EnableBackgroundBlur",
-                                          base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kEnablePlayStoreAppSearch{
     "EnablePlayStoreAppSearch", base::FEATURE_DISABLED_BY_DEFAULT};
 const base::Feature kEnableAppDataSearch{"EnableAppDataSearch",
@@ -55,10 +53,6 @@
          !IsEmbeddedAssistantUIEnabled();
 }
 
-bool IsBackgroundBlurEnabled() {
-  return base::FeatureList::IsEnabled(kEnableBackgroundBlur);
-}
-
 bool IsPlayStoreAppSearchEnabled() {
   // Not using local static variable to allow tests to change this value.
   return base::FeatureList::IsEnabled(kEnablePlayStoreAppSearch);
diff --git a/ash/public/cpp/app_list/app_list_features.h b/ash/public/cpp/app_list/app_list_features.h
index 5acdb603..d3cc1cfa 100644
--- a/ash/public/cpp/app_list/app_list_features.h
+++ b/ash/public/cpp/app_list/app_list_features.h
@@ -21,11 +21,6 @@
 // Enables the answer card in the app list.
 ASH_PUBLIC_EXPORT extern const base::Feature kEnableAnswerCard;
 
-// Enables background blur for the app list, lock screen, and tab switcher, also
-// enables the AppsGridView mask layer. In this mode, slower devices may have
-// choppier app list animations. crbug.com/765292.
-ASH_PUBLIC_EXPORT extern const base::Feature kEnableBackgroundBlur;
-
 // Enables the Play Store app search.
 ASH_PUBLIC_EXPORT extern const base::Feature kEnablePlayStoreAppSearch;
 
@@ -80,7 +75,6 @@
 ASH_PUBLIC_EXPORT extern const base::Feature kScalableAppList;
 
 bool ASH_PUBLIC_EXPORT IsAnswerCardEnabled();
-bool ASH_PUBLIC_EXPORT IsBackgroundBlurEnabled();
 bool ASH_PUBLIC_EXPORT IsPlayStoreAppSearchEnabled();
 bool ASH_PUBLIC_EXPORT IsAppDataSearchEnabled();
 bool ASH_PUBLIC_EXPORT IsSettingsShortcutSearchEnabled();
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index a98e683..d3867a4 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -87,6 +87,9 @@
 const base::Feature kUnifiedMessageCenterRefactor{
     "UnifiedMessageCenterRefactor", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kEnableBackgroundBlur{"EnableBackgroundBlur",
+                                          base::FEATURE_DISABLED_BY_DEFAULT};
+
 bool IsHideArcMediaNotificationsEnabled() {
   return base::FeatureList::IsEnabled(kMediaSessionNotification) &&
          base::FeatureList::IsEnabled(kHideArcMediaNotifications);
@@ -160,5 +163,9 @@
   return base::FeatureList::IsEnabled(kUnifiedMessageCenterRefactor);
 }
 
+bool IsBackgroundBlurEnabled() {
+  return base::FeatureList::IsEnabled(kEnableBackgroundBlur);
+}
+
 }  // namespace features
 }  // namespace ash
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index d4e3451a..1b3d57b 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -106,6 +106,11 @@
 ASH_PUBLIC_EXPORT extern const base::Feature
     kSwapSideVolumeButtonsForOrientation;
 
+// Enables background blur for the app list, shelf, unified system tray,
+// autoclick menu, etc. Also enables the AppsGridView mask layer, slower devices
+// may have choppier app list animations while in this mode. crbug.com/765292.
+ASH_PUBLIC_EXPORT extern const base::Feature kEnableBackgroundBlur;
+
 // Enables refactored UnifiedMessageCenter which is completely separated from
 // the UnifiedSystemTrayView.
 ASH_PUBLIC_EXPORT extern const base::Feature kUnifiedMessageCenterRefactor;
@@ -144,6 +149,8 @@
 
 ASH_PUBLIC_EXPORT bool IsUnifiedMessageCenterRefactorEnabled();
 
+ASH_PUBLIC_EXPORT bool IsBackgroundBlurEnabled();
+
 }  // namespace features
 }  // namespace ash
 
diff --git a/ash/shelf/overflow_bubble_view.cc b/ash/shelf/overflow_bubble_view.cc
index 8012d19e..18c83bf78 100644
--- a/ash/shelf/overflow_bubble_view.cc
+++ b/ash/shelf/overflow_bubble_view.cc
@@ -324,11 +324,11 @@
   return diff;
 }
 
-int OverflowBubbleView::GetFirstVisibleIndexForTest() const {
+int OverflowBubbleView::GetFirstVisibleIndex() const {
   return shelf_container_view_->first_visible_index();
 }
 
-int OverflowBubbleView::GetLastVisibleIndexForTest() const {
+int OverflowBubbleView::GetLastVisibleIndex() const {
   return shelf_container_view_->last_visible_index();
 }
 
@@ -451,6 +451,18 @@
     ScrollByYOffset(offset, true);
 }
 
+void OverflowBubbleView::ScrollToBeginning() {
+  scroll_offset_ = gfx::Vector2dF();
+  Layout();
+}
+
+void OverflowBubbleView::ScrollToEnd() {
+  scroll_offset_ = GetShelf()->IsHorizontalAlignment()
+                       ? gfx::Vector2dF(CalculateScrollUpperBound(), 0)
+                       : gfx::Vector2dF(0, CalculateScrollUpperBound());
+  Layout();
+}
+
 gfx::Size OverflowBubbleView::CalculatePreferredSize() const {
   gfx::Rect monitor_rect =
       display::Screen::GetScreen()
@@ -582,6 +594,32 @@
   return "OverflowBubbleView";
 }
 
+void OverflowBubbleView::ScrollRectToVisible(const gfx::Rect& rect) {
+  const bool is_horizontal_alignment = GetShelf()->IsHorizontalAlignment();
+
+  // |rect| should be a shelf app icon's bounds in OverflowBubbleView's
+  // coordinates. Calculates the index of this app icon.
+  const int start_location = is_horizontal_alignment ? rect.x() : rect.y();
+  const int shelf_container_start_location =
+      is_horizontal_alignment ? shelf_container_view_->bounds().x()
+                              : shelf_container_view_->bounds().y();
+  const int index =
+      (start_location - shelf_container_start_location) / GetUnit() +
+      shelf_view_->first_visible_index();
+
+  if (index <= GetLastVisibleIndex() && index >= GetFirstVisibleIndex())
+    return;
+
+  if (index == shelf_view_->last_visible_index())
+    ScrollToEnd();
+  else if (index == shelf_view_->first_visible_index())
+    ScrollToBeginning();
+  else if (index > GetLastVisibleIndex())
+    ScrollToNewPage(/*forward=*/true);
+  else if (index < GetFirstVisibleIndex())
+    ScrollToNewPage(/*forward=*/false);
+}
+
 void OverflowBubbleView::OnShelfButtonAboutToRequestFocusFromTabTraversal(
     ShelfButton* button,
     bool reverse) {}
diff --git a/ash/shelf/overflow_bubble_view.h b/ash/shelf/overflow_bubble_view.h
index dfe4877d..ee1e99f 100644
--- a/ash/shelf/overflow_bubble_view.h
+++ b/ash/shelf/overflow_bubble_view.h
@@ -53,8 +53,8 @@
   int ScrollByXOffset(float x_offset, bool animating);
   int ScrollByYOffset(float y_offset, bool animating);
 
-  int GetFirstVisibleIndexForTest() const;
-  int GetLastVisibleIndexForTest() const;
+  int GetFirstVisibleIndex() const;
+  int GetLastVisibleIndex() const;
 
   // views::BubbleDialogDelegateView:
   gfx::Rect GetBubbleBounds() override;
@@ -109,12 +109,16 @@
   // page or previous page is shown.
   void ScrollToNewPage(bool forward);
 
+  void ScrollToBeginning();
+  void ScrollToEnd();
+
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
   void Layout() override;
   void ChildPreferredSizeChanged(views::View* child) override;
   bool OnMouseWheel(const ui::MouseWheelEvent& event) override;
   const char* GetClassName() const override;
+  void ScrollRectToVisible(const gfx::Rect& rect) override;
 
   // ShelfButtonDelegate:
   void OnShelfButtonAboutToRequestFocusFromTabTraversal(ShelfButton* button,
diff --git a/ash/shelf/shelf_layout_manager.cc b/ash/shelf/shelf_layout_manager.cc
index 3d54ea2..85b6fa7d 100644
--- a/ash/shelf/shelf_layout_manager.cc
+++ b/ash/shelf/shelf_layout_manager.cc
@@ -15,8 +15,8 @@
 #include "ash/app_list/views/app_list_view.h"
 #include "ash/home_screen/home_launcher_gesture_handler.h"
 #include "ash/home_screen/home_screen_controller.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/app_list/app_list_types.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
 #include "ash/screen_util.h"
@@ -244,8 +244,7 @@
 ShelfLayoutManager::ShelfLayoutManager(ShelfWidget* shelf_widget, Shelf* shelf)
     : shelf_widget_(shelf_widget),
       shelf_(shelf),
-      is_background_blur_enabled_(
-          app_list_features::IsBackgroundBlurEnabled()) {
+      is_background_blur_enabled_(features::IsBackgroundBlurEnabled()) {
   DCHECK(shelf_widget_);
   DCHECK(shelf_);
 }
diff --git a/ash/shelf/shelf_view_unittest.cc b/ash/shelf/shelf_view_unittest.cc
index ea1e0902..ab438ce 100644
--- a/ash/shelf/shelf_view_unittest.cc
+++ b/ash/shelf/shelf_view_unittest.cc
@@ -2704,9 +2704,9 @@
   // overflow bubble is correct.  Note that the last visible index should be
   // |base_index| + |max_accommodated_shelf_num| - 2, because one place is
   // occupied by the arrow button.
-  EXPECT_EQ(base_index, bubble_view->GetFirstVisibleIndexForTest());
+  EXPECT_EQ(base_index, bubble_view->GetFirstVisibleIndex());
   EXPECT_EQ(base_index + max_accommodated_shelf_num - 2,
-            bubble_view->GetLastVisibleIndexForTest());
+            bubble_view->GetLastVisibleIndex());
 
   // Scroll the overflow bubble by half of |fading_zone_|.
   bubble_view->ScrollByXOffset(fading_zone_ / 2, false);
@@ -2715,20 +2715,20 @@
   // Verifies that the first visible index increases by 1 because the left arrow
   // button shows. The app short referred by the first visible index has the
   // correct opacity.
-  EXPECT_EQ(base_index + 1, bubble_view->GetFirstVisibleIndexForTest());
+  EXPECT_EQ(base_index + 1, bubble_view->GetFirstVisibleIndex());
   views::View* leftmost_view =
-      shelf_view_model->view_at(bubble_view->GetFirstVisibleIndexForTest());
+      shelf_view_model->view_at(bubble_view->GetFirstVisibleIndex());
   EXPECT_EQ(0.5f, leftmost_view->layer()->opacity());
 
   // Verifies that the last visible index is expected. Note that we need to
   // check the opacity of the app shortcut whose index is |last_visible_index| +
   // 1. See OverflowBubbleView::UpdateOpacityOfEdgeIcons for more details.
   EXPECT_EQ(base_index + max_accommodated_shelf_num - 2,
-            bubble_view->GetLastVisibleIndexForTest());
-  ASSERT_LT(bubble_view->GetLastVisibleIndexForTest() + 1,
+            bubble_view->GetLastVisibleIndex());
+  ASSERT_LT(bubble_view->GetLastVisibleIndex() + 1,
             shelf_view_model->view_size());
   views::View* rightmost_view =
-      shelf_view_model->view_at(bubble_view->GetLastVisibleIndexForTest() + 1);
+      shelf_view_model->view_at(bubble_view->GetLastVisibleIndex() + 1);
   EXPECT_EQ(0.f, rightmost_view->layer()->opacity());
 }
 
diff --git a/ash/system/accessibility/autoclick_menu_bubble_controller.cc b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
index 1de741e..076ca5c8 100644
--- a/ash/system/accessibility/autoclick_menu_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_menu_bubble_controller.cc
@@ -4,7 +4,7 @@
 
 #include "ash/system/accessibility/autoclick_menu_bubble_controller.h"
 
-#include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
@@ -242,7 +242,7 @@
       CollisionDetectionUtils::RelativePriority::kAutomaticClicksMenu);
   bubble_view_->InitializeAndShowBubble();
 
-  if (app_list_features::IsBackgroundBlurEnabled()) {
+  if (features::IsBackgroundBlurEnabled()) {
     bubble_widget_->client_view()->layer()->SetBackgroundBlur(
         kUnifiedMenuBackgroundBlur);
   }
diff --git a/ash/system/accessibility/autoclick_scroll_bubble_controller.cc b/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
index 3609ea0a..2dcc4d51 100644
--- a/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
+++ b/ash/system/accessibility/autoclick_scroll_bubble_controller.cc
@@ -4,7 +4,7 @@
 
 #include "ash/system/accessibility/autoclick_scroll_bubble_controller.h"
 
-#include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
@@ -218,7 +218,7 @@
       CollisionDetectionUtils::RelativePriority::kAutomaticClicksScrollMenu);
   bubble_view_->InitializeAndShowBubble();
 
-  if (app_list_features::IsBackgroundBlurEnabled()) {
+  if (features::IsBackgroundBlurEnabled()) {
     bubble_widget_->client_view()->layer()->SetBackgroundBlur(
         kUnifiedMenuBackgroundBlur);
   }
diff --git a/ash/system/bluetooth/tray_bluetooth_helper_experimental.cc b/ash/system/bluetooth/tray_bluetooth_helper_experimental.cc
index ac14de2..483ca70 100644
--- a/ash/system/bluetooth/tray_bluetooth_helper_experimental.cc
+++ b/ash/system/bluetooth/tray_bluetooth_helper_experimental.cc
@@ -11,6 +11,7 @@
 #include "ash/system/tray/system_tray_notifier.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/constants.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 #include "services/service_manager/public/cpp/identity.h"
@@ -30,9 +31,9 @@
 TrayBluetoothHelperExperimental::~TrayBluetoothHelperExperimental() = default;
 
 void TrayBluetoothHelperExperimental::Initialize() {
-  device::mojom::BluetoothSystemFactoryPtr bluetooth_system_factory;
-  connector_->BindInterface(device::mojom::kServiceName,
-                            &bluetooth_system_factory);
+  mojo::Remote<device::mojom::BluetoothSystemFactory> bluetooth_system_factory;
+  connector_->Connect(device::mojom::kServiceName,
+                      bluetooth_system_factory.BindNewPipeAndPassReceiver());
 
   device::mojom::BluetoothSystemClientPtr client_ptr;
   bluetooth_system_client_binding_.Bind(mojo::MakeRequest(&client_ptr));
diff --git a/ash/system/message_center/unified_message_center_bubble.cc b/ash/system/message_center/unified_message_center_bubble.cc
index 2b041c3..a1f43c86 100644
--- a/ash/system/message_center/unified_message_center_bubble.cc
+++ b/ash/system/message_center/unified_message_center_bubble.cc
@@ -55,9 +55,9 @@
 }
 
 UnifiedMessageCenterBubble::~UnifiedMessageCenterBubble() {
-  tray_->tray_event_filter()->RemoveBubble(this);
-  tray_->bubble()->unified_view()->RemoveObserver(this);
   if (bubble_widget_) {
+    tray_->tray_event_filter()->RemoveBubble(this);
+    tray_->bubble()->unified_view()->RemoveObserver(this);
     CHECK(message_center_view_);
     message_center_view_->RemoveObserver(this);
 
@@ -112,6 +112,8 @@
 
 void UnifiedMessageCenterBubble::OnWidgetDestroying(views::Widget* widget) {
   CHECK_EQ(bubble_widget_, widget);
+  tray_->tray_event_filter()->RemoveBubble(this);
+  tray_->bubble()->unified_view()->RemoveObserver(this);
   bubble_widget_->RemoveObserver(this);
   bubble_widget_ = nullptr;
 }
diff --git a/ash/system/message_center/unified_message_center_bubble_unittest.cc b/ash/system/message_center/unified_message_center_bubble_unittest.cc
index 17176529..360b3f8 100644
--- a/ash/system/message_center/unified_message_center_bubble_unittest.cc
+++ b/ash/system/message_center/unified_message_center_bubble_unittest.cc
@@ -49,7 +49,7 @@
   }
 
   UnifiedMessageCenterBubble* GetMessageCenterBubble() {
-    return GetPrimaryUnifiedSystemTray()->message_center_bubble_for_test();
+    return GetPrimaryUnifiedSystemTray()->message_center_bubble();
   }
 
   UnifiedSystemTrayBubble* GetSystemTrayBubble() {
diff --git a/ash/system/message_center/unified_message_center_view.cc b/ash/system/message_center/unified_message_center_view.cc
index 686bd2a..d560bd6 100644
--- a/ash/system/message_center/unified_message_center_view.cc
+++ b/ash/system/message_center/unified_message_center_view.cc
@@ -512,21 +512,13 @@
   SessionControllerImpl* session_controller =
       Shell::Get()->session_controller();
 
-  if (features::IsUnifiedMessageCenterRefactorEnabled()) {
-    SetVisible(message_list_view_->GetTotalNotificationCount() &&
-               (available_height_ >= kUnifiedNotificationMinimumHeight &&
-                session_controller->ShouldShowNotificationTray() &&
-                (!session_controller->IsScreenLocked() ||
-                 AshMessageCenterLockScreenController::IsEnabled())));
-  } else {
-    SetVisible(
-        available_height_ >= kUnifiedNotificationMinimumHeight &&
-        (animation_state_ == UnifiedMessageCenterAnimationState::COLLAPSE ||
-         message_list_view_->GetPreferredSize().height() > 0) &&
-        session_controller->ShouldShowNotificationTray() &&
-        (!session_controller->IsScreenLocked() ||
-         AshMessageCenterLockScreenController::IsEnabled()));
-  }
+  SetVisible(
+      available_height_ >= kUnifiedNotificationMinimumHeight &&
+      (animation_state_ == UnifiedMessageCenterAnimationState::COLLAPSE ||
+       message_list_view_->GetPreferredSize().height() > 0) &&
+      session_controller->ShouldShowNotificationTray() &&
+      (!session_controller->IsScreenLocked() ||
+       AshMessageCenterLockScreenController::IsEnabled()));
 
   // When notification list went invisible, the last notification should be
   // targeted next time.
diff --git a/ash/system/power/power_button_menu_view.cc b/ash/system/power/power_button_menu_view.cc
index 39e1d909..455733cd 100644
--- a/ash/system/power/power_button_menu_view.cc
+++ b/ash/system/power/power_button_menu_view.cc
@@ -5,7 +5,7 @@
 #include "ash/system/power/power_button_menu_view.h"
 
 #include "ash/display/screen_orientation_controller.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/new_window_delegate.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/session/session_controller_impl.h"
@@ -39,7 +39,7 @@
 
 SkColor GetMenuBackgroundColor() {
   return AshColorProvider::Get()->DeprecatedGetBaseLayerColor(
-      app_list_features::IsBackgroundBlurEnabled()
+      features::IsBackgroundBlurEnabled()
           ? AshColorProvider::BaseLayerType::kTransparentWithBlur
           : AshColorProvider::BaseLayerType::kTransparentWithoutBlur,
       kPowerButtonMenuBackgroundColor);
diff --git a/ash/system/toast/toast_overlay.cc b/ash/system/toast/toast_overlay.cc
index 8190c76..ad549b9 100644
--- a/ash/system/toast/toast_overlay.cc
+++ b/ash/system/toast/toast_overlay.cc
@@ -5,7 +5,7 @@
 #include "ash/system/toast/toast_overlay.h"
 
 #include "ash/keyboard/ui/keyboard_ui_controller.h"
-#include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/ash_typography.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/root_window_controller.h"
@@ -157,7 +157,7 @@
                    const base::Optional<base::string16>& dismiss_text)
       : overlay_(overlay) {
     background_color_ = AshColorProvider::Get()->DeprecatedGetBaseLayerColor(
-        app_list_features::IsBackgroundBlurEnabled()
+        features::IsBackgroundBlurEnabled()
             ? AshColorProvider::BaseLayerType::kTransparentWithBlur
             : AshColorProvider::BaseLayerType::kTransparentWithoutBlur,
         kToastBackgroundColor);
diff --git a/ash/system/tray/tray_event_filter.cc b/ash/system/tray/tray_event_filter.cc
index 6a94b31..1be18a9 100644
--- a/ash/system/tray/tray_event_filter.cc
+++ b/ash/system/tray/tray_event_filter.cc
@@ -4,13 +4,21 @@
 
 #include "ash/system/tray/tray_event_filter.h"
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shell_window_ids.h"
+#include "ash/root_window_controller.h"
+#include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/system/message_center/unified_message_center_bubble.h"
+#include "ash/system/status_area_widget.h"
 #include "ash/system/tray/tray_background_view.h"
 #include "ash/system/tray/tray_bubble_base.h"
+#include "ash/system/unified/unified_system_tray.h"
+#include "ash/system/unified/unified_system_tray_bubble.h"
 #include "ash/wm/container_finder.h"
 #include "ash/wm/tablet_mode/tablet_mode_controller.h"
 #include "ui/aura/window.h"
+#include "ui/display/screen.h"
 #include "ui/gfx/native_widget_types.h"
 #include "ui/views/widget/widget.h"
 
@@ -95,6 +103,32 @@
         bubble_container_id == kShellWindowId_SettingBubbleContainer) {
       bounds.Intersect(bubble_widget->GetWorkAreaBoundsInScreen());
     }
+
+    // The system tray and message center are separate bubbles but they need
+    // to stay open together. We need to make sure to check if a click falls
+    // with in both their bounds and not close them both in this case.
+    if (features::IsUnifiedMessageCenterRefactorEnabled() &&
+        bubble_container_id == kShellWindowId_SettingBubbleContainer) {
+      int64_t display_id = display::Screen::GetScreen()
+                               ->GetDisplayNearestPoint(screen_location)
+                               .id();
+      UnifiedSystemTray* tray =
+          Shell::GetRootWindowControllerWithDisplayId(display_id)
+              ->shelf()
+              ->GetStatusAreaWidget()
+              ->unified_system_tray();
+
+      TrayBubbleBase* system_tray_bubble = tray->bubble();
+      if (tray->IsBubbleShown() && system_tray_bubble != bubble) {
+        bounds.Union(
+            system_tray_bubble->GetBubbleWidget()->GetWindowBoundsInScreen());
+      } else if (tray->IsMessageCenterBubbleShown()) {
+        TrayBubbleBase* message_center_bubble = tray->message_center_bubble();
+        bounds.Union(message_center_bubble->GetBubbleWidget()
+                         ->GetWindowBoundsInScreen());
+      }
+    }
+
     if (bounds.Contains(screen_location))
       continue;
     if (bubble->GetTray()) {
diff --git a/ash/system/tray/tray_event_filter_unittest.cc b/ash/system/tray/tray_event_filter_unittest.cc
index 550e63ea..07e5611 100644
--- a/ash/system/tray/tray_event_filter_unittest.cc
+++ b/ash/system/tray/tray_event_filter_unittest.cc
@@ -4,16 +4,23 @@
 
 #include "ash/system/tray/tray_event_filter.h"
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/root_window_controller.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/system/message_center/unified_message_center_bubble.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/unified/unified_system_tray.h"
 #include "ash/test/ash_test_base.h"
 #include "base/macros.h"
+#include "base/test/scoped_feature_list.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/aura/client/aura_constants.h"
 #include "ui/aura/window.h"
+#include "ui/message_center/message_center.h"
+
+using message_center::MessageCenter;
+using message_center::Notification;
 
 namespace ash {
 
@@ -24,6 +31,12 @@
   TrayEventFilterTest() = default;
   ~TrayEventFilterTest() override = default;
 
+  // AshTestBase:
+  void SetUp() override {
+    AshTestBase::SetUp();
+    scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
+  }
+
   ui::MouseEvent outside_event() {
     const gfx::Rect tray_bounds = GetSystemTrayBoundsInScreen();
     const gfx::Point point = tray_bounds.bottom_right() + gfx::Vector2d(1, 1);
@@ -38,7 +51,30 @@
     return ui::MouseEvent(ui::ET_MOUSE_PRESSED, point, point, time, 0, 0);
   }
 
+  ui::MouseEvent InsideMessageCenterEvent() {
+    const gfx::Rect message_center_bounds = GetMessageCenterBoundsInScreen();
+    const gfx::Point point = message_center_bounds.origin();
+    const base::TimeTicks time = base::TimeTicks::Now();
+    return ui::MouseEvent(ui::ET_MOUSE_PRESSED, point, point, time, 0, 0);
+  }
+
  protected:
+  std::string AddNotification() {
+    std::string notification_id = base::NumberToString(notification_id_++);
+    MessageCenter::Get()->AddNotification(std::make_unique<Notification>(
+        message_center::NOTIFICATION_TYPE_BASE_FORMAT, notification_id,
+        base::UTF8ToUTF16("test title"), base::UTF8ToUTF16("test message"),
+        gfx::Image(), base::string16() /* display_source */, GURL(),
+        message_center::NotifierId(), message_center::RichNotificationData(),
+        new message_center::NotificationDelegate()));
+    return notification_id;
+  }
+
+  void EnableMessageCenterRefactor() {
+    scoped_feature_list_->InitAndEnableFeature(
+        features::kUnifiedMessageCenterRefactor);
+  }
+
   void ShowSystemTrayMainView() {
     GetPrimaryUnifiedSystemTray()->ShowBubble(false /* show_by_click */);
   }
@@ -47,6 +83,10 @@
     return GetPrimaryUnifiedSystemTray()->IsBubbleShown();
   }
 
+  bool IsMessageCenterBubbleShown() {
+    return GetPrimaryUnifiedSystemTray()->IsMessageCenterBubbleShown();
+  }
+
   gfx::Rect GetSystemTrayBoundsInScreen() {
     return GetPrimaryUnifiedSystemTray()->GetBubbleBoundsInScreen();
   }
@@ -59,7 +99,16 @@
     return GetPrimaryShelf()->GetStatusAreaWidget()->unified_system_tray();
   }
 
+  UnifiedMessageCenterBubble* GetMessageCenterBubble() {
+    return GetPrimaryUnifiedSystemTray()->message_center_bubble();
+  }
+  gfx::Rect GetMessageCenterBoundsInScreen() {
+    return GetMessageCenterBubble()->GetBubbleView()->GetBoundsInScreen();
+  }
+
  private:
+  int notification_id_ = 0;
+  std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_;
   DISALLOW_COPY_AND_ASSIGN(TrayEventFilterTest);
 };
 
@@ -142,5 +191,41 @@
   EXPECT_TRUE(IsBubbleShown());
 }
 
+TEST_F(TrayEventFilterTest, MessageCenterAndSystemTrayStayOpenTogether) {
+  EnableMessageCenterRefactor();
+  AddNotification();
+
+  ShowSystemTrayMainView();
+  EXPECT_TRUE(GetMessageCenterBubble()->GetBubbleWidget()->IsVisible());
+  EXPECT_TRUE(IsBubbleShown());
+
+  // Clicking inside system tray should not close either bubble.
+  ui::MouseEvent event = inside_event();
+  GetTrayEventFilter()->OnMouseEvent(&event);
+  EXPECT_TRUE(GetMessageCenterBubble()->GetBubbleWidget()->IsVisible());
+  EXPECT_TRUE(IsBubbleShown());
+
+  // Clicking inside the message center bubble should not close either bubble.
+  event = InsideMessageCenterEvent();
+  GetTrayEventFilter()->OnMouseEvent(&event);
+  EXPECT_TRUE(GetMessageCenterBubble()->GetBubbleWidget()->IsVisible());
+  EXPECT_TRUE(IsBubbleShown());
+}
+
+TEST_F(TrayEventFilterTest, MessageCenterAndSystemTrayCloseTogether) {
+  EnableMessageCenterRefactor();
+  AddNotification();
+
+  ShowSystemTrayMainView();
+  EXPECT_TRUE(IsMessageCenterBubbleShown());
+  EXPECT_TRUE(IsBubbleShown());
+
+  // Clicking outside should close both bubbles.
+  ui::MouseEvent event = outside_event();
+  GetTrayEventFilter()->OnMouseEvent(&event);
+  EXPECT_FALSE(IsMessageCenterBubbleShown());
+  EXPECT_FALSE(IsBubbleShown());
+}
+
 }  // namespace
 }  // namespace ash
diff --git a/ash/system/unified/unified_slider_bubble_controller.cc b/ash/system/unified/unified_slider_bubble_controller.cc
index abd2917..f2118252 100644
--- a/ash/system/unified/unified_slider_bubble_controller.cc
+++ b/ash/system/unified/unified_slider_bubble_controller.cc
@@ -4,7 +4,7 @@
 
 #include "ash/system/unified/unified_slider_bubble_controller.h"
 
-#include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/root_window_controller.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shelf/shelf.h"
@@ -186,7 +186,7 @@
   TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
   bubble_view_->InitializeAndShowBubble();
 
-  if (app_list_features::IsBackgroundBlurEnabled()) {
+  if (features::IsBackgroundBlurEnabled()) {
     bubble_widget_->client_view()->layer()->SetBackgroundBlur(
         kUnifiedMenuBackgroundBlur);
   }
diff --git a/ash/system/unified/unified_system_tray.cc b/ash/system/unified/unified_system_tray.cc
index 2954363..3e7e521 100644
--- a/ash/system/unified/unified_system_tray.cc
+++ b/ash/system/unified/unified_system_tray.cc
@@ -119,7 +119,8 @@
 }
 
 void UnifiedSystemTray::UiDelegate::HideMessageCenter() {
-  owner_->HideBubbleInternal();
+  if (!features::IsUnifiedMessageCenterRefactorEnabled())
+    owner_->HideBubbleInternal();
 }
 
 UnifiedSystemTray::UnifiedSystemTray(Shelf* shelf)
@@ -175,6 +176,13 @@
   return slider_bubble_controller_->IsBubbleShown();
 }
 
+bool UnifiedSystemTray::IsMessageCenterBubbleShown() const {
+  if (message_center_bubble_)
+    return !!message_center_bubble_->GetBubbleWidget();
+
+  return false;
+}
+
 bool UnifiedSystemTray::IsBubbleActive() const {
   return bubble_ && bubble_->IsBubbleActive();
 }
@@ -275,8 +283,11 @@
 }
 
 void UnifiedSystemTray::CloseBubble() {
-  // HideBubbleInternal will be called from UiDelegate.
+  // HideMessageCenterBubbleInternal will be called from UiDelegate.
   ui_delegate_->ui_controller()->HideMessageCenterBubble();
+
+  if (features::IsUnifiedMessageCenterRefactorEnabled())
+    HideBubbleInternal();
 }
 
 base::string16 UnifiedSystemTray::GetAccessibleNameForBubble() {
diff --git a/ash/system/unified/unified_system_tray.h b/ash/system/unified/unified_system_tray.h
index a8a6e32..e79410f 100644
--- a/ash/system/unified/unified_system_tray.h
+++ b/ash/system/unified/unified_system_tray.h
@@ -57,6 +57,9 @@
   // accelerator is shown.
   bool IsSliderBubbleShown() const;
 
+  // True if the bubble containing notifications is visible..
+  bool IsMessageCenterBubbleShown() const;
+
   // True if the bubble is active.
   bool IsBubbleActive() const;
 
@@ -111,7 +114,7 @@
   UnifiedSystemTrayModel* model() { return model_.get(); }
   UnifiedSystemTrayBubble* bubble() { return bubble_.get(); }
 
-  UnifiedMessageCenterBubble* message_center_bubble_for_test() {
+  UnifiedMessageCenterBubble* message_center_bubble() {
     return message_center_bubble_.get();
   }
 
diff --git a/ash/system/unified/unified_system_tray_bubble.cc b/ash/system/unified/unified_system_tray_bubble.cc
index 9fda6e15..69231dbb 100644
--- a/ash/system/unified/unified_system_tray_bubble.cc
+++ b/ash/system/unified/unified_system_tray_bubble.cc
@@ -4,9 +4,10 @@
 
 #include "ash/system/unified/unified_system_tray_bubble.h"
 
-#include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/shelf/shelf.h"
 #include "ash/shell.h"
+#include "ash/system/message_center/unified_message_center_bubble.h"
 #include "ash/system/status_area_widget.h"
 #include "ash/system/tray/tray_constants.h"
 #include "ash/system/tray/tray_event_filter.h"
@@ -113,7 +114,7 @@
   TrayBackgroundView::InitializeBubbleAnimations(bubble_widget_);
   bubble_view_->InitializeAndShowBubble();
 
-  if (app_list_features::IsBackgroundBlurEnabled()) {
+  if (features::IsBackgroundBlurEnabled()) {
     bubble_widget_->client_view()->layer()->SetBackgroundBlur(
         kUnifiedMenuBackgroundBlur);
   }
@@ -279,6 +280,21 @@
     return;
   }
 
+  // Don't close the bubble if the message center is gaining or losing
+  // activation.
+  if (features::IsUnifiedMessageCenterRefactorEnabled() &&
+      tray_->IsMessageCenterBubbleShown()) {
+    views::Widget* message_center_widget =
+        tray_->message_center_bubble()->GetBubbleWidget();
+    if (message_center_widget ==
+            views::Widget::GetWidgetForNativeView(gained_active) ||
+        (lost_active &&
+         message_center_widget ==
+             views::Widget::GetWidgetForNativeView(lost_active))) {
+      return;
+    }
+  }
+
   tray_->CloseBubble();
 }
 
@@ -315,7 +331,7 @@
 }
 
 void UnifiedSystemTrayBubble::CreateBlurLayerForAnimation() {
-  if (!app_list_features::IsBackgroundBlurEnabled())
+  if (!features::IsBackgroundBlurEnabled())
     return;
 
   if (blur_layer_)
@@ -342,7 +358,7 @@
 }
 
 void UnifiedSystemTrayBubble::DestroyBlurLayerForAnimation() {
-  if (!app_list_features::IsBackgroundBlurEnabled())
+  if (!features::IsBackgroundBlurEnabled())
     return;
 
   if (!blur_layer_)
diff --git a/ash/system/unified/unified_system_tray_view.cc b/ash/system/unified/unified_system_tray_view.cc
index 5ec2fa2..89eccdb 100644
--- a/ash/system/unified/unified_system_tray_view.cc
+++ b/ash/system/unified/unified_system_tray_view.cc
@@ -6,7 +6,6 @@
 
 #include <numeric>
 
-#include "ash/public/cpp/app_list/app_list_features.h"
 #include "ash/public/cpp/ash_features.h"
 #include "ash/session/session_controller_impl.h"
 #include "ash/shell.h"
@@ -232,7 +231,7 @@
 
 // static
 SkColor UnifiedSystemTrayView::GetBackgroundColor() {
-  if (app_list_features::IsBackgroundBlurEnabled()) {
+  if (features::IsBackgroundBlurEnabled()) {
     return AshColorProvider::Get()->DeprecatedGetBaseLayerColor(
         AshColorProvider::BaseLayerType::kTransparentWithBlur,
         kUnifiedMenuBackgroundColorWithBlur);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 2a50cd2..314b7d88 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -618,6 +618,10 @@
     "profiler/stack_buffer.h",
     "profiler/stack_copier.cc",
     "profiler/stack_copier.h",
+    "profiler/stack_copier_signal.cc",
+    "profiler/stack_copier_signal.h",
+    "profiler/stack_copier_suspend.cc",
+    "profiler/stack_copier_suspend.h",
     "profiler/stack_sampler.cc",
     "profiler/stack_sampler.h",
     "profiler/stack_sampler_android.cc",
@@ -2639,6 +2643,7 @@
     "process/process_util_unittest.cc",
     "profiler/metadata_recorder_unittest.cc",
     "profiler/sample_metadata_unittest.cc",
+    "profiler/stack_copier_suspend_unittest.cc",
     "profiler/stack_copier_unittest.cc",
     "profiler/stack_sampler_impl_unittest.cc",
     "profiler/stack_sampling_profiler_unittest.cc",
diff --git a/base/ios/crb_protocol_observers_unittest.mm b/base/ios/crb_protocol_observers_unittest.mm
index 07f5cff0..6321ac1 100644
--- a/base/ios/crb_protocol_observers_unittest.mm
+++ b/base/ios/crb_protocol_observers_unittest.mm
@@ -5,7 +5,6 @@
 #import "base/ios/crb_protocol_observers.h"
 #include "base/ios/weak_nsobject.h"
 #include "base/logging.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/gtest_mac.h"
@@ -122,11 +121,10 @@
 
   [observers_ addObserver:partial_observer_];
 
-  {
-    // Need an autorelease pool here, because
-    // -[CRBProtocolObservers forwardInvocation:] creates a temporary
-    // autoreleased array that holds all the observers.
-    base::mac::ScopedNSAutoreleasePool pool;
+  // Need an autorelease pool here, because
+  // -[CRBProtocolObservers forwardInvocation:] creates a temporary
+  // autoreleased array that holds all the observers.
+  @autoreleasepool {
     [observers_ requiredMethod];
     EXPECT_TRUE([partial_observer_ requiredMethodInvoked]);
   }
diff --git a/base/ios/weak_nsobject.mm b/base/ios/weak_nsobject.mm
index c017b1d..eaf700f6 100644
--- a/base/ios/weak_nsobject.mm
+++ b/base/ios/weak_nsobject.mm
@@ -4,7 +4,6 @@
 
 #include "base/ios/weak_nsobject.h"
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 
 namespace {
@@ -34,23 +33,24 @@
 + (scoped_refptr<base::WeakContainer>)containerForObject:(id)object {
   if (object == nil)
     return nullptr;
-  // The autoreleasePool is needed here as the call to objc_getAssociatedObject
+  // The autoreleasepool is needed here as the call to objc_getAssociatedObject
   // returns an autoreleased object which is better released sooner than later.
-  base::mac::ScopedNSAutoreleasePool pool;
-  CRBWeakNSProtocolSentinel* sentinel =
-      objc_getAssociatedObject(object, &sentinelObserverKey_);
-  if (!sentinel) {
-    base::scoped_nsobject<CRBWeakNSProtocolSentinel> newSentinel(
-        [[CRBWeakNSProtocolSentinel alloc]
-            initWithContainer:new base::WeakContainer(object)]);
-    sentinel = newSentinel;
-    objc_setAssociatedObject(object, &sentinelObserverKey_, sentinel,
-                             OBJC_ASSOCIATION_RETAIN);
-    // The retain count is 2. One retain is due to the alloc, the other to the
-    // association with the weak object.
-    DCHECK_EQ(2u, [sentinel retainCount]);
+  @autoreleasepool {
+    CRBWeakNSProtocolSentinel* sentinel =
+        objc_getAssociatedObject(object, &sentinelObserverKey_);
+    if (!sentinel) {
+      base::scoped_nsobject<CRBWeakNSProtocolSentinel> newSentinel(
+          [[CRBWeakNSProtocolSentinel alloc]
+              initWithContainer:new base::WeakContainer(object)]);
+      sentinel = newSentinel;
+      objc_setAssociatedObject(object, &sentinelObserverKey_, sentinel,
+                               OBJC_ASSOCIATION_RETAIN);
+      // The retain count is 2. One retain is due to the alloc, the other to the
+      // association with the weak object.
+      DCHECK_EQ(2u, [sentinel retainCount]);
+    }
+    return [sentinel container];
   }
-  return [sentinel container];
 }
 
 - (id)initWithContainer:(scoped_refptr<base::WeakContainer>)container {
diff --git a/base/mac/bind_objc_block_unittest.mm b/base/mac/bind_objc_block_unittest.mm
index 8c4a9892..485f421 100644
--- a/base/mac/bind_objc_block_unittest.mm
+++ b/base/mac/bind_objc_block_unittest.mm
@@ -7,7 +7,6 @@
 #include "base/bind.h"
 #include "base/callback.h"
 #include "base/callback_helpers.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "build/build_config.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/gtest_mac.h"
@@ -108,8 +107,7 @@
 TEST(BindObjcBlockTest, TestBlockMoveable) {
   base::OnceClosure c;
   __block BOOL invoked_block = NO;
-  {
-    base::mac::ScopedNSAutoreleasePool autorelease_pool;
+  @autoreleasepool {
     c = base::BindOnce(base::RetainBlock(^(std::unique_ptr<BOOL> v) {
                          invoked_block = *v;
                        }),
@@ -139,8 +137,7 @@
 
 TEST(BindObjcBlockTest, TestBlockReleased) {
   base::WeakNSObject<NSObject> weak_nsobject;
-  {
-    base::mac::ScopedNSAutoreleasePool autorelease_pool;
+  @autoreleasepool {
     NSObject* nsobject = [[[NSObject alloc] init] autorelease];
     weak_nsobject.reset(nsobject);
 
diff --git a/base/mac/foundation_util_unittest.mm b/base/mac/foundation_util_unittest.mm
index df8ef24b..677264b 100644
--- a/base/mac/foundation_util_unittest.mm
+++ b/base/mac/foundation_util_unittest.mm
@@ -11,7 +11,6 @@
 #include "base/files/file_path.h"
 #include "base/format_macros.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
 #include "build/build_config.h"
@@ -162,112 +161,107 @@
 }
 
 TEST(FoundationUtilTest, ObjCCast) {
-  ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    id test_array = @[];
+    id test_array_mutable = [NSMutableArray array];
+    id test_data = [NSData data];
+    id test_data_mutable = [NSMutableData dataWithCapacity:10];
+    id test_date = [NSDate date];
+    id test_dict = @{@"meaning" : @42};
+    id test_dict_mutable = [NSMutableDictionary dictionaryWithCapacity:10];
+    id test_number = @42;
+    id test_null = [NSNull null];
+    id test_set = [NSSet setWithObject:@"string object"];
+    id test_set_mutable = [NSMutableSet setWithCapacity:10];
+    id test_str = [NSString string];
+    id test_str_const = @"bonjour";
+    id test_str_mutable = [NSMutableString stringWithCapacity:10];
 
-  id test_array = @[];
-  id test_array_mutable = [NSMutableArray array];
-  id test_data = [NSData data];
-  id test_data_mutable = [NSMutableData dataWithCapacity:10];
-  id test_date = [NSDate date];
-  id test_dict = @{ @"meaning" : @42 };
-  id test_dict_mutable = [NSMutableDictionary dictionaryWithCapacity:10];
-  id test_number = @42;
-  id test_null = [NSNull null];
-  id test_set = [NSSet setWithObject:@"string object"];
-  id test_set_mutable = [NSMutableSet setWithCapacity:10];
-  id test_str = [NSString string];
-  id test_str_const = @"bonjour";
-  id test_str_mutable = [NSMutableString stringWithCapacity:10];
+    // Make sure the allocations of NS types are good.
+    EXPECT_TRUE(test_array);
+    EXPECT_TRUE(test_array_mutable);
+    EXPECT_TRUE(test_data);
+    EXPECT_TRUE(test_data_mutable);
+    EXPECT_TRUE(test_date);
+    EXPECT_TRUE(test_dict);
+    EXPECT_TRUE(test_dict_mutable);
+    EXPECT_TRUE(test_number);
+    EXPECT_TRUE(test_null);
+    EXPECT_TRUE(test_set);
+    EXPECT_TRUE(test_set_mutable);
+    EXPECT_TRUE(test_str);
+    EXPECT_TRUE(test_str_const);
+    EXPECT_TRUE(test_str_mutable);
 
-  // Make sure the allocations of NS types are good.
-  EXPECT_TRUE(test_array);
-  EXPECT_TRUE(test_array_mutable);
-  EXPECT_TRUE(test_data);
-  EXPECT_TRUE(test_data_mutable);
-  EXPECT_TRUE(test_date);
-  EXPECT_TRUE(test_dict);
-  EXPECT_TRUE(test_dict_mutable);
-  EXPECT_TRUE(test_number);
-  EXPECT_TRUE(test_null);
-  EXPECT_TRUE(test_set);
-  EXPECT_TRUE(test_set_mutable);
-  EXPECT_TRUE(test_str);
-  EXPECT_TRUE(test_str_const);
-  EXPECT_TRUE(test_str_mutable);
+    // Casting the id correctly provides the same pointer.
+    EXPECT_EQ(test_array, ObjCCast<NSArray>(test_array));
+    EXPECT_EQ(test_array_mutable, ObjCCast<NSArray>(test_array_mutable));
+    EXPECT_EQ(test_data, ObjCCast<NSData>(test_data));
+    EXPECT_EQ(test_data_mutable, ObjCCast<NSData>(test_data_mutable));
+    EXPECT_EQ(test_date, ObjCCast<NSDate>(test_date));
+    EXPECT_EQ(test_dict, ObjCCast<NSDictionary>(test_dict));
+    EXPECT_EQ(test_dict_mutable, ObjCCast<NSDictionary>(test_dict_mutable));
+    EXPECT_EQ(test_number, ObjCCast<NSNumber>(test_number));
+    EXPECT_EQ(test_null, ObjCCast<NSNull>(test_null));
+    EXPECT_EQ(test_set, ObjCCast<NSSet>(test_set));
+    EXPECT_EQ(test_set_mutable, ObjCCast<NSSet>(test_set_mutable));
+    EXPECT_EQ(test_str, ObjCCast<NSString>(test_str));
+    EXPECT_EQ(test_str_const, ObjCCast<NSString>(test_str_const));
+    EXPECT_EQ(test_str_mutable, ObjCCast<NSString>(test_str_mutable));
 
-  // Casting the id correctly provides the same pointer.
-  EXPECT_EQ(test_array, ObjCCast<NSArray>(test_array));
-  EXPECT_EQ(test_array_mutable, ObjCCast<NSArray>(test_array_mutable));
-  EXPECT_EQ(test_data, ObjCCast<NSData>(test_data));
-  EXPECT_EQ(test_data_mutable, ObjCCast<NSData>(test_data_mutable));
-  EXPECT_EQ(test_date, ObjCCast<NSDate>(test_date));
-  EXPECT_EQ(test_dict, ObjCCast<NSDictionary>(test_dict));
-  EXPECT_EQ(test_dict_mutable, ObjCCast<NSDictionary>(test_dict_mutable));
-  EXPECT_EQ(test_number, ObjCCast<NSNumber>(test_number));
-  EXPECT_EQ(test_null, ObjCCast<NSNull>(test_null));
-  EXPECT_EQ(test_set, ObjCCast<NSSet>(test_set));
-  EXPECT_EQ(test_set_mutable, ObjCCast<NSSet>(test_set_mutable));
-  EXPECT_EQ(test_str, ObjCCast<NSString>(test_str));
-  EXPECT_EQ(test_str_const, ObjCCast<NSString>(test_str_const));
-  EXPECT_EQ(test_str_mutable, ObjCCast<NSString>(test_str_mutable));
+    // When given an incorrect ObjC cast, provide nil.
+    EXPECT_FALSE(ObjCCast<NSString>(test_array));
+    EXPECT_FALSE(ObjCCast<NSString>(test_array_mutable));
+    EXPECT_FALSE(ObjCCast<NSString>(test_data));
+    EXPECT_FALSE(ObjCCast<NSString>(test_data_mutable));
+    EXPECT_FALSE(ObjCCast<NSSet>(test_date));
+    EXPECT_FALSE(ObjCCast<NSSet>(test_dict));
+    EXPECT_FALSE(ObjCCast<NSNumber>(test_dict_mutable));
+    EXPECT_FALSE(ObjCCast<NSNull>(test_number));
+    EXPECT_FALSE(ObjCCast<NSDictionary>(test_null));
+    EXPECT_FALSE(ObjCCast<NSDictionary>(test_set));
+    EXPECT_FALSE(ObjCCast<NSDate>(test_set_mutable));
+    EXPECT_FALSE(ObjCCast<NSData>(test_str));
+    EXPECT_FALSE(ObjCCast<NSData>(test_str_const));
+    EXPECT_FALSE(ObjCCast<NSArray>(test_str_mutable));
 
-  // When given an incorrect ObjC cast, provide nil.
-  EXPECT_FALSE(ObjCCast<NSString>(test_array));
-  EXPECT_FALSE(ObjCCast<NSString>(test_array_mutable));
-  EXPECT_FALSE(ObjCCast<NSString>(test_data));
-  EXPECT_FALSE(ObjCCast<NSString>(test_data_mutable));
-  EXPECT_FALSE(ObjCCast<NSSet>(test_date));
-  EXPECT_FALSE(ObjCCast<NSSet>(test_dict));
-  EXPECT_FALSE(ObjCCast<NSNumber>(test_dict_mutable));
-  EXPECT_FALSE(ObjCCast<NSNull>(test_number));
-  EXPECT_FALSE(ObjCCast<NSDictionary>(test_null));
-  EXPECT_FALSE(ObjCCast<NSDictionary>(test_set));
-  EXPECT_FALSE(ObjCCast<NSDate>(test_set_mutable));
-  EXPECT_FALSE(ObjCCast<NSData>(test_str));
-  EXPECT_FALSE(ObjCCast<NSData>(test_str_const));
-  EXPECT_FALSE(ObjCCast<NSArray>(test_str_mutable));
+    // Giving a nil provides a nil.
+    EXPECT_FALSE(ObjCCast<NSArray>(nil));
+    EXPECT_FALSE(ObjCCast<NSData>(nil));
+    EXPECT_FALSE(ObjCCast<NSDate>(nil));
+    EXPECT_FALSE(ObjCCast<NSDictionary>(nil));
+    EXPECT_FALSE(ObjCCast<NSNull>(nil));
+    EXPECT_FALSE(ObjCCast<NSNumber>(nil));
+    EXPECT_FALSE(ObjCCast<NSSet>(nil));
+    EXPECT_FALSE(ObjCCast<NSString>(nil));
 
-  // Giving a nil provides a nil.
-  EXPECT_FALSE(ObjCCast<NSArray>(nil));
-  EXPECT_FALSE(ObjCCast<NSData>(nil));
-  EXPECT_FALSE(ObjCCast<NSDate>(nil));
-  EXPECT_FALSE(ObjCCast<NSDictionary>(nil));
-  EXPECT_FALSE(ObjCCast<NSNull>(nil));
-  EXPECT_FALSE(ObjCCast<NSNumber>(nil));
-  EXPECT_FALSE(ObjCCast<NSSet>(nil));
-  EXPECT_FALSE(ObjCCast<NSString>(nil));
+    // ObjCCastStrict: correct cast results in correct pointer being returned.
+    EXPECT_EQ(test_array, ObjCCastStrict<NSArray>(test_array));
+    EXPECT_EQ(test_array_mutable, ObjCCastStrict<NSArray>(test_array_mutable));
+    EXPECT_EQ(test_data, ObjCCastStrict<NSData>(test_data));
+    EXPECT_EQ(test_data_mutable, ObjCCastStrict<NSData>(test_data_mutable));
+    EXPECT_EQ(test_date, ObjCCastStrict<NSDate>(test_date));
+    EXPECT_EQ(test_dict, ObjCCastStrict<NSDictionary>(test_dict));
+    EXPECT_EQ(test_dict_mutable,
+              ObjCCastStrict<NSDictionary>(test_dict_mutable));
+    EXPECT_EQ(test_number, ObjCCastStrict<NSNumber>(test_number));
+    EXPECT_EQ(test_null, ObjCCastStrict<NSNull>(test_null));
+    EXPECT_EQ(test_set, ObjCCastStrict<NSSet>(test_set));
+    EXPECT_EQ(test_set_mutable, ObjCCastStrict<NSSet>(test_set_mutable));
+    EXPECT_EQ(test_str, ObjCCastStrict<NSString>(test_str));
+    EXPECT_EQ(test_str_const, ObjCCastStrict<NSString>(test_str_const));
+    EXPECT_EQ(test_str_mutable, ObjCCastStrict<NSString>(test_str_mutable));
 
-  // ObjCCastStrict: correct cast results in correct pointer being returned.
-  EXPECT_EQ(test_array, ObjCCastStrict<NSArray>(test_array));
-  EXPECT_EQ(test_array_mutable,
-            ObjCCastStrict<NSArray>(test_array_mutable));
-  EXPECT_EQ(test_data, ObjCCastStrict<NSData>(test_data));
-  EXPECT_EQ(test_data_mutable,
-            ObjCCastStrict<NSData>(test_data_mutable));
-  EXPECT_EQ(test_date, ObjCCastStrict<NSDate>(test_date));
-  EXPECT_EQ(test_dict, ObjCCastStrict<NSDictionary>(test_dict));
-  EXPECT_EQ(test_dict_mutable,
-            ObjCCastStrict<NSDictionary>(test_dict_mutable));
-  EXPECT_EQ(test_number, ObjCCastStrict<NSNumber>(test_number));
-  EXPECT_EQ(test_null, ObjCCastStrict<NSNull>(test_null));
-  EXPECT_EQ(test_set, ObjCCastStrict<NSSet>(test_set));
-  EXPECT_EQ(test_set_mutable,
-            ObjCCastStrict<NSSet>(test_set_mutable));
-  EXPECT_EQ(test_str, ObjCCastStrict<NSString>(test_str));
-  EXPECT_EQ(test_str_const,
-            ObjCCastStrict<NSString>(test_str_const));
-  EXPECT_EQ(test_str_mutable,
-            ObjCCastStrict<NSString>(test_str_mutable));
-
-  // ObjCCastStrict: Giving a nil provides a nil.
-  EXPECT_FALSE(ObjCCastStrict<NSArray>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSData>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSDate>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSDictionary>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSNull>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSNumber>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSSet>(nil));
-  EXPECT_FALSE(ObjCCastStrict<NSString>(nil));
+    // ObjCCastStrict: Giving a nil provides a nil.
+    EXPECT_FALSE(ObjCCastStrict<NSArray>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSData>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSDate>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSDictionary>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSNull>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSNumber>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSSet>(nil));
+    EXPECT_FALSE(ObjCCastStrict<NSString>(nil));
+  }
 }
 
 TEST(FoundationUtilTest, GetValueFromDictionary) {
diff --git a/base/mac/objc_release_properties_unittest.mm b/base/mac/objc_release_properties_unittest.mm
index 4b51e42..6804669d 100644
--- a/base/mac/objc_release_properties_unittest.mm
+++ b/base/mac/objc_release_properties_unittest.mm
@@ -5,7 +5,6 @@
 #include "base/mac/objc_release_properties.h"
 #include "base/stl_util.h"
 
-#import "base/mac/scoped_nsautorelease_pool.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 #import <objc/runtime.h>
@@ -268,9 +267,7 @@
   // Make sure that worked before things get more involved.
   EXPECT_EQ(3, ah_ah_ah);
 
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
-
+  @autoreleasepool {
     test_object.baseCvcRetain = [CountVonCount countVonCount];
     test_object.baseCvcCopy = [CountVonCount countVonCount];
     test_object.baseCvcAssign = baseAssign;
@@ -324,9 +321,7 @@
   // readonly.
   EXPECT_EQ(6, ah_ah_ah);
 
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
-
+  @autoreleasepool {
     // Put things back to how they were.
     test_object.baseCvcRetain = [CountVonCount countVonCount];
     test_object.baseCvcCopy = [CountVonCount countVonCount];
diff --git a/base/mac/scoped_nsautorelease_pool.h b/base/mac/scoped_nsautorelease_pool.h
index 4d15e6d..6f58f30a 100644
--- a/base/mac/scoped_nsautorelease_pool.h
+++ b/base/mac/scoped_nsautorelease_pool.h
@@ -21,6 +21,8 @@
 // sends it a -drain message when destroyed.  This allows an autorelease pool to
 // be maintained in ordinary C++ code without bringing in any direct Objective-C
 // dependency.
+//
+// Use only in C++ code; use @autoreleasepool in Obj-C(++) code.
 
 class BASE_EXPORT ScopedNSAutoreleasePool {
  public:
diff --git a/base/mac/scoped_nsobject.h b/base/mac/scoped_nsobject.h
index d970d03..b7d1195 100644
--- a/base/mac/scoped_nsobject.h
+++ b/base/mac/scoped_nsobject.h
@@ -36,11 +36,10 @@
 // scoped_nsprotocol<> has the same behavior as scoped_nsobject, but can be used
 // with protocols.
 //
-// scoped_nsobject<> is not to be used for NSAutoreleasePools. For
-// NSAutoreleasePools use ScopedNSAutoreleasePool from
-// scoped_nsautorelease_pool.h instead.
-// We check for bad uses of scoped_nsobject and NSAutoreleasePool at compile
-// time with a template specialization (see below).
+// scoped_nsobject<> is not to be used for NSAutoreleasePools. For C++ code use
+// NSAutoreleasePool; for Objective-C(++) code use @autoreleasepool instead. We
+// check for bad uses of scoped_nsobject and NSAutoreleasePool at compile time
+// with a template specialization (see below).
 //
 // If Automatic Reference Counting (aka ARC) is enabled then the ownership
 // policy is not controllable by the user as ARC make it really difficult to
@@ -187,7 +186,7 @@
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
   static_assert(std::is_same<NST, NSAutoreleasePool>::value == false,
-                "Use ScopedNSAutoreleasePool instead");
+                "Use @autoreleasepool instead");
 #endif
 };
 
diff --git a/base/mac/scoped_nsobject_unittest.mm b/base/mac/scoped_nsobject_unittest.mm
index 72d52422..d33e96e 100644
--- a/base/mac/scoped_nsobject_unittest.mm
+++ b/base/mac/scoped_nsobject_unittest.mm
@@ -4,7 +4,6 @@
 
 #include <vector>
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -24,8 +23,7 @@
     base::scoped_nsobject<NSObject> p3 = p1;
     ASSERT_EQ(p1.get(), p3.get());
     ASSERT_EQ(2u, [p1 retainCount]);
-    {
-      base::mac::ScopedNSAutoreleasePool pool;
+    @autoreleasepool {
       p3 = p1;
     }
     ASSERT_EQ(p1.get(), p3.get());
@@ -46,8 +44,7 @@
 
   base::scoped_nsobject<NSObject> p6 = p1;
   ASSERT_EQ(3u, [p6 retainCount]);
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
     p6.autorelease();
     ASSERT_EQ(nil, p6.get());
     ASSERT_EQ(3u, [p1 retainCount]);
diff --git a/base/profiler/stack_copier.h b/base/profiler/stack_copier.h
index cea4574..921e1324 100644
--- a/base/profiler/stack_copier.h
+++ b/base/profiler/stack_copier.h
@@ -33,6 +33,7 @@
                          ProfileBuilder* profile_builder,
                          RegisterContext* thread_context) = 0;
 
+ protected:
   // If the value at |pointer| points to the original stack, rewrite it to point
   // to the corresponding location in the copied stack.
   //
diff --git a/base/profiler/stack_copier_signal.cc b/base/profiler/stack_copier_signal.cc
new file mode 100644
index 0000000..cd5940eb
--- /dev/null
+++ b/base/profiler/stack_copier_signal.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/stack_copier_signal.h"
+
+#include "base/profiler/metadata_recorder.h"
+#include "base/profiler/sample_metadata.h"
+#include "base/profiler/stack_buffer.h"
+#include "base/profiler/thread_delegate.h"
+
+namespace base {
+
+StackCopierSignal::StackCopierSignal() = default;
+
+StackCopierSignal::~StackCopierSignal() = default;
+
+bool StackCopierSignal::CopyStack(StackBuffer* stack_buffer,
+                                  uintptr_t* stack_top,
+                                  ProfileBuilder* profile_builder,
+                                  RegisterContext* thread_context) {
+  // TODO(wittman): Implement signal-based stack copying.
+  return false;
+}
+
+}  // namespace base
diff --git a/base/profiler/stack_copier_signal.h b/base/profiler/stack_copier_signal.h
new file mode 100644
index 0000000..5cc6478c
--- /dev/null
+++ b/base/profiler/stack_copier_signal.h
@@ -0,0 +1,29 @@
+// 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 BASE_PROFILER_STACK_COPIER_SIGNAL_H_
+#define BASE_PROFILER_STACK_COPIER_SIGNAL_H_
+
+#include "base/base_export.h"
+#include "base/profiler/stack_copier.h"
+
+namespace base {
+
+// Supports stack copying on platforms where a signal must be delivered to the
+// profiled thread and the stack is copied from the signal handler.
+class BASE_EXPORT StackCopierSignal : public StackCopier {
+ public:
+  StackCopierSignal();
+  ~StackCopierSignal() override;
+
+  // StackCopier:
+  bool CopyStack(StackBuffer* stack_buffer,
+                 uintptr_t* stack_top,
+                 ProfileBuilder* profile_builder,
+                 RegisterContext* thread_context) override;
+};
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_STACK_COPIER_SIGNAL_H_
diff --git a/base/profiler/stack_copier_suspend.cc b/base/profiler/stack_copier_suspend.cc
new file mode 100644
index 0000000..2de3a61
--- /dev/null
+++ b/base/profiler/stack_copier_suspend.cc
@@ -0,0 +1,82 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/profiler/stack_copier_suspend.h"
+
+#include "base/profiler/metadata_recorder.h"
+#include "base/profiler/sample_metadata.h"
+#include "base/profiler/stack_buffer.h"
+#include "base/profiler/thread_delegate.h"
+
+namespace base {
+
+StackCopierSuspend::StackCopierSuspend(
+    std::unique_ptr<ThreadDelegate> thread_delegate)
+    : thread_delegate_(std::move(thread_delegate)) {}
+
+StackCopierSuspend::~StackCopierSuspend() = default;
+
+// Suspends the thread, copies the stack state, and resumes the thread. The
+// copied stack state includes the stack itself, the top address of the stack
+// copy, the register context, and the current metadata state. Returns true on
+// success, and returns the copied state via the params.
+//
+// NO HEAP ALLOCATIONS within the ScopedSuspendThread scope.
+bool StackCopierSuspend::CopyStack(StackBuffer* stack_buffer,
+                                   uintptr_t* stack_top,
+                                   ProfileBuilder* profile_builder,
+                                   RegisterContext* thread_context) {
+  const uintptr_t top = thread_delegate_->GetStackBaseAddress();
+  uintptr_t bottom = 0;
+  const uint8_t* stack_copy_bottom = nullptr;
+  {
+    // The MetadataProvider must be created before the ScopedSuspendThread
+    // because it acquires a lock in its constructor that might otherwise be
+    // held by the target thread, resulting in deadlock.
+    std::unique_ptr<base::ProfileBuilder::MetadataProvider> get_metadata_items =
+        base::GetSampleMetadataRecorder()->CreateMetadataProvider();
+
+    // Allocation of the ScopedSuspendThread object itself is OK since it
+    // necessarily occurs before the thread is suspended by the object.
+    std::unique_ptr<ThreadDelegate::ScopedSuspendThread> suspend_thread =
+        thread_delegate_->CreateScopedSuspendThread();
+
+    if (!suspend_thread->WasSuccessful())
+      return false;
+
+    if (!thread_delegate_->GetThreadContext(thread_context))
+      return false;
+
+    bottom = RegisterContextStackPointer(thread_context);
+
+    // The StackBuffer allocation is expected to be at least as large as the
+    // largest stack region allocation on the platform, but check just in case
+    // it isn't *and* the actual stack itself exceeds the buffer allocation
+    // size.
+    if ((top - bottom) > stack_buffer->size())
+      return false;
+
+    if (!thread_delegate_->CanCopyStack(bottom))
+      return false;
+
+    profile_builder->RecordMetadata(get_metadata_items.get());
+
+    stack_copy_bottom = CopyStackContentsAndRewritePointers(
+        reinterpret_cast<uint8_t*>(bottom), reinterpret_cast<uintptr_t*>(top),
+        StackBuffer::kPlatformStackAlignment, stack_buffer->buffer());
+  }
+
+  *stack_top = reinterpret_cast<uintptr_t>(stack_copy_bottom) + (top - bottom);
+
+  for (uintptr_t* reg :
+       thread_delegate_->GetRegistersToRewrite(thread_context)) {
+    *reg = RewritePointerIfInOriginalStack(reinterpret_cast<uint8_t*>(bottom),
+                                           reinterpret_cast<uintptr_t*>(top),
+                                           stack_copy_bottom, *reg);
+  }
+
+  return true;
+}
+
+}  // namespace base
diff --git a/base/profiler/stack_copier_suspend.h b/base/profiler/stack_copier_suspend.h
new file mode 100644
index 0000000..d2da8f0
--- /dev/null
+++ b/base/profiler/stack_copier_suspend.h
@@ -0,0 +1,37 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_STACK_COPIER_SUSPEND_H_
+#define BASE_PROFILER_STACK_COPIER_SUSPEND_H_
+
+#include <memory>
+
+#include "base/base_export.h"
+#include "base/profiler/stack_copier.h"
+
+namespace base {
+
+class ThreadDelegate;
+
+// Supports stack copying on platforms where the profiled thread must be
+// explicitly suspended from the profiler thread and the stack is copied from
+// the profiler thread.
+class BASE_EXPORT StackCopierSuspend : public StackCopier {
+ public:
+  StackCopierSuspend(std::unique_ptr<ThreadDelegate> thread_delegate);
+  ~StackCopierSuspend() override;
+
+  // StackCopier:
+  bool CopyStack(StackBuffer* stack_buffer,
+                 uintptr_t* stack_top,
+                 ProfileBuilder* profile_builder,
+                 RegisterContext* thread_context) override;
+
+ private:
+  std::unique_ptr<ThreadDelegate> thread_delegate_;
+};
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_STACK_COPIER_SUSPEND_H_
diff --git a/base/profiler/stack_copier_suspend_unittest.cc b/base/profiler/stack_copier_suspend_unittest.cc
new file mode 100644
index 0000000..bc56abb
--- /dev/null
+++ b/base/profiler/stack_copier_suspend_unittest.cc
@@ -0,0 +1,198 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <numeric>
+#include <utility>
+
+#include "base/profiler/profile_builder.h"
+#include "base/profiler/stack_buffer.h"
+#include "base/profiler/stack_copier_suspend.h"
+#include "base/profiler/thread_delegate.h"
+#include "base/stl_util.h"
+#include "build/build_config.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace base {
+
+namespace {
+
+using ::testing::ElementsAre;
+
+// A thread delegate for use in tests that provides the expected behavior when
+// operating on the supplied fake stack.
+class TestThreadDelegate : public ThreadDelegate {
+ public:
+  class TestScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
+   public:
+    TestScopedSuspendThread() = default;
+
+    TestScopedSuspendThread(const TestScopedSuspendThread&) = delete;
+    TestScopedSuspendThread& operator=(const TestScopedSuspendThread&) = delete;
+
+    bool WasSuccessful() const override { return true; }
+  };
+
+  TestThreadDelegate(const std::vector<uintptr_t>& fake_stack,
+                     // The register context will be initialized to
+                     // *|thread_context| if non-null.
+                     RegisterContext* thread_context = nullptr)
+      : fake_stack_(fake_stack), thread_context_(thread_context) {}
+
+  TestThreadDelegate(const TestThreadDelegate&) = delete;
+  TestThreadDelegate& operator=(const TestThreadDelegate&) = delete;
+
+  std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() override {
+    return std::make_unique<TestScopedSuspendThread>();
+  }
+
+  bool GetThreadContext(RegisterContext* thread_context) override {
+    if (thread_context_)
+      *thread_context = *thread_context_;
+    // Set the stack pointer to be consistent with the provided fake stack.
+    RegisterContextStackPointer(thread_context) =
+        reinterpret_cast<uintptr_t>(&fake_stack_[0]);
+    RegisterContextInstructionPointer(thread_context) =
+        reinterpret_cast<uintptr_t>(fake_stack_[0]);
+    return true;
+  }
+
+  uintptr_t GetStackBaseAddress() const override {
+    return reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
+  }
+
+  bool CanCopyStack(uintptr_t stack_pointer) override { return true; }
+
+  std::vector<uintptr_t*> GetRegistersToRewrite(
+      RegisterContext* thread_context) override {
+    return {&RegisterContextFramePointer(thread_context)};
+  }
+
+ private:
+  // Must be a reference to retain the underlying allocation from the vector
+  // passed to the constructor.
+  const std::vector<uintptr_t>& fake_stack_;
+  RegisterContext* thread_context_;
+};
+
+class TestProfileBuilder : public ProfileBuilder {
+ public:
+  TestProfileBuilder() = default;
+
+  TestProfileBuilder(const TestProfileBuilder&) = delete;
+  TestProfileBuilder& operator=(const TestProfileBuilder&) = delete;
+
+  // ProfileBuilder
+  ModuleCache* GetModuleCache() override { return nullptr; }
+
+  void RecordMetadata(
+      base::ProfileBuilder::MetadataProvider* metadata_provider) override {
+    recorded_metadata_ = true;
+  }
+
+  void OnSampleCompleted(std::vector<Frame> frames) override {}
+  void OnProfileCompleted(TimeDelta profile_duration,
+                          TimeDelta sampling_period) override {}
+
+ private:
+  bool recorded_metadata_ = false;
+};
+
+}  // namespace
+
+TEST(StackCopierSuspendTest, CopyStack) {
+  const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
+  StackCopierSuspend stack_copier_suspend(
+      std::make_unique<TestThreadDelegate>(stack));
+
+  std::unique_ptr<StackBuffer> stack_buffer =
+      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
+  uintptr_t stack_top = 0;
+  TestProfileBuilder profile_builder;
+  RegisterContext register_context = {0};
+  stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top,
+                                 &profile_builder, &register_context);
+
+  uintptr_t* stack_copy_bottom =
+      reinterpret_cast<uintptr_t*>(stack_buffer.get()->buffer());
+  std::vector<uintptr_t> stack_copy(stack_copy_bottom,
+                                    stack_copy_bottom + stack.size());
+  EXPECT_EQ(stack, stack_copy);
+}
+
+TEST(StackCopierSuspendTest, CopyStackBufferTooSmall) {
+  std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
+  StackCopierSuspend stack_copier_suspend(
+      std::make_unique<TestThreadDelegate>(stack));
+
+  std::unique_ptr<StackBuffer> stack_buffer =
+      std::make_unique<StackBuffer>((stack.size() - 1) * sizeof(uintptr_t));
+  // Make the buffer different than the input stack.
+  stack_buffer->buffer()[0] = 100;
+  uintptr_t stack_top = 0;
+  TestProfileBuilder profile_builder;
+  RegisterContext register_context = {0};
+  stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top,
+                                 &profile_builder, &register_context);
+
+  uintptr_t* stack_copy_bottom =
+      reinterpret_cast<uintptr_t*>(stack_buffer.get()->buffer());
+  std::vector<uintptr_t> stack_copy(stack_copy_bottom,
+                                    stack_copy_bottom + stack.size());
+  // Use the buffer not being overwritten as a proxy for the unwind being
+  // aborted.
+  EXPECT_NE(stack, stack_copy);
+}
+
+TEST(StackCopierSuspendTest, CopyStackAndRewritePointers) {
+  // Allocate space for the stack, then make its elements point to themselves.
+  std::vector<uintptr_t> stack(2);
+  stack[0] = reinterpret_cast<uintptr_t>(&stack[0]);
+  stack[1] = reinterpret_cast<uintptr_t>(&stack[1]);
+  StackCopierSuspend stack_copier_suspend(
+      std::make_unique<TestThreadDelegate>(stack));
+
+  std::unique_ptr<StackBuffer> stack_buffer =
+      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
+  uintptr_t stack_top = 0;
+  TestProfileBuilder profile_builder;
+  RegisterContext register_context = {0};
+  stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top,
+                                 &profile_builder, &register_context);
+
+  uintptr_t* stack_copy_bottom =
+      reinterpret_cast<uintptr_t*>(stack_buffer.get()->buffer());
+  std::vector<uintptr_t> stack_copy(stack_copy_bottom,
+                                    stack_copy_bottom + stack.size());
+  EXPECT_THAT(stack_copy,
+              ElementsAre(reinterpret_cast<uintptr_t>(stack_copy_bottom),
+                          reinterpret_cast<uintptr_t>(stack_copy_bottom) +
+                              sizeof(uintptr_t)));
+}
+
+TEST(StackCopierSuspendTest, RewriteRegisters) {
+  std::vector<uintptr_t> stack = {0, 1, 2};
+  RegisterContext register_context = {0};
+  RegisterContextFramePointer(&register_context) =
+      reinterpret_cast<uintptr_t>(&stack[1]);
+  StackCopierSuspend stack_copier_suspend(
+      std::make_unique<TestThreadDelegate>(stack, &register_context));
+
+  std::unique_ptr<StackBuffer> stack_buffer =
+      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
+  uintptr_t stack_top = 0;
+  TestProfileBuilder profile_builder;
+  stack_copier_suspend.CopyStack(stack_buffer.get(), &stack_top,
+                                 &profile_builder, &register_context);
+
+  uintptr_t stack_copy_bottom =
+      reinterpret_cast<uintptr_t>(stack_buffer.get()->buffer());
+  EXPECT_EQ(stack_copy_bottom + sizeof(uintptr_t),
+            RegisterContextFramePointer(&register_context));
+}
+
+}  // namespace base
diff --git a/base/profiler/stack_copier_unittest.cc b/base/profiler/stack_copier_unittest.cc
index a065c9f..b4a81ef4 100644
--- a/base/profiler/stack_copier_unittest.cc
+++ b/base/profiler/stack_copier_unittest.cc
@@ -15,6 +15,12 @@
 
 namespace {
 
+class CopyFunctions : public StackCopier {
+ public:
+  using StackCopier::CopyStackContentsAndRewritePointers;
+  using StackCopier::RewritePointerIfInOriginalStack;
+};
+
 static constexpr size_t kTestStackBufferSize = sizeof(uintptr_t) * 4;
 
 union alignas(StackBuffer::kPlatformStackAlignment) TestStackBuffer {
@@ -29,7 +35,7 @@
   uintptr_t original_stack[4];
   uintptr_t stack_copy[4];
   EXPECT_EQ(reinterpret_cast<uintptr_t>(&stack_copy[2]),
-            StackCopier::RewritePointerIfInOriginalStack(
+            CopyFunctions::RewritePointerIfInOriginalStack(
                 reinterpret_cast<uint8_t*>(&original_stack[0]),
                 &original_stack[0] + base::size(original_stack),
                 reinterpret_cast<uint8_t*>(&stack_copy[0]),
@@ -44,7 +50,7 @@
   uintptr_t stack_copy[4];
 
   EXPECT_EQ(reinterpret_cast<uintptr_t>(&non_stack_location),
-            StackCopier::RewritePointerIfInOriginalStack(
+            CopyFunctions::RewritePointerIfInOriginalStack(
                 reinterpret_cast<uint8_t*>(&original_stack[0]),
                 &original_stack[0] + size(original_stack),
                 reinterpret_cast<uint8_t*>(&stack_copy[0]),
@@ -62,7 +68,7 @@
       reinterpret_cast<uintptr_t>(&original_stack.as_uintptr[1]);
   TestStackBuffer stack_copy;
 
-  StackCopier::CopyStackContentsAndRewritePointers(
+  CopyFunctions::CopyStackContentsAndRewritePointers(
       &original_stack.as_uint8[0],
       &original_stack.as_uintptr[0] + size(original_stack.as_uintptr),
       StackBuffer::kPlatformStackAlignment, &stack_copy.as_uintptr[0]);
@@ -97,7 +103,7 @@
   TestStackBuffer stack_copy_buffer = {{0}};
 
   const uint8_t* stack_copy_bottom =
-      StackCopier::CopyStackContentsAndRewritePointers(
+      CopyFunctions::CopyStackContentsAndRewritePointers(
           unaligned_stack_bottom, stack_top,
           StackBuffer::kPlatformStackAlignment,
           &stack_copy_buffer.as_uintptr[0]);
@@ -144,7 +150,7 @@
   TestStackBuffer stack_copy_buffer = {{0}};
 
   const uint8_t* stack_copy_bottom =
-      StackCopier::CopyStackContentsAndRewritePointers(
+      CopyFunctions::CopyStackContentsAndRewritePointers(
           unaligned_stack_bottom,
           &stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
           StackBuffer::kPlatformStackAlignment,
@@ -181,7 +187,7 @@
   TestStackBuffer stack_copy_buffer = {{0}};
 
   const uint8_t* stack_copy_bottom =
-      StackCopier::CopyStackContentsAndRewritePointers(
+      CopyFunctions::CopyStackContentsAndRewritePointers(
           unaligned_stack_bottom,
           &stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
           StackBuffer::kPlatformStackAlignment,
@@ -212,7 +218,7 @@
 
   TestStackBuffer stack_copy_buffer = {{0}};
 
-  StackCopier::CopyStackContentsAndRewritePointers(
+  CopyFunctions::CopyStackContentsAndRewritePointers(
       unaligned_stack_bottom,
       &stack_buffer.as_uintptr[0] + size(stack_buffer.as_uintptr),
       StackBuffer::kPlatformStackAlignment, &stack_copy_buffer.as_uintptr[0]);
diff --git a/base/profiler/stack_sampler_android.cc b/base/profiler/stack_sampler_android.cc
index e3190e3..e06216c 100644
--- a/base/profiler/stack_sampler_android.cc
+++ b/base/profiler/stack_sampler_android.cc
@@ -7,8 +7,8 @@
 #include <pthread.h>
 
 #include "base/profiler/native_unwinder_android.h"
+#include "base/profiler/stack_copier_signal.h"
 #include "base/profiler/stack_sampler_impl.h"
-#include "base/profiler/thread_delegate_android.h"
 #include "base/threading/platform_thread.h"
 
 namespace base {
@@ -18,7 +18,7 @@
     ModuleCache* module_cache,
     StackSamplerTestDelegate* test_delegate) {
   return std::make_unique<StackSamplerImpl>(
-      std::make_unique<ThreadDelegateAndroid>(),
+      std::make_unique<StackCopierSignal>(),
       std::make_unique<NativeUnwinderAndroid>(), module_cache, test_delegate);
 }
 
diff --git a/base/profiler/stack_sampler_impl.cc b/base/profiler/stack_sampler_impl.cc
index 1e63b54e..2152070 100644
--- a/base/profiler/stack_sampler_impl.cc
+++ b/base/profiler/stack_sampler_impl.cc
@@ -23,12 +23,11 @@
 
 namespace base {
 
-StackSamplerImpl::StackSamplerImpl(
-    std::unique_ptr<ThreadDelegate> thread_delegate,
-    std::unique_ptr<Unwinder> native_unwinder,
-    ModuleCache* module_cache,
-    StackSamplerTestDelegate* test_delegate)
-    : thread_delegate_(std::move(thread_delegate)),
+StackSamplerImpl::StackSamplerImpl(std::unique_ptr<StackCopier> stack_copier,
+                                   std::unique_ptr<Unwinder> native_unwinder,
+                                   ModuleCache* module_cache,
+                                   StackSamplerTestDelegate* test_delegate)
+    : stack_copier_(std::move(stack_copier)),
       native_unwinder_(std::move(native_unwinder)),
       module_cache_(module_cache),
       test_delegate_(test_delegate) {}
@@ -46,8 +45,8 @@
 
   RegisterContext thread_context;
   uintptr_t stack_top;
-  bool success =
-      CopyStack(stack_buffer, &stack_top, profile_builder, &thread_context);
+  bool success = stack_copier_->CopyStack(stack_buffer, &stack_top,
+                                          profile_builder, &thread_context);
   if (!success)
     return;
 
@@ -70,66 +69,6 @@
                    aux_unwinder);
 }
 
-// Suspends the thread, copies its stack, top address of the stack copy, and
-// register context, records the current metadata, then resumes the thread.
-// Returns true on success, and returns the copied state via the params. NO HEAP
-// ALLOCATIONS within the ScopedSuspendThread scope.
-bool StackSamplerImpl::CopyStack(StackBuffer* stack_buffer,
-                                 uintptr_t* stack_top,
-                                 ProfileBuilder* profile_builder,
-                                 RegisterContext* thread_context) {
-  const uintptr_t top = thread_delegate_->GetStackBaseAddress();
-  uintptr_t bottom = 0;
-  const uint8_t* stack_copy_bottom = nullptr;
-  {
-    // The MetadataProvider must be created before the ScopedSuspendThread
-    // because it acquires a lock in its constructor that might otherwise be
-    // held by the target thread, resulting in deadlock.
-    std::unique_ptr<base::ProfileBuilder::MetadataProvider> get_metadata_items =
-        base::GetSampleMetadataRecorder()->CreateMetadataProvider();
-
-    // Allocation of the ScopedSuspendThread object itself is OK since it
-    // necessarily occurs before the thread is suspended by the object.
-    std::unique_ptr<ThreadDelegate::ScopedSuspendThread> suspend_thread =
-        thread_delegate_->CreateScopedSuspendThread();
-
-    if (!suspend_thread->WasSuccessful())
-      return false;
-
-    if (!thread_delegate_->GetThreadContext(thread_context))
-      return false;
-
-    bottom = RegisterContextStackPointer(thread_context);
-
-    // The StackBuffer allocation is expected to be at least as large as the
-    // largest stack region allocation on the platform, but check just in case
-    // it isn't *and* the actual stack itself exceeds the buffer allocation
-    // size.
-    if ((top - bottom) > stack_buffer->size())
-      return false;
-
-    if (!thread_delegate_->CanCopyStack(bottom))
-      return false;
-
-    profile_builder->RecordMetadata(get_metadata_items.get());
-
-    stack_copy_bottom = StackCopier::CopyStackContentsAndRewritePointers(
-        reinterpret_cast<uint8_t*>(bottom), reinterpret_cast<uintptr_t*>(top),
-        StackBuffer::kPlatformStackAlignment, stack_buffer->buffer());
-  }
-
-  *stack_top = reinterpret_cast<uintptr_t>(stack_copy_bottom) + (top - bottom);
-
-  for (uintptr_t* reg :
-       thread_delegate_->GetRegistersToRewrite(thread_context)) {
-    *reg = StackCopier::RewritePointerIfInOriginalStack(
-        reinterpret_cast<uint8_t*>(bottom), reinterpret_cast<uintptr_t*>(top),
-        stack_copy_bottom, *reg);
-  }
-
-  return true;
-}
-
 // static
 std::vector<Frame> StackSamplerImpl::WalkStack(ModuleCache* module_cache,
                                                RegisterContext* thread_context,
diff --git a/base/profiler/stack_sampler_impl.h b/base/profiler/stack_sampler_impl.h
index b28d90c..226ccdb 100644
--- a/base/profiler/stack_sampler_impl.h
+++ b/base/profiler/stack_sampler_impl.h
@@ -14,14 +14,14 @@
 
 namespace base {
 
-class ThreadDelegate;
+class StackCopier;
 class Unwinder;
 
-// Cross-platform stack sampler implementation. Delegates to ThreadDelegate for
-// platform-specific implementation.
+// Cross-platform stack sampler implementation. Delegates to StackCopier for the
+// platform-specific stack copying implementation.
 class BASE_EXPORT StackSamplerImpl : public StackSampler {
  public:
-  StackSamplerImpl(std::unique_ptr<ThreadDelegate> delegate,
+  StackSamplerImpl(std::unique_ptr<StackCopier> stack_copier,
                    std::unique_ptr<Unwinder> native_unwinder,
                    ModuleCache* module_cache,
                    StackSamplerTestDelegate* test_delegate = nullptr);
@@ -43,18 +43,13 @@
                                                 Unwinder* aux_unwinder);
 
  private:
-  bool CopyStack(StackBuffer* stack_buffer,
-                 uintptr_t* stack_top,
-                 ProfileBuilder* profile_builder,
-                 RegisterContext* thread_context);
-
   static std::vector<Frame> WalkStack(ModuleCache* module_cache,
                                       RegisterContext* thread_context,
                                       uintptr_t stack_top,
                                       Unwinder* native_unwinder,
                                       Unwinder* aux_unwinder);
 
-  const std::unique_ptr<ThreadDelegate> thread_delegate_;
+  const std::unique_ptr<StackCopier> stack_copier_;
   const std::unique_ptr<Unwinder> native_unwinder_;
   std::unique_ptr<Unwinder> aux_unwinder_;
   ModuleCache* const module_cache_;
diff --git a/base/profiler/stack_sampler_impl_unittest.cc b/base/profiler/stack_sampler_impl_unittest.cc
index f9d56c3..090821d 100644
--- a/base/profiler/stack_sampler_impl_unittest.cc
+++ b/base/profiler/stack_sampler_impl_unittest.cc
@@ -10,6 +10,7 @@
 
 #include "base/profiler/profile_builder.h"
 #include "base/profiler/stack_buffer.h"
+#include "base/profiler/stack_copier.h"
 #include "base/profiler/stack_sampler_impl.h"
 #include "base/profiler/thread_delegate.h"
 #include "base/profiler/unwinder.h"
@@ -44,60 +45,31 @@
   ModuleCache* module_cache_;
 };
 
-// A thread delegate for use in tests that provides the expected behavior when
+// A stack copier for use in tests that provides the expected behavior when
 // operating on the supplied fake stack.
-class TestThreadDelegate : public ThreadDelegate {
+class TestStackCopier : public StackCopier {
  public:
-  class TestScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
-   public:
-    TestScopedSuspendThread() = default;
+  TestStackCopier(const std::vector<uintptr_t>& fake_stack)
+      : fake_stack_(fake_stack) {}
 
-    TestScopedSuspendThread(const TestScopedSuspendThread&) = delete;
-    TestScopedSuspendThread& operator=(const TestScopedSuspendThread&) = delete;
-
-    bool WasSuccessful() const override { return true; }
-  };
-
-  TestThreadDelegate(const std::vector<uintptr_t>& fake_stack,
-                     // The register context will be initialized to
-                     // *|thread_context| if non-null.
-                     RegisterContext* thread_context = nullptr)
-      : fake_stack_(fake_stack), thread_context_(thread_context) {}
-
-  TestThreadDelegate(const TestThreadDelegate&) = delete;
-  TestThreadDelegate& operator=(const TestThreadDelegate&) = delete;
-
-  std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() override {
-    return std::make_unique<TestScopedSuspendThread>();
-  }
-
-  bool GetThreadContext(RegisterContext* thread_context) override {
-    if (thread_context_)
-      *thread_context = *thread_context_;
+  bool CopyStack(StackBuffer* stack_buffer,
+                 uintptr_t* stack_top,
+                 ProfileBuilder* profile_builder,
+                 RegisterContext* thread_context) override {
+    std::memcpy(stack_buffer->buffer(), &fake_stack_[0], fake_stack_.size());
+    *stack_top =
+        reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
     // Set the stack pointer to be consistent with the provided fake stack.
     RegisterContextStackPointer(thread_context) =
         reinterpret_cast<uintptr_t>(&fake_stack_[0]);
-    RegisterContextInstructionPointer(thread_context) =
-        reinterpret_cast<uintptr_t>(fake_stack_[0]);
+
     return true;
   }
 
-  uintptr_t GetStackBaseAddress() const override {
-    return reinterpret_cast<uintptr_t>(&fake_stack_[0] + fake_stack_.size());
-  }
-
-  bool CanCopyStack(uintptr_t stack_pointer) override { return true; }
-
-  std::vector<uintptr_t*> GetRegistersToRewrite(
-      RegisterContext* thread_context) override {
-    return {&RegisterContextFramePointer(thread_context)};
-  }
-
  private:
   // Must be a reference to retain the underlying allocation from the vector
   // passed to the constructor.
   const std::vector<uintptr_t>& fake_stack_;
-  RegisterContext* thread_context_;
 };
 
 // Trivial unwinder implementation for testing.
@@ -236,7 +208,7 @@
   InjectModuleForContextInstructionPointer(stack, &module_cache);
   std::vector<uintptr_t> stack_copy;
   StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack),
+      std::make_unique<TestStackCopier>(stack),
       std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
 
   std::unique_ptr<StackBuffer> stack_buffer =
@@ -247,75 +219,6 @@
   EXPECT_EQ(stack, stack_copy);
 }
 
-TEST(StackSamplerImplTest, CopyStackBufferTooSmall) {
-  ModuleCache module_cache;
-  std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
-  InjectModuleForContextInstructionPointer(stack, &module_cache);
-  std::vector<uintptr_t> stack_copy;
-  StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack),
-      std::make_unique<TestUnwinder>(stack.size(), &stack_copy), &module_cache);
-
-  std::unique_ptr<StackBuffer> stack_buffer =
-      std::make_unique<StackBuffer>((stack.size() - 1) * sizeof(uintptr_t));
-  // Make the buffer different than the input stack.
-  stack_buffer->buffer()[0] = 100;
-  TestProfileBuilder profile_builder(&module_cache);
-
-  stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
-
-  // Use the buffer not being overwritten as a proxy for the unwind being
-  // aborted.
-  EXPECT_NE(stack, stack_copy);
-}
-
-TEST(StackSamplerImplTest, CopyStackAndRewritePointers) {
-  ModuleCache module_cache;
-  // Allocate space for the stack, then make its elements point to themselves.
-  std::vector<uintptr_t> stack(2);
-  stack[0] = reinterpret_cast<uintptr_t>(&stack[0]);
-  stack[1] = reinterpret_cast<uintptr_t>(&stack[1]);
-  InjectModuleForContextInstructionPointer(stack, &module_cache);
-  std::vector<uintptr_t> stack_copy;
-  uintptr_t stack_copy_bottom;
-  StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack),
-      std::make_unique<TestUnwinder>(stack.size(), &stack_copy,
-                                     &stack_copy_bottom),
-      &module_cache);
-
-  std::unique_ptr<StackBuffer> stack_buffer =
-      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
-  TestProfileBuilder profile_builder(&module_cache);
-
-  stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
-
-  EXPECT_THAT(stack_copy, ElementsAre(stack_copy_bottom,
-                                      stack_copy_bottom + sizeof(uintptr_t)));
-}
-
-TEST(StackSamplerImplTest, RewriteRegisters) {
-  ModuleCache module_cache;
-  std::vector<uintptr_t> stack = {0, 1, 2};
-  InjectModuleForContextInstructionPointer(stack, &module_cache);
-  uintptr_t stack_copy_bottom;
-  RegisterContext thread_context;
-  RegisterContextFramePointer(&thread_context) =
-      reinterpret_cast<uintptr_t>(&stack[1]);
-  StackSamplerImpl stack_sampler_impl(
-      std::make_unique<TestThreadDelegate>(stack, &thread_context),
-      std::make_unique<TestUnwinder>(stack.size(), nullptr, &stack_copy_bottom),
-      &module_cache);
-
-  std::unique_ptr<StackBuffer> stack_buffer =
-      std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
-  TestProfileBuilder profile_builder(&module_cache);
-  stack_sampler_impl.RecordStackFrames(stack_buffer.get(), &profile_builder);
-
-  EXPECT_EQ(stack_copy_bottom + sizeof(uintptr_t),
-            RegisterContextFramePointer(&thread_context));
-}
-
 TEST(StackSamplerImplTest, WalkStack_Completed) {
   ModuleCache module_cache;
   RegisterContext thread_context;
diff --git a/base/profiler/stack_sampler_mac.cc b/base/profiler/stack_sampler_mac.cc
index 54ca505e..2f5de9f 100644
--- a/base/profiler/stack_sampler_mac.cc
+++ b/base/profiler/stack_sampler_mac.cc
@@ -5,6 +5,7 @@
 #include "base/profiler/stack_sampler.h"
 
 #include "base/profiler/native_unwinder_mac.h"
+#include "base/profiler/stack_copier_suspend.h"
 #include "base/profiler/stack_sampler_impl.h"
 #include "base/profiler/thread_delegate_mac.h"
 
@@ -16,7 +17,8 @@
     ModuleCache* module_cache,
     StackSamplerTestDelegate* test_delegate) {
   return std::make_unique<StackSamplerImpl>(
-      std::make_unique<ThreadDelegateMac>(thread_id),
+      std::make_unique<StackCopierSuspend>(
+          std::make_unique<ThreadDelegateMac>(thread_id)),
       std::make_unique<NativeUnwinderMac>(module_cache), module_cache,
       test_delegate);
 }
diff --git a/base/profiler/stack_sampler_win.cc b/base/profiler/stack_sampler_win.cc
index fb09fb7..d5d2e50 100644
--- a/base/profiler/stack_sampler_win.cc
+++ b/base/profiler/stack_sampler_win.cc
@@ -5,6 +5,7 @@
 #include "base/profiler/stack_sampler.h"
 
 #include "base/profiler/native_unwinder_win.h"
+#include "base/profiler/stack_copier_suspend.h"
 #include "base/profiler/stack_sampler_impl.h"
 #include "base/profiler/thread_delegate_win.h"
 #include "build/build_config.h"
@@ -18,7 +19,8 @@
     StackSamplerTestDelegate* test_delegate) {
 #if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)
   return std::make_unique<StackSamplerImpl>(
-      std::make_unique<ThreadDelegateWin>(thread_id),
+      std::make_unique<StackCopierSuspend>(
+          std::make_unique<ThreadDelegateWin>(thread_id)),
       std::make_unique<NativeUnwinderWin>(), module_cache, test_delegate);
 #else
   return nullptr;
diff --git a/base/system/sys_info_ios.mm b/base/system/sys_info_ios.mm
index e8ae8fb..1dd9dc1 100644
--- a/base/system/sys_info_ios.mm
+++ b/base/system/sys_info_ios.mm
@@ -13,7 +13,6 @@
 
 #include "base/logging.h"
 #include "base/mac/scoped_mach_port.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/process/process_metrics.h"
 #include "base/stl_util.h"
 #include "base/strings/string_util.h"
@@ -43,9 +42,10 @@
   static dispatch_once_t get_system_name_once;
   static std::string* system_name;
   dispatch_once(&get_system_name_once, ^{
-    base::mac::ScopedNSAutoreleasePool pool;
-    system_name = new std::string(
-        SysNSStringToUTF8([[UIDevice currentDevice] systemName]));
+    @autoreleasepool {
+      system_name = new std::string(
+          SysNSStringToUTF8([[UIDevice currentDevice] systemName]));
+    }
   });
   // Examples of returned value: 'iPhone OS' on iPad 5.1.1
   // and iPhone 5.1.1.
@@ -57,9 +57,10 @@
   static dispatch_once_t get_system_version_once;
   static std::string* system_version;
   dispatch_once(&get_system_version_once, ^{
-    base::mac::ScopedNSAutoreleasePool pool;
-    system_version = new std::string(
-        SysNSStringToUTF8([[UIDevice currentDevice] systemVersion]));
+    @autoreleasepool {
+      system_version = new std::string(
+          SysNSStringToUTF8([[UIDevice currentDevice] systemVersion]));
+    }
   });
   return *system_version;
 }
@@ -68,18 +69,19 @@
 void SysInfo::OperatingSystemVersionNumbers(int32_t* major_version,
                                             int32_t* minor_version,
                                             int32_t* bugfix_version) {
-  base::mac::ScopedNSAutoreleasePool pool;
-  std::string system_version = OperatingSystemVersion();
-  if (!system_version.empty()) {
-    // Try to parse out the version numbers from the string.
-    int num_read = sscanf(system_version.c_str(), "%d.%d.%d", major_version,
-                          minor_version, bugfix_version);
-    if (num_read < 1)
-      *major_version = 0;
-    if (num_read < 2)
-      *minor_version = 0;
-    if (num_read < 3)
-      *bugfix_version = 0;
+  @autoreleasepool {
+    std::string system_version = OperatingSystemVersion();
+    if (!system_version.empty()) {
+      // Try to parse out the version numbers from the string.
+      int num_read = sscanf(system_version.c_str(), "%d.%d.%d", major_version,
+                            minor_version, bugfix_version);
+      if (num_read < 1)
+        *major_version = 0;
+      if (num_read < 2)
+        *minor_version = 0;
+      if (num_read < 3)
+        *bugfix_version = 0;
+    }
   }
 }
 
diff --git a/base/test/test_listener_ios.mm b/base/test/test_listener_ios.mm
index 12cf5bb9..54aa9acb 100644
--- a/base/test/test_listener_ios.mm
+++ b/base/test/test_listener_ios.mm
@@ -6,7 +6,6 @@
 
 #import <Foundation/Foundation.h>
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // The iOS watchdog timer will kill an app that doesn't spin the main event
@@ -22,11 +21,11 @@
 };
 
 void IOSRunLoopListener::OnTestEnd(const testing::TestInfo& test_info) {
-  base::mac::ScopedNSAutoreleasePool scoped_pool;
-
-  // At the end of the test, spin the default loop for a moment.
-  NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001];
-  [[NSRunLoop currentRunLoop] runUntilDate:stop_date];
+  @autoreleasepool {
+    // At the end of the test, spin the default loop for a moment.
+    NSDate* stop_date = [NSDate dateWithTimeIntervalSinceNow:0.001];
+    [[NSRunLoop currentRunLoop] runUntilDate:stop_date];
+  }
 }
 
 }  // namespace
diff --git a/base/test/test_support_ios.mm b/base/test/test_support_ios.mm
index 9cd4a40..4e5c56b 100644
--- a/base/test/test_support_ios.mm
+++ b/base/test/test_support_ios.mm
@@ -6,7 +6,6 @@
 
 #include "base/debug/debugger.h"
 #include "base/logging.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/message_loop/message_pump.h"
 #include "base/message_loop/message_pump_default.h"
@@ -209,10 +208,11 @@
   static bool ran_hook = false;
   if (!ran_hook) {
     ran_hook = true;
-    mac::ScopedNSAutoreleasePool pool;
-    int exit_status = UIApplicationMain(g_argc, g_argv, nil,
-                                        @"ChromeUnitTestDelegate");
-    exit(exit_status);
+    @autoreleasepool {
+      int exit_status =
+          UIApplicationMain(g_argc, g_argv, nil, @"ChromeUnitTestDelegate");
+      exit(exit_status);
+    }
   }
 }
 
diff --git a/build/android/devil_chromium.json b/build/android/devil_chromium.json
index 6cb7608c..4734decc 100644
--- a/build/android/devil_chromium.json
+++ b/build/android/devil_chromium.json
@@ -125,6 +125,15 @@
           ]
         }
       }
+    },
+    "bundletool": {
+      "file_info": {
+        "default": {
+          "local_paths": [
+            "../../third_party/android_build_tools/bundletool/bundletool-all-0.10.3.jar"
+          ]
+        }
+      }
     }
   }
 }
diff --git a/build/android/gyp/create_bundle_wrapper_script.pydeps b/build/android/gyp/create_bundle_wrapper_script.pydeps
index fb35bc0..a83e696 100644
--- a/build/android/gyp/create_bundle_wrapper_script.pydeps
+++ b/build/android/gyp/create_bundle_wrapper_script.pydeps
@@ -37,6 +37,7 @@
 ../../../third_party/catapult/devil/devil/android/sdk/aapt.py
 ../../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
 ../../../third_party/catapult/devil/devil/android/sdk/build_tools.py
+../../../third_party/catapult/devil/devil/android/sdk/bundletool.py
 ../../../third_party/catapult/devil/devil/android/sdk/intent.py
 ../../../third_party/catapult/devil/devil/android/sdk/keyevent.py
 ../../../third_party/catapult/devil/devil/android/sdk/split_select.py
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
index 0911422..d9fa340 100644
--- a/build/android/pylib/instrumentation/instrumentation_test_instance.py
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
@@ -446,6 +446,7 @@
     self._additional_apks = []
     self._apk_under_test = None
     self._apk_under_test_incremental_install_json = None
+    self._modules = None
     self._package_info = None
     self._suite = None
     self._test_apk = None
@@ -506,7 +507,8 @@
   def _initializeApkAttributes(self, args, error_func):
     if args.apk_under_test:
       apk_under_test_path = args.apk_under_test
-      if not args.apk_under_test.endswith('.apk'):
+      if (not args.apk_under_test.endswith('.apk')
+          and not args.apk_under_test.endswith('.apks')):
         apk_under_test_path = os.path.join(
             constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
             '%s.apk' % args.apk_under_test)
@@ -520,24 +522,20 @@
 
       self._apk_under_test = apk_helper.ToHelper(apk_under_test_path)
 
-    if args.test_apk.endswith('.apk'):
-      self._suite = os.path.splitext(os.path.basename(args.test_apk))[0]
-      test_apk_path = args.test_apk
-      self._test_apk = apk_helper.ToHelper(args.test_apk)
-    else:
-      self._suite = args.test_apk
+    test_apk_path = args.test_apk
+    if not os.path.exists(test_apk_path):
       test_apk_path = os.path.join(
           constants.GetOutDirectory(), constants.SDK_BUILD_APKS_DIR,
           '%s.apk' % args.test_apk)
-
-    # TODO(jbudorick): Move the realpath up to the argument parser once
-    # APK-by-name is no longer supported.
-    test_apk_path = os.path.realpath(test_apk_path)
+      # TODO(jbudorick): Move the realpath up to the argument parser once
+      # APK-by-name is no longer supported.
+      test_apk_path = os.path.realpath(test_apk_path)
 
     if not os.path.exists(test_apk_path):
       error_func('Unable to find test APK: %s' % test_apk_path)
 
     self._test_apk = apk_helper.ToHelper(test_apk_path)
+    self._suite = os.path.splitext(os.path.basename(args.test_apk))[0]
 
     self._apk_under_test_incremental_install_json = (
         args.apk_under_test_incremental_install_json)
@@ -548,13 +546,13 @@
       assert self._suite.endswith('_incremental')
       self._suite = self._suite[:-len('_incremental')]
 
+    self._modules = args.modules
+
     self._test_jar = args.test_jar
     self._test_support_apk = apk_helper.ToHelper(os.path.join(
         constants.GetOutDirectory(), constants.SDK_BUILD_TEST_JAVALIB_DIR,
         '%sSupport.apk' % self._suite))
 
-    if not os.path.exists(self._test_apk.path):
-      error_func('Unable to find test APK: %s' % self._test_apk.path)
     if not self._test_jar:
       logging.warning('Test jar not specified. Test runner will not have '
                       'Java annotation info available. May not handle test '
@@ -717,6 +715,10 @@
     return self._apk_under_test_incremental_install_json
 
   @property
+  def modules(self):
+    return self._modules
+
+  @property
   def coverage_directory(self):
     return self._coverage_directory
 
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 18914e9..3003afb 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -190,17 +190,20 @@
 
         steps.append(use_webview_provider)
 
-      def install_helper(apk, permissions):
+      def install_helper(apk, modules=None, permissions=None):
+
         @instrumentation_tracing.no_tracing
-        @trace_event.traced("apk_path")
-        def install_helper_internal(d, apk_path=apk.path):
+        @trace_event.traced
+        def install_helper_internal(d, apk_path=None):
           # pylint: disable=unused-argument
-          d.Install(apk, permissions=permissions)
+          d.Install(apk, modules=modules, permissions=permissions)
+
         return install_helper_internal
 
       def incremental_install_helper(apk, json_path, permissions):
-        @trace_event.traced("apk_path")
-        def incremental_install_helper_internal(d, apk_path=apk.path):
+
+        @trace_event.traced
+        def incremental_install_helper_internal(d, apk_path=None):
           # pylint: disable=unused-argument
           installer.Install(d, json_path, apk=apk, permissions=permissions)
         return incremental_install_helper_internal
@@ -214,8 +217,9 @@
                                apk_under_test_incremental_install_json,
                            permissions))
         else:
-          steps.append(install_helper(self._test_instance.apk_under_test,
-                                      permissions))
+          steps.append(
+              install_helper(self._test_instance.apk_under_test,
+                             self._test_instance.modules, permissions))
 
       permissions = self._test_instance.test_apk.GetPermissions()
       if self._test_instance.test_apk_incremental_install_json:
@@ -225,11 +229,12 @@
                              test_apk_incremental_install_json,
                          permissions))
       else:
-        steps.append(install_helper(self._test_instance.test_apk,
-                                    permissions))
+        steps.append(
+            install_helper(
+                self._test_instance.test_apk, permissions=permissions))
 
-      steps.extend(install_helper(apk, None)
-                   for apk in self._test_instance.additional_apks)
+      steps.extend(
+          install_helper(apk) for apk in self._test_instance.additional_apks)
 
       @trace_event.traced
       def set_debug_app(dev):
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 443eb26..932188e 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -414,6 +414,12 @@
       '--apk-under-test',
       help='Path or name of the apk under test.')
   parser.add_argument(
+      '--module',
+      action='append',
+      dest='modules',
+      help='Specify Android App Bundle modules to install in addition to the '
+      'base module.')
+  parser.add_argument(
       '--coverage-dir',
       type=os.path.realpath,
       help='Directory in which to place all generated '
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 9b722c40..4337f75 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -56,6 +56,7 @@
 ../../third_party/catapult/devil/devil/android/sdk/aapt.py
 ../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
 ../../third_party/catapult/devil/devil/android/sdk/build_tools.py
+../../third_party/catapult/devil/devil/android/sdk/bundletool.py
 ../../third_party/catapult/devil/devil/android/sdk/intent.py
 ../../third_party/catapult/devil/devil/android/sdk/keyevent.py
 ../../third_party/catapult/devil/devil/android/sdk/shared_prefs.py
diff --git a/cc/animation/animation_host_perftest.cc b/cc/animation/animation_host_perftest.cc
index 3bd2568..b9926f4 100644
--- a/cc/animation/animation_host_perftest.cc
+++ b/cc/animation/animation_host_perftest.cc
@@ -17,7 +17,7 @@
 #include "cc/test/stub_layer_tree_host_single_thread_client.h"
 #include "cc/test/test_task_graph_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 
@@ -120,7 +120,7 @@
       all_animations_timeline_->GetAnimationById(i)->SetNeedsPushProperties();
   }
 
-  void DoTest() {
+  void DoTest(const std::string& test_name) {
     timer_.Reset();
     do {
       // Invalidate dirty flags.
@@ -130,8 +130,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("push_properties_to", "", "", timer_.LapsPerSecond(),
-                           "runs/s", true);
+    perf_test::PerfResultReporter reporter("push_properties_to", test_name);
+    reporter.RegisterImportantMetric("", "runs/s");
+    reporter.AddResult("", timer_.LapsPerSecond());
   }
 
  private:
@@ -155,17 +156,17 @@
 
 TEST_F(AnimationHostPerfTest, Push1000AnimationsPropertiesTo) {
   CreateAnimations(1000);
-  DoTest();
+  DoTest("Push1000AnimationsPropertiesTo");
 }
 
 TEST_F(AnimationHostPerfTest, Push10TimelinesPropertiesTo) {
   CreateTimelines(10);
-  DoTest();
+  DoTest("Push10TimelinesPropertiesTo");
 }
 
 TEST_F(AnimationHostPerfTest, Push1000TimelinesPropertiesTo) {
   CreateTimelines(1000);
-  DoTest();
+  DoTest("Push1000TimelinesPropertiesTo");
 }
 
 }  // namespace cc
diff --git a/cc/base/rtree_perftest.cc b/cc/base/rtree_perftest.cc
index 3b12bf5..00d8a95 100644
--- a/cc/base/rtree_perftest.cc
+++ b/cc/base/rtree_perftest.cc
@@ -6,7 +6,7 @@
 #include "cc/base/rtree.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -39,8 +39,8 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("rtree_construct", "", test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_construct", timer_.LapsPerSecond());
   }
 
   void RunSearchTest(const std::string& test_name, int rect_count) {
@@ -65,8 +65,8 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("rtree_search", "", test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_search", timer_.LapsPerSecond());
   }
 
   std::vector<gfx::Rect> BuildRects(int count) {
@@ -85,6 +85,13 @@
   }
 
  protected:
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+    perf_test::PerfResultReporter reporter("rtree", story_name);
+    reporter.RegisterImportantMetric("_construct", "runs/s");
+    reporter.RegisterImportantMetric("_search", "runs/s");
+    return reporter;
+  }
+
   base::LapTimer timer_;
 };
 
diff --git a/cc/layers/layer_perftest.cc b/cc/layers/layer_perftest.cc
index c217188..debe5055 100644
--- a/cc/layers/layer_perftest.cc
+++ b/cc/layers/layer_perftest.cc
@@ -14,7 +14,7 @@
 #include "cc/test/stub_layer_tree_host_single_thread_client.h"
 #include "cc/test/test_task_graph_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -45,6 +45,14 @@
     layer_tree_host_ = nullptr;
   }
 
+  perf_test::PerfResultReporter SetUpReporter(
+      const std::string& metric_basename,
+      const std::string& story_name) {
+    perf_test::PerfResultReporter reporter(metric_basename, story_name);
+    reporter.RegisterImportantMetric("", "runs/s");
+    return reporter;
+  }
+
   FakeImplTaskRunnerProvider task_runner_provider_;
   TestTaskGraphRunner task_graph_runner_;
   FakeLayerTreeHostImpl host_impl_;
@@ -91,12 +99,9 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("push_properties_to",
-                         "",
-                         "props_changed",
-                         timer_.LapsPerSecond(),
-                         "runs/s",
-                         true);
+  perf_test::PerfResultReporter reporter =
+      SetUpReporter("push_properties_to", "props_changed");
+  reporter.AddResult("", timer_.LapsPerSecond());
 
   // Properties didn't change.
   timer_.Reset();
@@ -105,12 +110,8 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("push_properties_to",
-                         "",
-                         "props_didnt_change",
-                         timer_.LapsPerSecond(),
-                         "runs/s",
-                         true);
+  reporter = SetUpReporter("push_properties_to", "props_didnt_change");
+  reporter.AddResult("", timer_.LapsPerSecond());
 }
 
 TEST_F(LayerPerfTest, ImplPushPropertiesTo) {
@@ -147,8 +148,9 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("impl_push_properties_to", "", "props_changed",
-                         timer_.LapsPerSecond(), "runs/s", true);
+  perf_test::PerfResultReporter reporter =
+      SetUpReporter("impl_push_properties_to", "props_changed");
+  reporter.AddResult("", timer_.LapsPerSecond());
 
   // Properties didn't change.
   timer_.Reset();
@@ -157,8 +159,8 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("impl_push_properties_to", "", "props_didnt_change",
-                         timer_.LapsPerSecond(), "runs/s", true);
+  reporter = SetUpReporter("impl_push_properties_to", "props_didnt_change");
+  reporter.AddResult("", timer_.LapsPerSecond());
 }
 
 }  // namespace
diff --git a/cc/layers/picture_layer_impl_perftest.cc b/cc/layers/picture_layer_impl_perftest.cc
index 56bf774a..7db1e5a 100644
--- a/cc/layers/picture_layer_impl_perftest.cc
+++ b/cc/layers/picture_layer_impl_perftest.cc
@@ -15,7 +15,7 @@
 #include "cc/tiles/tiling_set_raster_queue_all.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -96,8 +96,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("tiling_set_raster_queue_construct_and_iterate", "",
-                           test_name, timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_raster_queue_construct_and_iterate",
+                       timer_.LapsPerSecond());
   }
 
   void RunRasterQueueConstructTest(const std::string& test_name,
@@ -118,8 +119,8 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("tiling_set_raster_queue_construct", "", test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_raster_queue_construct", timer_.LapsPerSecond());
   }
 
   void RunEvictionQueueConstructAndIterateTest(const std::string& test_name,
@@ -142,9 +143,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("tiling_set_eviction_queue_construct_and_iterate",
-                           "", test_name, timer_.LapsPerSecond(), "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_eviction_queue_construct_and_iterate",
+                       timer_.LapsPerSecond());
   }
 
   void RunEvictionQueueConstructTest(const std::string& test_name,
@@ -165,11 +166,21 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("tiling_set_eviction_queue_construct", "", test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_eviction_queue_construct", timer_.LapsPerSecond());
   }
 
  protected:
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+    perf_test::PerfResultReporter reporter("tiling_set", story_name);
+    reporter.RegisterImportantMetric("_raster_queue_construct_and_iterate",
+                                     "runs/s");
+    reporter.RegisterImportantMetric("_raster_queue_construct", "runs/s");
+    reporter.RegisterImportantMetric("_eviction_queue_construct_and_iterate",
+                                     "runs/s");
+    return reporter;
+  }
+
   TestTaskGraphRunner task_graph_runner_;
   FakeImplTaskRunnerProvider task_runner_provider_;
   std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink_;
diff --git a/cc/paint/paint_op_perftest.cc b/cc/paint/paint_op_perftest.cc
index 6dacf3eb..e3cd6cb 100644
--- a/cc/paint/paint_op_perftest.cc
+++ b/cc/paint/paint_op_perftest.cc
@@ -10,7 +10,7 @@
 #include "cc/paint/paint_op_buffer.h"
 #include "cc/paint/paint_op_buffer_serializer.h"
 #include "cc/test/test_options_provider.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 #include "third_party/skia/include/core/SkMaskFilter.h"
 #include "third_party/skia/include/effects/SkColorMatrixFilter.h"
 #include "third_party/skia/include/effects/SkDashPathEffect.h"
@@ -67,9 +67,9 @@
     } while (!timer_.HasTimeLimitExpired());
     CHECK_GT(bytes_written, 0u);
 
-    perf_test::PrintResult(name.c_str(), "", "  serialize",
-                           buffer.size() * timer_.LapsPerSecond(), "ops/s",
-                           true);
+    perf_test::PerfResultReporter reporter(name, "  serialize");
+    reporter.RegisterImportantMetric("", "runs/s");
+    reporter.AddResult("", timer_.LapsPerSecond());
 
     size_t bytes_read = 0;
     timer_.Reset();
@@ -98,9 +98,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult(name.c_str(), "", "deserialize",
-                           buffer.size() * timer_.LapsPerSecond(), "ops/s",
-                           true);
+    reporter = perf_test::PerfResultReporter(name, "deserialize");
+    reporter.RegisterImportantMetric("", "runs/s");
+    reporter.AddResult("", timer_.LapsPerSecond());
   }
 
  protected:
diff --git a/cc/raster/raster_buffer_provider_perftest.cc b/cc/raster/raster_buffer_provider_perftest.cc
index 541b22df..66391be 100644
--- a/cc/raster/raster_buffer_provider_perftest.cc
+++ b/cc/raster/raster_buffer_provider_perftest.cc
@@ -29,7 +29,7 @@
 #include "gpu/command_buffer/common/sync_token.h"
 #include "gpu/config/gpu_feature_info.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 #include "third_party/khronos/GLES2/gl2.h"
 #include "third_party/skia/include/gpu/GrContext.h"
 
@@ -435,8 +435,8 @@
     RunMessageLoopUntilAllTasksHaveCompleted();
     tile_task_manager_->CheckForCompletedTasks();
 
-    perf_test::PrintResult("schedule_tasks", TestModifierString(), test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_tasks" + TestModifierString(), timer_.LapsPerSecond());
   }
 
   void RunScheduleAlternateTasksTest(const std::string& test_name,
@@ -472,8 +472,9 @@
     RunMessageLoopUntilAllTasksHaveCompleted();
     tile_task_manager_->CheckForCompletedTasks();
 
-    perf_test::PrintResult("schedule_alternate_tasks", TestModifierString(),
-                           test_name, timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_alternate_tasks" + TestModifierString(),
+                       timer_.LapsPerSecond());
   }
 
   void RunScheduleAndExecuteTasksTest(const std::string& test_name,
@@ -501,8 +502,20 @@
     tile_task_manager_->ScheduleTasks(&empty);
     RunMessageLoopUntilAllTasksHaveCompleted();
 
-    perf_test::PrintResult("schedule_and_execute_tasks", TestModifierString(),
-                           test_name, timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_and_execute_tasks" + TestModifierString(),
+                       timer_.LapsPerSecond());
+  }
+
+ protected:
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+    perf_test::PerfResultReporter reporter("schedule", story_name);
+    reporter.RegisterImportantMetric("_tasks" + TestModifierString(), "runs/s");
+    reporter.RegisterImportantMetric("_alternate_tasks" + TestModifierString(),
+                                     "runs/s");
+    reporter.RegisterImportantMetric(
+        "_and_execute_tasks" + TestModifierString(), "runs/s");
+    return reporter;
   }
 
  private:
@@ -606,8 +619,16 @@
     for (auto& task : raster_tasks)
       task->OnTaskCompleted();
 
-    perf_test::PrintResult("build_raster_task_graph", "", test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("", timer_.LapsPerSecond());
+  }
+
+ protected:
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+    perf_test::PerfResultReporter reporter("build_raster_test_graph",
+                                           story_name);
+    reporter.RegisterImportantMetric("", "runs/s");
+    return reporter;
   }
 };
 
diff --git a/cc/raster/task_graph_runner_perftest.cc b/cc/raster/task_graph_runner_perftest.cc
index d7f35a0..c6119f0 100644
--- a/cc/raster/task_graph_runner_perftest.cc
+++ b/cc/raster/task_graph_runner_perftest.cc
@@ -14,7 +14,7 @@
 #include "cc/base/completion_event.h"
 #include "cc/raster/synchronous_task_graph_runner.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -79,12 +79,9 @@
     CancelTasks(tasks);
     CancelTasks(top_level_tasks);
 
-    perf_test::PrintResult("build_task_graph",
-                           TestModifierString(),
-                           test_name,
-                           timer_.LapsPerSecond(),
-                           "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("build_task_graph" + TestModifierString(),
+                       timer_.LapsPerSecond());
   }
 
   void RunScheduleTasksTest(const std::string& test_name,
@@ -118,12 +115,9 @@
     task_graph_runner_->ScheduleTasks(namespace_token_, &empty);
     CollectCompletedTasks(&completed_tasks);
 
-    perf_test::PrintResult("schedule_tasks",
-                           TestModifierString(),
-                           test_name,
-                           timer_.LapsPerSecond(),
-                           "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("schedule_tasks" + TestModifierString(),
+                       timer_.LapsPerSecond());
   }
 
   void RunScheduleAlternateTasksTest(const std::string& test_name,
@@ -168,12 +162,9 @@
     task_graph_runner_->ScheduleTasks(namespace_token_, &empty);
     CollectCompletedTasks(&completed_tasks);
 
-    perf_test::PrintResult("schedule_alternate_tasks",
-                           TestModifierString(),
-                           test_name,
-                           timer_.LapsPerSecond(),
-                           "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("schedule_alternate_tasks" + TestModifierString(),
+                       timer_.LapsPerSecond());
   }
 
   void RunScheduleAndExecuteTasksTest(const std::string& test_name,
@@ -208,12 +199,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("execute_tasks",
-                           TestModifierString(),
-                           test_name,
-                           timer_.LapsPerSecond(),
-                           "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("execute_tasks" + TestModifierString(),
+                       timer_.LapsPerSecond());
   }
 
  private:
@@ -269,6 +257,19 @@
     return completed_tasks->size();
   }
 
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+    perf_test::PerfResultReporter reporter("", story_name);
+    reporter.RegisterImportantMetric("build_task_graph" + TestModifierString(),
+                                     "runs/s");
+    reporter.RegisterImportantMetric("schedule_tasks" + TestModifierString(),
+                                     "runs/s");
+    reporter.RegisterImportantMetric(
+        "schedule_alternate_tasks" + TestModifierString(), "runs/s");
+    reporter.RegisterImportantMetric("execute_tasks" + TestModifierString(),
+                                     "runs/s");
+    return reporter;
+  }
+
   // Test uses SynchronousTaskGraphRunner, as this implementation introduces
   // minimal additional complexity over the TaskGraphWorkQueue helpers.
   std::unique_ptr<SynchronousTaskGraphRunner> task_graph_runner_;
diff --git a/cc/tiles/gpu_image_decode_cache_perftest.cc b/cc/tiles/gpu_image_decode_cache_perftest.cc
index b201c58b..258bec1 100644
--- a/cc/tiles/gpu_image_decode_cache_perftest.cc
+++ b/cc/tiles/gpu_image_decode_cache_perftest.cc
@@ -12,7 +12,7 @@
 #include "components/viz/test/test_in_process_context_provider.h"
 #include "gpu/command_buffer/client/raster_interface.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 #include "third_party/skia/include/core/SkSurface.h"
 
 namespace cc {
@@ -87,6 +87,14 @@
     }
   }
 
+  perf_test::PerfResultReporter SetUpReporter(
+      const std::string& metric_suffix) {
+    perf_test::PerfResultReporter reporter("gpu_image_decode_cache",
+                                           ParamName());
+    reporter.RegisterImportantMetric(metric_suffix, "runs/s");
+    return reporter;
+  }
+
   base::LapTimer timer_;
   scoped_refptr<viz::TestInProcessContextProvider> context_provider_;
   GpuImageDecodeCache cache_;
@@ -115,9 +123,9 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("gpu_image_decode_cache_decode_with_color_conversion",
-                         ParamName(), "result", timer_.LapsPerSecond(),
-                         "runs/s", true);
+  perf_test::PerfResultReporter reporter =
+      SetUpReporter("_with_color_conversion");
+  reporter.AddResult("_with_color_conversion", timer_.LapsPerSecond());
 }
 
 using GpuImageDecodeCachePerfTestNoSw = GpuImageDecodeCachePerfTest;
@@ -157,8 +165,8 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("gpu_image_decode_cache_decode_with_mips", ParamName(),
-                         "result", timer_.LapsPerSecond(), "runs/s", true);
+  perf_test::PerfResultReporter reporter = SetUpReporter("_with_mips");
+  reporter.AddResult("_with_mips", timer_.LapsPerSecond());
 }
 
 TEST_P(GpuImageDecodeCachePerfTest, AcquireExistingImages) {
@@ -181,9 +189,9 @@
     timer_.NextLap();
   } while (!timer_.HasTimeLimitExpired());
 
-  perf_test::PrintResult("gpu_image_decode_cache_acquire_existing_images",
-                         ParamName(), "result", timer_.LapsPerSecond(),
-                         "runs/s", true);
+  perf_test::PerfResultReporter reporter =
+      SetUpReporter("_acquire_existing_images");
+  reporter.AddResult("_acquire_existing_images", timer_.LapsPerSecond());
 }
 
 }  // namespace
diff --git a/cc/tiles/software_image_decode_cache_perftest.cc b/cc/tiles/software_image_decode_cache_perftest.cc
index fb99fb7..5d729ac8 100644
--- a/cc/tiles/software_image_decode_cache_perftest.cc
+++ b/cc/tiles/software_image_decode_cache_perftest.cc
@@ -10,7 +10,7 @@
 #include "cc/raster/tile_task.h"
 #include "cc/tiles/software_image_decode_cache.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -78,8 +78,10 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("software_image_decode_cache_fromdrawimage", "",
-                           "result", timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter("software_image_decode_cache",
+                                           "fromdrawimage");
+    reporter.RegisterImportantMetric("", "runs/s");
+    reporter.AddResult("", timer_.LapsPerSecond());
   }
 
  private:
diff --git a/cc/tiles/tile_manager_perftest.cc b/cc/tiles/tile_manager_perftest.cc
index a683a74..f1b36e4e 100644
--- a/cc/tiles/tile_manager_perftest.cc
+++ b/cc/tiles/tile_manager_perftest.cc
@@ -28,9 +28,8 @@
 #include "cc/tiles/tile_priority.h"
 #include "cc/trees/layer_tree_impl.h"
 #include "components/viz/test/begin_frame_args_test.h"
-
 #include "testing/gtest/include/gtest/gtest.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -65,6 +64,18 @@
     SetupPendingTree(std::move(pending_raster_source), tile_size, Region());
   }
 
+  perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) {
+    perf_test::PerfResultReporter reporter("tile_manager", story_name);
+    reporter.RegisterImportantMetric("_raster_tile_queue_construct", "runs/s");
+    reporter.RegisterImportantMetric("_raster_tile_queue_construct_and_iterate",
+                                     "runs/s");
+    reporter.RegisterImportantMetric("_eviction_tile_queue_construct",
+                                     "runs/s");
+    reporter.RegisterImportantMetric(
+        "_eviction_tile_queue_construct_and_iterate", "runs/s");
+    return reporter;
+  }
+
   void RunRasterQueueConstructTest(const std::string& test_name,
                                    int layer_count) {
     TreePriority priorities[] = {SAME_PRIORITY_FOR_BOTH_TREES,
@@ -85,12 +96,8 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("tile_manager_raster_tile_queue_construct",
-                           "",
-                           test_name,
-                           timer_.LapsPerSecond(),
-                           "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_raster_tile_queue_construct", timer_.LapsPerSecond());
   }
 
   void RunRasterQueueConstructAndIterateTest(const std::string& test_name,
@@ -120,13 +127,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult(
-        "tile_manager_raster_tile_queue_construct_and_iterate",
-        "",
-        test_name,
-        timer_.LapsPerSecond(),
-        "runs/s",
-        true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_raster_tile_queue_construct_and_iterate",
+                       timer_.LapsPerSecond());
   }
 
   void RunEvictionQueueConstructTest(const std::string& test_name,
@@ -153,12 +156,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("tile_manager_eviction_tile_queue_construct",
-                           "",
-                           test_name,
-                           timer_.LapsPerSecond(),
-                           "runs/s",
-                           true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_eviction_tile_queue_construct",
+                       timer_.LapsPerSecond());
   }
 
   void RunEvictionQueueConstructAndIterateTest(const std::string& test_name,
@@ -193,13 +193,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult(
-        "tile_manager_eviction_tile_queue_construct_and_iterate",
-        "",
-        test_name,
-        timer_.LapsPerSecond(),
-        "runs/s",
-        true);
+    perf_test::PerfResultReporter reporter = SetUpReporter(test_name);
+    reporter.AddResult("_eviction_tile_queue_construct_and_iterate",
+                       timer_.LapsPerSecond());
   }
 
   std::vector<FakePictureLayerImpl*> CreateLayers(int layer_count,
@@ -285,8 +281,9 @@
       timer_.NextLap();
     } while (!timer_.HasTimeLimitExpired());
 
-    perf_test::PrintResult("prepare_tiles", "", test_name,
-                           timer_.LapsPerSecond(), "runs/s", true);
+    perf_test::PerfResultReporter reporter("prepare_tiles", test_name);
+    reporter.RegisterImportantMetric("", "runs/s");
+    reporter.AddResult("", timer_.LapsPerSecond());
   }
 
   TileManager* tile_manager() { return host_impl()->tile_manager(); }
diff --git a/cc/trees/layer_tree_host.h b/cc/trees/layer_tree_host.h
index 0c3405f..a43cf8b 100644
--- a/cc/trees/layer_tree_host.h
+++ b/cc/trees/layer_tree_host.h
@@ -280,8 +280,7 @@
 
   // Input Handling ---------------------------------------------
 
-  // Sets the state of the browser controls. (Used for URL bar animations on
-  // android).
+  // Sets the state of the browser controls. (Used for URL bar animations).
   void UpdateBrowserControlsState(BrowserControlsState constraints,
                                   BrowserControlsState current,
                                   bool animate);
diff --git a/cc/trees/layer_tree_host_common_perftest.cc b/cc/trees/layer_tree_host_common_perftest.cc
index 14283d0..7fbadbfb 100644
--- a/cc/trees/layer_tree_host_common_perftest.cc
+++ b/cc/trees/layer_tree_host_common_perftest.cc
@@ -23,7 +23,7 @@
 #include "cc/trees/layer_tree_impl.h"
 #include "cc/trees/transform_node.h"
 #include "components/viz/test/paths.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -58,19 +58,22 @@
     content_layer_client_.set_bounds(viewport);
   }
 
-  void SetTestName(const std::string& name) { test_name_ = name; }
+  void SetUpReporter(const std::string& story_name) {
+    reporter_ = std::make_unique<perf_test::PerfResultReporter>(
+        "calc_draw_props_time", story_name);
+    reporter_->RegisterImportantMetric("", "us");
+  }
 
   void AfterTest() override {
-    CHECK(!test_name_.empty()) << "Must SetTestName() before TearDown().";
-    perf_test::PrintResult("calc_draw_props_time", "", test_name_,
-                           timer_.TimePerLap().InMicrosecondsF(), "us", true);
+    CHECK(reporter_) << "Must SetUpReporter() before TearDown().";
+    reporter_->AddResult("", timer_.TimePerLap().InMicrosecondsF());
   }
 
  protected:
   FakeContentLayerClient content_layer_client_;
   base::LapTimer timer_;
-  std::string test_name_;
   std::string json_;
+  std::unique_ptr<perf_test::PerfResultReporter> reporter_;
 };
 
 class CalcDrawPropsTest : public LayerTreeHostCommonPerfTest {
@@ -115,25 +118,25 @@
 };
 
 TEST_F(CalcDrawPropsTest, TenTen) {
-  SetTestName("10_10");
+  SetUpReporter("10_10");
   ReadTestFile("10_10_layer_tree");
   RunCalcDrawProps();
 }
 
 TEST_F(CalcDrawPropsTest, HeavyPage) {
-  SetTestName("heavy_page");
+  SetUpReporter("heavy_page");
   ReadTestFile("heavy_layer_tree");
   RunCalcDrawProps();
 }
 
 TEST_F(CalcDrawPropsTest, TouchRegionLight) {
-  SetTestName("touch_region_light");
+  SetUpReporter("touch_region_light");
   ReadTestFile("touch_region_light");
   RunCalcDrawProps();
 }
 
 TEST_F(CalcDrawPropsTest, TouchRegionHeavy) {
-  SetTestName("touch_region_heavy");
+  SetUpReporter("touch_region_heavy");
   ReadTestFile("touch_region_heavy");
   RunCalcDrawProps();
 }
diff --git a/cc/trees/layer_tree_host_perftest.cc b/cc/trees/layer_tree_host_perftest.cc
index 379d099..7d9e9d4 100644
--- a/cc/trees/layer_tree_host_perftest.cc
+++ b/cc/trees/layer_tree_host_perftest.cc
@@ -27,7 +27,7 @@
 #include "components/viz/test/paths.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/sync_token.h"
-#include "testing/perf/perf_test.h"
+#include "testing/perf/perf_result_reporter.h"
 
 namespace cc {
 namespace {
@@ -99,6 +99,13 @@
       host_impl->SetFullViewportDamage();
   }
 
+  void SetUpReporter(const std::string& story_name) {
+    reporter_ = std::make_unique<perf_test::PerfResultReporter>(
+        "layer_tree_host", story_name);
+    reporter_->RegisterImportantMetric("_frame_time", "us");
+    reporter_->RegisterImportantMetric("_commit_time", "us");
+  }
+
   virtual void CleanUpAndEndTest() { EndTest(); }
 
   virtual bool CleanUpStarted() { return false; }
@@ -106,14 +113,12 @@
   virtual void BuildTree() {}
 
   void AfterTest() override {
-    CHECK(!test_name_.empty()) << "Must SetTestName() before AfterTest().";
-    perf_test::PrintResult("layer_tree_host_frame_time", "", test_name_,
-                           draw_timer_.TimePerLap().InMicrosecondsF(), "us",
-                           true);
+    CHECK(reporter_) << "Must SetUpReporter() before AfterTest().";
+    reporter_->AddResult("_frame_time",
+                         draw_timer_.TimePerLap().InMicrosecondsF());
     if (measure_commit_cost_) {
-      perf_test::PrintResult("layer_tree_host_commit_time", "", test_name_,
-                             commit_timer_.TimePerLap().InMicrosecondsF(), "us",
-                             true);
+      reporter_->AddResult("_commit_time",
+                           commit_timer_.TimePerLap().InMicrosecondsF());
     }
   }
 
@@ -121,7 +126,7 @@
   base::LapTimer draw_timer_;
   base::LapTimer commit_timer_;
 
-  std::string test_name_;
+  std::unique_ptr<perf_test::PerfResultReporter> reporter_;
   FakeContentLayerClient fake_content_layer_client_;
   bool full_damage_each_frame_;
   bool begin_frame_driven_drawing_;
@@ -136,10 +141,6 @@
       : LayerTreeHostPerfTest() {
   }
 
-  void SetTestName(const std::string& name) {
-    test_name_ = name;
-  }
-
   void ReadTestFile(const std::string& name) {
     base::FilePath test_data_dir;
     ASSERT_TRUE(
@@ -171,7 +172,7 @@
 #define MAYBE_TenTenSingleThread TenTenSingleThread
 #endif
 TEST_F(LayerTreeHostPerfTestJsonReader, MAYBE_TenTenSingleThread) {
-  SetTestName("10_10_single_thread");
+  SetUpReporter("10_10_single_thread");
   ReadTestFile("10_10_layer_tree");
   RunTest(CompositorMode::SINGLE_THREADED);
 }
@@ -183,7 +184,7 @@
 #define MAYBE_TenTenThreaded TenTenThreaded
 #endif
 TEST_F(LayerTreeHostPerfTestJsonReader, MAYBE_TenTenThreaded) {
-  SetTestName("10_10_threaded_impl_side");
+  SetUpReporter("10_10_threaded_impl_side");
   ReadTestFile("10_10_layer_tree");
   RunTest(CompositorMode::THREADED);
 }
@@ -192,14 +193,14 @@
 TEST_F(LayerTreeHostPerfTestJsonReader,
        TenTenSingleThread_FullDamageEachFrame) {
   full_damage_each_frame_ = true;
-  SetTestName("10_10_single_thread_full_damage_each_frame");
+  SetUpReporter("10_10_single_thread_full_damage_each_frame");
   ReadTestFile("10_10_layer_tree");
   RunTest(CompositorMode::SINGLE_THREADED);
 }
 
 TEST_F(LayerTreeHostPerfTestJsonReader, TenTenThreaded_FullDamageEachFrame) {
   full_damage_each_frame_ = true;
-  SetTestName("10_10_threaded_impl_side_full_damage_each_frame");
+  SetUpReporter("10_10_threaded_impl_side_full_damage_each_frame");
   ReadTestFile("10_10_layer_tree");
   RunTest(CompositorMode::THREADED);
 }
@@ -233,14 +234,14 @@
 // Simulates a tab switcher scene with two stacks of 10 tabs each. Invalidate a
 // property on a leaf layer in the tree every commit.
 TEST_F(LayerTreeHostPerfTestLeafInvalidates, TenTenSingleThread) {
-  SetTestName("10_10_single_thread_leaf_invalidates");
+  SetUpReporter("10_10_single_thread_leaf_invalidates");
   ReadTestFile("10_10_layer_tree");
   RunTest(CompositorMode::SINGLE_THREADED);
 }
 
 // Timed out on Android: http://crbug.com/723821
 TEST_F(LayerTreeHostPerfTestLeafInvalidates, MAYBE_TenTenThreaded) {
-  SetTestName("10_10_threaded_impl_side_leaf_invalidates");
+  SetUpReporter("10_10_threaded_impl_side_leaf_invalidates");
   ReadTestFile("10_10_layer_tree");
   RunTest(CompositorMode::THREADED);
 }
@@ -278,7 +279,7 @@
 #define MAYBE_LongScrollablePageSingleThread LongScrollablePageSingleThread
 #endif
 TEST_F(ScrollingLayerTreePerfTest, MAYBE_LongScrollablePageSingleThread) {
-  SetTestName("long_scrollable_page");
+  SetUpReporter("long_scrollable_page");
   ReadTestFile("long_scrollable_page");
   RunTest(CompositorMode::SINGLE_THREADED);
 }
@@ -290,7 +291,7 @@
 #define MAYBE_LongScrollablePageThreaded LongScrollablePageThreaded
 #endif
 TEST_F(ScrollingLayerTreePerfTest, MAYBE_LongScrollablePageThreaded) {
-  SetTestName("long_scrollable_page_threaded_impl_side");
+  SetUpReporter("long_scrollable_page_threaded_impl_side");
   ReadTestFile("long_scrollable_page");
   RunTest(CompositorMode::THREADED);
 }
@@ -382,7 +383,7 @@
 
 TEST_F(BrowserCompositorInvalidateLayerTreePerfTest, DenseBrowserUIThreaded) {
   measure_commit_cost_ = true;
-  SetTestName("dense_layer_tree");
+  SetUpReporter("dense_layer_tree");
   ReadTestFile("dense_layer_tree");
   RunTest(CompositorMode::THREADED);
 }
@@ -397,7 +398,7 @@
 TEST_F(LayerTreeHostPerfTestJsonReader, MAYBE_HeavyPageThreaded) {
   begin_frame_driven_drawing_ = true;
   measure_commit_cost_ = true;
-  SetTestName("heavy_page");
+  SetUpReporter("heavy_page");
   ReadTestFile("heavy_layer_tree");
   RunTest(CompositorMode::THREADED);
 }
diff --git a/chrome/android/features/ar/AndroidManifest.xml b/chrome/android/features/ar/AndroidManifest.xml
index 1ac4da4..8caf5cb8 100644
--- a/chrome/android/features/ar/AndroidManifest.xml
+++ b/chrome/android/features/ar/AndroidManifest.xml
@@ -10,7 +10,7 @@
     <dist:module
         dist:onDemand="true"
         dist:title="@string/ar_module_title">
-        <dist:fusing dist:include="false" />
+        <dist:fusing dist:include="true" />
     </dist:module>
 
     <application>
diff --git a/chrome/android/features/autofill_assistant/java/AndroidManifest.xml b/chrome/android/features/autofill_assistant/java/AndroidManifest.xml
index 5b3945c0..e0858a1 100644
--- a/chrome/android/features/autofill_assistant/java/AndroidManifest.xml
+++ b/chrome/android/features/autofill_assistant/java/AndroidManifest.xml
@@ -5,7 +5,7 @@
     <dist:module
         dist:onDemand="true"
         dist:title="@string/autofill_assistant_module_title">
-        <dist:fusing dist:include="false" />
+        <dist:fusing dist:include="true" />
     </dist:module>
 
     <application>
diff --git a/chrome/android/features/dev_ui/java/AndroidManifest.xml b/chrome/android/features/dev_ui/java/AndroidManifest.xml
index 0222e41ff..899f0c1 100644
--- a/chrome/android/features/dev_ui/java/AndroidManifest.xml
+++ b/chrome/android/features/dev_ui/java/AndroidManifest.xml
@@ -10,7 +10,7 @@
     <dist:module
         dist:onDemand="true"
         dist:title="@string/dev_ui_module_title">
-        <dist:fusing dist:include="false" />
+        <dist:fusing dist:include="true" />
     </dist:module>
 
     <application />
diff --git a/chrome/android/features/tab_ui/AndroidManifest.xml b/chrome/android/features/tab_ui/AndroidManifest.xml
index 468d7f1..af2cfe1c 100644
--- a/chrome/android/features/tab_ui/AndroidManifest.xml
+++ b/chrome/android/features/tab_ui/AndroidManifest.xml
@@ -11,7 +11,7 @@
     <dist:module
         dist:onDemand="true"
         dist:title="@string/tab_management_module_title">
-        <dist:fusing dist:include="false" />
+        <dist:fusing dist:include="true" />
     </dist:module>
 </manifest>
 
diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListRecyclerView.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListRecyclerView.java
index 014f7b6..4a01be6 100644
--- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListRecyclerView.java
+++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListRecyclerView.java
@@ -175,6 +175,10 @@
                 if (mRecyclerViewFooter != null) {
                     mRecyclerViewFooter.setVisibility(VISIBLE);
                 }
+                // TODO(crbug.com/972157): remove this band-aid after we know why GTS is invisible.
+                if (FeatureUtilities.isTabToGtsAnimationEnabled()) {
+                    requestLayout();
+                }
             }
         });
         if (!animate) mFadeInAnimator.end();
diff --git a/chrome/android/features/vr/java/AndroidManifest.xml b/chrome/android/features/vr/java/AndroidManifest.xml
index 388353f7..e0a0841 100644
--- a/chrome/android/features/vr/java/AndroidManifest.xml
+++ b/chrome/android/features/vr/java/AndroidManifest.xml
@@ -10,7 +10,7 @@
     <dist:module
       dist:instant="false"
       dist:title="@string/vr_module_title">
-      <dist:fusing dist:include="false" />
+      <dist:fusing dist:include="true" />
       <dist:delivery>
         <dist:install-time>
           <dist:conditions>
diff --git a/chrome/android/java/res/values/dimens.xml b/chrome/android/java/res/values/dimens.xml
index 3df97ca..e880180 100644
--- a/chrome/android/java/res/values/dimens.xml
+++ b/chrome/android/java/res/values/dimens.xml
@@ -125,9 +125,8 @@
     <dimen name="contextual_search_term_caption_spacing">
         @dimen/overlay_panel_caption_spacing
     </dimen>
-    <dimen name="contextual_search_padded_button_width">36dp</dimen>
-    <dimen name="contextual_search_end_buttons_width">
-      @dimen/overlay_panel_end_buttons_width
+    <dimen name="contextual_search_padded_button_width">
+      @dimen/overlay_panel_padded_button_width
     </dimen>
     <dimen name="contextual_search_divider_line_width">1dp</dimen>
     <dimen name="contextual_search_divider_line_height">24dp</dimen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
index da75293..16249f3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanel.java
@@ -646,11 +646,12 @@
 
     /**
      * @param x The x coordinate in dp.
-     * @return Whether the given |x| coordinate is inside the close button.
+     * @return Whether the given |x| coordinate is inside the open-in-new-tab button.
      */
     protected boolean isCoordinateInsideOpenTabButton(float x) {
-        float width = getOpenTabIconDimension() + 2 * mButtonPaddingDps;
-        return getOpenTabIconX() - mButtonPaddingDps <= x && x <= getOpenTabIconX() + width;
+        // Calculation is the same for RTL: within the button plus padding.
+        return getOpenTabIconX() - mButtonPaddingDps <= x
+                && x <= getOpenTabIconX() + getOpenTabIconDimension() + mButtonPaddingDps;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java
index 1b2af7d..bc4ebf6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/OverlayPanelBase.java
@@ -375,14 +375,14 @@
     private boolean mIsMaximized;
 
     /**
-     * @return The vertical offset of the Overlay Panel.
+     * @return The horizontal offset of the Overlay Panel in DPs.
      */
     public float getOffsetX() {
         return mOffsetX;
     }
 
     /**
-     * @return The vertical offset of the Overlay Panel.
+     * @return The vertical offset of the Overlay Panel in DPs.
      */
     public float getOffsetY() {
         return mOffsetY;
@@ -548,7 +548,7 @@
     }
 
     /**
-     * @return The width/height of the open tab icon.
+     * @return The width/height of the open tab icon in DPs.
      */
     public float getOpenTabIconDimension() {
         if (mOpenTabIconWidth == 0) {
@@ -560,7 +560,7 @@
     }
 
     /**
-     * @return The left X coordinate of the open new tab icon.
+     * @return The left X coordinate of the open new tab icon in DPs.
      */
     public float getOpenTabIconX() {
         float offset = getCloseIconDimension() + 2 * mButtonPaddingDps;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchBarControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchBarControl.java
index 3740d30..90a3b20 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchBarControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchBarControl.java
@@ -60,12 +60,9 @@
      */
     private final ContextualSearchCardIconControl mCardIconControl;
 
-    /** The width of our icons, including padding, in pixels. */
+    /** The width of our icon, including padding, in pixels. */
     private final float mPaddedIconWidthPx;
 
-    /** The start offset of the middle icon in the Bar, in pixels. */
-    private final int mMiddleIconStartPx;
-
     /**
      * The {@link ContextualSearchImageControl} for the panel.
      */
@@ -170,17 +167,11 @@
         mDividerLineColor = ApiCompatibilityUtils.getColor(
                 context.getResources(), R.color.contextual_search_divider_line_color);
 
-        int endButtonsWidthDimension =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                ? R.dimen.contextual_search_end_buttons_width
-                : R.dimen.contextual_search_padded_button_width;
-        mEndButtonWidth = context.getResources().getDimension(endButtonsWidthDimension)
-                + context.getResources().getDimension(R.dimen.overlay_panel_button_padding);
-        // Number of pixels from the end of the Bar.
-        mMiddleIconStartPx = context.getResources().getDimensionPixelSize(
-                R.dimen.overlay_panel_end_buttons_width);
+        // Icon attributes.
         mPaddedIconWidthPx =
-                context.getResources().getDimension(R.dimen.overlay_panel_padded_button_width);
+                context.getResources().getDimension(R.dimen.contextual_search_padded_button_width);
+        mEndButtonWidth = mPaddedIconWidthPx
+                + context.getResources().getDimension(R.dimen.overlay_panel_button_padding);
         mDpToPx = context.getResources().getDisplayMetrics().density;
     }
 
@@ -577,33 +568,31 @@
     private void classifyTouchLocation(float xPx) {
         assert ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT);
 
-        // There are 4 cases:
-        // 1) The whole Bar minus icons (when 2 icons present)
-        // 2) The middle icon
-        // 3) The end icon
-        // 4) The whole Bar (without any icons)
+        // There are 3 cases:
+        // 1) The whole Bar minus icon (when the icon is present)
+        // 2) The icon
+        // 3) The whole Bar (without any icons)
         boolean wereIconsVisibleOnTouch = !mContextualSearchPanel.isPeeking();
         int panelWidth = mContextualSearchPanel.getContentViewWidthPx();
         if (wereIconsVisibleOnTouch) {
-            int barWidthMinusIcons = panelWidth - mMiddleIconStartPx;
-            if (xPx < barWidthMinusIcons) {
-                // Case 1 - whole Bar minus icons.
+            float iconOffsetPx = (mContextualSearchPanel.getOpenTabIconX()
+                                         - mContextualSearchPanel.getButtonPaddingDps())
+                    * mDpToPx;
+            if (xPx < iconOffsetPx) {
+                // Case 1 - whole Bar minus icon.
                 mTouchHighlightXOffsetPx = 0;
-                mTouchHighlightWidthPx = barWidthMinusIcons;
-            } else if (xPx < barWidthMinusIcons + mPaddedIconWidthPx) {
-                // Case 2 - middle icon.
-                mTouchHighlightXOffsetPx = barWidthMinusIcons;
-                mTouchHighlightWidthPx = mPaddedIconWidthPx;
+                mTouchHighlightWidthPx = iconOffsetPx;
             } else {
-                // Case 3 - end icon.
-                mTouchHighlightXOffsetPx = barWidthMinusIcons + mPaddedIconWidthPx;
-                mTouchHighlightWidthPx = panelWidth - mTouchHighlightXOffsetPx;
+                // Case 2 - the icon.
+                mTouchHighlightXOffsetPx = iconOffsetPx;
+                mTouchHighlightWidthPx = panelWidth - iconOffsetPx;
             }
         } else {
-            // Case 4 - whole Bar.
+            // Case 3 - whole Bar.
             mTouchHighlightXOffsetPx = 0;
             mTouchHighlightWidthPx = panelWidth;
         }
+        // If RTL then width is correct, just move offset to the other side of the panel.
         if (LocalizationUtils.isLayoutRtl()) {
             mTouchHighlightXOffsetPx = panelWidth - mTouchHighlightXOffsetPx;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchCaptionControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchCaptionControl.java
index 774b758..ecc75e7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchCaptionControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchCaptionControl.java
@@ -90,13 +90,8 @@
     public ContextualSearchCaptionControl(OverlayPanel panel, Context context, ViewGroup container,
             DynamicResourceLoader resourceLoader, boolean shouldShowExpandedCaption) {
         super(panel, R.layout.contextual_search_caption_view, R.id.contextual_search_caption_view,
-                context, container, resourceLoader,
-                (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                                ? R.dimen.contextual_search_end_padding
-                                : R.dimen.overlay_panel_padded_button_width),
-                (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                                ? R.dimen.contextual_search_end_buttons_width
-                                : R.dimen.overlay_panel_padded_button_width));
+                context, container, resourceLoader, R.dimen.contextual_search_padded_button_width,
+                R.dimen.contextual_search_padded_button_width);
         mShouldShowExpandedCaption = shouldShowExpandedCaption;
     }
 
@@ -266,8 +261,9 @@
 
         if (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)) {
             animateTransitionIn();
-        } else if (!mShowingExpandedCaption)
+        } else if (!mShowingExpandedCaption) {
             animateTransitionIn();
+        }
     }
 
     // ============================================================================================
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchContextControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchContextControl.java
index 48da9104..53bcbce 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchContextControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchContextControl.java
@@ -10,7 +10,6 @@
 import android.widget.TextView;
 
 import org.chromium.chrome.R;
-import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
 import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelRepaddingTextView;
 import org.chromium.ui.resources.dynamics.DynamicResourceLoader;
@@ -40,13 +39,8 @@
                                           ViewGroup container,
                                           DynamicResourceLoader resourceLoader) {
         super(panel, R.layout.contextual_search_context_view, R.id.contextual_search_context_view,
-                context, container, resourceLoader,
-                (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                                ? R.dimen.contextual_search_end_padding
-                                : R.dimen.overlay_panel_padded_button_width),
-                (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                                ? R.dimen.contextual_search_end_buttons_width
-                                : R.dimen.overlay_panel_padded_button_width));
+                context, container, resourceLoader, R.dimen.contextual_search_padded_button_width,
+                R.dimen.contextual_search_padded_button_width);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
index 5c6c6a369..805439c8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java
@@ -94,13 +94,9 @@
                 ApiCompatibilityUtils
                         .getDrawable(mContext.getResources(), R.drawable.modern_toolbar_shadow)
                         .getIntrinsicHeight();
-        // We may have 1 or 2 buttons depending on old/new layout.
-        int endButtonsWidthDimension =
-                ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                ? R.dimen.contextual_search_end_buttons_width
-                : R.dimen.contextual_search_padded_button_width;
-        mEndButtonWidthDp =
-                mPxToDp * mContext.getResources().getDimensionPixelSize(endButtonsWidthDimension);
+        mEndButtonWidthDp = mContext.getResources().getDimensionPixelSize(
+                                    R.dimen.contextual_search_padded_button_width)
+                * mPxToDp;
     }
 
     @Override
@@ -282,6 +278,8 @@
                             || ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
                                     && isCoordinateInsideOpenTabButton(x))) {
                 mManagementDelegate.promoteToTab();
+            } else {
+                peekPanel(StateChangeReason.UNKNOWN);
             }
         }
     }
@@ -353,6 +351,32 @@
     }
 
     @Override
+    public float getOpenTabIconX() {
+        if (!ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)) {
+            return super.getOpenTabIconX();
+        } else {
+            if (LocalizationUtils.isLayoutRtl()) {
+                return getOffsetX() + getBarMarginSide();
+            } else {
+                return getOffsetX() + getWidth() - getBarMarginSide() - getCloseIconDimension();
+            }
+        }
+    }
+
+    @Override
+    protected boolean isCoordinateInsideCloseButton(float x) {
+        if (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)) return false;
+
+        return super.isCoordinateInsideCloseButton(x);
+    }
+
+    @Override
+    protected boolean isCoordinateInsideOpenTabButton(float x) {
+        return getOpenTabIconX() - getButtonPaddingDps() <= x
+                && x <= getOpenTabIconX() + getOpenTabIconDimension() + getButtonPaddingDps();
+    }
+
+    @Override
     public float getContentY() {
         return getOffsetY() + getBarContainerHeight() + getPromoHeightPx() * mPxToDp;
     }
@@ -630,6 +654,11 @@
         return new Rect(left, top, right, bottom);
     }
 
+    /** @return The padding used for each side of the button in the Bar. */
+    public float getButtonPaddingDps() {
+        return mButtonPaddingDps;
+    }
+
     // ============================================================================================
     // Panel Metrics
     // ============================================================================================
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchTermControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchTermControl.java
index 4a0e263..990f285 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchTermControl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchTermControl.java
@@ -39,9 +39,7 @@
                 (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
                                 ? R.dimen.contextual_search_end_padding
                                 : R.dimen.overlay_panel_padded_button_width),
-                (ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
-                                ? R.dimen.contextual_search_end_buttons_width
-                                : R.dimen.overlay_panel_padded_button_width));
+                R.dimen.contextual_search_padded_button_width);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/ContextualSearchSceneLayer.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/ContextualSearchSceneLayer.java
index 6337b8c..033fd18 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/ContextualSearchSceneLayer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/scene_layer/ContextualSearchSceneLayer.java
@@ -150,23 +150,25 @@
         int panelShadowResourceId = panel.getPanelShadowVisible()
                 ? R.drawable.contextual_search_bar_background
                 : INVALID_RESOURCE_ID;
+        int closeIconResourceId = ChromeFeatureList.isEnabled(ChromeFeatureList.OVERLAY_NEW_LAYOUT)
+                ? INVALID_RESOURCE_ID
+                : ContextualSearchPanel.CLOSE_ICON_DRAWABLE_ID;
 
         ContextualSearchSceneLayerJni.get().updateContextualSearchLayer(mNativePtr,
                 ContextualSearchSceneLayer.this, panelShadowResourceId, searchBarBackgroundColor,
                 searchContextViewId, searchTermViewId, searchCaptionViewId,
                 R.drawable.modern_toolbar_shadow, R.drawable.ic_logo_googleg_24dp,
                 quickActionIconResId, R.drawable.breadcrumb_arrow, dragHandlebarId,
-                openNewTabIconId, ContextualSearchPanel.CLOSE_ICON_DRAWABLE_ID,
-                R.drawable.progress_bar_background, R.drawable.progress_bar_foreground,
-                searchPromoViewId, R.drawable.contextual_search_promo_ripple,
-                searchBarBannerTextViewId, mDpToPx, panel.getFullscreenWidth() * mDpToPx,
-                panel.getTabHeight() * mDpToPx, panel.getBasePageBrightness(),
-                panel.getBasePageY() * mDpToPx, panelWebContents, searchPromoVisible,
-                searchPromoHeightPx, searchPromoOpacity, searchPromoBackgroundColor,
-                searchBarBannerVisible, searchBarBannerHeightPx, searchBarBannerPaddingPx,
-                searchBarBannerRippleWidthPx, searchBarBannerRippleOpacity,
-                searchBarBannerTextOpacity, searchPanelX * mDpToPx, searchPanelY * mDpToPx,
-                searchPanelWidth * mDpToPx, searchPanelHeight * mDpToPx,
+                openNewTabIconId, closeIconResourceId, R.drawable.progress_bar_background,
+                R.drawable.progress_bar_foreground, searchPromoViewId,
+                R.drawable.contextual_search_promo_ripple, searchBarBannerTextViewId, mDpToPx,
+                panel.getFullscreenWidth() * mDpToPx, panel.getTabHeight() * mDpToPx,
+                panel.getBasePageBrightness(), panel.getBasePageY() * mDpToPx, panelWebContents,
+                searchPromoVisible, searchPromoHeightPx, searchPromoOpacity,
+                searchPromoBackgroundColor, searchBarBannerVisible, searchBarBannerHeightPx,
+                searchBarBannerPaddingPx, searchBarBannerRippleWidthPx,
+                searchBarBannerRippleOpacity, searchBarBannerTextOpacity, searchPanelX * mDpToPx,
+                searchPanelY * mDpToPx, searchPanelWidth * mDpToPx, searchPanelHeight * mDpToPx,
                 searchBarMarginSide * mDpToPx, searchBarMarginTop * mDpToPx,
                 searchBarHeight * mDpToPx, searchContextOpacity,
                 searchBarControl.getTextLayerMinHeight(), searchTermOpacity,
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
index 696aa67..9d8e9ac 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtils.java
@@ -21,6 +21,8 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.base.task.TaskTraits;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
@@ -42,6 +44,7 @@
 import org.chromium.components.offline_items_collection.LaunchLocation;
 import org.chromium.components.offlinepages.SavePageResult;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.net.NetworkChangeNotifier;
 import org.chromium.ui.base.PageTransition;
@@ -361,20 +364,49 @@
         return true;
     }
 
+    private static void getOfflinePageUriForSharing(String tabUrl, boolean isPageTemporary,
+            String offlinePagePath, Callback<Uri> callback) {
+        // Ensure that we have a file path that is longer than just "/".
+        if (isPageTemporary && offlinePagePath.length() > 1) {
+            // We share temporary pages by content URI to prevent unanticipated side effects in the
+            // public directory.
+
+            // Avoid file access (getContentUriFromFile()) from UI thread.
+            PostTask.postTask(TaskTraits.USER_VISIBLE_MAY_BLOCK, () -> {
+                File file = new File(offlinePagePath);
+                // We might get an exception if chrome does not have sharing roots configured.  If
+                // so, just share by URL of the original page instead of sharing the offline page.
+                Uri uri;
+                try {
+                    uri = (new FileProviderHelper()).getContentUriFromFile(file);
+                } catch (Exception e) {
+                    uri = Uri.parse(tabUrl);
+                }
+                final Uri finalUri = uri;
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onResult(finalUri));
+            });
+        } else {
+            callback.onResult(Uri.parse(tabUrl));
+        }
+    }
+
     /**
      * If possible, creates the ShareParams needed to share the current offline page loaded in the
      * provided tab as a MHTML file.
      *
      * @param activity The activity used for sharing and file provider interaction.
      * @param currentTab The current tab from which the page is being shared.
-     * @param shareCallback The callback to be used to send the ShareParams. This will only be
-     *                      called if this function call returns true.
-     * @return true if the sharing of the page is possible.  The callback will be invoked if
-     *                      publishing the page succeeds.
+     * @param shareCallback The callback invoked when either sharing is complete, or when sharing
+     *                      cannot be completed. If sharing cannot be done, the callback parameter
+     *                      is null. May either be invoked from within the function call, or
+     *                      afterwards via PostTask.
      */
-    public static boolean maybeShareOfflinePage(
+    public static void maybeShareOfflinePage(
             final Activity activity, Tab tab, final Callback<ShareParams> shareCallback) {
-        if (tab == null) return false;
+        if (tab == null) {
+            shareCallback.onResult(null);
+            return;
+        }
 
         boolean isOfflinePage = OfflinePageUtils.isOfflinePage(tab);
         RecordHistogram.recordBooleanHistogram("OfflinePages.SharedPageWasOffline", isOfflinePage);
@@ -383,9 +415,13 @@
         // sharing.
         if (!isOfflinePage) {
             if (ChromeFeatureList.isEnabled(ChromeFeatureList.OFFLINE_PAGES_LIVE_PAGE_SHARING)) {
-                return saveAndSharePage(activity, tab, shareCallback);
+                if (!saveAndSharePage(activity, tab, shareCallback)) {
+                    shareCallback.onResult(null);
+                }
+                return;
             } else {
-                return false;
+                shareCallback.onResult(null);
+                return;
             }
         }
 
@@ -393,61 +429,65 @@
 
         if (offlinePageBridge == null) {
             Log.e(TAG, "Unable to share current tab as an offline page.");
-            return false;
+            shareCallback.onResult(null);
+            return;
         }
 
         WebContents webContents = tab.getWebContents();
-        if (webContents == null) return false;
+        if (webContents == null) {
+            shareCallback.onResult(null);
+            return;
+        }
 
         OfflinePageItem offlinePage = offlinePageBridge.getOfflinePage(webContents);
-        if (offlinePage == null) return false;
+        if (offlinePage == null) {
+            shareCallback.onResult(null);
+            return;
+        }
 
         String offlinePath = offlinePage.getFilePath();
 
-        final String pageUrl = tab.getUrl();
-        // We share temporary pages by content URI to prevent unanticipated side effects in the
-        // public directory.
-        Uri uri;
         boolean isPageTemporary =
                 offlinePageBridge.isTemporaryNamespace(offlinePage.getClientId().getNamespace());
-        // Ensure that we have a file path that is longer than just "/".
-        if (isPageTemporary && offlinePath.length() > 1) {
-            File file = new File(offlinePath);
-            // We might get an exception if chrome does not have sharing roots configured.  If so,
-            // just share by URL of the original page instead of sharing the offline page.
-            try {
-                uri = (new FileProviderHelper()).getContentUriFromFile(file);
-            } catch (Exception e) {
-                uri = Uri.parse(pageUrl);
-            }
-        } else {
-            uri = Uri.parse(pageUrl);
+        String tabTitle = tab.getTitle();
+        getOfflinePageUriForSharing(tab.getUrl(), isPageTemporary, offlinePath,
+                (Uri uri)
+                        -> maybeShareOfflinePageWithUri(activity, tabTitle, webContents,
+                                offlinePageBridge, offlinePage, isPageTemporary, shareCallback,
+                                uri));
+    }
+
+    // A continuation of maybeShareOfflinePage, after the URI is determined in a background thread.
+    private static void maybeShareOfflinePageWithUri(Activity activity, String tabTitle,
+            WebContents webContents, OfflinePageBridge offlinePageBridge,
+            OfflinePageItem offlinePage, boolean isPageTemporary,
+            Callback<ShareParams> shareCallback, Uri uri) {
+        if (!isOfflinePageShareable(offlinePageBridge, offlinePage, uri)) {
+            shareCallback.onResult(null);
+            return;
         }
 
-        if (!isOfflinePageShareable(offlinePageBridge, offlinePage, uri)) return false;
-
+        String offlinePath = offlinePage.getFilePath();
         if (isPageTemporary || !offlinePageBridge.isInPrivateDirectory(offlinePath)) {
             // Share temporary pages and pages already in a public location.
-            final String pageTitle = tab.getTitle();
             final File offlinePageFile = new File(offlinePath);
-            sharePage(activity, uri.toString(), pageTitle, offlinePath, offlinePageFile,
+            sharePage(activity, uri.toString(), tabTitle, offlinePath, offlinePageFile,
                     shareCallback);
-            return true;
+            return;
         }
 
-        // The file access permission is needed since we may need to publish the archive file
-        // if it resides in internal directory.
+        // The file access permission is needed since we may need to publish the archive
+        // file if it resides in internal directory.
         offlinePageBridge.acquireFileAccessPermission(webContents, (granted) -> {
             if (!granted) {
                 recordPublishPageResult(SavePageResult.PERMISSION_DENIED);
                 return;
             }
 
-            // If the page is not in a public location, we must publish it before sharing it.
+            // If the page is not in a public location, we must publish it before
+            // sharing it.
             publishThenShareInternalPage(activity, offlinePageBridge, offlinePage, shareCallback);
         });
-
-        return true;
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareMenuActionHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareMenuActionHandler.java
index 3f0ea14..a5c5fbd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareMenuActionHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareMenuActionHandler.java
@@ -156,28 +156,32 @@
             tabObserver.onActionPerformedAfterScreenshot(
                     ScreenshotTabObserver.SCREENSHOT_ACTION_SHARE);
         }
-        if (OfflinePageUtils.maybeShareOfflinePage(
-                    activity, currentTab, (ShareParams p) -> mDelegate.share(p))) {
-            return;
-        }
 
-        if (shouldFetchCanonicalUrl(currentTab)) {
-            WebContents webContents = currentTab.getWebContents();
-            String title = currentTab.getTitle();
-            String visibleUrl = currentTab.getUrl();
-            webContents.getMainFrame().getCanonicalUrlForSharing(new Callback<String>() {
-                @Override
-                public void onResult(String result) {
-                    logCanonicalUrlResult(visibleUrl, result);
+        OfflinePageUtils.maybeShareOfflinePage(activity, currentTab, (ShareParams p) -> {
+            if (p != null) {
+                mDelegate.share(p);
+            } else {
+                // Could not share as an offline page.
+                if (shouldFetchCanonicalUrl(currentTab)) {
+                    WebContents webContents = currentTab.getWebContents();
+                    String title = currentTab.getTitle();
+                    String visibleUrl = currentTab.getUrl();
+                    webContents.getMainFrame().getCanonicalUrlForSharing(new Callback<String>() {
+                        @Override
+                        public void onResult(String result) {
+                            logCanonicalUrlResult(visibleUrl, result);
 
-                    triggerShareWithCanonicalUrlResolved(activity, webContents, title, visibleUrl,
-                            result, shareDirectly, isIncognito);
+                            triggerShareWithCanonicalUrlResolved(activity, webContents, title,
+                                    visibleUrl, result, shareDirectly, isIncognito);
+                        }
+                    });
+                } else {
+                    triggerShareWithCanonicalUrlResolved(activity, currentTab.getWebContents(),
+                            currentTab.getTitle(), currentTab.getUrl(), null, shareDirectly,
+                            isIncognito);
                 }
-            });
-        } else {
-            triggerShareWithCanonicalUrlResolved(activity, currentTab.getWebContents(),
-                    currentTab.getTitle(), currentTab.getUrl(), null, shareDirectly, isIncognito);
-        }
+            }
+        });
     }
 
     private void triggerShareWithCanonicalUrlResolved(final Activity mainActivity,
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java
index 6672e6d8..e13e794 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java
@@ -67,7 +67,7 @@
         mFloats.put(org.chromium.chrome.R.dimen.overlay_panel_bar_height, 56.f);
         mFloats.put(org.chromium.chrome.R.dimen.control_container_height, 56.f);
         mFloats.put(org.chromium.chrome.R.dimen.contextual_search_bar_banner_padding, 12.f);
-        mFloats.put(org.chromium.chrome.R.dimen.contextual_search_end_buttons_width, 48.f);
+        mFloats.put(org.chromium.chrome.R.dimen.contextual_search_padded_button_width, 48.f);
         mFloats.put(org.chromium.chrome.R.dimen.overlay_panel_end_buttons_width, 94.f);
         mFloats.put(org.chromium.chrome.R.dimen.toolbar_height_no_shadow, 56.f);
         mIntegers.put(R.color.modern_grey_100, 0xFFF1F3F4);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
index 9356b35..b8fc295 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/offlinepages/OfflinePageUtilsTest.java
@@ -274,11 +274,8 @@
         final TestShareCallback shareCallback = new TestShareCallback(semaphore);
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            boolean shared = OfflinePageUtils.maybeShareOfflinePage(mActivityTestRule.getActivity(),
+            OfflinePageUtils.maybeShareOfflinePage(mActivityTestRule.getActivity(),
                     mActivityTestRule.getActivity().getActivityTab(), shareCallback);
-            // Attempt to share a public page should pass the initial checks and return true,
-            // which means the callback will be called.
-            Assert.assertTrue(shared);
         });
 
         // Wait for share callback to get called.
@@ -299,10 +296,8 @@
         final TestShareCallback shareCallback = new TestShareCallback(semaphore);
 
         TestThreadUtils.runOnUiThreadBlocking(() -> {
-            boolean shared = OfflinePageUtils.maybeShareOfflinePage(mActivityTestRule.getActivity(),
+            OfflinePageUtils.maybeShareOfflinePage(mActivityTestRule.getActivity(),
                     mActivityTestRule.getActivity().getActivityTab(), shareCallback);
-            // The attempt to share a temporary page should share a content URL.
-            Assert.assertTrue(shared);
         });
         // Wait for share callback to get called.
         Assert.assertTrue(semaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/chrome/android/modules/test_dummy/internal/java/AndroidManifest.xml b/chrome/android/modules/test_dummy/internal/java/AndroidManifest.xml
index 38e95645b..1413145 100644
--- a/chrome/android/modules/test_dummy/internal/java/AndroidManifest.xml
+++ b/chrome/android/modules/test_dummy/internal/java/AndroidManifest.xml
@@ -9,8 +9,8 @@
     <dist:module
         dist:onDemand="true"
         dist:title="@string/test_dummy_module_title">
-        <dist:fusing dist:include="false" />
+        <dist:fusing dist:include="true" />
     </dist:module>
 
     <application />
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/chrome/app/chrome_content_browser_overlay_manifest.cc b/chrome/app/chrome_content_browser_overlay_manifest.cc
index 4a7274e..b95f0e6 100644
--- a/chrome/app/chrome_content_browser_overlay_manifest.cc
+++ b/chrome/app/chrome_content_browser_overlay_manifest.cc
@@ -71,8 +71,8 @@
 
 #if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) || \
     defined(OS_CHROMEOS)
-#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"  // nogncheck
 #include "chrome/browser/ui/webui/discards/discards.mojom.h"
+#include "chrome/browser/ui/webui/discards/webui_graph_dump.mojom.h"
 #endif
 
 #if BUILDFLAG(ENABLE_EXTENSIONS)
diff --git a/chrome/app/chrome_main_mac.mm b/chrome/app/chrome_main_mac.mm
index fc34e35..6196f18 100644
--- a/chrome/app/chrome_main_mac.mm
+++ b/chrome/app/chrome_main_mac.mm
@@ -12,7 +12,6 @@
 #include "base/logging.h"
 #import "base/mac/bundle_locations.h"
 #import "base/mac/foundation_util.h"
-#import "base/mac/scoped_nsautorelease_pool.h"
 #include "base/path_service.h"
 #include "base/strings/sys_string_conversions.h"
 #include "chrome/common/chrome_constants.h"
@@ -20,19 +19,18 @@
 #include "content/public/common/content_paths.h"
 
 void SetUpBundleOverrides() {
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    base::mac::SetOverrideFrameworkBundlePath(chrome::GetFrameworkBundlePath());
 
-  base::mac::SetOverrideFrameworkBundlePath(chrome::GetFrameworkBundlePath());
+    NSBundle* base_bundle = chrome::OuterAppBundle();
+    base::mac::SetBaseBundleID([[base_bundle bundleIdentifier] UTF8String]);
 
-  NSBundle* base_bundle = chrome::OuterAppBundle();
-  base::mac::SetBaseBundleID([[base_bundle bundleIdentifier] UTF8String]);
+    base::FilePath child_exe_path =
+        chrome::GetFrameworkBundlePath().Append("Helpers").Append(
+            chrome::kHelperProcessExecutablePath);
 
-  base::FilePath child_exe_path =
-      chrome::GetFrameworkBundlePath()
-          .Append("Helpers")
-          .Append(chrome::kHelperProcessExecutablePath);
-
-  // On the Mac, the child executable lives at a predefined location within
-  // the app bundle's versioned directory.
-  base::PathService::Override(content::CHILD_PROCESS_EXE, child_exe_path);
+    // On the Mac, the child executable lives at a predefined location within
+    // the app bundle's versioned directory.
+    base::PathService::Override(content::CHILD_PROCESS_EXE, child_exe_path);
+  }
 }
diff --git a/chrome/app/settings_strings.grdp b/chrome/app/settings_strings.grdp
index c7d97db..48aa744 100644
--- a/chrome/app/settings_strings.grdp
+++ b/chrome/app/settings_strings.grdp
@@ -5205,9 +5205,6 @@
   <message name="IDS_SETTINGS_SECURITY_KEYS_PIN" desc="A label shown above a text box where the user is expected to enter a new PIN for a security key. PINs, in this context, are short, often numeric codes that are often used with, for example, ATM cards. Security keys are external devices used to authenticate people.">
     PIN
   </message>
-  <message name="IDS_SETTINGS_SECURITY_KEYS_PIN_ERROR_TOO_SHORT" desc="A validation error shown beneath a text box where the user is expected to enter a PIN for a security key. PINs, in this context, are short, often numeric codes that are often used with, for example, ATM cards. Security keys are external devices used to authenticate people.">
-    PIN must be at least 4 characters
-  </message>
   <message name="IDS_SETTINGS_SECURITY_KEYS_PIN_ERROR_TOO_SHORT_SMALL" desc="A validation error shown beneath a text box where the user is expected to enter a PIN for a security key. PINs, in this context, are short, often numeric codes that are often used with, for example, ATM cards. Security keys are external devices used to authenticate people. This message has limited horizontal space.">
     Too short
   </message>
diff --git a/chrome/app_shim/app_mode_loader_mac.mm b/chrome/app_shim/app_mode_loader_mac.mm
index 357700ac..b937c24 100644
--- a/chrome/app_shim/app_mode_loader_mac.mm
+++ b/chrome/app_shim/app_mode_loader_mac.mm
@@ -18,7 +18,6 @@
 #include "base/logging.h"
 #include "base/mac/foundation_util.h"
 #import "base/mac/launch_services_util.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/process/launch.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
@@ -38,165 +37,166 @@
   using base::mac::CFCastStrict;
   using base::mac::NSToCFCast;
 
-  base::mac::ScopedNSAutoreleasePool scoped_pool;
+  @autoreleasepool {
+    // Get the current main bundle, i.e., that of the app loader that's running.
+    NSBundle* app_bundle = [NSBundle mainBundle];
+    CHECK(app_bundle) << "couldn't get loader bundle";
 
-  // Get the current main bundle, i.e., that of the app loader that's running.
-  NSBundle* app_bundle = [NSBundle mainBundle];
-  CHECK(app_bundle) << "couldn't get loader bundle";
+    // ** 1: Get path to outer Chrome bundle.
+    // Get the bundle ID of the browser that created this app bundle.
+    NSString* cr_bundle_id = base::mac::ObjCCast<NSString>(
+        [app_bundle objectForInfoDictionaryKey:app_mode::kBrowserBundleIDKey]);
+    CHECK(cr_bundle_id) << "couldn't get browser bundle ID";
 
-  // ** 1: Get path to outer Chrome bundle.
-  // Get the bundle ID of the browser that created this app bundle.
-  NSString* cr_bundle_id = base::mac::ObjCCast<NSString>(
-      [app_bundle objectForInfoDictionaryKey:app_mode::kBrowserBundleIDKey]);
-  CHECK(cr_bundle_id) << "couldn't get browser bundle ID";
+    // First check if Chrome exists at the last known location.
+    base::FilePath cr_bundle_path;
+    NSString* cr_bundle_path_ns =
+        [CFToNSCast(CFCastStrict<CFStringRef>(CFPreferencesCopyAppValue(
+            NSToCFCast(app_mode::kLastRunAppBundlePathPrefsKey),
+            NSToCFCast(cr_bundle_id)))) autorelease];
+    cr_bundle_path = base::mac::NSStringToFilePath(cr_bundle_path_ns);
+    bool found_bundle =
+        !cr_bundle_path.empty() && base::DirectoryExists(cr_bundle_path);
 
-  // First check if Chrome exists at the last known location.
-  base::FilePath cr_bundle_path;
-  NSString* cr_bundle_path_ns =
-      [CFToNSCast(CFCastStrict<CFStringRef>(CFPreferencesCopyAppValue(
-          NSToCFCast(app_mode::kLastRunAppBundlePathPrefsKey),
-          NSToCFCast(cr_bundle_id)))) autorelease];
-  cr_bundle_path = base::mac::NSStringToFilePath(cr_bundle_path_ns);
-  bool found_bundle =
-      !cr_bundle_path.empty() && base::DirectoryExists(cr_bundle_path);
-
-  if (!found_bundle) {
-    // If no such bundle path exists, try to search by bundle ID.
-    if (!app_mode::FindBundleById(cr_bundle_id, &cr_bundle_path)) {
-      // TODO(jeremy): Display UI to allow user to manually locate the Chrome
-      // bundle.
-      LOG(FATAL) << "Failed to locate bundle by identifier";
+    if (!found_bundle) {
+      // If no such bundle path exists, try to search by bundle ID.
+      if (!app_mode::FindBundleById(cr_bundle_id, &cr_bundle_path)) {
+        // TODO(jeremy): Display UI to allow user to manually locate the Chrome
+        // bundle.
+        LOG(FATAL) << "Failed to locate bundle by identifier";
+      }
     }
-  }
 
-  // ** 2: Read the running Chrome version.
-  // The user_data_dir for shims actually contains the app_data_path.
-  // I.e. <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/
-  base::FilePath app_data_dir = base::mac::NSStringToFilePath([app_bundle
-      objectForInfoDictionaryKey:app_mode::kCrAppModeUserDataDirKey]);
-  base::FilePath user_data_dir = app_data_dir.DirName().DirName().DirName();
-  CHECK(!user_data_dir.empty());
+    // ** 2: Read the running Chrome version.
+    // The user_data_dir for shims actually contains the app_data_path.
+    // I.e. <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/
+    base::FilePath app_data_dir = base::mac::NSStringToFilePath([app_bundle
+        objectForInfoDictionaryKey:app_mode::kCrAppModeUserDataDirKey]);
+    base::FilePath user_data_dir = app_data_dir.DirName().DirName().DirName();
+    CHECK(!user_data_dir.empty());
 
-  // If the version file does not exist, |cr_version_str| will be empty and
-  // app_mode::GetChromeBundleInfo will default to the latest version.
-  base::FilePath cr_version_str;
-  base::ReadSymbolicLink(
-      user_data_dir.Append(app_mode::kRunningChromeVersionSymlinkName),
-      &cr_version_str);
+    // If the version file does not exist, |cr_version_str| will be empty and
+    // app_mode::GetChromeBundleInfo will default to the latest version.
+    base::FilePath cr_version_str;
+    base::ReadSymbolicLink(
+        user_data_dir.Append(app_mode::kRunningChromeVersionSymlinkName),
+        &cr_version_str);
 
-  // If the version file does exist, it may have been left by a crashed Chrome
-  // process. Ensure the process is still running.
-  if (!cr_version_str.empty()) {
-    NSArray* existing_chrome = [NSRunningApplication
-        runningApplicationsWithBundleIdentifier:cr_bundle_id];
-    if ([existing_chrome count] == 0)
-      cr_version_str.clear();
-  }
-
-  // ** 3: Read information from the Chrome bundle.
-  base::FilePath executable_path;
-  base::FilePath framework_path;
-  base::FilePath framework_dylib_path;
-  if (!app_mode::GetChromeBundleInfo(cr_bundle_path, cr_version_str.value(),
-                                     &executable_path, &framework_path,
-                                     &framework_dylib_path)) {
-    LOG(FATAL) << "Couldn't ready Chrome bundle info";
-  }
-  base::FilePath app_mode_bundle_path =
-      base::mac::NSStringToFilePath([app_bundle bundlePath]);
-
-  // ** 4: Read information from the Info.plist.
-  // Read information about the this app shortcut from the Info.plist.
-  // Don't check for null-ness on optional items.
-  NSDictionary* info_plist = [app_bundle infoDictionary];
-  CHECK(info_plist) << "couldn't get loader Info.plist";
-
-  const std::string app_mode_id = SysNSStringToUTF8(
-      [info_plist objectForKey:app_mode::kCrAppModeShortcutIDKey]);
-  CHECK(app_mode_id.size()) << "couldn't get app shortcut ID";
-
-  const std::string app_mode_name = SysNSStringToUTF8(
-      [info_plist objectForKey:app_mode::kCrAppModeShortcutNameKey]);
-  const std::string app_mode_url = SysNSStringToUTF8(
-      [info_plist objectForKey:app_mode::kCrAppModeShortcutURLKey]);
-
-  base::FilePath plist_user_data_dir = base::mac::NSStringToFilePath(
-      [info_plist objectForKey:app_mode::kCrAppModeUserDataDirKey]);
-
-  base::FilePath profile_dir = base::mac::NSStringToFilePath(
-      [info_plist objectForKey:app_mode::kCrAppModeProfileDirKey]);
-
-  // ** 5: Open the framework.
-  StartFun ChromeAppModeStart = NULL;
-  void* cr_dylib = dlopen(framework_dylib_path.value().c_str(), RTLD_LAZY);
-  if (cr_dylib) {
-    // Find the entry point.
-    ChromeAppModeStart =
-        (StartFun)dlsym(cr_dylib, APP_SHIM_ENTRY_POINT_NAME_STRING);
-    if (!ChromeAppModeStart)
-      LOG(ERROR) << "Couldn't get entry point: " << dlerror();
-  } else {
-    LOG(ERROR) << "Couldn't load framework: " << dlerror();
-  }
-
-  // ** 6: Fill in ChromeAppModeInfo and call into Chrome's framework.
-  if (ChromeAppModeStart) {
-    // Ensure that the strings pointed to by |info| outlive |info|.
-    const std::string framework_path_utf8 = framework_path.AsUTF8Unsafe();
-    const std::string cr_bundle_path_utf8 = cr_bundle_path.AsUTF8Unsafe();
-    const std::string app_mode_bundle_path_utf8 =
-        app_mode_bundle_path.AsUTF8Unsafe();
-    const std::string plist_user_data_dir_utf8 =
-        plist_user_data_dir.AsUTF8Unsafe();
-    const std::string profile_dir_utf8 = profile_dir.AsUTF8Unsafe();
-    app_mode::ChromeAppModeInfo info;
-    info.argc = argc;
-    info.argv = argv;
-    info.chrome_framework_path = framework_path_utf8.c_str();
-    info.chrome_outer_bundle_path = cr_bundle_path_utf8.c_str();
-    info.app_mode_bundle_path = app_mode_bundle_path_utf8.c_str();
-    info.app_mode_id = app_mode_id.c_str();
-    info.app_mode_name = app_mode_name.c_str();
-    info.app_mode_url = app_mode_url.c_str();
-    info.user_data_dir = plist_user_data_dir_utf8.c_str();
-    info.profile_dir = profile_dir_utf8.c_str();
-    return ChromeAppModeStart(&info);
-  }
-
-  LOG(ERROR) << "Loading Chrome failed, launching Chrome with command line";
-  base::CommandLine command_line(executable_path);
-  // The user_data_dir from the plist is actually the app data dir.
-  command_line.AppendSwitchPath(
-      switches::kUserDataDir,
-      plist_user_data_dir.DirName().DirName().DirName());
-  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
-          app_mode::kLaunchedByChromeProcessId)) {
-    // Pass --app-shim-error to have Chrome rebuild this shim.
-    // If Chrome has rebuilt this shim once already, then rebuilding doesn't fix
-    // the problem, so don't try again.
-    if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
-            app_mode::kLaunchedAfterRebuild)) {
-      command_line.AppendSwitchPath(app_mode::kAppShimError,
-                                    app_mode_bundle_path);
+    // If the version file does exist, it may have been left by a crashed Chrome
+    // process. Ensure the process is still running.
+    if (!cr_version_str.empty()) {
+      NSArray* existing_chrome = [NSRunningApplication
+          runningApplicationsWithBundleIdentifier:cr_bundle_id];
+      if ([existing_chrome count] == 0)
+        cr_version_str.clear();
     }
-  } else {
-    // If the shim was launched directly (instead of by Chrome), first ask
-    // Chrome to launch the app. Chrome will launch the shim again, the same
-    // error will occur and be handled above. This approach allows the app to be
-    // started without blocking on fixing the shim and guarantees that the
-    // profile is loaded when Chrome receives --app-shim-error.
-    command_line.AppendSwitchPath(switches::kProfileDirectory, profile_dir);
-    command_line.AppendSwitchASCII(switches::kAppId, app_mode_id);
-  }
-  // Launch the executable directly since base::mac::OpenApplicationWithPath
-  // doesn't pass command line arguments if the application is already running.
-  if (!base::LaunchProcess(command_line, base::LaunchOptions()).IsValid()) {
-    LOG(ERROR) << "Could not launch Chrome: "
-               << command_line.GetCommandLineString();
-    return 1;
-  }
 
-  return 0;
+    // ** 3: Read information from the Chrome bundle.
+    base::FilePath executable_path;
+    base::FilePath framework_path;
+    base::FilePath framework_dylib_path;
+    if (!app_mode::GetChromeBundleInfo(cr_bundle_path, cr_version_str.value(),
+                                       &executable_path, &framework_path,
+                                       &framework_dylib_path)) {
+      LOG(FATAL) << "Couldn't ready Chrome bundle info";
+    }
+    base::FilePath app_mode_bundle_path =
+        base::mac::NSStringToFilePath([app_bundle bundlePath]);
+
+    // ** 4: Read information from the Info.plist.
+    // Read information about the this app shortcut from the Info.plist.
+    // Don't check for null-ness on optional items.
+    NSDictionary* info_plist = [app_bundle infoDictionary];
+    CHECK(info_plist) << "couldn't get loader Info.plist";
+
+    const std::string app_mode_id = SysNSStringToUTF8(
+        [info_plist objectForKey:app_mode::kCrAppModeShortcutIDKey]);
+    CHECK(app_mode_id.size()) << "couldn't get app shortcut ID";
+
+    const std::string app_mode_name = SysNSStringToUTF8(
+        [info_plist objectForKey:app_mode::kCrAppModeShortcutNameKey]);
+    const std::string app_mode_url = SysNSStringToUTF8(
+        [info_plist objectForKey:app_mode::kCrAppModeShortcutURLKey]);
+
+    base::FilePath plist_user_data_dir = base::mac::NSStringToFilePath(
+        [info_plist objectForKey:app_mode::kCrAppModeUserDataDirKey]);
+
+    base::FilePath profile_dir = base::mac::NSStringToFilePath(
+        [info_plist objectForKey:app_mode::kCrAppModeProfileDirKey]);
+
+    // ** 5: Open the framework.
+    StartFun ChromeAppModeStart = NULL;
+    void* cr_dylib = dlopen(framework_dylib_path.value().c_str(), RTLD_LAZY);
+    if (cr_dylib) {
+      // Find the entry point.
+      ChromeAppModeStart =
+          (StartFun)dlsym(cr_dylib, APP_SHIM_ENTRY_POINT_NAME_STRING);
+      if (!ChromeAppModeStart)
+        LOG(ERROR) << "Couldn't get entry point: " << dlerror();
+    } else {
+      LOG(ERROR) << "Couldn't load framework: " << dlerror();
+    }
+
+    // ** 6: Fill in ChromeAppModeInfo and call into Chrome's framework.
+    if (ChromeAppModeStart) {
+      // Ensure that the strings pointed to by |info| outlive |info|.
+      const std::string framework_path_utf8 = framework_path.AsUTF8Unsafe();
+      const std::string cr_bundle_path_utf8 = cr_bundle_path.AsUTF8Unsafe();
+      const std::string app_mode_bundle_path_utf8 =
+          app_mode_bundle_path.AsUTF8Unsafe();
+      const std::string plist_user_data_dir_utf8 =
+          plist_user_data_dir.AsUTF8Unsafe();
+      const std::string profile_dir_utf8 = profile_dir.AsUTF8Unsafe();
+      app_mode::ChromeAppModeInfo info;
+      info.argc = argc;
+      info.argv = argv;
+      info.chrome_framework_path = framework_path_utf8.c_str();
+      info.chrome_outer_bundle_path = cr_bundle_path_utf8.c_str();
+      info.app_mode_bundle_path = app_mode_bundle_path_utf8.c_str();
+      info.app_mode_id = app_mode_id.c_str();
+      info.app_mode_name = app_mode_name.c_str();
+      info.app_mode_url = app_mode_url.c_str();
+      info.user_data_dir = plist_user_data_dir_utf8.c_str();
+      info.profile_dir = profile_dir_utf8.c_str();
+      return ChromeAppModeStart(&info);
+    }
+
+    LOG(ERROR) << "Loading Chrome failed, launching Chrome with command line";
+    base::CommandLine command_line(executable_path);
+    // The user_data_dir from the plist is actually the app data dir.
+    command_line.AppendSwitchPath(
+        switches::kUserDataDir,
+        plist_user_data_dir.DirName().DirName().DirName());
+    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+            app_mode::kLaunchedByChromeProcessId)) {
+      // Pass --app-shim-error to have Chrome rebuild this shim.
+      // If Chrome has rebuilt this shim once already, then rebuilding doesn't
+      // fix the problem, so don't try again.
+      if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+              app_mode::kLaunchedAfterRebuild)) {
+        command_line.AppendSwitchPath(app_mode::kAppShimError,
+                                      app_mode_bundle_path);
+      }
+    } else {
+      // If the shim was launched directly (instead of by Chrome), first ask
+      // Chrome to launch the app. Chrome will launch the shim again, the same
+      // error will occur and be handled above. This approach allows the app to
+      // be started without blocking on fixing the shim and guarantees that the
+      // profile is loaded when Chrome receives --app-shim-error.
+      command_line.AppendSwitchPath(switches::kProfileDirectory, profile_dir);
+      command_line.AppendSwitchASCII(switches::kAppId, app_mode_id);
+    }
+    // Launch the executable directly since base::mac::OpenApplicationWithPath
+    // doesn't pass command line arguments if the application is already
+    // running.
+    if (!base::LaunchProcess(command_line, base::LaunchOptions()).IsValid()) {
+      LOG(ERROR) << "Could not launch Chrome: "
+                 << command_line.GetCommandLineString();
+      return 1;
+    }
+
+    return 0;
+  }
 }
 
 } // namespace
diff --git a/chrome/app_shim/chrome_main_app_mode_mac.mm b/chrome/app_shim/chrome_main_app_mode_mac.mm
index c0aacd7..6627c62e 100644
--- a/chrome/app_shim/chrome_main_app_mode_mac.mm
+++ b/chrome/app_shim/chrome_main_app_mode_mac.mm
@@ -18,7 +18,6 @@
 #include "base/logging.h"
 #include "base/mac/bundle_locations.h"
 #include "base/mac/mac_logging.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/macros.h"
 #include "base/message_loop/message_pump_type.h"
 #include "base/run_loop.h"
@@ -73,94 +72,96 @@
 int APP_SHIM_ENTRY_POINT_NAME(const app_mode::ChromeAppModeInfo* info) {
   base::CommandLine::Init(info->argc, info->argv);
 
-  base::mac::ScopedNSAutoreleasePool scoped_pool;
-  base::AtExitManager exit_manager;
-  chrome::RegisterPathProvider();
+  @autoreleasepool {
+    base::AtExitManager exit_manager;
+    chrome::RegisterPathProvider();
 
-  // Set bundle paths. This loads the bundles.
-  base::mac::SetOverrideOuterBundlePath(
-      base::FilePath(info->chrome_outer_bundle_path));
-  base::mac::SetOverrideFrameworkBundlePath(
-      base::FilePath(info->chrome_framework_path));
+    // Set bundle paths. This loads the bundles.
+    base::mac::SetOverrideOuterBundlePath(
+        base::FilePath(info->chrome_outer_bundle_path));
+    base::mac::SetOverrideFrameworkBundlePath(
+        base::FilePath(info->chrome_framework_path));
 
-  ChromeCrashReporterClient::Create();
-  crash_reporter::InitializeCrashpad(true, "app_shim");
+    ChromeCrashReporterClient::Create();
+    crash_reporter::InitializeCrashpad(true, "app_shim");
 
-  // Calculate the preferred locale used by Chrome.
-  // We can't use l10n_util::OverrideLocaleWithCocoaLocale() because it calls
-  // [base::mac::OuterBundle() preferredLocalizations] which gets localizations
-  // from the bundle of the running app (i.e. it is equivalent to
-  // [[NSBundle mainBundle] preferredLocalizations]) instead of the target
-  // bundle.
-  NSArray* preferred_languages = [NSLocale preferredLanguages];
-  NSArray* supported_languages = [base::mac::OuterBundle() localizations];
-  std::string preferred_localization;
-  for (NSString* language in preferred_languages) {
-    // We must convert the "-" separator to "_" to be compatible with
-    // NSBundle::localizations() e.g. "en-GB" becomes "en_GB".
-    // See https://crbug.com/913345.
-    language = [language stringByReplacingOccurrencesOfString:@"-"
-                                                   withString:@"_"];
-    if ([supported_languages containsObject:language]) {
-      preferred_localization = base::SysNSStringToUTF8(language);
-      break;
+    // Calculate the preferred locale used by Chrome. We can't use
+    // l10n_util::OverrideLocaleWithCocoaLocale() because it calls
+    // [base::mac::OuterBundle() preferredLocalizations] which gets
+    // localizations from the bundle of the running app (i.e. it is equivalent
+    // to [[NSBundle mainBundle] preferredLocalizations]) instead of the target
+    // bundle.
+    NSArray* preferred_languages = [NSLocale preferredLanguages];
+    NSArray* supported_languages = [base::mac::OuterBundle() localizations];
+    std::string preferred_localization;
+    for (NSString* language in preferred_languages) {
+      // We must convert the "-" separator to "_" to be compatible with
+      // NSBundle::localizations() e.g. "en-GB" becomes "en_GB".
+      // See https://crbug.com/913345.
+      language = [language stringByReplacingOccurrencesOfString:@"-"
+                                                     withString:@"_"];
+      if ([supported_languages containsObject:language]) {
+        preferred_localization = base::SysNSStringToUTF8(language);
+        break;
+      }
+      // Check for language support without the region component.
+      language = [language componentsSeparatedByString:@"_"][0];
+      if ([supported_languages containsObject:language]) {
+        preferred_localization = base::SysNSStringToUTF8(language);
+        break;
+      }
     }
-    // Check for language support without the region component.
-    language = [language componentsSeparatedByString:@"_"][0];
-    if ([supported_languages containsObject:language]) {
-      preferred_localization = base::SysNSStringToUTF8(language);
-      break;
-    }
+    std::string locale = l10n_util::NormalizeLocale(
+        l10n_util::GetApplicationLocale(preferred_localization));
+
+    // Load localized strings and mouse cursor images.
+    ui::ResourceBundle::InitSharedInstanceWithLocale(
+        locale, NULL, ui::ResourceBundle::LOAD_COMMON_RESOURCES);
+
+    ChromeContentClient chrome_content_client;
+    content::SetContentClient(&chrome_content_client);
+
+    // Launch the IO thread.
+    base::Thread::Options io_thread_options;
+    io_thread_options.message_pump_type = base::MessagePumpType::IO;
+    base::Thread* io_thread = new base::Thread("CrAppShimIO");
+    io_thread->StartWithOptions(io_thread_options);
+
+    mojo::core::Init();
+    mojo::core::ScopedIPCSupport ipc_support(
+        io_thread->task_runner(),
+        mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
+
+    // Initialize the NSApplication (and ensure that it was not previously
+    // initialized).
+    [AppShimApplication sharedApplication];
+    CHECK([NSApp isKindOfClass:[AppShimApplication class]]);
+
+    base::SingleThreadTaskExecutor main_task_executor(
+        base::MessagePumpType::UI);
+    ui::WindowResizeHelperMac::Get()->Init(main_task_executor.task_runner());
+    base::PlatformThread::SetName("CrAppShimMain");
+
+    // TODO(https://crbug.com/925998): This workaround ensures that there is
+    // always delayed work enqueued. If there is ever not enqueued delayed work,
+    // then NSMenus and NSAlerts can start misbehaving (see
+    // https://crbug.com/920795 for examples). This workaround is not an
+    // appropriate solution to the problem, and should be replaced by a fix in
+    // the relevant message pump code.
+    PostRepeatingDelayedTask();
+
+    AppShimController::Params controller_params;
+    // Note that |info->user_data_dir| for shims contains the app data path,
+    // <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/.
+    controller_params.user_data_dir =
+        base::FilePath(info->user_data_dir).DirName().DirName().DirName();
+    controller_params.profile_dir = base::FilePath(info->profile_dir);
+    controller_params.app_id = info->app_mode_id;
+    controller_params.app_name = base::UTF8ToUTF16(info->app_mode_name);
+    controller_params.app_url = GURL(info->app_mode_url);
+
+    AppShimController controller(controller_params);
+    base::RunLoop().Run();
+    return 0;
   }
-  std::string locale = l10n_util::NormalizeLocale(
-      l10n_util::GetApplicationLocale(preferred_localization));
-
-  // Load localized strings and mouse cursor images.
-  ui::ResourceBundle::InitSharedInstanceWithLocale(
-      locale, NULL, ui::ResourceBundle::LOAD_COMMON_RESOURCES);
-
-  ChromeContentClient chrome_content_client;
-  content::SetContentClient(&chrome_content_client);
-
-  // Launch the IO thread.
-  base::Thread::Options io_thread_options;
-  io_thread_options.message_pump_type = base::MessagePumpType::IO;
-  base::Thread *io_thread = new base::Thread("CrAppShimIO");
-  io_thread->StartWithOptions(io_thread_options);
-
-  mojo::core::Init();
-  mojo::core::ScopedIPCSupport ipc_support(
-      io_thread->task_runner(),
-      mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST);
-
-  // Initialize the NSApplication (and ensure that it was not previously
-  // initialized).
-  [AppShimApplication sharedApplication];
-  CHECK([NSApp isKindOfClass:[AppShimApplication class]]);
-
-  base::SingleThreadTaskExecutor main_task_executor(base::MessagePumpType::UI);
-  ui::WindowResizeHelperMac::Get()->Init(main_task_executor.task_runner());
-  base::PlatformThread::SetName("CrAppShimMain");
-
-  // TODO(https://crbug.com/925998): This workaround ensures that there is
-  // always delayed work enqueued. If there is ever not enqueued delayed work,
-  // then NSMenus and NSAlerts can start misbehaving (see
-  // https://crbug.com/920795 for examples). This workaround is not an
-  // appropriate solution to the problem, and should be replaced by a fix in
-  // the relevant message pump code.
-  PostRepeatingDelayedTask();
-
-  AppShimController::Params controller_params;
-  // Note that |info->user_data_dir| for shims contains the app data path,
-  // <user_data_dir>/<profile_dir>/Web Applications/_crx_extensionid/.
-  controller_params.user_data_dir =
-      base::FilePath(info->user_data_dir).DirName().DirName().DirName();
-  controller_params.profile_dir = base::FilePath(info->profile_dir);
-  controller_params.app_id = info->app_mode_id;
-  controller_params.app_name = base::UTF8ToUTF16(info->app_mode_name);
-  controller_params.app_url = GURL(info->app_mode_url);
-
-  AppShimController controller(controller_params);
-  base::RunLoop().Run();
-  return 0;
 }
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 54c1f7e..b559f4c3 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -1187,8 +1187,6 @@
     "performance_manager/web_contents_proxy.cc",
     "performance_manager/web_contents_proxy_impl.cc",
     "performance_manager/web_contents_proxy_impl.h",
-    "performance_manager/webui_graph_dump_impl.cc",
-    "performance_manager/webui_graph_dump_impl.h",
     "performance_monitor/metric_evaluator_helper_win.cc",
     "performance_monitor/metric_evaluator_helper_win.h",
     "performance_monitor/process_metrics_history.cc",
@@ -1955,7 +1953,6 @@
     "//chrome/browser/metrics/variations:chrome_ui_string_overrider_factory",
     "//chrome/browser/net:probe_message_proto",
     "//chrome/browser/notifications",
-    "//chrome/browser/performance_manager:mojo_bindings",
     "//chrome/browser/performance_manager:site_data_proto",
     "//chrome/browser/profiling_host",
     "//chrome/browser/push_messaging:budget_proto",
@@ -3201,6 +3198,8 @@
       "media_galleries/win/portable_device_map_service.h",
       "media_galleries/win/snapshot_file_details.cc",
       "media_galleries/win/snapshot_file_details.h",
+      "memory/enterprise_memory_limit_evaluator.cc",
+      "memory/enterprise_memory_limit_evaluator.h",
       "memory/memory_pressure_monitor.cc",
       "memory/memory_pressure_monitor.h",
       "memory/memory_pressure_monitor_utils.cc",
@@ -3721,8 +3720,6 @@
     deps += [
       "//ash/public/cpp",
       "//chrome/browser/chromeos",
-      "//chrome/services/cups_proxy",
-      "//chrome/services/cups_proxy/public/mojom",
       "//chromeos/components/account_manager",
       "//chromeos/components/sync_wifi",
       "//chromeos/services/assistant/public:feature_flags",
@@ -5172,6 +5169,12 @@
 
   if (use_cups) {
     configs += [ "//printing:cups" ]
+    if (is_chromeos) {
+      deps += [
+        "//chrome/services/cups_proxy",
+        "//chrome/services/cups_proxy/public/mojom",
+      ]
+    }
   }
 
   if (use_nss_certs) {
@@ -5317,7 +5320,6 @@
     ":chrome_internal_resources_gen",
     "//chrome/browser/engagement:mojo_bindings_js",
     "//chrome/browser/media:mojo_bindings_js",
-    "//chrome/browser/performance_manager:mojo_bindings_js",
     "//chrome/browser/resources/safety_tips:make_safety_tips_protobuf",
     "//chrome/browser/resources/ssl/ssl_error_assistant:make_ssl_error_assistant_protobuf",
     "//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings_js",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index 00854aa..a25c22c 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -10,6 +10,7 @@
 #include <utility>
 
 #include "ash/public/cpp/app_list/app_list_features.h"
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/keyboard/keyboard_switches.h"
 #include "base/base_switches.h"
 #include "base/bind.h"
@@ -1660,7 +1661,7 @@
         flag_descriptions::kEnableBackgroundBlurName,
         flag_descriptions::kEnableBackgroundBlurDescription,
         kOsCrOS,
-        FEATURE_VALUE_TYPE(app_list_features::kEnableBackgroundBlur),
+        FEATURE_VALUE_TYPE(ash::features::kEnableBackgroundBlur),
     },
     {"enable-notification-indicator",
      flag_descriptions::kNotificationIndicatorName,
diff --git a/chrome/browser/android/compositor/layer/overlay_panel_layer.cc b/chrome/browser/android/compositor/layer/overlay_panel_layer.cc
index a7510883..77b2cfe 100644
--- a/chrome/browser/android/compositor/layer/overlay_panel_layer.cc
+++ b/chrome/browser/android/compositor/layer/overlay_panel_layer.cc
@@ -234,29 +234,34 @@
   // ---------------------------------------------------------------------------
   // Close Icon
   // ---------------------------------------------------------------------------
-  // Grab the Close Icon resource.
-  ui::Resource* close_icon_resource =
-      resource_manager_->GetStaticResourceWithTint(close_icon_resource_id_,
-                                                   icon_tint);
-
+  int close_icon_width = 0;
+  float close_icon_left = 0.f;
   // Positions the icon at the end of the bar.
-  float close_icon_left;
   if (is_rtl) {
     close_icon_left = bar_margin_side;
   } else {
-    close_icon_left =
-        panel_width - close_icon_resource->size().width() - bar_margin_side;
+    close_icon_left = panel_width - bar_margin_side;
   }
+  if (close_icon_resource_id_ != kInvalidResourceID) {
+    // Grab the Close Icon resource.
+    ui::Resource* close_icon_resource =
+        resource_manager_->GetStaticResourceWithTint(close_icon_resource_id_,
+                                                     icon_tint);
 
-  // Centers the Close Icon vertically in the bar.
-  float close_icon_top =
-      bar_top_y + bar_height / 2 - close_icon_resource->size().height() / 2;
+    // Positions the icon at the end of the bar.
+    close_icon_width = close_icon_resource->size().width();
+    if (!is_rtl) {
+      close_icon_left = close_icon_left - close_icon_width;
+    }
 
-  close_icon_->SetUIResourceId(close_icon_resource->ui_resource()->id());
-  close_icon_->SetBounds(close_icon_resource->size());
-  close_icon_->SetPosition(
-      gfx::PointF(close_icon_left, close_icon_top));
-  close_icon_->SetOpacity(icon_opacity);
+    // Centers the Close Icon vertically in the bar.
+    float close_icon_top =
+        bar_top_y + bar_height / 2 - close_icon_resource->size().height() / 2;
+    close_icon_->SetUIResourceId(close_icon_resource->ui_resource()->id());
+    close_icon_->SetBounds(close_icon_resource->size());
+    close_icon_->SetPosition(gfx::PointF(close_icon_left, close_icon_top));
+    close_icon_->SetOpacity(icon_opacity);
+  }
 
   // ---------------------------------------------------------------------------
   // Open Tab icon
@@ -265,13 +270,12 @@
     ui::Resource* open_tab_icon_resource =
         resource_manager_->GetStaticResourceWithTint(open_tab_icon_resource_id_,
                                                      icon_tint);
-
     // Positions the icon at the end of the bar.
-    float open_tab_top = close_icon_top;
+    float open_tab_top = bar_top_y + bar_height / 2 -
+                         open_tab_icon_resource->size().height() / 2;
     float open_tab_left;
     float spacing_between_icons = 2 * bar_margin_side;
-    float margin_from_close_icon =
-        close_icon_resource->size().width() + spacing_between_icons;
+    float margin_from_close_icon = close_icon_width + spacing_between_icons;
     if (is_rtl) {
       open_tab_left = close_icon_left + margin_from_close_icon;
     } else {
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc
index 8980cc00..e67ce3b 100644
--- a/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate_unittest.cc
@@ -288,8 +288,8 @@
   std::string search_url_preload_;
   int coca_card_tag_;
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   std::unique_ptr<TemplateURLService> template_url_service_;
   scoped_refptr<network::SharedURLLoaderFactory>
       test_shared_url_loader_factory_;
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 87c529bf3..164275f 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -104,7 +104,7 @@
         <include name="IDR_DISCARDS_MOJO_PUBLIC_BASE_PROCESS_ID_MOJOM_LITE_JS" file="${root_gen_dir}\mojo\public\mojom\base\process_id.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
         <include name="IDR_DISCARDS_SORTED_TABLE_BEHAVIOR_HTML" file="resources\discards\sorted_table_behavior.html" compress="gzip" allowexternalscript="true" type="BINDATA" />
         <include name="IDR_DISCARDS_SORTED_TABLE_BEHAVIOR_JS" file="resources\discards\sorted_table_behavior.js" compress="gzip" type="BINDATA" />
-        <include name="IDR_DISCARDS_WEBUI_GRAPH_DUMP_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\performance_manager\webui_graph_dump.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
+        <include name="IDR_DISCARDS_WEBUI_GRAPH_DUMP_MOJOM_LITE_JS" file="${root_gen_dir}\chrome\browser\ui\webui\discards\webui_graph_dump.mojom-lite.js" compress="gzip" use_base_dir="false" type="BINDATA" />
       </if>
       <if expr="is_win or is_macosx or (is_linux and not is_chromeos)">
         <include name="IDR_BROWSER_SWITCH_APP_HTML" file="resources\browser_switch\app.html" compress="gzip" allowexternalscript="true" type="BINDATA" />
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 30bc6b2e..c816702 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -74,8 +74,6 @@
     "//chrome/common/extensions/api",
     "//chrome/services/app_service:lib",
     "//chrome/services/app_service/public/cpp:app_update",
-    "//chrome/services/cups_proxy",
-    "//chrome/services/cups_proxy/public/mojom",
     "//chrome/services/file_util/public/cpp",
     "//chrome/services/wilco_dtc_supportd/public/mojom",
     "//chromeos",
@@ -1280,6 +1278,8 @@
     "login/login_client_cert_usage_observer.h",
     "login/login_screen_extensions_lifetime_manager.cc",
     "login/login_screen_extensions_lifetime_manager.h",
+    "login/login_screen_extensions_storage_cleaner.cc",
+    "login/login_screen_extensions_storage_cleaner.h",
     "login/login_wizard.h",
     "login/mojo_system_info_dispatcher.cc",
     "login/mojo_system_info_dispatcher.h",
@@ -1918,12 +1918,6 @@
     "printing/cups_printers_manager.h",
     "printing/cups_printers_manager_factory.cc",
     "printing/cups_printers_manager_factory.h",
-    "printing/cups_proxy_service_delegate_impl.cc",
-    "printing/cups_proxy_service_delegate_impl.h",
-    "printing/cups_proxy_service_manager.cc",
-    "printing/cups_proxy_service_manager.h",
-    "printing/cups_proxy_service_manager_factory.cc",
-    "printing/cups_proxy_service_manager_factory.h",
     "printing/enterprise_printers_provider.cc",
     "printing/enterprise_printers_provider.h",
     "printing/ppd_provider_factory.cc",
@@ -2225,8 +2219,18 @@
   ]
 
   if (use_cups) {
+    deps += [
+      "//chrome/services/cups_proxy",
+      "//chrome/services/cups_proxy/public/mojom",
+    ]
     sources += [
       "printing/cups_print_job_manager_impl.cc",
+      "printing/cups_proxy_service_delegate_impl.cc",
+      "printing/cups_proxy_service_delegate_impl.h",
+      "printing/cups_proxy_service_manager.cc",
+      "printing/cups_proxy_service_manager.h",
+      "printing/cups_proxy_service_manager_factory.cc",
+      "printing/cups_proxy_service_manager_factory.h",
       "printing/printer_info_cups.cc",
     ]
   } else {
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index 8949c13..483a083 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -76,6 +76,7 @@
 #include "chrome/browser/chromeos/login/helper.h"
 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
 #include "chrome/browser/chromeos/login/login_screen_extensions_lifetime_manager.h"
+#include "chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.h"
 #include "chrome/browser/chromeos/login/login_wizard.h"
 #include "chrome/browser/chromeos/login/session/chrome_session_manager.h"
 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
@@ -1010,6 +1011,8 @@
 
   login_screen_extensions_lifetime_manager_ =
       std::make_unique<LoginScreenExtensionsLifetimeManager>();
+  login_screen_extensions_storage_cleaner_ =
+      std::make_unique<LoginScreenExtensionsStorageCleaner>();
 
   ChromeBrowserMainPartsLinux::PostProfileInit();
 }
@@ -1148,6 +1151,7 @@
   wilco_dtc_supportd_manager_.reset();
   gnubby_notification_.reset();
   login_screen_extensions_lifetime_manager_.reset();
+  login_screen_extensions_storage_cleaner_.reset();
 
   // Detach D-Bus clients before DBusThreadManager is shut down.
   idle_action_warning_observer_.reset();
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.h b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
index 217b01ad..75fe1f0 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.h
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
@@ -50,6 +50,7 @@
 class GnubbyNotification;
 class IdleActionWarningObserver;
 class LoginScreenExtensionsLifetimeManager;
+class LoginScreenExtensionsStorageCleaner;
 class LowDiskNotification;
 class NetworkChangeManagerClient;
 class NetworkPrefStateObserver;
@@ -181,6 +182,8 @@
   std::unique_ptr<WilcoDtcSupportdManager> wilco_dtc_supportd_manager_;
   std::unique_ptr<LoginScreenExtensionsLifetimeManager>
       login_screen_extensions_lifetime_manager_;
+  std::unique_ptr<LoginScreenExtensionsStorageCleaner>
+      login_screen_extensions_storage_cleaner_;
 
   std::unique_ptr<GnubbyNotification> gnubby_notification_;
 
diff --git a/chrome/browser/chromeos/extensions/login_screen/login_screen_storage/login_screen_storage_api.cc b/chrome/browser/chromeos/extensions/login_screen/login_screen_storage/login_screen_storage_api.cc
index d604cf74..b8f1222 100644
--- a/chrome/browser/chromeos/extensions/login_screen/login_screen_storage/login_screen_storage_api.cc
+++ b/chrome/browser/chromeos/extensions/login_screen/login_screen_storage/login_screen_storage_api.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/chromeos/extensions/login_screen/login_screen_storage/login_screen_storage_api.h"
 
+#include "base/strings/strcat.h"
 #include "base/values.h"
 #include "chrome/common/extensions/api/login_screen_storage.h"
 #include "chromeos/dbus/session_manager/session_manager_client.h"
@@ -82,10 +83,11 @@
   if (extension_ids.empty())
     return;
 
-  std::string extension_id = extension_ids.back();
+  std::string receiver_id = extension_ids.back();
   extension_ids.pop_back();
   chromeos::SessionManagerClient::Get()->LoginScreenStorageStore(
-      kPersistentDataKeyPrefix + extension_id, metadata, data,
+      kPersistentDataKeyPrefix + extension_id() + "_" + receiver_id, metadata,
+      data,
       base::BindOnce(
           &LoginScreenStorageStorePersistentDataFunction::OnDataStored, this,
           std::move(extension_ids), metadata, data));
@@ -98,8 +100,13 @@
 
 ExtensionFunction::ResponseAction
 LoginScreenStorageRetrievePersistentDataFunction::Run() {
+  std::unique_ptr<login_screen_storage::RetrievePersistentData::Params> params =
+      login_screen_storage::RetrievePersistentData::Params::Create(*args_);
+  EXTENSION_FUNCTION_VALIDATE(params);
+
   chromeos::SessionManagerClient::Get()->LoginScreenStorageRetrieve(
-      kPersistentDataKeyPrefix + extension_id(),
+      base::StrCat(
+          {kPersistentDataKeyPrefix, params->owner_id, "_", extension_id()}),
       base::BindOnce(
           &LoginScreenStorageRetrievePersistentDataFunction::OnDataRetrieved,
           this));
diff --git a/chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.cc b/chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.cc
new file mode 100644
index 0000000..cb5760b
--- /dev/null
+++ b/chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.cc
@@ -0,0 +1,87 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chromeos/dbus/session_manager/session_manager_client.h"
+#include "components/prefs/pref_service.h"
+#include "extensions/browser/pref_names.h"
+
+namespace chromeos {
+
+namespace {
+
+const char kPersistentDataKeyPrefix[] = "persistent_data_";
+
+}  // namespace
+
+LoginScreenExtensionsStorageCleaner::LoginScreenExtensionsStorageCleaner() {
+  DCHECK(ProfileHelper::IsSigninProfileInitialized());
+  prefs_ = ProfileHelper::GetSigninProfile()->GetPrefs();
+  pref_change_registrar_.Init(prefs_);
+  pref_change_registrar_.Add(
+      extensions::pref_names::kLoginScreenExtensions,
+      base::BindRepeating(&LoginScreenExtensionsStorageCleaner::OnPolicyUpdated,
+                          base::Unretained(this)));
+  ClearPersistentDataForUninstalledExtensions();
+}
+
+LoginScreenExtensionsStorageCleaner::~LoginScreenExtensionsStorageCleaner() =
+    default;
+
+void LoginScreenExtensionsStorageCleaner::OnPolicyUpdated() {
+  ClearPersistentDataForUninstalledExtensions();
+}
+
+void LoginScreenExtensionsStorageCleaner::
+    ClearPersistentDataForUninstalledExtensions() {
+  std::vector<std::string> installed_extension_ids;
+  const PrefService::Preference* const pref =
+      prefs_->FindPreference(extensions::pref_names::kLoginScreenExtensions);
+  if (pref && pref->IsManaged() &&
+      pref->GetType() == base::Value::Type::DICTIONARY) {
+    // Each |item| contains a pair of extension ID and update URL.
+    for (const auto& item : pref->GetValue()->DictItems())
+      installed_extension_ids.push_back(item.first);
+  }
+  SessionManagerClient::Get()->LoginScreenStorageListKeys(base::BindOnce(
+      &LoginScreenExtensionsStorageCleaner::
+          ClearPersistentDataForUninstalledExtensionsImpl,
+      base::Unretained(this), std::move(installed_extension_ids)));
+}
+
+void LoginScreenExtensionsStorageCleaner::
+    ClearPersistentDataForUninstalledExtensionsImpl(
+        const std::vector<std::string>& installed_extension_ids,
+        std::vector<std::string> keys,
+        base::Optional<std::string> error) {
+  if (error)
+    return;
+
+  for (const std::string& key : keys) {
+    // Skip the keys that are not extension's persistent data.
+    if (!base::StartsWith(key, kPersistentDataKeyPrefix,
+                          base::CompareCase::SENSITIVE))
+      continue;
+
+    // If no installed extension has created the key, delete it.
+    bool has_owner_extension = false;
+    for (const std::string& extension_id : installed_extension_ids) {
+      if (key.find(extension_id) != std::string::npos) {
+        has_owner_extension = true;
+        break;
+      }
+    }
+    if (!has_owner_extension)
+      SessionManagerClient::Get()->LoginScreenStorageDelete(key);
+  }
+}
+
+}  // namespace chromeos
diff --git a/chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.h b/chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.h
new file mode 100644
index 0000000..ad200b3
--- /dev/null
+++ b/chrome/browser/chromeos/login/login_screen_extensions_storage_cleaner.h
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_LOGIN_SCREEN_EXTENSIONS_STORAGE_CLEANER_H_
+#define CHROME_BROWSER_CHROMEOS_LOGIN_LOGIN_SCREEN_EXTENSIONS_STORAGE_CLEANER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "components/prefs/pref_change_registrar.h"
+
+class PrefService;
+
+namespace chromeos {
+
+// Tracks changes to 'DeviceLoginScreenExtensions' policy and clears its data
+// stored in the login screen storage whenever a login screen extension is
+// removed.
+class LoginScreenExtensionsStorageCleaner {
+ public:
+  LoginScreenExtensionsStorageCleaner();
+  ~LoginScreenExtensionsStorageCleaner();
+
+ private:
+  // Called whenever the value of 'DeviceLoginScreenExtensions' policy is
+  // updated.
+  void OnPolicyUpdated();
+
+  // Makes sure that persistent data in the login screen storage is only stored
+  // for currently installed login screen extensions (so when an extension is
+  // uninstalled its data would automatically be deleted).
+  void ClearPersistentDataForUninstalledExtensions();
+
+  void ClearPersistentDataForUninstalledExtensionsImpl(
+      const std::vector<std::string>& installed_extension_ids,
+      std::vector<std::string> keys,
+      base::Optional<std::string> error);
+
+  PrefService* prefs_;
+  PrefChangeRegistrar pref_change_registrar_;
+};
+
+}  // namespace chromeos
+
+#endif  // CHROME_BROWSER_CHROMEOS_LOGIN_LOGIN_SCREEN_EXTENSIONS_STORAGE_CLEANER_H_
diff --git a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
index 543a764..6072405 100644
--- a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
+++ b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.cc
@@ -391,9 +391,9 @@
   return MarketSegment::UNKNOWN;
 }
 
-void BrowserPolicyConnectorChromeOS::SetUserPolicyDelegate(
-    ConfigurationPolicyProvider* user_policy_provider) {
-  global_user_cloud_policy_provider_->SetDelegate(user_policy_provider);
+ProxyPolicyProvider*
+BrowserPolicyConnectorChromeOS::GetGlobalUserCloudPolicyProvider() {
+  return global_user_cloud_policy_provider_;
 }
 
 void BrowserPolicyConnectorChromeOS::SetDeviceCloudPolicyInitializerForTesting(
diff --git a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h
index 00bf279..3a5e152 100644
--- a/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h
+++ b/chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h
@@ -171,16 +171,23 @@
   // Returns device's market segment.
   MarketSegment GetEnterpriseMarketSegment() const;
 
-  // The browser-global PolicyService is created before Profiles are ready, to
-  // provide managed values for the local state PrefService. It includes a
-  // policy provider that forwards policies from a delegate policy provider.
-  // This call can be used to set the user policy provider as that delegate
-  // once the Profile is ready, so that user policies can also affect local
-  // state preferences.
-  // Only one user policy provider can be set as a delegate at a time, and any
-  // previously set delegate is removed. Passing NULL removes the current
-  // delegate, if there is one.
-  void SetUserPolicyDelegate(ConfigurationPolicyProvider* user_policy_provider);
+  // Returns a ProxyPolicyProvider that will be used to forward user policies
+  // from the primary Profile to the device-wide PolicyService[1].
+  // This means that user policies from the primary Profile will also affect
+  // local state[2] Preferences.
+  //
+  // Note that the device-wide PolicyService[1] is created before Profiles are
+  // ready / before a user has signed-in. As PolicyProviders can only be
+  // configured during PolicyService creation, a ProxyPolicyProvider (which does
+  // not have a delegate yet) is included in the device-wide PolicyService at
+  // the time of its creation. This returns an unowned pointer to that
+  // ProxyPolicyProvider so the caller can invoke SetDelegate on it. The
+  // returned pointer is guaranteed to be valid as long as this instance is
+  // valid.
+  //
+  // [1] i.e. g_browser_process->policy_service()
+  // [2] i.e. g_browser_process->local_state()
+  ProxyPolicyProvider* GetGlobalUserCloudPolicyProvider();
 
   // Sets the device cloud policy initializer for testing.
   void SetDeviceCloudPolicyInitializerForTesting(
diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
index a68bdc2..6cfcfd9 100644
--- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc
@@ -432,6 +432,7 @@
           DeviceStatusCollector::AndroidStatusFetcher(),
           DeviceStatusCollector::TpmStatusFetcher(),
           DeviceStatusCollector::EMMCLifetimeFetcher(),
+          DeviceStatusCollector::StatefulPartitionInfoFetcher(),
           true /* is_enterprise_device */),
       task_runner_, kDeviceStatusUploadFrequency));
 }
diff --git a/chrome/browser/chromeos/policy/login_policy_test_base.cc b/chrome/browser/chromeos/policy/login_policy_test_base.cc
index 8c4ef13..7d12aff 100644
--- a/chrome/browser/chromeos/policy/login_policy_test_base.cc
+++ b/chrome/browser/chromeos/policy/login_policy_test_base.cc
@@ -108,13 +108,19 @@
   OobeBaseTest::WaitForSigninScreen();
 }
 
-void LoginPolicyTestBase::LogIn(const std::string& user_id,
-                                const std::string& password,
-                                const std::string& services) {
+void LoginPolicyTestBase::TriggerLogIn(const std::string& user_id,
+                                       const std::string& password,
+                                       const std::string& services) {
   chromeos::LoginDisplayHost::default_host()
       ->GetOobeUI()
       ->GetView<chromeos::GaiaScreenHandler>()
       ->ShowSigninScreenForTest(user_id, password, services);
+}
+
+void LoginPolicyTestBase::LogIn(const std::string& user_id,
+                                const std::string& password,
+                                const std::string& services) {
+  TriggerLogIn(user_id, password, services);
   chromeos::test::WaitForPrimaryUserSessionStart();
 }
 
diff --git a/chrome/browser/chromeos/policy/login_policy_test_base.h b/chrome/browser/chromeos/policy/login_policy_test_base.h
index 88d248d1..0b278876 100644
--- a/chrome/browser/chromeos/policy/login_policy_test_base.h
+++ b/chrome/browser/chromeos/policy/login_policy_test_base.h
@@ -44,7 +44,13 @@
   Profile* GetProfileForActiveUser();
 
   void SkipToLoginScreen();
-  // Should match ShowSigninScreenForTest method in SigninScreenHandler.
+
+  // Triggers the login, but does not wait for a user session to start.
+  void TriggerLogIn(const std::string& user_id,
+                    const std::string& password,
+                    const std::string& services);
+
+  // Triggers the login and waits for a user session to start.
   void LogIn(const std::string& user_id,
              const std::string& password,
              const std::string& services);
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
index 6300d2d3..8fb66cd 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.cc
@@ -127,6 +127,9 @@
 // data for components.
 const char kGenericDeviceName[] = "generic";
 
+// The location where stateful partition info is read from.
+const char kStatefulPartitionPath[] = "/home/.shadow";
+
 // How often the child's usage time is stored.
 static constexpr base::TimeDelta kUpdateChildActiveTimeInterval =
     base::TimeDelta::FromSeconds(30);
@@ -315,6 +318,30 @@
   return est;
 }
 
+// Read stateful partition info for user data.
+em::StatefulPartitionInfo ReadStatefulPartitionInfo() {
+  em::StatefulPartitionInfo spi;
+  const base::FilePath statefulPartitionPath(kStatefulPartitionPath);
+  const int64_t available_space =
+      base::SysInfo::AmountOfFreeDiskSpace(statefulPartitionPath);
+  const int64_t total_space =
+      base::SysInfo::AmountOfTotalDiskSpace(statefulPartitionPath);
+
+  if (available_space == -1) {
+    LOG(ERROR) << "ReadStatefulPartitionInfo failed fetching available space.";
+    return spi;
+  }
+
+  if (total_space == -1) {
+    LOG(ERROR) << "ReadStatefulPartitionInfo failed fetching total space.";
+    return spi;
+  }
+
+  spi.set_available_space(available_space);
+  spi.set_total_space(total_space);
+  return spi;
+}
+
 bool ReadAndroidStatus(
     const policy::DeviceStatusCollector::AndroidStatusReceiver& receiver) {
   auto* const arc_service_manager = arc::ArcServiceManager::Get();
@@ -554,6 +581,19 @@
                        this));
   }
 
+  void FetchStatefulPartitionInfo(
+      const policy::DeviceStatusCollector::StatefulPartitionInfoFetcher&
+          stateful_partition_info_fetcher) {
+    // Call out to the blocking pool to read stateful partition information.
+    base::PostTaskAndReplyWithResult(
+        FROM_HERE,
+        {base::ThreadPool(), base::MayBlock(), base::TaskPriority::BEST_EFFORT},
+        base::BindOnce(stateful_partition_info_fetcher),
+        base::BindOnce(
+            &DeviceStatusCollectorState::OnStatefulPartitionInfoReceived,
+            this));
+  }
+
  private:
   ~DeviceStatusCollectorState() override = default;
 
@@ -716,6 +756,16 @@
             ->mutable_lifetime_estimation();
     state->CopyFrom(est);
   }
+
+  void OnStatefulPartitionInfoReceived(const em::StatefulPartitionInfo& hdsi) {
+    if (!hdsi.has_available_space() && !hdsi.has_total_space())
+      return;
+    em::StatefulPartitionInfo* stateful_partition_info =
+        response_params_.device_status->mutable_stateful_partition_info();
+    DCHECK(hdsi.available_space() >= 0);
+    DCHECK(hdsi.total_space() >= hdsi.available_space());
+    stateful_partition_info->CopyFrom(hdsi);
+  }
 };
 
 TpmStatusInfo::TpmStatusInfo() = default;
@@ -756,6 +806,7 @@
     const AndroidStatusFetcher& android_status_fetcher,
     const TpmStatusFetcher& tpm_status_fetcher,
     const EMMCLifetimeFetcher& emmc_lifetime_fetcher,
+    const StatefulPartitionInfoFetcher& stateful_partition_info_fetcher,
     bool is_enterprise_reporting)
     : StatusCollector(provider,
                       chromeos::CrosSettings::Get(),
@@ -768,6 +819,7 @@
       android_status_fetcher_(android_status_fetcher),
       tpm_status_fetcher_(tpm_status_fetcher),
       emmc_lifetime_fetcher_(emmc_lifetime_fetcher),
+      stateful_partition_info_fetcher_(stateful_partition_info_fetcher),
       runtime_probe_(
           chromeos::DBusThreadManager::Get()->GetRuntimeProbeClient()),
       is_enterprise_reporting_(is_enterprise_reporting) {
@@ -801,6 +853,10 @@
 
   if (emmc_lifetime_fetcher_.is_null())
     emmc_lifetime_fetcher_ = base::BindRepeating(&ReadDiskLifeTimeEstimation);
+
+  if (stateful_partition_info_fetcher_.is_null())
+    stateful_partition_info_fetcher_ = base::Bind(&ReadStatefulPartitionInfo);
+
   idle_poll_timer_.Start(FROM_HERE,
                          TimeDelta::FromSeconds(kIdlePollIntervalSeconds), this,
                          &DeviceStatusCollector::CheckIdleState);
@@ -1663,6 +1719,10 @@
     // Sample CPU temperature in a background thread.
     state->SampleCPUTempInfo(cpu_temp_fetcher_);
   }
+
+  // Fetch Stateful Partition Information on a background thread.
+  state->FetchStatefulPartitionInfo(stateful_partition_info_fetcher_);
+
   return true;
 }
 
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector.h b/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
index 523464c..b2551472 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector.h
@@ -162,20 +162,25 @@
   using EMMCLifetimeFetcher =
       base::RepeatingCallback<enterprise_management::DiskLifetimeEstimation(
           void)>;
+  // Reads the stateful partition info from /home/.shadow
+  using StatefulPartitionInfoFetcher =
+      base::Callback<enterprise_management::StatefulPartitionInfo()>;
 
   // Constructor. Callers can inject their own *Fetcher callbacks, e.g. for unit
   // testing. A null callback can be passed for any *Fetcher parameter, to use
   // the default implementation. These callbacks are always executed on Blocking
   // Pool. Caller is responsible for passing already initialized |pref_service|.
-  DeviceStatusCollector(PrefService* pref_service,
-                        chromeos::system::StatisticsProvider* provider,
-                        const VolumeInfoFetcher& volume_info_fetcher,
-                        const CPUStatisticsFetcher& cpu_statistics_fetcher,
-                        const CPUTempFetcher& cpu_temp_fetcher,
-                        const AndroidStatusFetcher& android_status_fetcher,
-                        const TpmStatusFetcher& tpm_status_fetcher,
-                        const EMMCLifetimeFetcher& emmc_lifetime_fetcher,
-                        bool is_enterprise_reporting);
+  DeviceStatusCollector(
+      PrefService* pref_service,
+      chromeos::system::StatisticsProvider* provider,
+      const VolumeInfoFetcher& volume_info_fetcher,
+      const CPUStatisticsFetcher& cpu_statistics_fetcher,
+      const CPUTempFetcher& cpu_temp_fetcher,
+      const AndroidStatusFetcher& android_status_fetcher,
+      const TpmStatusFetcher& tpm_status_fetcher,
+      const EMMCLifetimeFetcher& emmc_lifetime_fetcher,
+      const StatefulPartitionInfoFetcher& stateful_partition_info_fetcher,
+      bool is_enterprise_reporting);
   ~DeviceStatusCollector() override;
 
   // StatusCollector:
@@ -403,6 +408,8 @@
 
   EMMCLifetimeFetcher emmc_lifetime_fetcher_;
 
+  StatefulPartitionInfoFetcher stateful_partition_info_fetcher_;
+
   PowerStatusCallback power_status_callback_;
 
   // Runtime probe client. Used to fetch hardware data.
diff --git a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
index 7f5ccc93..3296434 100644
--- a/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/status_collector/device_status_collector_browsertest.cc
@@ -148,6 +148,8 @@
       const policy::DeviceStatusCollector::TpmStatusFetcher& tpm_status_fetcher,
       const policy::DeviceStatusCollector::EMMCLifetimeFetcher&
           emmc_lifetime_fetcher,
+      const policy::DeviceStatusCollector::StatefulPartitionInfoFetcher&
+          stateful_partition_info_fetcher,
       bool is_enterprise_device)
       : policy::DeviceStatusCollector(pref_service,
                                       provider,
@@ -157,6 +159,7 @@
                                       android_status_fetcher,
                                       tpm_status_fetcher,
                                       emmc_lifetime_fetcher,
+                                      stateful_partition_info_fetcher,
                                       is_enterprise_device) {
     // Set the baseline time to a fixed value (1 hour after day start) to
     // prevent test flakiness due to a single activity period spanning two days.
@@ -337,6 +340,15 @@
   return value;
 }
 
+em::StatefulPartitionInfo GetEmptyStatefulPartitionInfo() {
+  return em::StatefulPartitionInfo();
+}
+
+em::StatefulPartitionInfo GetFakeStatefulPartitionInfo(
+    const em::StatefulPartitionInfo& value) {
+  return value;
+}
+
 }  // namespace
 
 namespace policy {
@@ -441,13 +453,13 @@
   }
 
   void SetUp() override {
-    RestartStatusCollector(
-        base::BindRepeating(&GetEmptyVolumeInfo),
-        base::BindRepeating(&GetEmptyCPUStatistics),
-        base::BindRepeating(&GetEmptyCPUTempInfo),
-        base::BindRepeating(&GetEmptyAndroidStatus),
-        base::BindRepeating(&GetEmptyTpmStatus),
-        base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+    RestartStatusCollector(base::BindRepeating(&GetEmptyVolumeInfo),
+                           base::BindRepeating(&GetEmptyCPUStatistics),
+                           base::BindRepeating(&GetEmptyCPUTempInfo),
+                           base::BindRepeating(&GetEmptyAndroidStatus),
+                           base::BindRepeating(&GetEmptyTpmStatus),
+                           base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+                           base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
     // Disable network interface reporting since it requires additional setup.
     scoped_testing_cros_settings_.device_settings()->SetBoolean(
@@ -522,12 +534,15 @@
           android_status_fetcher,
       const policy::DeviceStatusCollector::TpmStatusFetcher& tpm_status_fetcher,
       const policy::DeviceStatusCollector::EMMCLifetimeFetcher&
-          emmc_lifetime_fetcher) {
+          emmc_lifetime_fetcher,
+      const policy::DeviceStatusCollector::StatefulPartitionInfoFetcher&
+          stateful_partition_info_fetcher) {
     std::vector<em::VolumeInfo> expected_volume_info;
     status_collector_ = std::make_unique<TestingDeviceStatusCollector>(
         &local_state_, &fake_statistics_provider_, volume_info, cpu_stats,
         cpu_temp_fetcher, android_status_fetcher, tpm_status_fetcher,
-        emmc_lifetime_fetcher, true /* is_enterprise_device */);
+        emmc_lifetime_fetcher, stateful_partition_info_fetcher,
+        true /* is_enterprise_device */);
   }
 
   void GetStatus() {
@@ -842,7 +857,8 @@
                          base::BindRepeating(&GetEmptyCPUTempInfo),
                          base::BindRepeating(&GetEmptyAndroidStatus),
                          base::BindRepeating(&GetEmptyTpmStatus),
-                         base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+                         base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+                         base::BindRepeating(&GetEmptyStatefulPartitionInfo));
   status_collector_->Simulate(test_states,
                               sizeof(test_states) / sizeof(ui::IdleState));
 
@@ -1302,7 +1318,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetEmptyAndroidStatus),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
   // Force finishing tasks posted by ctor of DeviceStatusCollector.
   content::RunAllTasksUntilIdle();
 
@@ -1359,7 +1376,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetEmptyAndroidStatus),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
   // Force finishing tasks posted by ctor of DeviceStatusCollector.
   content::RunAllTasksUntilIdle();
   GetStatus();
@@ -1414,7 +1432,8 @@
       base::BindRepeating(&GetFakeCPUTempInfo, expected_temp_info),
       base::BindRepeating(&GetEmptyAndroidStatus),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
   // Force finishing tasks posted by ctor of DeviceStatusCollector.
   content::RunAllTasksUntilIdle();
 
@@ -1453,7 +1472,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetEmptyAndroidStatus),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetFakeEMMCLifetiemEstimation, est));
+      base::BindRepeating(&GetFakeEMMCLifetiemEstimation, est),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
   // Force finishing tasks posted by ctor of DeviceStatusCollector.
   content::RunAllTasksUntilIdle();
   scoped_testing_cros_settings_.device_settings()->SetBoolean(
@@ -1480,7 +1500,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
   status_collector_->set_kiosk_account(
       std::make_unique<DeviceLocalAccount>(fake_kiosk_device_local_account_));
   MockRunningKioskApp(fake_kiosk_device_local_account_, false /* arc_kiosk */);
@@ -1502,7 +1523,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   // Mock Kiosk app, so some session status is reported
   status_collector_->set_kiosk_account(
@@ -1523,7 +1545,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail("user0@managed.com"));
   MockRegularUserWithAffiliation(account_id, true);
@@ -1546,7 +1569,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail(kCrostiniUserEmail));
   MockRegularUserWithAffiliation(account_id, true);
@@ -1577,7 +1601,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail(kCrostiniUserEmail));
   MockRegularUserWithAffiliation(account_id, true);
@@ -1599,7 +1624,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   // Prerequisites for any Crostini reporting to take place:
   const AccountId account_id(AccountId::FromUserEmail(kCrostiniUserEmail));
@@ -1640,7 +1666,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail(kCrostiniUserEmail));
   MockRegularUserWithAffiliation(account_id, true);
@@ -1728,7 +1755,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail(kCrostiniUserEmail));
   MockRegularUserWithAffiliation(account_id, true);
@@ -1768,7 +1796,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail("user0@managed.com"));
   MockRegularUserWithAffiliation(account_id, true);
@@ -1790,7 +1819,8 @@
       base::BindRepeating(&GetEmptyCPUTempInfo),
       base::BindRepeating(&GetFakeAndroidStatus, kArcStatus, kDroidGuardInfo),
       base::BindRepeating(&GetEmptyTpmStatus),
-      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+      base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+      base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   const AccountId account_id(AccountId::FromUserEmail("user0@managed.com"));
   MockRegularUserWithAffiliation(account_id, false);
@@ -1822,7 +1852,8 @@
                          base::BindRepeating(&GetEmptyCPUTempInfo),
                          base::BindRepeating(&GetEmptyAndroidStatus),
                          base::BindRepeating(&GetFakeTpmStatus, kFakeTpmStatus),
-                         base::BindRepeating(&GetEmptyEMMCLifetimeEstimation));
+                         base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+                         base::BindRepeating(&GetEmptyStatefulPartitionInfo));
 
   GetStatus();
 
@@ -2099,6 +2130,31 @@
   EXPECT_EQ(kCustomVolume, device_status_.sound_volume());
 }
 
+TEST_F(DeviceStatusCollectorTest, TestStatefulPartitionInfo) {
+  // Create a fake stateful partition info and populate it with some arbitrary
+  // values.
+  em::StatefulPartitionInfo fakeStatefulPartitionInfo;
+  fakeStatefulPartitionInfo.set_available_space(350);
+  fakeStatefulPartitionInfo.set_total_space(500);
+
+  RestartStatusCollector(base::BindRepeating(&GetEmptyVolumeInfo),
+                         base::BindRepeating(&GetEmptyCPUStatistics),
+                         base::BindRepeating(&GetEmptyCPUTempInfo),
+                         base::BindRepeating(&GetEmptyAndroidStatus),
+                         base::BindRepeating(&GetEmptyTpmStatus),
+                         base::BindRepeating(&GetEmptyEMMCLifetimeEstimation),
+                         base::BindRepeating(&GetFakeStatefulPartitionInfo,
+                                             fakeStatefulPartitionInfo));
+
+  GetStatus();
+
+  EXPECT_TRUE(device_status_.has_stateful_partition_info());
+  EXPECT_EQ(fakeStatefulPartitionInfo.available_space(),
+            device_status_.stateful_partition_info().available_space());
+  EXPECT_EQ(fakeStatefulPartitionInfo.total_space(),
+            device_status_.stateful_partition_info().total_space());
+}
+
 // Fake device state.
 struct FakeDeviceData {
   const char* device_path;
diff --git a/chrome/browser/chromeos/policy/status_uploader_unittest.cc b/chrome/browser/chromeos/policy/status_uploader_unittest.cc
index 0818b94..87cae44 100644
--- a/chrome/browser/chromeos/policy/status_uploader_unittest.cc
+++ b/chrome/browser/chromeos/policy/status_uploader_unittest.cc
@@ -64,6 +64,7 @@
             policy::DeviceStatusCollector::AndroidStatusFetcher(),
             policy::DeviceStatusCollector::TpmStatusFetcher(),
             policy::DeviceStatusCollector::EMMCLifetimeFetcher(),
+            policy::DeviceStatusCollector::StatefulPartitionInfoFetcher(),
             true /* is_enterprise_device */) {}
 
   MOCK_METHOD1(GetStatusAsync, void(const policy::StatusCollectorCallback&));
diff --git a/chrome/browser/chromeos/policy/user_network_configuration_updater_factory.cc b/chrome/browser/chromeos/policy/user_network_configuration_updater_factory.cc
index 8eed083..829a7ae 100644
--- a/chrome/browser/chromeos/policy/user_network_configuration_updater_factory.cc
+++ b/chrome/browser/chromeos/policy/user_network_configuration_updater_factory.cc
@@ -73,16 +73,15 @@
   if (user != user_manager::UserManager::Get()->GetPrimaryUser())
     return nullptr;
 
-  // Guest sessions don't get user policy, so there's no reason for them to have
-  // a UserNetworkConfigurationUpdater.
-  if (user->GetType() == user_manager::USER_TYPE_GUEST)
-    return nullptr;
-
-  ProfilePolicyConnector* profile_connector =
-      profile->GetProfilePolicyConnector();
-
+  // Note that sessions which don't have policy (e.g. guest sessions) still
+  // expect to have UserNetworkConfigurationUpdater, because
+  // ManagedNetworkConfigurationHandler requires a (possibly empty) policy to be
+  // set for all user sessions.
+  // TODO(https://crbug.com/1001490): Evaluate if this is can be solved in a
+  // more elegant way.
   return UserNetworkConfigurationUpdater::CreateForUserPolicy(
-             profile, *user, profile_connector->policy_service(),
+             profile, *user,
+             profile->GetProfilePolicyConnector()->policy_service(),
              chromeos::NetworkHandler::Get()
                  ->managed_network_configuration_handler())
       .release();
diff --git a/chrome/browser/extensions/BUILD.gn b/chrome/browser/extensions/BUILD.gn
index 000374b..5472027 100644
--- a/chrome/browser/extensions/BUILD.gn
+++ b/chrome/browser/extensions/BUILD.gn
@@ -661,6 +661,8 @@
     "policy_handlers.h",
     "proxy_overridden_bubble_delegate.cc",
     "proxy_overridden_bubble_delegate.h",
+    "scoped_active_install.cc",
+    "scoped_active_install.h",
     "scripting_permissions_modifier.cc",
     "scripting_permissions_modifier.h",
     "settings_api_bubble_delegate.cc",
diff --git a/chrome/browser/extensions/active_install_data.cc b/chrome/browser/extensions/active_install_data.cc
index ee6cdfe..31a27aac 100644
--- a/chrome/browser/extensions/active_install_data.cc
+++ b/chrome/browser/extensions/active_install_data.cc
@@ -4,55 +4,9 @@
 
 #include "chrome/browser/extensions/active_install_data.h"
 
-#include "chrome/browser/extensions/install_tracker.h"
-
 namespace extensions {
 
-// ActiveInstallData:
-
-ActiveInstallData::ActiveInstallData()
-    : percent_downloaded(0) {
-}
-
 ActiveInstallData::ActiveInstallData(const std::string& extension_id)
-    : extension_id(extension_id), percent_downloaded(0) {
-}
-
-// ScopedActiveInstall:
-
-ScopedActiveInstall::ScopedActiveInstall(InstallTracker* tracker,
-                                         const ActiveInstallData& install_data)
-    : tracker_(tracker),
-      tracker_observer_(this),
-      extension_id_(install_data.extension_id) {
-  Init();
-  tracker_->AddActiveInstall(install_data);
-}
-
-ScopedActiveInstall::ScopedActiveInstall(InstallTracker* tracker,
-                                         const std::string& extension_id)
-    : tracker_(tracker), tracker_observer_(this), extension_id_(extension_id) {
-  Init();
-}
-
-ScopedActiveInstall::~ScopedActiveInstall() {
-  if (tracker_)
-    tracker_->RemoveActiveInstall(extension_id_);
-}
-
-void ScopedActiveInstall::CancelDeregister() {
-  tracker_observer_.RemoveAll();
-  tracker_ = NULL;
-}
-
-void ScopedActiveInstall::Init() {
-  DCHECK(!extension_id_.empty());
-  DCHECK(tracker_);
-  tracker_observer_.Add(tracker_);
-}
-
-void ScopedActiveInstall::OnShutdown() {
-  CancelDeregister();
-}
+    : extension_id(extension_id) {}
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/active_install_data.h b/chrome/browser/extensions/active_install_data.h
index c7dd9ba..861258f 100644
--- a/chrome/browser/extensions/active_install_data.h
+++ b/chrome/browser/extensions/active_install_data.h
@@ -7,54 +7,15 @@
 
 #include <string>
 
-#include "base/macros.h"
-#include "base/scoped_observer.h"
-#include "chrome/browser/extensions/install_observer.h"
-
 namespace extensions {
 
-class InstallTracker;
-
 // Details of an active extension install.
 struct ActiveInstallData {
- public:
-  ActiveInstallData();
+  ActiveInstallData() = default;
   explicit ActiveInstallData(const std::string& extension_id);
 
   std::string extension_id;
-  int percent_downloaded;
-};
-
-// Registers and deregisters and an active extension install with the
-// InstallTracker.
-class ScopedActiveInstall : public InstallObserver {
- public:
-  // This constructor registers an active install with the InstallTracker.
-  ScopedActiveInstall(InstallTracker* tracker,
-                      const ActiveInstallData& install_data);
-
-  // This constructor does not register an active install. The extension install
-  // is still deregistered upon destruction.
-  ScopedActiveInstall(InstallTracker* tracker, const std::string& extension_id);
-
-  ~ScopedActiveInstall() override;
-
-  // Ensures that the active install is not deregistered upon destruction. This
-  // may be necessary if the extension install outlives the lifetime of this
-  // instance.
-  void CancelDeregister();
-
- private:
-  void Init();
-
-  // InstallObserver implementation.
-  void OnShutdown() override;
-
-  InstallTracker* tracker_;
-  ScopedObserver<InstallTracker, InstallObserver> tracker_observer_;
-  std::string extension_id_;
-
-  DISALLOW_COPY_AND_ASSIGN(ScopedActiveInstall);
+  int percent_downloaded = 0;
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/active_tab_permission_granter.cc b/chrome/browser/extensions/active_tab_permission_granter.cc
index ff28a41..0eb49cc 100644
--- a/chrome/browser/extensions/active_tab_permission_granter.cc
+++ b/chrome/browser/extensions/active_tab_permission_granter.cc
@@ -19,7 +19,6 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/process_manager.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/permissions/permission_set.h"
@@ -108,9 +107,7 @@
     content::WebContents* web_contents,
     int tab_id,
     Profile* profile)
-    : content::WebContentsObserver(web_contents),
-      tab_id_(tab_id),
-      extension_registry_observer_(this) {
+    : content::WebContentsObserver(web_contents), tab_id_(tab_id) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
 }
 
diff --git a/chrome/browser/extensions/active_tab_permission_granter.h b/chrome/browser/extensions/active_tab_permission_granter.h
index 7483574..f006609 100644
--- a/chrome/browser/extensions/active_tab_permission_granter.h
+++ b/chrome/browser/extensions/active_tab_permission_granter.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/url_pattern_set.h"
@@ -25,7 +26,6 @@
 namespace extensions {
 
 class Extension;
-class ExtensionRegistry;
 
 // Responsible for granting and revoking tab-specific permissions to extensions
 // with the activeTab or tabCapture permission.
@@ -82,7 +82,7 @@
 
   // Listen to extension unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ActiveTabPermissionGranter);
 };
diff --git a/chrome/browser/extensions/activity_log/activity_log.cc b/chrome/browser/extensions/activity_log/activity_log.cc
index da2b44dd..6fb972a 100644
--- a/chrome/browser/extensions/activity_log/activity_log.cc
+++ b/chrome/browser/extensions/activity_log/activity_log.cc
@@ -47,7 +47,6 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/api_activity_monitor.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_factory.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_system_provider.h"
@@ -560,7 +559,6 @@
       extension_system_(ExtensionSystem::Get(context)),
       db_enabled_(false),
       testing_mode_(false),
-      extension_registry_observer_(this),
       active_consumers_(0),
       cached_consumer_count_(0),
       has_listeners_(false),
diff --git a/chrome/browser/extensions/activity_log/activity_log.h b/chrome/browser/extensions/activity_log/activity_log.h
index 0f876082..f67e3ca 100644
--- a/chrome/browser/extensions/activity_log/activity_log.h
+++ b/chrome/browser/extensions/activity_log/activity_log.h
@@ -22,6 +22,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/browser/script_executor.h"
 #include "extensions/common/dom_action_types.h"
@@ -38,7 +39,6 @@
 
 namespace extensions {
 class Extension;
-class ExtensionRegistry;
 class ExtensionSystem;
 
 // A utility for tracing interesting activity for each extension.
@@ -217,7 +217,7 @@
   // added or removed, enabled_ may change.
   ScopedObserver<extensions::ExtensionRegistry,
                  extensions::ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   // The number of active consumers of the activity log.
   // TODO(kelvinjiang): eliminate this flag if possible and use has_listeners_
diff --git a/chrome/browser/extensions/api/commands/command_service.cc b/chrome/browser/extensions/api/commands/command_service.cc
index 6f1e04f..1bba6ab 100644
--- a/chrome/browser/extensions/api/commands/command_service.cc
+++ b/chrome/browser/extensions/api/commands/command_service.cc
@@ -27,7 +27,6 @@
 #include "content/public/browser/notification_service.h"
 #include "extensions/browser/extension_function_registry.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/common/feature_switch.h"
@@ -109,8 +108,7 @@
 }
 
 CommandService::CommandService(content::BrowserContext* context)
-    : profile_(Profile::FromBrowserContext(context)),
-      extension_registry_observer_(this) {
+    : profile_(Profile::FromBrowserContext(context)) {
   ExtensionFunctionRegistry::GetInstance()
       .RegisterFunction<GetAllCommandsFunction>();
 
diff --git a/chrome/browser/extensions/api/commands/command_service.h b/chrome/browser/extensions/api/commands/command_service.h
index 6f3e1e89..73251ed8 100644
--- a/chrome/browser/extensions/api/commands/command_service.h
+++ b/chrome/browser/extensions/api/commands/command_service.h
@@ -12,6 +12,7 @@
 #include "base/scoped_observer.h"
 #include "chrome/common/extensions/command.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension.h"
 
@@ -30,7 +31,6 @@
 }
 
 namespace extensions {
-class ExtensionRegistry;
 
 // This service keeps track of preferences related to extension commands
 // (assigning initial keybindings on install and removing them on deletion
@@ -263,7 +263,7 @@
   Profile* profile_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::ObserverList<Observer>::Unchecked observers_;
 
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_service.cc b/chrome/browser/extensions/api/content_settings/content_settings_service.cc
index 4775799..5ef415d 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_service.cc
+++ b/chrome/browser/extensions/api/content_settings/content_settings_service.cc
@@ -6,15 +6,13 @@
 
 #include "base/lazy_instance.h"
 #include "base/memory/scoped_refptr.h"
-#include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_scope.h"
 #include "extensions/browser/pref_names.h"
 
 namespace extensions {
 
 ContentSettingsService::ContentSettingsService(content::BrowserContext* context)
-    : content_settings_store_(base::MakeRefCounted<ContentSettingsStore>()),
-      scoped_observer_(this) {}
+    : content_settings_store_(base::MakeRefCounted<ContentSettingsStore>()) {}
 
 ContentSettingsService::~ContentSettingsService() {}
 
diff --git a/chrome/browser/extensions/api/content_settings/content_settings_service.h b/chrome/browser/extensions/api/content_settings/content_settings_service.h
index 4887f5e..c5a86f87f 100644
--- a/chrome/browser/extensions/api/content_settings/content_settings_service.h
+++ b/chrome/browser/extensions/api/content_settings/content_settings_service.h
@@ -10,12 +10,12 @@
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/api/content_settings/content_settings_store.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_observer.h"
 
 namespace extensions {
 
 class ContentSettingsStore;
-class ExtensionPrefs;
 
 // This service hosts a single ContentSettingsStore for the
 // chrome.contentSettings API.
@@ -59,7 +59,7 @@
   static const char* service_name() { return "ContentSettingsService"; }
 
   scoped_refptr<ContentSettingsStore> content_settings_store_;
-  ScopedObserver<ExtensionPrefs, ExtensionPrefsObserver> scoped_observer_;
+  ScopedObserver<ExtensionPrefs, ExtensionPrefsObserver> scoped_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ContentSettingsService);
 };
diff --git a/chrome/browser/extensions/api/debugger/debugger_api.cc b/chrome/browser/extensions/api/debugger/debugger_api.cc
index 6c79f077..9e2cb329 100644
--- a/chrome/browser/extensions/api/debugger/debugger_api.cc
+++ b/chrome/browser/extensions/api/debugger/debugger_api.cc
@@ -180,7 +180,7 @@
 
   // Listen to extension unloaded notification.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionDevToolsClientHost);
 };
@@ -195,8 +195,7 @@
       extension_(std::move(extension)),
       last_request_id_(0),
       infobar_(nullptr),
-      detach_reason_(api::debugger::DETACH_REASON_TARGET_CLOSED),
-      extension_registry_observer_(this) {
+      detach_reason_(api::debugger::DETACH_REASON_TARGET_CLOSED) {
   CopyDebuggee(&debuggee_, debuggee);
 
   g_attached_client_hosts.Get().insert(this);
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc
index 5d8e60d..a63a5d9 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.cc
@@ -10,7 +10,6 @@
 #include "base/strings/stringprintf.h"
 #include "base/values.h"
 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
-#include "components/bookmarks/browser/bookmark_model.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/common/api/declarative/declarative_constants.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -147,11 +146,10 @@
 //
 
 DeclarativeContentIsBookmarkedConditionTracker::
-DeclarativeContentIsBookmarkedConditionTracker(content::BrowserContext* context,
-                                               Delegate* delegate)
-    : delegate_(delegate),
-      extensive_bookmark_changes_in_progress_(0),
-      scoped_bookmarks_observer_(this) {
+    DeclarativeContentIsBookmarkedConditionTracker(
+        content::BrowserContext* context,
+        Delegate* delegate)
+    : delegate_(delegate), extensive_bookmark_changes_in_progress_(0) {
   bookmarks::BookmarkModel* bookmark_model =
       BookmarkModelFactory::GetForBrowserContext(context);
   // Can be null during unit test execution.
diff --git a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h
index 7cbc0b2..76b38a7 100644
--- a/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h
+++ b/chrome/browser/extensions/api/declarative_content/declarative_content_is_bookmarked_condition_tracker.h
@@ -14,6 +14,7 @@
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/api/declarative_content/content_predicate_evaluator.h"
 #include "components/bookmarks/browser/base_bookmark_model_observer.h"
+#include "components/bookmarks/browser/bookmark_model.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "extensions/common/extension.h"
 
@@ -158,7 +159,7 @@
   int extensive_bookmark_changes_in_progress_;
 
   ScopedObserver<bookmarks::BookmarkModel, bookmarks::BookmarkModelObserver>
-      scoped_bookmarks_observer_;
+      scoped_bookmarks_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(DeclarativeContentIsBookmarkedConditionTracker);
 };
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
index 6cd0dd1f..568341e 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc
@@ -358,16 +358,7 @@
 }
 
 DeveloperPrivateEventRouter::DeveloperPrivateEventRouter(Profile* profile)
-    : extension_registry_observer_(this),
-      error_console_observer_(this),
-      process_manager_observer_(this),
-      app_window_registry_observer_(this),
-      warning_service_observer_(this),
-      extension_prefs_observer_(this),
-      extension_management_observer_(this),
-      command_service_observer_(this),
-      profile_(profile),
-      event_router_(EventRouter::Get(profile_)) {
+    : profile_(profile), event_router_(EventRouter::Get(profile_)) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
   error_console_observer_.Add(ErrorConsole::Get(profile));
   process_manager_observer_.Add(ProcessManager::Get(profile));
@@ -955,9 +946,8 @@
   return RespondNow(NoArguments());
 }
 
-DeveloperPrivateReloadFunction::DeveloperPrivateReloadFunction()
-    : registry_observer_(this), error_reporter_observer_(this) {}
-DeveloperPrivateReloadFunction::~DeveloperPrivateReloadFunction() {}
+DeveloperPrivateReloadFunction::DeveloperPrivateReloadFunction() = default;
+DeveloperPrivateReloadFunction::~DeveloperPrivateReloadFunction() = default;
 
 ExtensionFunction::ResponseAction DeveloperPrivateReloadFunction::Run() {
   std::unique_ptr<Reload::Params> params(Reload::Params::Create(*args_));
diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.h b/chrome/browser/extensions/api/developer_private/developer_private_api.h
index 639cf9e..33955e2 100644
--- a/chrome/browser/extensions/api/developer_private/developer_private_api.h
+++ b/chrome/browser/extensions/api/developer_private/developer_private_api.h
@@ -30,8 +30,11 @@
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_function.h"
+#include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_observer.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
+#include "extensions/browser/process_manager.h"
 #include "extensions/browser/process_manager_observer.h"
 #include "extensions/browser/warning_service.h"
 #include "storage/browser/fileapi/file_system_context.h"
@@ -45,8 +48,6 @@
 class EventRouter;
 class ExtensionError;
 class ExtensionInfoGenerator;
-class ExtensionRegistry;
-class ProcessManager;
 
 namespace api {
 
@@ -144,20 +145,21 @@
       std::vector<api::developer_private::ExtensionInfo> infos);
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
-  ScopedObserver<ErrorConsole, ErrorConsole::Observer> error_console_observer_;
+      extension_registry_observer_{this};
+  ScopedObserver<ErrorConsole, ErrorConsole::Observer> error_console_observer_{
+      this};
   ScopedObserver<ProcessManager, ProcessManagerObserver>
-      process_manager_observer_;
+      process_manager_observer_{this};
   ScopedObserver<AppWindowRegistry, AppWindowRegistry::Observer>
-      app_window_registry_observer_;
+      app_window_registry_observer_{this};
   ScopedObserver<WarningService, WarningService::Observer>
-      warning_service_observer_;
+      warning_service_observer_{this};
   ScopedObserver<ExtensionPrefs, ExtensionPrefsObserver>
-      extension_prefs_observer_;
+      extension_prefs_observer_{this};
   ScopedObserver<ExtensionManagement, ExtensionManagement::Observer>
-      extension_management_observer_;
+      extension_management_observer_{this};
   ScopedObserver<CommandService, CommandService::Observer>
-      command_service_observer_;
+      command_service_observer_{this};
 
   Profile* profile_;
 
@@ -464,9 +466,9 @@
   base::FilePath reloading_extension_path_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
   ScopedObserver<LoadErrorReporter, LoadErrorReporter::Observer>
-      error_reporter_observer_;
+      error_reporter_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(DeveloperPrivateReloadFunction);
 };
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.cc b/chrome/browser/extensions/api/downloads/downloads_api.cc
index 7ba05fc..6f0a105 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.cc
+++ b/chrome/browser/extensions/api/downloads/downloads_api.cc
@@ -70,7 +70,6 @@
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_function_dispatcher.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/browser/warning_service.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -1611,9 +1610,7 @@
 ExtensionDownloadsEventRouter::ExtensionDownloadsEventRouter(
     Profile* profile,
     DownloadManager* manager)
-    : profile_(profile),
-      notifier_(manager, this),
-      extension_registry_observer_(this) {
+    : profile_(profile), notifier_(manager, this) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(profile_);
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
diff --git a/chrome/browser/extensions/api/downloads/downloads_api.h b/chrome/browser/extensions/api/downloads/downloads_api.h
index 1f39ba0..d0b6f83 100644
--- a/chrome/browser/extensions/api/downloads/downloads_api.h
+++ b/chrome/browser/extensions/api/downloads/downloads_api.h
@@ -20,16 +20,13 @@
 #include "components/download/public/common/download_path_reservation_tracker.h"
 #include "content/public/browser/download_manager.h"
 #include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/browser/warning_set.h"
 
 class DownloadFileIconExtractor;
 class DownloadOpenPrompt;
 
-namespace extensions {
-class ExtensionRegistry;
-}
-
 // Functions in the chrome.downloads namespace facilitate
 // controlling downloads from extensions. See the full API doc at
 // http://goo.gl/6hO1n
@@ -395,7 +392,7 @@
   // Listen to extension unloaded notifications.
   ScopedObserver<extensions::ExtensionRegistry,
                  extensions::ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionDownloadsEventRouter);
 };
diff --git a/chrome/browser/extensions/api/file_system/file_system_apitest.cc b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
index 0c5ea4b2..d624301f 100644
--- a/chrome/browser/extensions/api/file_system/file_system_apitest.cc
+++ b/chrome/browser/extensions/api/file_system/file_system_apitest.cc
@@ -33,7 +33,7 @@
  public:
   AppLoadObserver(content::BrowserContext* browser_context,
                   base::Callback<void(const Extension*)> callback)
-      : callback_(callback), extension_registry_observer_(this) {
+      : callback_(callback) {
     extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context));
   }
 
@@ -45,7 +45,7 @@
  private:
   base::Callback<void(const Extension*)> callback_;
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
   DISALLOW_COPY_AND_ASSIGN(AppLoadObserver);
 };
 
diff --git a/chrome/browser/extensions/api/history/history_api.cc b/chrome/browser/extensions/api/history/history_api.cc
index 010b847..1692d8d95 100644
--- a/chrome/browser/extensions/api/history/history_api.cc
+++ b/chrome/browser/extensions/api/history/history_api.cc
@@ -132,7 +132,7 @@
 
 HistoryEventRouter::HistoryEventRouter(Profile* profile,
                                        history::HistoryService* history_service)
-    : profile_(profile), history_service_observer_(this) {
+    : profile_(profile) {
   DCHECK(profile);
   history_service_observer_.Add(history_service);
 }
diff --git a/chrome/browser/extensions/api/history/history_api.h b/chrome/browser/extensions/api/history/history_api.h
index 5349fca..8c3814e 100644
--- a/chrome/browser/extensions/api/history/history_api.h
+++ b/chrome/browser/extensions/api/history/history_api.h
@@ -13,6 +13,7 @@
 #include "base/scoped_observer.h"
 #include "base/task/cancelable_task_tracker.h"
 #include "chrome/common/extensions/api/history.h"
+#include "components/history/core/browser/history_service.h"
 #include "components/history/core/browser/history_service_observer.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/browser/event_router.h"
@@ -24,10 +25,6 @@
 class ListValue;
 }
 
-namespace history {
-class HistoryService;
-}
-
 namespace extensions {
 
 // Observes History service and routes the notifications as events to the
@@ -55,7 +52,7 @@
 
   Profile* profile_;
   ScopedObserver<history::HistoryService, history::HistoryServiceObserver>
-      history_service_observer_;
+      history_service_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(HistoryEventRouter);
 };
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_manager.cc b/chrome/browser/extensions/api/image_writer_private/operation_manager.cc
index f0dbae03..e8010124 100644
--- a/chrome/browser/extensions/api/image_writer_private/operation_manager.cc
+++ b/chrome/browser/extensions/api/image_writer_private/operation_manager.cc
@@ -23,7 +23,6 @@
 #include "content/public/browser/storage_partition.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_host.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/notification_types.h"
 
 #if defined(OS_CHROMEOS)
@@ -38,7 +37,7 @@
 using content::BrowserThread;
 
 OperationManager::OperationManager(content::BrowserContext* context)
-    : browser_context_(context), extension_registry_observer_(this) {
+    : browser_context_(context) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
   Profile* profile = Profile::FromBrowserContext(browser_context_);
   registrar_.Add(this,
diff --git a/chrome/browser/extensions/api/image_writer_private/operation_manager.h b/chrome/browser/extensions/api/image_writer_private/operation_manager.h
index ae2a04f..f45645f 100644
--- a/chrome/browser/extensions/api/image_writer_private/operation_manager.h
+++ b/chrome/browser/extensions/api/image_writer_private/operation_manager.h
@@ -18,6 +18,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension_id.h"
 #include "url/gurl.h"
@@ -29,7 +30,6 @@
 }
 
 namespace extensions {
-class ExtensionRegistry;
 
 namespace image_writer {
 
@@ -116,7 +116,7 @@
 
   // Listen to extension unloaded notification.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::WeakPtrFactory<OperationManager> weak_factory_{this};
 
diff --git a/chrome/browser/extensions/api/input_ime/input_ime_api.cc b/chrome/browser/extensions/api/input_ime/input_ime_api.cc
index 9bfb7c8e..80eef43 100644
--- a/chrome/browser/extensions/api/input_ime/input_ime_api.cc
+++ b/chrome/browser/extensions/api/input_ime/input_ime_api.cc
@@ -420,7 +420,7 @@
 }
 
 InputImeAPI::InputImeAPI(content::BrowserContext* context)
-    : browser_context_(context), extension_registry_observer_(this) {
+    : browser_context_(context) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
 
   EventRouter* event_router = EventRouter::Get(browser_context_);
diff --git a/chrome/browser/extensions/api/input_ime/input_ime_api.h b/chrome/browser/extensions/api/input_ime/input_ime_api.h
index 2822538..c42805b 100644
--- a/chrome/browser/extensions/api/input_ime/input_ime_api.h
+++ b/chrome/browser/extensions/api/input_ime/input_ime_api.h
@@ -218,7 +218,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   content::NotificationRegistrar registrar_;
 
diff --git a/chrome/browser/extensions/api/omnibox/omnibox_api.cc b/chrome/browser/extensions/api/omnibox/omnibox_api.cc
index 7438cd8..25f0690 100644
--- a/chrome/browser/extensions/api/omnibox/omnibox_api.cc
+++ b/chrome/browser/extensions/api/omnibox/omnibox_api.cc
@@ -26,7 +26,6 @@
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_factory.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/notification_types.h"
 #include "ui/gfx/image/image.h"
 
@@ -187,8 +186,7 @@
 
 OmniboxAPI::OmniboxAPI(content::BrowserContext* context)
     : profile_(Profile::FromBrowserContext(context)),
-      url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
-      extension_registry_observer_(this) {
+      url_service_(TemplateURLServiceFactory::GetForProfile(profile_)) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
   if (url_service_) {
     template_url_sub_ = url_service_->RegisterOnLoadedCallback(
diff --git a/chrome/browser/extensions/api/omnibox/omnibox_api.h b/chrome/browser/extensions/api/omnibox/omnibox_api.h
index 435ea64..9a648b7 100644
--- a/chrome/browser/extensions/api/omnibox/omnibox_api.h
+++ b/chrome/browser/extensions/api/omnibox/omnibox_api.h
@@ -18,6 +18,7 @@
 #include "components/omnibox/browser/autocomplete_match.h"
 #include "components/search_engines/template_url_service.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "ui/base/window_open_disposition.h"
 
@@ -35,7 +36,6 @@
 }
 
 namespace extensions {
-class ExtensionRegistry;
 
 // Event router class for events related to the omnibox API.
 class ExtensionOmniboxEventRouter {
@@ -134,7 +134,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   // Keeps track of favicon-sized omnibox icons for extensions.
   ExtensionIconManager omnibox_icon_manager_;
diff --git a/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc b/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc
index 7462a00..3fa5004 100644
--- a/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc
+++ b/chrome/browser/extensions/api/platform_keys/verify_trust_api.cc
@@ -91,7 +91,7 @@
 }
 
 VerifyTrustAPI::VerifyTrustAPI(content::BrowserContext* context)
-    : io_part_(new IOPart), registry_observer_(this) {
+    : io_part_(new IOPart) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   registry_observer_.Add(ExtensionRegistry::Get(context));
 }
diff --git a/chrome/browser/extensions/api/platform_keys/verify_trust_api.h b/chrome/browser/extensions/api/platform_keys/verify_trust_api.h
index 7c08e3d..f3b21ee 100644
--- a/chrome/browser/extensions/api/platform_keys/verify_trust_api.h
+++ b/chrome/browser/extensions/api/platform_keys/verify_trust_api.h
@@ -98,7 +98,7 @@
   std::unique_ptr<IOPart, content::BrowserThread::DeleteOnIOThread> io_part_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
 
   base::WeakPtrFactory<VerifyTrustAPI> weak_factory_{this};
 
diff --git a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
index 06c5812..2921111 100644
--- a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
+++ b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.cc
@@ -28,7 +28,6 @@
 #include "chrome/browser/ui/browser_window.h"
 #include "components/update_client/update_query_params.h"
 #include "content/public/browser/notification_service.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/browser/warning_service.h"
@@ -145,9 +144,7 @@
 
 ChromeRuntimeAPIDelegate::ChromeRuntimeAPIDelegate(
     content::BrowserContext* context)
-    : browser_context_(context),
-      registered_for_updates_(false),
-      extension_registry_observer_(this) {
+    : browser_context_(context), registered_for_updates_(false) {
   registrar_.Add(this,
                  extensions::NOTIFICATION_EXTENSION_UPDATE_FOUND,
                  content::NotificationService::AllSources());
diff --git a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h
index 3b19513..7abc720 100644
--- a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h
+++ b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate.h
@@ -15,6 +15,7 @@
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/api/runtime/runtime_api.h"
 #include "extensions/browser/api/runtime/runtime_api_delegate.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 namespace base {
@@ -29,7 +30,6 @@
 }
 
 namespace extensions {
-class ExtensionRegistry;
 class RuntimeAPI;
 class UpdateObserver;
 }
@@ -92,7 +92,7 @@
 
   ScopedObserver<extensions::ExtensionRegistry,
                  extensions::ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ChromeRuntimeAPIDelegate);
 };
diff --git a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc
index af092f1b..c22736886 100644
--- a/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc
+++ b/chrome/browser/extensions/api/runtime/chrome_runtime_api_delegate_unittest.cc
@@ -334,7 +334,7 @@
 class ExtensionLoadWaiter : public ExtensionRegistryObserver {
  public:
   explicit ExtensionLoadWaiter(content::BrowserContext* context)
-      : context_(context), extension_registry_observer_(this) {
+      : context_(context) {
     extension_registry_observer_.Add(ExtensionRegistry::Get(context_));
   }
 
@@ -366,7 +366,7 @@
   base::RunLoop run_loop_;
   content::BrowserContext* context_;
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionLoadWaiter);
 };
diff --git a/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc b/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc
index 706e3a5..a9d7d9a 100644
--- a/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc
+++ b/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.cc
@@ -22,7 +22,6 @@
 #include "components/search_engines/template_url_prepopulate_data.h"
 #include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_factory.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/error_utils.h"
 #include "extensions/common/manifest_constants.h"
 
@@ -110,8 +109,7 @@
 
 SettingsOverridesAPI::SettingsOverridesAPI(content::BrowserContext* context)
     : profile_(Profile::FromBrowserContext(context)),
-      url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
-      extension_registry_observer_(this) {
+      url_service_(TemplateURLServiceFactory::GetForProfile(profile_)) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
 }
 
diff --git a/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.h b/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.h
index 3c80f897..baf7419 100644
--- a/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.h
+++ b/chrome/browser/extensions/api/settings_overrides/settings_overrides_api.h
@@ -13,12 +13,12 @@
 #include "base/scoped_observer.h"
 #include "components/search_engines/template_url_service.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class Profile;
 
 namespace extensions {
-class ExtensionRegistry;
 
 class SettingsOverridesAPI : public BrowserContextKeyedAPI,
                              public ExtensionRegistryObserver {
@@ -56,7 +56,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(SettingsOverridesAPI);
 };
diff --git a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.cc b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.cc
index 8b6576e..aa0cb34 100644
--- a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.cc
+++ b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.cc
@@ -19,7 +19,6 @@
 #include "components/sync_device_info/device_info.h"
 #include "components/sync_device_info/device_info_sync_service.h"
 #include "extensions/browser/event_router.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 
 using syncer::DeviceInfo;
@@ -93,13 +92,9 @@
   return g_signed_in_devices_manager_factory.Pointer();
 }
 
-SignedInDevicesManager::SignedInDevicesManager()
-    : profile_(NULL), extension_registry_observer_(this) {
-}
-
+SignedInDevicesManager::SignedInDevicesManager() = default;
 SignedInDevicesManager::SignedInDevicesManager(content::BrowserContext* context)
-    : profile_(Profile::FromBrowserContext(context)),
-      extension_registry_observer_(this) {
+    : profile_(Profile::FromBrowserContext(context)) {
   EventRouter* router = EventRouter::Get(profile_);
   if (router) {
     router->RegisterObserver(
diff --git a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.h b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.h
index bb7ab76b..512d83d3 100644
--- a/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.h
+++ b/chrome/browser/extensions/api/signed_in_devices/signed_in_devices_manager.h
@@ -16,6 +16,7 @@
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
 #include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class Profile;
@@ -26,7 +27,6 @@
 
 namespace extensions {
 class BrowserContextKeyedAPI;
-class ExtensionRegistry;
 
 struct EventListenerInfo;
 
@@ -54,6 +54,8 @@
   std::string extension_id_;
   Profile* const profile_;
   content::NotificationRegistrar registrar_;
+
+  DISALLOW_COPY_AND_ASSIGN(SignedInDevicesChangeObserver);
 };
 
 class SignedInDevicesManager : public BrowserContextKeyedAPI,
@@ -90,12 +92,12 @@
 
   void RemoveChangeObserverForExtension(const std::string& extension_id);
 
-  Profile* const profile_;
+  Profile* const profile_ = nullptr;
   std::vector<std::unique_ptr<SignedInDevicesChangeObserver>> change_observers_;
 
   // Listen to extension unloaded notification.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   FRIEND_TEST_ALL_PREFIXES(SignedInDevicesManager, UpdateListener);
 
diff --git a/chrome/browser/extensions/api/spellcheck/spellcheck_api.cc b/chrome/browser/extensions/api/spellcheck/spellcheck_api.cc
index 2605ca7..0ba0c2d 100644
--- a/chrome/browser/extensions/api/spellcheck/spellcheck_api.cc
+++ b/chrome/browser/extensions/api/spellcheck/spellcheck_api.cc
@@ -8,7 +8,6 @@
 #include "chrome/browser/spellchecker/spellcheck_factory.h"
 #include "chrome/browser/spellchecker/spellcheck_service.h"
 #include "chrome/common/extensions/api/spellcheck/spellcheck_handler.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/manifest_constants.h"
 
 namespace extensions {
@@ -36,13 +35,11 @@
 
 }  // namespace
 
-SpellcheckAPI::SpellcheckAPI(content::BrowserContext* context)
-    : extension_registry_observer_(this) {
+SpellcheckAPI::SpellcheckAPI(content::BrowserContext* context) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(context));
 }
 
-SpellcheckAPI::~SpellcheckAPI() {
-}
+SpellcheckAPI::~SpellcheckAPI() = default;
 
 static base::LazyInstance<BrowserContextKeyedAPIFactory<SpellcheckAPI>>::
     DestructorAtExit g_spellcheck_api_factory = LAZY_INSTANCE_INITIALIZER;
diff --git a/chrome/browser/extensions/api/spellcheck/spellcheck_api.h b/chrome/browser/extensions/api/spellcheck/spellcheck_api.h
index 71ed27a..b75884c6 100644
--- a/chrome/browser/extensions/api/spellcheck/spellcheck_api.h
+++ b/chrome/browser/extensions/api/spellcheck/spellcheck_api.h
@@ -8,10 +8,10 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 namespace extensions {
-class ExtensionRegistry;
 
 class SpellcheckAPI : public BrowserContextKeyedAPI,
                       public ExtensionRegistryObserver {
@@ -39,7 +39,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(SpellcheckAPI);
 };
diff --git a/chrome/browser/extensions/api/storage/managed_value_store_cache.cc b/chrome/browser/extensions/api/storage/managed_value_store_cache.cc
index 91b42ed..13344e4 100644
--- a/chrome/browser/extensions/api/storage/managed_value_store_cache.cc
+++ b/chrome/browser/extensions/api/storage/managed_value_store_cache.cc
@@ -99,7 +99,7 @@
   Profile* profile_;
   policy::PolicyDomain policy_domain_;
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
   policy::SchemaRegistry* schema_registry_;
   base::WeakPtrFactory<ExtensionTracker> weak_factory_{this};
 
@@ -111,7 +111,6 @@
     policy::PolicyDomain policy_domain)
     : profile_(profile),
       policy_domain_(policy_domain),
-      extension_registry_observer_(this),
       schema_registry_(profile->GetPolicySchemaRegistryService()->registry()) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
   // Load schemas when the extension system is ready. It might be ready now.
diff --git a/chrome/browser/extensions/api/system_indicator/system_indicator_manager.cc b/chrome/browser/extensions/api/system_indicator/system_indicator_manager.cc
index 926161b..6d8f4bc 100644
--- a/chrome/browser/extensions/api/system_indicator/system_indicator_manager.cc
+++ b/chrome/browser/extensions/api/system_indicator/system_indicator_manager.cc
@@ -18,7 +18,6 @@
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_icon_image.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_icon_set.h"
 
@@ -141,9 +140,7 @@
 
 SystemIndicatorManager::SystemIndicatorManager(Profile* profile,
                                                StatusTray* status_tray)
-    : profile_(profile),
-      status_tray_(status_tray),
-      extension_registry_observer_(this) {
+    : profile_(profile), status_tray_(status_tray) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
 }
 
diff --git a/chrome/browser/extensions/api/system_indicator/system_indicator_manager.h b/chrome/browser/extensions/api/system_indicator/system_indicator_manager.h
index 378cc6a5..4055d0189 100644
--- a/chrome/browser/extensions/api/system_indicator/system_indicator_manager.h
+++ b/chrome/browser/extensions/api/system_indicator/system_indicator_manager.h
@@ -13,6 +13,7 @@
 #include "base/scoped_observer.h"
 #include "base/threading/thread_checker.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension_icon_set.h"
 #include "extensions/common/extension_id.h"
@@ -25,7 +26,6 @@
 FORWARD_DECLARE_TEST(SystemIndicatorApiTest, SystemIndicatorUnloaded);
 
 class ExtensionIndicatorIcon;
-class ExtensionRegistry;
 
 // Keeps track of all the systemIndicator icons created for a given Profile
 // that are currently visible in the UI.  Use SystemIndicatorManagerFactory to
@@ -86,7 +86,7 @@
   base::ThreadChecker thread_checker_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(SystemIndicatorManager);
 };
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
index 9a140c1..a761a4ef 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.cc
@@ -19,7 +19,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "extensions/browser/event_router.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 
 using content::BrowserThread;
@@ -140,7 +139,7 @@
 };
 
 TabCaptureRegistry::TabCaptureRegistry(content::BrowserContext* context)
-    : browser_context_(context), extension_registry_observer_(this) {
+    : browser_context_(context) {
   MediaCaptureDevicesDispatcher::GetInstance()->AddObserver(this);
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
 }
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.h b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.h
index 5f02c88..586d8949 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_registry.h
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_registry.h
@@ -16,6 +16,7 @@
 #include "content/public/browser/desktop_media_id.h"
 #include "content/public/browser/media_request_state.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 namespace base {
@@ -29,8 +30,6 @@
 
 namespace extensions {
 
-class ExtensionRegistry;
-
 namespace tab_capture = api::tab_capture;
 
 class TabCaptureRegistry : public BrowserContextKeyedAPI,
@@ -118,7 +117,7 @@
   std::vector<std::unique_ptr<LiveRequest>> requests_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TabCaptureRegistry);
 };
diff --git a/chrome/browser/extensions/api/tabs/tabs_event_router.cc b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
index f86ebb9..5459c09 100644
--- a/chrome/browser/extensions/api/tabs/tabs_event_router.cc
+++ b/chrome/browser/extensions/api/tabs/tabs_event_router.cc
@@ -162,10 +162,7 @@
 }
 
 TabsEventRouter::TabsEventRouter(Profile* profile)
-    : profile_(profile),
-      favicon_scoped_observer_(this),
-      browser_tab_strip_tracker_(this, this, this),
-      tab_manager_scoped_observer_(this) {
+    : profile_(profile), browser_tab_strip_tracker_(this, this, this) {
   DCHECK(!profile->IsOffTheRecord());
 
   browser_tab_strip_tracker_.Init();
diff --git a/chrome/browser/extensions/api/tabs/tabs_event_router.h b/chrome/browser/extensions/api/tabs/tabs_event_router.h
index 14f37b6f..e7335a90 100644
--- a/chrome/browser/extensions/api/tabs/tabs_event_router.h
+++ b/chrome/browser/extensions/api/tabs/tabs_event_router.h
@@ -18,6 +18,7 @@
 #include "chrome/browser/ui/browser_tab_strip_tracker.h"
 #include "chrome/browser/ui/browser_tab_strip_tracker_delegate.h"
 #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
+#include "components/favicon/core/favicon_driver.h"
 #include "components/favicon/core/favicon_driver_observer.h"
 #include "components/zoom/zoom_observer.h"
 #include "content/public/browser/web_contents_observer.h"
@@ -27,10 +28,6 @@
 class WebContents;
 }
 
-namespace favicon {
-class FaviconDriver;
-}
-
 namespace extensions {
 
 // The TabsEventRouter listens to tab events and routes them to listeners inside
@@ -205,13 +202,14 @@
   // The main profile that owns this event router.
   Profile* profile_;
 
-  ScopedObserver<favicon::FaviconDriver, TabsEventRouter>
-      favicon_scoped_observer_;
+  ScopedObserver<favicon::FaviconDriver, favicon::FaviconDriverObserver>
+      favicon_scoped_observer_{this};
 
   BrowserTabStripTracker browser_tab_strip_tracker_;
 
-  ScopedObserver<resource_coordinator::TabManager, TabsEventRouter>
-      tab_manager_scoped_observer_;
+  ScopedObserver<resource_coordinator::TabManager,
+                 resource_coordinator::TabLifecycleObserver>
+      tab_manager_scoped_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(TabsEventRouter);
 };
diff --git a/chrome/browser/extensions/api/tabs/windows_event_router.cc b/chrome/browser/extensions/api/tabs/windows_event_router.cc
index c890c88..5f41fa2 100644
--- a/chrome/browser/extensions/api/tabs/windows_event_router.cc
+++ b/chrome/browser/extensions/api/tabs/windows_event_router.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_tab_util.h"
 #include "chrome/browser/extensions/window_controller.h"
-#include "chrome/browser/extensions/window_controller_list.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/extensions/api/windows.h"
 #include "chrome/common/extensions/extension_constants.h"
@@ -148,9 +147,7 @@
 WindowsEventRouter::WindowsEventRouter(Profile* profile)
     : profile_(profile),
       focused_profile_(nullptr),
-      focused_window_id_(extension_misc::kUnknownWindowId),
-      observed_app_registry_(this),
-      observed_controller_list_(this) {
+      focused_window_id_(extension_misc::kUnknownWindowId) {
   DCHECK(!profile->IsOffTheRecord());
 
   observed_app_registry_.Add(AppWindowRegistry::Get(profile_));
diff --git a/chrome/browser/extensions/api/tabs/windows_event_router.h b/chrome/browser/extensions/api/tabs/windows_event_router.h
index 0f9d46f..ce4bcaec 100644
--- a/chrome/browser/extensions/api/tabs/windows_event_router.h
+++ b/chrome/browser/extensions/api/tabs/windows_event_router.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "build/build_config.h"
+#include "chrome/browser/extensions/window_controller_list.h"
 #include "chrome/browser/extensions/window_controller_list_observer.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
@@ -32,7 +33,6 @@
 
 class AppWindow;
 class AppWindowController;
-class WindowControllerList;
 
 // The WindowsEventRouter sends chrome.windows.* events to listeners
 // inside extension process renderers. The router listens to *all* events,
@@ -97,11 +97,11 @@
 
   // Observed AppWindowRegistry.
   ScopedObserver<AppWindowRegistry, AppWindowRegistry::Observer>
-      observed_app_registry_;
+      observed_app_registry_{this};
 
   // Observed WindowControllerList.
   ScopedObserver<WindowControllerList, WindowControllerListObserver>
-      observed_controller_list_;
+      observed_controller_list_{this};
 
   DISALLOW_COPY_AND_ASSIGN(WindowsEventRouter);
 };
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
index dcff38d5..8cc6edf 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc
@@ -24,6 +24,7 @@
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/install_tracker.h"
+#include "chrome/browser/extensions/scoped_active_install.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
 #include "chrome/browser/safe_browsing/safe_browsing_service.h"
diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.h b/chrome/browser/extensions/api/webstore_private/webstore_private_api.h
index 72f6544..7bb1172e 100644
--- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.h
+++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.h
@@ -26,6 +26,7 @@
 namespace extensions {
 
 class Extension;
+class ScopedActiveInstall;
 
 class WebstorePrivateApi {
  public:
diff --git a/chrome/browser/extensions/chrome_app_icon_service.cc b/chrome/browser/extensions/chrome_app_icon_service.cc
index e6cfbe5..e5a184e 100644
--- a/chrome/browser/extensions/chrome_app_icon_service.cc
+++ b/chrome/browser/extensions/chrome_app_icon_service.cc
@@ -8,7 +8,6 @@
 #include "base/threading/thread_task_runner_handle.h"
 #include "chrome/browser/extensions/chrome_app_icon.h"
 #include "chrome/browser/extensions/chrome_app_icon_service_factory.h"
-#include "extensions/browser/extension_registry.h"
 
 namespace extensions {
 
@@ -20,7 +19,7 @@
 }
 
 ChromeAppIconService::ChromeAppIconService(content::BrowserContext* context)
-    : context_(context), observer_(this) {
+    : context_(context) {
 #if defined(OS_CHROMEOS)
   app_updater_ = std::make_unique<LauncherExtensionAppUpdater>(this, context);
 #endif
diff --git a/chrome/browser/extensions/chrome_app_icon_service.h b/chrome/browser/extensions/chrome_app_icon_service.h
index 264742f..841f6ae 100644
--- a/chrome/browser/extensions/chrome_app_icon_service.h
+++ b/chrome/browser/extensions/chrome_app_icon_service.h
@@ -14,6 +14,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 #if defined(OS_CHROMEOS)
@@ -114,7 +115,7 @@
 
   IconMap icon_map_;
 
-  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
+  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_{this};
 
   base::WeakPtrFactory<ChromeAppIconService> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/extensions/chrome_extension_cookies.cc b/chrome/browser/extensions/chrome_extension_cookies.cc
index a14cb64..99a1850 100644
--- a/chrome/browser/extensions/chrome_extension_cookies.cc
+++ b/chrome/browser/extensions/chrome_extension_cookies.cc
@@ -23,7 +23,7 @@
 namespace extensions {
 
 ChromeExtensionCookies::ChromeExtensionCookies(Profile* profile)
-    : profile_(profile), cookie_settings_observer_(this) {
+    : profile_(profile) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   cookie_settings_ = CookieSettingsFactory::GetForProfile(profile);
   cookie_settings_observer_.Add(cookie_settings_.get());
diff --git a/chrome/browser/extensions/chrome_extension_cookies.h b/chrome/browser/extensions/chrome_extension_cookies.h
index f3cdb2c..b1c900a 100644
--- a/chrome/browser/extensions/chrome_extension_cookies.h
+++ b/chrome/browser/extensions/chrome_extension_cookies.h
@@ -126,8 +126,9 @@
 
   // Cookie config Chrome-side.
   scoped_refptr<content_settings::CookieSettings> cookie_settings_;
-  ScopedObserver<content_settings::CookieSettings, ChromeExtensionCookies>
-      cookie_settings_observer_;
+  ScopedObserver<content_settings::CookieSettings,
+                 content_settings::CookieSettings::Observer>
+      cookie_settings_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ChromeExtensionCookies);
 };
diff --git a/chrome/browser/extensions/content_verifier_browsertest.cc b/chrome/browser/extensions/content_verifier_browsertest.cc
index 52f368d0..928c009 100644
--- a/chrome/browser/extensions/content_verifier_browsertest.cc
+++ b/chrome/browser/extensions/content_verifier_browsertest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/extensions/browsertest_util.h"
 #include "chrome/browser/extensions/chrome_content_verifier_delegate.h"
 #include "chrome/browser/extensions/content_verifier_test_utils.h"
+#include "chrome/browser/extensions/crx_installer.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
 #include "chrome/browser/extensions/extension_management_test_util.h"
 #include "chrome/browser/extensions/extension_service.h"
@@ -37,8 +38,7 @@
 #include "extensions/browser/management_policy.h"
 #include "extensions/browser/mock_external_provider.h"
 #include "extensions/browser/test_extension_registry_observer.h"
-#include "extensions/browser/updater/extension_downloader.h"
-#include "extensions/browser/updater/extension_downloader_test_delegate.h"
+#include "extensions/browser/updater/extension_update_data.h"
 #include "extensions/browser/updater/manifest_fetch_data.h"
 #include "extensions/common/extension_features.h"
 #include "extensions/common/extension_urls.h"
@@ -48,21 +48,47 @@
 namespace {
 constexpr char kTenMegResourceExtensionId[] =
     "mibjhafkjlepkpbjleahhallgddpjgle";
+
+class MockUpdateService : public UpdateService {
+ public:
+  MockUpdateService() : UpdateService(nullptr, nullptr) {}
+  MOCK_CONST_METHOD0(IsBusy, bool());
+  MOCK_CONST_METHOD1(CanUpdate, bool(const std::string& id));
+  MOCK_METHOD3(SendUninstallPing,
+               void(const std::string& id,
+                    const base::Version& version,
+                    int reason));
+  MOCK_METHOD2(StartUpdateCheck,
+               void(const ExtensionUpdateCheckParams& params,
+                    base::OnceClosure callback));
+};
+
+void ExtensionUpdateComplete(base::OnceClosure callback,
+                             const base::Optional<CrxInstallError>& error) {
+  // Expect success (no CrxInstallError). Assert on an error to put the error
+  // message into the test log to aid debugging.
+  ASSERT_FALSE(error.has_value()) << error->message();
+  std::move(callback).Run();
 }
 
+}  // namespace
+
 class ContentVerifierTest : public ExtensionBrowserTest {
  public:
   ContentVerifierTest() {}
   ~ContentVerifierTest() override {}
 
   void SetUp() override {
-    scoped_feature_list_.InitAndDisableFeature(
-        extensions_features::kNewExtensionUpdaterService);
     // Override content verification mode before ExtensionSystemImpl initializes
     // ChromeContentVerifierDelegate.
     ChromeContentVerifierDelegate::SetDefaultModeForTesting(
         ChromeContentVerifierDelegate::ENFORCE);
 
+    ON_CALL(update_service_, StartUpdateCheck)
+        .WillByDefault(Invoke(this, &ContentVerifierTest::OnUpdateCheck));
+    ON_CALL(update_service_, CanUpdate).WillByDefault(testing::Return(true));
+    UpdateService::SupplyUpdateServiceForTest(&update_service_);
+
     ExtensionBrowserTest::SetUp();
   }
 
@@ -73,6 +99,31 @@
 
   bool ShouldEnableContentVerification() override { return true; }
 
+  void AssertIsCorruptBitSetOnUpdateCheck(
+      const ExtensionUpdateCheckParams& params,
+      base::OnceClosure callback) {
+    ASSERT_FALSE(params.update_info.empty());
+    for (auto element : params.update_info) {
+      ASSERT_TRUE(element.second.is_corrupt_reinstall);
+    }
+    OnUpdateCheck(params, std::move(callback));
+  }
+
+  void OnUpdateCheck(const ExtensionUpdateCheckParams& params,
+                     base::OnceClosure callback) {
+    scoped_refptr<CrxInstaller> installer(
+        CrxInstaller::CreateSilent(extension_service()));
+    installer->set_install_source(Manifest::EXTERNAL_POLICY_DOWNLOAD);
+    installer->set_install_immediately(true);
+    installer->set_allow_silent_install(true);
+    installer->set_off_store_install_allow_reason(
+        CrxInstaller::OffStoreInstallAllowedInTest);
+    installer->set_installer_callback(
+        base::BindOnce(&ExtensionUpdateComplete, std::move(callback)));
+    installer->InstallCrx(
+        test_data_dir_.AppendASCII("content_verifier/v1.crx"));
+  }
+
   void TestContentScriptExtension(const std::string& crx_relpath,
                                   const std::string& id,
                                   const std::string& script_relpath) {
@@ -137,6 +188,7 @@
 
  protected:
   base::test::ScopedFeatureList scoped_feature_list_;
+  MockUpdateService update_service_;
 };
 
 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, DotSlashPaths) {
@@ -225,9 +277,7 @@
 
   // Setup fake policy and update check objects.
   content_verifier_test::ForceInstallProvider policy(kExtensionId);
-  content_verifier_test::DownloaderTestDelegate downloader;
   system->management_policy()->RegisterProvider(&policy);
-  ExtensionDownloader::set_test_delegate(&downloader);
   auto external_provider = std::make_unique<MockExternalProvider>(
       service, Manifest::EXTERNAL_POLICY_DOWNLOAD);
   external_provider->UpdateOrAddExtension(
@@ -244,14 +294,17 @@
       InstallExtension(crx_path, 1, Manifest::EXTERNAL_POLICY_DOWNLOAD);
   ASSERT_TRUE(extension);
 
-  downloader.AddResponse(kExtensionId, extension->VersionString(), crx_path);
-  EXPECT_EQ(kExtensionId, extension->id());
-
   TestExtensionRegistryObserver registry_observer(
       ExtensionRegistry::Get(profile()), kExtensionId);
   ContentVerifier* verifier = system->content_verifier();
   verifier->VerifyFailedForTest(kExtensionId, ContentVerifyJob::HASH_MISMATCH);
 
+  // Set our mock update client to check that the corrupt bit is set on the
+  // data structure it receives.
+  ON_CALL(update_service_, StartUpdateCheck)
+      .WillByDefault(Invoke(
+          this, &ContentVerifierTest::AssertIsCorruptBitSetOnUpdateCheck));
+
   // Make sure the extension first got disabled due to corruption.
   EXPECT_TRUE(registry_observer.WaitForExtensionUnloaded());
   ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
@@ -264,25 +317,6 @@
 
   reasons = prefs->GetDisableReasons(kExtensionId);
   EXPECT_FALSE(reasons & disable_reason::DISABLE_CORRUPTED);
-
-  // Make sure that the update check request properly included a parameter
-  // indicating that this was a corrupt policy reinstall.
-  bool found = false;
-  for (const auto& request : downloader.requests()) {
-    if (request->Includes(kExtensionId)) {
-      std::string query = request->full_url().query();
-      for (const auto& part : base::SplitString(
-               query, "&", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL)) {
-        if (base::StartsWith(part, "x=", base::CompareCase::SENSITIVE) &&
-            part.find(std::string("id%3D") + kExtensionId) !=
-                std::string::npos) {
-          found = true;
-          EXPECT_NE(std::string::npos, part.find("installsource%3Dreinstall"));
-        }
-      }
-    }
-  }
-  EXPECT_TRUE(found);
 }
 
 // Tests that verification failure during navigating to an extension resource
@@ -418,12 +452,6 @@
     ExtensionManagementPolicyUpdater management_policy(&policy_provider_);
     management_policy.SetIndividualExtensionAutoInstalled(
         id_, extension_urls::kChromeWebstoreUpdateURL, true /* forced */);
-
-    ExtensionDownloader::set_test_delegate(&downloader_);
-    base::FilePath crx_path =
-        test_data_dir_.AppendASCII("content_verifier/v1.crx");
-    std::string version = "2";
-    downloader_.AddResponse(id_, version, crx_path);
   }
 
   void SetUpOnMainThread() override {
@@ -436,7 +464,6 @@
 
  private:
   policy::MockConfigurationPolicyProvider policy_provider_;
-  content_verifier_test::DownloaderTestDelegate downloader_;
 };
 
 // We want to test what happens at startup with a corroption-disabled policy
diff --git a/chrome/browser/extensions/error_console/error_console.cc b/chrome/browser/extensions/error_console/error_console.cc
index f0560e2a..6cabcac 100644
--- a/chrome/browser/extensions/error_console/error_console.cc
+++ b/chrome/browser/extensions/error_console/error_console.cc
@@ -23,7 +23,6 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_source.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/constants.h"
 #include "extensions/common/extension.h"
@@ -62,8 +61,7 @@
     : enabled_(false),
       default_mask_(kDefaultMask),
       profile_(profile),
-      prefs_(nullptr),
-      registry_observer_(this) {
+      prefs_(nullptr) {
   pref_registrar_.Init(profile_->GetPrefs());
   pref_registrar_.Add(prefs::kExtensionsUIDeveloperMode,
                       base::Bind(&ErrorConsole::OnPrefChanged,
diff --git a/chrome/browser/extensions/error_console/error_console.h b/chrome/browser/extensions/error_console/error_console.h
index 1184887..dc628b5 100644
--- a/chrome/browser/extensions/error_console/error_console.h
+++ b/chrome/browser/extensions/error_console/error_console.h
@@ -22,6 +22,7 @@
 #include "content/public/browser/notification_registrar.h"
 #include "extensions/browser/error_map.h"
 #include "extensions/browser/extension_error.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 namespace content {
@@ -35,7 +36,6 @@
 namespace extensions {
 class Extension;
 class ExtensionPrefs;
-class ExtensionRegistry;
 
 // The ErrorConsole is a central object to which all extension errors are
 // reported. This includes errors detected in extensions core, as well as
@@ -200,7 +200,7 @@
   PrefChangeRegistrar pref_registrar_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ErrorConsole);
 };
diff --git a/chrome/browser/extensions/extension_action_manager.cc b/chrome/browser/extensions/extension_action_manager.cc
index 90884f9..b835944 100644
--- a/chrome/browser/extensions/extension_action_manager.cc
+++ b/chrome/browser/extensions/extension_action_manager.cc
@@ -10,7 +10,6 @@
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/keyed_service/content/browser_context_keyed_service_factory.h"
 #include "extensions/browser/extension_icon_image.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extensions_browser_client.h"
 #include "extensions/common/constants.h"
@@ -60,7 +59,7 @@
 }  // namespace
 
 ExtensionActionManager::ExtensionActionManager(Profile* profile)
-    : profile_(profile), extension_registry_observer_(this) {
+    : profile_(profile) {
   CHECK_EQ(profile, profile->GetOriginalProfile())
       << "Don't instantiate this with an incognito profile.";
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
diff --git a/chrome/browser/extensions/extension_action_manager.h b/chrome/browser/extensions/extension_action_manager.h
index 9123bcf..4c9ad62 100644
--- a/chrome/browser/extensions/extension_action_manager.h
+++ b/chrome/browser/extensions/extension_action_manager.h
@@ -12,6 +12,7 @@
 #include "base/scoped_observer.h"
 #include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "components/keyed_service/core/keyed_service.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class ExtensionAction;
@@ -20,7 +21,6 @@
 namespace extensions {
 
 class Extension;
-class ExtensionRegistry;
 
 // Owns the ExtensionActions associated with each extension.  These actions live
 // while an extension is loaded and are destroyed on unload.
@@ -51,7 +51,7 @@
 
   // Listen to extension unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   // Keyed by Extension ID.  These maps are populated lazily when their
   // ExtensionAction is first requested, and the entries are removed when the
diff --git a/chrome/browser/extensions/extension_action_runner.cc b/chrome/browser/extensions/extension_action_runner.cc
index 9608fa90..55e7116 100644
--- a/chrome/browser/extensions/extension_action_runner.cc
+++ b/chrome/browser/extensions/extension_action_runner.cc
@@ -38,7 +38,6 @@
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/api/declarative_net_request/action_tracker.h"
 #include "extensions/browser/api/declarative_net_request/rules_monitor_service.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_messages.h"
 #include "extensions/common/extension_set.h"
@@ -73,8 +72,7 @@
       browser_context_(web_contents->GetBrowserContext()),
       was_used_on_page_(false),
       ignore_active_tab_granted_(false),
-      test_observer_(nullptr),
-      extension_registry_observer_(this) {
+      test_observer_(nullptr) {
   CHECK(web_contents);
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
 }
diff --git a/chrome/browser/extensions/extension_action_runner.h b/chrome/browser/extensions/extension_action_runner.h
index 6c68a55..8448474 100644
--- a/chrome/browser/extensions/extension_action_runner.h
+++ b/chrome/browser/extensions/extension_action_runner.h
@@ -21,6 +21,7 @@
 #include "chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "extensions/browser/blocked_action_type.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/permissions/permissions_data.h"
 #include "extensions/common/user_script.h"
@@ -36,7 +37,6 @@
 
 namespace extensions {
 class Extension;
-class ExtensionRegistry;
 
 // The provider for ExtensionActions corresponding to scripts which are actively
 // running or need permission.
@@ -251,7 +251,7 @@
   TestObserver* test_observer_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::WeakPtrFactory<ExtensionActionRunner> weak_factory_{this};
 
diff --git a/chrome/browser/extensions/extension_action_storage_manager.cc b/chrome/browser/extensions/extension_action_storage_manager.cc
index d3ae2ae..ab0001f 100644
--- a/chrome/browser/extensions/extension_action_storage_manager.cc
+++ b/chrome/browser/extensions/extension_action_storage_manager.cc
@@ -13,7 +13,6 @@
 #include "base/bind.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/values.h"
-#include "chrome/browser/extensions/extension_action.h"
 #include "chrome/browser/extensions/extension_action_manager.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
@@ -197,9 +196,7 @@
 
 ExtensionActionStorageManager::ExtensionActionStorageManager(
     content::BrowserContext* context)
-    : browser_context_(context),
-      extension_action_observer_(this),
-      extension_registry_observer_(this) {
+    : browser_context_(context) {
   extension_action_observer_.Add(ExtensionActionAPI::Get(browser_context_));
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
 
diff --git a/chrome/browser/extensions/extension_action_storage_manager.h b/chrome/browser/extensions/extension_action_storage_manager.h
index 28478e8..2c415dd 100644
--- a/chrome/browser/extensions/extension_action_storage_manager.h
+++ b/chrome/browser/extensions/extension_action_storage_manager.h
@@ -11,16 +11,15 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
-class ExtensionAction;
-
 namespace content {
 class BrowserContext;
 }
 
 namespace extensions {
-class ExtensionRegistry;
 class StateStore;
 
 // This class manages reading and writing browser action values from storage.
@@ -54,10 +53,10 @@
   content::BrowserContext* browser_context_;
 
   ScopedObserver<ExtensionActionAPI, ExtensionActionAPI::Observer>
-      extension_action_observer_;
+      extension_action_observer_{this};
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::WeakPtrFactory<ExtensionActionStorageManager> weak_factory_{this};
 
diff --git a/chrome/browser/extensions/extension_browser_window_helper.cc b/chrome/browser/extensions/extension_browser_window_helper.cc
index 03a493d0..2aaec50 100644
--- a/chrome/browser/extensions/extension_browser_window_helper.cc
+++ b/chrome/browser/extensions/extension_browser_window_helper.cc
@@ -14,7 +14,6 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/url_constants.h"
-#include "extensions/browser/extension_registry.h"
 #include "url/origin.h"
 
 namespace extensions {
@@ -70,7 +69,7 @@
 }  // namespace
 
 ExtensionBrowserWindowHelper::ExtensionBrowserWindowHelper(Browser* browser)
-    : browser_(browser), registry_observer_(this) {
+    : browser_(browser) {
   registry_observer_.Add(ExtensionRegistry::Get(browser_->profile()));
 }
 
diff --git a/chrome/browser/extensions/extension_browser_window_helper.h b/chrome/browser/extensions/extension_browser_window_helper.h
index 3ba0c49..803e62c 100644
--- a/chrome/browser/extensions/extension_browser_window_helper.h
+++ b/chrome/browser/extensions/extension_browser_window_helper.h
@@ -7,6 +7,7 @@
 
 #include "base/macros.h"
 #include "base/scoped_observer.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class Browser;
@@ -36,7 +37,7 @@
   Browser* const browser_ = nullptr;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionBrowserWindowHelper);
 };
diff --git a/chrome/browser/extensions/extension_disabled_ui.cc b/chrome/browser/extensions/extension_disabled_ui.cc
index 03a0308..5556d723 100644
--- a/chrome/browser/extensions/extension_disabled_ui.cc
+++ b/chrome/browser/extensions/extension_disabled_ui.cc
@@ -127,7 +127,9 @@
   content::NotificationRegistrar registrar_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionDisabledGlobalError);
 };
 
 // TODO(yoz): create error at startup for disabled extensions.
@@ -140,8 +142,7 @@
       extension_(extension),
       is_remote_install_(is_remote_install),
       icon_(icon),
-      user_response_(IGNORED),
-      registry_observer_(this) {
+      user_response_(IGNORED) {
   if (icon_.IsEmpty()) {
     icon_ = gfx::Image(gfx::ImageSkiaOperations::CreateResizedImage(
         extension_->is_app() ? util::GetDefaultAppIcon()
diff --git a/chrome/browser/extensions/extension_gcm_app_handler.cc b/chrome/browser/extensions/extension_gcm_app_handler.cc
index 648c2fd..3fb0137 100644
--- a/chrome/browser/extensions/extension_gcm_app_handler.cc
+++ b/chrome/browser/extensions/extension_gcm_app_handler.cc
@@ -18,7 +18,6 @@
 #include "components/gcm_driver/gcm_profile_service.h"
 #include "components/gcm_driver/instance_id/instance_id_driver.h"
 #include "components/gcm_driver/instance_id/instance_id_profile_service.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/permissions/permissions_data.h"
@@ -47,8 +46,7 @@
 }
 
 ExtensionGCMAppHandler::ExtensionGCMAppHandler(content::BrowserContext* context)
-    : profile_(Profile::FromBrowserContext(context)),
-      extension_registry_observer_(this) {
+    : profile_(Profile::FromBrowserContext(context)) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
   js_event_router_.reset(new extensions::GcmJsEventRouter(profile_));
 }
diff --git a/chrome/browser/extensions/extension_gcm_app_handler.h b/chrome/browser/extensions/extension_gcm_app_handler.h
index 8cf76af7..ff7d055 100644
--- a/chrome/browser/extensions/extension_gcm_app_handler.h
+++ b/chrome/browser/extensions/extension_gcm_app_handler.h
@@ -17,6 +17,7 @@
 #include "components/gcm_driver/gcm_client.h"
 #include "components/gcm_driver/instance_id/instance_id.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class Profile;
@@ -34,7 +35,6 @@
 
 namespace extensions {
 
-class ExtensionRegistry;
 class GcmJsEventRouter;
 
 // Defines the interface to provide handling logic for a given app.
@@ -99,7 +99,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   std::unique_ptr<extensions::GcmJsEventRouter> js_event_router_;
 
diff --git a/chrome/browser/extensions/extension_keybinding_registry.cc b/chrome/browser/extensions/extension_keybinding_registry.cc
index e8323b04..c5f0378 100644
--- a/chrome/browser/extensions/extension_keybinding_registry.cc
+++ b/chrome/browser/extensions/extension_keybinding_registry.cc
@@ -14,7 +14,6 @@
 #include "content/public/browser/browser_context.h"
 #include "content/public/browser/media_keys_listener_manager.h"
 #include "extensions/browser/event_router.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/common/extension_set.h"
 #include "extensions/common/manifest_constants.h"
@@ -38,7 +37,6 @@
     : browser_context_(context),
       extension_filter_(extension_filter),
       delegate_(delegate),
-      extension_registry_observer_(this),
       shortcut_handling_suspended_(false) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
 
diff --git a/chrome/browser/extensions/extension_keybinding_registry.h b/chrome/browser/extensions/extension_keybinding_registry.h
index 8c1e570..562f06f 100644
--- a/chrome/browser/extensions/extension_keybinding_registry.h
+++ b/chrome/browser/extensions/extension_keybinding_registry.h
@@ -17,6 +17,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/notification_source.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "ui/base/accelerators/media_keys_listener.h"
 
@@ -32,7 +33,6 @@
 
 class ActiveTabPermissionGranter;
 class Extension;
-class ExtensionRegistry;
 
 // The ExtensionKeybindingRegistry is a class that handles the cross-platform
 // logic for keyboard accelerators. See platform-specific implementations for
@@ -186,7 +186,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   // Keeps track of whether shortcut handling is currently suspended. Shortcuts
   // are suspended briefly while capturing which shortcut to assign to an
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.cc b/chrome/browser/extensions/extension_message_bubble_controller.cc
index 20ed6d4..f1ed3d8c 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.cc
+++ b/chrome/browser/extensions/extension_message_bubble_controller.cc
@@ -20,7 +20,6 @@
 #include "chrome/common/url_constants.h"
 #include "components/strings/grit/components_strings.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "ui/base/l10n/l10n_util.h"
 
@@ -100,14 +99,13 @@
       delegate_(delegate),
       initialized_(false),
       is_highlighting_(false),
-      is_active_bubble_(false),
-      extension_registry_observer_(this),
-      browser_list_observer_(this) {
+      is_active_bubble_(false) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_->profile()));
-  browser_list_observer_.Add(BrowserList::GetInstance());
+  BrowserList::AddObserver(this);
 }
 
 ExtensionMessageBubbleController::~ExtensionMessageBubbleController() {
+  BrowserList::RemoveObserver(this);
   if (is_active_bubble_)
     model_->set_has_active_bubble(false);
   if (is_highlighting_)
diff --git a/chrome/browser/extensions/extension_message_bubble_controller.h b/chrome/browser/extensions/extension_message_bubble_controller.h
index d1dbaddb..bb25bf4 100644
--- a/chrome/browser/extensions/extension_message_bubble_controller.h
+++ b/chrome/browser/extensions/extension_message_bubble_controller.h
@@ -13,17 +13,16 @@
 #include "base/scoped_observer.h"
 #include "chrome/browser/ui/browser_list_observer.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension.h"
 
 class Browser;
-class BrowserList;
 class ToolbarActionsModel;
 class Profile;
 
 namespace extensions {
 
-class ExtensionRegistry;
 class ExtensionService;
 
 class ExtensionMessageBubbleController : public BrowserListObserver,
@@ -242,8 +241,7 @@
   base::Closure close_bubble_callback_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
-  ScopedObserver<BrowserList, BrowserListObserver> browser_list_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionMessageBubbleController);
 };
diff --git a/chrome/browser/extensions/extension_service_test_with_install.cc b/chrome/browser/extensions/extension_service_test_with_install.cc
index 762ca33a..35cbf027 100644
--- a/chrome/browser/extensions/extension_service_test_with_install.cc
+++ b/chrome/browser/extensions/extension_service_test_with_install.cc
@@ -14,7 +14,6 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/test/test_utils.h"
 #include "extensions/browser/extension_creator.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/notification_types.h"
 #include "extensions/common/verifier_formats.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -48,8 +47,7 @@
       expected_extensions_count_(0),
       override_external_install_prompt_(
           FeatureSwitch::prompt_for_external_extensions(),
-          false),
-      registry_observer_(this) {}
+          false) {}
 
 ExtensionServiceTestWithInstall::~ExtensionServiceTestWithInstall() {}
 
diff --git a/chrome/browser/extensions/extension_service_test_with_install.h b/chrome/browser/extensions/extension_service_test_with_install.h
index 838b0e0..ccb33ff 100644
--- a/chrome/browser/extensions/extension_service_test_with_install.h
+++ b/chrome/browser/extensions/extension_service_test_with_install.h
@@ -12,6 +12,7 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/extension_service_test_base.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/feature_switch.h"
@@ -22,8 +23,6 @@
 
 namespace extensions {
 
-class ExtensionRegistry;
-
 // An enhancement of ExtensionServiceTestBase that provides helpers to install,
 // update, and uninstall extensions.
 class ExtensionServiceTestWithInstall : public ExtensionServiceTestBase,
@@ -146,7 +145,7 @@
   FeatureSwitch::ScopedOverride override_external_install_prompt_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionServiceTestWithInstall);
 };
diff --git a/chrome/browser/extensions/extension_sync_service.cc b/chrome/browser/extensions/extension_sync_service.cc
index affa048..dd944284 100644
--- a/chrome/browser/extensions/extension_sync_service.cc
+++ b/chrome/browser/extensions/extension_sync_service.cc
@@ -27,8 +27,6 @@
 #include "components/sync/model/sync_change.h"
 #include "components/sync/model/sync_error_factory.h"
 #include "extensions/browser/app_sorting.h"
-#include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_util.h"
 #include "extensions/browser/uninstall_reason.h"
@@ -108,8 +106,6 @@
 
 ExtensionSyncService::ExtensionSyncService(Profile* profile)
     : profile_(profile),
-      registry_observer_(this),
-      prefs_observer_(this),
       ignore_updates_(false),
       flare_(sync_start_util::GetFlareForSyncableService(profile->GetPath())) {
   registry_observer_.Add(ExtensionRegistry::Get(profile_));
diff --git a/chrome/browser/extensions/extension_sync_service.h b/chrome/browser/extensions/extension_sync_service.h
index 6557a8a..9d2e4d5 100644
--- a/chrome/browser/extensions/extension_sync_service.h
+++ b/chrome/browser/extensions/extension_sync_service.h
@@ -17,7 +17,9 @@
 #include "chrome/browser/extensions/sync_bundle.h"
 #include "components/keyed_service/core/keyed_service.h"
 #include "components/sync/model/syncable_service.h"
+#include "extensions/browser/extension_prefs.h"
 #include "extensions/browser/extension_prefs_observer.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class Profile;
@@ -132,9 +134,10 @@
   Profile* profile_;
 
   ScopedObserver<extensions::ExtensionRegistry,
-                 extensions::ExtensionRegistryObserver> registry_observer_;
-  ScopedObserver<extensions::ExtensionPrefs,
-                 extensions::ExtensionPrefsObserver> prefs_observer_;
+                 extensions::ExtensionRegistryObserver>
+      registry_observer_{this};
+  ScopedObserver<extensions::ExtensionPrefs, extensions::ExtensionPrefsObserver>
+      prefs_observer_{this};
 
   // When this is set to true, any incoming updates (from the observers as well
   // as from explicit SyncExtensionChangeIfNeeded calls) are ignored. This is
diff --git a/chrome/browser/extensions/extension_uninstall_dialog.cc b/chrome/browser/extensions/extension_uninstall_dialog.cc
index c17540d..e8953ab8 100644
--- a/chrome/browser/extensions/extension_uninstall_dialog.cc
+++ b/chrome/browser/extensions/extension_uninstall_dialog.cc
@@ -21,7 +21,6 @@
 #include "components/url_formatter/elide_url.h"
 #include "content/public/browser/clear_site_data_utils.h"
 #include "extensions/browser/extension_dialog_auto_confirm.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/image_loader.h"
 #include "extensions/common/constants.h"
@@ -71,7 +70,7 @@
     Profile* profile,
     gfx::NativeWindow parent,
     ExtensionUninstallDialog::Delegate* delegate)
-    : profile_(profile), parent_(parent), delegate_(delegate), observer_(this) {
+    : profile_(profile), parent_(parent), delegate_(delegate) {
   if (parent)
     parent_window_tracker_ = NativeWindowTracker::Create(parent);
 }
diff --git a/chrome/browser/extensions/extension_uninstall_dialog.h b/chrome/browser/extensions/extension_uninstall_dialog.h
index 0612e33..a3e8347 100644
--- a/chrome/browser/extensions/extension_uninstall_dialog.h
+++ b/chrome/browser/extensions/extension_uninstall_dialog.h
@@ -14,6 +14,7 @@
 #include "base/threading/thread_checker.h"
 #include "chrome/browser/extensions/chrome_app_icon.h"
 #include "chrome/browser/extensions/chrome_app_icon_delegate.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/browser/uninstall_reason.h"
 #include "ui/gfx/image/image_skia.h"
@@ -172,7 +173,7 @@
 
   UninstallReason uninstall_reason_ = UNINSTALL_REASON_FOR_TESTING;
 
-  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
+  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_{this};
 
   base::ThreadChecker thread_checker_;
 
diff --git a/chrome/browser/extensions/extension_web_ui_override_registrar.cc b/chrome/browser/extensions/extension_web_ui_override_registrar.cc
index feb11ca..dde2d4c 100644
--- a/chrome/browser/extensions/extension_web_ui_override_registrar.cc
+++ b/chrome/browser/extensions/extension_web_ui_override_registrar.cc
@@ -9,14 +9,12 @@
 #include "base/one_shot_event.h"
 #include "chrome/browser/extensions/extension_web_ui.h"
 #include "chrome/browser/profiles/profile.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 
 namespace extensions {
 
 ExtensionWebUIOverrideRegistrar::ExtensionWebUIOverrideRegistrar(
-    content::BrowserContext* context)
-    : extension_registry_observer_(this) {
+    content::BrowserContext* context) {
   ExtensionWebUI::InitializeChromeURLOverrides(
       Profile::FromBrowserContext(context));
   extension_registry_observer_.Add(ExtensionRegistry::Get(context));
diff --git a/chrome/browser/extensions/extension_web_ui_override_registrar.h b/chrome/browser/extensions/extension_web_ui_override_registrar.h
index d41d4b7..3f57703 100644
--- a/chrome/browser/extensions/extension_web_ui_override_registrar.h
+++ b/chrome/browser/extensions/extension_web_ui_override_registrar.h
@@ -9,6 +9,7 @@
 #include "base/memory/weak_ptr.h"
 #include "base/scoped_observer.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 namespace content {
@@ -16,7 +17,6 @@
 }
 
 namespace extensions {
-class ExtensionRegistry;
 
 class ExtensionWebUIOverrideRegistrar : public BrowserContextKeyedAPI,
                                         public ExtensionRegistryObserver {
@@ -51,7 +51,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::WeakPtrFactory<ExtensionWebUIOverrideRegistrar> weak_factory_{this};
 
diff --git a/chrome/browser/extensions/external_install_manager.cc b/chrome/browser/extensions/external_install_manager.cc
index 8f1547c..717f5bf1 100644
--- a/chrome/browser/extensions/external_install_manager.cc
+++ b/chrome/browser/extensions/external_install_manager.cc
@@ -16,7 +16,6 @@
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_source.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/feature_switch.h"
 #include "extensions/common/features/feature_channel.h"
@@ -64,8 +63,7 @@
     : browser_context_(browser_context),
       is_first_run_(is_first_run),
       extension_prefs_(ExtensionPrefs::Get(browser_context_)),
-      currently_visible_install_alert_(nullptr),
-      extension_registry_observer_(this) {
+      currently_visible_install_alert_(nullptr) {
   DCHECK(browser_context_);
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
   Profile* profile = Profile::FromBrowserContext(browser_context_);
diff --git a/chrome/browser/extensions/external_install_manager.h b/chrome/browser/extensions/external_install_manager.h
index 323e089..d7668c9 100644
--- a/chrome/browser/extensions/external_install_manager.h
+++ b/chrome/browser/extensions/external_install_manager.h
@@ -12,6 +12,7 @@
 #include "base/scoped_observer.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension_id.h"
 
@@ -23,7 +24,6 @@
 
 namespace extensions {
 class Extension;
-class ExtensionRegistry;
 class ExtensionPrefs;
 class ExternalInstallError;
 
@@ -118,7 +118,7 @@
   content::NotificationRegistrar registrar_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(ExternalInstallManager);
 };
diff --git a/chrome/browser/extensions/forced_extensions/installation_tracker.cc b/chrome/browser/extensions/forced_extensions/installation_tracker.cc
index 314fc49..de4b61d 100644
--- a/chrome/browser/extensions/forced_extensions/installation_tracker.cc
+++ b/chrome/browser/extensions/forced_extensions/installation_tracker.cc
@@ -10,7 +10,6 @@
 #include "chrome/browser/extensions/forced_extensions/installation_reporter.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/prefs/pref_service.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/install/crx_install_error.h"
 #include "extensions/browser/pref_names.h"
 
@@ -30,7 +29,6 @@
       profile_(profile),
       pref_service_(profile->GetPrefs()),
       start_time_(base::Time::Now()),
-      observer_(this),
       timer_(std::move(timer)) {
   observer_.Add(registry_);
   pref_change_registrar_.Init(pref_service_);
diff --git a/chrome/browser/extensions/forced_extensions/installation_tracker.h b/chrome/browser/extensions/forced_extensions/installation_tracker.h
index c2d4f92..65edf1e 100644
--- a/chrome/browser/extensions/forced_extensions/installation_tracker.h
+++ b/chrome/browser/extensions/forced_extensions/installation_tracker.h
@@ -12,6 +12,7 @@
 #include "base/time/time.h"
 #include "base/timer/timer.h"
 #include "components/prefs/pref_change_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/extension.h"
 
@@ -24,8 +25,6 @@
 
 namespace extensions {
 
-class ExtensionRegistry;
-
 // Used to track installation of force-installed extensions for the profile
 // and report stats to UMA.
 // ExtensionService owns this class and outlives it.
@@ -72,7 +71,7 @@
   // Tracks whether stats were already reported for the session.
   bool reported_ = false;
 
-  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_;
+  ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver> observer_{this};
 
   // Tracks installation reporting timeout.
   std::unique_ptr<base::OneShotTimer> timer_;
diff --git a/chrome/browser/extensions/install_tracker.cc b/chrome/browser/extensions/install_tracker.cc
index fbffefc3..abcf8e9 100644
--- a/chrome/browser/extensions/install_tracker.cc
+++ b/chrome/browser/extensions/install_tracker.cc
@@ -9,15 +9,13 @@
 #include "chrome/browser/extensions/install_tracker_factory.h"
 #include "content/public/browser/notification_service.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/pref_names.h"
 
 namespace extensions {
 
 InstallTracker::InstallTracker(content::BrowserContext* browser_context,
-                               extensions::ExtensionPrefs* prefs)
-    : extension_registry_observer_(this) {
+                               extensions::ExtensionPrefs* prefs) {
   registrar_.Add(this,
                  extensions::NOTIFICATION_EXTENSION_UPDATE_DISABLED,
                  content::Source<content::BrowserContext>(browser_context));
diff --git a/chrome/browser/extensions/install_tracker.h b/chrome/browser/extensions/install_tracker.h
index 91cdd92..b273963 100644
--- a/chrome/browser/extensions/install_tracker.h
+++ b/chrome/browser/extensions/install_tracker.h
@@ -16,6 +16,7 @@
 #include "components/prefs/pref_change_registrar.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 namespace content {
@@ -25,7 +26,6 @@
 namespace extensions {
 
 class ExtensionPrefs;
-class ExtensionRegistry;
 
 class InstallTracker : public KeyedService,
                        public content::NotificationObserver,
@@ -92,7 +92,7 @@
   content::NotificationRegistrar registrar_;
   PrefChangeRegistrar pref_change_registrar_;
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   DISALLOW_COPY_AND_ASSIGN(InstallTracker);
 };
diff --git a/chrome/browser/extensions/install_tracker_unittest.cc b/chrome/browser/extensions/install_tracker_unittest.cc
index d7319c1..58949bf3 100644
--- a/chrome/browser/extensions/install_tracker_unittest.cc
+++ b/chrome/browser/extensions/install_tracker_unittest.cc
@@ -6,6 +6,7 @@
 
 #include "base/files/file_path.h"
 #include "chrome/browser/extensions/active_install_data.h"
+#include "chrome/browser/extensions/scoped_active_install.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/browser_task_environment.h"
 #include "extensions/browser/extension_registry.h"
diff --git a/chrome/browser/extensions/lazy_background_page_apitest.cc b/chrome/browser/extensions/lazy_background_page_apitest.cc
index e0cf2dac..1e154446 100644
--- a/chrome/browser/extensions/lazy_background_page_apitest.cc
+++ b/chrome/browser/extensions/lazy_background_page_apitest.cc
@@ -66,8 +66,7 @@
 // unloaded before listening to the background page notifications.
 class LoadedIncognitoObserver : public ExtensionRegistryObserver {
  public:
-  explicit LoadedIncognitoObserver(Profile* profile)
-      : profile_(profile), extension_registry_observer_(this) {
+  explicit LoadedIncognitoObserver(Profile* profile) : profile_(profile) {
     extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
   }
 
@@ -88,7 +87,7 @@
 
   Profile* profile_;
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
   std::unique_ptr<LazyBackgroundObserver> original_complete_;
   std::unique_ptr<LazyBackgroundObserver> incognito_complete_;
 };
diff --git a/chrome/browser/extensions/menu_manager.cc b/chrome/browser/extensions/menu_manager.cc
index ea2547d..f01d4dab 100644
--- a/chrome/browser/extensions/menu_manager.cc
+++ b/chrome/browser/extensions/menu_manager.cc
@@ -32,7 +32,6 @@
 #include "content/public/common/context_menu_params.h"
 #include "extensions/browser/event_router.h"
 #include "extensions/browser/extension_api_frame_id_map.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/guest_view/web_view/web_view_guest.h"
 #include "extensions/browser/state_store.h"
 #include "extensions/common/extension.h"
@@ -316,9 +315,7 @@
     "webViewInternal.contextMenus";
 
 MenuManager::MenuManager(content::BrowserContext* context, StateStore* store)
-    : extension_registry_observer_(this),
-      browser_context_(context),
-      store_(store) {
+    : browser_context_(context), store_(store) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
                  content::NotificationService::AllSources());
diff --git a/chrome/browser/extensions/menu_manager.h b/chrome/browser/extensions/menu_manager.h
index 54eba7c31..e44d27e 100644
--- a/chrome/browser/extensions/menu_manager.h
+++ b/chrome/browser/extensions/menu_manager.h
@@ -25,6 +25,7 @@
 #include "components/keyed_service/core/keyed_service.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/url_pattern_set.h"
 #include "ui/gfx/image/image.h"
@@ -38,7 +39,6 @@
 
 namespace extensions {
 class Extension;
-class ExtensionRegistry;
 class StateStore;
 
 // Represents a menu item added by an extension.
@@ -408,7 +408,7 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   ExtensionIconManager icon_manager_;
 
diff --git a/chrome/browser/extensions/navigation_observer.cc b/chrome/browser/extensions/navigation_observer.cc
index 2e3782e..bf3303c 100644
--- a/chrome/browser/extensions/navigation_observer.cc
+++ b/chrome/browser/extensions/navigation_observer.cc
@@ -12,7 +12,6 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 
 using content::NavigationController;
@@ -25,8 +24,7 @@
 bool g_repeat_prompting = false;
 }
 
-NavigationObserver::NavigationObserver(Profile* profile)
-    : profile_(profile), extension_registry_observer_(this) {
+NavigationObserver::NavigationObserver(Profile* profile) : profile_(profile) {
   RegisterForNotifications();
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile));
 }
diff --git a/chrome/browser/extensions/navigation_observer.h b/chrome/browser/extensions/navigation_observer.h
index 02c7c27..f2428a3a 100644
--- a/chrome/browser/extensions/navigation_observer.h
+++ b/chrome/browser/extensions/navigation_observer.h
@@ -15,6 +15,7 @@
 #include "chrome/browser/extensions/extension_install_prompt.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class Profile;
@@ -76,7 +77,7 @@
   std::set<std::string> prompted_extensions_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::WeakPtrFactory<NavigationObserver> weak_factory_{this};
 
diff --git a/chrome/browser/extensions/plugin_manager.cc b/chrome/browser/extensions/plugin_manager.cc
index 188f34b..950975c2 100644
--- a/chrome/browser/extensions/plugin_manager.cc
+++ b/chrome/browser/extensions/plugin_manager.cc
@@ -15,7 +15,6 @@
 #include "chrome/common/chrome_paths.h"
 #include "content/public/browser/plugin_service.h"
 #include "content/public/common/pepper_plugin_info.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/mime_types_handler.h"
 #include "third_party/skia/include/core/SkColor.h"
@@ -30,8 +29,7 @@
 namespace extensions {
 
 PluginManager::PluginManager(content::BrowserContext* context)
-    : profile_(Profile::FromBrowserContext(context)),
-      extension_registry_observer_(this) {
+    : profile_(Profile::FromBrowserContext(context)) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
 }
 
diff --git a/chrome/browser/extensions/plugin_manager.h b/chrome/browser/extensions/plugin_manager.h
index 33263d35..3a3e213 100644
--- a/chrome/browser/extensions/plugin_manager.h
+++ b/chrome/browser/extensions/plugin_manager.h
@@ -8,9 +8,11 @@
 #include <set>
 #include <string>
 
+#include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "components/nacl/common/buildflags.h"
 #include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/manifest_handlers/nacl_modules_handler.h"
 
@@ -22,7 +24,6 @@
 }
 
 namespace extensions {
-class ExtensionRegistry;
 
 class PluginManager : public BrowserContextKeyedAPI,
                       public ExtensionRegistryObserver {
@@ -72,7 +73,9 @@
 
   // Listen to extension load, unloaded notifications.
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
+
+  DISALLOW_COPY_AND_ASSIGN(PluginManager);
 };
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/policy_handlers.cc b/chrome/browser/extensions/policy_handlers.cc
index 33edb396..5b482c0 100644
--- a/chrome/browser/extensions/policy_handlers.cc
+++ b/chrome/browser/extensions/policy_handlers.cc
@@ -9,6 +9,7 @@
 
 #include "base/logging.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
 #include "build/build_config.h"
 #include "chrome/browser/extensions/extension_management_constants.h"
 #include "chrome/browser/extensions/external_policy_loader.h"
@@ -29,7 +30,22 @@
 #endif
 
 namespace extensions {
-
+namespace {
+// Returns true if extensions_ids contains a list of valid extension ids,
+// divided by comma.
+bool IsValidIdList(const std::string& extension_ids) {
+  std::vector<base::StringPiece> ids = base::SplitStringPiece(
+      extension_ids, ",", base::WhitespaceHandling::TRIM_WHITESPACE,
+      base::SplitResult::SPLIT_WANT_NONEMPTY);
+  if (ids.size() == 0)
+    return false;
+  for (const auto& id : ids) {
+    if (!crx_file::id_util::IdIsValid(id.as_string()))
+      return false;
+  }
+  return true;
+}
+}  // namespace
 // ExtensionListPolicyHandler implementation -----------------------------------
 
 ExtensionListPolicyHandler::ExtensionListPolicyHandler(const char* policy_name,
@@ -248,8 +264,7 @@
 
   for (base::DictionaryValue::Iterator it(*dict_value); !it.IsAtEnd();
        it.Advance()) {
-    DCHECK(it.key() == schema_constants::kWildcard ||
-           crx_file::id_util::IdIsValid(it.key()));
+    DCHECK(it.key() == schema_constants::kWildcard || IsValidIdList(it.key()));
     DCHECK(it.value().is_dict());
 
     // Extracts sub dictionary.
diff --git a/chrome/browser/extensions/scoped_active_install.cc b/chrome/browser/extensions/scoped_active_install.cc
new file mode 100644
index 0000000..1cfe705
--- /dev/null
+++ b/chrome/browser/extensions/scoped_active_install.cc
@@ -0,0 +1,44 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/extensions/scoped_active_install.h"
+
+#include "chrome/browser/extensions/active_install_data.h"
+
+namespace extensions {
+
+ScopedActiveInstall::ScopedActiveInstall(InstallTracker* tracker,
+                                         const ActiveInstallData& install_data)
+    : tracker_(tracker), extension_id_(install_data.extension_id) {
+  Init();
+  tracker_->AddActiveInstall(install_data);
+}
+
+ScopedActiveInstall::ScopedActiveInstall(InstallTracker* tracker,
+                                         const std::string& extension_id)
+    : tracker_(tracker), extension_id_(extension_id) {
+  Init();
+}
+
+ScopedActiveInstall::~ScopedActiveInstall() {
+  if (tracker_)
+    tracker_->RemoveActiveInstall(extension_id_);
+}
+
+void ScopedActiveInstall::CancelDeregister() {
+  tracker_observer_.RemoveAll();
+  tracker_ = nullptr;
+}
+
+void ScopedActiveInstall::Init() {
+  DCHECK(!extension_id_.empty());
+  DCHECK(tracker_);
+  tracker_observer_.Add(tracker_);
+}
+
+void ScopedActiveInstall::OnShutdown() {
+  CancelDeregister();
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/scoped_active_install.h b/chrome/browser/extensions/scoped_active_install.h
new file mode 100644
index 0000000..3e7069e0
--- /dev/null
+++ b/chrome/browser/extensions/scoped_active_install.h
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_SCOPED_ACTIVE_INSTALL_H_
+#define CHROME_BROWSER_EXTENSIONS_SCOPED_ACTIVE_INSTALL_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/scoped_observer.h"
+#include "chrome/browser/extensions/install_observer.h"
+#include "chrome/browser/extensions/install_tracker.h"
+
+namespace extensions {
+
+struct ActiveInstallData;
+
+// Registers and deregisters an active extension install with InstallTracker.
+class ScopedActiveInstall : public InstallObserver {
+ public:
+  // This constructor registers an active install with InstallTracker.
+  ScopedActiveInstall(InstallTracker* tracker,
+                      const ActiveInstallData& install_data);
+
+  // This constructor does not register an active install. The extension install
+  // is still deregistered upon destruction.
+  ScopedActiveInstall(InstallTracker* tracker, const std::string& extension_id);
+
+  ~ScopedActiveInstall() override;
+
+  // Ensures that the active install is not deregistered upon destruction. This
+  // may be necessary if the extension install outlives the lifetime of this
+  // instance.
+  void CancelDeregister();
+
+ private:
+  void Init();
+
+  // InstallObserver implementation.
+  void OnShutdown() override;
+
+  InstallTracker* tracker_;
+  ScopedObserver<InstallTracker, InstallObserver> tracker_observer_{this};
+  const std::string extension_id_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedActiveInstall);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_SCOPED_ACTIVE_INSTALL_H_
diff --git a/chrome/browser/extensions/shared_module_service.cc b/chrome/browser/extensions/shared_module_service.cc
index 53b826a6..93bb3ab 100644
--- a/chrome/browser/extensions/shared_module_service.cc
+++ b/chrome/browser/extensions/shared_module_service.cc
@@ -12,7 +12,6 @@
 #include "base/version.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/pending_extension_manager.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/uninstall_reason.h"
 #include "extensions/common/extension.h"
@@ -28,7 +27,7 @@
 }  // namespace
 
 SharedModuleService::SharedModuleService(content::BrowserContext* context)
-    : extension_registry_observer_(this), browser_context_(context) {
+    : browser_context_(context) {
   extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
 }
 
diff --git a/chrome/browser/extensions/shared_module_service.h b/chrome/browser/extensions/shared_module_service.h
index b10094ed..c541a97 100644
--- a/chrome/browser/extensions/shared_module_service.h
+++ b/chrome/browser/extensions/shared_module_service.h
@@ -10,6 +10,7 @@
 #include "base/macros.h"
 #include "base/scoped_observer.h"
 #include "chrome/browser/extensions/install_gate.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 
@@ -20,7 +21,6 @@
 namespace extensions {
 class Extension;
 class ExtensionSet;
-class ExtensionRegistry;
 
 class SharedModuleService : public ExtensionRegistryObserver,
                             public InstallGate {
@@ -75,7 +75,7 @@
                               extensions::UninstallReason reason) override;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   // The context associated with this SharedModuleService.
   content::BrowserContext* browser_context_;
diff --git a/chrome/browser/extensions/tab_helper.cc b/chrome/browser/extensions/tab_helper.cc
index 4c0296d..8eae5786 100644
--- a/chrome/browser/extensions/tab_helper.cc
+++ b/chrome/browser/extensions/tab_helper.cc
@@ -45,7 +45,6 @@
 #include "extensions/browser/api/declarative/rules_registry_service.h"
 #include "extensions/browser/disable_reason.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/extension_web_contents_observer.h"
 #include "extensions/browser/image_loader.h"
@@ -73,8 +72,7 @@
       profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
       extension_app_(NULL),
       script_executor_(new ScriptExecutor(web_contents)),
-      extension_action_runner_(new ExtensionActionRunner(web_contents)),
-      registry_observer_(this) {
+      extension_action_runner_(new ExtensionActionRunner(web_contents)) {
   // The ActiveTabPermissionManager requires a session ID; ensure this
   // WebContents has one.
   SessionTabHelper::CreateForWebContents(web_contents);
diff --git a/chrome/browser/extensions/tab_helper.h b/chrome/browser/extensions/tab_helper.h
index c033b34..fc1dea95 100644
--- a/chrome/browser/extensions/tab_helper.h
+++ b/chrome/browser/extensions/tab_helper.h
@@ -21,6 +21,7 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "extensions/browser/extension_function_dispatcher.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/browser/script_executor.h"
 #include "extensions/common/extension_id.h"
@@ -155,7 +156,7 @@
   std::unique_ptr<ActiveTabPermissionGranter> active_tab_permission_granter_;
 
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      registry_observer_;
+      registry_observer_{this};
 
   // Vend weak pointers that can be invalidated to stop in-progress loads.
   base::WeakPtrFactory<TabHelper> image_loader_ptr_factory_{this};
diff --git a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
index 6f4bf35..c920847 100644
--- a/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
+++ b/chrome/browser/extensions/updater/extension_update_client_base_browsertest.cc
@@ -141,8 +141,6 @@
   ASSERT_TRUE(https_server_for_update_.InitializeAndListen());
   ASSERT_TRUE(https_server_for_ping_.InitializeAndListen());
 
-  scoped_feature_list_.InitAndEnableFeature(
-      extensions_features::kNewExtensionUpdaterService);
   ChromeUpdateClientConfig::SetChromeUpdateClientConfigFactoryForTesting(
       ChromeUpdateClientConfigFactory());
   ExtensionBrowserTest::SetUp();
diff --git a/chrome/browser/extensions/updater/extension_updater_unittest.cc b/chrome/browser/extensions/updater/extension_updater_unittest.cc
index a32dab7..6931c1ee 100644
--- a/chrome/browser/extensions/updater/extension_updater_unittest.cc
+++ b/chrome/browser/extensions/updater/extension_updater_unittest.cc
@@ -64,6 +64,7 @@
 #include "extensions/browser/updater/extension_downloader_delegate.h"
 #include "extensions/browser/updater/extension_downloader_test_delegate.h"
 #include "extensions/browser/updater/extension_downloader_test_helper.h"
+#include "extensions/browser/updater/extension_update_data.h"
 #include "extensions/browser/updater/manifest_fetch_data.h"
 #include "extensions/browser/updater/request_queue_impl.h"
 #include "extensions/common/extension.h"
@@ -237,6 +238,20 @@
   return 0;
 }
 
+class MockUpdateService : public UpdateService {
+ public:
+  MockUpdateService() : UpdateService(nullptr, nullptr) {}
+  MOCK_CONST_METHOD0(IsBusy, bool());
+  MOCK_CONST_METHOD1(CanUpdate, bool(const std::string& id));
+  MOCK_METHOD3(SendUninstallPing,
+               void(const std::string& id,
+                    const base::Version& version,
+                    int reason));
+  MOCK_METHOD2(StartUpdateCheck,
+               void(const ExtensionUpdateCheckParams& params,
+                    base::OnceClosure callback));
+};
+
 }  // namespace
 
 // Base class for further specialized test classes.
@@ -586,6 +601,11 @@
 
   void SimulateTimerFired(ExtensionUpdater* updater) { updater->NextCheck(); }
 
+  void OverrideUpdateService(ExtensionUpdater* updater,
+                             UpdateService* service) {
+    updater->update_service_ = service;
+  }
+
   // Adds a Result with the given data to results.
   void AddParseResult(const std::string& id,
                       const std::string& version,
@@ -1976,102 +1996,6 @@
     EXPECT_LT(seconds_diff - kDaystartElapsedSeconds, 5);
   }
 
-  // This lets us run a test with some enabled and some disabled
-  // extensions. The |num_enabled| value specifies how many enabled extensions
-  // to have, and |disabled| is a vector of DisableReason bitmasks for each
-  // disabled extension we want.
-  void TestPingMetrics(int num_enabled,
-                       const std::vector<int>& disabled) {
-    ExtensionDownloaderTestHelper helper;
-    ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
-    ExtensionList enabled_extensions;
-    ExtensionList disabled_extensions;
-
-    std::string update_url = extension_urls::GetWebstoreUpdateUrl().spec();
-    if (num_enabled > 0)
-      service.CreateTestExtensions(
-          1, num_enabled, &enabled_extensions, &update_url, Manifest::INTERNAL);
-    if (!disabled.empty()) {
-      service.CreateTestExtensions(2,
-                                   disabled.size(),
-                                   &disabled_extensions,
-                                   &update_url,
-                                   Manifest::INTERNAL);
-    }
-
-    service.set_extensions(enabled_extensions, disabled_extensions);
-
-    ExtensionPrefs* prefs = prefs_->prefs();
-
-    for (size_t i = 0; i < disabled.size(); i++)
-      prefs->SetExtensionDisabled(disabled_extensions[i]->id(), disabled[i]);
-
-    // Create the extension updater, make it issue an update, and capture the
-    // URL that it tried to fetch.
-    ExtensionUpdater updater(&service,
-                             service.extension_prefs(),
-                             service.pref_service(),
-                             service.profile(),
-                             kUpdateFrequencySecs,
-                             nullptr,
-                             service.GetDownloaderFactory());
-    updater.Start();
-    SimulateTimerFired(&updater);
-    ASSERT_NE(nullptr, updater.downloader_->manifest_loader_);
-    const ManifestFetchData& fetch =
-        *updater.downloader_->manifests_queue_.active_request();
-    const GURL& url = fetch.full_url();
-    EXPECT_FALSE(url.is_empty());
-    EXPECT_TRUE(url.is_valid());
-    EXPECT_TRUE(url.has_query());
-
-    std::map<std::string, ParamsMap> all_pings = GetPingDataFromURL(url);
-
-    // Make sure that all the enabled extensions have "e=1" in their ping
-    // parameter.
-    for (const auto& ext : enabled_extensions) {
-      ASSERT_TRUE(base::Contains(all_pings, ext->id()));
-      ParamsMap& ping = all_pings[ext->id()];
-      EXPECT_FALSE(base::Contains(ping, "dr"));
-      ASSERT_TRUE(base::Contains(ping, "e")) << url;
-      std::set<std::string> e = ping["e"];
-      ASSERT_EQ(1u, e.size()) << url;
-      EXPECT_EQ(std::string("1"), *e.begin()) << url;
-      EXPECT_FALSE(base::Contains(ping, "dr"));
-    }
-
-    // Make sure that all the disable extensions have the appropriate
-    // "dr=<num>" values in their ping parameter if metrics are on, or omit
-    // it otherwise.
-    ASSERT_EQ(disabled_extensions.size(), disabled.size());
-    for (size_t i = 0; i < disabled.size(); i++) {
-      scoped_refptr<const Extension>& ext = disabled_extensions[i];
-      int disable_reasons = disabled[i];
-      ASSERT_TRUE(base::Contains(all_pings, ext->id())) << url;
-      ParamsMap& ping = all_pings[ext->id()];
-
-      ASSERT_TRUE(base::Contains(ping, "e")) << url;
-      std::set<std::string> e = ping["e"];
-      ASSERT_EQ(1u, e.size()) << url;
-      EXPECT_EQ(std::string("0"), *e.begin()) << url;
-
-      if (disable_reasons == 0) {
-        EXPECT_FALSE(base::Contains(ping, "dr"));
-      } else {
-        ASSERT_TRUE(base::Contains(ping, "dr"));
-        int found_reasons = 0;
-        for (const auto& reason_string : ping["dr"]) {
-          int reason = 0;
-          ASSERT_TRUE(base::StringToInt(reason_string, &reason));
-          // Make sure it's a power of 2.
-          ASSERT_TRUE(reason < 2 || !(reason & (reason - 1))) << reason;
-          found_reasons |= reason;
-        }
-        EXPECT_EQ(disable_reasons, found_reasons);
-      }
-    }
-  }
-
   void TestManifestAddExtension(
       ManifestFetchData::FetchPriority data_priority,
       ManifestFetchData::FetchPriority extension_priority,
@@ -2308,16 +2232,9 @@
   ExtensionList extensions;
   service.CreateTestExtensions(1, 1, &extensions, NULL,
                                Manifest::INVALID_LOCATION);
-  service.CreateTestExtensions(2, 1, &extensions, NULL, Manifest::INTERNAL);
-  ASSERT_EQ(2u, extensions.size());
-  const std::string& updateable_id = extensions[1]->id();
-
-  // These expectations fail if the delegate's methods are invoked for the
-  // first extension, which has a non-matching id.
-  EXPECT_CALL(delegate,
-              GetUpdateUrlData(updateable_id)).WillOnce(Return(""));
-  EXPECT_CALL(delegate, GetPingDataForExtension(updateable_id, _));
-
+  ASSERT_EQ(1u, extensions.size());
+  // The test will fail with unexpected calls if the delegate's methods are
+  // invoked for the extension.
   service.set_extensions(extensions, ExtensionList());
   updater.Start();
   updater.CheckNow(ExtensionUpdater::CheckParams());
@@ -2333,8 +2250,8 @@
                            kUpdateFrequencySecs,
                            NULL,
                            service.GetDownloaderFactory());
-  MockExtensionDownloaderDelegate delegate;
-  service.OverrideDownloaderDelegate(&delegate);
+  MockUpdateService update_service;
+  OverrideUpdateService(&updater, &update_service);
 
   // Non-internal non-external extensions should be rejected.
   ExtensionList enabled_extensions;
@@ -2349,11 +2266,13 @@
   const std::string& disabled_id = disabled_extensions[0]->id();
 
   // We expect that both enabled and disabled extensions are auto-updated.
-  EXPECT_CALL(delegate, GetUpdateUrlData(enabled_id)).WillOnce(Return(""));
-  EXPECT_CALL(delegate, GetPingDataForExtension(enabled_id, _));
-  EXPECT_CALL(delegate,
-              GetUpdateUrlData(disabled_id)).WillOnce(Return(""));
-  EXPECT_CALL(delegate, GetPingDataForExtension(disabled_id, _));
+  EXPECT_CALL(update_service, CanUpdate(enabled_id)).WillOnce(Return(true));
+  EXPECT_CALL(update_service, CanUpdate(disabled_id)).WillOnce(Return(true));
+  EXPECT_CALL(update_service,
+              StartUpdateCheck(
+                  ::testing::Field(&ExtensionUpdateCheckParams::update_info,
+                                   ::testing::SizeIs(2)),
+                  _));
 
   service.set_extensions(enabled_extensions, disabled_extensions);
   updater.Start();
@@ -2511,20 +2430,6 @@
   EXPECT_FALSE(updater.WillCheckSoon());
 }
 
-TEST_F(ExtensionUpdaterTest, TestDisabledReasons1) {
-  TestPingMetrics(1, {disable_reason::DISABLE_USER_ACTION,
-                      disable_reason::DISABLE_PERMISSIONS_INCREASE |
-                          disable_reason::DISABLE_CORRUPTED});
-}
-
-TEST_F(ExtensionUpdaterTest, TestDisabledReasons2) {
-  TestPingMetrics(1, {});
-}
-
-TEST_F(ExtensionUpdaterTest, TestDisabledReasons3) {
-  TestPingMetrics(0, {0});
-}
-
 TEST_F(ExtensionUpdaterTest, TestUninstallWhileUpdateCheck) {
   ExtensionDownloaderTestHelper helper;
   ServiceForManifestTests service(prefs_.get(), helper.url_loader_factory());
diff --git a/chrome/browser/extensions/user_script_listener.cc b/chrome/browser/extensions/user_script_listener.cc
index 30fe8a2..8e948661 100644
--- a/chrome/browser/extensions/user_script_listener.cc
+++ b/chrome/browser/extensions/user_script_listener.cc
@@ -16,7 +16,6 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/browser/notification_service.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/manifest_handlers/content_scripts_handler.h"
 #include "extensions/common/url_pattern.h"
@@ -77,7 +76,7 @@
   URLPatterns url_patterns;
 };
 
-UserScriptListener::UserScriptListener() : extension_registry_observer_(this) {
+UserScriptListener::UserScriptListener() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   // Profile manager can be null in unit tests.
diff --git a/chrome/browser/extensions/user_script_listener.h b/chrome/browser/extensions/user_script_listener.h
index c8207ab..a7d08aa 100644
--- a/chrome/browser/extensions/user_script_listener.h
+++ b/chrome/browser/extensions/user_script_listener.h
@@ -15,6 +15,7 @@
 #include "base/scoped_observer.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class GURL;
@@ -110,7 +111,7 @@
 
   ScopedObserver<extensions::ExtensionRegistry,
                  extensions::ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   content::NotificationRegistrar registrar_;
 
diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc
index 13e36de..14bf3e05 100644
--- a/chrome/browser/extensions/webstore_installer.cc
+++ b/chrome/browser/extensions/webstore_installer.cc
@@ -55,7 +55,6 @@
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
 #include "extensions/browser/extension_file_task_runner.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 #include "extensions/browser/install/crx_install_error.h"
 #include "extensions/common/extension.h"
@@ -286,7 +285,6 @@
                                      std::unique_ptr<Approval> approval,
                                      InstallSource source)
     : content::WebContentsObserver(web_contents),
-      extension_registry_observer_(this),
       profile_(profile),
       delegate_(delegate),
       id_(id),
diff --git a/chrome/browser/extensions/webstore_installer.h b/chrome/browser/extensions/webstore_installer.h
index 2dc86e3a..653f0f19 100644
--- a/chrome/browser/extensions/webstore_installer.h
+++ b/chrome/browser/extensions/webstore_installer.h
@@ -24,6 +24,7 @@
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 #include "extensions/common/manifest_handlers/shared_module_info.h"
 #include "ui/gfx/image/image_skia.h"
@@ -43,7 +44,6 @@
 
 class CrxInstaller;
 class Extension;
-class ExtensionRegistry;
 class Manifest;
 
 // Downloads and installs extensions from the web store.
@@ -263,7 +263,7 @@
 
   content::NotificationRegistrar registrar_;
   ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
   Profile* profile_;
   Delegate* delegate_;
   std::string id_;
diff --git a/chrome/browser/extensions/webstore_standalone_installer.cc b/chrome/browser/extensions/webstore_standalone_installer.cc
index d323555..f96c18d 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.cc
+++ b/chrome/browser/extensions/webstore_standalone_installer.cc
@@ -14,6 +14,7 @@
 #include "chrome/browser/extensions/extension_install_prompt.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/extensions/install_tracker.h"
+#include "chrome/browser/extensions/scoped_active_install.h"
 #include "chrome/browser/extensions/webstore_data_fetcher.h"
 #include "chrome/browser/profiles/profile.h"
 #include "components/crx_file/id_util.h"
diff --git a/chrome/browser/extensions/webstore_standalone_installer.h b/chrome/browser/extensions/webstore_standalone_installer.h
index fa586fa..273a6050 100644
--- a/chrome/browser/extensions/webstore_standalone_installer.h
+++ b/chrome/browser/extensions/webstore_standalone_installer.h
@@ -26,6 +26,7 @@
 
 namespace extensions {
 class Extension;
+class ScopedActiveInstall;
 class WebstoreDataFetcher;
 
 // A a purely abstract base for concrete classes implementing various types of
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 39a520d..699f417 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -3286,7 +3286,7 @@
 
 const char kEnableBackgroundBlurName[] = "Enable background blur.";
 const char kEnableBackgroundBlurDescription[] =
-    "Enables background blur for the Launcher and Shelf.";
+    "Enables background blur for the Launcher, Shelf, Unified System Tray etc.";
 
 const char kEnableChromeOsAccountManagerName[] = "Enable Account Manager";
 const char kEnableChromeOsAccountManagerDescription[] =
diff --git a/chrome/browser/mac/install_from_dmg.mm b/chrome/browser/mac/install_from_dmg.mm
index 758046a..bf53a408 100644
--- a/chrome/browser/mac/install_from_dmg.mm
+++ b/chrome/browser/mac/install_from_dmg.mm
@@ -29,7 +29,6 @@
 #include "base/mac/scoped_authorizationref.h"
 #include "base/mac/scoped_cftyperef.h"
 #include "base/mac/scoped_ioobject.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/sdk_forward_declarations.h"
 #include "base/macros.h"
 #include "base/stl_util.h"
@@ -407,84 +406,80 @@
 }
 
 bool MaybeInstallFromDiskImage() {
-  base::mac::ScopedNSAutoreleasePool autorelease_pool;
+  @autoreleasepool {
+    std::string dmg_bsd_device_name;
+    if (IsAppRunningFromReadOnlyDiskImage(&dmg_bsd_device_name) !=
+        DiskImageStatusTrue) {
+      return false;
+    }
 
-  std::string dmg_bsd_device_name;
-  if (IsAppRunningFromReadOnlyDiskImage(&dmg_bsd_device_name) !=
-      DiskImageStatusTrue) {
-    return false;
+    NSArray* application_directories = NSSearchPathForDirectoriesInDomains(
+        NSApplicationDirectory, NSLocalDomainMask, YES);
+    if ([application_directories count] == 0) {
+      LOG(ERROR) << "NSSearchPathForDirectoriesInDomains: "
+                 << "no local application directories";
+      return false;
+    }
+    NSString* application_directory = [application_directories objectAtIndex:0];
+
+    NSFileManager* file_manager = [NSFileManager defaultManager];
+
+    BOOL is_directory;
+    if (![file_manager fileExistsAtPath:application_directory
+                            isDirectory:&is_directory] ||
+        !is_directory) {
+      VLOG(1) << "No application directory at "
+              << [application_directory UTF8String];
+      return false;
+    }
+
+    NSString* source_path = [base::mac::OuterBundle() bundlePath];
+    NSString* application_name = [source_path lastPathComponent];
+    NSString* target_path =
+        [application_directory stringByAppendingPathComponent:application_name];
+
+    if ([file_manager fileExistsAtPath:target_path]) {
+      VLOG(1) << "Something already exists at " << [target_path UTF8String];
+      return false;
+    }
+
+    NSString* installer_path =
+        [base::mac::FrameworkBundle() pathForResource:@"install" ofType:@"sh"];
+    if (!installer_path) {
+      VLOG(1) << "Could not locate install.sh";
+      return false;
+    }
+
+    if (!ShouldInstallDialog()) {
+      return false;
+    }
+
+    base::mac::ScopedAuthorizationRef authorization(
+        MaybeShowAuthorizationDialog(application_directory));
+    // authorization will be NULL if it's deemed unnecessary or if
+    // authentication fails.  In either case, try to install without privilege
+    // escalation.
+
+    if (!InstallFromDiskImage(authorization.release(), installer_path,
+                              source_path, target_path)) {
+      ShowErrorDialog();
+      return false;
+    }
+
+    dock::AddIcon(target_path, source_path);
+
+    if (dmg_bsd_device_name.empty()) {
+      // Not fatal, just diagnostic.
+      LOG(ERROR) << "Could not determine disk image BSD device name";
+    }
+
+    if (!LaunchInstalledApp(target_path, dmg_bsd_device_name)) {
+      ShowErrorDialog();
+      return false;
+    }
+
+    return true;
   }
-
-  NSArray* application_directories =
-      NSSearchPathForDirectoriesInDomains(NSApplicationDirectory,
-                                          NSLocalDomainMask,
-                                          YES);
-  if ([application_directories count] == 0) {
-    LOG(ERROR) << "NSSearchPathForDirectoriesInDomains: "
-               << "no local application directories";
-    return false;
-  }
-  NSString* application_directory = [application_directories objectAtIndex:0];
-
-  NSFileManager* file_manager = [NSFileManager defaultManager];
-
-  BOOL is_directory;
-  if (![file_manager fileExistsAtPath:application_directory
-                          isDirectory:&is_directory] ||
-      !is_directory) {
-    VLOG(1) << "No application directory at "
-            << [application_directory UTF8String];
-    return false;
-  }
-
-  NSString* source_path = [base::mac::OuterBundle() bundlePath];
-  NSString* application_name = [source_path lastPathComponent];
-  NSString* target_path =
-      [application_directory stringByAppendingPathComponent:application_name];
-
-  if ([file_manager fileExistsAtPath:target_path]) {
-    VLOG(1) << "Something already exists at " << [target_path UTF8String];
-    return false;
-  }
-
-  NSString* installer_path =
-      [base::mac::FrameworkBundle() pathForResource:@"install" ofType:@"sh"];
-  if (!installer_path) {
-    VLOG(1) << "Could not locate install.sh";
-    return false;
-  }
-
-  if (!ShouldInstallDialog()) {
-    return false;
-  }
-
-  base::mac::ScopedAuthorizationRef authorization(
-      MaybeShowAuthorizationDialog(application_directory));
-  // authorization will be NULL if it's deemed unnecessary or if
-  // authentication fails.  In either case, try to install without privilege
-  // escalation.
-
-  if (!InstallFromDiskImage(authorization.release(),
-                            installer_path,
-                            source_path,
-                            target_path)) {
-    ShowErrorDialog();
-    return false;
-  }
-
-  dock::AddIcon(target_path, source_path);
-
-  if (dmg_bsd_device_name.empty()) {
-    // Not fatal, just diagnostic.
-    LOG(ERROR) << "Could not determine disk image BSD device name";
-  }
-
-  if (!LaunchInstalledApp(target_path, dmg_bsd_device_name)) {
-    ShowErrorDialog();
-    return false;
-  }
-
-  return true;
 }
 
 namespace {
diff --git a/chrome/browser/mac/keystone_glue.mm b/chrome/browser/mac/keystone_glue.mm
index c5fdad6..3555487 100644
--- a/chrome/browser/mac/keystone_glue.mm
+++ b/chrome/browser/mac/keystone_glue.mm
@@ -18,7 +18,6 @@
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_logging.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/memory/ref_counted.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/sys_string_conversions.h"
@@ -111,8 +110,9 @@
 
   // Happens on a WorkerPool thread.
   void Run() {
-    base::mac::ScopedNSAutoreleasePool pool;
-    [target_ performSelector:sel_ withObject:arg_];
+    @autoreleasepool {
+      [target_ performSelector:sel_ withObject:arg_];
+    }
   }
 
   base::scoped_nsobject<id> target_;
diff --git a/chrome/browser/mac/relauncher.mm b/chrome/browser/mac/relauncher.mm
index f90e8bf..f6b203b 100644
--- a/chrome/browser/mac/relauncher.mm
+++ b/chrome/browser/mac/relauncher.mm
@@ -23,7 +23,6 @@
 #include "base/files/scoped_file.h"
 #include "base/logging.h"
 #include "base/mac/mac_logging.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/path_service.h"
 #include "base/posix/eintr_wrapper.h"
@@ -266,137 +265,137 @@
 namespace internal {
 
 int RelauncherMain(const content::MainFunctionParams& main_parameters) {
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    // CommandLine rearranges the order of the arguments returned by
+    // main_parameters.argv(), rendering it impossible to determine which
+    // arguments originally came before kRelauncherArgSeparator and which came
+    // after. It's crucial to distinguish between these because only those
+    // after the separator should be given to the relaunched process; it's also
+    // important to not treat the path to the relaunched process as a "loose"
+    // argument. NXArgc and NXArgv are pointers to the original argc and argv as
+    // passed to main(), so use those. Access them through _NSGetArgc and
+    // _NSGetArgv because NXArgc and NXArgv are normally only available to a
+    // main executable via crt1.o and this code will run from a dylib, and
+    // because of http://crbug.com/139902.
+    const int* argcp = _NSGetArgc();
+    if (!argcp) {
+      NOTREACHED();
+      return 1;
+    }
+    int argc = *argcp;
 
-  // CommandLine rearranges the order of the arguments returned by
-  // main_parameters.argv(), rendering it impossible to determine which
-  // arguments originally came before kRelauncherArgSeparator and which came
-  // after. It's crucial to distinguish between these because only those
-  // after the separator should be given to the relaunched process; it's also
-  // important to not treat the path to the relaunched process as a "loose"
-  // argument. NXArgc and NXArgv are pointers to the original argc and argv as
-  // passed to main(), so use those. Access them through _NSGetArgc and
-  // _NSGetArgv because NXArgc and NXArgv are normally only available to a
-  // main executable via crt1.o and this code will run from a dylib, and
-  // because of http://crbug.com/139902.
-  const int* argcp = _NSGetArgc();
-  if (!argcp) {
-    NOTREACHED();
-    return 1;
-  }
-  int argc = *argcp;
+    const char* const* const* argvp = _NSGetArgv();
+    if (!argvp) {
+      NOTREACHED();
+      return 1;
+    }
+    const char* const* argv = *argvp;
 
-  const char* const* const* argvp = _NSGetArgv();
-  if (!argvp) {
-    NOTREACHED();
-    return 1;
-  }
-  const char* const* argv = *argvp;
-
-  if (argc < 4 || RelauncherTypeArg() != argv[1]) {
-    LOG(ERROR) << "relauncher process invoked with unexpected arguments";
-    return 1;
-  }
-
-  RelauncherSynchronizeWithParent();
-
-  // The capacity for relaunch_args is 4 less than argc, because it
-  // won't contain the argv[0] of the relauncher process, the
-  // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the
-  // executable path of the process to be launched.
-  base::scoped_nsobject<NSMutableArray> relaunch_args(
-      [[NSMutableArray alloc] initWithCapacity:argc - 4]);
-
-  // Figure out what to execute, what arguments to pass it, and whether to
-  // start it in the background.
-  bool background = false;
-  bool wait_for_staged_update = false;
-  bool in_relaunch_args = false;
-  std::string dmg_bsd_device_name;
-  bool seen_relaunch_executable = false;
-  std::string relaunch_executable;
-  const std::string relauncher_dmg_device_arg =
-      base::StringPrintf("--%s=", switches::kRelauncherProcessDMGDevice);
-  for (int argv_index = 2; argv_index < argc; ++argv_index) {
-    const std::string arg(argv[argv_index]);
-
-    // Strip any -psn_ arguments, as they apply to a specific process.
-    if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) {
-      continue;
+    if (argc < 4 || RelauncherTypeArg() != argv[1]) {
+      LOG(ERROR) << "relauncher process invoked with unexpected arguments";
+      return 1;
     }
 
-    if (!in_relaunch_args) {
-      if (arg == kRelauncherArgSeparator) {
-        in_relaunch_args = true;
-      } else if (arg == kRelauncherBackgroundArg) {
-        background = true;
-      } else if (arg == kRelauncherWaitForUpdateArg) {
-        wait_for_staged_update = true;
-      } else if (arg.compare(0, relauncher_dmg_device_arg.size(),
-                             relauncher_dmg_device_arg) == 0) {
-        dmg_bsd_device_name.assign(
-            arg.substr(relauncher_dmg_device_arg.size()));
-      }
-    } else {
-      if (!seen_relaunch_executable) {
-        // The first argument after kRelauncherBackgroundArg is the path to
-        // the executable file or .app bundle directory. The Launch Services
-        // interface wants this separate from the rest of the arguments. In
-        // the relaunched process, this path will still be visible at argv[0].
-        relaunch_executable.assign(arg);
-        seen_relaunch_executable = true;
-      } else {
+    RelauncherSynchronizeWithParent();
 
-        NSString* arg_string = base::SysUTF8ToNSString(arg);
-        if (!arg_string) {
-          LOG(ERROR) << "base::SysUTF8ToNSString failed for " << arg;
-          return 1;
+    // The capacity for relaunch_args is 4 less than argc, because it
+    // won't contain the argv[0] of the relauncher process, the
+    // RelauncherTypeArg() at argv[1], kRelauncherArgSeparator, or the
+    // executable path of the process to be launched.
+    base::scoped_nsobject<NSMutableArray> relaunch_args(
+        [[NSMutableArray alloc] initWithCapacity:argc - 4]);
+
+    // Figure out what to execute, what arguments to pass it, and whether to
+    // start it in the background.
+    bool background = false;
+    bool wait_for_staged_update = false;
+    bool in_relaunch_args = false;
+    std::string dmg_bsd_device_name;
+    bool seen_relaunch_executable = false;
+    std::string relaunch_executable;
+    const std::string relauncher_dmg_device_arg =
+        base::StringPrintf("--%s=", switches::kRelauncherProcessDMGDevice);
+    for (int argv_index = 2; argv_index < argc; ++argv_index) {
+      const std::string arg(argv[argv_index]);
+
+      // Strip any -psn_ arguments, as they apply to a specific process.
+      if (arg.compare(0, strlen(kPSNArg), kPSNArg) == 0) {
+        continue;
+      }
+
+      if (!in_relaunch_args) {
+        if (arg == kRelauncherArgSeparator) {
+          in_relaunch_args = true;
+        } else if (arg == kRelauncherBackgroundArg) {
+          background = true;
+        } else if (arg == kRelauncherWaitForUpdateArg) {
+          wait_for_staged_update = true;
+        } else if (arg.compare(0, relauncher_dmg_device_arg.size(),
+                               relauncher_dmg_device_arg) == 0) {
+          dmg_bsd_device_name.assign(
+              arg.substr(relauncher_dmg_device_arg.size()));
         }
-        [relaunch_args addObject:arg_string];
+      } else {
+        if (!seen_relaunch_executable) {
+          // The first argument after kRelauncherBackgroundArg is the path to
+          // the executable file or .app bundle directory. The Launch Services
+          // interface wants this separate from the rest of the arguments. In
+          // the relaunched process, this path will still be visible at argv[0].
+          relaunch_executable.assign(arg);
+          seen_relaunch_executable = true;
+        } else {
+          NSString* arg_string = base::SysUTF8ToNSString(arg);
+          if (!arg_string) {
+            LOG(ERROR) << "base::SysUTF8ToNSString failed for " << arg;
+            return 1;
+          }
+          [relaunch_args addObject:arg_string];
+        }
       }
     }
+
+    if (!seen_relaunch_executable) {
+      LOG(ERROR) << "nothing to relaunch";
+      return 1;
+    }
+
+    // If an update is staged but not yet installed, wait for it to be
+    // installed.
+    if (wait_for_staged_update) {
+      base::scoped_nsobject<CrStagingKeyWatcher> watcher(
+          [[CrStagingKeyWatcher alloc] initWithPollingTime:0.5]);
+      [watcher waitForStagingKeyToClear];
+    }
+
+    NSString* path = base::SysUTF8ToNSString(relaunch_executable);
+    base::scoped_nsobject<NSURL> url([[NSURL alloc] initFileURLWithPath:path]);
+    NSDictionary* configuration =
+        @{NSWorkspaceLaunchConfigurationArguments : (relaunch_args.get())};
+
+    NSRunningApplication* application = [[NSWorkspace sharedWorkspace]
+        launchApplicationAtURL:url
+                       options:NSWorkspaceLaunchDefault |
+                               NSWorkspaceLaunchWithErrorPresentation |
+                               (background ? NSWorkspaceLaunchWithoutActivation
+                                           : 0) |
+                               NSWorkspaceLaunchNewInstance
+                 configuration:configuration
+                         error:nil];
+    if (!application) {
+      LOG(ERROR) << "Failed to relaunch " << relaunch_executable;
+      return 1;
+    }
+
+    // The application should have relaunched (or is in the process of
+    // relaunching). From this point on, only clean-up tasks should occur, and
+    // failures are tolerable.
+
+    if (!dmg_bsd_device_name.empty()) {
+      EjectAndTrashDiskImage(dmg_bsd_device_name);
+    }
+
+    return 0;
   }
-
-  if (!seen_relaunch_executable) {
-    LOG(ERROR) << "nothing to relaunch";
-    return 1;
-  }
-
-  // If an update is staged but not yet installed, wait for it to be installed.
-  if (wait_for_staged_update) {
-    base::scoped_nsobject<CrStagingKeyWatcher> watcher(
-        [[CrStagingKeyWatcher alloc] initWithPollingTime:0.5]);
-    [watcher waitForStagingKeyToClear];
-  }
-
-  NSString* path = base::SysUTF8ToNSString(relaunch_executable);
-  base::scoped_nsobject<NSURL> url([[NSURL alloc] initFileURLWithPath:path]);
-  NSDictionary* configuration =
-      @{NSWorkspaceLaunchConfigurationArguments : (relaunch_args.get())};
-
-  NSRunningApplication *application = [[NSWorkspace sharedWorkspace]
-      launchApplicationAtURL:url
-                     options:NSWorkspaceLaunchDefault |
-                             NSWorkspaceLaunchWithErrorPresentation |
-                             (background ? NSWorkspaceLaunchWithoutActivation
-                                         : 0) |
-                             NSWorkspaceLaunchNewInstance
-               configuration:configuration
-                       error:nil];
-  if (!application) {
-    LOG(ERROR) << "Failed to relaunch " << relaunch_executable;
-    return 1;
-  }
-
-  // The application should have relaunched (or is in the process of
-  // relaunching). From this point on, only clean-up tasks should occur, and
-  // failures are tolerable.
-
-  if (!dmg_bsd_device_name.empty()) {
-    EjectAndTrashDiskImage(dmg_bsd_device_name);
-  }
-
-  return 0;
 }
 
 }  // namespace internal
diff --git a/chrome/browser/memory/enterprise_memory_limit_evaluator.cc b/chrome/browser/memory/enterprise_memory_limit_evaluator.cc
new file mode 100644
index 0000000..50188d0
--- /dev/null
+++ b/chrome/browser/memory/enterprise_memory_limit_evaluator.cc
@@ -0,0 +1,121 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/memory/enterprise_memory_limit_evaluator.h"
+
+#include "base/bind.h"
+#include "chrome/browser/performance_manager/performance_manager.h"
+#include "chrome/browser/performance_manager/public/graph/process_node.h"
+
+namespace memory {
+
+EnterpriseMemoryLimitEvaluator::EnterpriseMemoryLimitEvaluator(
+    std::unique_ptr<util::MemoryPressureVoter> voter)
+    : voter_(std::move(voter)), weak_ptr_factory_(this) {}
+
+EnterpriseMemoryLimitEvaluator::~EnterpriseMemoryLimitEvaluator() {
+  DCHECK(!observer_);
+}
+
+void EnterpriseMemoryLimitEvaluator::Start() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(!observer_);
+  auto observer =
+      std::make_unique<EnterpriseMemoryLimitEvaluator::GraphObserver>(
+          base::BindRepeating(
+              &EnterpriseMemoryLimitEvaluator::OnTotalResidentSetKbSample,
+              weak_ptr_factory_.GetWeakPtr()),
+          base::SequencedTaskRunnerHandle::Get());
+  observer_ = observer.get();
+  performance_manager::PerformanceManager::PassToGraph(FROM_HERE,
+                                                       std::move(observer));
+}
+
+std::unique_ptr<EnterpriseMemoryLimitEvaluator::GraphObserver>
+EnterpriseMemoryLimitEvaluator::StartForTesting() {
+  DCHECK(!observer_);
+  auto observer =
+      std::make_unique<EnterpriseMemoryLimitEvaluator::GraphObserver>(
+          base::BindRepeating(
+              &EnterpriseMemoryLimitEvaluator::OnTotalResidentSetKbSample,
+              weak_ptr_factory_.GetWeakPtr()),
+          base::SequencedTaskRunnerHandle::Get());
+  observer_ = observer.get();
+  return observer;
+}
+
+void EnterpriseMemoryLimitEvaluator::Stop() {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(observer_);
+  // Start by invalidating all the WeakPtrs that have been served, this will
+  // invalidate the callback owned by the observer.
+  weak_ptr_factory_.InvalidateWeakPtrs();
+  // It's safe to pass |observer_| as Unretained, it's merely used as a key.
+  performance_manager::PerformanceManager::CallOnGraph(
+      FROM_HERE, base::BindOnce(
+                     [](performance_manager::GraphOwned* observer,
+                        performance_manager::GraphImpl* graph) {
+                       // This will destroy the observer since TakeFromGraph
+                       // returns a unique_ptr.
+                       graph->TakeFromGraph(observer);
+                     },
+                     base::Unretained(observer_)));
+  observer_ = nullptr;
+}
+
+void EnterpriseMemoryLimitEvaluator::StopForTesting() {
+  observer_ = nullptr;
+}
+
+void EnterpriseMemoryLimitEvaluator::OnTotalResidentSetKbSample(
+    uint64_t resident_set_sample_kb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  // Limit is in mb, must convert to kb.
+  bool is_critical = resident_set_sample_kb > (resident_set_limit_mb_ * 1024);
+  voter_->SetVote(
+      is_critical ? base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL
+                  : base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE,
+      /* notify_listeners = */ is_critical);
+}
+
+void EnterpriseMemoryLimitEvaluator::SetResidentSetLimitMb(
+    uint64_t resident_set_limit_mb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  resident_set_limit_mb_ = resident_set_limit_mb;
+}
+
+EnterpriseMemoryLimitEvaluator::GraphObserver::GraphObserver(
+    base::RepeatingCallback<void(uint64_t)> on_sample_callback,
+    scoped_refptr<base::SequencedTaskRunner> task_runner)
+    : on_sample_callback_(std::move(on_sample_callback)),
+      task_runner_(task_runner) {}
+
+EnterpriseMemoryLimitEvaluator::GraphObserver::~GraphObserver() = default;
+
+void EnterpriseMemoryLimitEvaluator::GraphObserver::OnPassedToGraph(
+    performance_manager::Graph* graph) {
+  graph->AddSystemNodeObserver(this);
+}
+
+void EnterpriseMemoryLimitEvaluator::GraphObserver::OnTakenFromGraph(
+    performance_manager::Graph* graph) {
+  graph->RemoveSystemNodeObserver(this);
+}
+
+void EnterpriseMemoryLimitEvaluator::GraphObserver::
+    OnProcessMemoryMetricsAvailable(
+        const performance_manager::SystemNode* system_node) {
+  uint64_t total_rss_kb = 0U;
+  for (const auto* node : system_node->GetGraph()->GetAllProcessNodes())
+    total_rss_kb += node->GetResidentSetKb();
+  task_runner_->PostTask(
+      FROM_HERE, base::BindOnce(std::move(on_sample_callback_), total_rss_kb));
+}
+
+void EnterpriseMemoryLimitEvaluator::GraphObserver::
+    OnProcessMemoryMetricsAvailableForTesting(uint64_t total_rss_kb) {
+  on_sample_callback_.Run(total_rss_kb);
+}
+
+}  // namespace memory
diff --git a/chrome/browser/memory/enterprise_memory_limit_evaluator.h b/chrome/browser/memory/enterprise_memory_limit_evaluator.h
new file mode 100644
index 0000000..59f3069
--- /dev/null
+++ b/chrome/browser/memory/enterprise_memory_limit_evaluator.h
@@ -0,0 +1,98 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_MEMORY_ENTERPRISE_MEMORY_LIMIT_EVALUATOR_H_
+#define CHROME_BROWSER_MEMORY_ENTERPRISE_MEMORY_LIMIT_EVALUATOR_H_
+
+#include "base/callback.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/util/memory_pressure/memory_pressure_voter.h"
+#include "chrome/browser/performance_manager/public/graph/graph.h"
+#include "chrome/browser/performance_manager/public/graph/system_node.h"
+
+namespace memory {
+
+// Periodically receives updates on Chrome's current total resident set,
+// compares that usage to the limit set via an Enterprise policy, and uses a
+// MemoryLimitVoter to send its vote on the MemoryPressureLevel to the
+// MultiSourceMemoryPressureMonitor.
+class EnterpriseMemoryLimitEvaluator {
+ protected:
+  class GraphObserver;
+
+ public:
+  explicit EnterpriseMemoryLimitEvaluator(
+      std::unique_ptr<util::MemoryPressureVoter> voter);
+  ~EnterpriseMemoryLimitEvaluator();
+
+  // Starts/stops observing the resident set of Chrome processes and notifying
+  // its MemoryPressureVoter when it is above the limit.
+  void Start();
+  void Stop();
+
+  std::unique_ptr<EnterpriseMemoryLimitEvaluator::GraphObserver>
+  StartForTesting();
+  void StopForTesting();
+
+  // Sets the limit against which the total resident set will be compared.
+  void SetResidentSetLimitMb(uint64_t resident_set_limit_mb);
+
+ private:
+  // Called on the sequence that owns this object every time a new total RSS
+  // sample is available.
+  void OnTotalResidentSetKbSample(uint64_t resident_set_sample_kb);
+
+  // Raw pointer to the GraphObserver used by this object to monitor the total
+  // RSS. This is only meant to be used as a key to remove the observer once
+  // it's not necessary anymore, do not call functions directly from this
+  // pointer.
+  GraphObserver* observer_ = nullptr;
+
+  uint64_t resident_set_limit_mb_ = 0;
+
+  const std::unique_ptr<util::MemoryPressureVoter> voter_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  base::WeakPtrFactory<EnterpriseMemoryLimitEvaluator> weak_ptr_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(EnterpriseMemoryLimitEvaluator);
+};
+
+// Instances of this class are constructed and destructed on the main thread.
+// They are then passed to the Performance Manager's Graph and a task gets
+// posted to the proper task runner when new data is available.
+class EnterpriseMemoryLimitEvaluator::GraphObserver
+    : public performance_manager::SystemNode::ObserverDefaultImpl,
+      public performance_manager::GraphOwned {
+ public:
+  // The constructor of this class takes 2 parameters: the callback to run each
+  // time a new sample is available and the task runner on which this callback
+  // should run.
+  GraphObserver(base::RepeatingCallback<void(uint64_t)> on_sample_callback,
+                scoped_refptr<base::SequencedTaskRunner> task_runner);
+
+  ~GraphObserver() override;
+
+  // GraphOwned implementation, called on the PM sequence:
+  void OnPassedToGraph(performance_manager::Graph* graph) override;
+  void OnTakenFromGraph(performance_manager::Graph* graph) override;
+
+  // SystemNode::ObserverDefaultImpl, called on the PM sequence:
+  void OnProcessMemoryMetricsAvailable(
+      const performance_manager::SystemNode* system_node) override;
+
+  void OnProcessMemoryMetricsAvailableForTesting(uint64_t total_rss_kb);
+
+ private:
+  const base::RepeatingCallback<void(uint64_t)> on_sample_callback_;
+
+  // The task runner on which |on_sample_callback_| should be invoked.
+  const scoped_refptr<base::SequencedTaskRunner> task_runner_;
+};
+
+}  // namespace memory
+
+#endif  // CHROME_BROWSER_MEMORY_ENTERPRISE_MEMORY_LIMIT_EVALUATOR_H_
diff --git a/chrome/browser/memory/enterprise_memory_limit_evaluator_unittest.cc b/chrome/browser/memory/enterprise_memory_limit_evaluator_unittest.cc
new file mode 100644
index 0000000..eb64769a
--- /dev/null
+++ b/chrome/browser/memory/enterprise_memory_limit_evaluator_unittest.cc
@@ -0,0 +1,45 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/memory/enterprise_memory_limit_evaluator.h"
+
+#include "base/test/bind_test_util.h"
+#include "base/test/task_environment.h"
+#include "base/util/memory_pressure/multi_source_memory_pressure_monitor.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace memory {
+
+TEST(EnterpriseMemoryLimitEvaluatorTest, OnProcessMemoryMetricsAvailable) {
+  base::test::TaskEnvironment task_environment;
+
+  util::MultiSourceMemoryPressureMonitor monitor;
+  monitor.ResetSystemEvaluatorForTesting();
+  bool cb_called = false;
+  monitor.SetDispatchCallback(base::BindLambdaForTesting(
+      [&](base::MemoryPressureListener::MemoryPressureLevel level) {
+        EXPECT_EQ(level,
+                  base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
+        cb_called = true;
+      }));
+
+  EnterpriseMemoryLimitEvaluator evaluator(monitor.CreateVoter());
+  evaluator.SetResidentSetLimitMb(1);
+  auto observer = evaluator.StartForTesting();
+
+  observer->OnProcessMemoryMetricsAvailableForTesting(1025);
+  EXPECT_TRUE(cb_called);
+
+  cb_called = false;
+
+  // MEMORY_PRESSURE_LEVEL_NONE should not trigger dispatch callback.
+  observer->OnProcessMemoryMetricsAvailableForTesting(1023);
+  EXPECT_FALSE(cb_called);
+  EXPECT_EQ(monitor.aggregator_for_testing()->EvaluateVotesForTesting(),
+            base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_NONE);
+
+  evaluator.StopForTesting();
+}
+
+}  // namespace memory
diff --git a/chrome/browser/performance_manager/BUILD.gn b/chrome/browser/performance_manager/BUILD.gn
index 588d3c0..ab1114ae 100644
--- a/chrome/browser/performance_manager/BUILD.gn
+++ b/chrome/browser/performance_manager/BUILD.gn
@@ -3,19 +3,8 @@
 # found in the LICENSE file.
 
 import("//chrome/common/features.gni")
-import("//mojo/public/tools/bindings/mojom.gni")
 import("//third_party/protobuf/proto_library.gni")
 
-mojom("mojo_bindings") {
-  sources = [
-    "webui_graph_dump.mojom",
-  ]
-  public_deps = [
-    "//mojo/public/mojom/base",
-    "//url/mojom:url_mojom_gurl",
-  ]
-}
-
 proto_library("site_data_proto") {
   sources = [
     "persistence/site_data/site_data.proto",
diff --git a/chrome/browser/performance_manager/DEPS b/chrome/browser/performance_manager/DEPS
index 86d5863..166ed20 100644
--- a/chrome/browser/performance_manager/DEPS
+++ b/chrome/browser/performance_manager/DEPS
@@ -9,7 +9,4 @@
   "chrome.*": [
     "+chrome/browser",
   ],
-  "webui_graph_dump_impl\.cc": [
-    "+chrome/browser",
-  ],
 }
diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc
index cfc2b8be..d8c1ac63 100644
--- a/chrome/browser/policy/policy_browsertest.cc
+++ b/chrome/browser/policy/policy_browsertest.cc
@@ -51,6 +51,7 @@
 #include "chrome/browser/background/background_contents_service.h"
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
 #include "chrome/browser/chromeos/policy/configuration_policy_handler_chromeos.h"
 #include "chrome/browser/component_updater/chrome_component_updater_configurator.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
@@ -275,6 +276,7 @@
 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
 #include "chromeos/audio/cras_audio_handler.h"
 #include "chromeos/constants/chromeos_features.h"
+#include "chromeos/constants/chromeos_pref_names.h"
 #include "chromeos/constants/chromeos_switches.h"
 #include "chromeos/cryptohome/cryptohome_parameters.h"
 #include "components/account_id/account_id.h"
@@ -1371,6 +1373,56 @@
   CheckLaunchedBrowserCount(1u);
 }
 
+class PrimaryUserPoliciesProxiedTest : public LoginPolicyTestBase {
+ public:
+  PrimaryUserPoliciesProxiedTest() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(PrimaryUserPoliciesProxiedTest);
+};
+
+IN_PROC_BROWSER_TEST_F(PrimaryUserPoliciesProxiedTest,
+                       AvailableInLocalStateEarly) {
+  PolicyService* const device_wide_policy_service =
+      g_browser_process->platform_part()
+          ->browser_policy_connector_chromeos()
+          ->GetPolicyService();
+
+  // Sanity check default state without a policy active.
+  EXPECT_FALSE(device_wide_policy_service
+                   ->GetPolicies(PolicyNamespace(
+                       POLICY_DOMAIN_CHROME, std::string() /* component_id */))
+                   .GetValue(key::kAudioOutputAllowed));
+  const PrefService::Preference* pref =
+      g_browser_process->local_state()->FindPreference(
+          chromeos::prefs::kAudioOutputAllowed);
+  EXPECT_FALSE(pref->IsManaged());
+  EXPECT_TRUE(pref->GetValue()->GetBool());
+
+  base::DictionaryValue policy;
+  policy.SetKey(key::kAudioOutputAllowed, base::Value(false));
+  user_policy_helper()->SetPolicy(policy, base::DictionaryValue());
+
+  SkipToLoginScreen();
+
+  content::WindowedNotificationObserver profile_created_observer(
+      chrome::NOTIFICATION_PROFILE_CREATED,
+      content::NotificationService::AllSources());
+  TriggerLogIn(kAccountId, kAccountPassword, kEmptyServices);
+  profile_created_observer.Wait();
+
+  const base::Value* policy_value =
+      device_wide_policy_service
+          ->GetPolicies(PolicyNamespace(POLICY_DOMAIN_CHROME,
+                                        std::string() /* component_id */))
+          .GetValue(key::kAudioOutputAllowed);
+  ASSERT_TRUE(policy_value);
+  EXPECT_FALSE(policy_value->GetBool());
+
+  EXPECT_TRUE(pref->IsManaged());
+  EXPECT_FALSE(pref->GetValue()->GetBool());
+}
+
 #endif  // defined(OS_CHROMEOS)
 
 IN_PROC_BROWSER_TEST_F(PolicyTest, BookmarkBarEnabled) {
diff --git a/chrome/browser/policy/profile_policy_connector.cc b/chrome/browser/policy/profile_policy_connector.cc
index e35525d..faf302d 100644
--- a/chrome/browser/policy/profile_policy_connector.cc
+++ b/chrome/browser/policy/profile_policy_connector.cc
@@ -8,6 +8,8 @@
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/threading/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "build/build_config.h"
 #include "chrome/browser/browser_process.h"
@@ -31,12 +33,67 @@
 #include "chrome/browser/chromeos/policy/device_local_account.h"
 #include "chrome/browser/chromeos/policy/device_local_account_policy_provider.h"
 #include "chrome/browser/chromeos/policy/login_profile_policy_provider.h"
+#include "components/policy/core/common/proxy_policy_provider.h"
 #include "components/user_manager/user.h"
 #include "components/user_manager/user_manager.h"
 #endif
 
 namespace policy {
 
+#if defined(OS_CHROMEOS)
+namespace internal {
+
+// This class allows observing a |device_wide_policy_service| for policy updates
+// during which the |source_policy_provider| has already been initialized.
+// It is used to know when propagation of primary user policies proxied to the
+// device-wide PolicyService has finished.
+class ProxiedPoliciesPropagatedWatcher : PolicyService::ProviderUpdateObserver {
+ public:
+  ProxiedPoliciesPropagatedWatcher(
+      PolicyService* device_wide_policy_service,
+      ProxyPolicyProvider* proxy_policy_provider,
+      ConfigurationPolicyProvider* source_policy_provider,
+      base::OnceClosure proxied_policies_propagated_callback)
+      : device_wide_policy_service_(device_wide_policy_service),
+        proxy_policy_provider_(proxy_policy_provider),
+        source_policy_provider_(source_policy_provider),
+        proxied_policies_propagated_callback_(
+            std::move(proxied_policies_propagated_callback)) {
+    device_wide_policy_service->AddProviderUpdateObserver(this);
+  }
+
+  ~ProxiedPoliciesPropagatedWatcher() override {
+    device_wide_policy_service_->RemoveProviderUpdateObserver(this);
+  }
+
+  // PolicyService::Observer:
+  void OnProviderUpdatePropagated(
+      ConfigurationPolicyProvider* provider) override {
+    if (!proxied_policies_propagated_callback_)
+      return;
+    if (provider != proxy_policy_provider_)
+      return;
+
+    if (!source_policy_provider_->IsInitializationComplete(
+            POLICY_DOMAIN_CHROME)) {
+      return;
+    }
+
+    std::move(proxied_policies_propagated_callback_).Run();
+  }
+
+ private:
+  PolicyService* const device_wide_policy_service_;
+  const ProxyPolicyProvider* const proxy_policy_provider_;
+  const ConfigurationPolicyProvider* const source_policy_provider_;
+  base::OnceClosure proxied_policies_propagated_callback_;
+
+  DISALLOW_COPY_AND_ASSIGN(ProxiedPoliciesPropagatedWatcher);
+};
+
+}  // namespace internal
+#endif  // defined(OS_CHROMEOS)
+
 ProfilePolicyConnector::ProfilePolicyConnector() {}
 
 ProfilePolicyConnector::~ProfilePolicyConnector() {}
@@ -120,14 +177,14 @@
   policy_service_ = std::make_unique<PolicyServiceImpl>(policy_providers_);
 
 #if defined(OS_CHROMEOS)
+  ConfigurationPolicyProvider* user_policy_delegate = nullptr;
   if (is_primary_user_) {
-    if (configuration_policy_provider)
-      browser_policy_connector->SetUserPolicyDelegate(
-          configuration_policy_provider);
-    else if (special_user_policy_provider_)
-      browser_policy_connector->SetUserPolicyDelegate(
-          special_user_policy_provider_.get());
+    user_policy_delegate = configuration_policy_provider
+                               ? configuration_policy_provider
+                               : special_user_policy_provider_.get();
   }
+  if (user_policy_delegate)
+    SetGlobalUserPolicyDelegate(user_policy_delegate);
 #endif
 }
 
@@ -142,14 +199,13 @@
 
 void ProfilePolicyConnector::Shutdown() {
 #if defined(OS_CHROMEOS)
-  if (is_primary_user_) {
-    BrowserPolicyConnectorChromeOS* connector =
-        g_browser_process->platform_part()->browser_policy_connector_chromeos();
-    connector->SetUserPolicyDelegate(nullptr);
-  }
+  if (is_primary_user_)
+    SetGlobalUserPolicyDelegate(nullptr);
+
   if (special_user_policy_provider_)
     special_user_policy_provider_->Shutdown();
 #endif
+
   if (wrapped_platform_policy_provider_)
     wrapped_platform_policy_provider_->Shutdown();
 }
@@ -199,4 +255,48 @@
   return nullptr;
 }
 
+#if defined(OS_CHROMEOS)
+void ProfilePolicyConnector::SetGlobalUserPolicyDelegate(
+    ConfigurationPolicyProvider* user_policy_delegate) {
+  BrowserPolicyConnectorChromeOS* browser_policy_connector =
+      g_browser_process->platform_part()->browser_policy_connector_chromeos();
+  PolicyService* device_wide_policy_service =
+      browser_policy_connector->GetPolicyService();
+  ProxyPolicyProvider* proxy_policy_provider =
+      browser_policy_connector->GetGlobalUserCloudPolicyProvider();
+
+  // The ProxyPolicyProvider may be available from |browser_policy_connector|
+  // but not actually used by the |device_wide_policy_service| in tests (e.g. if
+  // BrowserPolicyConnectorBase::SetPolicyProviderForTesting has been used).
+  if (!device_wide_policy_service->HasProvider(proxy_policy_provider))
+    return;
+
+  if (!user_policy_delegate) {
+    proxy_policy_provider->SetDelegate(nullptr);
+    return;
+  }
+
+  policy_service_->SetInitializationThrottled(true);
+  // base::Unretained is OK for |this| because
+  // |proxied_policies_propagated_watcher_| is guaranteed not to call its
+  // callback after it has been destroyed.
+  proxied_policies_propagated_watcher_ =
+      std::make_unique<internal::ProxiedPoliciesPropagatedWatcher>(
+          device_wide_policy_service, proxy_policy_provider,
+          user_policy_delegate,
+          base::BindOnce(&ProfilePolicyConnector::OnProxiedPoliciesPropagated,
+                         base::Unretained(this)));
+  proxy_policy_provider->SetDelegate(user_policy_delegate);
+}
+
+void ProfilePolicyConnector::OnProxiedPoliciesPropagated() {
+  policy_service_->SetInitializationThrottled(false);
+  // Do not delete |proxied_policies_propagated_watcher_| synchronously, as the
+  // PolicyService it is observing is expected to be iterating its observer
+  // list.
+  base::ThreadTaskRunnerHandle::Get()->DeleteSoon(
+      FROM_HERE, std::move(proxied_policies_propagated_watcher_));
+}
+#endif
+
 }  // namespace policy
diff --git a/chrome/browser/policy/profile_policy_connector.h b/chrome/browser/policy/profile_policy_connector.h
index c50a3c9..5e3f8ea 100644
--- a/chrome/browser/policy/profile_policy_connector.h
+++ b/chrome/browser/policy/profile_policy_connector.h
@@ -10,6 +10,7 @@
 #include <vector>
 
 #include "base/macros.h"
+#include "base/memory/weak_ptr.h"
 #include "build/build_config.h"
 
 namespace user_manager {
@@ -17,6 +18,11 @@
 }
 
 namespace policy {
+#if defined(OS_CHROMEOS)
+namespace internal {
+class ProxiedPoliciesPropagatedWatcher;
+}
+#endif  // defined(OS_CHROMEOS)
 
 class CloudPolicyStore;
 class ConfigurationPolicyProvider;
@@ -74,6 +80,24 @@
       const char* policy_key) const;
 
 #if defined(OS_CHROMEOS)
+  // On Chrome OS, primary Profile user policies are forwarded to the
+  // device-global PolicyService[1] using a ProxyPolicyProvider. This function
+  // sets up that forwarding, using |user_policy_delegate| as the policy source.
+  // It also delays signaling that |policy_service_| is initialized until the
+  // policies provided by |user_policy_delegate| have propagated to the
+  // device-wide PolicyService[1]. This is done so that code that runs early on
+  // Profile initialization can rely on the device-wide PolicyService[1] and
+  // local state Preferences[2] respecting the proxied primary user policies.
+  //
+  // [1] i.e. g_browser_process->policy_service()
+  // [2] i.e. g_browser_process->local_state()
+  void SetGlobalUserPolicyDelegate(
+      ConfigurationPolicyProvider* user_policy_delegate);
+
+  // Called when primary user policies that are proxied to the device-wide
+  // PolicyService have propagated.
+  void OnProxiedPoliciesPropagated();
+
   // Some of the user policy configuration affects browser global state, and
   // can only come from one Profile. |is_primary_user_| is true if this
   // connector belongs to the first signed-in Profile, and in that case that
@@ -82,6 +106,15 @@
   bool is_primary_user_ = false;
 
   std::unique_ptr<ConfigurationPolicyProvider> special_user_policy_provider_;
+
+  // If the user associated with the Profile for this ProfilePolicyConnector is
+  // the primary user, the user policies will be proxied into the device-wide
+  // PolicyService. This object allows calling a callback when that is finished
+  // so it is possible to delay signaling that |policy_service_| is initialized
+  // until the policies have been reflected in the device-wide PolicyService.
+  std::unique_ptr<internal::ProxiedPoliciesPropagatedWatcher>
+      proxied_policies_propagated_watcher_;
+
 #endif  // defined(OS_CHROMEOS)
 
   std::unique_ptr<ConfigurationPolicyProvider>
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index a7918299..c16d658 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -272,13 +272,15 @@
   chromeos::AccountManagerMigratorFactory::GetInstance();
   chromeos::CupsPrintJobManagerFactory::GetInstance();
   chromeos::CupsPrintersManagerFactory::GetInstance();
-  chromeos::CupsProxyServiceManagerFactory::GetInstance();
   chromeos::SyncedPrintersManagerFactory::GetInstance();
   chromeos::smb_client::SmbServiceFactory::GetInstance();
   crostini::CrostiniRegistryServiceFactory::GetInstance();
   extensions::SessionStateChangedEventDispatcher::GetFactoryInstance();
   extensions::VerifyTrustAPI::GetFactoryInstance();
   TetherServiceFactory::GetInstance();
+#if defined(USE_CUPS)
+  chromeos::CupsProxyServiceManagerFactory::GetInstance();
+#endif
 #endif
   FaviconServiceFactory::GetInstance();
   HistoryUiFaviconRequestHandlerFactory::GetInstance();
diff --git a/chrome/browser/resources/chromeos/add_supervision/add_supervision.html b/chrome/browser/resources/chromeos/add_supervision/add_supervision.html
index 2259d170..2fdc636 100644
--- a/chrome/browser/resources/chromeos/add_supervision/add_supervision.html
+++ b/chrome/browser/resources/chromeos/add_supervision/add_supervision.html
@@ -23,20 +23,20 @@
           html,
           body {
             height: 100%;
-            overflow: auto;
-            overflow-y: hidden;
           }
 
           .title {
             color: var(--google-grey-900);
             font-family: 'Google Sans', Roboto, sans-serif;
             font-size: 28px;
-            padding: 32px 0 16px 0;
+            padding-top: 20px;
           }
 
           .sub-title {
-            color: var(--google-grey-900);
-            font: 13px Roboto, sans-serif;
+            color: var(--google-grey-700);
+            font-family: Roboto;
+            font-size: 14px;
+            padding-top: 8px;
           }
 
           .error-icon {
@@ -48,7 +48,7 @@
           }
 
           #offlineContentDiv {
-            padding-inline-start: 50px;
+            padding-inline-start: 32px;
           }
 
           #networkUnavailableDiv {
@@ -57,14 +57,14 @@
           }
 
           #closeButtonDiv {
-            padding-inline-start: 600px;
-            padding-top: 100px;
+            padding-inline-start: 632px;
+            padding-top: 116px;
             width: 100%;
           }
         </style>
         <div id="offlineContentDiv">
           <div>
-            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="error-icon">
+            <svg xmlns="http://www.w3.org/2000/svg" class="error-icon" width="32px" height="32px" viewBox="0 0 22 22">
               <path d="M11 15h2v2h-2v-2zm0-8h2v6h-2V7zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z">
               <path fill="none" d="M0 0h24v24H0V0z">
             </svg>
diff --git a/chrome/browser/resources/discards/BUILD.gn b/chrome/browser/resources/discards/BUILD.gn
index abe3f6ba..8cdbf0b 100644
--- a/chrome/browser/resources/discards/BUILD.gn
+++ b/chrome/browser/resources/discards/BUILD.gn
@@ -50,7 +50,6 @@
 
 js_library("graph_tab") {
   deps = [
-    "//chrome/browser/performance_manager:mojo_bindings_js_library_for_compile",
     "//chrome/browser/ui/webui/discards:mojo_bindings_js_library_for_compile",
     "//ui/webui/resources/js:assert",
     "//ui/webui/resources/js:cr",
@@ -59,7 +58,7 @@
 
 js_library("graph_doc") {
   deps = [
-    "//chrome/browser/performance_manager:mojo_bindings_js_library_for_compile",
+    "//chrome/browser/ui/webui/discards:mojo_bindings_js_library_for_compile",
   ]
 
   externs_list = [ "../../../../third_party/d3/src/externs.js" ]
diff --git a/chrome/browser/resources/pdf/BUILD.gn b/chrome/browser/resources/pdf/BUILD.gn
index 89a0223c..772c1e79 100644
--- a/chrome/browser/resources/pdf/BUILD.gn
+++ b/chrome/browser/resources/pdf/BUILD.gn
@@ -86,6 +86,13 @@
   ]
 }
 
+js_library("toolbar_manager") {
+  deps = [
+    "elements/viewer-pdf-toolbar:viewer-pdf-toolbar",
+    "elements/viewer-zoom-toolbar:viewer-zoom-toolbar",
+  ]
+}
+
 js_type_check("pdf_resources") {
   deps = [
     ":browser_api",
@@ -95,6 +102,7 @@
     ":open_pdf_params_parser",
     ":pdf_fitting_type",
     ":pdf_scripting_api",
+    ":toolbar_manager",
     ":viewport",
     ":viewport_scroller",
     ":zoom_manager",
diff --git a/chrome/browser/resources/pdf/toolbar_manager.js b/chrome/browser/resources/pdf/toolbar_manager.js
index 255bc33..a8caa38 100644
--- a/chrome/browser/resources/pdf/toolbar_manager.js
+++ b/chrome/browser/resources/pdf/toolbar_manager.js
@@ -20,10 +20,8 @@
 const SIDE_TOOLBAR_REVEAL_DISTANCE_RIGHT = 150;
 const SIDE_TOOLBAR_REVEAL_DISTANCE_BOTTOM = 250;
 
-
-
 /**
- * @param {MouseEvent} e Event to test.
+ * @param {!MouseEvent} e Event to test.
  * @return {boolean} True if the mouse is close to the top of the screen.
  */
 function isMouseNearTopToolbar(e) {
@@ -31,7 +29,7 @@
 }
 
 /**
- * @param {MouseEvent} e Event to test.
+ * @param {!MouseEvent} e Event to test.
  * @param {Window} window Window to test against.
  * @param {boolean} reverse Whether the side toolbar is reversed.
  * @return {boolean} True if the mouse is close to the bottom-right of the
@@ -47,46 +45,59 @@
   return atSide && atBottom;
 }
 
-/**
- * Constructs a Toolbar Manager, responsible for co-ordinating between multiple
- * toolbar elements.
- *
- * @param {Object} window The window containing the UI.
- * @param {Object} toolbar The top toolbar element.
- * @param {Object} zoomToolbar The zoom toolbar element.
- * @constructor
- */
-function ToolbarManager(window, toolbar, zoomToolbar) {
-  this.window_ = window;
-  this.toolbar_ = toolbar;
-  this.zoomToolbar_ = zoomToolbar;
+/** Responsible for co-ordinating between multiple toolbar elements. */
+class ToolbarManager {
+  /**
+   * @param {!Window} window The window containing the UI.
+   * @param {!ViewerPdfToolbarElement} toolbar
+   * @param {!ViewerZoomToolbarElement} zoomToolbar
+   */
+  constructor(window, toolbar, zoomToolbar) {
+    /** @private {!Window} */
+    this.window_ = window;
 
-  this.toolbarTimeout_ = null;
-  this.isMouseNearTopToolbar_ = false;
-  this.isMouseNearSideToolbar_ = false;
+    /** @private {!ViewerPdfToolbarElement} */
+    this.toolbar_ = toolbar;
 
-  this.sideToolbarAllowedOnly_ = false;
-  this.sideToolbarAllowedOnlyTimer_ = null;
+    /** @private {!ViewerZoomToolbarElement} */
+    this.zoomToolbar_ = zoomToolbar;
 
-  this.keyboardNavigationActive = false;
+    /** @private {?number} */
+    this.toolbarTimeout_ = null;
 
-  this.lastMovementTimestamp = null;
+    /** @private {boolean} */
+    this.isMouseNearTopToolbar_ = false;
 
-  this.reverseSideToolbar_ = false;
+    /** @private {boolean} */
+    this.isMouseNearSideToolbar_ = false;
 
-  this.window_.addEventListener('resize', this.resizeDropdowns_.bind(this));
-  this.resizeDropdowns_();
+    /** @private {boolean} */
+    this.sideToolbarAllowedOnly_ = false;
 
-  if (zoomToolbar.isPrintPreview()) {
-    this.zoomToolbar_.addEventListener('keyboard-navigation-active', e => {
-      this.keyboardNavigationActive = e.detail;
-    });
+    /** @private {?number} */
+    this.sideToolbarAllowedOnlyTimer_ = null;
+
+    /** @private {boolean} */
+    this.keyboardNavigationActive = false;
+
+    /** @private {?number} */
+    this.lastMovementTimestamp = null;
+
+    /** @private {boolean} */
+    this.reverseSideToolbar_ = false;
+
+    this.window_.addEventListener('resize', this.resizeDropdowns_.bind(this));
+    this.resizeDropdowns_();
+
+    if (zoomToolbar.isPrintPreview()) {
+      this.zoomToolbar_.addEventListener('keyboard-navigation-active', e => {
+        this.keyboardNavigationActive = e.detail;
+      });
+    }
   }
-}
 
-ToolbarManager.prototype = {
-
-  handleMouseMove: function(e) {
+  /** @param {!MouseEvent} e */
+  handleMouseMove(e) {
     this.isMouseNearTopToolbar_ = this.toolbar_ && isMouseNearTopToolbar(e);
     this.isMouseNearSideToolbar_ =
         isMouseNearSideToolbar(e, this.window_, this.reverseSideToolbar_);
@@ -123,17 +134,16 @@
       }
     }
     this.hideToolbarsAfterTimeout();
-  },
+  }
 
   /**
    * Whether a mousemove event is high enough velocity to reveal the toolbars.
-   *
-   * @param {MouseEvent} e Event to test.
+   * @param {!MouseEvent} e Event to test.
    * @return {boolean} true if the event is a high velocity mousemove, false
    * otherwise.
    * @private
    */
-  isHighVelocityMouseMove_: function(e) {
+  isHighVelocityMouseMove_(e) {
     if (e.type == 'mousemove') {
       if (this.lastMovementTimestamp == null) {
         this.lastMovementTimestamp = this.getCurrentTimestamp_();
@@ -150,54 +160,51 @@
       }
     }
     return false;
-  },
+  }
 
   /**
    * Wrapper around Date.now() to make it easily replaceable for testing.
-   *
    * @return {number}
    * @private
    */
-  getCurrentTimestamp_: function() {
+  getCurrentTimestamp_() {
     return Date.now();
-  },
+  }
 
-  /**
-   * Display both UI toolbars.
-   */
-  showToolbars: function() {
+  /** Display both UI toolbars. */
+  showToolbars() {
     if (this.toolbar_) {
       this.toolbar_.show();
     }
     this.zoomToolbar_.show();
-  },
+  }
 
   /**
    * Show toolbars and mark that navigation is being performed with
    * tab/shift-tab. This disables toolbar hiding until the mouse is moved or
    * escape is pressed.
    */
-  showToolbarsForKeyboardNavigation: function() {
+  showToolbarsForKeyboardNavigation() {
     this.keyboardNavigationActive = true;
     this.showToolbars();
-  },
+  }
 
   /**
    * Hide toolbars after a delay, regardless of the position of the mouse.
    * Intended to be called when the mouse has moved out of the parent window.
    */
-  hideToolbarsForMouseOut: function() {
+  hideToolbarsForMouseOut() {
     this.isMouseNearTopToolbar_ = false;
     this.isMouseNearSideToolbar_ = false;
     this.hideToolbarsAfterTimeout();
-  },
+  }
 
   /**
    * Check if the toolbars are able to be closed, and close them if they are.
    * Toolbars may be kept open based on mouse/keyboard activity and active
    * elements.
    */
-  hideToolbarsIfAllowed: function() {
+  hideToolbarsIfAllowed() {
     if (this.isMouseNearSideToolbar_ || this.isMouseNearTopToolbar_) {
       return;
     }
@@ -221,47 +228,44 @@
       this.toolbar_.hide();
     }
     this.zoomToolbar_.hide();
-  },
+  }
 
-  /**
-   * Hide the toolbar after the HIDE_TIMEOUT has elapsed.
-   */
-  hideToolbarsAfterTimeout: function() {
+  /** Hide the toolbars after the HIDE_TIMEOUT has elapsed. */
+  hideToolbarsAfterTimeout() {
     if (this.toolbarTimeout_) {
       this.window_.clearTimeout(this.toolbarTimeout_);
     }
     this.toolbarTimeout_ = this.window_.setTimeout(
         this.hideToolbarsIfAllowed.bind(this), HIDE_TIMEOUT);
-  },
+  }
 
   /**
    * Hide the 'topmost' layer of toolbars. Hides any dropdowns that are open, or
    * hides the basic toolbars otherwise.
    */
-  hideSingleToolbarLayer: function() {
+  hideSingleToolbarLayer() {
     if (!this.toolbar_ || !this.toolbar_.hideDropdowns()) {
       this.keyboardNavigationActive = false;
       this.hideToolbarsIfAllowed();
     }
-  },
+  }
 
   /**
    * Clears the keyboard navigation state and hides the toolbars after a delay.
    */
-  resetKeyboardNavigationAndHideToolbars: function() {
+  resetKeyboardNavigationAndHideToolbars() {
     this.keyboardNavigationActive = false;
     this.hideToolbarsAfterTimeout();
-  },
+  }
 
   /**
    * Hide the top toolbar and keep it hidden until both:
    * - The mouse is moved away from the right side of the screen
    * - 1 second has passed.
-   *
    * The top toolbar can be immediately re-opened by moving the mouse to the top
    * of the screen.
    */
-  forceHideTopToolbar: function() {
+  forceHideTopToolbar() {
     if (!this.toolbar_) {
       return;
     }
@@ -270,20 +274,19 @@
     this.sideToolbarAllowedOnlyTimer_ = this.window_.setTimeout(() => {
       this.sideToolbarAllowedOnlyTimer_ = null;
     }, FORCE_HIDE_TIMEOUT);
-  },
+  }
 
   /** Reverse the position of the side toolbar. */
-  reverseSideToolbar: function() {
+  reverseSideToolbar() {
     this.reverseSideToolbar_ = true;
-  },
+  }
 
   /**
    * Updates the size of toolbar dropdowns based on the positions of the rest of
    * the UI.
-   *
    * @private
    */
-  resizeDropdowns_: function() {
+  resizeDropdowns_() {
     if (!this.toolbar_) {
       return;
     }
@@ -291,4 +294,4 @@
         this.window_.innerHeight - this.zoomToolbar_.clientHeight;
     this.toolbar_.setDropdownLowerBound(lowerBound);
   }
-};
+}
diff --git a/chrome/browser/resources/settings/internet_page/internet_detail_page.js b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
index 8829dbe..88b6efc 100644
--- a/chrome/browser/resources/settings/internet_page/internet_detail_page.js
+++ b/chrome/browser/resources/settings/internet_page/internet_detail_page.js
@@ -518,10 +518,11 @@
       this.close();
       return;
     }
-    this.managedProperties_ = OncMojo.getDefaultManagedProperties(
+    const managedProperties = OncMojo.getDefaultManagedProperties(
         networkState.type, networkState.guid, networkState.name);
-    this.managedProperties_.connectable = networkState.connectable;
-    this.managedProperties_.connectionState = networkState.connectionState;
+    managedProperties.connectable = networkState.connectable;
+    managedProperties.connectionState = networkState.connectionState;
+    this.managedProperties_ = managedProperties;
 
     this.propertiesReceived_ = true;
     this.outOfRange_ = false;
diff --git a/chrome/browser/resources/settings/internet_page/network_summary_item.js b/chrome/browser/resources/settings/internet_page/network_summary_item.js
index 12ba1e3..2d826b67 100644
--- a/chrome/browser/resources/settings/internet_page/network_summary_item.js
+++ b/chrome/browser/resources/settings/internet_page/network_summary_item.js
@@ -133,7 +133,12 @@
     const name =
         networkState ? OncMojo.getNetworkStateDisplayName(networkState) : '';
     if (OncMojo.connectionStateIsConnected(connectionState)) {
-      return name;
+      // Ethernet networks always have the display name 'Ethernet' so we use the
+      // state text 'Connected' to avoid repeating the label in the sublabel.
+      // See http://crbug.com/989907 for details.
+      return networkState.type == mojom.NetworkType.kEthernet ?
+          CrOncStrings.networkListItemConnected :
+          name;
     }
     if (connectionState == mojom.ConnectionStateType.kConnecting) {
       return name ?
diff --git a/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html b/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
index 01d7cb1..6fb8781 100644
--- a/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
+++ b/chrome/browser/resources/settings/site_settings/category_setting_exceptions.html
@@ -1,7 +1,9 @@
 <link rel="import" href="chrome://resources/html/polymer.html">
 
+<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
 <link rel="import" href="constants.html">
 <link rel="import" href="site_list.html">
+<link rel="import" href="site_settings_prefs_browser_proxy.html">
 
 <dom-module id="category-setting-exceptions">
   <template>
@@ -9,7 +11,7 @@
         category="[[category]]"
         category-subtype="[[ContentSetting.BLOCK]]"
         category-header="[[blockHeader]]"
-        read-only-list="[[readOnlyList]]"
+        read-only-list="[[getReadOnlyList_(readOnlyList, defaultManaged_)]]"
         search-filter="[[searchFilter]]"
         hidden$="[[!showBlockSiteList_]]">
     </site-list>
@@ -17,14 +19,14 @@
         category="[[category]]"
         category-subtype="[[ContentSetting.SESSION_ONLY]]"
         category-header="$i18n{siteSettingsSessionOnly}"
-        read-only-list="[[readOnlyList]]"
+        read-only-list="[[getReadOnlyList_(readOnlyList, defaultManaged_)]]"
         search-filter="[[searchFilter]]">
     </site-list>
     <site-list
         category="[[category]]"
         category-subtype="[[ContentSetting.ALLOW]]"
         category-header="$i18n{siteSettingsAllow}"
-        read-only-list="[[readOnlyList]]"
+        read-only-list="[[getReadOnlyList_(readOnlyList, defaultManaged_)]]"
         search-filter="[[searchFilter]]"
         hidden$="[[!showAllowSiteList_]]">
     </site-list>
diff --git a/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js b/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js
index 2122044..4499725 100644
--- a/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js
+++ b/chrome/browser/resources/settings/site_settings/category_setting_exceptions.js
@@ -10,6 +10,8 @@
 Polymer({
   is: 'category-setting-exceptions',
 
+  behaviors: [SiteSettingsBehavior, WebUIListenerBehavior],
+
   properties: {
 
     /**
@@ -30,6 +32,12 @@
     },
 
     /**
+     * True if the default value is managed by a policy.
+     * @private
+     */
+    defaultManaged_: Boolean,
+
+    /**
      * The heading text for the blocked exception list.
      */
     blockHeader: String,
@@ -54,9 +62,15 @@
     },
   },
 
+  observers: [
+    'updateDefaultManaged_(category)',
+  ],
+
   /** @override */
   ready: function() {
     this.ContentSetting = settings.ContentSetting;
+    this.addWebUIListener(
+        'contentSettingCategoryChanged', this.updateDefaultManaged_.bind(this));
   },
 
   /**
@@ -69,4 +83,32 @@
     return this.category !=
         settings.ContentSettingsTypes.NATIVE_FILE_SYSTEM_WRITE;
   },
+
+  /**
+   * Updates whether or not the default value is managed by a policy.
+   * @private
+   */
+  updateDefaultManaged_: function() {
+    if (this.category === undefined) {
+      return;
+    }
+
+    this.browserProxy.getDefaultValueForContentType(this.category)
+      .then(update => {
+        this.defaultManaged_ =
+          update.source === settings.SiteSettingSource.POLICY;
+      });
+  },
+
+  /**
+   * Returns true if this list is explicitly marked as readonly by a consumer
+   * of this component or if the default value for these exceptions are managed
+   * by a policy. User should not be able to set exceptions to managed default
+   * values.
+   * @return {boolean}
+   * @private
+   */
+  getReadOnlyList_: function() {
+    return this.readOnlyList || this.defaultManaged_;
+  }
 });
diff --git a/chrome/browser/resources/tab_strip/tab_list.js b/chrome/browser/resources/tab_strip/tab_list.js
index dd72aec..914c12d 100644
--- a/chrome/browser/resources/tab_strip/tab_list.js
+++ b/chrome/browser/resources/tab_strip/tab_list.js
@@ -11,6 +11,13 @@
 
 const GHOST_PINNED_TAB_COUNT = 3;
 
+/**
+ * The amount of padding to leave between the edge of the screen and the active
+ * tab when auto-scrolling. This should leave some room to show the previous or
+ * next tab to afford to users that there more tabs if the user scrolls.
+ */
+const SCROLL_PADDING = 32;
+
 class TabListElement extends CustomElement {
   static get template() {
     return `{__html_template__}`;
@@ -33,6 +40,9 @@
         /** @type {!Element} */ (
             this.shadowRoot.querySelector('#pinnedTabsContainer'));
 
+    /** @private {!Element} */
+    this.scrollingParent_ = document.documentElement;
+
     /** @private {!TabsApiProxy} */
     this.tabsApi_ = TabsApiProxy.getInstance();
 
@@ -74,6 +84,11 @@
             this.onTabCreated_(tab);
           }
         }
+
+        const activeTab = this.getActiveTab_();
+        if (activeTab) {
+          this.scrollToTab_(activeTab);
+        }
       }
 
       this.tabsApiHandler_.onActivated.addListener(
@@ -107,6 +122,15 @@
   }
 
   /**
+   * @return {?TabElement}
+   * @private
+   */
+  getActiveTab_() {
+    return /** @type {?TabElement} */ (
+        this.shadowRoot.querySelector('tabstrip-tab[active]'));
+  }
+
+  /**
    * @param {!TabElement} tabElement
    * @param {number} index
    * @private
@@ -140,16 +164,18 @@
       return;
     }
 
-    const previouslyActiveTab =
-        this.shadowRoot.querySelector('tabstrip-tab[active]');
+    const previouslyActiveTab = this.getActiveTab_();
     if (previouslyActiveTab) {
       previouslyActiveTab.tab = /** @type {!Tab} */ (
           Object.assign({}, previouslyActiveTab.tab, {active: false}));
     }
 
     const newlyActiveTab = this.findTabElement_(activeInfo.tabId);
-    newlyActiveTab.tab = /** @type {!Tab} */ (
-        Object.assign({}, newlyActiveTab.tab, {active: true}));
+    if (newlyActiveTab) {
+      newlyActiveTab.tab = /** @type {!Tab} */ (
+          Object.assign({}, newlyActiveTab.tab, {active: true}));
+      this.scrollToTab_(newlyActiveTab);
+    }
   }
 
   /**
@@ -179,6 +205,9 @@
     const movedTab = this.findTabElement_(tabId);
     if (movedTab) {
       this.insertTabOrMoveTo_(movedTab, moveInfo.toIndex);
+      if (movedTab.tab.active) {
+        this.scrollToTab_(movedTab);
+      }
     }
   }
 
@@ -221,11 +250,39 @@
         // If the tab is being pinned or unpinned, we need to move it to its new
         // location
         this.insertTabOrMoveTo_(tabElement, tab.index);
+        if (tab.active) {
+          this.scrollToTab_(tabElement);
+        }
       }
     }
   }
 
   /**
+   * @param {!TabElement} tabElement
+   * @private
+   */
+  scrollToTab_(tabElement) {
+    this.animationPromises.then(() => {
+      const screenLeft = this.scrollingParent_.scrollLeft;
+      const screenRight = screenLeft + this.scrollingParent_.offsetWidth;
+
+      if (screenLeft > tabElement.offsetLeft) {
+        // If the element's left is to the left of the visible screen, scroll
+        // such that the element's left edge is aligned with the screen's edge
+        this.scrollingParent_.scrollLeft =
+            tabElement.offsetLeft - SCROLL_PADDING;
+      } else if (screenRight < tabElement.offsetLeft + tabElement.offsetWidth) {
+        // If the element's right is to the right of the visible screen, scroll
+        // such that the element's right edge is aligned with the screen's right
+        // edge.
+        this.scrollingParent_.scrollLeft = tabElement.offsetLeft +
+            tabElement.offsetWidth - this.scrollingParent_.offsetWidth +
+            SCROLL_PADDING;
+      }
+    });
+  }
+
+  /**
    * @param {number} tabId
    * @param {string} imgData
    * @private
diff --git a/chrome/browser/ui/BUILD.gn b/chrome/browser/ui/BUILD.gn
index fd86aa3..efab61f 100644
--- a/chrome/browser/ui/BUILD.gn
+++ b/chrome/browser/ui/BUILD.gn
@@ -1980,13 +1980,16 @@
 
   if (is_win || is_mac || is_desktop_linux || is_chromeos) {
     sources += [
-      "autofill/payments/webauthn_offer_dialog.h",
-      "autofill/payments/webauthn_offer_dialog_controller.cc",
       "autofill/payments/webauthn_offer_dialog_controller.h",
+      "autofill/payments/webauthn_offer_dialog_controller_impl.cc",
+      "autofill/payments/webauthn_offer_dialog_controller_impl.h",
+      "autofill/payments/webauthn_offer_dialog_model.cc",
+      "autofill/payments/webauthn_offer_dialog_model.h",
+      "autofill/payments/webauthn_offer_dialog_view.h",
       "frame/window_frame_util.cc",
       "frame/window_frame_util.h",
-      "views/autofill/payments/webauthn_offer_dialog_view.cc",
-      "views/autofill/payments/webauthn_offer_dialog_view.h",
+      "views/autofill/payments/webauthn_offer_dialog_view_impl.cc",
+      "views/autofill/payments/webauthn_offer_dialog_view_impl.h",
       "views/close_bubble_on_tab_activation_helper.cc",
       "views/close_bubble_on_tab_activation_helper.h",
       "views/hats/hats_bubble_view.cc",
@@ -1999,6 +2002,8 @@
       "views/profiles/profile_menu_view_base.h",
       "webui/discards/discards_ui.cc",
       "webui/discards/discards_ui.h",
+      "webui/discards/webui_graph_dump_impl.cc",
+      "webui/discards/webui_graph_dump_impl.h",
       "webui/signin/inline_login_handler.cc",
       "webui/signin/inline_login_handler.h",
       "webui/signin/inline_login_ui.cc",
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 84ffbcf..2c40247 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -29,7 +29,6 @@
 #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
 #include "chrome/browser/ui/autofill/payments/create_card_unmask_prompt_view.h"
 #include "chrome/browser/ui/autofill/payments/credit_card_scanner_controller.h"
-#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog.h"
 #include "chrome/browser/ui/chrome_pages.h"
 #include "chrome/browser/ui/page_info/page_info_dialog.h"
 #include "chrome/browser/ui/passwords/manage_passwords_view_utils.h"
@@ -83,6 +82,8 @@
 #include "ui/android/window_android.h"
 #else  // !OS_ANDROID
 #include "chrome/browser/ui/autofill/payments/save_card_bubble_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_commands.h"
 #include "chrome/browser/ui/browser_finder.h"
@@ -270,10 +271,24 @@
 void ChromeAutofillClient::ShowWebauthnOfferDialog(
     WebauthnOfferDialogCallback callback) {
 #if !defined(OS_ANDROID)
-  ShowWebauthnOfferDialogView(web_contents(), callback);
+  autofill::WebauthnOfferDialogControllerImpl::CreateForWebContents(
+      web_contents());
+  autofill::WebauthnOfferDialogControllerImpl::FromWebContents(web_contents())
+      ->ShowOfferDialog(std::move(callback));
 #endif
 }
 
+bool ChromeAutofillClient::CloseWebauthnOfferDialog() {
+#if !defined(OS_ANDROID)
+  WebauthnOfferDialogControllerImpl* controller =
+      autofill::WebauthnOfferDialogControllerImpl::FromWebContents(
+          web_contents());
+  if (controller)
+    return controller->CloseDialog();
+#endif
+  return false;
+}
+
 void ChromeAutofillClient::ConfirmSaveAutofillProfile(
     const AutofillProfile& profile,
     base::OnceClosure callback) {
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index 8bac3d2..dbdb34d 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -83,6 +83,7 @@
       const std::vector<MigratableCreditCard>& migratable_credit_cards,
       MigrationDeleteCardCallback delete_local_card_callback) override;
   void ShowWebauthnOfferDialog(WebauthnOfferDialogCallback callback) override;
+  bool CloseWebauthnOfferDialog() override;
   void ConfirmSaveAutofillProfile(const AutofillProfile& profile,
                                   base::OnceClosure callback) override;
   void ConfirmSaveCreditCardLocally(
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog.h b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog.h
deleted file mode 100644
index 3a7d389c..0000000
--- a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_H_
-#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_H_
-
-#include "base/memory/weak_ptr.h"
-#include "components/autofill/core/browser/autofill_client.h"
-
-namespace content {
-class WebContents;
-}
-
-namespace autofill {
-
-// Creates and shows the dialog to offer the option of using device's platform
-// authenticator instead of CVC to verify the card in the future.
-void ShowWebauthnOfferDialogView(
-    content::WebContents* web_contents,
-    AutofillClient::WebauthnOfferDialogCallback callback);
-
-}  // namespace autofill
-
-#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_H_
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_browsertest.cc b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_browsertest.cc
index aeef4cc2..d0f1d85 100644
--- a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_browsertest.cc
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_browsertest.cc
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog.h"
-#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/test/test_browser_dialog.h"
 
@@ -18,7 +18,12 @@
     content::WebContents* web_contents =
         browser()->tab_strip_model()->GetActiveWebContents();
 
-    ShowWebauthnOfferDialogView(web_contents, base::DoNothing());
+    // Do lazy initialization of WebauthnOfferDialogControllerImpl.
+    WebauthnOfferDialogControllerImpl::CreateForWebContents(web_contents);
+    WebauthnOfferDialogControllerImpl* controller =
+        WebauthnOfferDialogControllerImpl::FromWebContents(web_contents);
+    DCHECK(controller);
+    controller->ShowOfferDialog(base::DoNothing());
   }
 
  private:
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.cc b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.cc
deleted file mode 100644
index 52f425c9..0000000
--- a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.cc
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h"
-
-#include "chrome/app/vector_icons/vector_icons.h"
-#include "components/strings/grit/components_strings.h"
-#include "ui/base/l10n/l10n_util.h"
-
-namespace autofill {
-
-WebauthnOfferDialogController::WebauthnOfferDialogController(
-    AutofillClient::WebauthnOfferDialogCallback callback)
-    : callback_(callback) {}
-
-WebauthnOfferDialogController::~WebauthnOfferDialogController() {
-  callback_.Reset();
-}
-
-bool WebauthnOfferDialogController::IsActivityIndicatorVisible() const {
-  return fetching_authentication_challenge_;
-}
-
-bool WebauthnOfferDialogController::IsBackButtonVisible() const {
-  return false;
-}
-
-bool WebauthnOfferDialogController::IsCancelButtonVisible() const {
-  return true;
-}
-
-base::string16 WebauthnOfferDialogController::GetCancelButtonLabel() const {
-  return l10n_util::GetStringUTF16(
-      IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_CANCEL_BUTTON_LABEL);
-}
-
-bool WebauthnOfferDialogController::IsAcceptButtonVisible() const {
-  return true;
-}
-
-bool WebauthnOfferDialogController::IsAcceptButtonEnabled() const {
-  return !fetching_authentication_challenge_;
-}
-
-base::string16 WebauthnOfferDialogController::GetAcceptButtonLabel() const {
-  return l10n_util::GetStringUTF16(
-      IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_OK_BUTTON_LABEL);
-}
-
-const gfx::VectorIcon& WebauthnOfferDialogController::GetStepIllustration(
-    ImageColorScheme color_scheme) const {
-  return color_scheme == ImageColorScheme::kDark
-             ? kWebauthnOfferDialogHeaderDarkIcon
-             : kWebauthnOfferDialogHeaderIcon;
-}
-
-base::string16 WebauthnOfferDialogController::GetStepTitle() const {
-  return l10n_util::GetStringUTF16(IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE);
-}
-
-base::string16 WebauthnOfferDialogController::GetStepDescription() const {
-  return l10n_util::GetStringUTF16(
-      IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_INSTRUCTION);
-}
-
-base::Optional<base::string16>
-WebauthnOfferDialogController::GetAdditionalDescription() const {
-  return base::nullopt;
-}
-
-ui::MenuModel* WebauthnOfferDialogController::GetOtherTransportsMenuModel() {
-  return nullptr;
-}
-
-void WebauthnOfferDialogController::OnBack() {}
-
-void WebauthnOfferDialogController::OnAccept() {
-  DCHECK(callback_);
-  callback_.Run(true);
-}
-
-void WebauthnOfferDialogController::OnCancel() {
-  DCHECK(callback_);
-  callback_.Run(false);
-}
-
-}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h
index bc29bce..ce58f07 100644
--- a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h
@@ -5,48 +5,30 @@
 #ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_CONTROLLER_H_
 #define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_CONTROLLER_H_
 
-#include "base/callback_forward.h"
 #include "base/macros.h"
-#include "base/memory/weak_ptr.h"
-#include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
-#include "components/autofill/core/browser/autofill_client.h"
+
+namespace content {
+class WebContents;
+}
 
 namespace autofill {
 
-// Per-tab controller lazily initialized when the WebauthnOfferDialogView is
-// shown. Owned by the AuthenticatorRequestSheetView.
-class WebauthnOfferDialogController : public AuthenticatorRequestSheetModel {
+// An interface that exposes necessary controller functionality to
+// WebauthnOfferDialogView.
+class WebauthnOfferDialogController {
  public:
-  explicit WebauthnOfferDialogController(
-      AutofillClient::WebauthnOfferDialogCallback callback);
-  ~WebauthnOfferDialogController() override;
+  WebauthnOfferDialogController() = default;
+  virtual ~WebauthnOfferDialogController() = default;
 
-  // AuthenticatorRequestSheetModel:
-  bool IsActivityIndicatorVisible() const override;
-  bool IsBackButtonVisible() const override;
-  bool IsCancelButtonVisible() const override;
-  base::string16 GetCancelButtonLabel() const override;
-  bool IsAcceptButtonVisible() const override;
-  bool IsAcceptButtonEnabled() const override;
-  base::string16 GetAcceptButtonLabel() const override;
-  const gfx::VectorIcon& GetStepIllustration(
-      ImageColorScheme color_scheme) const override;
-  base::string16 GetStepTitle() const override;
-  base::string16 GetStepDescription() const override;
-  base::Optional<base::string16> GetAdditionalDescription() const override;
-  ui::MenuModel* GetOtherTransportsMenuModel() override;
-  void OnBack() override;
-  void OnAccept() override;
-  void OnCancel() override;
+  virtual void OnOkButtonClicked() = 0;
+
+  virtual void OnCancelButtonClicked() = 0;
+
+  virtual void OnDialogClosed() = 0;
+
+  virtual content::WebContents* GetWebContents() = 0;
 
  private:
-  bool fetching_authentication_challenge_ = false;
-
-  // Callback invoked when any button in the dialog is clicked. Note this repeating callback can be
-  // run twice, since after the accept button is clicked, the dialog stays and the cancel button is
-  // still clickable.
-  AutofillClient::WebauthnOfferDialogCallback callback_;
-
   DISALLOW_COPY_AND_ASSIGN(WebauthnOfferDialogController);
 };
 
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.cc b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.cc
new file mode 100644
index 0000000..3e20e6e
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.cc
@@ -0,0 +1,56 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h"
+
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h"
+
+namespace autofill {
+
+WebauthnOfferDialogControllerImpl::WebauthnOfferDialogControllerImpl(
+    content::WebContents* web_contents)
+    : content::WebContentsObserver(web_contents) {}
+
+WebauthnOfferDialogControllerImpl::~WebauthnOfferDialogControllerImpl() =
+    default;
+
+void WebauthnOfferDialogControllerImpl::ShowOfferDialog(
+    AutofillClient::WebauthnOfferDialogCallback callback) {
+  DCHECK(!dialog_view_);
+
+  offer_dialog_callback_ = std::move(callback);
+  dialog_view_ = WebauthnOfferDialogView::CreateAndShow(this);
+}
+
+bool WebauthnOfferDialogControllerImpl::CloseDialog() {
+  if (!dialog_view_)
+    return false;
+
+  dialog_view_->Hide();
+  return true;
+}
+
+void WebauthnOfferDialogControllerImpl::OnOkButtonClicked() {
+  DCHECK(offer_dialog_callback_);
+  offer_dialog_callback_.Run(/*did_accept=*/true);
+  dialog_view_->RefreshContent();
+}
+
+void WebauthnOfferDialogControllerImpl::OnCancelButtonClicked() {
+  DCHECK(offer_dialog_callback_);
+  offer_dialog_callback_.Run(/*did_accept=*/false);
+}
+
+void WebauthnOfferDialogControllerImpl::OnDialogClosed() {
+  dialog_view_ = nullptr;
+  offer_dialog_callback_.Reset();
+}
+
+content::WebContents* WebauthnOfferDialogControllerImpl::GetWebContents() {
+  return web_contents();
+}
+
+WEB_CONTENTS_USER_DATA_KEY_IMPL(WebauthnOfferDialogControllerImpl)
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h
new file mode 100644
index 0000000..3474a9b
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller_impl.h
@@ -0,0 +1,58 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_CONTROLLER_IMPL_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_CONTROLLER_IMPL_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h"
+#include "components/autofill/core/browser/autofill_client.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/browser/web_contents_user_data.h"
+
+namespace autofill {
+
+class WebauthnOfferDialogView;
+
+// Implementation of the per-tab controller to control the
+// WebauthnOfferDialogView. Lazily initialized when used.
+class WebauthnOfferDialogControllerImpl
+    : public WebauthnOfferDialogController,
+      public content::WebContentsObserver,
+      public content::WebContentsUserData<WebauthnOfferDialogControllerImpl> {
+ public:
+  ~WebauthnOfferDialogControllerImpl() override;
+
+  void ShowOfferDialog(AutofillClient::WebauthnOfferDialogCallback callback);
+  bool CloseDialog();
+
+  // WebauthnOfferDialogController:
+  void OnOkButtonClicked() override;
+  void OnCancelButtonClicked() override;
+  void OnDialogClosed() override;
+  content::WebContents* GetWebContents() override;
+
+ protected:
+  explicit WebauthnOfferDialogControllerImpl(
+      content::WebContents* web_contents);
+
+ private:
+  friend class content::WebContentsUserData<WebauthnOfferDialogControllerImpl>;
+
+  // Callback invoked when any button in the dialog is clicked. Note this
+  // repeating callback can be run twice, since after the accept button is
+  // clicked, the dialog stays and the cancel button is still clickable.
+  AutofillClient::WebauthnOfferDialogCallback offer_dialog_callback_;
+
+  // Reference to the dialog, which is owned by the view hierarchy.
+  WebauthnOfferDialogView* dialog_view_ = nullptr;
+
+  WEB_CONTENTS_USER_DATA_KEY_DECL();
+
+  DISALLOW_COPY_AND_ASSIGN(WebauthnOfferDialogControllerImpl);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_CONTROLLER_IMPL_H_
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.cc b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.cc
new file mode 100644
index 0000000..72aca01
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.cc
@@ -0,0 +1,82 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.h"
+
+#include "chrome/app/vector_icons/vector_icons.h"
+#include "components/strings/grit/components_strings.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace autofill {
+
+WebauthnOfferDialogModel::WebauthnOfferDialogModel() {
+  state_ = kOffer;
+}
+
+WebauthnOfferDialogModel::~WebauthnOfferDialogModel() = default;
+
+bool WebauthnOfferDialogModel::IsActivityIndicatorVisible() const {
+  return state_ == kPending;
+}
+
+bool WebauthnOfferDialogModel::IsBackButtonVisible() const {
+  return false;
+}
+
+bool WebauthnOfferDialogModel::IsCancelButtonVisible() const {
+  return true;
+}
+
+base::string16 WebauthnOfferDialogModel::GetCancelButtonLabel() const {
+  return l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_CANCEL_BUTTON_LABEL);
+}
+
+bool WebauthnOfferDialogModel::IsAcceptButtonVisible() const {
+  return true;
+}
+
+bool WebauthnOfferDialogModel::IsAcceptButtonEnabled() const {
+  return state_ != kPending;
+}
+
+base::string16 WebauthnOfferDialogModel::GetAcceptButtonLabel() const {
+  return l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_OK_BUTTON_LABEL);
+}
+
+const gfx::VectorIcon& WebauthnOfferDialogModel::GetStepIllustration(
+    ImageColorScheme color_scheme) const {
+  return color_scheme == ImageColorScheme::kDark
+             ? kWebauthnOfferDialogHeaderDarkIcon
+             : kWebauthnOfferDialogHeaderIcon;
+}
+
+base::string16 WebauthnOfferDialogModel::GetStepTitle() const {
+  return l10n_util::GetStringUTF16(IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE);
+}
+
+base::string16 WebauthnOfferDialogModel::GetStepDescription() const {
+  return l10n_util::GetStringUTF16(
+      IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_INSTRUCTION);
+}
+
+base::Optional<base::string16>
+WebauthnOfferDialogModel::GetAdditionalDescription() const {
+  return base::nullopt;
+}
+
+ui::MenuModel* WebauthnOfferDialogModel::GetOtherTransportsMenuModel() {
+  return nullptr;
+}
+
+void WebauthnOfferDialogModel::OnBack() {}
+
+void WebauthnOfferDialogModel::OnAccept() {
+  state_ = kPending;
+}
+
+void WebauthnOfferDialogModel::OnCancel() {}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.h b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.h
new file mode 100644
index 0000000..b4484dc
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.h
@@ -0,0 +1,55 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_MODEL_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_MODEL_H_
+
+#include "base/macros.h"
+#include "chrome/browser/ui/webauthn/authenticator_request_sheet_model.h"
+
+namespace autofill {
+
+// The model for WebauthnOfferDialogView determining what content is shown.
+// Owned by the AuthenticatorRequestSheetView.
+class WebauthnOfferDialogModel : public AuthenticatorRequestSheetModel {
+ public:
+  enum DialogState {
+    kUnknown,
+    // The option of using platform authenticator is being offered.
+    kOffer,
+    // Offer was accepted, fetching authentication challenge.
+    kPending,
+    // TODO(crbug.com/991037): Add error state.
+  };
+
+  WebauthnOfferDialogModel();
+  ~WebauthnOfferDialogModel() override;
+
+  // AuthenticatorRequestSheetModel:
+  bool IsActivityIndicatorVisible() const override;
+  bool IsBackButtonVisible() const override;
+  bool IsCancelButtonVisible() const override;
+  base::string16 GetCancelButtonLabel() const override;
+  bool IsAcceptButtonVisible() const override;
+  bool IsAcceptButtonEnabled() const override;
+  base::string16 GetAcceptButtonLabel() const override;
+  const gfx::VectorIcon& GetStepIllustration(
+      ImageColorScheme color_scheme) const override;
+  base::string16 GetStepTitle() const override;
+  base::string16 GetStepDescription() const override;
+  base::Optional<base::string16> GetAdditionalDescription() const override;
+  ui::MenuModel* GetOtherTransportsMenuModel() override;
+  void OnBack() override;
+  void OnAccept() override;
+  void OnCancel() override;
+
+ private:
+  DialogState state_ = kUnknown;
+
+  DISALLOW_COPY_AND_ASSIGN(WebauthnOfferDialogModel);
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_MODEL_H_
diff --git a/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h
new file mode 100644
index 0000000..af5a7f3
--- /dev/null
+++ b/chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h
@@ -0,0 +1,27 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_H_
+#define CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_H_
+
+namespace autofill {
+
+class WebauthnOfferDialogController;
+
+// An interface which displays the dialog to offer the option of using device's
+// platform authenticator instead of CVC to verify the card in the future.
+class WebauthnOfferDialogView {
+ public:
+  static WebauthnOfferDialogView* CreateAndShow(
+      WebauthnOfferDialogController* controller);
+
+  virtual void Hide() = 0;
+
+  // Reinitializes the content in the dialog and resizes.
+  virtual void RefreshContent() = 0;
+};
+
+}  // namespace autofill
+
+#endif  // CHROME_BROWSER_UI_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_H_
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index dace9f3..87f066a 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -30,7 +30,6 @@
 #include "chrome/browser/ui/ui_features.h"
 #include "chrome/common/extensions/api/extension_action/action_info.h"
 #include "chrome/grit/generated_resources.h"
-#include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_registry.h"
 #include "extensions/common/extension.h"
 #include "extensions/common/extension_features.h"
@@ -59,8 +58,7 @@
       platform_delegate_(ExtensionActionPlatformDelegate::Create(this)),
       icon_factory_(browser->profile(), extension, extension_action, this),
       extension_registry_(
-          extensions::ExtensionRegistry::Get(browser_->profile())),
-      popup_host_observer_(this) {
+          extensions::ExtensionRegistry::Get(browser_->profile())) {
   DCHECK(extensions_container);
   DCHECK(extension_action);
   DCHECK(extension);
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.h b/chrome/browser/ui/extensions/extension_action_view_controller.h
index 4996c8c..73e8e39c 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.h
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.h
@@ -11,6 +11,7 @@
 #include "chrome/browser/extensions/extension_action_icon_factory.h"
 #include "chrome/browser/extensions/extension_context_menu_model.h"
 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
+#include "extensions/browser/extension_host.h"
 #include "extensions/browser/extension_host_observer.h"
 #include "ui/gfx/image/image.h"
 
@@ -185,7 +186,7 @@
   extensions::ExtensionRegistry* extension_registry_;
 
   ScopedObserver<extensions::ExtensionHost, extensions::ExtensionHostObserver>
-      popup_host_observer_;
+      popup_host_observer_{this};
 
   base::WeakPtrFactory<ExtensionActionViewController> weak_factory_{this};
 
diff --git a/chrome/browser/ui/extensions/extension_enable_flow.cc b/chrome/browser/ui/extensions/extension_enable_flow.cc
index 3315e33..3383da3 100644
--- a/chrome/browser/ui/extensions/extension_enable_flow.cc
+++ b/chrome/browser/ui/extensions/extension_enable_flow.cc
@@ -16,7 +16,6 @@
 #include "content/public/browser/notification_details.h"
 #include "content/public/browser/notification_source.h"
 #include "extensions/browser/extension_prefs.h"
-#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_system.h"
 
 #if !defined(OS_CHROMEOS)
@@ -28,15 +27,9 @@
 ExtensionEnableFlow::ExtensionEnableFlow(Profile* profile,
                                          const std::string& extension_id,
                                          ExtensionEnableFlowDelegate* delegate)
-    : profile_(profile),
-      extension_id_(extension_id),
-      delegate_(delegate),
-      parent_contents_(NULL),
-      parent_window_(NULL),
-      extension_registry_observer_(this) {}
+    : profile_(profile), extension_id_(extension_id), delegate_(delegate) {}
 
-ExtensionEnableFlow::~ExtensionEnableFlow() {
-}
+ExtensionEnableFlow::~ExtensionEnableFlow() = default;
 
 void ExtensionEnableFlow::StartForWebContents(
     content::WebContents* parent_contents) {
diff --git a/chrome/browser/ui/extensions/extension_enable_flow.h b/chrome/browser/ui/extensions/extension_enable_flow.h
index b4e0d681..f46fb2764 100644
--- a/chrome/browser/ui/extensions/extension_enable_flow.h
+++ b/chrome/browser/ui/extensions/extension_enable_flow.h
@@ -16,6 +16,7 @@
 #include "chrome/browser/extensions/extension_install_prompt.h"
 #include "content/public/browser/notification_observer.h"
 #include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/extension_registry.h"
 #include "extensions/browser/extension_registry_observer.h"
 
 class ExtensionEnableFlowDelegate;
@@ -24,10 +25,6 @@
 class WebContents;
 }
 
-namespace extensions {
-class ExtensionRegistry;
-}
-
 // ExtensionEnableFlow performs an UI flow to enable a disabled/terminated
 // extension. It calls its delegate when enabling is done or is aborted.
 // Callback on the delegate might be called synchronously if there is no
@@ -96,11 +93,11 @@
 
   // Parent web contents for ExtensionInstallPrompt that may be created during
   // the flow. Note this is mutually exclusive with |parent_window_| below.
-  content::WebContents* parent_contents_;
+  content::WebContents* parent_contents_ = nullptr;
 
   // Parent native window for ExtensionInstallPrompt. Note this is mutually
   // exclusive with |parent_contents_| above.
-  gfx::NativeWindow parent_window_;
+  gfx::NativeWindow parent_window_ = nullptr;
 
   // Called to acquire a parent window for the prompt. This is used for clients
   // who only want to create a window if it is required.
@@ -112,7 +109,7 @@
   // Listen to extension load notification.
   ScopedObserver<extensions::ExtensionRegistry,
                  extensions::ExtensionRegistryObserver>
-      extension_registry_observer_;
+      extension_registry_observer_{this};
 
   base::WeakPtrFactory<ExtensionEnableFlow> weak_ptr_factory_{this};
 
diff --git a/chrome/browser/ui/extensions/extension_installed_bubble.cc b/chrome/browser/ui/extensions/extension_installed_bubble.cc
index 5876d36e..9c252e9 100644
--- a/chrome/browser/ui/extensions/extension_installed_bubble.cc
+++ b/chrome/browser/ui/extensions/extension_installed_bubble.cc
@@ -47,20 +47,20 @@
   explicit ExtensionInstalledBubbleObserver(
       std::unique_ptr<ExtensionInstalledBubble> bubble)
       : bubble_(std::move(bubble)),
-        extension_registry_observer_(this),
-        browser_list_observer_(this),
         animation_wait_retries_(0) {
     // |extension| has been initialized but not loaded at this point. We need to
     // wait on showing the Bubble until the EXTENSION_LOADED gets fired.
     extension_registry_observer_.Add(
         extensions::ExtensionRegistry::Get(bubble_->browser()->profile()));
-    browser_list_observer_.Add(BrowserList::GetInstance());
+    BrowserList::AddObserver(this);
   }
 
   void Run() { OnExtensionLoaded(nullptr, bubble_->extension()); }
 
  private:
-  ~ExtensionInstalledBubbleObserver() override {}
+  ~ExtensionInstalledBubbleObserver() override {
+    BrowserList::RemoveObserver(this);
+  }
 
   // BrowserListObserver:
   void OnBrowserClosing(Browser* browser) override {
@@ -133,9 +133,7 @@
 
   ScopedObserver<extensions::ExtensionRegistry,
                  extensions::ExtensionRegistryObserver>
-      extension_registry_observer_;
-
-  ScopedObserver<BrowserList, BrowserListObserver> browser_list_observer_;
+      extension_registry_observer_{this};
 
   // The number of times to retry showing the bubble if the bubble_->browser()
   // action toolbar is animating.
diff --git a/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.cc b/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.cc
index 61278bb..a16b55a1 100644
--- a/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.cc
+++ b/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.cc
@@ -4,14 +4,46 @@
 
 #include "chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h"
 
+#include "base/metrics/histogram_functions.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_list.h"
 #include "chrome/browser/ui/global_media_controls/media_dialog_delegate.h"
 #include "chrome/browser/ui/global_media_controls/media_toolbar_button_controller_delegate.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/media_message_center/media_notification_item.h"
 #include "components/media_message_center/media_notification_util.h"
+#include "content/public/browser/media_session.h"
 #include "services/media_session/public/mojom/constants.mojom.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 #include "services/service_manager/public/cpp/connector.h"
 
+namespace {
+
+bool IsWebContentsFocused(content::WebContents* web_contents) {
+  DCHECK(web_contents);
+  Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+  if (!browser)
+    return false;
+
+  // If the given WebContents is not in the focused window, then it's not
+  // focused. Note that we know a Browser is focused because otherwise the user
+  // could not interact with the MediaDialogView.
+  if (BrowserList::GetInstance()->GetLastActive() != browser)
+    return false;
+
+  return browser->tab_strip_model()->GetActiveWebContents() == web_contents;
+}
+
+}  // anonymous namespace
+
+MediaToolbarButtonController::Session::Session(
+    std::unique_ptr<media_message_center::MediaNotificationItem> item,
+    content::WebContents* web_contents)
+    : item_(std::move(item)), web_contents_(web_contents) {}
+
+MediaToolbarButtonController::Session::~Session() = default;
+
 MediaToolbarButtonController::MediaToolbarButtonController(
     const base::UnguessableToken& source_id,
     service_manager::Connector* connector,
@@ -50,7 +82,7 @@
   // If we have an existing unfrozen item then this is a duplicate call and
   // we should ignore it.
   auto it = sessions_.find(id);
-  if (it != sessions_.end() && !it->second.frozen())
+  if (it != sessions_.end() && !it->second.item()->frozen())
     return;
 
   mojo::Remote<media_session::mojom::MediaController> controller;
@@ -65,8 +97,8 @@
   if (it != sessions_.end()) {
     // If the notification was previously frozen then we should reset the
     // controller because the mojo pipe would have been reset.
-    it->second.SetController(std::move(controller),
-                             std::move(session->session_info));
+    it->second.item()->SetController(std::move(controller),
+                                     std::move(session->session_info));
     active_controllable_session_ids_.insert(id);
     frozen_session_ids_.erase(id);
     UpdateToolbarButtonState();
@@ -74,8 +106,11 @@
     sessions_.emplace(
         std::piecewise_construct, std::forward_as_tuple(id),
         std::forward_as_tuple(
-            this, id, session->source_name.value_or(std::string()),
-            std::move(controller), std::move(session->session_info)));
+            std::make_unique<media_message_center::MediaNotificationItem>(
+                this, id, session->source_name.value_or(std::string()),
+                std::move(controller), std::move(session->session_info)),
+            content::MediaSession::GetWebContentsFromRequestId(
+                *session->request_id)));
   }
 }
 
@@ -87,7 +122,7 @@
   if (it == sessions_.end())
     return;
 
-  it->second.Freeze();
+  it->second.item()->Freeze();
   active_controllable_session_ids_.erase(id);
   frozen_session_ids_.insert(id);
   UpdateToolbarButtonState();
@@ -104,7 +139,7 @@
 
   auto it = sessions_.find(id);
   if (it != sessions_.end())
-    item = it->second.GetWeakPtr();
+    item = it->second.item()->GetWeakPtr();
 
   dialog_delegate_->ShowMediaSession(id, item);
 }
@@ -133,6 +168,20 @@
   UpdateToolbarButtonState();
 }
 
+void MediaToolbarButtonController::LogMediaSessionActionButtonPressed(
+    const std::string& id) {
+  auto it = sessions_.find(id);
+  if (it == sessions_.end())
+    return;
+
+  content::WebContents* web_contents = it->second.web_contents();
+  if (!web_contents)
+    return;
+
+  base::UmaHistogramBoolean("Media.GlobalMediaControls.UserActionFocus",
+                            IsWebContentsFocused(web_contents));
+}
+
 void MediaToolbarButtonController::SetDialogDelegate(
     MediaDialogDelegate* delegate) {
   DCHECK(!delegate || !dialog_delegate_);
@@ -148,7 +197,7 @@
 
     auto it = sessions_.find(id);
     if (it != sessions_.end())
-      item = it->second.GetWeakPtr();
+      item = it->second.item()->GetWeakPtr();
 
     dialog_delegate_->ShowMediaSession(id, item);
   }
diff --git a/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h b/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h
index 1c52184..4432b02 100644
--- a/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h
+++ b/chrome/browser/ui/global_media_controls/media_toolbar_button_controller.h
@@ -17,6 +17,10 @@
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 #include "services/media_session/public/mojom/media_controller.mojom.h"
 
+namespace content {
+class WebContents;
+}  // namespace content
+
 namespace media_message_center {
 class MediaNotificationItem;
 }  // namespace media_message_center
@@ -51,6 +55,7 @@
   void HideNotification(const std::string& id) override;
   void RemoveItem(const std::string& id) override;
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override;
+  void LogMediaSessionActionButtonPressed(const std::string& id) override;
 
   void SetDialogDelegate(MediaDialogDelegate* delegate);
 
@@ -64,6 +69,22 @@
     kHidden,
   };
 
+  class Session {
+   public:
+    Session(std::unique_ptr<media_message_center::MediaNotificationItem> item,
+            content::WebContents* web_contents);
+    ~Session();
+
+    media_message_center::MediaNotificationItem* item() { return item_.get(); }
+    content::WebContents* web_contents() { return web_contents_; }
+
+   private:
+    std::unique_ptr<media_message_center::MediaNotificationItem> item_;
+    content::WebContents* web_contents_;
+
+    DISALLOW_COPY_AND_ASSIGN(Session);
+  };
+
   void OnReceivedAudioFocusRequests(
       std::vector<media_session::mojom::AudioFocusRequestStatePtr> sessions);
 
@@ -85,10 +106,9 @@
   // sessions, we will disable the toolbar icon and wait to hide it.
   std::unordered_set<std::string> frozen_session_ids_;
 
-  // Stores a |media_message_center::MediaNotificationItem| for each media
-  // session keyed by its |request_id| in string format.
-  std::map<const std::string, media_message_center::MediaNotificationItem>
-      sessions_;
+  // Stores a Session for each media session keyed by its |request_id| in string
+  // format.
+  std::map<const std::string, Session> sessions_;
 
   // Connections with the media session service to listen for audio focus
   // updates and control media sessions.
diff --git a/chrome/browser/ui/global_media_controls/media_toolbar_button_controller_unittest.cc b/chrome/browser/ui/global_media_controls/media_toolbar_button_controller_unittest.cc
index ff2c39c..545a742 100644
--- a/chrome/browser/ui/global_media_controls/media_toolbar_button_controller_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/media_toolbar_button_controller_unittest.cc
@@ -134,7 +134,7 @@
     media_session::MediaMetadata metadata;
     metadata.title = base::ASCIIToUTF16("title");
     metadata.artist = base::ASCIIToUTF16("artist");
-    item_itr->second.MediaSessionMetadataChanged(std::move(metadata));
+    item_itr->second.item()->MediaSessionMetadataChanged(std::move(metadata));
   }
 
   void SimulateReceivedAudioFocusRequests(
@@ -145,7 +145,7 @@
   bool IsSessionFrozen(const base::UnguessableToken& id) const {
     auto item_itr = controller_->sessions_.find(id.ToString());
     EXPECT_NE(controller_->sessions_.end(), item_itr);
-    return item_itr->second.frozen();
+    return item_itr->second.item()->frozen();
   }
 
   void SimulateDialogOpened(MockMediaDialogDelegate* delegate) {
diff --git a/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.cc b/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.cc
deleted file mode 100644
index 25aa54d7..0000000
--- a/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.cc
+++ /dev/null
@@ -1,99 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.h"
-
-#include "chrome/browser/ui/views/chrome_layout_provider.h"
-#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
-#include "chrome/browser/ui/views/webauthn/sheet_view_factory.h"
-#include "components/constrained_window/constrained_window_views.h"
-#include "ui/views/controls/button/label_button.h"
-#include "ui/views/layout/fill_layout.h"
-#include "ui/views/window/dialog_client_view.h"
-
-namespace autofill {
-
-WebauthnOfferDialogView::WebauthnOfferDialogView(
-    content::WebContents* web_contents,
-    AutofillClient::WebauthnOfferDialogCallback callback) {
-  SetLayoutManager(std::make_unique<views::FillLayout>());
-  std::unique_ptr<WebauthnOfferDialogController> controller =
-      std::make_unique<WebauthnOfferDialogController>(callback);
-  controller_ = controller.get();
-  sheet_view_ =
-      AddChildView(CreateSheetViewForAutofillWebAuthn(std::move(controller)));
-  sheet_view_->ReInitChildViews();
-}
-
-WebauthnOfferDialogView::~WebauthnOfferDialogView() = default;
-
-// static
-void ShowWebauthnOfferDialogView(
-    content::WebContents* web_contents,
-    AutofillClient::WebauthnOfferDialogCallback callback) {
-  WebauthnOfferDialogView* dialog =
-      new WebauthnOfferDialogView(web_contents, callback);
-  constrained_window::ShowWebModalDialogViews(dialog, web_contents);
-}
-
-gfx::Size WebauthnOfferDialogView::CalculatePreferredSize() const {
-  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
-      DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH);
-  return gfx::Size(width, GetHeightForWidth(width));
-}
-
-bool WebauthnOfferDialogView::Accept() {
-  controller_->OnAccept();
-  // TODO(crbug.com/991037): Make the dialog stay and show the progression
-  // indicator.
-  return true;
-}
-
-bool WebauthnOfferDialogView::Cancel() {
-  controller_->OnCancel();
-  return true;
-}
-
-bool WebauthnOfferDialogView::Close() {
-  return true;
-}
-
-// TODO(crbug.com/991037): Fetching authentication challenge will send a request
-// to Payments server, and it can fail sometimes. For the error case, the dialog
-// should be updated and the OK button should not be shown.
-int WebauthnOfferDialogView::GetDialogButtons() const {
-  DCHECK(controller_->IsAcceptButtonVisible() &&
-         controller_->IsCancelButtonVisible());
-  return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
-}
-
-base::string16 WebauthnOfferDialogView::GetDialogButtonLabel(
-    ui::DialogButton button) const {
-  return button == ui::DIALOG_BUTTON_OK ? controller_->GetAcceptButtonLabel()
-                                        : controller_->GetCancelButtonLabel();
-}
-
-bool WebauthnOfferDialogView::IsDialogButtonEnabled(
-    ui::DialogButton button) const {
-  return button == ui::DIALOG_BUTTON_OK ? controller_->IsAcceptButtonEnabled()
-                                        : true;
-}
-
-ui::ModalType WebauthnOfferDialogView::GetModalType() const {
-  return ui::MODAL_TYPE_CHILD;
-}
-
-base::string16 WebauthnOfferDialogView::GetWindowTitle() const {
-  return controller_->GetStepTitle();
-}
-
-bool WebauthnOfferDialogView::ShouldShowWindowTitle() const {
-  return false;
-}
-
-bool WebauthnOfferDialogView::ShouldShowCloseButton() const {
-  return false;
-}
-
-}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.cc b/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.cc
new file mode 100644
index 0000000..1056a1c
--- /dev/null
+++ b/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.cc
@@ -0,0 +1,122 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.h"
+
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.h"
+#include "chrome/browser/ui/views/chrome_layout_provider.h"
+#include "chrome/browser/ui/views/webauthn/authenticator_request_sheet_view.h"
+#include "chrome/browser/ui/views/webauthn/sheet_view_factory.h"
+#include "components/constrained_window/constrained_window_views.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/layout/fill_layout.h"
+#include "ui/views/window/dialog_client_view.h"
+
+namespace autofill {
+
+WebauthnOfferDialogViewImpl::WebauthnOfferDialogViewImpl(
+    WebauthnOfferDialogController* controller)
+    : controller_(controller) {
+  SetLayoutManager(std::make_unique<views::FillLayout>());
+  std::unique_ptr<WebauthnOfferDialogModel> model =
+      std::make_unique<WebauthnOfferDialogModel>();
+  model_ = model.get();
+  sheet_view_ =
+      AddChildView(CreateSheetViewForAutofillWebAuthn(std::move(model)));
+  sheet_view_->ReInitChildViews();
+}
+
+WebauthnOfferDialogViewImpl::~WebauthnOfferDialogViewImpl() = default;
+
+// static
+WebauthnOfferDialogView* WebauthnOfferDialogView::CreateAndShow(
+    WebauthnOfferDialogController* controller) {
+  WebauthnOfferDialogViewImpl* dialog =
+      new WebauthnOfferDialogViewImpl(controller);
+  constrained_window::ShowWebModalDialogViews(dialog,
+                                              controller->GetWebContents());
+  return dialog;
+}
+
+void WebauthnOfferDialogViewImpl::Hide() {
+  GetWidget()->Close();
+}
+
+// TODO(crbug.com/991037): Need to resize the dialog after reinitializing the
+// dialog to error dialog since the string content in the error dialog is
+// different.
+void WebauthnOfferDialogViewImpl::RefreshContent() {
+  sheet_view_->ReInitChildViews();
+  sheet_view_->InvalidateLayout();
+  Layout();
+  DialogModelChanged();
+}
+
+gfx::Size WebauthnOfferDialogViewImpl::CalculatePreferredSize() const {
+  const int width = ChromeLayoutProvider::Get()->GetDistanceMetric(
+      DISTANCE_MODAL_DIALOG_PREFERRED_WIDTH);
+  return gfx::Size(width, GetHeightForWidth(width));
+}
+
+bool WebauthnOfferDialogViewImpl::Accept() {
+  model_->OnAccept();
+  controller_->OnOkButtonClicked();
+  return false;
+}
+
+bool WebauthnOfferDialogViewImpl::Cancel() {
+  model_->OnCancel();
+  controller_->OnCancelButtonClicked();
+  return true;
+}
+
+bool WebauthnOfferDialogViewImpl::Close() {
+  return true;
+}
+
+// TODO(crbug.com/991037): Fetching authentication challenge will send a request
+// to Payments server, and it can fail sometimes. For the error case, the dialog
+// should be updated and the OK button should not be shown.
+int WebauthnOfferDialogViewImpl::GetDialogButtons() const {
+  DCHECK(model_->IsAcceptButtonVisible() && model_->IsCancelButtonVisible());
+  return ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL;
+}
+
+base::string16 WebauthnOfferDialogViewImpl::GetDialogButtonLabel(
+    ui::DialogButton button) const {
+  return button == ui::DIALOG_BUTTON_OK ? model_->GetAcceptButtonLabel()
+                                        : model_->GetCancelButtonLabel();
+}
+
+bool WebauthnOfferDialogViewImpl::IsDialogButtonEnabled(
+    ui::DialogButton button) const {
+  return button == ui::DIALOG_BUTTON_OK ? model_->IsAcceptButtonEnabled()
+                                        : true;
+}
+
+ui::ModalType WebauthnOfferDialogViewImpl::GetModalType() const {
+  return ui::MODAL_TYPE_CHILD;
+}
+
+base::string16 WebauthnOfferDialogViewImpl::GetWindowTitle() const {
+  return model_->GetStepTitle();
+}
+
+bool WebauthnOfferDialogViewImpl::ShouldShowWindowTitle() const {
+  return false;
+}
+
+bool WebauthnOfferDialogViewImpl::ShouldShowCloseButton() const {
+  return false;
+}
+
+void WebauthnOfferDialogViewImpl::WindowClosing() {
+  if (controller_) {
+    controller_->OnDialogClosed();
+    controller_ = nullptr;
+  }
+}
+
+}  // namespace autofill
diff --git a/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.h b/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.h
similarity index 63%
rename from chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.h
rename to chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.h
index 47f0b5b9..0f59993a 100644
--- a/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view.h
+++ b/chrome/browser/ui/views/autofill/payments/webauthn_offer_dialog_view_impl.h
@@ -2,30 +2,33 @@
 // 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_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_H_
-#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_H_
+#ifndef CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_IMPL_H_
+#define CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_IMPL_H_
 
 #include "base/macros.h"
-#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h"
-#include "components/autofill/core/browser/autofill_client.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_view.h"
 #include "ui/views/window/dialog_delegate.h"
 
 class AuthenticatorRequestSheetView;
 
-namespace content {
-class WebContents;
-}
-
 namespace autofill {
 
+class WebauthnOfferDialogController;
+class WebauthnOfferDialogModel;
+
 // The view of the dialog that offers the option to use device's platform
 // authenticator. It is shown automatically after card unmasked details are
 // obtained and filled into the form.
-class WebauthnOfferDialogView : public views::DialogDelegateView {
+class WebauthnOfferDialogViewImpl : public WebauthnOfferDialogView,
+                                    public views::DialogDelegateView {
  public:
-  WebauthnOfferDialogView(content::WebContents* web_contents,
-                          AutofillClient::WebauthnOfferDialogCallback callback);
-  ~WebauthnOfferDialogView() override;
+  explicit WebauthnOfferDialogViewImpl(
+      WebauthnOfferDialogController* controller);
+  ~WebauthnOfferDialogViewImpl() override;
+
+  // WebauthnOfferDialogView:
+  void Hide() override;
+  void RefreshContent() override;
 
   // views::DialogDelegateView:
   gfx::Size CalculatePreferredSize() const override;
@@ -39,21 +42,20 @@
   base::string16 GetWindowTitle() const override;
   bool ShouldShowWindowTitle() const override;
   bool ShouldShowCloseButton() const override;
+  void WindowClosing() override;
 
  private:
-  friend void ShowWebauthnOfferDialogView(
-      content::WebContents* web_contents,
-      AutofillClient::WebauthnOfferDialogCallback callback);
-
-  // Reference to the controller. The controller is owned by the
-  // AuthenticatorRequestSheetView. Since this dialog view owns the sheet view,
-  // the controller is destroyed only when the dialog is.
   WebauthnOfferDialogController* controller_ = nullptr;
+
   AuthenticatorRequestSheetView* sheet_view_ = nullptr;
 
-  DISALLOW_COPY_AND_ASSIGN(WebauthnOfferDialogView);
+  // Dialog model owned by |sheet_view_|. Since this dialog owns the
+  // |sheet_view_|, the model_ will always be valid.
+  WebauthnOfferDialogModel* model_ = nullptr;
+
+  DISALLOW_COPY_AND_ASSIGN(WebauthnOfferDialogViewImpl);
 };
 
 }  // namespace autofill
 
-#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_H_
+#endif  // CHROME_BROWSER_UI_VIEWS_AUTOFILL_PAYMENTS_WEBAUTHN_OFFER_DIALOG_VIEW_IMPL_H_
diff --git a/chrome/browser/ui/views/frame/browser_frame_mac.mm b/chrome/browser/ui/views/frame/browser_frame_mac.mm
index 3e8ad61..2e0f6ec9 100644
--- a/chrome/browser/ui/views/frame/browser_frame_mac.mm
+++ b/chrome/browser/ui/views/frame/browser_frame_mac.mm
@@ -372,7 +372,7 @@
 }
 
 bool BrowserFrameMac::UsesNativeSystemMenu() const {
-  return true;
+  return false;
 }
 
 bool BrowserFrameMac::ShouldSaveWindowPlacement() const {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
index 119631d..6f08640 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_match_cell_view.cc
@@ -354,12 +354,14 @@
 }
 
 void OmniboxMatchCellView::Layout() {
-  // Update the margins.
+  // Update the margins. Avoid re-creating the border if the insets haven't
+  // changed, because SetBorder invalidates the layout.
   gfx::Insets insets =
       GetMarginInsets(content()->GetLineHeight(),
                       layout_style_ != LayoutStyle::ONE_LINE_SUGGESTION);
-  SetBorder(views::CreateEmptyBorder(insets.top(), insets.left(),
-                                     insets.bottom(), insets.right()));
+  if (insets != GetInsets())
+    SetBorder(views::CreateEmptyBorder(insets));
+
   // Layout children *after* updating the margins.
   views::View::Layout();
 
diff --git a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
index 94d9ed8..07fbc045 100644
--- a/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
+++ b/chrome/browser/ui/views/webauthn/sheet_view_factory.cc
@@ -5,7 +5,7 @@
 #include "chrome/browser/ui/views/webauthn/sheet_view_factory.h"
 
 #include "base/logging.h"
-#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_controller.h"
+#include "chrome/browser/ui/autofill/payments/webauthn_offer_dialog_model.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_ble_pin_entry_sheet_view.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_client_pin_entry_sheet_view.h"
 #include "chrome/browser/ui/views/webauthn/authenticator_qr_sheet_view.h"
@@ -200,6 +200,6 @@
 
 std::unique_ptr<AuthenticatorRequestSheetView>
 CreateSheetViewForAutofillWebAuthn(
-    std::unique_ptr<autofill::WebauthnOfferDialogController> controller) {
-  return std::make_unique<AuthenticatorRequestSheetView>(std::move(controller));
+    std::unique_ptr<autofill::WebauthnOfferDialogModel> model) {
+  return std::make_unique<AuthenticatorRequestSheetView>(std::move(model));
 }
diff --git a/chrome/browser/ui/views/webauthn/sheet_view_factory.h b/chrome/browser/ui/views/webauthn/sheet_view_factory.h
index f79374f..7f11667e 100644
--- a/chrome/browser/ui/views/webauthn/sheet_view_factory.h
+++ b/chrome/browser/ui/views/webauthn/sheet_view_factory.h
@@ -11,7 +11,7 @@
 class AuthenticatorRequestDialogModel;
 
 namespace autofill {
-class WebauthnOfferDialogController;
+class WebauthnOfferDialogModel;
 }
 
 // Creates the appropriate AuthenticatorRequestSheetView subclass instance,
@@ -24,6 +24,6 @@
 // WebauthnOfferDialogView.
 std::unique_ptr<AuthenticatorRequestSheetView>
 CreateSheetViewForAutofillWebAuthn(
-    std::unique_ptr<autofill::WebauthnOfferDialogController> controller);
+    std::unique_ptr<autofill::WebauthnOfferDialogModel> model);
 
 #endif  // CHROME_BROWSER_UI_VIEWS_WEBAUTHN_SHEET_VIEW_FACTORY_H_
diff --git a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.cc b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.cc
index c897249..392ed8d 100644
--- a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.cc
+++ b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.cc
@@ -21,6 +21,9 @@
   base::UmaHistogramEnumeration("AddSupervisionDialog.Enrollment", action);
   switch (action) {
     case EnrollmentState::kInitiated:
+      DCHECK(!EnrollmentCompleted())
+          << "The user should not be enrolled in supervision at the start of "
+             "the Add Supervision process.";
       base::RecordAction(
           base::UserMetricsAction("AddSupervisionDialog_Launched"));
       start_time_ = clock_->NowTicks();
diff --git a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.h b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.h
index ddfb885..eee764e 100644
--- a/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.h
+++ b/chrome/browser/ui/webui/chromeos/add_supervision/add_supervision_metrics_recorder.h
@@ -20,13 +20,14 @@
   // These values are persisted to logs. Entries should not be renumbered and
   // numeric values should never be reused.
   enum class EnrollmentState {
-    // Recorded when user initiates Add Supervision dialog.
+    // Recorded when user opens Add Supervision dialog.
     kInitiated = 0,
     // Recorded when user successfully enrolls in supervision.
     kCompleted = 1,
-    // Recorded when user clicks "Sign out".
+    // Recorded when user clicks "Sign out" after enrollment in the dialog.
     kSignedOut = 2,
-    // Recorded when user closes the Add Supervison dialog except sign out.
+    // Recorded when user closes the dialog without enrollment, excluding sign
+    // out.
     kClosed = 3,
     // Add future entries above this comment, in sync with enums.xml.
     // Update kMaxValue to the last value.
diff --git a/chrome/browser/ui/webui/discards/BUILD.gn b/chrome/browser/ui/webui/discards/BUILD.gn
index 83e8fb4..cbb1e6dd 100644
--- a/chrome/browser/ui/webui/discards/BUILD.gn
+++ b/chrome/browser/ui/webui/discards/BUILD.gn
@@ -8,11 +8,13 @@
   mojom("mojo_bindings") {
     sources = [
       "discards.mojom",
+      "webui_graph_dump.mojom",
     ]
 
     public_deps = [
-      "//chrome/browser/performance_manager:mojo_bindings",
       "//chrome/browser/resource_coordinator:mojo_bindings",
+      "//mojo/public/mojom/base",
+      "//url/mojom:url_mojom_gurl",
     ]
   }
 }
diff --git a/chrome/browser/ui/webui/discards/DEPS b/chrome/browser/ui/webui/discards/DEPS
index b6e5e56..bd0c3962 100644
--- a/chrome/browser/ui/webui/discards/DEPS
+++ b/chrome/browser/ui/webui/discards/DEPS
@@ -1,10 +1,11 @@
 specific_include_rules = {
-  # TODO(siggi): Move WebUIGraphDumpImpl into this directory and kill these
-  #     include rules.
-  "discards_ui.h": [
-    "+chrome/browser/performance_manager/webui_graph_dump.mojom.h",
+  # Tests need to include the graph implementation.
+  "webui_graph_dump_impl_unittest\.cc": [
+    "+chrome/browser/performance_manager/graph",
+    "+chrome/browser/performance_manager/performance_manager_clock.h",
   ],
-  "discards_ui.cc": [
-    "+chrome/browser/performance_manager/webui_graph_dump_impl.h",
+  # Until CallOnGraph is in the public interface, the impl needs GraphImpl
+  "webui_graph_dump_impl\.cc": [
+    "+chrome/browser/performance_manager/graph",
   ],
 }
diff --git a/chrome/browser/ui/webui/discards/discards_ui.cc b/chrome/browser/ui/webui/discards/discards_ui.cc
index 04bc887b..5ef03de 100644
--- a/chrome/browser/ui/webui/discards/discards_ui.cc
+++ b/chrome/browser/ui/webui/discards/discards_ui.cc
@@ -15,7 +15,6 @@
 #include "chrome/browser/browser_process.h"
 #include "chrome/browser/engagement/site_engagement_service.h"
 #include "chrome/browser/performance_manager/performance_manager.h"
-#include "chrome/browser/performance_manager/webui_graph_dump_impl.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/resource_coordinator/lifecycle_unit.h"
 #include "chrome/browser/resource_coordinator/lifecycle_unit_state.mojom.h"
@@ -27,6 +26,7 @@
 #include "chrome/browser/resource_coordinator/tab_manager.h"
 #include "chrome/browser/resource_coordinator/time.h"
 #include "chrome/browser/ui/webui/discards/discards.mojom.h"
+#include "chrome/browser/ui/webui/discards/webui_graph_dump_impl.h"
 #include "chrome/browser/ui/webui/favicon_source.h"
 #include "chrome/common/webui_url_constants.h"
 #include "chrome/grit/browser_resources.h"
diff --git a/chrome/browser/ui/webui/discards/discards_ui.h b/chrome/browser/ui/webui/discards/discards_ui.h
index 1ec8c1c..fd438d9 100644
--- a/chrome/browser/ui/webui/discards/discards_ui.h
+++ b/chrome/browser/ui/webui/discards/discards_ui.h
@@ -8,8 +8,8 @@
 #include <memory>
 
 #include "base/macros.h"
-#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"
 #include "chrome/browser/ui/webui/discards/discards.mojom.h"
+#include "chrome/browser/ui/webui/discards/webui_graph_dump.mojom.h"
 #include "ui/webui/mojo_web_ui_controller.h"
 
 namespace resource_coordinator {
diff --git a/chrome/browser/performance_manager/webui_graph_dump.mojom b/chrome/browser/ui/webui/discards/webui_graph_dump.mojom
similarity index 100%
rename from chrome/browser/performance_manager/webui_graph_dump.mojom
rename to chrome/browser/ui/webui/discards/webui_graph_dump.mojom
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.cc b/chrome/browser/ui/webui/discards/webui_graph_dump_impl.cc
similarity index 99%
rename from chrome/browser/performance_manager/webui_graph_dump_impl.cc
rename to chrome/browser/ui/webui/discards/webui_graph_dump_impl.cc
index 895ba2d1..5287174 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.cc
+++ b/chrome/browser/ui/webui/discards/webui_graph_dump_impl.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/performance_manager/webui_graph_dump_impl.h"
+#include "chrome/browser/ui/webui/discards/webui_graph_dump_impl.h"
 
 #include <memory>
 #include <utility>
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl.h b/chrome/browser/ui/webui/discards/webui_graph_dump_impl.h
similarity index 94%
rename from chrome/browser/performance_manager/webui_graph_dump_impl.h
rename to chrome/browser/ui/webui/discards/webui_graph_dump_impl.h
index 88a0d0c..beec338a 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl.h
+++ b/chrome/browser/ui/webui/discards/webui_graph_dump_impl.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_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
-#define CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
+#ifndef CHROME_BROWSER_UI_WEBUI_DISCARDS_WEBUI_GRAPH_DUMP_IMPL_H_
+#define CHROME_BROWSER_UI_WEBUI_DISCARDS_WEBUI_GRAPH_DUMP_IMPL_H_
 
 #include <memory>
 
@@ -14,7 +14,7 @@
 #include "chrome/browser/performance_manager/public/graph/graph.h"
 #include "chrome/browser/performance_manager/public/graph/page_node.h"
 #include "chrome/browser/performance_manager/public/graph/process_node.h"
-#include "chrome/browser/performance_manager/webui_graph_dump.mojom.h"
+#include "chrome/browser/ui/webui/discards/webui_graph_dump.mojom.h"
 #include "mojo/public/cpp/bindings/binding.h"
 
 namespace performance_manager {
@@ -139,4 +139,4 @@
 
 }  // namespace performance_manager
 
-#endif  // CHROME_BROWSER_PERFORMANCE_MANAGER_WEBUI_GRAPH_DUMP_IMPL_H_
+#endif  // CHROME_BROWSER_UI_WEBUI_DISCARDS_WEBUI_GRAPH_DUMP_IMPL_H_
diff --git a/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc b/chrome/browser/ui/webui/discards/webui_graph_dump_impl_unittest.cc
similarity index 98%
rename from chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
rename to chrome/browser/ui/webui/discards/webui_graph_dump_impl_unittest.cc
index 3b57b9b..113c413a 100644
--- a/chrome/browser/performance_manager/webui_graph_dump_impl_unittest.cc
+++ b/chrome/browser/ui/webui/discards/webui_graph_dump_impl_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "chrome/browser/performance_manager/webui_graph_dump_impl.h"
+#include "chrome/browser/ui/webui/discards/webui_graph_dump_impl.h"
 
 #include <map>
 #include <set>
diff --git a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
index 84cc2b0..6df5175 100644
--- a/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
+++ b/chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
@@ -3236,8 +3236,6 @@
        IDS_SETTINGS_SECURITY_KEYS_BIO_ENROLLMENT_SUBPAGE_DESCRIPTION},
       {"securityKeysBioEnrollmentTouch",
        IDS_SETTINGS_SECURITY_KEYS_BIO_ENROLLMENT_TOUCH},
-      {"securityKeysPINTooShort",
-       IDS_SETTINGS_SECURITY_KEYS_PIN_ERROR_TOO_SHORT},
       {"securityKeysConfirmPIN", IDS_SETTINGS_SECURITY_KEYS_CONFIRM_PIN},
       {"securityKeysCredentialWebsite",
        IDS_SETTINGS_SECURITY_KEYS_CREDENTIAL_WEBSITE},
diff --git a/chrome/chrome_cleaner/test/BUILD.gn b/chrome/chrome_cleaner/test/BUILD.gn
index 118454c..2c77845 100644
--- a/chrome/chrome_cleaner/test/BUILD.gn
+++ b/chrome/chrome_cleaner/test/BUILD.gn
@@ -213,6 +213,7 @@
 
   sources = [
     "cleaner_test.cc",
+    "generate_test_uws_test.cc",
     "secure_dll_loading_test.cc",
   ]
 
@@ -239,6 +240,7 @@
     ":empty_dll",
     "//chrome/chrome_cleaner/executables:chrome_cleanup_tool",
     "//chrome/chrome_cleaner/executables:software_reporter_tool",
+    "//chrome/chrome_cleaner/tools:generate_test_uws",
   ]
 }
 
diff --git a/chrome/chrome_cleaner/test/generate_test_uws_test.cc b/chrome/chrome_cleaner/test/generate_test_uws_test.cc
new file mode 100644
index 0000000..71b5afc
--- /dev/null
+++ b/chrome/chrome_cleaner/test/generate_test_uws_test.cc
@@ -0,0 +1,71 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/base_paths_win.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/strings/string16.h"
+#include "base/test/test_timeouts.h"
+#include "chrome/chrome_cleaner/pup_data/test_uws.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+TEST(GenerateTestUwsTest, WriteTestUwS) {
+  // Ensure the expected output files don't exist.
+  base::FilePath start_menu_path;
+  ASSERT_TRUE(base::PathService::Get(base::DIR_START_MENU, &start_menu_path));
+  base::FilePath startup_dir =
+      start_menu_path.Append(STRING16_LITERAL("Startup"));
+
+  base::FilePath uws_file_a =
+      startup_dir.Append(chrome_cleaner::kTestUwsAFilename);
+  ASSERT_TRUE(base::DeleteFile(uws_file_a, /*recursive=*/false));
+
+  base::FilePath uws_file_b =
+      startup_dir.Append(chrome_cleaner::kTestUwsBFilename);
+  ASSERT_TRUE(base::DeleteFile(uws_file_b, /*recursive=*/false));
+
+  // Delete the output files on exit, including on early exit.
+  base::ScopedClosureRunner delete_uws_file_a(base::BindOnce(
+      base::IgnoreResult(&base::DeleteFile), uws_file_a, /*recursive=*/false));
+  base::ScopedClosureRunner delete_uws_file_b(base::BindOnce(
+      base::IgnoreResult(&base::DeleteFile), uws_file_b, /*recursive=*/false));
+
+  // Expect generate_test_uws to finish quickly with exit code 0 (success).
+  base::Process process(base::LaunchProcess(
+      STRING16_LITERAL("generate_test_uws.exe"), base::LaunchOptions()));
+  ASSERT_TRUE(process.IsValid());
+
+  int exit_code = -1;
+  bool exited_within_timeout = process.WaitForExitWithTimeout(
+      TestTimeouts::action_timeout(), &exit_code);
+  EXPECT_TRUE(exited_within_timeout);
+  EXPECT_EQ(exit_code, 0);
+
+  if (!exited_within_timeout)
+    process.Terminate(/*exit_code=*/-1, /*wait=*/false);
+
+  // Verify that generate_test_uws created the expected files.
+  std::string uws_file_contents_a;
+  EXPECT_TRUE(base::ReadFileToStringWithMaxSize(
+      uws_file_a, &uws_file_contents_a,
+      chrome_cleaner::kTestUwsAFileContentsSize))
+      << uws_file_a;
+  EXPECT_EQ(uws_file_contents_a, chrome_cleaner::kTestUwsAFileContents);
+
+  std::string uws_file_contents_b;
+  EXPECT_TRUE(base::ReadFileToStringWithMaxSize(
+      uws_file_b, &uws_file_contents_b,
+      chrome_cleaner::kTestUwsBFileContentsSize))
+      << uws_file_b;
+  EXPECT_EQ(uws_file_contents_b, chrome_cleaner::kTestUwsBFileContents);
+}
+
+}  // namespace
diff --git a/chrome/chrome_cleaner/tools/BUILD.gn b/chrome/chrome_cleaner/tools/BUILD.gn
new file mode 100644
index 0000000..4c5da25
--- /dev/null
+++ b/chrome/chrome_cleaner/tools/BUILD.gn
@@ -0,0 +1,26 @@
+# Copyright 2019 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Nothing outside //chrome/chrome_cleaner can depend on these targets.
+visibility = [ "//chrome/chrome_cleaner/*" ]
+
+# We need at least one non-executable to prevent an "unused visibility
+# declaration" warning.
+source_set("generate_test_uws_src") {
+  sources = [
+    "generate_test_uws.cc",
+  ]
+
+  deps = [
+    "//base:base",
+    "//chrome/chrome_cleaner/pup_data:test_uws",
+  ]
+}
+
+executable("generate_test_uws") {
+  deps = [
+    ":generate_test_uws_src",
+    "//build/win:default_exe_manifest",
+  ]
+}
diff --git a/chrome/chrome_cleaner/tools/generate_test_uws.cc b/chrome/chrome_cleaner/tools/generate_test_uws.cc
new file mode 100644
index 0000000..b00f2ae
--- /dev/null
+++ b/chrome/chrome_cleaner/tools/generate_test_uws.cc
@@ -0,0 +1,34 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "chrome/chrome_cleaner/pup_data/test_uws.h"
+
+int main(int argc, char** argv) {
+  base::FilePath start_menu_folder;
+  CHECK(base::PathService::Get(base::DIR_START_MENU, &start_menu_folder));
+  base::FilePath startup_dir = start_menu_folder.Append(L"Startup");
+
+  base::FilePath google_test_a =
+      startup_dir.Append(chrome_cleaner::kTestUwsAFilename);
+  if (base::WriteFile(google_test_a, chrome_cleaner::kTestUwsAFileContents,
+                      chrome_cleaner::kTestUwsAFileContentsSize) == -1) {
+    PLOG(ERROR) << "Failed to create test UwS at " << google_test_a;
+    return 1;
+  }
+
+  base::FilePath google_test_b =
+      startup_dir.Append(chrome_cleaner::kTestUwsBFilename);
+  if (base::WriteFile(google_test_b, chrome_cleaner::kTestUwsBFileContents,
+                      chrome_cleaner::kTestUwsBFileContentsSize) == -1) {
+    PLOG(ERROR) << "Failed to create test UwS at " << google_test_b;
+    return 1;
+  }
+
+  LOG(INFO) << "Test UwS successfully generated in " << startup_dir;
+  return 0;
+}
diff --git a/chrome/common/chrome_paths_mac.mm b/chrome/common/chrome_paths_mac.mm
index 36d4f06..bf7849a 100644
--- a/chrome/common/chrome_paths_mac.mm
+++ b/chrome/common/chrome_paths_mac.mm
@@ -11,7 +11,6 @@
 #include "base/base_paths.h"
 #include "base/logging.h"
 #import "base/mac/foundation_util.h"
-#import "base/mac/scoped_nsautorelease_pool.h"
 #include "base/memory/free_deleter.h"
 #include "base/path_service.h"
 #include "build/build_config.h"
@@ -24,48 +23,50 @@
 // implementation of chrome::OuterAppBundle(), which should be the only
 // caller.
 NSBundle* OuterAppBundleInternal() {
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    if (!base::mac::AmIBundled()) {
+      // If unbundled (as in a test), there's no app bundle.
+      return nil;
+    }
 
-  if (!base::mac::AmIBundled()) {
-    // If unbundled (as in a test), there's no app bundle.
-    return nil;
+    if (!base::mac::IsBackgroundOnlyProcess()) {
+      // Shortcut: in the browser process, just return the main app bundle.
+      return [[NSBundle mainBundle] retain];
+    }
+
+    // From C.app/Contents/Frameworks/C.framework/Versions/1.2.3.4, go up five
+    // steps to C.app.
+    base::FilePath framework_path = chrome::GetFrameworkBundlePath();
+    base::FilePath outer_app_dir =
+        framework_path.DirName().DirName().DirName().DirName().DirName();
+    const char* outer_app_dir_c = outer_app_dir.value().c_str();
+    NSString* outer_app_dir_ns =
+        [NSString stringWithUTF8String:outer_app_dir_c];
+
+    return [[NSBundle bundleWithPath:outer_app_dir_ns] retain];
   }
-
-  if (!base::mac::IsBackgroundOnlyProcess()) {
-    // Shortcut: in the browser process, just return the main app bundle.
-    return [[NSBundle mainBundle] retain];
-  }
-
-  // From C.app/Contents/Frameworks/C.framework/Versions/1.2.3.4, go up five
-  // steps to C.app.
-  base::FilePath framework_path = chrome::GetFrameworkBundlePath();
-  base::FilePath outer_app_dir =
-      framework_path.DirName().DirName().DirName().DirName().DirName();
-  const char* outer_app_dir_c = outer_app_dir.value().c_str();
-  NSString* outer_app_dir_ns = [NSString stringWithUTF8String:outer_app_dir_c];
-
-  return [[NSBundle bundleWithPath:outer_app_dir_ns] retain];
 }
 
 char* ProductDirNameForBundle(NSBundle* chrome_bundle) {
-  const char* product_dir_name = NULL;
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    const char* product_dir_name = NULL;
 
-  NSString* product_dir_name_ns =
-      [chrome_bundle objectForInfoDictionaryKey:@"CrProductDirName"];
-  product_dir_name = [product_dir_name_ns fileSystemRepresentation];
+    NSString* product_dir_name_ns =
+        [chrome_bundle objectForInfoDictionaryKey:@"CrProductDirName"];
+    product_dir_name = [product_dir_name_ns fileSystemRepresentation];
 
-  if (!product_dir_name) {
+    if (!product_dir_name) {
 #if defined(GOOGLE_CHROME_BUILD)
-    product_dir_name = "Google/Chrome";
+      product_dir_name = "Google/Chrome";
 #else
-    product_dir_name = "Chromium";
+      product_dir_name = "Chromium";
 #endif
-  }
+    }
 
-  // Leaked, but the only caller initializes a static with this result, so it
-  // only happens once, and that's OK.
-  return strdup(product_dir_name);
+    // Leaked, but the only caller initializes a static with this result, so it
+    // only happens once, and that's OK.
+    return strdup(product_dir_name);
+  }
 }
 
 // ProductDirName returns the name of the directory inside
diff --git a/chrome/common/extensions/api/_manifest_features.json b/chrome/common/extensions/api/_manifest_features.json
index 77eb698..11940a57 100644
--- a/chrome/common/extensions/api/_manifest_features.json
+++ b/chrome/common/extensions/api/_manifest_features.json
@@ -221,7 +221,12 @@
   },
   "storage": {
     "channel": "stable",
-    "extension_types": ["extension", "legacy_packaged_app", "platform_app"],
+    "extension_types": [
+      "extension",
+      "legacy_packaged_app",
+      "platform_app",
+      "login_screen_extension"
+    ],
     "min_manifest_version": 2
   },
   "system_indicator": [
diff --git a/chrome/common/extensions/api/login_screen_storage.idl b/chrome/common/extensions/api/login_screen_storage.idl
index 9d6aa4d..21e90a1 100644
--- a/chrome/common/extensions/api/login_screen_storage.idl
+++ b/chrome/common/extensions/api/login_screen_storage.idl
@@ -24,7 +24,10 @@
 
     // Retrieves persistent data that was previously stored using
     // $(ref:storePersistentData) for the caller's extension ID.
-    static void retrievePersistentData(RetrieveCallback callback);
+    // |ownerId|: ID of the extension that saved the data that the caller is
+    // trying to retrieve.
+    static void retrievePersistentData(DOMString ownerId,
+                                       RetrieveCallback callback);
 
     // Stores credentials for later access from the user session. This method
     // will fail if called while a user session is active.
diff --git a/chrome/common/mac/launchd.mm b/chrome/common/mac/launchd.mm
index 376e559..5eab6f9 100644
--- a/chrome/common/mac/launchd.mm
+++ b/chrome/common/mac/launchd.mm
@@ -9,7 +9,6 @@
 
 #include "base/mac/foundation_util.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/process/launch.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/sys_string_conversions.h"
@@ -100,63 +99,67 @@
                          Type type,
                          CFStringRef name,
                          CFStringRef cf_session_type) {
-  base::mac::ScopedNSAutoreleasePool pool;
-  NSURL* url = GetPlistURL(domain, type, name);
-  NSString* ns_path = [url path];
-  ns_path = SanitizeShellArgument(ns_path);
-  const char* file_path = [ns_path fileSystemRepresentation];
+  @autoreleasepool {
+    NSURL* url = GetPlistURL(domain, type, name);
+    NSString* ns_path = [url path];
+    ns_path = SanitizeShellArgument(ns_path);
+    const char* file_path = [ns_path fileSystemRepresentation];
 
-  NSString* ns_session_type =
-      SanitizeShellArgument(base::mac::CFToNSCast(cf_session_type));
-  if (!file_path || !ns_session_type) {
-    return false;
+    NSString* ns_session_type =
+        SanitizeShellArgument(base::mac::CFToNSCast(cf_session_type));
+    if (!file_path || !ns_session_type) {
+      return false;
+    }
+
+    std::vector<std::string> argv;
+    argv.push_back("/bin/bash");
+    argv.push_back("--noprofile");
+    argv.push_back("-c");
+    std::string command =
+        base::StringPrintf("/bin/launchctl unload -S %s %s;"
+                           "/bin/launchctl load -S %s %s;",
+                           [ns_session_type UTF8String], file_path,
+                           [ns_session_type UTF8String], file_path);
+    argv.push_back(command);
+
+    base::LaunchOptions options;
+    options.new_process_group = true;
+    return base::LaunchProcess(argv, options).IsValid();
   }
-
-  std::vector<std::string> argv;
-  argv.push_back("/bin/bash");
-  argv.push_back("--noprofile");
-  argv.push_back("-c");
-  std::string command = base::StringPrintf(
-      "/bin/launchctl unload -S %s %s;"
-      "/bin/launchctl load -S %s %s;",
-      [ns_session_type UTF8String], file_path,
-      [ns_session_type UTF8String], file_path);
-  argv.push_back(command);
-
-  base::LaunchOptions options;
-  options.new_process_group = true;
-  return base::LaunchProcess(argv, options).IsValid();
 }
 
 CFMutableDictionaryRef Launchd::CreatePlistFromFile(Domain domain,
                                                     Type type,
                                                     CFStringRef name) {
-  base::mac::ScopedNSAutoreleasePool pool;
-  NSURL* ns_url = GetPlistURL(domain, type, name);
-  NSMutableDictionary* plist =
-      [[NSMutableDictionary alloc] initWithContentsOfURL:ns_url];
-  return base::mac::NSToCFCast(plist);
+  @autoreleasepool {
+    NSURL* ns_url = GetPlistURL(domain, type, name);
+    NSMutableDictionary* plist =
+        [[NSMutableDictionary alloc] initWithContentsOfURL:ns_url];
+    return base::mac::NSToCFCast(plist);
+  }
 }
 
 bool Launchd::WritePlistToFile(Domain domain,
                                Type type,
                                CFStringRef name,
                                CFDictionaryRef dict) {
-  base::mac::ScopedNSAutoreleasePool pool;
-  NSURL* ns_url = GetPlistURL(domain, type, name);
-  return [base::mac::CFToNSCast(dict) writeToURL:ns_url atomically:YES];
+  @autoreleasepool {
+    NSURL* ns_url = GetPlistURL(domain, type, name);
+    return [base::mac::CFToNSCast(dict) writeToURL:ns_url atomically:YES];
+  }
 }
 
 bool Launchd::DeletePlist(Domain domain, Type type, CFStringRef name) {
-  base::mac::ScopedNSAutoreleasePool pool;
-  NSURL* ns_url = GetPlistURL(domain, type, name);
-  NSError* err = nil;
-  if (![[NSFileManager defaultManager] removeItemAtPath:[ns_url path]
-                                                  error:&err]) {
-    if ([err code] != NSFileNoSuchFileError) {
-      DLOG(ERROR) << "DeletePlist: " << base::mac::NSToCFCast(err);
+  @autoreleasepool {
+    NSURL* ns_url = GetPlistURL(domain, type, name);
+    NSError* err = nil;
+    if (![[NSFileManager defaultManager] removeItemAtPath:[ns_url path]
+                                                    error:&err]) {
+      if ([err code] != NSFileNoSuchFileError) {
+        DLOG(ERROR) << "DeletePlist: " << base::mac::NSToCFCast(err);
+      }
+      return false;
     }
-    return false;
+    return true;
   }
-  return true;
 }
diff --git a/chrome/common/service_process_util_mac.mm b/chrome/common/service_process_util_mac.mm
index b2de9d1..edef9967 100644
--- a/chrome/common/service_process_util_mac.mm
+++ b/chrome/common/service_process_util_mac.mm
@@ -16,7 +16,6 @@
 #include "base/mac/bundle_locations.h"
 #include "base/mac/foundation_util.h"
 #include "base/mac/mac_util.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/path_service.h"
@@ -37,10 +36,11 @@
 #define kServiceProcessSessionType "Aqua"
 
 CFStringRef CopyServiceProcessLaunchDName() {
-  base::mac::ScopedNSAutoreleasePool pool;
-  NSBundle* bundle = base::mac::FrameworkBundle();
-  return CFStringCreateCopy(kCFAllocatorDefault,
-                            base::mac::NSToCFCast([bundle bundleIdentifier]));
+  @autoreleasepool {
+    NSBundle* bundle = base::mac::FrameworkBundle();
+    return CFStringCreateCopy(kCFAllocatorDefault,
+                              base::mac::NSToCFCast([bundle bundleIdentifier]));
+  }
 }
 
 NSString* GetServiceProcessLaunchDLabel() {
@@ -105,44 +105,46 @@
 
 bool ServiceProcessState::GetServiceProcessData(std::string* version,
                                                 base::ProcessId* pid) {
-  base::mac::ScopedNSAutoreleasePool pool;
-  std::string label = base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
-  mac::services::JobInfo info;
-  if (!Launchd::GetInstance()->GetJobInfo(label, &info))
-    return false;
-  // Anything past here will return true in that there does appear
-  // to be a service process of some sort registered with launchd.
-  if (version) {
-    *version = "0";
-    NSString* exe_path = base::SysUTF8ToNSString(info.program);
-    if (exe_path) {
-      NSString* bundle_path = [[[exe_path stringByDeletingLastPathComponent]
-                                stringByDeletingLastPathComponent]
-                               stringByDeletingLastPathComponent];
-      NSBundle* bundle = [NSBundle bundleWithPath:bundle_path];
-      if (bundle) {
-        NSString* ns_version =
-            [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
-        if (ns_version) {
-          *version = base::SysNSStringToUTF8(ns_version);
+  @autoreleasepool {
+    std::string label =
+        base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
+    mac::services::JobInfo info;
+    if (!Launchd::GetInstance()->GetJobInfo(label, &info))
+      return false;
+    // Anything past here will return true in that there does appear
+    // to be a service process of some sort registered with launchd.
+    if (version) {
+      *version = "0";
+      NSString* exe_path = base::SysUTF8ToNSString(info.program);
+      if (exe_path) {
+        NSString* bundle_path = [[[exe_path stringByDeletingLastPathComponent]
+            stringByDeletingLastPathComponent]
+            stringByDeletingLastPathComponent];
+        NSBundle* bundle = [NSBundle bundleWithPath:bundle_path];
+        if (bundle) {
+          NSString* ns_version =
+              [bundle objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+          if (ns_version) {
+            *version = base::SysNSStringToUTF8(ns_version);
+          } else {
+            DLOG(ERROR) << "Unable to get version at: "
+                        << reinterpret_cast<CFStringRef>(bundle_path);
+          }
         } else {
-          DLOG(ERROR) << "Unable to get version at: "
+          // The bundle has been deleted out from underneath the registered
+          // job.
+          DLOG(ERROR) << "Unable to get bundle at: "
                       << reinterpret_cast<CFStringRef>(bundle_path);
         }
       } else {
-        // The bundle has been deleted out from underneath the registered
-        // job.
-        DLOG(ERROR) << "Unable to get bundle at: "
-                    << reinterpret_cast<CFStringRef>(bundle_path);
+        DLOG(ERROR) << "Unable to get executable path for service process";
       }
-    } else {
-      DLOG(ERROR) << "Unable to get executable path for service process";
     }
+    if (pid) {
+      *pid = info.pid ? *info.pid : -1;
+    }
+    return true;
   }
-  if (pid) {
-    *pid = info.pid ? *info.pid : -1;
-  }
-  return true;
 }
 
 bool ServiceProcessState::Initialize() {
@@ -211,43 +213,41 @@
 
 CFDictionaryRef CreateServiceProcessLaunchdPlist(base::CommandLine* cmd_line,
                                                  bool for_auto_launch) {
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    NSString* program = base::SysUTF8ToNSString(cmd_line->GetProgram().value());
 
-  NSString* program =
-      base::SysUTF8ToNSString(cmd_line->GetProgram().value());
+    std::vector<std::string> args = cmd_line->argv();
+    NSMutableArray* ns_args = [NSMutableArray arrayWithCapacity:args.size()];
 
-  std::vector<std::string> args = cmd_line->argv();
-  NSMutableArray* ns_args = [NSMutableArray arrayWithCapacity:args.size()];
+    for (std::vector<std::string>::iterator iter = args.begin();
+         iter < args.end(); ++iter) {
+      [ns_args addObject:base::SysUTF8ToNSString(*iter)];
+    }
 
-  for (std::vector<std::string>::iterator iter = args.begin();
-       iter < args.end();
-       ++iter) {
-    [ns_args addObject:base::SysUTF8ToNSString(*iter)];
+    // See the man page for launchd.plist.
+    NSMutableDictionary* launchd_plist = [@{
+      @LAUNCH_JOBKEY_LABEL : GetServiceProcessLaunchDLabel(),
+      @LAUNCH_JOBKEY_PROGRAM : program,
+      @LAUNCH_JOBKEY_PROGRAMARGUMENTS : ns_args,
+      @LAUNCH_JOBKEY_MACHSERVICES : GetServiceProcessMachName(),
+    } mutableCopy];
+
+    if (for_auto_launch) {
+      // We want the service process to be able to exit if there are no services
+      // enabled. With a value of NO in the SuccessfulExit key, launchd will
+      // relaunch the service automatically in any other case than exiting
+      // cleanly with a 0 return code.
+      NSDictionary* keep_alive =
+          @{@LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT : @NO};
+      NSDictionary* auto_launchd_plist = @{
+        @LAUNCH_JOBKEY_RUNATLOAD : @YES,
+        @LAUNCH_JOBKEY_KEEPALIVE : keep_alive,
+        @LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE : @kServiceProcessSessionType
+      };
+      [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
+    }
+    return reinterpret_cast<CFDictionaryRef>(launchd_plist);
   }
-
-  // See the man page for launchd.plist.
-  NSMutableDictionary* launchd_plist = [@{
-    @LAUNCH_JOBKEY_LABEL : GetServiceProcessLaunchDLabel(),
-    @LAUNCH_JOBKEY_PROGRAM : program,
-    @LAUNCH_JOBKEY_PROGRAMARGUMENTS : ns_args,
-    @LAUNCH_JOBKEY_MACHSERVICES : GetServiceProcessMachName(),
-  } mutableCopy];
-
-  if (for_auto_launch) {
-    // We want the service process to be able to exit if there are no services
-    // enabled. With a value of NO in the SuccessfulExit key, launchd will
-    // relaunch the service automatically in any other case than exiting
-    // cleanly with a 0 return code.
-    NSDictionary* keep_alive =
-        @{ @LAUNCH_JOBKEY_KEEPALIVE_SUCCESSFULEXIT : @NO };
-    NSDictionary* auto_launchd_plist = @{
-      @LAUNCH_JOBKEY_RUNATLOAD : @YES,
-      @LAUNCH_JOBKEY_KEEPALIVE : keep_alive,
-      @LAUNCH_JOBKEY_LIMITLOADTOSESSIONTYPE : @kServiceProcessSessionType
-    };
-    [launchd_plist addEntriesFromDictionary:auto_launchd_plist];
-  }
-  return reinterpret_cast<CFDictionaryRef>(launchd_plist);
 }
 
 // Writes the launchd property list into the user's LaunchAgents directory,
@@ -272,24 +272,23 @@
 }
 
 bool ServiceProcessState::StateData::WatchExecutable() {
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  base::FilePath executable_path = base::FilePath(job_info.program);
-  std::unique_ptr<ExecFilePathWatcherCallback> callback(
-      new ExecFilePathWatcherCallback);
-  if (!callback->Init(executable_path)) {
-    DLOG(ERROR) << "executable_watcher.Init " << executable_path.value();
-    return false;
+  @autoreleasepool {
+    base::FilePath executable_path = base::FilePath(job_info.program);
+    std::unique_ptr<ExecFilePathWatcherCallback> callback(
+        new ExecFilePathWatcherCallback);
+    if (!callback->Init(executable_path)) {
+      DLOG(ERROR) << "executable_watcher.Init " << executable_path.value();
+      return false;
+    }
+    if (!executable_watcher.Watch(
+            executable_path, false,
+            base::Bind(&ExecFilePathWatcherCallback::NotifyPathChanged,
+                       base::Owned(callback.release())))) {
+      DLOG(ERROR) << "executable_watcher.watch " << executable_path.value();
+      return false;
+    }
+    return true;
   }
-  if (!executable_watcher.Watch(
-          executable_path,
-          false,
-          base::Bind(&ExecFilePathWatcherCallback::NotifyPathChanged,
-                     base::Owned(callback.release())))) {
-    DLOG(ERROR) << "executable_watcher.watch " << executable_path.value();
-    return false;
-  }
-  return true;
 }
 
 bool ExecFilePathWatcherCallback::Init(const base::FilePath& path) {
@@ -306,103 +305,100 @@
     return;
   }
 
-  base::mac::ScopedNSAutoreleasePool pool;
-  bool needs_shutdown = false;
-  bool needs_restart = false;
-  bool good_bundle = false;
+  @autoreleasepool {
+    bool needs_shutdown = false;
+    bool needs_restart = false;
+    bool good_bundle = false;
 
-  // Go from bundle/Contents/MacOS/executable to bundle.
-  NSURL* bundle_url = [[[executable_fsref_ URLByDeletingLastPathComponent]
-      URLByDeletingLastPathComponent] URLByDeletingLastPathComponent];
-  if (bundle_url) {
-    base::ScopedCFTypeRef<CFBundleRef> bundle(
-        CFBundleCreate(kCFAllocatorDefault, base::mac::NSToCFCast(bundle_url)));
-    good_bundle = CFBundleGetIdentifier(bundle) != NULL;
-  }
-
-  if (!good_bundle) {
-    needs_shutdown = true;
-  } else {
-    bool in_trash = false;
-    NSFileManager* file_manager = [NSFileManager defaultManager];
-    NSURLRelationship relationship;
-    if ([file_manager getRelationship:&relationship
-                          ofDirectory:NSTrashDirectory
-                             inDomain:0
-                          toItemAtURL:executable_fsref_
-                                error:nil]) {
-      in_trash = relationship == NSURLRelationshipContains;
+    // Go from bundle/Contents/MacOS/executable to bundle.
+    NSURL* bundle_url = [[[executable_fsref_ URLByDeletingLastPathComponent]
+        URLByDeletingLastPathComponent] URLByDeletingLastPathComponent];
+    if (bundle_url) {
+      base::ScopedCFTypeRef<CFBundleRef> bundle(CFBundleCreate(
+          kCFAllocatorDefault, base::mac::NSToCFCast(bundle_url)));
+      good_bundle = CFBundleGetIdentifier(bundle) != NULL;
     }
-    if (in_trash) {
+
+    if (!good_bundle) {
       needs_shutdown = true;
     } else {
-      bool was_moved = true;
-      NSString* path_string = base::mac::FilePathToNSString(path);
-      NSURL* path_url = [NSURL fileURLWithPath:path_string isDirectory:NO];
-      NSURL* path_ref = [path_url fileReferenceURL];
-      if (path_ref != nil) {
-        if ([path_ref isEqual:executable_fsref_]) {
-          was_moved = false;
+      bool in_trash = false;
+      NSFileManager* file_manager = [NSFileManager defaultManager];
+      NSURLRelationship relationship;
+      if ([file_manager getRelationship:&relationship
+                            ofDirectory:NSTrashDirectory
+                               inDomain:0
+                            toItemAtURL:executable_fsref_
+                                  error:nil]) {
+        in_trash = relationship == NSURLRelationshipContains;
+      }
+      if (in_trash) {
+        needs_shutdown = true;
+      } else {
+        bool was_moved = true;
+        NSString* path_string = base::mac::FilePathToNSString(path);
+        NSURL* path_url = [NSURL fileURLWithPath:path_string isDirectory:NO];
+        NSURL* path_ref = [path_url fileReferenceURL];
+        if (path_ref != nil) {
+          if ([path_ref isEqual:executable_fsref_]) {
+            was_moved = false;
+          }
+        }
+        if (was_moved) {
+          needs_restart = true;
         }
       }
-      if (was_moved) {
-        needs_restart = true;
-      }
     }
-  }
-  if (needs_shutdown || needs_restart) {
-    // First deal with the plist.
-    base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
-    if (needs_restart) {
-      base::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
-          Launchd::GetInstance()->CreatePlistFromFile(
-              Launchd::User, Launchd::Agent, name));
-      if (plist.get()) {
-        NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
-        NSURL* new_path = [executable_fsref_ filePathURL];
-        DCHECK([new_path isFileURL]);
-        NSString* ns_new_path = [new_path path];
-        ns_plist[@LAUNCH_JOBKEY_PROGRAM] = ns_new_path;
-        base::scoped_nsobject<NSMutableArray> args(
-            [ns_plist[@LAUNCH_JOBKEY_PROGRAMARGUMENTS] mutableCopy]);
-        args[0] = ns_new_path;
-        ns_plist[@LAUNCH_JOBKEY_PROGRAMARGUMENTS] = args;
-        if (!Launchd::GetInstance()->WritePlistToFile(Launchd::User,
-                                                      Launchd::Agent,
-                                                      name,
-                                                      plist)) {
-          DLOG(ERROR) << "Unable to rewrite plist.";
+    if (needs_shutdown || needs_restart) {
+      // First deal with the plist.
+      base::ScopedCFTypeRef<CFStringRef> name(CopyServiceProcessLaunchDName());
+      if (needs_restart) {
+        base::ScopedCFTypeRef<CFMutableDictionaryRef> plist(
+            Launchd::GetInstance()->CreatePlistFromFile(Launchd::User,
+                                                        Launchd::Agent, name));
+        if (plist.get()) {
+          NSMutableDictionary* ns_plist = base::mac::CFToNSCast(plist);
+          NSURL* new_path = [executable_fsref_ filePathURL];
+          DCHECK([new_path isFileURL]);
+          NSString* ns_new_path = [new_path path];
+          ns_plist[@LAUNCH_JOBKEY_PROGRAM] = ns_new_path;
+          base::scoped_nsobject<NSMutableArray> args(
+              [ns_plist[@LAUNCH_JOBKEY_PROGRAMARGUMENTS] mutableCopy]);
+          args[0] = ns_new_path;
+          ns_plist[@LAUNCH_JOBKEY_PROGRAMARGUMENTS] = args;
+          if (!Launchd::GetInstance()->WritePlistToFile(
+                  Launchd::User, Launchd::Agent, name, plist)) {
+            DLOG(ERROR) << "Unable to rewrite plist.";
+            needs_shutdown = true;
+          }
+        } else {
+          DLOG(ERROR) << "Unable to read plist.";
           needs_shutdown = true;
         }
-      } else {
-        DLOG(ERROR) << "Unable to read plist.";
-        needs_shutdown = true;
       }
-    }
-    if (needs_shutdown) {
-      if (!RemoveFromLaunchd()) {
-        DLOG(ERROR) << "Unable to RemoveFromLaunchd.";
+      if (needs_shutdown) {
+        if (!RemoveFromLaunchd()) {
+          DLOG(ERROR) << "Unable to RemoveFromLaunchd.";
+        }
       }
-    }
 
-    // Then deal with the process.
-    CFStringRef session_type = CFSTR(kServiceProcessSessionType);
-    if (needs_restart) {
-      if (!Launchd::GetInstance()->RestartJob(Launchd::User,
-                                              Launchd::Agent,
-                                              name,
-                                              session_type)) {
-        DLOG(ERROR) << "RestartLaunchdJob";
-        needs_shutdown = true;
+      // Then deal with the process.
+      CFStringRef session_type = CFSTR(kServiceProcessSessionType);
+      if (needs_restart) {
+        if (!Launchd::GetInstance()->RestartJob(Launchd::User, Launchd::Agent,
+                                                name, session_type)) {
+          DLOG(ERROR) << "RestartLaunchdJob";
+          needs_shutdown = true;
+        }
       }
-    }
-    if (needs_shutdown) {
-      const std::string& label =
-          base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
-      if (!Launchd::GetInstance()->RemoveJob(label)) {
-        DLOG(ERROR) << "RemoveJob " << label;
-        // Exiting with zero, so launchd doesn't restart the process.
-        exit(0);
+      if (needs_shutdown) {
+        const std::string& label =
+            base::SysNSStringToUTF8(GetServiceProcessLaunchDLabel());
+        if (!Launchd::GetInstance()->RemoveJob(label)) {
+          DLOG(ERROR) << "RemoveJob " << label;
+          // Exiting with zero, so launchd doesn't restart the process.
+          exit(0);
+        }
       }
     }
   }
diff --git a/chrome/common/webui_url_constants.cc b/chrome/common/webui_url_constants.cc
index 9a0781a4..fb7e6795 100644
--- a/chrome/common/webui_url_constants.cc
+++ b/chrome/common/webui_url_constants.cc
@@ -388,6 +388,7 @@
 const char kCrostiniExportImportSubPage[] = "crostini/exportImport";
 const char kDateTimeSubPage[] = "dateTime";
 const char kDisplaySubPage[] = "display";
+const char kExternalStorageSubPage[] = "storage/externalStoragePreferences";
 const char kHelpSubPage[] = "help";
 const char kInternetSubPage[] = "internet";
 const char kKerberosAccountsSubPage[] = "kerberosAccounts";
@@ -428,6 +429,7 @@
       kCrostiniSharedUsbDevicesSubPage,
       kDateTimeSubPage,
       kDisplaySubPage,
+      kExternalStorageSubPage,
       kHelpSubPage,
       kInternetSubPage,
       kKerberosAccountsSubPage,
diff --git a/chrome/common/webui_url_constants.h b/chrome/common/webui_url_constants.h
index 0c46b710..525a8b2 100644
--- a/chrome/common/webui_url_constants.h
+++ b/chrome/common/webui_url_constants.h
@@ -344,6 +344,7 @@
 extern const char kCrostiniExportImportSubPage[];
 extern const char kDateTimeSubPage[];
 extern const char kDisplaySubPage[];
+extern const char kExternalStorageSubPage[];
 extern const char kHelpSubPage[];
 extern const char kInternetSubPage[];
 extern const char kKerberosAccountsSubPage[];
diff --git a/chrome/services/cups_proxy/BUILD.gn b/chrome/services/cups_proxy/BUILD.gn
index 5d45be4..869f668 100644
--- a/chrome/services/cups_proxy/BUILD.gn
+++ b/chrome/services/cups_proxy/BUILD.gn
@@ -5,8 +5,8 @@
 import("//build/config/features.gni")
 import("//printing/buildflags/buildflags.gni")
 
-assert(is_chromeos,
-       "Non-Chrome-OS builds must not depend on //chrome/services/cups_proxy")
+assert(is_chromeos, "Non-ChromeOS builds must not depend on this")
+assert(use_cups, "Non-CUPS builds must not depend on this")
 
 source_set("cups_proxy") {
   sources = [
@@ -14,6 +14,16 @@
     "cups_proxy_service.h",
     "cups_proxy_service_delegate.cc",
     "cups_proxy_service_delegate.h",
+    "ipp_attribute_validator.cc",
+    "ipp_attribute_validator.h",
+    "ipp_validator.cc",
+    "ipp_validator.h",
+    "printer_installer.cc",
+    "printer_installer.h",
+    "proxy_manager.cc",
+    "proxy_manager.h",
+    "socket_manager.cc",
+    "socket_manager.h",
   ]
 
   deps = [
@@ -24,24 +34,11 @@
     "//net",
   ]
 
-  # We stub this service if libCUPS is not present.
-  if (use_cups) {
-    configs += [ "//printing:cups" ]
-    sources += [
-      "ipp_attribute_validator.cc",
-      "ipp_attribute_validator.h",
-      "ipp_validator.cc",
-      "ipp_validator.h",
-      "printer_installer.cc",
-      "printer_installer.h",
-      "socket_manager.cc",
-      "socket_manager.h",
-    ]
-  }
-
   public_deps = [
     "//printing",
   ]
+
+  configs += [ "//printing:cups" ]
 }
 
 static_library("test_support") {
@@ -58,26 +55,22 @@
 
 source_set("unit_tests") {
   testonly = true
+  sources = [
+    "ipp_validator_unittest.cc",
+    "printer_installer_unittest.cc",
+    "socket_manager_unittest.cc",
+  ]
 
-  # Target is empty unless libCUPS is available.
-  if (use_cups) {
-    sources = [
-      "ipp_validator_unittest.cc",
-      "printer_installer_unittest.cc",
-      "socket_manager_unittest.cc",
-    ]
+  deps = [
+    ":cups_proxy",
+    ":test_support",
+    "//base",
+    "//chrome/services/cups_proxy/public/cpp",
+    "//chrome/services/cups_proxy/public/cpp:unit_tests",
+    "//testing/gtest",
+  ]
 
-    deps = [
-      ":cups_proxy",
-      ":test_support",
-      "//base",
-      "//chrome/services/cups_proxy/public/cpp",
-      "//chrome/services/cups_proxy/public/cpp:unit_tests",
-      "//testing/gtest",
-    ]
-
-    data = [
-      "//chrome/test/data/cups_proxy",
-    ]
-  }
+  data = [
+    "//chrome/test/data/cups_proxy",
+  ]
 }
diff --git a/chrome/services/cups_proxy/DEPS b/chrome/services/cups_proxy/DEPS
index 09b3efe..464fd30 100644
--- a/chrome/services/cups_proxy/DEPS
+++ b/chrome/services/cups_proxy/DEPS
@@ -1,6 +1,6 @@
 include_rules = [
   "+chromeos/printing",
-  "+chrome/services/ipp_parser/public",
+  "+chrome/services/ipp_parser",
   "+chromeos/dbus/cups_proxy",
   "+third_party/cros_system_api/dbus",
 ]
diff --git a/chrome/services/cups_proxy/cups_proxy_service.cc b/chrome/services/cups_proxy/cups_proxy_service.cc
index a983b1d..c14fd16 100644
--- a/chrome/services/cups_proxy/cups_proxy_service.cc
+++ b/chrome/services/cups_proxy/cups_proxy_service.cc
@@ -10,6 +10,7 @@
 
 #include "base/no_destructor.h"
 #include "chrome/services/cups_proxy/cups_proxy_service_delegate.h"
+#include "chrome/services/cups_proxy/proxy_manager.h"
 #include "chromeos/dbus/cups_proxy/cups_proxy_client.h"
 #include "mojo/public/cpp/platform/platform_channel.h"
 #include "mojo/public/cpp/system/invitation.h"
@@ -47,7 +48,9 @@
 
   // Bind our end of pipe to our |proxy_manager_|. The daemon should
   // bind its end to a remote<CupsProxier>;
-  // TODO(crbug.com/945409): Add handler & connection error handler.
+  proxy_manager_ = ProxyManager::Create(
+      mojo::PendingReceiver<mojom::CupsProxier>(std::move(pipe)),
+      std::move(delegate));
 
   // Send the file descriptor for the other end of |platform_channel| to the
   // CupsProxyDaemon over D-Bus.
diff --git a/chrome/services/cups_proxy/cups_proxy_service.h b/chrome/services/cups_proxy/cups_proxy_service.h
index 65345ca..21233da 100644
--- a/chrome/services/cups_proxy/cups_proxy_service.h
+++ b/chrome/services/cups_proxy/cups_proxy_service.h
@@ -6,7 +6,6 @@
 #define CHROME_SERVICES_CUPS_PROXY_CUPS_PROXY_SERVICE_H_
 
 #include <memory>
-#include <string>
 
 #include "base/memory/weak_ptr.h"
 #include "base/observer_list.h"
@@ -16,6 +15,7 @@
 namespace cups_proxy {
 
 class CupsProxyServiceDelegate;
+class ProxyManager;
 
 // This service lives in the browser process and is managed by the
 // CupsProxyServiceManager. It bootstraps/maintains a mojom connection with the
@@ -42,6 +42,9 @@
       std::unique_ptr<CupsProxyServiceDelegate> delegate);
   void OnBindToCupsProxyDaemon(bool success);
 
+  // Handler that implements the top-level mojom interface (mojom::CupsProxier)
+  std::unique_ptr<ProxyManager> proxy_manager_;
+
   base::WeakPtrFactory<CupsProxyService> weak_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(CupsProxyService);
 };
diff --git a/chrome/services/cups_proxy/ipp_validator.cc b/chrome/services/cups_proxy/ipp_validator.cc
index e212a1f..3eda616 100644
--- a/chrome/services/cups_proxy/ipp_validator.cc
+++ b/chrome/services/cups_proxy/ipp_validator.cc
@@ -277,19 +277,14 @@
                     pdf_magic_bytes.begin());
 }
 
-IppValidator::IppValidator(base::WeakPtr<CupsProxyServiceDelegate> delegate)
-    : delegate_(std::move(delegate)) {}
+IppValidator::IppValidator(CupsProxyServiceDelegate* const delegate)
+    : delegate_(delegate) {}
 
 IppValidator::~IppValidator() = default;
 
 base::Optional<IppRequest> IppValidator::ValidateIppRequest(
     ipp_parser::mojom::IppRequestPtr to_validate) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
-  if (!delegate_) {
-    // TODO(crbug/495409): Add fatal error option to bring down service.
-    return base::nullopt;
-  }
-
   // Build ipp message.
   // Note: Moving ipp here, to_validate->ipp no longer valid below.
   printing::ScopedIppPtr ipp =
diff --git a/chrome/services/cups_proxy/ipp_validator.h b/chrome/services/cups_proxy/ipp_validator.h
index 08df348b..6ddb0ba 100644
--- a/chrome/services/cups_proxy/ipp_validator.h
+++ b/chrome/services/cups_proxy/ipp_validator.h
@@ -27,7 +27,7 @@
 // sequenced context.
 class IppValidator {
  public:
-  explicit IppValidator(base::WeakPtr<CupsProxyServiceDelegate> delegate);
+  explicit IppValidator(CupsProxyServiceDelegate* const delegate);
   ~IppValidator();
 
   // Validates each of |to_validate|'s fields and returns a POD representation
@@ -49,8 +49,8 @@
 
   bool ValidateIppData(const std::vector<uint8_t>& ipp_data);
 
-  // Delegate providing necessary Profile dependencies.
-  base::WeakPtr<CupsProxyServiceDelegate> delegate_;
+  // Unowned delegate providing necessary Profile dependencies.
+  CupsProxyServiceDelegate* const delegate_;
 
   SEQUENCE_CHECKER(sequence_checker_);
 };
diff --git a/chrome/services/cups_proxy/printer_installer.cc b/chrome/services/cups_proxy/printer_installer.cc
index b41a2fd..8cf967a 100644
--- a/chrome/services/cups_proxy/printer_installer.cc
+++ b/chrome/services/cups_proxy/printer_installer.cc
@@ -20,9 +20,8 @@
 
 namespace cups_proxy {
 
-PrinterInstaller::PrinterInstaller(
-    base::WeakPtr<CupsProxyServiceDelegate> delegate)
-    : delegate_(std::move(delegate)) {}
+PrinterInstaller::PrinterInstaller(CupsProxyServiceDelegate* const delegate)
+    : delegate_(delegate) {}
 
 PrinterInstaller::~PrinterInstaller() = default;
 
diff --git a/chrome/services/cups_proxy/printer_installer.h b/chrome/services/cups_proxy/printer_installer.h
index c026b1f..ffce3e5 100644
--- a/chrome/services/cups_proxy/printer_installer.h
+++ b/chrome/services/cups_proxy/printer_installer.h
@@ -31,7 +31,7 @@
 // proxying. This class must be created and accessed from a sequenced context.
 class PrinterInstaller {
  public:
-  explicit PrinterInstaller(base::WeakPtr<CupsProxyServiceDelegate> delegate);
+  explicit PrinterInstaller(CupsProxyServiceDelegate* const delegate);
   ~PrinterInstaller();
 
   // Pre-installs any printers required by |ipp| into the CUPS daemon, as
@@ -42,8 +42,8 @@
   void OnInstallPrinter(InstallPrinterCallback cb, bool success);
   void Finish(InstallPrinterCallback cb, InstallPrinterResult res);
 
-  // Service delegate granting access to printing stack dependencies.
-  base::WeakPtr<CupsProxyServiceDelegate> delegate_;
+  // Unowned delegate granting access to printing stack dependencies.
+  CupsProxyServiceDelegate* const delegate_;
 
   SEQUENCE_CHECKER(sequence_checker_);
   base::WeakPtrFactory<PrinterInstaller> weak_factory_{this};
diff --git a/chrome/services/cups_proxy/proxy_manager.cc b/chrome/services/cups_proxy/proxy_manager.cc
new file mode 100644
index 0000000..b7a253f
--- /dev/null
+++ b/chrome/services/cups_proxy/proxy_manager.cc
@@ -0,0 +1,353 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/cups_proxy/proxy_manager.h"
+
+#include <cups/ipp.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/services/cups_proxy/cups_proxy_service_delegate.h"
+#include "chrome/services/cups_proxy/ipp_validator.h"
+#include "chrome/services/cups_proxy/printer_installer.h"
+#include "chrome/services/cups_proxy/public/cpp/cups_util.h"
+#include "chrome/services/cups_proxy/public/cpp/ipp_messages.h"
+#include "chrome/services/cups_proxy/public/cpp/type_conversions.h"
+#include "chrome/services/cups_proxy/socket_manager.h"
+#include "chrome/services/ipp_parser/ipp_parser_service.h"
+#include "chrome/services/ipp_parser/public/cpp/ipp_converter.h"
+#include "content/public/common/service_manager_connection.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/remote.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "printing/backend/cups_ipp_util.h"
+#include "services/service_manager/public/cpp/connector.h"
+
+namespace cups_proxy {
+namespace {
+
+struct InFlightRequest {
+  IppRequest request;
+  ProxyManager::ProxyRequestCallback cb;
+};
+
+class ProxyManagerImpl : public ProxyManager {
+ public:
+  ProxyManagerImpl(mojo::PendingReceiver<mojom::CupsProxier> request,
+                   std::unique_ptr<CupsProxyServiceDelegate> delegate,
+                   std::unique_ptr<IppValidator> ipp_validator,
+                   std::unique_ptr<PrinterInstaller> printer_installer,
+                   std::unique_ptr<SocketManager> socket_manager);
+  ~ProxyManagerImpl() override;
+
+  void ProxyRequest(const std::string& method,
+                    const std::string& url,
+                    const std::string& version,
+                    const std::vector<ipp_converter::HttpHeader>& headers,
+                    const std::vector<uint8_t>& body,
+                    ProxyRequestCallback cb) override;
+
+ private:
+  // These methods interface with |ipp_parser_| to parse the syntax of the
+  // inflight IPP request.
+  void ParseIpp();
+  void OnParseIpp(ipp_parser::mojom::IppRequestPtr parsed_request);
+
+  // For CUPS-Get-Printers requests, we spoof the response.
+  void SpoofGetPrinters();
+
+  // The callback for interfacing with |printer_installer_|; used to
+  // pre-install any printers referenced by the inflight request into CUPS.
+  void OnInstallPrinter(InstallPrinterResult res);
+
+  // These methods interface with |socket_manager_| to actually proxy the
+  // inflight request to CUPS and propagate back its IPP response.
+  void ProxyToCups();
+  void OnProxyToCups(std::unique_ptr<std::vector<uint8_t>> response);
+
+  // Proxy the IPP response back to the caller.
+  void ProxyResponseToCaller(const std::vector<uint8_t>& response);
+
+  // Fail in-flight request.
+  void Fail(const std::string& error_message);
+
+  // Delegate providing necessary Profile dependencies.
+  std::unique_ptr<CupsProxyServiceDelegate> delegate_;
+
+  // Current in-flight request.
+  std::unique_ptr<InFlightRequest> in_flight_;
+
+  // CupsIppParser Service handle.
+  mojo::Remote<ipp_parser::mojom::IppParser> ipp_parser_;
+
+  // Runs in current sequence.
+  std::unique_ptr<IppValidator> ipp_validator_;
+  std::unique_ptr<PrinterInstaller> printer_installer_;
+  std::unique_ptr<SocketManager> socket_manager_;
+
+  SEQUENCE_CHECKER(sequence_checker_);
+
+  mojo::Receiver<mojom::CupsProxier> receiver_;
+  base::WeakPtrFactory<ProxyManagerImpl> weak_factory_{this};
+  DISALLOW_COPY_AND_ASSIGN(ProxyManagerImpl);
+};
+
+base::Optional<std::vector<uint8_t>> RebuildIppRequest(
+    const std::string& method,
+    const std::string& url,
+    const std::string& version,
+    const std::vector<ipp_converter::HttpHeader>& headers,
+    const std::vector<uint8_t>& body) {
+  auto request_line_buffer =
+      ipp_converter::BuildRequestLine(method, url, version);
+  if (!request_line_buffer.has_value()) {
+    return base::nullopt;
+  }
+
+  auto headers_buffer = ipp_converter::BuildHeaders(headers);
+  if (!headers_buffer.has_value()) {
+    return base::nullopt;
+  }
+
+  std::vector<uint8_t> ret;
+  ret.insert(ret.end(), request_line_buffer->begin(),
+             request_line_buffer->end());
+  ret.insert(ret.end(), headers_buffer->begin(), headers_buffer->end());
+  ret.insert(ret.end(), body.begin(), body.end());
+  return ret;
+}
+
+ProxyManagerImpl::ProxyManagerImpl(
+    mojo::PendingReceiver<CupsProxier> receiver,
+    std::unique_ptr<CupsProxyServiceDelegate> delegate,
+    std::unique_ptr<IppValidator> ipp_validator,
+    std::unique_ptr<PrinterInstaller> printer_installer,
+    std::unique_ptr<SocketManager> socket_manager)
+    : delegate_(std::move(delegate)),
+      ipp_validator_(std::move(ipp_validator)),
+      printer_installer_(std::move(printer_installer)),
+      socket_manager_(std::move(socket_manager)),
+      receiver_(this, std::move(receiver)) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+}
+
+ProxyManagerImpl::~ProxyManagerImpl() = default;
+
+void ProxyManagerImpl::ProxyRequest(
+    const std::string& method,
+    const std::string& url,
+    const std::string& version,
+    const std::vector<ipp_converter::HttpHeader>& headers,
+    const std::vector<uint8_t>& body,
+    ProxyRequestCallback cb) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+  // If we already have an in-flight request, we fail this incoming one
+  // directly.
+  if (in_flight_) {
+    DVLOG(1) << "CupsPrintService Error: Already have an in-flight request";
+    std::move(cb).Run({}, {});
+    return;
+  }
+
+  in_flight_ = std::make_unique<InFlightRequest>();
+  in_flight_->cb = std::move(cb);
+
+  // TODO(crbug.com/945409): Rename in both proxy.mojom's: url -> endpoint.
+  auto request_buffer = RebuildIppRequest(method, url, version, headers, body);
+  if (!request_buffer) {
+    return Fail("Failed to rebuild incoming IPP request");
+  }
+
+  // Save request.
+  in_flight_->request.buffer = *request_buffer;
+  ParseIpp();
+  return;
+}
+
+void ProxyManagerImpl::ParseIpp() {
+  // Launch CupsIppParser service, if needed.
+  if (!ipp_parser_) {
+    ipp_parser_.Bind(ipp_parser::LaunchIppParser());
+    ipp_parser_.reset_on_disconnect();
+  }
+
+  // Run out-of-process IPP parsing.
+  ipp_parser_->ParseIpp(in_flight_->request.buffer,
+                        base::BindOnce(&ProxyManagerImpl::OnParseIpp,
+                                       weak_factory_.GetWeakPtr()));
+}
+
+void ProxyManagerImpl::OnParseIpp(
+    ipp_parser::mojom::IppRequestPtr parsed_request) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(in_flight_);
+
+  if (!parsed_request) {
+    return Fail("Failed to parse IPP request");
+  }
+
+  // Validate parsed request.
+  auto valid_request =
+      ipp_validator_->ValidateIppRequest(std::move(parsed_request));
+  if (!valid_request) {
+    return Fail("Failed to validate IPP request");
+  }
+
+  // Save newly validated request.
+  in_flight_->request = std::move(*valid_request);
+
+  auto opcode = ippGetOperation(in_flight_->request.ipp.get());
+  if (opcode == IPP_OP_CUPS_NONE) {
+    return Fail("Failed to parse IPP operation ID");
+  }
+
+  // Since Chrome is the source-of-truth for printers on ChromeOS, for
+  // CUPS-Get-Printers requests, we spoof the response rather than proxying to
+  // CUPS.
+  if (opcode == IPP_OP_CUPS_GET_PRINTERS) {
+    SpoofGetPrinters();
+    return;
+  }
+
+  // If this request references a printer, pre-install it into CUPS.
+  auto printer_uuid = GetPrinterId(in_flight_->request.ipp.get());
+  if (printer_uuid.has_value()) {
+    printer_installer_->InstallPrinter(
+        *printer_uuid, base::BindOnce(&ProxyManagerImpl::OnInstallPrinter,
+                                      weak_factory_.GetWeakPtr()));
+    return;
+  }
+
+  // Nothing left to do, skip straight to proxying.
+  ProxyToCups();
+  return;
+}
+
+void ProxyManagerImpl::SpoofGetPrinters() {
+  auto response =
+      BuildGetDestsResponse(in_flight_->request, delegate_->GetPrinters());
+  if (!response.has_value()) {
+    return Fail("Failed to spoof CUPS-Get-Printers response");
+  }
+
+  ProxyResponseToCaller(response->buffer);
+  return;
+}
+
+void ProxyManagerImpl::OnInstallPrinter(InstallPrinterResult res) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(in_flight_);
+
+  if (res != InstallPrinterResult::kSuccess) {
+    return Fail("Failed to pre-install printer");
+  }
+
+  ProxyToCups();
+  return;
+}
+
+void ProxyManagerImpl::ProxyToCups() {
+  // Queue request with socket_manager_
+  socket_manager_->ProxyToCups(std::move(in_flight_->request.buffer),
+                               base::BindOnce(&ProxyManagerImpl::OnProxyToCups,
+                                              weak_factory_.GetWeakPtr()));
+}
+
+void ProxyManagerImpl::OnProxyToCups(
+    std::unique_ptr<std::vector<uint8_t>> response) {
+  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+  DCHECK(in_flight_);
+
+  if (!response) {
+    return Fail("Failed to proxy request");
+  }
+
+  ProxyResponseToCaller(*response);
+  return;
+}
+
+void ProxyManagerImpl::ProxyResponseToCaller(
+    const std::vector<uint8_t>& response) {
+  // Convert to string for parsing HTTP headers.
+  std::string response_str = ipp_converter::ConvertToString(response);
+  auto end_of_headers = net::HttpUtil::LocateEndOfHeaders(response_str.data(),
+                                                          response_str.size());
+  if (end_of_headers < 0) {
+    return Fail("IPP response missing end of headers");
+  }
+
+  base::StringPiece headers_slice(response_str.data(), end_of_headers);
+  scoped_refptr<net::HttpResponseHeaders> response_headers =
+      net::HttpResponseHeaders::TryToCreate(headers_slice);
+  if (!response_headers) {
+    return Fail("Failed to parse HTTP response headers");
+  }
+
+  std::vector<ipp_converter::HttpHeader> parsed_headers;
+  size_t iter = 0;
+  std::string name, value;
+  while (response_headers->EnumerateHeaderLines(&iter, &name, &value)) {
+    parsed_headers.push_back({name, value});
+  }
+
+  // Slice off the ipp_message as is.
+  std::vector<uint8_t> ipp_message(response.begin() + end_of_headers,
+                                   response.end());
+
+  // Send parsed response back to caller.
+  std::move(in_flight_->cb)
+      .Run(std::move(parsed_headers), std::move(ipp_message));
+  in_flight_.reset();
+}
+
+// TODO(crbug.com/945409): Fail with comprehensive HTTP Error response.
+// Fails current request by running its callback with an empty response and
+// clearing in_flight_.
+void ProxyManagerImpl::Fail(const std::string& error_message) {
+  DCHECK(in_flight_);
+
+  DVLOG(1) << "CupsPrintService Error: " << error_message;
+
+  std::move(in_flight_->cb).Run({}, {});
+  in_flight_.reset();
+}
+
+}  // namespace
+
+// static
+std::unique_ptr<ProxyManager> ProxyManager::Create(
+    mojo::PendingReceiver<mojom::CupsProxier> request,
+    std::unique_ptr<CupsProxyServiceDelegate> delegate) {
+  // Setting up injected managers.
+  auto ipp_validator = std::make_unique<IppValidator>(delegate.get());
+  auto printer_installer = std::make_unique<PrinterInstaller>(delegate.get());
+  auto socket_manager = SocketManager::Create(delegate.get());
+
+  return std::make_unique<ProxyManagerImpl>(
+      std::move(request), std::move(delegate), std::move(ipp_validator),
+      std::move(printer_installer), std::move(socket_manager));
+}
+
+std::unique_ptr<ProxyManager> ProxyManager::CreateForTesting(
+    mojo::PendingReceiver<mojom::CupsProxier> request,
+    std::unique_ptr<CupsProxyServiceDelegate> delegate,
+    std::unique_ptr<IppValidator> ipp_validator,
+    std::unique_ptr<PrinterInstaller> printer_installer,
+    std::unique_ptr<SocketManager> socket_manager) {
+  return std::make_unique<ProxyManagerImpl>(
+      std::move(request), std::move(delegate), std::move(ipp_validator),
+      std::move(printer_installer), std::move(socket_manager));
+}
+
+}  // namespace cups_proxy
diff --git a/chrome/services/cups_proxy/proxy_manager.h b/chrome/services/cups_proxy/proxy_manager.h
new file mode 100644
index 0000000..ba3c599
--- /dev/null
+++ b/chrome/services/cups_proxy/proxy_manager.h
@@ -0,0 +1,59 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_CUPS_PROXY_PROXY_MANAGER_H_
+#define CHROME_SERVICES_CUPS_PROXY_PROXY_MANAGER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "chrome/services/cups_proxy/public/mojom/proxy.mojom.h"
+#include "chrome/services/ipp_parser/public/cpp/ipp_converter.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+
+namespace cups_proxy {
+
+class CupsProxyServiceDelegate;
+class IppValidator;
+class PrinterInstaller;
+class SocketManager;
+
+// mojom::CupsProxier handler.
+//
+// This handler's job is vetting incoming arbitrary CUPS IPP requests before
+// they reach the CUPS Daemon. Requests are parsed out-of-process, by the
+// CupsIppParser Service, and validated/rebuilt in-process before being proxied.
+// This handler must be created/accessed from a seqeunced context.
+//
+// Note: This handler only supports processing one request at a time; any
+// concurrent requests will immediately fail with an empty response.
+class ProxyManager : public mojom::CupsProxier {
+ public:
+  // Factory function.
+  static std::unique_ptr<ProxyManager> Create(
+      mojo::PendingReceiver<mojom::CupsProxier> request,
+      std::unique_ptr<CupsProxyServiceDelegate> delegate);
+
+  // Factory function that allows injected dependencies, for testing.
+  static std::unique_ptr<ProxyManager> CreateForTesting(
+      mojo::PendingReceiver<mojom::CupsProxier> request,
+      std::unique_ptr<CupsProxyServiceDelegate> delegate,
+      std::unique_ptr<IppValidator> ipp_validator,
+      std::unique_ptr<PrinterInstaller> printer_installer,
+      std::unique_ptr<SocketManager> socket_manager);
+
+  ~ProxyManager() override = default;
+
+  void ProxyRequest(const std::string& method,
+                    const std::string& url,
+                    const std::string& version,
+                    const std::vector<ipp_converter::HttpHeader>& headers,
+                    const std::vector<uint8_t>& body,
+                    ProxyRequestCallback cb) override = 0;
+};
+
+}  // namespace cups_proxy
+
+#endif  // CHROME_SERVICES_CUPS_PROXY_PROXY_MANAGER_H_
diff --git a/chrome/services/cups_proxy/public/cpp/BUILD.gn b/chrome/services/cups_proxy/public/cpp/BUILD.gn
index 9394f64..d51cbc3 100644
--- a/chrome/services/cups_proxy/public/cpp/BUILD.gn
+++ b/chrome/services/cups_proxy/public/cpp/BUILD.gn
@@ -6,9 +6,14 @@
 import("//printing/buildflags/buildflags.gni")
 
 assert(is_chromeos, "Non-Chrome-OS builds must not depend on this")
+assert(use_cups, "Non-CUPS builds must not depend on this")
 
 source_set("cpp") {
   sources = [
+    "cups_util.cc",
+    "cups_util.h",
+    "ipp_messages.cc",
+    "ipp_messages.h",
     "type_conversions.cc",
     "type_conversions.h",
   ]
@@ -17,33 +22,23 @@
     "//base",
   ]
 
-  if (use_cups) {
-    configs += [ "//printing:cups" ]
-    sources += [
-      "cups_util.cc",
-      "cups_util.h",
-      "ipp_messages.cc",
-      "ipp_messages.h",
-    ]
-    public_deps = [
-      "//chrome/services/ipp_parser/public/cpp",
-      "//printing",
-    ]
-  }
+  public_deps = [
+    "//chrome/services/ipp_parser/public/cpp",
+    "//printing",
+  ]
+
+  configs += [ "//printing:cups" ]
 }
 
 source_set("unit_tests") {
   testonly = true
-
-  if (use_cups) {
-    sources = [
-      "cups_util_unittest.cc",
-    ]
-    deps = [
-      ":cpp",
-      "//base",
-      "//testing/gmock",
-      "//testing/gtest",
-    ]
-  }
+  sources = [
+    "cups_util_unittest.cc",
+  ]
+  deps = [
+    ":cpp",
+    "//base",
+    "//testing/gmock",
+    "//testing/gtest",
+  ]
 }
diff --git a/chrome/services/cups_proxy/public/mojom/OWNERS b/chrome/services/cups_proxy/public/mojom/OWNERS
index 08850f4..ae29a36aa 100644
--- a/chrome/services/cups_proxy/public/mojom/OWNERS
+++ b/chrome/services/cups_proxy/public/mojom/OWNERS
@@ -1,2 +1,6 @@
 per-file *.mojom=set noparent
 per-file *.mojom=file://ipc/SECURITY_OWNERS
+per-file *_mojom_traits*.*=set noparent
+per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
+per-file *.typemap=set noparent
+per-file *.typemap=file://ipc/SECURITY_OWNERS
diff --git a/chrome/services/cups_proxy/public/mojom/proxy.mojom b/chrome/services/cups_proxy/public/mojom/proxy.mojom
index 24129b6..cf7951c1 100644
--- a/chrome/services/cups_proxy/public/mojom/proxy.mojom
+++ b/chrome/services/cups_proxy/public/mojom/proxy.mojom
@@ -5,6 +5,27 @@
 // NOTE: This mojom should be kept in sync with the copy in Chromium OS's repo
 // in TODO(crbug.com/945409): Update this with CrOS side mojom copy.
 
-module chromeos.printing.mojom;
+module cups_proxy.mojom;
 
-// TODO(crbug.com/945409): Add proxy method.
+struct HttpHeader {
+  string key;
+  string value;
+};
+
+// Implemented by the CupsProxyService; this service runs in the browser process
+// and supports serving CUPS printing requests from VMs on ChromeOS (currently
+// just PluginVM).
+interface CupsProxier {
+  // Expects, and strictly enforces, that |request| be a well-formatted IPP
+  // printing request. If so, the service integrates with the Chrome printing
+  // stack to correctly proxy it to the CUPS daemon, returning the resulting
+  // IPP response. Invalid IPP requests will generate an appropriate error IPP
+  // response.
+  //
+  // Note: This service expects only one request at a time; any further
+  // concurrent requests will fail with an empty IPP response, i.e. empty
+  // returned headers and ipp_message.
+  ProxyRequest@0(string method, string url, string version,
+                 array<HttpHeader> headers, array<uint8> body) =>
+                 (array<HttpHeader> headers, array<uint8> ipp_message);
+};
diff --git a/chrome/services/cups_proxy/public/mojom/proxy.typemap b/chrome/services/cups_proxy/public/mojom/proxy.typemap
new file mode 100644
index 0000000..ebb9f53
--- /dev/null
+++ b/chrome/services/cups_proxy/public/mojom/proxy.typemap
@@ -0,0 +1,16 @@
+mojom = "//chrome/services/cups_proxy/public/mojom/proxy.mojom"
+
+public_headers = [ "//chrome/services/ipp_parser/public/cpp/ipp_converter.h" ]
+
+traits_headers =
+    [ "//chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.h" ]
+
+sources = [
+  "//chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.cc",
+]
+
+public_deps = [
+  "//printing",
+]
+
+type_mappings = [ "cups_proxy.mojom.HttpHeader=ipp_converter::HttpHeader" ]
diff --git a/chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.cc b/chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.cc
new file mode 100644
index 0000000..bcfb878
--- /dev/null
+++ b/chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.cc
@@ -0,0 +1,18 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.h"
+
+namespace mojo {
+
+// static
+bool StructTraits<
+    cups_proxy::mojom::HttpHeaderDataView,
+    ipp_converter::HttpHeader>::Read(cups_proxy::mojom::HttpHeaderDataView data,
+                                     ipp_converter::HttpHeader* out_header) {
+  return data.ReadKey(&out_header->first) &&
+         data.ReadValue(&out_header->second);
+}
+
+}  // namespace mojo
diff --git a/chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.h b/chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.h
new file mode 100644
index 0000000..bf7a4a0
--- /dev/null
+++ b/chrome/services/cups_proxy/public/mojom/proxy_mojom_traits.h
@@ -0,0 +1,31 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_CUPS_PROXY_PUBLIC_MOJOM_PROXY_MOJOM_TRAITS_H_
+#define CHROME_SERVICES_CUPS_PROXY_PUBLIC_MOJOM_PROXY_MOJOM_TRAITS_H_
+
+#include "build/build_config.h"
+#include "chrome/services/cups_proxy/public/mojom/proxy.mojom.h"
+#include "chrome/services/ipp_parser/public/cpp/ipp_converter.h"
+
+namespace mojo {
+
+template <>
+class StructTraits<cups_proxy::mojom::HttpHeaderDataView,
+                   ipp_converter::HttpHeader> {
+ public:
+  static const std::string& key(const ipp_converter::HttpHeader& header) {
+    return header.first;
+  }
+  static const std::string& value(const ipp_converter::HttpHeader& header) {
+    return header.second;
+  }
+
+  static bool Read(cups_proxy::mojom::HttpHeaderDataView data,
+                   ipp_converter::HttpHeader* out_header);
+};
+
+}  // namespace mojo
+
+#endif  // CHROME_SERVICES_CUPS_PROXY_PUBLIC_MOJOM_PROXY_MOJOM_TRAITS_H_
diff --git a/chrome/services/cups_proxy/public/mojom/typemaps.gni b/chrome/services/cups_proxy/public/mojom/typemaps.gni
new file mode 100644
index 0000000..c9ed6967
--- /dev/null
+++ b/chrome/services/cups_proxy/public/mojom/typemaps.gni
@@ -0,0 +1,5 @@
+# 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.
+
+typemaps = [ "//chrome/services/cups_proxy/public/mojom/proxy.typemap" ]
diff --git a/chrome/services/cups_proxy/socket_manager.cc b/chrome/services/cups_proxy/socket_manager.cc
index d43c379..d45261954 100644
--- a/chrome/services/cups_proxy/socket_manager.cc
+++ b/chrome/services/cups_proxy/socket_manager.cc
@@ -75,7 +75,7 @@
   // Used for writing/reading the IPP request/response.
   scoped_refptr<net::DrainableIOBuffer> io_buffer;
 
-  std::vector<uint8_t> response;
+  std::unique_ptr<std::vector<uint8_t>> response;
   SocketManagerCallback cb;
 };
 
@@ -85,7 +85,7 @@
  public:
   explicit SocketManagerImpl(
       std::unique_ptr<net::UnixDomainClientSocket> socket,
-      base::WeakPtr<CupsProxyServiceDelegate> delegate);
+      CupsProxyServiceDelegate* const delegate);
   ~SocketManagerImpl() override;
 
   void ProxyToCups(std::vector<uint8_t> request,
@@ -128,7 +128,7 @@
 
 SocketManagerImpl::SocketManagerImpl(
     std::unique_ptr<net::UnixDomainClientSocket> socket,
-    base::WeakPtr<CupsProxyServiceDelegate> delegate)
+    CupsProxyServiceDelegate* const delegate)
     : main_runner_(base::SequencedTaskRunnerHandle::Get()),
       socket_runner_(delegate->GetIOTaskRunner()),
       socket_(std::move(socket)) {}
@@ -214,6 +214,7 @@
   }
 
   // Prime io_buffer for reading.
+  in_flight_->response = std::make_unique<std::vector<uint8_t>>();
   in_flight_->io_buffer = base::MakeRefCounted<net::DrainableIOBuffer>(
       base::MakeRefCounted<net::IOBuffer>(kHttpMaxBufferSize),
       kHttpMaxBufferSize);
@@ -245,10 +246,10 @@
   // Save new response data.
   std::copy(in_flight_->io_buffer->data(),
             in_flight_->io_buffer->data() + num_read,
-            std::back_inserter(in_flight_->response));
+            std::back_inserter(*in_flight_->response));
 
   // If more response left to read, read more.
-  if (!FinishedReadingResponse(in_flight_->response)) {
+  if (!FinishedReadingResponse(*in_flight_->response)) {
     return Read();
   }
 
@@ -262,9 +263,9 @@
   base::OnceClosure cb;
   if (success) {
     cb = base::BindOnce(std::move(in_flight_->cb),
-                        std::move(in_flight_->response));
+                        base::Passed(&in_flight_->response));
   } else {
-    cb = base::BindOnce(std::move(in_flight_->cb), base::nullopt);
+    cb = base::BindOnce(std::move(in_flight_->cb), nullptr);
   }
 
   // Post callback back to main sequence.
@@ -282,18 +283,17 @@
 }  // namespace
 
 std::unique_ptr<SocketManager> SocketManager::Create(
-    base::WeakPtr<CupsProxyServiceDelegate> delegate) {
+    CupsProxyServiceDelegate* const delegate) {
   return std::make_unique<SocketManagerImpl>(
       std::make_unique<net::UnixDomainClientSocket>(
           kCupsSocketPath, false /* not abstract namespace */),
-      std::move(delegate));
+      delegate);
 }
 
 std::unique_ptr<SocketManager> SocketManager::CreateForTesting(
     std::unique_ptr<net::UnixDomainClientSocket> socket,
-    base::WeakPtr<CupsProxyServiceDelegate> delegate) {
-  return std::make_unique<SocketManagerImpl>(std::move(socket),
-                                             std::move(delegate));
+    CupsProxyServiceDelegate* const delegate) {
+  return std::make_unique<SocketManagerImpl>(std::move(socket), delegate);
 }
 
 }  // namespace cups_proxy
diff --git a/chrome/services/cups_proxy/socket_manager.h b/chrome/services/cups_proxy/socket_manager.h
index 9423f899..2f12e1b 100644
--- a/chrome/services/cups_proxy/socket_manager.h
+++ b/chrome/services/cups_proxy/socket_manager.h
@@ -20,7 +20,7 @@
 namespace cups_proxy {
 
 using SocketManagerCallback =
-    base::OnceCallback<void(base::Optional<std::vector<uint8_t>>)>;
+    base::OnceCallback<void(std::unique_ptr<std::vector<uint8_t>>)>;
 
 // This manager proxies IPP requests to the CUPS daemon and asynchronously
 // responds with the IPP response. This class must be created and accessed
@@ -29,12 +29,12 @@
  public:
   // Factory function.
   static std::unique_ptr<SocketManager> Create(
-      base::WeakPtr<CupsProxyServiceDelegate> delegate);
+      CupsProxyServiceDelegate* const delegate);
 
   // Factory function that allows injected dependencies, for testing.
   static std::unique_ptr<SocketManager> CreateForTesting(
       std::unique_ptr<net::UnixDomainClientSocket> socket,
-      base::WeakPtr<CupsProxyServiceDelegate> delegate);
+      CupsProxyServiceDelegate* const delegate);
 
   virtual ~SocketManager() = default;
 
diff --git a/chrome/services/ipp_parser/BUILD.gn b/chrome/services/ipp_parser/BUILD.gn
index fa304ec..4ce37c9 100644
--- a/chrome/services/ipp_parser/BUILD.gn
+++ b/chrome/services/ipp_parser/BUILD.gn
@@ -17,7 +17,6 @@
   deps = [
     "//base",
     "//chrome:strings",
-    "//chrome/services/cups_proxy/public/cpp",
     "//mojo/public/cpp/bindings",
     "//net",
   ]
@@ -32,7 +31,10 @@
   if (enable_service) {
     configs += [ "//printing:cups" ]
     sources += [ "ipp_parser.cc" ]
-    deps += [ "//chrome/services/ipp_parser/public/cpp" ]
+    deps += [
+      "//chrome/services/cups_proxy/public/cpp",
+      "//chrome/services/ipp_parser/public/cpp",
+    ]
   } else {
     sources += [ "fake_ipp_parser.cc" ]
   }
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 9a47c39..e0ec8df2 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -3090,7 +3090,6 @@
     "../browser/performance_manager/persistence/site_data/unittest_utils.h",
     "../browser/performance_manager/render_process_host_proxy_unittest.cc",
     "../browser/performance_manager/web_contents_proxy_unittest.cc",
-    "../browser/performance_manager/webui_graph_dump_impl_unittest.cc",
     "../browser/performance_monitor/metric_evaluator_helper_win_unittest.cc",
     "../browser/performance_monitor/system_monitor_unittest.cc",
     "../browser/permissions/chooser_context_base_mock_permission_observer.cc",
@@ -3351,6 +3350,7 @@
       "../browser/ui/thumbnails/thumbnail_utils_unittest.cc",
       "../browser/ui/toolbar/app_menu_icon_controller_unittest.cc",
       "../browser/ui/webui/devtools_ui_data_source_unittest.cc",
+      "../browser/ui/webui/discards/webui_graph_dump_impl_unittest.cc",
       "../browser/ui/webui/favicon_source_unittest.cc",
       "../browser/webauthn/authenticator_request_scheduler_unittest.cc",
       "../browser/webauthn/chrome_authenticator_request_delegate_unittest.cc",
@@ -3684,6 +3684,7 @@
       "../browser/media_galleries/media_galleries_preferences_unittest.cc",
       "../browser/media_galleries/win/mtp_device_delegate_impl_win_unittest.cc",
       "../browser/media_galleries/win/mtp_device_object_enumerator_unittest.cc",
+      "../browser/memory/enterprise_memory_limit_evaluator_unittest.cc",
       "../browser/memory/memory_pressure_monitor_utils_unittest.cc",
       "../browser/memory/memory_pressure_monitor_win_unittest.cc",
       "../browser/memory/swap_thrashing_monitor_delegate_win_unittest.cc",
@@ -4771,12 +4772,14 @@
   if (is_chromeos) {
     deps += [
       "//chrome/browser/chromeos:unit_tests",
-      "//chrome/services/cups_proxy:unit_tests",
       "//chromeos/ime:gencode",
     ]
     data_deps += [ "//testing/buildbot/filters:chromeos_filters" ]
     sources -=
         [ "../browser/policy/cloud/user_policy_signin_service_unittest.cc" ]
+    if (use_cups) {
+      deps += [ "//chrome/services/cups_proxy:unit_tests" ]
+    }
   }
   if (use_x11) {
     deps += [ "//ui/events/devices" ]
diff --git a/chrome/test/base/in_process_browser_test_mac.mm b/chrome/test/base/in_process_browser_test_mac.mm
index a31ac2c..1daaaee 100644
--- a/chrome/test/base/in_process_browser_test_mac.mm
+++ b/chrome/test/base/in_process_browser_test_mac.mm
@@ -4,7 +4,6 @@
 
 #include "chrome/test/base/in_process_browser_test.h"
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "chrome/browser/devtools/devtools_window.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -18,26 +17,26 @@
     content::WebContents* web_contents) {
   // Opening a Devtools Window can cause AppKit to throw objects into the
   // autorelease pool. Flush the pool when this function returns.
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  ASSERT_FALSE(content::DevToolsAgentHost::HasFor(web_contents));
-  DevToolsWindow::OpenDevToolsWindow(web_contents);
-  ASSERT_TRUE(content::DevToolsAgentHost::HasFor(web_contents));
+  @autoreleasepool {
+    ASSERT_FALSE(content::DevToolsAgentHost::HasFor(web_contents));
+    DevToolsWindow::OpenDevToolsWindow(web_contents);
+    ASSERT_TRUE(content::DevToolsAgentHost::HasFor(web_contents));
+  }
 }
 
 Browser* InProcessBrowserTest::OpenURLOffTheRecord(Profile* profile,
                                                    const GURL& url) {
   // Opening an incognito window can cause AppKit to throw objects into the
   // autorelease pool. Flush the pool when this function returns.
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  chrome::OpenURLOffTheRecord(profile, url);
-  Browser* browser =
-      chrome::FindTabbedBrowser(profile->GetOffTheRecordProfile(), false);
-  content::TestNavigationObserver observer(
-      browser->tab_strip_model()->GetActiveWebContents());
-  observer.Wait();
-  return browser;
+  @autoreleasepool {
+    chrome::OpenURLOffTheRecord(profile, url);
+    Browser* browser =
+        chrome::FindTabbedBrowser(profile->GetOffTheRecordProfile(), false);
+    content::TestNavigationObserver observer(
+        browser->tab_strip_model()->GetActiveWebContents());
+    observer.Wait();
+    return browser;
+  }
 }
 
 // Creates a browser with a single tab (about:blank), waits for the tab to
@@ -45,48 +44,48 @@
 Browser* InProcessBrowserTest::CreateBrowser(Profile* profile) {
   // Making a browser window can cause AppKit to throw objects into the
   // autorelease pool. Flush the pool when this function returns.
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  Browser* browser = new Browser(Browser::CreateParams(profile, true));
-  AddBlankTabAndShow(browser);
-  return browser;
+  @autoreleasepool {
+    Browser* browser = new Browser(Browser::CreateParams(profile, true));
+    AddBlankTabAndShow(browser);
+    return browser;
+  }
 }
 
 Browser* InProcessBrowserTest::CreateIncognitoBrowser(Profile* profile) {
   // Making a browser window can cause AppKit to throw objects into the
   // autorelease pool. Flush the pool when this function returns.
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    // Use active profile if default nullptr was passed.
+    if (!profile)
+      profile = browser()->profile();
 
-  // Use active profile if default nullptr was passed.
-  if (!profile)
-    profile = browser()->profile();
-
-  // Create a new browser with using the incognito profile.
-  Browser* incognito = new Browser(
-      Browser::CreateParams(profile->GetOffTheRecordProfile(), true));
-  AddBlankTabAndShow(incognito);
-  return incognito;
+    // Create a new browser with using the incognito profile.
+    Browser* incognito = new Browser(
+        Browser::CreateParams(profile->GetOffTheRecordProfile(), true));
+    AddBlankTabAndShow(incognito);
+    return incognito;
+  }
 }
 
 Browser* InProcessBrowserTest::CreateBrowserForPopup(Profile* profile) {
   // Making a browser window can cause AppKit to throw objects into the
   // autorelease pool. Flush the pool when this function returns.
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  Browser* browser =
-      new Browser(Browser::CreateParams(Browser::TYPE_POPUP, profile, true));
-  AddBlankTabAndShow(browser);
-  return browser;
+  @autoreleasepool {
+    Browser* browser =
+        new Browser(Browser::CreateParams(Browser::TYPE_POPUP, profile, true));
+    AddBlankTabAndShow(browser);
+    return browser;
+  }
 }
 
 Browser* InProcessBrowserTest::CreateBrowserForApp(const std::string& app_name,
                                                    Profile* profile) {
   // Making a browser window can cause AppKit to throw objects into the
   // autorelease pool. Flush the pool when this function returns.
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  Browser* browser = new Browser(Browser::CreateParams::CreateForApp(
-      app_name, false /* trusted_source */, gfx::Rect(), profile, true));
-  AddBlankTabAndShow(browser);
-  return browser;
+  @autoreleasepool {
+    Browser* browser = new Browser(Browser::CreateParams::CreateForApp(
+        app_name, false /* trusted_source */, gfx::Rect(), profile, true));
+    AddBlankTabAndShow(browser);
+    return browser;
+  }
 }
diff --git a/chrome/test/chromedriver/chrome/navigation_tracker.cc b/chrome/test/chromedriver/chrome/navigation_tracker.cc
index 8d8b51c..754f3d798 100644
--- a/chrome/test/chromedriver/chrome/navigation_tracker.cc
+++ b/chrome/test/chromedriver/chrome/navigation_tracker.cc
@@ -19,6 +19,9 @@
     "chrome-extension://aapnijgdinlhnhlmodcfapnahmbfebeb/"
     "_generated_background_page.html";
 
+// Match to content/browser/devtools/devTools_session const of same name
+const char kTargetClosedMessage[] = "Inspected target navigated or closed";
+
 Status MakeNavigationCheckFailedStatus(Status command_status) {
   if (command_status.code() == kUnexpectedAlertOpen)
     return Status(kUnexpectedAlertOpen);
@@ -261,8 +264,18 @@
     base::DictionaryValue params;
     params.SetString("expression", "document.URL");
     std::unique_ptr<base::DictionaryValue> result;
-    Status status = client_->SendCommandAndGetResultWithTimeout(
-        "Runtime.evaluate", params, &command_timeout, &result);
+    Status status(kOk);
+    for (int attempt = 0; attempt < 3; attempt++) {
+      status = client_->SendCommandAndGetResultWithTimeout(
+          "Runtime.evaluate", params, &command_timeout, &result);
+      if (status.code() == kUnknownError &&
+          status.message().find(kTargetClosedMessage) != std::string::npos) {
+        continue;
+      } else {
+        break;
+      }
+    }
+
     std::string url;
     if (status.IsError() || !result->GetString("result.value", &url))
       return MakeNavigationCheckFailedStatus(status);
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index 0158f52..def91fa 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -103,8 +103,6 @@
 _OS_SPECIFIC_FILTER['linux'] = [
     # https://bugs.chromium.org/p/chromium/issues/detail?id=1000530
     'ChromeDriverTest.testActionsMouseMove',
-    # https://bugs.chromium.org/p/chromium/issues/detail?id=999261
-    'ChromeDriverTest.testGoBackAndGoForward',
 ]
 _OS_SPECIFIC_FILTER['mac'] = [
     # https://bugs.chromium.org/p/chromedriver/issues/detail?id=1927
@@ -116,8 +114,6 @@
     'ChromeDownloadDirTest.testFileDownloadWithGetHeadless',
     # https://bugs.chromium.org/p/chromium/issues/detail?id=1000530
     'ChromeDriverTest.testActionsMouseMove',
-    # https://bugs.chromium.org/p/chromium/issues/detail?id=999261
-    'ChromeDriverTest.testGoBackAndGoForward',
 ]
 
 _DESKTOP_NEGATIVE_FILTER = [
diff --git a/chrome/test/chromedriver/test/run_py_tests.pydeps b/chrome/test/chromedriver/test/run_py_tests.pydeps
index c789d1d6..86ec1d2 100644
--- a/chrome/test/chromedriver/test/run_py_tests.pydeps
+++ b/chrome/test/chromedriver/test/run_py_tests.pydeps
@@ -40,6 +40,7 @@
 ../../../../third_party/catapult/devil/devil/android/sdk/aapt.py
 ../../../../third_party/catapult/devil/devil/android/sdk/adb_wrapper.py
 ../../../../third_party/catapult/devil/devil/android/sdk/build_tools.py
+../../../../third_party/catapult/devil/devil/android/sdk/bundletool.py
 ../../../../third_party/catapult/devil/devil/android/sdk/intent.py
 ../../../../third_party/catapult/devil/devil/android/sdk/keyevent.py
 ../../../../third_party/catapult/devil/devil/android/sdk/split_select.py
diff --git a/chrome/test/chromedriver/window_commands.cc b/chrome/test/chromedriver/window_commands.cc
index 7a22976..9a6cf84f 100644
--- a/chrome/test/chromedriver/window_commands.cc
+++ b/chrome/test/chromedriver/window_commands.cc
@@ -1334,6 +1334,9 @@
           GetOptionalInt(action, "duration", &duration);
           tick_duration = std::max(tick_duration, duration);
         } else {
+          bool async_dispatch_event = false;
+          GetOptionalBool(action, "asyncDispatch", &async_dispatch_event);
+
           if (type == "key") {
             std::list<KeyEvent> dispatch_key_events;
             KeyEventBuilder builder;
@@ -1353,8 +1356,8 @@
                 session->sticky_modifiers &= ~KeyToKeyModifiers(event.key);
               }
 
-              Status status =
-                  web_view->DispatchKeyEvents(dispatch_key_events, true);
+              Status status = web_view->DispatchKeyEvents(dispatch_key_events,
+                                                          async_dispatch_event);
               if (status.IsError())
                 return status;
             }
@@ -1446,7 +1449,8 @@
               }
               dispatch_mouse_events.push_back(event);
               Status status = web_view->DispatchMouseEvents(
-                  dispatch_mouse_events, session->GetCurrentFrameId(), true);
+                  dispatch_mouse_events, session->GetCurrentFrameId(),
+                  async_dispatch_event);
               if (status.IsError())
                 return status;
             } else if (pointer_type == "touch") {
@@ -1463,7 +1467,8 @@
                 action_input_states[j]->SetInteger("pressed", 0);
               }
               if (has_touch_start[id]) {
-                Status status = web_view->DispatchTouchEvent(event, true);
+                Status status =
+                    web_view->DispatchTouchEvent(event, async_dispatch_event);
                 if (status.IsError())
                   return status;
               }
diff --git a/chrome/test/data/webui/settings/category_setting_exceptions_tests.js b/chrome/test/data/webui/settings/category_setting_exceptions_tests.js
index ea4fc61..d0ea9e8 100644
--- a/chrome/test/data/webui/settings/category_setting_exceptions_tests.js
+++ b/chrome/test/data/webui/settings/category_setting_exceptions_tests.js
@@ -97,4 +97,78 @@
       }
     });
   });
+
+  test(
+      'all lists are read-only if the default policy is set by policy',
+      function() {
+        PolymerTest.clearBody();
+        const policyPref = test_util.createSiteSettingsPrefs(
+            [
+              test_util.createContentSettingTypeToValuePair(
+                  settings.ContentSettingsTypes.COOKIES,
+                  test_util.createDefaultContentSetting({
+                    setting: settings.ContentSetting.ALLOW,
+                    source: settings.SiteSettingSource.POLICY
+                  })),
+            ],
+            []);
+        browserProxy.reset();
+        browserProxy.setPrefs(policyPref);
+
+        // Creates a new category-setting-exceptions element to that it is
+        // initialized with the right value.
+        testElement = document.createElement('category-setting-exceptions');
+        testElement.category = settings.ContentSettingsTypes.COOKIES;
+        document.body.appendChild(testElement);
+
+        const initializationTest =
+            browserProxy.whenCalled('getDefaultValueForContentType')
+                .then(function() {
+                  // Flush the container to ensure that the container is
+                  // populated.
+                  Polymer.dom.flush();
+
+                  assertTrue(testElement.getReadOnlyList_());
+                  assertTrue(testElement.defaultManaged_);
+
+                  // Make sure that the Allow and Session Only site lists are
+                  // hidden.
+                  const siteListElements =
+                      testElement.shadowRoot.querySelectorAll('site-list');
+                  siteListElements.forEach(element => {
+                    assertTrue(!!element.readOnlyList);
+                  });
+                });
+
+        const dummyPref = test_util.createSiteSettingsPrefs(
+            [
+              test_util.createContentSettingTypeToValuePair(
+                  settings.ContentSettingsTypes.COOKIES,
+                  test_util.createDefaultContentSetting({
+                    setting: settings.ContentSetting.ALLOW,
+                  })),
+            ],
+            []);
+        browserProxy.setPrefs(dummyPref);
+
+        const updateTest =
+            browserProxy.whenCalled('getDefaultValueForContentType')
+                .then(function() {
+                  // Flush the container to ensure that the container is
+                  // populated.
+                  Polymer.dom.flush();
+
+                  assertFalse(testElement.getReadOnlyList_());
+                  assertFalse(testElement.defaultManaged_);
+
+                  // Make sure that the Allow and Session Only site lists are
+                  // hidden.
+                  const siteListElements =
+                      testElement.shadowRoot.querySelectorAll('site-list');
+                  siteListElements.forEach(element => {
+                    assertTrue(!element.readOnlyList);
+                  });
+                });
+        return Promise.all([initializationTest, updateTest]);
+      });
 });
diff --git a/chrome/test/data/webui/tab_strip/tab_list_test.js b/chrome/test/data/webui/tab_strip/tab_list_test.js
index 52d7228e..f5a6f2c 100644
--- a/chrome/test/data/webui/tab_strip/tab_list_test.js
+++ b/chrome/test/data/webui/tab_strip/tab_list_test.js
@@ -252,4 +252,46 @@
     assertEquals(tabElementsBeforeMove[1], tabElementsAfterMove[0]);
     assertEquals(tabElementsBeforeMove[2], tabElementsAfterMove[1]);
   });
+
+  test('activating a tab off-screen scrolls to it', async () => {
+    const scrollPadding = 32;
+
+    // Mock the width of each tab element
+    const tabElements = getUnpinnedTabs();
+    tabElements.forEach((tabElement) => {
+      tabElement.style.width = '200px';
+    });
+
+    // Mock the scrolling parent such that it cannot fit only 1 tab at a time
+    const fakeScroller = {
+      offsetWidth: 300,
+      scrollLeft: 0,
+    };
+    tabList.scrollingParent_ = fakeScroller;
+
+    // The 2nd tab should be off-screen to the right, so activating it should
+    // scroll so that the element's right edge is aligned with the screen's
+    // right edge
+    callbackRouter.onActivated.dispatchEvent({
+      tabId: currentWindow.tabs[1].id,
+      windowId: currentWindow.id,
+    });
+    let activeTab = getUnpinnedTabs()[1];
+    await tabList.animationPromises;
+    assertEquals(
+        fakeScroller.scrollLeft,
+        activeTab.offsetLeft + activeTab.offsetWidth -
+            fakeScroller.offsetWidth + scrollPadding);
+
+    // The 1st tab should be now off-screen to the left, so activating it should
+    // scroll so that the element's left edge is aligned with the screen's
+    // left edge
+    callbackRouter.onActivated.dispatchEvent({
+      tabId: currentWindow.tabs[0].id,
+      windowId: currentWindow.id,
+    });
+    activeTab = getUnpinnedTabs()[0];
+    await tabList.animationPromises;
+    assertEquals(fakeScroller.scrollLeft, activeTab.offsetLeft - scrollPadding);
+  });
 });
diff --git a/chrome/tools/build/mac/infoplist_strings_util.mm b/chrome/tools/build/mac/infoplist_strings_util.mm
index 8f8102c5..45e4c8d7 100644
--- a/chrome/tools/build/mac/infoplist_strings_util.mm
+++ b/chrome/tools/build/mac/infoplist_strings_util.mm
@@ -17,7 +17,6 @@
 #include "base/files/file_util.h"
 #include "base/i18n/icu_util.h"
 #include "base/i18n/message_formatter.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/strings/string_piece.h"
 #include "base/strings/string_util.h"
@@ -154,188 +153,188 @@
 }  // namespace
 
 int main(int argc, char* const argv[]) {
-  base::mac::ScopedNSAutoreleasePool autorelease_pool;
+  @autoreleasepool {
+    const char* version_file_path = NULL;
+    const char* grit_output_dir = NULL;
+    const char* branding_strings_name = NULL;
+    const char* output_dir = NULL;
+    const char* app_type = kAppType_Main;
 
-  const char* version_file_path = NULL;
-  const char* grit_output_dir = NULL;
-  const char* branding_strings_name = NULL;
-  const char* output_dir = NULL;
-  const char* app_type = kAppType_Main;
-
-  // Process the args
-  int ch;
-  while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
-    switch (ch) {
-      case 't':
-        app_type = optarg;
-        break;
-      case 'v':
-        version_file_path = optarg;
-        break;
-      case 'g':
-        grit_output_dir = optarg;
-        break;
-      case 'b':
-        branding_strings_name = optarg;
-        break;
-      case 'o':
-        output_dir = optarg;
-        break;
-      default:
-        fprintf(stderr, "ERROR: bad command line arg\n");
-        exit(1);
-        break;
+    // Process the args
+    int ch;
+    while ((ch = getopt(argc, argv, "t:v:g:b:o:")) != -1) {
+      switch (ch) {
+        case 't':
+          app_type = optarg;
+          break;
+        case 'v':
+          version_file_path = optarg;
+          break;
+        case 'g':
+          grit_output_dir = optarg;
+          break;
+        case 'b':
+          branding_strings_name = optarg;
+          break;
+        case 'o':
+          output_dir = optarg;
+          break;
+        default:
+          fprintf(stderr, "ERROR: bad command line arg\n");
+          exit(1);
+          break;
+      }
     }
-  }
-  argc -= optind;
-  argv += optind;
+    argc -= optind;
+    argv += optind;
 
 #define CHECK_ARG(a, b) \
-  do { \
-    if ((a)) { \
-      fprintf(stderr, "ERROR: " b "\n"); \
-      exit(1); \
-    } \
-  } while (false)
+    do { \
+      if ((a)) { \
+        fprintf(stderr, "ERROR: " b "\n"); \
+        exit(1); \
+      } \
+    } while (false)
 
-  // Check our args
-  CHECK_ARG(!version_file_path, "Missing VERSION file path");
-  CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
-  CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
-  CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
-  CHECK_ARG(argc == 0, "Missing language list");
-  CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
-             strcmp(app_type, kAppType_Helper) != 0),
-            "Unknown app type");
+    // Check our args
+    CHECK_ARG(!version_file_path, "Missing VERSION file path");
+    CHECK_ARG(!grit_output_dir, "Missing grit output dir path");
+    CHECK_ARG(!output_dir, "Missing path to write InfoPlist.strings files");
+    CHECK_ARG(!branding_strings_name, "Missing branding strings file name");
+    CHECK_ARG(argc == 0, "Missing language list");
+    CHECK_ARG((strcmp(app_type, kAppType_Main) != 0 &&
+               strcmp(app_type, kAppType_Helper) != 0),
+              "Unknown app type");
 
-  char* const* lang_list = argv;
-  int lang_list_count = argc;
+    char* const* lang_list = argv;
+    int lang_list_count = argc;
 
-  base::i18n::InitializeICU();
+    base::i18n::InitializeICU();
 
-  // Parse the version file and build our string
-  NSString* version_string = ApplicationVersionString(version_file_path);
-  if (!version_string) {
-    fprintf(stderr, "ERROR: failed to get a version string");
-    exit(1);
-  }
-
-  NSFileManager* fm = [NSFileManager defaultManager];
-
-  for (int loop = 0; loop < lang_list_count; ++loop) {
-    const char* cur_lang = lang_list[loop];
-
-    // Open the branded string pak file
-    std::unique_ptr<ui::DataPack> branded_data_pack(
-        LoadResourceDataPack(grit_output_dir, branding_strings_name, cur_lang));
-    if (branded_data_pack.get() == NULL) {
-      fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
-              cur_lang);
+    // Parse the version file and build our string
+    NSString* version_string = ApplicationVersionString(version_file_path);
+    if (!version_string) {
+      fprintf(stderr, "ERROR: failed to get a version string");
       exit(1);
     }
 
-    uint32_t name_id = IDS_PRODUCT_NAME;
-    const char* name_id_str = "IDS_PRODUCT_NAME";
-    uint32_t short_name_id = IDS_APP_MENU_PRODUCT_NAME;
-    const char* short_name_id_str = "IDS_APP_MENU_PRODUCT_NAME";
-    if (strcmp(app_type, kAppType_Helper) == 0) {
-      name_id = IDS_HELPER_NAME;
-      name_id_str = "IDS_HELPER_NAME";
-      short_name_id = IDS_SHORT_HELPER_NAME;
-      short_name_id_str = "IDS_SHORT_HELPER_NAME";
-    }
+    NSFileManager* fm = [NSFileManager defaultManager];
 
-    // Fetch the strings.
-    NSString* name =
+    for (int loop = 0; loop < lang_list_count; ++loop) {
+      const char* cur_lang = lang_list[loop];
+
+      // Open the branded string pak file
+      std::unique_ptr<ui::DataPack> branded_data_pack(
+          LoadResourceDataPack(grit_output_dir, branding_strings_name, cur_lang));
+      if (branded_data_pack.get() == NULL) {
+        fprintf(stderr, "ERROR: Failed to load branded pak for language: %s\n",
+                cur_lang);
+        exit(1);
+      }
+
+      uint32_t name_id = IDS_PRODUCT_NAME;
+      const char* name_id_str = "IDS_PRODUCT_NAME";
+      uint32_t short_name_id = IDS_APP_MENU_PRODUCT_NAME;
+      const char* short_name_id_str = "IDS_APP_MENU_PRODUCT_NAME";
+      if (strcmp(app_type, kAppType_Helper) == 0) {
+        name_id = IDS_HELPER_NAME;
+        name_id_str = "IDS_HELPER_NAME";
+        short_name_id = IDS_SHORT_HELPER_NAME;
+        short_name_id_str = "IDS_SHORT_HELPER_NAME";
+      }
+
+      // Fetch the strings.
+      NSString* name =
+            LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
+                                   name_id, name_id_str);
+      NSString* short_name =
+            LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
+                                   short_name_id, short_name_id_str);
+      NSString* copyright_format =
           LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
-                                 name_id, name_id_str);
-    NSString* short_name =
+                                 IDS_ABOUT_VERSION_COPYRIGHT,
+                                 "IDS_ABOUT_VERSION_COPYRIGHT");
+
+      NSString* copyright = base::SysUTF16ToNSString(
+          base::i18n::MessageFormatter::FormatWithNumberedArgs(
+              base::SysNSStringToUTF16(copyright_format), base::Time::Now()));
+
+      NSString* permission_reason =
           LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
-                                 short_name_id, short_name_id_str);
-    NSString* copyright_format =
-        LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
-                               IDS_ABOUT_VERSION_COPYRIGHT,
-                               "IDS_ABOUT_VERSION_COPYRIGHT");
+                                 IDS_RUNTIME_PERMISSION_OS_REASON_TEXT,
+                                 "IDS_RUNTIME_PERMISSION_OS_REASON_TEXT");
 
-    NSString* copyright = base::SysUTF16ToNSString(
-        base::i18n::MessageFormatter::FormatWithNumberedArgs(
-            base::SysNSStringToUTF16(copyright_format), base::Time::Now()));
+      // For now, assume this is ok for all languages. If we need to, this could
+      // be moved into generated_resources.grd and fetched.
+      NSString* get_info = [NSString
+          stringWithFormat:@"%@ %@, %@", name, version_string, copyright];
 
-    NSString* permission_reason =
-        LoadStringFromDataPack(branded_data_pack.get(), cur_lang,
-                               IDS_RUNTIME_PERMISSION_OS_REASON_TEXT,
-                               "IDS_RUNTIME_PERMISSION_OS_REASON_TEXT");
+      // Generate the InfoPlist.strings file contents.
+      NSDictionary<NSString*, NSString*>* infoplist_strings = @{
+        @"CFBundleDisplayName" : name,
+        @"CFBundleGetInfoString" : get_info,
+        @"CFBundleName" : short_name,
+        @"NSHumanReadableCopyright" : copyright,
+        @"NSLocationUsageDescription" : permission_reason,
+        @"NSMicrophoneUsageDescription" : permission_reason,
+        @"NSCameraUsageDescription" : permission_reason,
+        @"NSBluetoothPeripheralUsageDescription" : permission_reason,
+      };
+      base::scoped_nsobject<NSMutableString> strings_file_contents_string(
+          [[NSMutableString alloc] init]);
+      for (NSString* key in infoplist_strings) {
+        [strings_file_contents_string
+            appendFormat:@"%@ = \"%@\";\n", key,
+                         EscapeForStringsFileValue(infoplist_strings[key])];
+      }
 
-    // For now, assume this is ok for all languages. If we need to, this could
-    // be moved into generated_resources.grd and fetched.
-    NSString* get_info = [NSString
-        stringWithFormat:@"%@ %@, %@", name, version_string, copyright];
+      // We set up Xcode projects expecting strings files to be UTF8, so make
+      // sure we write the data in that form.  When Xcode copies them it will
+      // put them final runtime encoding.
+      NSData* strings_file_contents_utf8 =
+          [strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
 
-    // Generate the InfoPlist.strings file contents.
-    NSDictionary<NSString*, NSString*>* infoplist_strings = @{
-      @"CFBundleDisplayName" : name,
-      @"CFBundleGetInfoString" : get_info,
-      @"CFBundleName" : short_name,
-      @"NSHumanReadableCopyright" : copyright,
-      @"NSLocationUsageDescription" : permission_reason,
-      @"NSMicrophoneUsageDescription" : permission_reason,
-      @"NSCameraUsageDescription" : permission_reason,
-      @"NSBluetoothPeripheralUsageDescription" : permission_reason,
-    };
-    base::scoped_nsobject<NSMutableString> strings_file_contents_string(
-        [[NSMutableString alloc] init]);
-    for (NSString* key in infoplist_strings) {
-      [strings_file_contents_string
-          appendFormat:@"%@ = \"%@\";\n", key,
-                       EscapeForStringsFileValue(infoplist_strings[key])];
+      if ([strings_file_contents_utf8 length] == 0) {
+        fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
+                "file for language: %s\n", cur_lang);
+        exit(1);
+      }
+
+      // For Cocoa to find the locale at runtime, it needs to use '_' instead of
+      // '-' (http://crbug.com/20441).  Also, 'en-US' should be represented
+      // simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
+      NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
+      if ([cur_lang_ns isEqualToString:@"en-US"]) {
+        cur_lang_ns = @"en";
+      }
+      cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
+                                                           withString:@"_"];
+      // Make sure the lproj we write to exists
+      NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
+      NSString *output_path =
+          [[NSString stringWithUTF8String:output_dir]
+           stringByAppendingPathComponent:lproj_name];
+      NSError* error = nil;
+      if (![fm fileExistsAtPath:output_path] &&
+          ![fm createDirectoryAtPath:output_path
+          withIntermediateDirectories:YES
+                          attributes:nil
+                               error:&error]) {
+        fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
+                [output_path UTF8String]);
+        exit(1);
+      }
+
+      // Write out the file
+      output_path =
+          [output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
+      if (![strings_file_contents_utf8 writeToFile:output_path
+                                        atomically:YES]) {
+        fprintf(stderr, "ERROR: Failed to write out '%s'\n",
+                [output_path UTF8String]);
+        exit(1);
+      }
     }
-
-    // We set up Xcode projects expecting strings files to be UTF8, so make
-    // sure we write the data in that form.  When Xcode copies them it will
-    // put them final runtime encoding.
-    NSData* strings_file_contents_utf8 =
-        [strings_file_contents_string dataUsingEncoding:NSUTF8StringEncoding];
-
-    if ([strings_file_contents_utf8 length] == 0) {
-      fprintf(stderr, "ERROR: failed to get the utf8 encoding of the strings "
-              "file for language: %s\n", cur_lang);
-      exit(1);
-    }
-
-    // For Cocoa to find the locale at runtime, it needs to use '_' instead of
-    // '-' (http://crbug.com/20441).  Also, 'en-US' should be represented
-    // simply as 'en' (http://crbug.com/19165, http://crbug.com/25578).
-    NSString* cur_lang_ns = [NSString stringWithUTF8String:cur_lang];
-    if ([cur_lang_ns isEqualToString:@"en-US"]) {
-      cur_lang_ns = @"en";
-    }
-    cur_lang_ns = [cur_lang_ns stringByReplacingOccurrencesOfString:@"-"
-                                                         withString:@"_"];
-    // Make sure the lproj we write to exists
-    NSString *lproj_name = [NSString stringWithFormat:@"%@.lproj", cur_lang_ns];
-    NSString *output_path =
-        [[NSString stringWithUTF8String:output_dir]
-         stringByAppendingPathComponent:lproj_name];
-    NSError* error = nil;
-    if (![fm fileExistsAtPath:output_path] &&
-        ![fm createDirectoryAtPath:output_path
-        withIntermediateDirectories:YES
-                        attributes:nil
-                             error:&error]) {
-      fprintf(stderr, "ERROR: '%s' didn't exist or we failed to create it\n",
-              [output_path UTF8String]);
-      exit(1);
-    }
-
-    // Write out the file
-    output_path =
-        [output_path stringByAppendingPathComponent:@"InfoPlist.strings"];
-    if (![strings_file_contents_utf8 writeToFile:output_path
-                                      atomically:YES]) {
-      fprintf(stderr, "ERROR: Failed to write out '%s'\n",
-              [output_path UTF8String]);
-      exit(1);
-    }
+    return 0;
   }
-  return 0;
 }
diff --git a/chrome/typemaps.gni b/chrome/typemaps.gni
index dbf801e..24fe700 100644
--- a/chrome/typemaps.gni
+++ b/chrome/typemaps.gni
@@ -7,6 +7,7 @@
   "//chrome/common/mac/app_shim.typemap",
   "//chrome/common/search.typemap",
   "//chrome/common/web_application_info_provider.typemap",
+  "//chrome/services/cups_proxy/public/mojom/proxy.typemap",
   "//chrome/services/file_util/public/mojom/safe_archive_analyzer.typemap",
   "//chrome/services/media_gallery_util/public/mojom/media_parser.typemap",
   "//chrome/services/printing/public/mojom/pdf_render_settings.typemap",
diff --git a/chromeos/components/drivefs/OWNERS b/chromeos/components/drivefs/OWNERS
index ca823864..ba9c1e0 100644
--- a/chromeos/components/drivefs/OWNERS
+++ b/chromeos/components/drivefs/OWNERS
@@ -6,3 +6,4 @@
 per-file *_mojom_traits*.*=file://ipc/SECURITY_OWNERS
 per-file *.typemap=set noparent
 per-file *.typemap=file://ipc/SECURITY_OWNERS
+# COMPONENT: Platform>Apps>FileManager
diff --git a/chromeos/dbus/fake_cicerone_client.h b/chromeos/dbus/fake_cicerone_client.h
index 23729c4f8..0b381411 100644
--- a/chromeos/dbus/fake_cicerone_client.h
+++ b/chromeos/dbus/fake_cicerone_client.h
@@ -22,73 +22,33 @@
   FakeCiceroneClient();
   ~FakeCiceroneClient() override;
 
-  // CiceroneClient overrides:
+  // CiceroneClient:
   void AddObserver(Observer* observer) override;
   void RemoveObserver(Observer* observer) override;
-
-  // IsContainerStartedSignalConnected must return true before StartContainer
-  // is called.
   bool IsContainerStartedSignalConnected() override;
-
-  // IsContainerShutdownSignalConnected must return true before StartContainer
-  // is called.
   bool IsContainerShutdownSignalConnected() override;
-
-  // This should be true prior to calling InstallLinuxPackage.
   bool IsInstallLinuxPackageProgressSignalConnected() override;
-
-  // This should be true prior to calling UninstallPackageOwningFile.
   bool IsUninstallPackageProgressSignalConnected() override;
-
-  // This should be true prior to calling CreateLxdContainer or
-  // StartLxdContainer.
   bool IsLxdContainerCreatedSignalConnected() override;
-
-  // This should be true prior to calling DeleteLxdContainer.
   bool IsLxdContainerDeletedSignalConnected() override;
-
-  // This should be true prior to calling CreateLxdContainer or
-  // StartLxdContainer.
   bool IsLxdContainerDownloadingSignalConnected() override;
-
-  // This should be true prior to calling CreateLxdContainer or
-  // StartLxdContainer.
   bool IsTremplinStartedSignalConnected() override;
-
-  // This should be true prior to calling StartLxdContainer in async mode.
   bool IsLxdContainerStartingSignalConnected() override;
-
-  // This should be true prior to calling ExportLxdContainer.
   bool IsExportLxdContainerProgressSignalConnected() override;
-
-  // This should be true prior to calling ImportLxdContainer.
   bool IsImportLxdContainerProgressSignalConnected() override;
-
-  // This should be true before expecting to recieve
-  // PendingAppListUpdatesSignal.
   bool IsPendingAppListUpdatesSignalConnected() override;
-
-  // Fake version of the method that launches an application inside a running
-  // Container. |callback| is called after the method call finishes.
   void LaunchContainerApplication(
       const vm_tools::cicerone::LaunchContainerApplicationRequest& request,
       DBusMethodCallback<vm_tools::cicerone::LaunchContainerApplicationResponse>
           callback) override;
-
-  // Fake version of the method that gets application icons from inside a
-  // Container. |callback| is called after the method call finishes.
   void GetContainerAppIcons(
       const vm_tools::cicerone::ContainerAppIconRequest& request,
       DBusMethodCallback<vm_tools::cicerone::ContainerAppIconResponse> callback)
       override;
-
-  // Fake version of the method that gets information about a Linux package file
-  // inside a Container. |callback| is called after the method call finishes.
   void GetLinuxPackageInfo(
       const vm_tools::cicerone::LinuxPackageInfoRequest& request,
       DBusMethodCallback<vm_tools::cicerone::LinuxPackageInfoResponse> callback)
       override;
-
   // Fake version of the method that installs an application inside a running
   // Container. |callback| is called after the method call finishes. This does
   // not cause progress events to be fired.
@@ -96,13 +56,7 @@
       const vm_tools::cicerone::InstallLinuxPackageRequest& request,
       DBusMethodCallback<vm_tools::cicerone::InstallLinuxPackageResponse>
           callback) override;
-
-  // Sets a callback to be called during any call to UninstallPackageOwningFile.
-  void SetOnUninstallPackageOwningFileCallback(
-      UninstallPackageOwningFileCallback callback);
-
-  // Fake version of the method that uninstalls an application inside a running
-  // Container. If SetOnUninstallPackageOwningFileCallback has been called, it
+  // If SetOnUninstallPackageOwningFileCallback has been called, it
   // just triggers that callback. Otherwise, it generates a task to call
   // |callback| with the response from
   // set_uninstall_package_owning_file_response.
@@ -110,238 +64,171 @@
       const vm_tools::cicerone::UninstallPackageOwningFileRequest& request,
       DBusMethodCallback<vm_tools::cicerone::UninstallPackageOwningFileResponse>
           callback) override;
-
-  // Fake version of the method that creates a new Container.
-  // |callback| is called to indicate creation status.
   void CreateLxdContainer(
       const vm_tools::cicerone::CreateLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::CreateLxdContainerResponse>
           callback) override;
-
   void DeleteLxdContainer(
       const vm_tools::cicerone::DeleteLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::DeleteLxdContainerResponse>
           callback) override;
-
-  // Fake version of the method that starts a new Container.
-  // |callback| is called when the method completes.
   void StartLxdContainer(
       const vm_tools::cicerone::StartLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::StartLxdContainerResponse>
           callback) override;
-
-  // Fake version of the method that gets the container username.
-  // |callback| is called when the method completes.
   void GetLxdContainerUsername(
       const vm_tools::cicerone::GetLxdContainerUsernameRequest& request,
       DBusMethodCallback<vm_tools::cicerone::GetLxdContainerUsernameResponse>
           callback) override;
-
-  // Fake version of the method that sets the container user.
-  // |callback| is called when the method completes.
   void SetUpLxdContainerUser(
       const vm_tools::cicerone::SetUpLxdContainerUserRequest& request,
       DBusMethodCallback<vm_tools::cicerone::SetUpLxdContainerUserResponse>
           callback) override;
-
-  // Fake version of the method that exports the container.
-  // |callback| is called when the method completes.
   void ExportLxdContainer(
       const vm_tools::cicerone::ExportLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::ExportLxdContainerResponse>
           callback) override;
-
-  // Fake version of the method that imports the container.
-  // |callback| is called when the method completes.
   void ImportLxdContainer(
       const vm_tools::cicerone::ImportLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::ImportLxdContainerResponse>
           callback) override;
-
-  // Fake version of the method that cancels the in progress Lxd container
-  // export. |callback| is called when the method completes.
   void CancelExportLxdContainer(
       const vm_tools::cicerone::CancelExportLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::CancelExportLxdContainerResponse>
           callback) override;
-
-  // Fake version of the method that cancels the in progress Lxd container
-  // import. |callback| is called when the method completes.
   void CancelImportLxdContainer(
       const vm_tools::cicerone::CancelImportLxdContainerRequest& request,
       DBusMethodCallback<vm_tools::cicerone::CancelImportLxdContainerResponse>
           callback) override;
-
-  // Fake version of the method that waits for the Cicerone service to be
-  // availble.  |callback| is called after the method call finishes.
   void WaitForServiceToBeAvailable(
       dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) override;
 
-  // Set ContainerStartedSignalConnected state
+  // Sets a callback to be called during any call to UninstallPackageOwningFile.
+  void SetOnUninstallPackageOwningFileCallback(
+      UninstallPackageOwningFileCallback callback);
   void set_container_started_signal_connected(bool connected) {
     is_container_started_signal_connected_ = connected;
   }
-
-  // Set ContainerShutdownSignalConnected state
   void set_container_shutdown_signal_connected(bool connected) {
     is_container_shutdown_signal_connected_ = connected;
   }
-
-  // Set InstallLinuxPackageProgressSignalConnected state
   void set_install_linux_package_progress_signal_connected(bool connected) {
     is_install_linux_package_progress_signal_connected_ = connected;
   }
-
-  // Set IsUninstallPackageProgressSignalConnected state
   void set_uninstall_package_progress_signal_connected(bool connected) {
     is_uninstall_package_progress_signal_connected_ = connected;
   }
-
-  // Set LxdContainerCreatedSignalConnected state
   void set_lxd_container_created_signal_connected(bool connected) {
     is_lxd_container_created_signal_connected_ = connected;
   }
-
-  // Set LxdContainerCreatedSignalConnected response status
   void set_lxd_container_created_signal_status(
       vm_tools::cicerone::LxdContainerCreatedSignal_Status status) {
     lxd_container_created_signal_status_ = status;
   }
-
-  // Set LxdContainerDeletedSignalConnected state
   void set_lxd_container_deleted_signal_connected(bool connected) {
     is_lxd_container_deleted_signal_connected_ = connected;
   }
-
-  // Set LxdContainerDeletedSignalConnected response status
   void set_lxd_container_deleted_signal_status(
       vm_tools::cicerone::LxdContainerDeletedSignal_Status status) {
     lxd_container_deleted_signal_status_ = status;
   }
-
-  // Set LxdContainerDownloadingSignalConnected state
   void set_lxd_container_downloading_signal_connected(bool connected) {
     is_lxd_container_downloading_signal_connected_ = connected;
   }
-
-  // Set TremplinStartedSignalConnected state
   void set_tremplin_started_signal_connected(bool connected) {
     is_tremplin_started_signal_connected_ = connected;
   }
-
-  // Set LxdContainerStartingSignalConnected state
   void set_lxd_container_starting_signal_connected(bool connected) {
     is_lxd_container_starting_signal_connected_ = connected;
   }
-
-  // Set ExportLxdContainerProgressSignalConnected state
   void set_export_lxd_container_progress_signal_connected(bool connected) {
     is_export_lxd_container_progress_signal_connected_ = connected;
   }
-
-  // Set ImportLxdContainerProgressSignalConnected state
   void set_import_lxd_container_progress_signal_connected(bool connected) {
     is_import_lxd_container_progress_signal_connected_ = connected;
   }
-
   void set_launch_container_application_response(
       const vm_tools::cicerone::LaunchContainerApplicationResponse&
           launch_container_application_response) {
     launch_container_application_response_ =
         launch_container_application_response;
   }
-
   void set_container_app_icon_response(
       const vm_tools::cicerone::ContainerAppIconResponse&
           container_app_icon_response) {
     container_app_icon_response_ = container_app_icon_response;
   }
-
   const vm_tools::cicerone::LinuxPackageInfoRequest&
   get_most_recent_linux_package_info_request() const {
     return most_recent_linux_package_info_request_;
   }
-
   void set_linux_package_info_response(
       const vm_tools::cicerone::LinuxPackageInfoResponse&
           get_linux_package_info_response) {
     get_linux_package_info_response_ = get_linux_package_info_response;
   }
-
   const vm_tools::cicerone::InstallLinuxPackageRequest&
   get_most_recent_install_linux_package_request() const {
     return most_recent_install_linux_package_request_;
   }
-
   void set_install_linux_package_response(
       const vm_tools::cicerone::InstallLinuxPackageResponse&
           install_linux_package_response) {
     install_linux_package_response_ = install_linux_package_response;
   }
-
   void set_uninstall_package_owning_file_response(
       const vm_tools::cicerone::UninstallPackageOwningFileResponse&
           uninstall_package_owning_file_response) {
     uninstall_package_owning_file_response_ =
         uninstall_package_owning_file_response;
   }
-
   void set_create_lxd_container_response(
       const vm_tools::cicerone::CreateLxdContainerResponse&
           create_lxd_container_response) {
     create_lxd_container_response_ = create_lxd_container_response;
   }
-
   void set_delete_lxd_container_response_(
       const vm_tools::cicerone::DeleteLxdContainerResponse&
           delete_lxd_container_response) {
     delete_lxd_container_response_ = delete_lxd_container_response;
   }
-
   void set_start_lxd_container_response(
       const vm_tools::cicerone::StartLxdContainerResponse&
           start_lxd_container_response) {
     start_lxd_container_response_ = start_lxd_container_response;
   }
-
   void set_get_lxd_container_username_response(
       const vm_tools::cicerone::GetLxdContainerUsernameResponse&
           get_lxd_container_username_response) {
     get_lxd_container_username_response_ = get_lxd_container_username_response;
   }
-
   void set_setup_lxd_container_user_response(
       const vm_tools::cicerone::SetUpLxdContainerUserResponse&
           setup_lxd_container_user_response) {
     setup_lxd_container_user_response_ = setup_lxd_container_user_response;
   }
-
   void set_export_lxd_container_response(
       const vm_tools::cicerone::ExportLxdContainerResponse&
           export_lxd_container_response) {
     export_lxd_container_response_ = export_lxd_container_response;
   }
-
   void set_import_lxd_container_response(
       const vm_tools::cicerone::ImportLxdContainerResponse&
           import_lxd_container_response) {
     import_lxd_container_response_ = import_lxd_container_response;
   }
-
   void set_cancel_export_lxd_container_response(
       vm_tools::cicerone::CancelExportLxdContainerResponse
           cancel_export_lxd_container_response) {
     cancel_export_lxd_container_response_ =
         std::move(cancel_export_lxd_container_response);
   }
-
   void set_cancel_import_lxd_container_response(
       vm_tools::cicerone::CancelImportLxdContainerResponse
           cancel_import_lxd_container_response) {
     cancel_import_lxd_container_response_ =
         std::move(cancel_import_lxd_container_response);
   }
-
   void set_lxd_container_os_release(vm_tools::cicerone::OsRelease os_release) {
     lxd_container_os_release_ = std::move(os_release);
   }
diff --git a/chromeos/dbus/fake_concierge_client.h b/chromeos/dbus/fake_concierge_client.h
index 1d7a36b..da6c963a4 100644
--- a/chromeos/dbus/fake_concierge_client.h
+++ b/chromeos/dbus/fake_concierge_client.h
@@ -21,38 +21,21 @@
   FakeConciergeClient();
   ~FakeConciergeClient() override;
 
+  // ConciergeClient:
   void AddContainerObserver(ContainerObserver* observer) override;
-
   void RemoveContainerObserver(ContainerObserver* observer) override;
-
   void AddDiskImageObserver(DiskImageObserver* observer) override;
-
   void RemoveDiskImageObserver(DiskImageObserver* observer) override;
-
-  // IsContainerStartupFailedSignalConnected must return true before
-  // StartLxdContainer is called.
   bool IsContainerStartupFailedSignalConnected() override;
-
-  // IsDiskImageProgressSignalConnected must return true before
-  // ImportDiskImage is called.
   bool IsDiskImageProgressSignalConnected() override;
-
-  // Fake version of the method that creates a disk image for the Termina VM.
-  // Sets create_disk_image_called_. |callback| is called after the method
-  // call finishes.
   void CreateDiskImage(
       const vm_tools::concierge::CreateDiskImageRequest& request,
       DBusMethodCallback<vm_tools::concierge::CreateDiskImageResponse> callback)
       override;
-
-  // Fake version of the method that destroys a Termina VM and removes its disk
-  // image. Sets destroy_disk_image_called_. |callback| is called after the
-  // method call finishes.
   void DestroyDiskImage(
       const vm_tools::concierge::DestroyDiskImageRequest& request,
       DBusMethodCallback<vm_tools::concierge::DestroyDiskImageResponse>
           callback) override;
-
   // Fake version of the method that imports a VM disk image.
   // This function can fake a series of callbacks. It always first runs the
   // callback provided as an argument, and then optionally a series of fake
@@ -62,145 +45,83 @@
       const vm_tools::concierge::ImportDiskImageRequest& request,
       DBusMethodCallback<vm_tools::concierge::ImportDiskImageResponse> callback)
       override;
-
-  // Fake version of the method that cancels a VM disk image operation (import
-  // or export).
   void CancelDiskImageOperation(
       const vm_tools::concierge::CancelDiskImageRequest& request,
       DBusMethodCallback<vm_tools::concierge::CancelDiskImageResponse> callback)
       override;
-
-  // Fake version of the method that returns the status of a disk image
-  // operation.
   void DiskImageStatus(
       const vm_tools::concierge::DiskImageStatusRequest& request,
       DBusMethodCallback<vm_tools::concierge::DiskImageStatusResponse> callback)
       override;
-
-  // Fake version of the method that lists Termina VM disks. Sets
-  // list_vm_disks_called_. |callback| is called after the method call
-  // finishes.
   void ListVmDisks(const vm_tools::concierge::ListVmDisksRequest& request,
                    DBusMethodCallback<vm_tools::concierge::ListVmDisksResponse>
                        callback) override;
-
-  // Fake version of the method that starts a Termina VM. Sets
-  // start_termina_vm_called_. |callback| is called after the method call
-  // finishes.
   void StartTerminaVm(const vm_tools::concierge::StartVmRequest& request,
                       DBusMethodCallback<vm_tools::concierge::StartVmResponse>
                           callback) override;
-
-  // Fake version of the method that stops the named Termina VM if it is
-  // running. Sets stop_vm_called_. |callback| is called after the method
-  // call finishes.
   void StopVm(const vm_tools::concierge::StopVmRequest& request,
               DBusMethodCallback<vm_tools::concierge::StopVmResponse> callback)
       override;
-
-  // Fake version of the method that gets VM info. Sets get_vm_info_called_.
-  // |callback| is called after the method call finishes.
   void GetVmInfo(const vm_tools::concierge::GetVmInfoRequest& request,
                  DBusMethodCallback<vm_tools::concierge::GetVmInfoResponse>
                      callback) override;
-
-  // Fake version of the method that gets VM enterprise reporting info. Sets
-  // get_vm_enterprise_reporting_info_called_. |callback| is called after the
-  // method call finishes.
   void GetVmEnterpriseReportingInfo(
       const vm_tools::concierge::GetVmEnterpriseReportingInfoRequest& request,
       DBusMethodCallback<
           vm_tools::concierge::GetVmEnterpriseReportingInfoResponse> callback)
       override;
-
   void SetVmCpuRestriction(
       const vm_tools::concierge::SetVmCpuRestrictionRequest& request,
       DBusMethodCallback<vm_tools::concierge::SetVmCpuRestrictionResponse>
           callback) override;
-
-  // Fake version of the method that waits for the Concierge service to be
-  // availble.  |callback| is called after the method call finishes.
   void WaitForServiceToBeAvailable(
       dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) override;
-
-  // Fake version of the method that fetches ssh key information.
-  // |callback| is called after the method call finishes.
   void GetContainerSshKeys(
       const vm_tools::concierge::ContainerSshKeysRequest& request,
       DBusMethodCallback<vm_tools::concierge::ContainerSshKeysResponse>
           callback) override;
-
-  // Fake version of the method that attaches a USB device to a VM
-  // |callback| is called once the method call has finished
   void AttachUsbDevice(base::ScopedFD fd,
       const vm_tools::concierge::AttachUsbDeviceRequest& request,
       DBusMethodCallback<vm_tools::concierge::AttachUsbDeviceResponse> callback)
       override;
-
-  // Fake version of the method that removes a USB device from a VM it's been
-  // attached to
-  // |callback| is called once the method call has finished
   void DetachUsbDevice(
       const vm_tools::concierge::DetachUsbDeviceRequest& request,
       DBusMethodCallback<vm_tools::concierge::DetachUsbDeviceResponse> callback)
       override;
-
-  // Fake version of the method that lists all the USB devices currently
-  // attached to a given VM
-  // |callback| is called once the method call has finished
   void ListUsbDevices(
       const vm_tools::concierge::ListUsbDeviceRequest& request,
       DBusMethodCallback<vm_tools::concierge::ListUsbDeviceResponse> callback)
       override;
-
-  // Fake version of the method that starts ARCVM. Sets start_arc_vm_called_.
-  // |callback| is called after the method call finishes.
   void StartArcVm(const vm_tools::concierge::StartArcVmRequest& request,
                   DBusMethodCallback<vm_tools::concierge::StartVmResponse>
                       callback) override;
 
-  // Indicates whether WaitForServiceToBeAvailable has been called.
   bool wait_for_service_to_be_available_called() const {
     return wait_for_service_to_be_available_called_;
   }
-  // Indicates whether CreateDiskImage has been called
   bool create_disk_image_called() const { return create_disk_image_called_; }
-  // Indicates whether DestroyDiskImage has been called
   bool destroy_disk_image_called() const { return destroy_disk_image_called_; }
-  // Indicates whether ImportDiskImage has been called
   bool import_disk_image_called() const { return import_disk_image_called_; }
-  // Indicates whether ListVmDisks has been called
   bool list_vm_disks_called() const { return list_vm_disks_called_; }
-  // Indicates whether StartTerminaVm has been called
   bool start_termina_vm_called() const { return start_termina_vm_called_; }
-  // Indicates whether StopVm has been called
   bool stop_vm_called() const { return stop_vm_called_; }
-  // Indicates whether GetVmInfo has been called
   bool get_vm_info_called() const { return get_vm_info_called_; }
-  // Indicates whether GetEnterpriseReportingInfo has been called
   bool get_vm_enterprise_reporting_info_called() const {
     return get_vm_enterprise_reporting_info_called_;
   }
-  // Indicates whether GetContainerSshKeys has been called
   bool get_container_ssh_keys_called() const {
     return get_container_ssh_keys_called_;
   }
-  // Indicates whether AttachUsbDevice has been called
   bool attach_usb_device_called() const { return attach_usb_device_called_; }
-  // Indicates whether DetachUsbDevice has been called
   bool detach_usb_device_called() const { return detach_usb_device_called_; }
-  // Indicates whether ListUsbDevices has been called
   bool list_usb_devices_called() const { return list_usb_devices_called_; }
-  // Indicates whether StartArcVm has been called
   bool start_arc_vm_called() const { return start_arc_vm_called_; }
-  // Set ContainerStartupFailedSignalConnected state
   void set_container_startup_failed_signal_connected(bool connected) {
     is_container_startup_failed_signal_connected_ = connected;
   }
   void set_disk_image_progress_signal_connected(bool connected) {
     is_disk_image_progress_signal_connected_ = connected;
   }
-
   void set_wait_for_service_to_be_available_response(
       bool wait_for_service_to_be_available_response) {
     wait_for_service_to_be_available_response_ =
@@ -278,7 +199,6 @@
       list_usb_devices_response) {
     list_usb_devices_response_ = list_usb_devices_response;
   }
-
   void set_disk_image_status_signals(
       const std::vector<vm_tools::concierge::DiskImageStatusResponse>&
           disk_image_status_signals) {
diff --git a/chromeos/dbus/session_manager/fake_session_manager_client.cc b/chromeos/dbus/session_manager/fake_session_manager_client.cc
index 9dad503..52debd30 100644
--- a/chromeos/dbus/session_manager/fake_session_manager_client.cc
+++ b/chromeos/dbus/session_manager/fake_session_manager_client.cc
@@ -302,6 +302,17 @@
                                 base::nullopt /* error */));
 }
 
+void FakeSessionManagerClient::LoginScreenStorageListKeys(
+    LoginScreenStorageListKeysCallback callback) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(std::move(callback), std::vector<std::string>() /* keys */,
+                     base::nullopt /* error */));
+}
+
+void FakeSessionManagerClient::LoginScreenStorageDelete(
+    const std::string& key) {}
+
 void FakeSessionManagerClient::StartSession(
     const cryptohome::AccountIdentifier& cryptohome_id) {
   DCHECK_EQ(0UL, user_sessions_.count(cryptohome_id.account_id()));
diff --git a/chromeos/dbus/session_manager/fake_session_manager_client.h b/chromeos/dbus/session_manager/fake_session_manager_client.h
index e09d31b..658f867 100644
--- a/chromeos/dbus/session_manager/fake_session_manager_client.h
+++ b/chromeos/dbus/session_manager/fake_session_manager_client.h
@@ -65,10 +65,12 @@
       const login_manager::LoginScreenStorageMetadata& metadata,
       const std::string& data,
       LoginScreenStorageStoreCallback callback) override;
-
   void LoginScreenStorageRetrieve(
       const std::string& key,
       LoginScreenStorageRetrieveCallback callback) override;
+  void LoginScreenStorageListKeys(
+      LoginScreenStorageListKeysCallback callback) override;
+  void LoginScreenStorageDelete(const std::string& key) override;
 
   void StartSession(
       const cryptohome::AccountIdentifier& cryptohome_id) override;
diff --git a/chromeos/dbus/session_manager/session_manager_client.cc b/chromeos/dbus/session_manager/session_manager_client.cc
index 022c8d7..3bcd2552 100644
--- a/chromeos/dbus/session_manager/session_manager_client.cc
+++ b/chromeos/dbus/session_manager/session_manager_client.cc
@@ -272,6 +272,28 @@
                        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   }
 
+  void LoginScreenStorageListKeys(
+      LoginScreenStorageListKeysCallback callback) override {
+    dbus::MethodCall method_call(
+        login_manager::kSessionManagerInterface,
+        login_manager::kSessionManagerLoginScreenStorageListKeys);
+    session_manager_proxy_->CallMethod(
+        &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+        base::BindOnce(&SessionManagerClientImpl::OnLoginScreenStorageListKeys,
+                       weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+  }
+
+  void LoginScreenStorageDelete(const std::string& key) override {
+    dbus::MethodCall method_call(
+        login_manager::kSessionManagerInterface,
+        login_manager::kSessionManagerLoginScreenStorageDelete);
+    dbus::MessageWriter writer(&method_call);
+    writer.AppendString(key);
+    session_manager_proxy_->CallMethod(&method_call,
+                                       dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
+                                       base::DoNothing());
+  }
+
   void StartSession(
       const cryptohome::AccountIdentifier& cryptohome_id) override {
     dbus::MethodCall method_call(login_manager::kSessionManagerInterface,
@@ -761,6 +783,26 @@
                             base::nullopt /* error */);
   }
 
+  void OnLoginScreenStorageListKeys(LoginScreenStorageListKeysCallback callback,
+                                    dbus::Response* response) {
+    if (!response) {
+      // TODO(voit): Add more granular error handling: key is not found error vs
+      // general 'something went wrong' error.
+      std::move(callback).Run({} /* keys */,
+                              "LoginScreenStorageListKeys() D-Bus method "
+                              "returned an empty response");
+      return;
+    }
+    dbus::MessageReader reader(response);
+    std::vector<std::string> keys;
+    if (!reader.PopArrayOfStrings(&keys)) {
+      std::string error = "Invalid response: " + response->ToString();
+      std::move(callback).Run({} /* keys */, error);
+      return;
+    }
+    std::move(callback).Run(std::move(keys), base::nullopt /* error */);
+  }
+
   // Reads an array of policy data bytes data as std::string.
   void ExtractPolicyResponseString(
       login_manager::PolicyAccountType account_type,
diff --git a/chromeos/dbus/session_manager/session_manager_client.h b/chromeos/dbus/session_manager/session_manager_client.h
index ec7d5a75..0277129 100644
--- a/chromeos/dbus/session_manager/session_manager_client.h
+++ b/chromeos/dbus/session_manager/session_manager_client.h
@@ -173,6 +173,21 @@
       const std::string& key,
       LoginScreenStorageRetrieveCallback callback) = 0;
 
+  // Used for |LoginScreenStorageListKeys()| method. |keys| argument is the list
+  // of keys currently stored in the login screen storage. In case of error,
+  // |keys| is empty and |error| contains the error message.
+  using LoginScreenStorageListKeysCallback =
+      base::OnceCallback<void(std::vector<std::string> /* keys */,
+                              base::Optional<std::string> /* error */)>;
+
+  // List all keys currently stored in the login screen storage.
+  virtual void LoginScreenStorageListKeys(
+      LoginScreenStorageListKeysCallback callback) = 0;
+
+  // Delete a key and the value associated with it from the login screen
+  // storage.
+  virtual void LoginScreenStorageDelete(const std::string& key) = 0;
+
   // Starts the session for the user.
   virtual void StartSession(
       const cryptohome::AccountIdentifier& cryptohome_id) = 0;
diff --git a/chromeos/profiles/orderfile.newest.txt b/chromeos/profiles/orderfile.newest.txt
index 45ba8008..613b52f 100644
--- a/chromeos/profiles/orderfile.newest.txt
+++ b/chromeos/profiles/orderfile.newest.txt
@@ -1 +1 @@
-chromeos-chrome-orderfile-field-78-3877.0-1566814872-benchmark-78.0.3893.0-r1.orderfile.xz
\ No newline at end of file
+chromeos-chrome-orderfile-field-78-3877.0-1567418235-benchmark-78.0.3893.0-r1.orderfile.xz
\ No newline at end of file
diff --git a/components/autofill/core/browser/autofill_client.cc b/components/autofill/core/browser/autofill_client.cc
index 98d69be2..aaa1e239 100644
--- a/components/autofill/core/browser/autofill_client.cc
+++ b/components/autofill/core/browser/autofill_client.cc
@@ -23,4 +23,8 @@
   return nullptr;
 }
 
+bool AutofillClient::CloseWebauthnOfferDialog() {
+  return false;
+}
+
 }  // namespace autofill
diff --git a/components/autofill/core/browser/autofill_client.h b/components/autofill/core/browser/autofill_client.h
index 09e30585..e632d44 100644
--- a/components/autofill/core/browser/autofill_client.h
+++ b/components/autofill/core/browser/autofill_client.h
@@ -311,6 +311,10 @@
   virtual void ShowWebauthnOfferDialog(
       WebauthnOfferDialogCallback callback) = 0;
 
+  // Will close the WebAuthn offer dialog. Returns true if dialog was visible
+  // and has been closed. Implemented only on desktop.
+  virtual bool CloseWebauthnOfferDialog();
+
   // Runs |callback| if the |profile| should be imported as personal data.
   virtual void ConfirmSaveAutofillProfile(const AutofillProfile& profile,
                                           base::OnceClosure callback) = 0;
diff --git a/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc b/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc
index e6c1567..7e4f920 100644
--- a/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc
+++ b/components/autofill/core/browser/payments/credit_card_fido_authenticator.cc
@@ -12,6 +12,7 @@
 #include "base/containers/flat_set.h"
 #include "base/strings/string16.h"
 #include "base/strings/string_util.h"
+#include "build/build_config.h"
 #include "components/autofill/core/browser/autofill_client.h"
 #include "components/autofill/core/browser/data_model/credit_card.h"
 #include "components/autofill/core/browser/payments/payments_client.h"
@@ -171,6 +172,16 @@
     PublicKeyCredentialCreationOptionsPtr creation_options) {
   autofill_driver_->ConnectToAuthenticator(
       authenticator_.BindNewPipeAndPassReceiver());
+#if !defined(OS_ANDROID)
+  // On desktop, close the WebAuthn offer dialog and get ready to show the OS
+  // level authentication dialog. If dialog is already closed, then the offer
+  // was declined during the fetching challenge process, and thus returned
+  // early.
+  if (!autofill_client_->CloseWebauthnOfferDialog()) {
+    current_flow_ = NONE_FLOW;
+    return;
+  }
+#endif
   authenticator_->MakeCredential(
       std::move(creation_options),
       base::BindOnce(&CreditCardFIDOAuthenticator::OnDidMakeCredential,
@@ -253,8 +264,12 @@
 
 void CreditCardFIDOAuthenticator::OnWebauthnOfferDialogUserResponse(
     bool did_accept) {
-  // TODO(crbug.com/): Register and start fetching authentication challenge if
-  // |did_accept|, otherwise cancel any ongoing request.
+  if (did_accept) {
+    Register();
+  } else {
+    payments_client_->CancelRequest();
+    current_flow_ = NONE_FLOW;
+  }
 }
 
 void CreditCardFIDOAuthenticator::OnFullCardRequestSucceeded(
diff --git a/components/autofill/core/browser/test_autofill_client.cc b/components/autofill/core/browser/test_autofill_client.cc
index 89baf299..218404d 100644
--- a/components/autofill/core/browser/test_autofill_client.cc
+++ b/components/autofill/core/browser/test_autofill_client.cc
@@ -110,6 +110,10 @@
 void TestAutofillClient::ShowWebauthnOfferDialog(
     WebauthnOfferDialogCallback callback) {}
 
+bool TestAutofillClient::CloseWebauthnOfferDialog() {
+  return true;
+}
+
 void TestAutofillClient::ConfirmSaveAutofillProfile(
     const AutofillProfile& profile,
     base::OnceClosure callback) {
diff --git a/components/autofill/core/browser/test_autofill_client.h b/components/autofill/core/browser/test_autofill_client.h
index 2f8b9bd..1f936c8 100644
--- a/components/autofill/core/browser/test_autofill_client.h
+++ b/components/autofill/core/browser/test_autofill_client.h
@@ -65,6 +65,7 @@
       const std::vector<MigratableCreditCard>& migratable_credit_cards,
       MigrationDeleteCardCallback delete_local_card_callback) override;
   void ShowWebauthnOfferDialog(WebauthnOfferDialogCallback callback) override;
+  bool CloseWebauthnOfferDialog() override;
   void ConfirmSaveAutofillProfile(const AutofillProfile& profile,
                                   base::OnceClosure callback) override;
   void ConfirmSaveCreditCardLocally(
diff --git a/components/autofill_payments_strings.grdp b/components/autofill_payments_strings.grdp
index b993cfb..86bd899 100644
--- a/components/autofill_payments_strings.grdp
+++ b/components/autofill_payments_strings.grdp
@@ -440,6 +440,9 @@
       <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE" desc="Headline asking the user if they want to use their device's platform authenticator to confirm their cards in the future instead of CVC.">
         Use Touch ID instead of CVC?
       </message>
+      <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE_ERROR" desc="Headline explaining to users there were errors in the attempt to opt in using platform's authenticator.">
+        Couldn't use Touch ID
+      </message>
       <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_OK_BUTTON_LABEL" desc="Button that allows the user to use their device's platform authenticator to confirm card use in the future.">
         Use Touch ID
       </message>
@@ -448,6 +451,9 @@
       <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE" desc="Headline asking the user if they want to use their device's platform authenticator to confirm their cards in the future instead of CVC.">
         Use Windows Hello instead of CVC?
       </message>
+      <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE_ERROR" desc="Headline explaining to users there were errors in the attempt to opt in using platform's authenticator.">
+        Couldn't use Windows Hello
+      </message>
       <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_OK_BUTTON_LABEL" desc="Button that allows the user to use their device's platform authenticator to confirm card use in the future.">
         Use Windows Hello
       </message>
@@ -456,6 +462,9 @@
       <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE" desc="Headline asking the user if they want to use their device's platform authenticator to confirm their cards in the future instead of CVC.">
         Use WebAuthn instead of CVC?
       </message>
+      <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_TITLE_ERROR" desc="Headline explaining to users there were errors in the attempt to opt in using platform's authenticator.">
+        Couldn't use WebAuthn
+      </message>
       <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_OK_BUTTON_LABEL" desc="Button that allows the user to use their device's platform authenticator to confirm card use in the future.">
         Use WebAuthn
       </message>
@@ -463,9 +472,15 @@
     <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_INSTRUCTION" desc="Subheading that appears below the headline explaining to the user that they won't have to enter their card's security code if they decide to use their device's platform authenticator in the future.">
       You won't need to enter a card security code from now on
     </message>
+    <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_INSTRUCTION_ERROR" desc="Subheading that appears below the headline, acknowledging the current error and encouraging users to retry later.">
+      Please try again next time
+    </message>
     <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_CANCEL_BUTTON_LABEL" desc="Button that allows the user to decline the option of using their device's platform authenticator for card confirmation.">
       Not now
     </message>
+    <message name="IDS_AUTOFILL_WEBAUTHN_OPT_IN_DIALOG_CANCEL_BUTTON_LABEL_ERROR" desc="Button that allows the user to close the dialog.">
+      Close
+    </message>
   </if>
 
   <message name="IDS_AUTOFILL_WALLET_MANAGEMENT_LINK_TEXT" desc="Text for link that allows users to see and edit their Wallet information." formatter_data="android_java">
diff --git a/components/history/core/test/thumbnail_ios.mm b/components/history/core/test/thumbnail_ios.mm
index d3b0cee..ddbfe1d5 100644
--- a/components/history/core/test/thumbnail_ios.mm
+++ b/components/history/core/test/thumbnail_ios.mm
@@ -7,24 +7,24 @@
 #import <Foundation/Foundation.h>
 #import <UIKit/UIKit.h>
 
-#import "base/mac/scoped_nsautorelease_pool.h"
 #include "components/history/core/test/thumbnail-inl.h"
 #include "ui/gfx/image/image.h"
 
 namespace history {
 
 gfx::Image CreateGoogleThumbnailForTest() {
-  base::mac::ScopedNSAutoreleasePool pool;
-  // -[NSData dataWithBytesNoCopy:length:freeWhenDone:] takes it first parameter
-  // as a void* but does not modify it (API is not const clean) so we need to
-  // use const_cast<> here.
-  NSData* data =
-      [NSData dataWithBytesNoCopy:const_cast<void*>(static_cast<const void*>(
-                                      kGoogleThumbnail))
-                           length:sizeof(kGoogleThumbnail)
-                     freeWhenDone:NO];
-  UIImage* image = [UIImage imageWithData:data scale:1];
-  return gfx::Image([image retain]);
+  @autoreleasepool {
+    // -[NSData dataWithBytesNoCopy:length:freeWhenDone:] takes its first
+    // parameter as a void* but does not modify it (API is not const clean) so
+    // we need to use const_cast<> here.
+    NSData* data = [NSData
+        dataWithBytesNoCopy:const_cast<void*>(
+                                static_cast<const void*>(kGoogleThumbnail))
+                     length:sizeof(kGoogleThumbnail)
+               freeWhenDone:NO];
+    UIImage* image = [UIImage imageWithData:data scale:1];
+    return gfx::Image([image retain]);
+  }
 }
 
 }  // namespace
diff --git a/components/media_message_center/media_notification_controller.h b/components/media_message_center/media_notification_controller.h
index 3e6a629..86467d6 100644
--- a/components/media_message_center/media_notification_controller.h
+++ b/components/media_message_center/media_notification_controller.h
@@ -34,6 +34,10 @@
   // Returns a task runner that the MediaNotificationItem should use.
   // It typically returns null except in tests.
   virtual scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const = 0;
+
+  // Notifies the MediaNotificationController that a media button was pressed on
+  // the MediaNotificationView.
+  virtual void LogMediaSessionActionButtonPressed(const std::string& id) = 0;
 };
 
 }  // namespace media_message_center
diff --git a/components/media_message_center/media_notification_item.cc b/components/media_message_center/media_notification_item.cc
index 8fbdd4a..21c3cb54 100644
--- a/components/media_message_center/media_notification_item.cc
+++ b/components/media_message_center/media_notification_item.cc
@@ -195,6 +195,7 @@
   if (frozen_)
     return;
 
+  controller_->LogMediaSessionActionButtonPressed(request_id_);
   media_session::PerformMediaSessionAction(action, media_controller_remote_);
 }
 
diff --git a/components/media_message_center/media_notification_view_unittest.cc b/components/media_message_center/media_notification_view_unittest.cc
index a6f439bd..eb1d49a8 100644
--- a/components/media_message_center/media_notification_view_unittest.cc
+++ b/components/media_message_center/media_notification_view_unittest.cc
@@ -71,6 +71,7 @@
   scoped_refptr<base::SequencedTaskRunner> GetTaskRunner() const override {
     return nullptr;
   }
+  MOCK_METHOD1(LogMediaSessionActionButtonPressed, void(const std::string& id));
 
  private:
   DISALLOW_COPY_AND_ASSIGN(MockMediaNotificationController);
@@ -179,6 +180,8 @@
 
   MockMediaNotificationContainer& container() { return container_; }
 
+  MockMediaNotificationController& controller() { return controller_; }
+
   MediaNotificationView* view() const { return container_.view(); }
 
   TestMediaController* media_controller() const {
@@ -389,6 +392,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, NextTrackButtonClick) {
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
   EnableAction(MediaSessionAction::kNextTrack);
 
   EXPECT_EQ(0, media_controller()->next_track_count());
@@ -401,6 +405,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, PlayButtonClick) {
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
   EnableAction(MediaSessionAction::kPlay);
 
   EXPECT_EQ(0, media_controller()->resume_count());
@@ -413,6 +418,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, PauseButtonClick) {
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
   EnableAction(MediaSessionAction::kPause);
 
   EXPECT_EQ(0, media_controller()->suspend_count());
@@ -432,6 +438,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, PreviousTrackButtonClick) {
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
   EnableAction(MediaSessionAction::kPreviousTrack);
 
   EXPECT_EQ(0, media_controller()->previous_track_count());
@@ -444,6 +451,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, SeekBackwardButtonClick) {
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
   EnableAction(MediaSessionAction::kSeekBackward);
 
   EXPECT_EQ(0, media_controller()->seek_backward_count());
@@ -456,6 +464,7 @@
 }
 
 TEST_F(MediaNotificationViewTest, SeekForwardButtonClick) {
+  EXPECT_CALL(controller(), LogMediaSessionActionButtonPressed(_));
   EnableAction(MediaSessionAction::kSeekForward);
 
   EXPECT_EQ(0, media_controller()->seek_forward_count());
diff --git a/components/neterror/OWNERS b/components/neterror/OWNERS
index 74669b39..a28823a 100644
--- a/components/neterror/OWNERS
+++ b/components/neterror/OWNERS
@@ -6,3 +6,4 @@
 jianli@chromium.org
 
 # COMPONENT: Internals>Network
+# TEAM: net-dev@chromium.org
diff --git a/components/omnibox/common/omnibox_features.cc b/components/omnibox/common/omnibox_features.cc
index 7b624d3..bae71a6 100644
--- a/components/omnibox/common/omnibox_features.cc
+++ b/components/omnibox/common/omnibox_features.cc
@@ -92,15 +92,6 @@
 #endif
 };
 
-const base::Feature kOmniboxPreserveDefaultMatchScore{
-  "OmniboxPreserveDefaultMatchScore",
-#if defined(OS_IOS) || defined(OS_ANDROID)
-      base::FEATURE_DISABLED_BY_DEFAULT
-#else
-      base::FEATURE_ENABLED_BY_DEFAULT
-#endif
-};
-
 // Feature used to enable swapping the rows on answers.
 const base::Feature kOmniboxReverseAnswers{"OmniboxReverseAnswers",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
@@ -287,6 +278,24 @@
 const base::Feature kOmniboxSearchEngineLogo{"OmniboxSearchEngineLogo",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Exempts the default match from demotion-by-type.
+const base::Feature kOmniboxPreserveDefaultMatchScore {
+  "OmniboxPreserveDefaultMatchScore",
+#if defined(OS_IOS) || defined(OS_ANDROID)
+      base::FEATURE_DISABLED_BY_DEFAULT
+#else
+      base::FEATURE_ENABLED_BY_DEFAULT
+#endif
+};
+
+// Preserves the default match against change when providers return results
+// asynchronously. This prevents the default match from changing after the user
+// finishes typing. Without this feature, if the default match is updated right
+// when the user presses Enter, the user may go to a surprising destination.
+const base::Feature kOmniboxPreserveDefaultMatchAgainstAsyncUpdate{
+    "OmniboxPreserveDefaultMatchAgainstAsyncUpdate",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Feature to configure on-focus suggestions provided by ZeroSuggestProvider.
 // This feature's main job is to contain some field trial parameters such as:
 //  - "ZeroSuggestVariant" configures the per-page-classification mode of
diff --git a/components/omnibox/common/omnibox_features.h b/components/omnibox/common/omnibox_features.h
index 95cc7d8..44196054 100644
--- a/components/omnibox/common/omnibox_features.h
+++ b/components/omnibox/common/omnibox_features.h
@@ -32,7 +32,6 @@
 extern const base::Feature kOmniboxSuggestionTransparencyOptions;
 extern const base::Feature kOmniboxUICuesForSearchHistoryMatches;
 extern const base::Feature kOmniboxAlternateMatchDescriptionSeparator;
-extern const base::Feature kOmniboxPreserveDefaultMatchScore;
 extern const base::Feature kEnableClipboardProviderTextSuggestions;
 extern const base::Feature kEnableClipboardProviderImageSuggestions;
 extern const base::Feature kSearchProviderWarmUpOnFocus;
@@ -50,6 +49,11 @@
 extern const base::Feature kOmniboxDisableInstantExtendedLimit;
 extern const base::Feature kOmniboxSearchEngineLogo;
 
+// Flags that affect the "twiddle" step of AutocompleteResult, i.e. SortAndCull.
+// TODO(tommycli): There are more flags above that belong in this category.
+extern const base::Feature kOmniboxPreserveDefaultMatchScore;
+extern const base::Feature kOmniboxPreserveDefaultMatchAgainstAsyncUpdate;
+
 // On-Focus Suggestions a.k.a. ZeroSuggest.
 extern const base::Feature kOnFocusSuggestions;
 extern const base::Feature kZeroSuggestionsOnNTP;
diff --git a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
index 061e29e..b7e59401 100644
--- a/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
+++ b/components/page_load_metrics/renderer/metrics_render_frame_observer.cc
@@ -56,7 +56,7 @@
  private:
   // Use associated interface to make sure mojo messages are ordered with regard
   // to legacy IPC messages.
-  mojom::PageLoadMetricsAssociatedPtr page_load_metrics_;
+  mojo::AssociatedRemote<mojom::PageLoadMetrics> page_load_metrics_;
 };
 
 }  //  namespace
diff --git a/components/policy/core/common/mock_policy_service.cc b/components/policy/core/common/mock_policy_service.cc
index 6e3e0c1..84a4583d 100644
--- a/components/policy/core/common/mock_policy_service.cc
+++ b/components/policy/core/common/mock_policy_service.cc
@@ -6,16 +6,18 @@
 
 namespace policy {
 
-MockPolicyServiceObserver::MockPolicyServiceObserver() {
-}
+MockPolicyServiceObserver::MockPolicyServiceObserver() = default;
 
-MockPolicyServiceObserver::~MockPolicyServiceObserver() {
-}
+MockPolicyServiceObserver::~MockPolicyServiceObserver() = default;
 
-MockPolicyService::MockPolicyService() {
-}
+MockPolicyServiceProviderUpdateObserver::
+    MockPolicyServiceProviderUpdateObserver() = default;
 
-MockPolicyService::~MockPolicyService() {
-}
+MockPolicyServiceProviderUpdateObserver::
+    ~MockPolicyServiceProviderUpdateObserver() = default;
+
+MockPolicyService::MockPolicyService() = default;
+
+MockPolicyService::~MockPolicyService() = default;
 
 }  // namespace policy
diff --git a/components/policy/core/common/mock_policy_service.h b/components/policy/core/common/mock_policy_service.h
index 3352860..a06caa5c 100644
--- a/components/policy/core/common/mock_policy_service.h
+++ b/components/policy/core/common/mock_policy_service.h
@@ -21,6 +21,16 @@
   MOCK_METHOD1(OnPolicyServiceInitialized, void(PolicyDomain));
 };
 
+class MockPolicyServiceProviderUpdateObserver
+    : public PolicyService::ProviderUpdateObserver {
+ public:
+  MockPolicyServiceProviderUpdateObserver();
+  ~MockPolicyServiceProviderUpdateObserver() override;
+
+  MOCK_METHOD1(OnProviderUpdatePropagated,
+               void(ConfigurationPolicyProvider* provider));
+};
+
 class MockPolicyService : public PolicyService {
  public:
   MockPolicyService();
@@ -28,10 +38,14 @@
 
   MOCK_METHOD2(AddObserver, void(PolicyDomain, Observer*));
   MOCK_METHOD2(RemoveObserver, void(PolicyDomain, Observer*));
+  MOCK_METHOD1(AddProviderUpdateObserver, void(ProviderUpdateObserver*));
+  MOCK_METHOD1(RemoveProviderUpdateObserver, void(ProviderUpdateObserver*));
+  MOCK_CONST_METHOD1(HasProvider, bool(ConfigurationPolicyProvider*));
 
   MOCK_CONST_METHOD1(GetPolicies, const PolicyMap&(const PolicyNamespace&));
   MOCK_CONST_METHOD1(IsInitializationComplete, bool(PolicyDomain domain));
   MOCK_METHOD1(RefreshPolicies, void(const base::Closure&));
+  MOCK_METHOD1(SetInitializationThrottled, void(bool initialization_throttled));
 };
 
 }  // namespace policy
diff --git a/components/policy/core/common/policy_service.h b/components/policy/core/common/policy_service.h
index b9897e1..4d04446 100644
--- a/components/policy/core/common/policy_service.h
+++ b/components/policy/core/common/policy_service.h
@@ -10,12 +10,15 @@
 
 #include "base/callback.h"
 #include "base/macros.h"
+#include "base/observer_list_types.h"
 #include "components/policy/core/common/policy_map.h"
 #include "components/policy/core/common/policy_namespace.h"
 #include "components/policy/policy_export.h"
 
 namespace policy {
 
+class ConfigurationPolicyProvider;
+
 // The PolicyService merges policies from all available sources, taking into
 // account their priorities. Policy clients can retrieve policy for their domain
 // and register for notifications on policy updates.
@@ -45,6 +48,19 @@
     virtual ~Observer() {}
   };
 
+  class POLICY_EXPORT ProviderUpdateObserver : public base::CheckedObserver {
+   public:
+    // Invoked when a policy update signaled by |provider| has been propagated
+    // to the PolicyService's Observers and its contents are now available
+    // through PolicyService::GetPolicies. This is intentionally also called if
+    // the policy update signaled by |provider| did not change the effective
+    // policy values. Note that multiple policy updates by |provider| can result
+    // in a single call to this function, e.g. if a subsequent policy update is
+    // signaled before the previous one has been processed by the PolicyService.
+    virtual void OnProviderUpdatePropagated(
+        ConfigurationPolicyProvider* provider) = 0;
+  };
+
   virtual ~PolicyService() {}
 
   // Observes changes to all components of the given |domain|.
@@ -52,6 +68,15 @@
 
   virtual void RemoveObserver(PolicyDomain domain, Observer* observer) = 0;
 
+  // Observes propagation of policy updates by ConfigurationPolicyProviders.
+  virtual void AddProviderUpdateObserver(ProviderUpdateObserver* observer) = 0;
+  virtual void RemoveProviderUpdateObserver(
+      ProviderUpdateObserver* observer) = 0;
+
+  // Returns true if this PolicyService uses |provider| as one of its sources of
+  // policies.
+  virtual bool HasProvider(ConfigurationPolicyProvider* provider) const = 0;
+
   virtual const PolicyMap& GetPolicies(const PolicyNamespace& ns) const = 0;
 
   // The PolicyService loads policy from several sources, and some require
@@ -72,6 +97,16 @@
   // |callback| is invoked once every source has reloaded its policies, and
   // GetPolicies() is guaranteed to return the updated values at that point.
   virtual void RefreshPolicies(const base::Closure& callback) = 0;
+
+  // When |initialization_throttled| is set to true, this PolicyService should
+  // return false in IsInitializationComplete for all domains and should not
+  // notify observers that it has initialized any domain. When
+  // |initialization_throttled| is set to false and the
+  // OnPolicyServiceInitialized notifications for some domains have not been
+  // sent out due to a previous call to this function with
+  // |initialization_throttled|=true, this function will notify observers that
+  // those domains are now initializted.
+  virtual void SetInitializationThrottled(bool initialization_throttled) = 0;
 };
 
 // A registrar that only observes changes to particular policies within the
diff --git a/components/policy/core/common/policy_service_impl.cc b/components/policy/core/common/policy_service_impl.cc
index 7353dee..288d11e 100644
--- a/components/policy/core/common/policy_service_impl.cc
+++ b/components/policy/core/common/policy_service_impl.cc
@@ -150,6 +150,24 @@
   }
 }
 
+void PolicyServiceImpl::AddProviderUpdateObserver(
+    ProviderUpdateObserver* observer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  provider_update_observers_.AddObserver(observer);
+}
+
+void PolicyServiceImpl::RemoveProviderUpdateObserver(
+    ProviderUpdateObserver* observer) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  provider_update_observers_.RemoveObserver(observer);
+}
+
+bool PolicyServiceImpl::HasProvider(
+    ConfigurationPolicyProvider* provider) const {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  return base::Contains(providers_, provider);
+}
+
 const PolicyMap& PolicyServiceImpl::GetPolicies(
     const PolicyNamespace& ns) const {
   DCHECK(thread_checker_.CalledOnValidThread());
@@ -185,9 +203,18 @@
   }
 }
 
+void PolicyServiceImpl::SetInitializationThrottled(
+    bool initialization_throttled) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(initialization_throttled != initialization_throttled_);
+  initialization_throttled_ = initialization_throttled;
+  CheckInitializationComplete();
+}
+
 void PolicyServiceImpl::OnUpdatePolicy(ConfigurationPolicyProvider* provider) {
   DCHECK_EQ(1, std::count(providers_.begin(), providers_.end(), provider));
   refresh_pending_.erase(provider);
+  provider_update_pending_.insert(provider);
 
   // Note: a policy change may trigger further policy changes in some providers.
   // For example, disabling SigninAllowed would cause the CloudPolicyManager to
@@ -215,6 +242,18 @@
   }
 }
 
+void PolicyServiceImpl::NotifyProviderUpdatesPropagated() {
+  if (provider_update_pending_.empty())
+    return;
+
+  for (auto& provider_update_observer : provider_update_observers_) {
+    for (ConfigurationPolicyProvider* provider : provider_update_pending_) {
+      provider_update_observer.OnProviderUpdatePropagated(provider);
+    }
+  }
+  provider_update_pending_.clear();
+}
+
 void PolicyServiceImpl::MergeAndTriggerUpdates() {
   // Merge from each provider in their order of priority.
   const PolicyNamespace chrome_namespace(POLICY_DOMAIN_CHROME, std::string());
@@ -310,11 +349,15 @@
 
   CheckInitializationComplete();
   CheckRefreshComplete();
+  NotifyProviderUpdatesPropagated();
 }
 
 void PolicyServiceImpl::CheckInitializationComplete() {
   DCHECK(thread_checker_.CalledOnValidThread());
 
+  if (initialization_throttled_)
+    return;
+
   // Check if all the providers just became initialized for each domain; if so,
   // notify that domain's observers.
   for (int domain = 0; domain < POLICY_DOMAIN_SIZE; ++domain) {
diff --git a/components/policy/core/common/policy_service_impl.h b/components/policy/core/common/policy_service_impl.h
index 4593394..f1a87a7 100644
--- a/components/policy/core/common/policy_service_impl.h
+++ b/components/policy/core/common/policy_service_impl.h
@@ -41,9 +41,13 @@
                    PolicyService::Observer* observer) override;
   void RemoveObserver(PolicyDomain domain,
                       PolicyService::Observer* observer) override;
+  void AddProviderUpdateObserver(ProviderUpdateObserver* observer) override;
+  void RemoveProviderUpdateObserver(ProviderUpdateObserver* observer) override;
+  bool HasProvider(ConfigurationPolicyProvider* provider) const override;
   const PolicyMap& GetPolicies(const PolicyNamespace& ns) const override;
   bool IsInitializationComplete(PolicyDomain domain) const override;
   void RefreshPolicies(const base::Closure& callback) override;
+  void SetInitializationThrottled(bool initialization_throttled) override;
 
  private:
   using Observers =
@@ -58,6 +62,8 @@
                               const PolicyMap& previous,
                               const PolicyMap& current);
 
+  void NotifyProviderUpdatesPropagated();
+
   // Combines the policies from all the providers, and notifies the observers
   // of namespaces whose policies have been modified.
   void MergeAndTriggerUpdates();
@@ -89,6 +95,23 @@
   // RefreshPolicies() call.
   std::vector<base::Closure> refresh_callbacks_;
 
+  // Observers for propagation of policy updates by
+  // ConfigurationPolicyProviders.
+  base::ObserverList<ProviderUpdateObserver> provider_update_observers_;
+
+  // Contains all ConfigurationPolicyProviders that have signaled a policy
+  // update which is still being processed (i.e. for which a notification to
+  // |provider_update_observers_| has not been sent out yet).
+  // Note that this is intentionally a set - if multiple updates from the same
+  // provider come in faster than they can be processed, they should only
+  // trigger one notification to |provider_update_observers_|.
+  std::set<ConfigurationPolicyProvider*> provider_update_pending_;
+
+  // If this is true, IsInitializationComplete should be returning false for all
+  // policy domains because the owner of this PolicyService is delaying the
+  // initialization signal.
+  bool initialization_throttled_ = false;
+
   // Used to verify thread-safe usage.
   base::ThreadChecker thread_checker_;
 
diff --git a/components/policy/core/common/policy_service_impl_unittest.cc b/components/policy/core/common/policy_service_impl_unittest.cc
index 694e4d60..4cd96e0 100644
--- a/components/policy/core/common/policy_service_impl_unittest.cc
+++ b/components/policy/core/common/policy_service_impl_unittest.cc
@@ -356,6 +356,35 @@
   EXPECT_TRUE(observer.observer_invoked());
 }
 
+TEST_F(PolicyServiceTest, HasProvider) {
+  MockConfigurationPolicyProvider other_provider;
+  EXPECT_TRUE(policy_service_->HasProvider(&provider0_));
+  EXPECT_TRUE(policy_service_->HasProvider(&provider1_));
+  EXPECT_TRUE(policy_service_->HasProvider(&provider2_));
+  EXPECT_FALSE(policy_service_->HasProvider(&other_provider));
+}
+
+TEST_F(PolicyServiceTest, NotifyProviderUpdateObserver) {
+  MockPolicyServiceProviderUpdateObserver provider_update_observer;
+  policy_service_->AddProviderUpdateObserver(&provider_update_observer);
+
+  policy0_.Set("aaa", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
+               POLICY_SOURCE_CLOUD, std::make_unique<base::Value>(123),
+               nullptr);
+  EXPECT_CALL(provider_update_observer,
+              OnProviderUpdatePropagated(&provider0_));
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&provider_update_observer);
+
+  // No changes, ProviderUpdateObserver still notified.
+  EXPECT_CALL(provider_update_observer,
+              OnProviderUpdatePropagated(&provider0_));
+  provider0_.UpdateChromePolicy(policy0_);
+  Mock::VerifyAndClearExpectations(&provider_update_observer);
+
+  policy_service_->RemoveProviderUpdateObserver(&provider_update_observer);
+}
+
 TEST_F(PolicyServiceTest, Priorities) {
   PolicyMap expected;
   expected.Set("pre", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto
index 74c7628..2a64a60c 100644
--- a/components/policy/proto/device_management_backend.proto
+++ b/components/policy/proto/device_management_backend.proto
@@ -850,6 +850,15 @@
   optional int64 timestamp = 3;
 }
 
+// Contains the Stateful Partition Information for user data storage in the
+// device.
+message StatefulPartitionInfo {
+  // Available space for user data storage in the device in bytes.
+  optional int64 available_space = 1;
+  // Total space for user data storage in the device in bytes.
+  optional int64 total_space = 2;
+}
+
 // Chrome release channel, shared for different reports.
 enum Channel {
   CHANNEL_UNKNOWN = 0;
@@ -1036,6 +1045,9 @@
 
   // Status for various system-wide non-hardware elements.
   optional SystemStatus system_status = 28;
+
+  // Stateful Partition Information for user data.
+  optional StatefulPartitionInfo stateful_partition_info = 29;
 }
 
 message OsUpdateStatus {
diff --git a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
index bf351c4..bf4cd6d 100644
--- a/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
+++ b/components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
@@ -230,17 +230,34 @@
     return;
 
   NSEventType type = [event type];
-  if ((type != NSKeyDown && type != NSKeyUp) || ![self hasViewsMenuActive]) {
-    [super sendEvent:event];
-    return;
+
+  // Draggable regions only respond to left-click dragging, but the system will
+  // still suppress right-clicks in a draggable region. Forwarding right-clicks
+  // allows the underlying views to respond to right-click to potentially bring
+  // up a frame context menu.
+  if (type == NSRightMouseDown) {
+    if ([[self contentView] hitTest:event.locationInWindow] == nil) {
+      [[self contentView] rightMouseDown:event];
+      return;
+    }
+  } else if (type == NSRightMouseUp) {
+    if ([[self contentView] hitTest:event.locationInWindow] == nil) {
+      [[self contentView] rightMouseUp:event];
+      return;
+    }
+  } else if ([self hasViewsMenuActive]) {
+    // Send to the menu, after converting the event into an action message using
+    // the content view.
+    if (type == NSKeyDown) {
+      [[self contentView] keyDown:event];
+      return;
+    } else if (type == NSKeyUp) {
+      [[self contentView] keyUp:event];
+      return;
+    }
   }
 
-  // Send to the menu, after converting the event into an action message using
-  // the content view.
-  if (type == NSKeyDown)
-    [[self contentView] keyDown:event];
-  else
-    [[self contentView] keyUp:event];
+  [super sendEvent:event];
 }
 
 // Override window order functions to intercept other visibility changes. This
diff --git a/components/safe_browsing/web_ui/resources/safe_browsing.css b/components/safe_browsing/web_ui/resources/safe_browsing.css
index 0b9eaf0..6e6c6e0b 100644
--- a/components/safe_browsing/web_ui/resources/safe_browsing.css
+++ b/components/safe_browsing/web_ui/resources/safe_browsing.css
@@ -29,6 +29,8 @@
   width: 100%;
   word-break:break-all;
   white-space:pre-wrap;
+  border: 1px solid #cecece;
+  border-radius: 3px;
 }
 table.request-response td {
   width: 50%;
diff --git a/components/safe_browsing/web_ui/resources/safe_browsing.js b/components/safe_browsing/web_ui/resources/safe_browsing.js
index cab3834..4722efb 100644
--- a/components/safe_browsing/web_ui/resources/safe_browsing.js
+++ b/components/safe_browsing/web_ui/resources/safe_browsing.js
@@ -99,10 +99,11 @@
 
     $('get-referrer-chain-form').addEventListener('submit', addReferrerChain);
 
-    // Allow tabs to be navigated to by anchor.
-    showTab(window.location.hash.substr(1));
+    // Allow tabs to be navigated to by fragment. The fragment with be of the
+    // format "#tab-<tab id>"
+    showTab(window.location.hash.substr(5));
     window.onhashchange = function () {
-      showTab(window.location.hash.substr(1));
+      showTab(window.location.hash.substr(5));
     };
 
     // When the tab updates, update the anchor
@@ -110,7 +111,7 @@
       var tabbox = $('tabbox');
       var tabs = tabbox.querySelector('tabs').children;
       var selectedTab = tabs[tabbox.selectedIndex];
-      window.location.hash = selectedTab.id;
+      window.location.hash = 'tab-' + selectedTab.id;
     }, true);
   }
 
@@ -207,8 +208,8 @@
     var row = $('pg-ping-list').insertRow();
     row.className = 'content';
     row.id = 'pg-ping-list-' + token;
-    row.insertCell();
-    row.insertCell();
+    row.insertCell().className = 'content';
+    row.insertCell().className = 'content';
   }
 
   function addPGPing(result) {
diff --git a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
index ac4e262..41c523f 100644
--- a/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
+++ b/components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.cc
@@ -130,7 +130,7 @@
     ad_frame_type = parent_is_ad ? blink::mojom::AdFrameType::kChildAd
                                  : blink::mojom::AdFrameType::kRootAd;
 
-  mojom::SubresourceFilterAgentAssociatedPtr agent;
+  mojo::AssociatedRemote<mojom::SubresourceFilterAgent> agent;
   frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&agent);
   agent->ActivateForNextCommittedLoad(filter->activation_state().Clone(),
                                       ad_frame_type);
diff --git a/components/subresource_filter/content/renderer/subresource_filter_agent.cc b/components/subresource_filter/content/renderer/subresource_filter_agent.cc
index 120997b..f80fc71f 100644
--- a/components/subresource_filter/content/renderer/subresource_filter_agent.cc
+++ b/components/subresource_filter/content/renderer/subresource_filter_agent.cc
@@ -42,8 +42,7 @@
     : content::RenderFrameObserver(render_frame),
       content::RenderFrameObserverTracker<SubresourceFilterAgent>(render_frame),
       ruleset_dealer_(ruleset_dealer),
-      ad_resource_tracker_(std::move(ad_resource_tracker)),
-      binding_(this) {
+      ad_resource_tracker_(std::move(ad_resource_tracker)) {
   DCHECK(ruleset_dealer);
   // |render_frame| can be nullptr in unit tests.
   if (render_frame) {
@@ -129,18 +128,18 @@
   activation_state_for_next_commit_ = mojom::ActivationState();
 }
 
-const mojom::SubresourceFilterHostAssociatedPtr&
+mojom::SubresourceFilterHost*
 SubresourceFilterAgent::GetSubresourceFilterHost() {
   if (!subresource_filter_host_) {
     render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
         &subresource_filter_host_);
   }
-  return subresource_filter_host_;
+  return subresource_filter_host_.get();
 }
 
 void SubresourceFilterAgent::OnSubresourceFilterAgentRequest(
-    mojom::SubresourceFilterAgentAssociatedRequest request) {
-  binding_.Bind(std::move(request));
+    mojo::PendingAssociatedReceiver<mojom::SubresourceFilterAgent> receiver) {
+  receiver_.Bind(std::move(receiver));
 }
 
 void SubresourceFilterAgent::ActivateForNextCommittedLoad(
diff --git a/components/subresource_filter/content/renderer/subresource_filter_agent.h b/components/subresource_filter/content/renderer/subresource_filter_agent.h
index 5e4f270..8630a818 100644
--- a/components/subresource_filter/content/renderer/subresource_filter_agent.h
+++ b/components/subresource_filter/content/renderer/subresource_filter_agent.h
@@ -14,7 +14,9 @@
 #include "components/subresource_filter/core/mojom/subresource_filter.mojom.h"
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "url/gurl.h"
 
 namespace blink {
@@ -88,10 +90,10 @@
       const mojom::ActivationState& activation_state);
   void ResetInfoForNextCommit();
 
-  const mojom::SubresourceFilterHostAssociatedPtr& GetSubresourceFilterHost();
+  mojom::SubresourceFilterHost* GetSubresourceFilterHost();
 
   void OnSubresourceFilterAgentRequest(
-      mojom::SubresourceFilterAgentAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<mojom::SubresourceFilterAgent> receiver);
 
   // content::RenderFrameObserver:
   void OnDestruct() override;
@@ -110,9 +112,9 @@
 
   // Use associated interface to make sure mojo messages are ordered with regard
   // to legacy IPC messages.
-  mojom::SubresourceFilterHostAssociatedPtr subresource_filter_host_;
+  mojo::AssociatedRemote<mojom::SubresourceFilterHost> subresource_filter_host_;
 
-  mojo::AssociatedBinding<mojom::SubresourceFilterAgent> binding_;
+  mojo::AssociatedReceiver<mojom::SubresourceFilterAgent> receiver_{this};
 
   // If a document has been created for this frame before. The first document
   // for a new local subframe should be about:blank.
diff --git a/components/update_client/updater_state_mac.mm b/components/update_client/updater_state_mac.mm
index a7bceb6..9ffb980 100644
--- a/components/update_client/updater_state_mac.mm
+++ b/components/update_client/updater_state_mac.mm
@@ -8,7 +8,6 @@
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/mac/foundation_util.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/version.h"
@@ -45,29 +44,28 @@
 }
 
 base::Version GetVersionFromPlist(const base::FilePath& info_plist) {
-  base::mac::ScopedNSAutoreleasePool scoped_pool;
-  NSData* data =
-      [NSData dataWithContentsOfFile:
-          base::mac::FilePathToNSString(info_plist)];
-  if ([data length] == 0) {
-    return base::Version();
+  @autoreleasepool {
+    NSData* data = [NSData
+        dataWithContentsOfFile:base::mac::FilePathToNSString(info_plist)];
+    if ([data length] == 0) {
+      return base::Version();
+    }
+    NSDictionary* all_keys =
+        base::mac::ObjCCastStrict<NSDictionary>([NSPropertyListSerialization
+            propertyListWithData:data
+                         options:NSPropertyListImmutable
+                          format:nil
+                           error:nil]);
+    if (all_keys == nil) {
+      return base::Version();
+    }
+    CFStringRef version = base::mac::GetValueFromDictionary<CFStringRef>(
+        base::mac::NSToCFCast(all_keys), kCFBundleVersionKey);
+    if (version == NULL) {
+      return base::Version();
+    }
+    return base::Version(base::SysCFStringRefToUTF8(version));
   }
-  NSDictionary* all_keys = base::mac::ObjCCastStrict<NSDictionary>(
-      [NSPropertyListSerialization propertyListWithData:data
-          options:NSPropertyListImmutable
-           format:nil
-            error:nil]);
-  if (all_keys == nil) {
-    return base::Version();
-  }
-  CFStringRef version =
-      base::mac::GetValueFromDictionary<CFStringRef>(
-          base::mac::NSToCFCast(all_keys),
-          kCFBundleVersionKey);
-  if (version == NULL) {
-    return base::Version();
-  }
-  return base::Version(base::SysCFStringRefToUTF8(version));
 }
 
 }  // namespace
diff --git a/content/browser/media/encrypted_media_browsertest.cc b/content/browser/media/encrypted_media_browsertest.cc
index 53c2eccd..2993f08 100644
--- a/content/browser/media/encrypted_media_browsertest.cc
+++ b/content/browser/media/encrypted_media_browsertest.cc
@@ -271,7 +271,9 @@
   TestSimplePlayback("bear-320x240-v-vp9_profile2_subsample_cenc-v.webm");
 }
 
-IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoOnly_MP4_VP9Profile2) {
+// Flaky, disabled. https://crbug.com/1001364.
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest,
+                       DISABLED_Playback_VideoOnly_MP4_VP9Profile2) {
   // MP4 without MSE is not support yet, http://crbug.com/170793.
   if (CurrentSourceType() != SrcType::MSE) {
     DVLOG(0) << "Skipping test; Can only play MP4 encrypted streams by MSE.";
@@ -286,7 +288,9 @@
   TestSimplePlayback("bear-av1-cenc.webm");
 }
 
-IN_PROC_BROWSER_TEST_P(EncryptedMediaTest, Playback_VideoOnly_WebM_AV1_10bit) {
+// Flaky, disabled. https://crbug.com/1001364.
+IN_PROC_BROWSER_TEST_P(EncryptedMediaTest,
+                       DISABLED_Playback_VideoOnly_WebM_AV1_10bit) {
   TestSimplePlayback("bear-av1-320x180-10bit-cenc.webm");
 }
 
diff --git a/content/browser/media/media_browsertest.cc b/content/browser/media/media_browsertest.cc
index 8c57976..6be88ad 100644
--- a/content/browser/media/media_browsertest.cc
+++ b/content/browser/media/media_browsertest.cc
@@ -181,7 +181,8 @@
   PlayVideo("bear-320x180-hi10p-vp9.webm", GetParam());
 }
 
-IN_PROC_BROWSER_TEST_P(MediaTest, VideoBear12DepthVP9) {
+// Flaky, disabled. https://crbug.com/1001364.
+IN_PROC_BROWSER_TEST_P(MediaTest, DISABLED_VideoBear12DepthVP9) {
   PlayVideo("bear-320x180-hi12p-vp9.webm", GetParam());
 }
 #endif
@@ -233,7 +234,8 @@
 
 #if !defined(OS_ANDROID)
 // Android devices usually only support baseline, main and high.
-IN_PROC_BROWSER_TEST_P(MediaTest, VideoBearHighBitDepthMp4) {
+// Flaky, disabled. https://crbug.com/1001364.
+IN_PROC_BROWSER_TEST_P(MediaTest, DISABLED_VideoBearHighBitDepthMp4) {
   PlayVideo("bear-320x180-hi10p.mp4", GetParam());
 }
 
diff --git a/content/browser/media/session/audio_focus_delegate.h b/content/browser/media/session/audio_focus_delegate.h
index 38eb74a..c771254 100644
--- a/content/browser/media/session/audio_focus_delegate.h
+++ b/content/browser/media/session/audio_focus_delegate.h
@@ -7,6 +7,10 @@
 
 #include "services/media_session/public/mojom/audio_focus.mojom.h"
 
+namespace base {
+class UnguessableToken;
+}  // namespace base
+
 namespace content {
 
 class MediaSessionImpl;
@@ -38,6 +42,9 @@
   // |MediaSession| should call this when it's state changes.
   virtual void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr) = 0;
+
+  // Retrieves the current request ID for the associated |MediaSession|.
+  virtual const base::UnguessableToken& request_id() const = 0;
 };
 
 }  // namespace content
diff --git a/content/browser/media/session/audio_focus_delegate_android.cc b/content/browser/media/session/audio_focus_delegate_android.cc
index 56344b84..dbc8bb36 100644
--- a/content/browser/media/session/audio_focus_delegate_android.cc
+++ b/content/browser/media/session/audio_focus_delegate_android.cc
@@ -5,6 +5,7 @@
 #include "content/browser/media/session/audio_focus_delegate_android.h"
 
 #include "base/android/jni_android.h"
+#include "base/unguessable_token.h"
 #include "content/browser/media/session/media_session_impl.h"
 #include "content/public/android/content_jni_headers/AudioFocusDelegate_jni.h"
 #include "media/base/media_switches.h"
@@ -60,6 +61,10 @@
              : media_session::mojom::AudioFocusType::kGain;
 }
 
+const base::UnguessableToken& AudioFocusDelegateAndroid::request_id() const {
+  return base::UnguessableToken::Null();
+}
+
 void AudioFocusDelegateAndroid::OnSuspend(JNIEnv*,
                                           const JavaParamRef<jobject>&) {
   if (!media_session_->IsActive() ||
diff --git a/content/browser/media/session/audio_focus_delegate_android.h b/content/browser/media/session/audio_focus_delegate_android.h
index e8f4436..676b760 100644
--- a/content/browser/media/session/audio_focus_delegate_android.h
+++ b/content/browser/media/session/audio_focus_delegate_android.h
@@ -32,6 +32,7 @@
   void AbandonAudioFocus() override;
   base::Optional<media_session::mojom::AudioFocusType> GetCurrentFocusType()
       const override;
+  const base::UnguessableToken& request_id() const override;
 
   // Called when the Android system requests the MediaSession to be suspended.
   // Called by Java through JNI.
diff --git a/content/browser/media/session/audio_focus_delegate_default.cc b/content/browser/media/session/audio_focus_delegate_default.cc
index 3570c8fc..516c4754 100644
--- a/content/browser/media/session/audio_focus_delegate_default.cc
+++ b/content/browser/media/session/audio_focus_delegate_default.cc
@@ -52,6 +52,9 @@
       const override;
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr) override;
+  const base::UnguessableToken& request_id() const override {
+    return request_id_;
+  }
 
  private:
   // Finishes an async audio focus request.
diff --git a/content/browser/media/session/media_session_impl.cc b/content/browser/media/session/media_session_impl.cc
index db2159b..69d8f81 100644
--- a/content/browser/media/session/media_session_impl.cc
+++ b/content/browser/media/session/media_session_impl.cc
@@ -186,6 +186,20 @@
 }
 
 // static
+WebContents* MediaSession::GetWebContentsFromRequestId(
+    const base::UnguessableToken& request_id) {
+  DCHECK_NE(base::UnguessableToken::Null(), request_id);
+  for (WebContentsImpl* web_contents : WebContentsImpl::GetAllWebContents()) {
+    MediaSessionImpl* session = MediaSessionImpl::FromWebContents(web_contents);
+    if (!session)
+      continue;
+    if (session->GetRequestId() == request_id)
+      return web_contents;
+  }
+  return nullptr;
+}
+
+// static
 MediaSessionImpl* MediaSessionImpl::Get(WebContents* web_contents) {
   MediaSessionImpl* session = FromWebContents(web_contents);
   if (!session) {
@@ -1263,6 +1277,10 @@
       ->source_id();
 }
 
+const base::UnguessableToken& MediaSessionImpl::GetRequestId() const {
+  return delegate_->request_id();
+}
+
 void MediaSessionImpl::RebuildAndNotifyActionsChanged() {
   std::set<media_session::mojom::MediaSessionAction> actions =
       routed_service_ ? routed_service_->actions()
diff --git a/content/browser/media/session/media_session_impl.h b/content/browser/media/session/media_session_impl.h
index 391c7dc..d06e5d4 100644
--- a/content/browser/media/session/media_session_impl.h
+++ b/content/browser/media/session/media_session_impl.h
@@ -268,6 +268,9 @@
   // context together.
   CONTENT_EXPORT const base::UnguessableToken& GetSourceId() const;
 
+  // Returns the Audio Focus request ID associated with this media session.
+  const base::UnguessableToken& GetRequestId() const;
+
  private:
   friend class content::WebContentsUserData<MediaSessionImpl>;
   friend class ::MediaSessionImplBrowserTest;
diff --git a/content/browser/media/session/media_session_impl_browsertest.cc b/content/browser/media/session/media_session_impl_browsertest.cc
index c1edace..32916c6 100644
--- a/content/browser/media/session/media_session_impl_browsertest.cc
+++ b/content/browser/media/session/media_session_impl_browsertest.cc
@@ -85,6 +85,8 @@
   void MediaSessionInfoChanged(
       media_session::mojom::MediaSessionInfoPtr session_info) override {}
 
+  MOCK_CONST_METHOD0(request_id, const base::UnguessableToken&());
+
   void ResolveRequest(bool result) {
     if (!async_mode_)
       return;
diff --git a/content/browser/media/session/media_session_impl_unittest.cc b/content/browser/media/session/media_session_impl_unittest.cc
index 14235533..1ee07ae 100644
--- a/content/browser/media/session/media_session_impl_unittest.cc
+++ b/content/browser/media/session/media_session_impl_unittest.cc
@@ -62,6 +62,8 @@
     session_info_ = std::move(session_info);
   }
 
+  MOCK_CONST_METHOD0(request_id, const base::UnguessableToken&());
+
   MediaSessionInfo::SessionState GetState() const {
     DCHECK(!session_info_.is_null());
     return session_info_->state;
diff --git a/content/browser/renderer_host/frame_connector_delegate.cc b/content/browser/renderer_host/frame_connector_delegate.cc
index 9ae1454..9f728c4 100644
--- a/content/browser/renderer_host/frame_connector_delegate.cc
+++ b/content/browser/renderer_host/frame_connector_delegate.cc
@@ -48,6 +48,8 @@
   render_widget_host->SetPageScaleState(
       visual_properties.page_scale_factor,
       visual_properties.is_pinch_gesture_active);
+  render_widget_host->SetCompositorViewport(
+      visual_properties.compositor_viewport);
 
   render_widget_host->SynchronizeVisualProperties();
 }
diff --git a/content/browser/renderer_host/input/synthetic_gesture_target_mac.mm b/content/browser/renderer_host/input/synthetic_gesture_target_mac.mm
index 449627c..d96301d 100644
--- a/content/browser/renderer_host/input/synthetic_gesture_target_mac.mm
+++ b/content/browser/renderer_host/input/synthetic_gesture_target_mac.mm
@@ -4,7 +4,6 @@
 
 #include "content/browser/renderer_host/input/synthetic_gesture_target_mac.h"
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "content/browser/renderer_host/render_widget_host_impl.h"
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
 #import "content/app_shim_remote_cocoa/render_widget_host_view_cocoa.h"
@@ -88,42 +87,42 @@
     const ui::LatencyInfo& latency_info) {
   // Create an autorelease pool so that we clean up any synthetic events we
   // generate.
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    NSPoint content_local = NSMakePoint(
+        web_gesture.PositionInWidget().x,
+        [cocoa_view_ frame].size.height - web_gesture.PositionInWidget().y);
+    NSPoint location_in_window = [cocoa_view_ convertPoint:content_local
+                                                    toView:nil];
 
-  NSPoint content_local = NSMakePoint(
-      web_gesture.PositionInWidget().x,
-      [cocoa_view_ frame].size.height - web_gesture.PositionInWidget().y);
-  NSPoint location_in_window =
-      [cocoa_view_ convertPoint:content_local toView:nil];
-
-  switch (web_gesture.GetType()) {
-    case WebInputEvent::kGesturePinchBegin: {
-      id cocoa_event =
-          [SyntheticPinchEvent eventWithMagnification:0.0f
-                                     locationInWindow:location_in_window
-                                                phase:NSEventPhaseBegan];
-      [cocoa_view_ handleBeginGestureWithEvent:cocoa_event
-                       isSyntheticallyInjected:YES];
-      return;
+    switch (web_gesture.GetType()) {
+      case WebInputEvent::kGesturePinchBegin: {
+        id cocoa_event =
+            [SyntheticPinchEvent eventWithMagnification:0.0f
+                                       locationInWindow:location_in_window
+                                                  phase:NSEventPhaseBegan];
+        [cocoa_view_ handleBeginGestureWithEvent:cocoa_event
+                         isSyntheticallyInjected:YES];
+        return;
+      }
+      case WebInputEvent::kGesturePinchEnd: {
+        id cocoa_event =
+            [SyntheticPinchEvent eventWithMagnification:0.0f
+                                       locationInWindow:location_in_window
+                                                  phase:NSEventPhaseEnded];
+        [cocoa_view_ handleEndGestureWithEvent:cocoa_event];
+        return;
+      }
+      case WebInputEvent::kGesturePinchUpdate: {
+        id cocoa_event = [SyntheticPinchEvent
+            eventWithMagnification:web_gesture.data.pinch_update.scale - 1.0f
+                  locationInWindow:location_in_window
+                             phase:NSEventPhaseChanged];
+        [cocoa_view_ magnifyWithEvent:cocoa_event];
+        return;
+      }
+      default:
+        NOTREACHED();
     }
-    case WebInputEvent::kGesturePinchEnd: {
-      id cocoa_event =
-          [SyntheticPinchEvent eventWithMagnification:0.0f
-                                     locationInWindow:location_in_window
-                                                phase:NSEventPhaseEnded];
-      [cocoa_view_ handleEndGestureWithEvent:cocoa_event];
-      return;
-    }
-    case WebInputEvent::kGesturePinchUpdate: {
-      id cocoa_event = [SyntheticPinchEvent
-          eventWithMagnification:web_gesture.data.pinch_update.scale - 1.0f
-                locationInWindow:location_in_window
-                           phase:NSEventPhaseChanged];
-      [cocoa_view_ magnifyWithEvent:cocoa_event];
-      return;
-    }
-    default:
-      NOTREACHED();
   }
 }
 
diff --git a/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm b/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm
index bfaf5ba..91fd4ca3 100644
--- a/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm
+++ b/content/browser/renderer_host/pepper/pepper_truetype_font_list_mac.mm
@@ -6,7 +6,6 @@
 
 #import <Cocoa/Cocoa.h>
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/stl_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "ppapi/c/dev/ppb_truetype_font_dev.h"
@@ -36,47 +35,49 @@
 }  // namespace
 
 void GetFontFamilies_SlowBlocking(std::vector<std::string>* font_families) {
-  base::mac::ScopedNSAutoreleasePool autorelease_pool;
-  NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
-  NSArray* fonts = [fontManager availableFontFamilies];
-  font_families->reserve([fonts count]);
-  for (NSString* family_name in fonts)
-    font_families->push_back(base::SysNSStringToUTF8(family_name));
+  @autoreleasepool {
+    NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
+    NSArray* fonts = [fontManager availableFontFamilies];
+    font_families->reserve([fonts count]);
+    for (NSString* family_name in fonts)
+      font_families->push_back(base::SysNSStringToUTF8(family_name));
+  }
 }
 
 void GetFontsInFamily_SlowBlocking(
     const std::string& family,
     std::vector<ppapi::proxy::SerializedTrueTypeFontDesc>* fonts_in_family) {
-  base::mac::ScopedNSAutoreleasePool autorelease_pool;
-  NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
-  NSString* ns_family = base::SysUTF8ToNSString(family);
-  NSArray* ns_fonts_in_family =
-      [fontManager availableMembersOfFontFamily:ns_family];
+  @autoreleasepool {
+    NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
+    NSString* ns_family = base::SysUTF8ToNSString(family);
+    NSArray* ns_fonts_in_family =
+        [fontManager availableMembersOfFontFamily:ns_family];
 
-  for (NSArray* font_info in ns_fonts_in_family) {
-    ppapi::proxy::SerializedTrueTypeFontDesc desc;
-    desc.family = family;
-    NSInteger font_weight = [[font_info objectAtIndex:2] intValue];
-    font_weight = std::max(static_cast<NSInteger>(0), font_weight);
-    font_weight = std::min(kPepperFontWeightsLength - 1, font_weight);
-    desc.weight = kPepperFontWeights[font_weight];
+    for (NSArray* font_info in ns_fonts_in_family) {
+      ppapi::proxy::SerializedTrueTypeFontDesc desc;
+      desc.family = family;
+      NSInteger font_weight = [[font_info objectAtIndex:2] intValue];
+      font_weight = std::max(static_cast<NSInteger>(0), font_weight);
+      font_weight = std::min(kPepperFontWeightsLength - 1, font_weight);
+      desc.weight = kPepperFontWeights[font_weight];
 
-    NSFontTraitMask font_traits =
-        [[font_info objectAtIndex:3] unsignedIntValue];
-    desc.style = PP_TRUETYPEFONTSTYLE_NORMAL;
-    if (font_traits & NSItalicFontMask)
-      desc.style = PP_TRUETYPEFONTSTYLE_ITALIC;
+      NSFontTraitMask font_traits =
+          [[font_info objectAtIndex:3] unsignedIntValue];
+      desc.style = PP_TRUETYPEFONTSTYLE_NORMAL;
+      if (font_traits & NSItalicFontMask)
+        desc.style = PP_TRUETYPEFONTSTYLE_ITALIC;
 
-    desc.width = PP_TRUETYPEFONTWIDTH_NORMAL;
-    if (font_traits & NSCondensedFontMask)
-      desc.width = PP_TRUETYPEFONTWIDTH_CONDENSED;
-    else if (font_traits & NSExpandedFontMask)
-      desc.width = PP_TRUETYPEFONTWIDTH_EXPANDED;
+      desc.width = PP_TRUETYPEFONTWIDTH_NORMAL;
+      if (font_traits & NSCondensedFontMask)
+        desc.width = PP_TRUETYPEFONTWIDTH_CONDENSED;
+      else if (font_traits & NSExpandedFontMask)
+        desc.width = PP_TRUETYPEFONTWIDTH_EXPANDED;
 
-    // Mac doesn't support requesting non-default character sets.
-    desc.charset = PP_TRUETYPEFONTCHARSET_DEFAULT;
+      // Mac doesn't support requesting non-default character sets.
+      desc.charset = PP_TRUETYPEFONTCHARSET_DEFAULT;
 
-    fonts_in_family->push_back(desc);
+      fonts_in_family->push_back(desc);
+    }
   }
 }
 
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 39f1820..d7055ce 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -809,8 +809,16 @@
     visual_properties.new_size = view_->GetRequestedRendererSize();
     visual_properties.capture_sequence_number =
         view_->GetCaptureSequenceNumber();
-    visual_properties.compositor_viewport_pixel_size =
-        view_->GetCompositorViewportPixelSize();
+    // For OOPIFs, use the compositor viewport received from the FrameConnector.
+    visual_properties.compositor_viewport_pixel_rect =
+        view_->IsRenderWidgetHostViewChildFrame() &&
+                !view_->IsRenderWidgetHostViewGuest()
+            ? gfx::ScaleToEnclosingRect(
+                  compositor_viewport_,
+                  IsUseZoomForDSFEnabled()
+                      ? 1.f
+                      : visual_properties.screen_info.device_scale_factor)
+            : gfx::Rect(view_->GetCompositorViewportPixelSize());
     visual_properties.visible_viewport_size = view_->GetVisibleViewportSize();
     // TODO(ccameron): GetLocalSurfaceId is not synchronized with the device
     // scale factor of the surface. Fix this.
@@ -1984,6 +1992,11 @@
   is_pinch_gesture_active_ = is_pinch_gesture_active;
 }
 
+void RenderWidgetHostImpl::SetCompositorViewport(
+    const gfx::Rect& compositor_viewport) {
+  compositor_viewport_ = compositor_viewport;
+}
+
 void RenderWidgetHostImpl::Destroy(bool also_delete) {
   DCHECK(!destroyed_);
   destroyed_ = true;
@@ -2240,8 +2253,8 @@
                new_visual_properties.max_size_for_auto_resize)) ||
          (!old_visual_properties.auto_resize_enabled &&
           (old_visual_properties.new_size != new_visual_properties.new_size ||
-           (old_visual_properties.compositor_viewport_pixel_size.IsEmpty() &&
-            !new_visual_properties.compositor_viewport_pixel_size.IsEmpty())));
+           (old_visual_properties.compositor_viewport_pixel_rect.IsEmpty() &&
+            !new_visual_properties.compositor_viewport_pixel_rect.IsEmpty())));
 }
 
 // static
@@ -2256,7 +2269,7 @@
       g_check_for_pending_visual_properties_ack &&
       !new_visual_properties.auto_resize_enabled &&
       !new_visual_properties.new_size.IsEmpty() &&
-      !new_visual_properties.compositor_viewport_pixel_size.IsEmpty() &&
+      !new_visual_properties.compositor_viewport_pixel_rect.IsEmpty() &&
       new_visual_properties.local_surface_id_allocation;
 
   // If acking is applicable, then check if there has been an
@@ -2304,8 +2317,10 @@
   return zoom_changed || size_changed || parent_local_surface_id_changed ||
          old_visual_properties->screen_info !=
              new_visual_properties.screen_info ||
-         old_visual_properties->compositor_viewport_pixel_size !=
-             new_visual_properties.compositor_viewport_pixel_size ||
+         old_visual_properties->compositor_viewport_pixel_rect !=
+             new_visual_properties.compositor_viewport_pixel_rect ||
+         old_visual_properties->compositor_viewport_pixel_rect !=
+             new_visual_properties.compositor_viewport_pixel_rect ||
          old_visual_properties->is_fullscreen_granted !=
              new_visual_properties.is_fullscreen_granted ||
          old_visual_properties->display_mode !=
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index a3f9a2d..3620b461 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -598,6 +598,9 @@
   // properties of this widget.
   VisualProperties GetVisualProperties();
 
+  // Tracks the compositor viewport requested for an OOPIF subframe.
+  void SetCompositorViewport(const gfx::Rect& compositor_viewport);
+
   // Sets the |visual_properties| that were sent to the renderer bundled with
   // the request to create a new RenderWidget.
   void SetInitialVisualProperties(const VisualProperties& visual_properties);
@@ -1079,6 +1082,8 @@
   // True when the renderer is currently undergoing a pinch-zoom gesture.
   bool is_pinch_gesture_active_ = false;
 
+  gfx::Rect compositor_viewport_;
+
   bool waiting_for_screen_rects_ack_ = false;
   gfx::Rect last_view_screen_rect_;
   gfx::Rect last_window_screen_rect_;
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 3573e84..a736944d 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -1572,14 +1572,15 @@
 
 TEST_F(RenderWidgetHostTest, VisualProperties) {
   gfx::Rect bounds(0, 0, 100, 100);
-  gfx::Size compositor_viewport_pixel_size(40, 50);
+  gfx::Rect compositor_viewport_pixel_rect(40, 50);
   view_->SetBounds(bounds);
-  view_->SetMockCompositorViewportPixelSize(compositor_viewport_pixel_size);
+  view_->SetMockCompositorViewportPixelSize(
+      compositor_viewport_pixel_rect.size());
 
   VisualProperties visual_properties = host_->GetVisualProperties();
   EXPECT_EQ(bounds.size(), visual_properties.new_size);
-  EXPECT_EQ(compositor_viewport_pixel_size,
-            visual_properties.compositor_viewport_pixel_size);
+  EXPECT_EQ(compositor_viewport_pixel_rect,
+            visual_properties.compositor_viewport_pixel_rect);
 }
 
 // Make sure no dragging occurs after renderer exited. See crbug.com/704832.
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index 2b0a01c..f97fbd3c 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -2603,7 +2603,8 @@
     EXPECT_EQ("100x100", std::get<0>(params).new_size.ToString());  // dip size
     EXPECT_EQ("100x100",
               std::get<0>(params)
-                  .compositor_viewport_pixel_size.ToString());  // backing size
+                  .compositor_viewport_pixel_rect.size()
+                  .ToString());  // backing size
   }
 
   widget_host_->ResetSentVisualProperties();
diff --git a/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc b/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
index 9dcb4c5..d426adda 100644
--- a/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_child_frame_unittest.cc
@@ -275,8 +275,8 @@
 
   widget_host_->Init();
 
-  constexpr gfx::Size compositor_viewport_pixel_size(100, 100);
-  constexpr gfx::Rect screen_space_rect(compositor_viewport_pixel_size);
+  constexpr gfx::Rect compositor_viewport_pixel_rect(100, 100);
+  constexpr gfx::Rect screen_space_rect(compositor_viewport_pixel_rect);
   viz::ParentLocalSurfaceIdAllocator allocator;
   allocator.GenerateId();
   viz::LocalSurfaceIdAllocation local_surface_id_allocation =
@@ -287,7 +287,8 @@
 
   FrameVisualProperties visual_properties;
   visual_properties.screen_space_rect = screen_space_rect;
-  visual_properties.local_frame_size = compositor_viewport_pixel_size;
+  visual_properties.compositor_viewport = compositor_viewport_pixel_rect;
+  visual_properties.local_frame_size = compositor_viewport_pixel_rect.size();
   visual_properties.capture_sequence_number = 123u;
   visual_properties.local_surface_id_allocation = local_surface_id_allocation;
   test_frame_connector_->SynchronizeVisualProperties(frame_sink_id,
@@ -300,8 +301,8 @@
   ASSERT_NE(nullptr, resize_msg);
   WidgetMsg_SynchronizeVisualProperties::Param params;
   WidgetMsg_SynchronizeVisualProperties::Read(resize_msg, &params);
-  EXPECT_EQ(compositor_viewport_pixel_size,
-            std::get<0>(params).compositor_viewport_pixel_size);
+  EXPECT_EQ(compositor_viewport_pixel_rect,
+            std::get<0>(params).compositor_viewport_pixel_rect);
   EXPECT_EQ(screen_space_rect.size(), std::get<0>(params).new_size);
   EXPECT_EQ(local_surface_id_allocation,
             std::get<0>(params).local_surface_id_allocation);
diff --git a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
index ce90b1dd..38ded99 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper_unittest.mm
@@ -140,45 +140,45 @@
   supported_factors.push_back(ui::SCALE_FACTOR_100P);
   ui::test::ScopedSetSupportedScaleFactors scoped_supported(supported_factors);
 
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    int32_t routing_id = process_host->GetNextRoutingID();
+    mojom::WidgetPtr widget;
+    std::unique_ptr<MockWidgetImpl> widget_impl =
+        std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget));
 
-  int32_t routing_id = process_host->GetNextRoutingID();
-  mojom::WidgetPtr widget;
-  std::unique_ptr<MockWidgetImpl> widget_impl =
-      std::make_unique<MockWidgetImpl>(mojo::MakeRequest(&widget));
+    RenderWidgetHostImpl* render_widget = new RenderWidgetHostImpl(
+        &delegate, process_host, routing_id, std::move(widget), false);
 
-  RenderWidgetHostImpl* render_widget = new RenderWidgetHostImpl(
-      &delegate, process_host, routing_id, std::move(widget), false);
+    ui::WindowResizeHelperMac::Get()->Init(base::ThreadTaskRunnerHandle::Get());
 
-  ui::WindowResizeHelperMac::Get()->Init(base::ThreadTaskRunnerHandle::Get());
+    // Owned by its |GetInProcessNSView()|, i.e. |rwhv_cocoa|.
+    RenderWidgetHostViewMac* rwhv_mac =
+        new RenderWidgetHostViewMac(render_widget, false);
+    base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa(
+        [rwhv_mac->GetInProcessNSView() retain]);
 
-  // Owned by its |GetInProcessNSView()|, i.e. |rwhv_cocoa|.
-  RenderWidgetHostViewMac* rwhv_mac = new RenderWidgetHostViewMac(
-      render_widget, false);
-  base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa(
-      [rwhv_mac->GetInProcessNSView() retain]);
+    RenderWidgetHostViewMacEditCommandHelper helper;
+    NSArray* edit_command_strings = helper.GetEditSelectorNames();
+    RenderWidgetHostNSViewHostOwner* rwhwvm_owner =
+        [[[RenderWidgetHostNSViewHostOwner alloc]
+            initWithRenderWidgetHostViewMac:rwhv_mac] autorelease];
 
-  RenderWidgetHostViewMacEditCommandHelper helper;
-  NSArray* edit_command_strings = helper.GetEditSelectorNames();
-  RenderWidgetHostNSViewHostOwner* rwhwvm_owner =
-      [[[RenderWidgetHostNSViewHostOwner alloc]
-          initWithRenderWidgetHostViewMac:rwhv_mac] autorelease];
+    helper.AddEditingSelectorsToClass([rwhwvm_owner class]);
 
-  helper.AddEditingSelectorsToClass([rwhwvm_owner class]);
+    for (NSString* edit_command_name in edit_command_strings) {
+      NSString* sel_str = [edit_command_name stringByAppendingString:@":"];
+      [rwhwvm_owner performSelector:NSSelectorFromString(sel_str)
+                         withObject:nil];
+    }
 
-  for (NSString* edit_command_name in edit_command_strings) {
-    NSString* sel_str = [edit_command_name stringByAppendingString:@":"];
-    [rwhwvm_owner performSelector:NSSelectorFromString(sel_str) withObject:nil];
+    size_t num_edit_commands = [edit_command_strings count];
+    EXPECT_EQ(delegate.edit_command_message_count_, num_edit_commands);
+    rwhv_cocoa.reset();
+
+    // The |render_widget|'s process needs to be deleted within |message_loop|.
+    delete render_widget;
   }
 
-  size_t num_edit_commands = [edit_command_strings count];
-  EXPECT_EQ(delegate.edit_command_message_count_, num_edit_commands);
-  rwhv_cocoa.reset();
-  pool.Recycle();
-
-  // The |render_widget|'s process needs to be deleted within |message_loop|.
-  delete render_widget;
-
   ui::WindowResizeHelperMac::Get()->ShutdownForTests();
 }
 
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
index e6d4bc4..472d8f4 100644
--- a/content/browser/site_per_process_browsertest.cc
+++ b/content/browser/site_per_process_browsertest.cc
@@ -15266,4 +15266,86 @@
   }
 }
 
+class SitePerProcessCompositorViewportBrowserTest
+    : public SitePerProcessBrowserTest,
+      public testing::WithParamInterface<double> {
+ public:
+  SitePerProcessCompositorViewportBrowserTest() {}
+
+ protected:
+  void SetUpCommandLine(base::CommandLine* command_line) override {
+    SitePerProcessBrowserTest::SetUpCommandLine(command_line);
+    command_line->AppendSwitchASCII(switches::kForceDeviceScaleFactor,
+                                    base::StringPrintf("%f", GetParam()));
+  }
+};
+
+IN_PROC_BROWSER_TEST_P(SitePerProcessCompositorViewportBrowserTest,
+                       OopifCompositorViewportSizeRelativeToParent) {
+  // Load page with very tall OOPIF.
+  GURL main_url(
+      embedded_test_server()->GetURL("a.com", "/super_tall_parent.html"));
+  EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+  // It is safe to obtain the root frame tree node here, as it doesn't change.
+  FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
+                            ->GetFrameTree()
+                            ->root();
+  ASSERT_EQ(1U, root->child_count());
+
+  FrameTreeNode* child = root->child_at(0);
+
+  GURL nested_site_url(
+      embedded_test_server()->GetURL("b.com", "/super_tall_page.html"));
+  NavigateFrameToURL(child, nested_site_url);
+
+  EXPECT_EQ(
+      " Site A ------------ proxies for B\n"
+      "   +--Site B ------- proxies for A\n"
+      "Where A = http://a.com/\n"
+      "      B = http://b.com/",
+      DepictFrameTree(root));
+
+  // Observe frame submission from parent.
+  RenderFrameSubmissionObserver parent_observer(
+      root->current_frame_host()
+          ->GetRenderWidgetHost()
+          ->render_frame_metadata_provider());
+  parent_observer.WaitForAnyFrameSubmission();
+  gfx::Size parent_viewport_size =
+      parent_observer.LastRenderFrameMetadata().viewport_size_in_pixels;
+
+  // Observe frame submission from child.
+  RenderFrameSubmissionObserver child_observer(
+      child->current_frame_host()
+          ->GetRenderWidgetHost()
+          ->render_frame_metadata_provider());
+  child_observer.WaitForAnyFrameSubmission();
+  gfx::Size child_viewport_size =
+      child_observer.LastRenderFrameMetadata().viewport_size_in_pixels;
+
+  // Verify child's compositor viewport is no more than about 30% larger than
+  // the parent's. See RemoteFrameView::GetCompositingRect() for explanation of
+  // the choice of 30%. Add +1 to child viewport height to account for rounding.
+  EXPECT_GE(1.3f * parent_viewport_size.height(),
+            1.f * (child_viewport_size.height() - 1));
+
+  // Verify the child's ViewBounds are much larger.
+  RenderWidgetHostViewBase* child_rwhv = static_cast<RenderWidgetHostViewBase*>(
+      child->current_frame_host()->GetRenderWidgetHost()->GetView());
+  // 30,000 is based on div/iframe sizes in the test HTML files.
+  EXPECT_LT(30000, child_rwhv->GetViewBounds().height());
+}
+
+#if defined(OS_ANDROID)
+// Android doesn't support forcing device scale factor in tests.
+INSTANTIATE_TEST_SUITE_P(SitePerProcess,
+                         SitePerProcessCompositorViewportBrowserTest,
+                         testing::Values(1.0));
+#else
+INSTANTIATE_TEST_SUITE_P(SitePerProcess,
+                         SitePerProcessCompositorViewportBrowserTest,
+                         testing::Values(1.0, 1.5, 2.0));
+#endif
+
 }  // namespace content
diff --git a/content/browser/tracing/background_startup_tracing_observer.cc b/content/browser/tracing/background_startup_tracing_observer.cc
index 89b2ea39..1590187 100644
--- a/content/browser/tracing/background_startup_tracing_observer.cc
+++ b/content/browser/tracing/background_startup_tracing_observer.cc
@@ -66,6 +66,11 @@
     return;
   const BackgroundTracingRule* startup_rule = FindStartupRuleInConfig(*config);
   DCHECK(startup_rule);
+  // TODO(ssid): Investigate when/how this can happen.
+  if (!startup_rule) {
+    return;
+  }
+
   // Post task to avoid reentrancy.
   base::PostTask(
       FROM_HERE, {content::BrowserThread::UI},
diff --git a/content/common/font_list_mac.mm b/content/common/font_list_mac.mm
index 64d2210..6b605e97 100644
--- a/content/common/font_list_mac.mm
+++ b/content/common/font_list_mac.mm
@@ -8,38 +8,38 @@
 
 #include <utility>
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/values.h"
 
 namespace content {
 
 std::unique_ptr<base::ListValue> GetFontList_SlowBlocking() {
-  base::mac::ScopedNSAutoreleasePool autorelease_pool;
-  std::unique_ptr<base::ListValue> font_list(new base::ListValue);
-  NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
-  NSMutableDictionary* fonts_dict = [NSMutableDictionary dictionary];
-  NSArray* fonts = [fontManager availableFontFamilies];
+  @autoreleasepool {
+    std::unique_ptr<base::ListValue> font_list(new base::ListValue);
+    NSFontManager* fontManager = [[[NSFontManager alloc] init] autorelease];
+    NSMutableDictionary* fonts_dict = [NSMutableDictionary dictionary];
+    NSArray* fonts = [fontManager availableFontFamilies];
 
-  for (NSString* family_name in fonts) {
-    NSString* localized_family_name =
-        [fontManager localizedNameForFamily:family_name face:nil];
-    fonts_dict[family_name] = localized_family_name;
+    for (NSString* family_name in fonts) {
+      NSString* localized_family_name =
+          [fontManager localizedNameForFamily:family_name face:nil];
+      fonts_dict[family_name] = localized_family_name;
+    }
+
+    // Sort family names based on localized names.
+    NSArray* sortedFonts = [fonts_dict
+        keysSortedByValueUsingSelector:@selector(localizedStandardCompare:)];
+
+    for (NSString* family_name in sortedFonts) {
+      NSString* localized_family_name = fonts_dict[family_name];
+      auto font_item = std::make_unique<base::ListValue>();
+      font_item->AppendString(base::SysNSStringToUTF16(family_name));
+      font_item->AppendString(base::SysNSStringToUTF16(localized_family_name));
+      font_list->Append(std::move(font_item));
+    }
+
+    return font_list;
   }
-
-  // Sort family names based on localized names.
-  NSArray* sortedFonts = [fonts_dict
-      keysSortedByValueUsingSelector:@selector(localizedStandardCompare:)];
-
-  for (NSString* family_name in sortedFonts) {
-    NSString* localized_family_name = fonts_dict[family_name];
-    auto font_item = std::make_unique<base::ListValue>();
-    font_item->AppendString(base::SysNSStringToUTF16(family_name));
-    font_item->AppendString(base::SysNSStringToUTF16(localized_family_name));
-    font_list->Append(std::move(font_item));
-  }
-
-  return font_list;
 }
 
 }  // namespace content
diff --git a/content/common/frame_messages.h b/content/common/frame_messages.h
index 318e199..c67d0cc 100644
--- a/content/common/frame_messages.h
+++ b/content/common/frame_messages.h
@@ -268,6 +268,7 @@
   IPC_STRUCT_TRAITS_MEMBER(max_size_for_auto_resize)
   IPC_STRUCT_TRAITS_MEMBER(screen_space_rect)
   IPC_STRUCT_TRAITS_MEMBER(local_frame_size)
+  IPC_STRUCT_TRAITS_MEMBER(compositor_viewport)
   IPC_STRUCT_TRAITS_MEMBER(capture_sequence_number)
   IPC_STRUCT_TRAITS_MEMBER(zoom_level)
   IPC_STRUCT_TRAITS_MEMBER(page_scale_factor)
diff --git a/content/common/frame_visual_properties.h b/content/common/frame_visual_properties.h
index c897b9b..9659e81 100644
--- a/content/common/frame_visual_properties.h
+++ b/content/common/frame_visual_properties.h
@@ -38,6 +38,9 @@
 
   gfx::Size local_frame_size;
 
+  // The size of the compositor viewport, to match the sub-frame's surface.
+  gfx::Rect compositor_viewport;
+
   uint32_t capture_sequence_number = 0u;
 
   // This represents the page zoom level for a WebContents.
diff --git a/content/common/visual_properties.h b/content/common/visual_properties.h
index dcccf0b..1984223 100644
--- a/content/common/visual_properties.h
+++ b/content/common/visual_properties.h
@@ -37,10 +37,10 @@
   // The size for the widget in DIPs.
   gfx::Size new_size;
 
-  // The size of compositor's viewport in pixels. Note that this may differ
-  // from a ScaleToCeiledSize of |new_size| due to Android's keyboard or due
-  // to rounding particulars.
-  gfx::Size compositor_viewport_pixel_size;
+  // The rect of compositor's viewport in pixels. Note that this may differ in
+  // size from a ScaleToCeiledSize of |new_size| due to Android's keyboard or
+  // due to rounding particulars.
+  gfx::Rect compositor_viewport_pixel_rect;
 
   // Whether or not Blink's viewport size should be shrunk by the height of the
   // URL-bar (always false on platforms where URL-bar hiding isn't supported).
diff --git a/content/common/widget_messages.h b/content/common/widget_messages.h
index c406148..54ca0be 100644
--- a/content/common/widget_messages.h
+++ b/content/common/widget_messages.h
@@ -55,7 +55,7 @@
   IPC_STRUCT_TRAITS_MEMBER(min_size_for_auto_resize)
   IPC_STRUCT_TRAITS_MEMBER(max_size_for_auto_resize)
   IPC_STRUCT_TRAITS_MEMBER(new_size)
-  IPC_STRUCT_TRAITS_MEMBER(compositor_viewport_pixel_size)
+  IPC_STRUCT_TRAITS_MEMBER(compositor_viewport_pixel_rect)
   IPC_STRUCT_TRAITS_MEMBER(browser_controls_shrink_blink_size)
   IPC_STRUCT_TRAITS_MEMBER(scroll_focused_node_into_view)
   IPC_STRUCT_TRAITS_MEMBER(top_controls_height)
diff --git a/content/public/browser/media_session.h b/content/public/browser/media_session.h
index 39aa9d06..a26f7e5 100644
--- a/content/public/browser/media_session.h
+++ b/content/public/browser/media_session.h
@@ -33,6 +33,9 @@
   CONTENT_EXPORT static const base::UnguessableToken& GetSourceId(
       BrowserContext* browser_context);
 
+  CONTENT_EXPORT static WebContents* GetWebContentsFromRequestId(
+      const base::UnguessableToken& request_id);
+
   // Tell the media session a user action has performed.
   virtual void DidReceiveAction(
       media_session::mojom::MediaSessionAction action) = 0;
diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc
index 77334181..5b045bc 100644
--- a/content/public/test/render_view_test.cc
+++ b/content/public/test/render_view_test.cc
@@ -691,7 +691,7 @@
   VisualProperties visual_properties;
   visual_properties.screen_info = ScreenInfo();
   visual_properties.new_size = new_size;
-  visual_properties.compositor_viewport_pixel_size = new_size;
+  visual_properties.compositor_viewport_pixel_rect = gfx::Rect(new_size);
   visual_properties.top_controls_height = 0.f;
   visual_properties.browser_controls_shrink_blink_size = false;
   visual_properties.is_fullscreen_granted = is_fullscreen_granted;
diff --git a/content/renderer/render_frame_impl_browsertest.cc b/content/renderer/render_frame_impl_browsertest.cc
index 609d9c8..ab2c344 100644
--- a/content/renderer/render_frame_impl_browsertest.cc
+++ b/content/renderer/render_frame_impl_browsertest.cc
@@ -222,7 +222,7 @@
   gfx::Size size(200, 200);
   visual_properties.screen_info = ScreenInfo();
   visual_properties.new_size = size;
-  visual_properties.compositor_viewport_pixel_size = size;
+  visual_properties.compositor_viewport_pixel_rect = gfx::Rect(size);
   visual_properties.visible_viewport_size = size;
   visual_properties.top_controls_height = 0.f;
   visual_properties.browser_controls_shrink_blink_size = false;
diff --git a/content/renderer/render_frame_proxy.cc b/content/renderer/render_frame_proxy.cc
index 0a89003d..5e483c5 100644
--- a/content/renderer/render_frame_proxy.cc
+++ b/content/renderer/render_frame_proxy.cc
@@ -617,6 +617,11 @@
       sent_visual_properties_->capture_sequence_number !=
           pending_visual_properties_.capture_sequence_number;
 
+  if (web_frame_) {
+    pending_visual_properties_.compositor_viewport =
+        web_frame_->GetCompositingRect();
+  }
+
   bool synchronized_props_changed =
       !sent_visual_properties_ ||
       sent_visual_properties_->auto_resize_enabled !=
@@ -637,6 +642,8 @@
           pending_visual_properties_.page_scale_factor ||
       sent_visual_properties_->is_pinch_gesture_active !=
           pending_visual_properties_.is_pinch_gesture_active ||
+      sent_visual_properties_->compositor_viewport !=
+          pending_visual_properties_.compositor_viewport ||
       capture_sequence_number_changed;
 
   if (synchronized_props_changed) {
@@ -652,7 +659,9 @@
                                     ? cc::DeadlinePolicy::UseInfiniteDeadline()
                                     : cc::DeadlinePolicy::UseDefaultDeadline();
   viz::SurfaceId surface_id(frame_sink_id_, GetLocalSurfaceId());
-  compositing_helper_->SetSurfaceId(surface_id, local_frame_size(), deadline);
+  compositing_helper_->SetSurfaceId(
+      surface_id, pending_visual_properties_.compositor_viewport.size(),
+      deadline);
 
   bool rect_changed = !sent_visual_properties_ ||
                       sent_visual_properties_->screen_space_rect !=
@@ -677,14 +686,6 @@
       "FrameHostMsg_SynchronizeVisualProperties", "local_surface_id",
       pending_visual_properties_.local_surface_id_allocation.local_surface_id()
           .ToString());
-
-  // The visible rect that the OOPIF needs to raster depends partially on
-  // parameters that might have changed. If they affect the raster area, resend
-  // the intersection rects.
-  gfx::Rect new_compositor_visible_rect = web_frame_->GetCompositingRect();
-  if (new_compositor_visible_rect != last_compositor_visible_rect_)
-    UpdateRemoteViewportIntersection(last_intersection_rect_,
-                                     last_occlusion_state_);
 }
 
 void RenderFrameProxy::OnSetHasReceivedUserGestureBeforeNavigation(bool value) {
@@ -822,12 +823,22 @@
 void RenderFrameProxy::UpdateRemoteViewportIntersection(
     const blink::WebRect& viewport_intersection,
     blink::FrameOcclusionState occlusion_state) {
-  last_intersection_rect_ = viewport_intersection;
-  last_compositor_visible_rect_ = web_frame_->GetCompositingRect();
-  last_occlusion_state_ = occlusion_state;
+  // If the remote viewport intersection has changed, then we should check if
+  // the compositing rect has also changed: if it has, then we should update the
+  // visible properties.
+  // TODO(wjmaclean): Maybe we should always call SynchronizeVisualProperties()
+  // here? If nothing has changed, it will early out, and it avoids duplicate
+  // checks here.
+  gfx::Rect compositor_visible_rect = web_frame_->GetCompositingRect();
+  bool compositor_visible_rect_changed =
+      compositor_visible_rect != pending_visual_properties_.compositor_viewport;
+
+  if (compositor_visible_rect_changed)
+    SynchronizeVisualProperties();
+
   Send(new FrameHostMsg_UpdateViewportIntersection(
-      routing_id_, gfx::Rect(viewport_intersection),
-      last_compositor_visible_rect_, last_occlusion_state_));
+      routing_id_, gfx::Rect(viewport_intersection), compositor_visible_rect,
+      occlusion_state));
 }
 
 void RenderFrameProxy::VisibilityChanged(
diff --git a/content/renderer/render_frame_proxy.h b/content/renderer/render_frame_proxy.h
index 37145f1..c7202fe2 100644
--- a/content/renderer/render_frame_proxy.h
+++ b/content/renderer/render_frame_proxy.h
@@ -322,11 +322,6 @@
   std::unique_ptr<viz::ParentLocalSurfaceIdAllocator>
       parent_local_surface_id_allocator_;
 
-  gfx::Rect last_intersection_rect_;
-  gfx::Rect last_compositor_visible_rect_;
-  blink::FrameOcclusionState last_occlusion_state_ =
-      blink::FrameOcclusionState::kUnknown;
-
   // The layer used to embed the out-of-process content.
   scoped_refptr<cc::Layer> embedded_layer_;
 
diff --git a/content/renderer/render_view_browsertest.cc b/content/renderer/render_view_browsertest.cc
index bd871e1..ad387b2 100644
--- a/content/renderer/render_view_browsertest.cc
+++ b/content/renderer/render_view_browsertest.cc
@@ -471,7 +471,7 @@
     VisualProperties visual_properties;
     visual_properties.screen_info.device_scale_factor = dsf;
     visual_properties.new_size = gfx::Size(100, 100);
-    visual_properties.compositor_viewport_pixel_size = gfx::Size(200, 200);
+    visual_properties.compositor_viewport_pixel_rect = gfx::Rect(200, 200);
     visual_properties.visible_viewport_size = visual_properties.new_size;
     visual_properties.auto_resize_enabled =
         view()->GetWidget()->auto_resize_mode();
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 0a5eecd..4777600 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -676,21 +676,16 @@
 }
 
 bool RenderWidget::Send(IPC::Message* message) {
+  // Undead RenderWidgets should not be used.
+  CHECK(!is_undead_);
+  // Provisional frames don't send IPCs until they are swapped in/committed.
+  CHECK(!IsForProvisionalFrame());
+
+  // Don't send any messages after the browser has told us to close.
   if (closing_) {
     delete message;
     return false;
   }
-  // TODO(danakj): We believe that we should be able to not block IPC sending.
-  // When there's a provisional main frame using this widget, we should not be
-  // sending messages with the RenderWidget yet. And when the widget is undead
-  // because there is no local main frame, there should be no code using
-  // RenderWidget and sending messages through it.
-  // We should CHECK() that the RenderWidget is not undead and that the frame
-  // attached to it is not provisional, instead of dropping messages.
-  if (IsUndeadOrProvisional()) {
-    delete message;
-    return false;
-  }
 
   // If given a messsage without a routing ID, then assign our routing ID.
   if (message->routing_id() == MSG_ROUTING_NONE)
@@ -764,9 +759,10 @@
   if (device_scale_factor_for_testing_) {
     visual_properties.screen_info.device_scale_factor =
         device_scale_factor_for_testing_;
-    visual_properties.compositor_viewport_pixel_size = gfx::ScaleToCeiledSize(
-        visual_properties.new_size,
-        visual_properties.screen_info.device_scale_factor);
+    visual_properties.compositor_viewport_pixel_rect =
+        gfx::Rect(gfx::ScaleToCeiledSize(
+            visual_properties.new_size,
+            visual_properties.screen_info.device_scale_factor));
   }
 
   // Inform the rendering thread of the color space indicating the presence of
@@ -919,8 +915,7 @@
     VisualProperties visual_properties;
     visual_properties.screen_info = screen_info_;
     visual_properties.new_size = size_;
-    visual_properties.compositor_viewport_pixel_size =
-        CompositorViewportRect().size();
+    visual_properties.compositor_viewport_pixel_rect = CompositorViewportRect();
     visual_properties.local_surface_id_allocation =
         local_surface_id_allocation_from_parent_;
     visual_properties.visible_viewport_size = visible_viewport_size_;
@@ -1647,16 +1642,15 @@
 
 void RenderWidget::SynchronizeVisualProperties(
     const VisualProperties& visual_properties) {
-  gfx::Size new_compositor_viewport_pixel_size =
+  gfx::Rect new_compositor_viewport_pixel_rect =
       visual_properties.auto_resize_enabled
-          ? gfx::ScaleToCeiledSize(
-                size_, visual_properties.screen_info.device_scale_factor)
-          : visual_properties.compositor_viewport_pixel_size;
+          ? gfx::Rect(gfx::ScaleToCeiledSize(
+                size_, visual_properties.screen_info.device_scale_factor))
+          : visual_properties.compositor_viewport_pixel_rect;
   UpdateSurfaceAndScreenInfo(
       visual_properties.local_surface_id_allocation.value_or(
           viz::LocalSurfaceIdAllocation()),
-      gfx::Rect(new_compositor_viewport_pixel_size),
-      visual_properties.screen_info);
+      new_compositor_viewport_pixel_rect, visual_properties.screen_info);
   UpdateCaptureSequenceNumber(visual_properties.capture_sequence_number);
   layer_tree_view_->layer_tree_host()->SetBrowserControlsHeight(
       visual_properties.top_controls_height,
@@ -2248,8 +2242,9 @@
   VisualProperties visual_properties;
   visual_properties.screen_info = screen_info_;
   visual_properties.new_size = new_window_rect.size();
-  visual_properties.compositor_viewport_pixel_size = gfx::ScaleToCeiledSize(
-      new_window_rect.size(), GetWebScreenInfo().device_scale_factor);
+  visual_properties.compositor_viewport_pixel_rect =
+      gfx::Rect(gfx::ScaleToCeiledSize(new_window_rect.size(),
+                                       GetWebScreenInfo().device_scale_factor));
   visual_properties.visible_viewport_size = new_window_rect.size();
   visual_properties.is_fullscreen_granted = is_fullscreen_granted_;
   visual_properties.display_mode = display_mode_;
@@ -2678,13 +2673,13 @@
 
     // TODO(ccameron): Note that this destroys any information differentiating
     // |size_| from the compositor's viewport size. Also note that the
-    // calculation of |new_compositor_viewport_pixel_size| does not appear to
+    // calculation of |new_compositor_viewport_pixel_rect| does not appear to
     // take into account device emulation.
     layer_tree_view_->RequestNewLocalSurfaceId();
-    gfx::Size new_compositor_viewport_pixel_size =
-        gfx::ScaleToCeiledSize(size_, GetWebScreenInfo().device_scale_factor);
+    gfx::Rect new_compositor_viewport_pixel_rect = gfx::Rect(
+        gfx::ScaleToCeiledSize(size_, GetWebScreenInfo().device_scale_factor));
     UpdateSurfaceAndScreenInfo(local_surface_id_allocation_from_parent_,
-                               gfx::Rect(new_compositor_viewport_pixel_size),
+                               new_compositor_viewport_pixel_rect,
                                screen_info_);
   }
 }
@@ -3764,7 +3759,7 @@
 
   // Make sure the DSF override stays for future VisualProperties updates, and
   // that includes overriding the VisualProperties'
-  // compositor_viewport_pixel_size with size * this for-testing DSF.
+  // compositor_viewport_pixel_rect with size * this for-testing DSF.
   device_scale_factor_for_testing_ = factor;
 }
 
@@ -3807,8 +3802,7 @@
   visual_properties.auto_resize_enabled = false;
   visual_properties.screen_info = screen_info_;
   visual_properties.new_size = new_size;
-  visual_properties.compositor_viewport_pixel_size =
-      CompositorViewportRect().size();
+  visual_properties.compositor_viewport_pixel_rect = CompositorViewportRect();
   visual_properties.browser_controls_shrink_blink_size =
       browser_controls_shrink_blink_size_;
   visual_properties.top_controls_height = top_controls_height_;
diff --git a/content/renderer/render_widget_browsertest.cc b/content/renderer/render_widget_browsertest.cc
index 91aaae7f..80b547f3 100644
--- a/content/renderer/render_widget_browsertest.cc
+++ b/content/renderer/render_widget_browsertest.cc
@@ -57,7 +57,7 @@
   VisualProperties visual_properties;
   visual_properties.screen_info = ScreenInfo();
   visual_properties.new_size = gfx::Size();
-  visual_properties.compositor_viewport_pixel_size = gfx::Size();
+  visual_properties.compositor_viewport_pixel_rect = gfx::Rect();
   visual_properties.top_controls_height = 0.f;
   visual_properties.browser_controls_shrink_blink_size = false;
   visual_properties.is_fullscreen_granted = false;
@@ -75,7 +75,7 @@
   visual_properties.local_surface_id_allocation =
       local_surface_id_allocator.GetCurrentLocalSurfaceIdAllocation();
   visual_properties.new_size = size;
-  visual_properties.compositor_viewport_pixel_size = size;
+  visual_properties.compositor_viewport_pixel_rect = gfx::Rect(size);
   OnSynchronizeVisualProperties(visual_properties);
 
   // Clear the flag.
@@ -87,7 +87,7 @@
 
   // Resetting the rect to empty should not send the ack.
   visual_properties.new_size = gfx::Size();
-  visual_properties.compositor_viewport_pixel_size = gfx::Size();
+  visual_properties.compositor_viewport_pixel_rect = gfx::Rect();
   visual_properties.local_surface_id_allocation = base::nullopt;
   OnSynchronizeVisualProperties(visual_properties);
 
@@ -106,7 +106,8 @@
     std::unique_ptr<VisualProperties> initial_visual_properties(
         new VisualProperties());
     initial_visual_properties->new_size = initial_size_;
-    initial_visual_properties->compositor_viewport_pixel_size = initial_size_;
+    initial_visual_properties->compositor_viewport_pixel_rect =
+        gfx::Rect(initial_size_);
     initial_visual_properties->local_surface_id_allocation =
         local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
     return initial_visual_properties;
diff --git a/content/test/data/super_tall_page.html b/content/test/data/super_tall_page.html
new file mode 100644
index 0000000..034556aa
--- /dev/null
+++ b/content/test/data/super_tall_page.html
@@ -0,0 +1,9 @@
+<html>
+  <head>
+  </head>
+  <body>
+    <div>This page is tall ...</div>
+    <div style="min-height: 30000px;"></div>
+    <div>... super tall!</div>
+  </body>
+</html>
diff --git a/content/test/data/super_tall_parent.html b/content/test/data/super_tall_parent.html
new file mode 100644
index 0000000..2b8eb7a
--- /dev/null
+++ b/content/test/data/super_tall_parent.html
@@ -0,0 +1,9 @@
+<html>
+  <head>
+  </head>
+  <body>
+    <div>This page contains a super-tall iframe.</div>
+    <iframe style="min-height: 31000px; min-width: 500px" src="about:blank">
+    </iframe>
+  </body>
+</html>
diff --git a/device/fido/cable/fido_cable_device.cc b/device/fido/cable/fido_cable_device.cc
index df77590..2c3767cc 100644
--- a/device/fido/cable/fido_cable_device.cc
+++ b/device/fido/cable/fido_cable_device.cc
@@ -7,6 +7,7 @@
 #include <utility>
 
 #include "base/bind.h"
+#include "base/numerics/safe_math.h"
 #include "base/strings/string_piece.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "components/device_event_log/device_event_log.h"
@@ -22,9 +23,9 @@
 // Maximum size of EncryptionData::read_sequence_num or
 // EncryptionData::write_sequence_num allowed. If we encounter
 // counter larger than |kMaxCounter| FidoCableDevice should error out.
-constexpr size_t kMaxCounter = (1 << 24) - 1;
+constexpr uint32_t kMaxCounter = (1 << 24) - 1;
 
-base::Optional<std::vector<uint8_t>> ConstructEncryptionNonce(
+base::Optional<std::vector<uint8_t>> ConstructV1Nonce(
     base::span<const uint8_t> nonce,
     bool is_sender_client,
     uint32_t counter) {
@@ -39,6 +40,20 @@
   return constructed_nonce;
 }
 
+bool ConstructV2Nonce(base::span<uint8_t, 12> out_nonce, uint32_t counter) {
+  if (counter > kMaxCounter) {
+    return false;
+  }
+
+  // Nonce is just a little-endian counter.
+  std::array<uint8_t, sizeof(counter)> counter_bytes;
+  memcpy(counter_bytes.data(), &counter, sizeof(counter));
+  auto remaining =
+      std::copy(counter_bytes.begin(), counter_bytes.end(), out_nonce.begin());
+  std::fill(remaining, out_nonce.end(), 0);
+  return true;
+}
+
 }  // namespace
 
 FidoCableDevice::EncryptionData::EncryptionData() = default;
@@ -105,16 +120,28 @@
                      std::move(handshake_message), std::move(callback));
 }
 
-void FidoCableDevice::SetEncryptionData(
+void FidoCableDevice::SetV1EncryptionData(
     base::span<const uint8_t, 32> session_key,
     base::span<const uint8_t, 8> nonce) {
   // Encryption data must be set at most once during Cable handshake protocol.
   DCHECK(!encryption_data_);
   encryption_data_.emplace();
-  encryption_data_->session_key = fido_parsing_utils::Materialize(session_key);
+  encryption_data_->read_key = fido_parsing_utils::Materialize(session_key);
+  encryption_data_->write_key = fido_parsing_utils::Materialize(session_key);
   encryption_data_->nonce = fido_parsing_utils::Materialize(nonce);
 }
 
+void FidoCableDevice::SetV2EncryptionData(
+    base::span<const uint8_t, 32> read_key,
+    base::span<const uint8_t, 32> write_key) {
+  DCHECK(!encryption_data_);
+  encryption_data_.emplace();
+  encryption_data_->read_key = fido_parsing_utils::Materialize(read_key);
+  encryption_data_->write_key = fido_parsing_utils::Materialize(write_key);
+  memset(encryption_data_->nonce.data(), 0, encryption_data_->nonce.size());
+  encryption_data_->is_version_two = true;
+}
+
 FidoTransportProtocol FidoCableDevice::DeviceTransport() const {
   return FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy;
 }
@@ -129,14 +156,32 @@
 bool FidoCableDevice::EncryptOutgoingMessage(
     const EncryptionData& encryption_data,
     std::vector<uint8_t>* message_to_encrypt) {
-  const auto nonce = ConstructEncryptionNonce(
-      encryption_data.nonce, true /* is_sender_client */,
-      encryption_data.write_sequence_num);
+  return encryption_data.is_version_two
+             ? EncryptV2OutgoingMessage(encryption_data, message_to_encrypt)
+             : EncryptV1OutgoingMessage(encryption_data, message_to_encrypt);
+}
+
+// static
+bool FidoCableDevice::DecryptIncomingMessage(
+    const EncryptionData& encryption_data,
+    FidoBleFrame* incoming_frame) {
+  return encryption_data.is_version_two
+             ? DecryptV2IncomingMessage(encryption_data, incoming_frame)
+             : DecryptV1IncomingMessage(encryption_data, incoming_frame);
+}
+
+// static
+bool FidoCableDevice::EncryptV1OutgoingMessage(
+    const EncryptionData& encryption_data,
+    std::vector<uint8_t>* message_to_encrypt) {
+  const auto nonce =
+      ConstructV1Nonce(encryption_data.nonce, /*is_sender_client=*/true,
+                       encryption_data.write_sequence_num);
   if (!nonce)
     return false;
 
   crypto::Aead aes_key(crypto::Aead::AES_256_GCM);
-  aes_key.Init(encryption_data.session_key);
+  aes_key.Init(encryption_data.write_key);
   DCHECK_EQ(nonce->size(), aes_key.NonceLength());
 
   const uint8_t additional_data[1] = {
@@ -148,17 +193,17 @@
 }
 
 // static
-bool FidoCableDevice::DecryptIncomingMessage(
+bool FidoCableDevice::DecryptV1IncomingMessage(
     const EncryptionData& encryption_data,
     FidoBleFrame* incoming_frame) {
-  const auto nonce = ConstructEncryptionNonce(
-      encryption_data.nonce, false /* is_sender_client */,
-      encryption_data.read_sequence_num);
+  const auto nonce =
+      ConstructV1Nonce(encryption_data.nonce, /*is_sender_client=*/false,
+                       encryption_data.read_sequence_num);
   if (!nonce)
     return false;
 
   crypto::Aead aes_key(crypto::Aead::AES_256_GCM);
-  aes_key.Init(encryption_data.session_key);
+  aes_key.Init(encryption_data.read_key);
   DCHECK_EQ(nonce->size(), aes_key.NonceLength());
 
   const uint8_t additional_data[1] = {
@@ -174,4 +219,91 @@
   return true;
 }
 
+// static
+bool FidoCableDevice::EncryptV2OutgoingMessage(
+    const EncryptionData& encryption_data,
+    std::vector<uint8_t>* message_to_encrypt) {
+  // Messages will be padded in order to round their length up to a multiple of
+  // kPaddingGranularity.
+  constexpr size_t kPaddingGranularity = 32;
+  static_assert(kPaddingGranularity > 0, "padding too small");
+  static_assert(kPaddingGranularity < 256, "padding too large");
+  static_assert((kPaddingGranularity & (kPaddingGranularity - 1)) == 0,
+                "padding must be a power of two");
+
+  // Padding consists of a some number of zero bytes appended to the message and
+  // the final byte in the message is the number of zeros.
+  base::CheckedNumeric<size_t> padded_size_checked = message_to_encrypt->size();
+  padded_size_checked += 1;  // padding-length byte.
+  padded_size_checked = (padded_size_checked + kPaddingGranularity - 1) &
+                        ~(kPaddingGranularity - 1);
+  if (!padded_size_checked.IsValid()) {
+    return false;
+  }
+
+  const size_t padded_size = padded_size_checked.ValueOrDie();
+  DCHECK_GT(padded_size, message_to_encrypt->size());
+  const size_t num_zeros = padded_size - message_to_encrypt->size() - 1;
+
+  std::vector<uint8_t> padded_message(padded_size, 0);
+  memcpy(padded_message.data(), message_to_encrypt->data(),
+         message_to_encrypt->size());
+  // The number of added zeros has to fit in a single byte so it has to be less
+  // than 256.
+  DCHECK_LT(num_zeros, 256u);
+  padded_message[padded_message.size() - 1] = static_cast<uint8_t>(num_zeros);
+
+  std::array<uint8_t, 12> nonce;
+  if (!ConstructV2Nonce(nonce, encryption_data.write_sequence_num)) {
+    return false;
+  }
+
+  crypto::Aead aes_key(crypto::Aead::AES_256_GCM);
+  aes_key.Init(encryption_data.write_key);
+  DCHECK_EQ(nonce.size(), aes_key.NonceLength());
+
+  const uint8_t additional_data[2] = {
+      base::strict_cast<uint8_t>(FidoBleDeviceCommand::kMsg), /*version=*/2};
+  std::vector<uint8_t> ciphertext =
+      aes_key.Seal(padded_message, nonce, additional_data);
+  message_to_encrypt->swap(ciphertext);
+  return true;
+}
+
+// static
+bool FidoCableDevice::DecryptV2IncomingMessage(
+    const EncryptionData& encryption_data,
+    FidoBleFrame* incoming_frame) {
+  std::array<uint8_t, 12> nonce;
+  if (!ConstructV2Nonce(nonce, encryption_data.read_sequence_num)) {
+    return false;
+  }
+
+  crypto::Aead aes_key(crypto::Aead::AES_256_GCM);
+  aes_key.Init(encryption_data.read_key);
+  DCHECK_EQ(nonce.size(), aes_key.NonceLength());
+
+  const uint8_t additional_data[2] = {
+      base::strict_cast<uint8_t>(incoming_frame->command()), /*version=*/2};
+  base::Optional<std::vector<uint8_t>> plaintext =
+      aes_key.Open(incoming_frame->data(), nonce, additional_data);
+  if (!plaintext) {
+    FIDO_LOG(ERROR) << "Failed to decrypt caBLE message.";
+    return false;
+  }
+
+  if (plaintext->empty()) {
+    return false;
+  }
+
+  const size_t padding_length = (*plaintext)[plaintext->size() - 1];
+  if (padding_length + 1 > plaintext->size()) {
+    return false;
+  }
+  plaintext->resize(plaintext->size() - padding_length - 1);
+
+  incoming_frame->data().swap(*plaintext);
+  return true;
+}
+
 }  // namespace device
diff --git a/device/fido/cable/fido_cable_device.h b/device/fido/cable/fido_cable_device.h
index 504a7c0..4e6d362 100644
--- a/device/fido/cable/fido_cable_device.h
+++ b/device/fido/cable/fido_cable_device.h
@@ -43,8 +43,12 @@
   void SendHandshakeMessage(std::vector<uint8_t> handshake_message,
                             DeviceCallback callback);
 
-  void SetEncryptionData(base::span<const uint8_t, 32> session_key,
-                         base::span<const uint8_t, 8> nonce);
+  // Configure caBLE v1 keys.
+  void SetV1EncryptionData(base::span<const uint8_t, 32> session_key,
+                           base::span<const uint8_t, 8> nonce);
+  // Configure caBLE v2 keys.
+  void SetV2EncryptionData(base::span<const uint8_t, 32> read_key,
+                           base::span<const uint8_t, 32> write_key);
   FidoTransportProtocol DeviceTransport() const override;
 
   // SetCountersForTesting allows tests to set the message counters. Non-test
@@ -58,10 +62,12 @@
   struct EncryptionData {
     EncryptionData();
 
-    std::array<uint8_t, 32> session_key;
+    std::array<uint8_t, 32> read_key;
+    std::array<uint8_t, 32> write_key;
     std::array<uint8_t, 8> nonce;
     uint32_t write_sequence_num = 0;
     uint32_t read_sequence_num = 0;
+    bool is_version_two = false;
   };
 
   static bool EncryptOutgoingMessage(const EncryptionData& encryption_data,
@@ -69,6 +75,18 @@
   static bool DecryptIncomingMessage(const EncryptionData& encryption_data,
                                      FidoBleFrame* incoming_frame);
 
+  static bool EncryptV1OutgoingMessage(
+      const EncryptionData& encryption_data,
+      std::vector<uint8_t>* message_to_encrypt);
+  static bool DecryptV1IncomingMessage(const EncryptionData& encryption_data,
+                                       FidoBleFrame* incoming_frame);
+
+  static bool EncryptV2OutgoingMessage(
+      const EncryptionData& encryption_data,
+      std::vector<uint8_t>* message_to_encrypt);
+  static bool DecryptV2IncomingMessage(const EncryptionData& encryption_data,
+                                       FidoBleFrame* incoming_frame);
+
   base::Optional<EncryptionData> encryption_data_;
   base::WeakPtrFactory<FidoCableDevice> weak_factory_{this};
 
diff --git a/device/fido/cable/fido_cable_device_unittest.cc b/device/fido/cable/fido_cable_device_unittest.cc
index 2c0a388..9f37dfb 100644
--- a/device/fido/cable/fido_cable_device_unittest.cc
+++ b/device/fido/cable/fido_cable_device_unittest.cc
@@ -140,7 +140,7 @@
         adapter_.get(), BluetoothTestBase::kTestDeviceAddress1);
     connection_ = connection.get();
     device_ = std::make_unique<FidoCableDevice>(std::move(connection));
-    device_->SetEncryptionData(kTestSessionKey, kTestEncryptionNonce);
+    device_->SetV1EncryptionData(kTestSessionKey, kTestEncryptionNonce);
     connection_->read_callback() = device_->GetReadCallbackForTesting();
   }
 
diff --git a/device/fido/cable/fido_cable_handshake_handler.cc b/device/fido/cable/fido_cable_handshake_handler.cc
index 76d7f5b..257ec8f 100644
--- a/device/fido/cable/fido_cable_handshake_handler.cc
+++ b/device/fido/cable/fido_cable_handshake_handler.cc
@@ -155,7 +155,7 @@
     return false;
   }
 
-  cable_device_->SetEncryptionData(
+  cable_device_->SetV1EncryptionData(
       base::make_span<32>(
           GetEncryptionKeyAfterSuccessfulHandshake(base::make_span<16>(
               authenticator_random_nonce->second.GetBytestring()))),
@@ -306,12 +306,10 @@
   // handshake messages so that's moot.
   // MixHash(ciphertext);
 
-  std::array<uint8_t, 32> key1, unused_key2;
-  std::tie(key1, unused_key2) =
+  std::array<uint8_t, 32> read_key, write_key;
+  std::tie(write_key, read_key) =
       HKDF2(chaining_key_, base::span<const uint8_t>());
-
-  uint8_t zero_nonce[8] = {0};
-  cable_device_->SetEncryptionData(key1, zero_nonce);
+  cable_device_->SetV2EncryptionData(read_key, write_key);
 
   return true;
 }
@@ -351,10 +349,7 @@
 std::vector<uint8_t> FidoCableV2HandshakeHandler::Encrypt(
     base::span<const uint8_t> plaintext) {
   uint8_t nonce[12] = {0};
-  nonce[8] = symmetric_nonce_ >> 24;
-  nonce[9] = symmetric_nonce_ >> 16;
-  nonce[10] = symmetric_nonce_ >> 8;
-  nonce[11] = symmetric_nonce_;
+  memcpy(nonce, &symmetric_nonce_, sizeof(symmetric_nonce_));
   symmetric_nonce_++;
 
   crypto::Aead aead(crypto::Aead::AES_256_GCM);
@@ -365,10 +360,7 @@
 base::Optional<std::vector<uint8_t>> FidoCableV2HandshakeHandler::Decrypt(
     base::span<const uint8_t> ciphertext) {
   uint8_t nonce[12] = {0};
-  nonce[8] = symmetric_nonce_ >> 24;
-  nonce[9] = symmetric_nonce_ >> 16;
-  nonce[10] = symmetric_nonce_ >> 8;
-  nonce[11] = symmetric_nonce_;
+  memcpy(nonce, &symmetric_nonce_, sizeof(symmetric_nonce_));
   symmetric_nonce_++;
 
   crypto::Aead aead(crypto::Aead::AES_256_GCM);
diff --git a/docs/android_dynamic_feature_modules.md b/docs/android_dynamic_feature_modules.md
index 913d5c9f..9a259ed 100644
--- a/docs/android_dynamic_feature_modules.md
+++ b/docs/android_dynamic_feature_modules.md
@@ -51,9 +51,9 @@
     <dist:module
         dist:onDemand="true"
         dist:title="@string/foo_module_title">
-        <!-- This will prevent the module to become part of the Android K
-             build in case we ever want to use bundles on Android K. -->
-        <dist:fusing dist:include="false" />
+        <!-- This will fuse the module into the base APK if a system image
+             APK is built from this bundle. -->
+        <dist:fusing dist:include="true" />
     </dist:module>
 
     <!-- Remove android:hasCode="false" when adding Java code. -->
@@ -775,7 +775,7 @@
     <dist:module
       dist:instant="false"
       dist:title="@string/foo_module_title">
-      <dist:fusing dist:include="false" />
+      <dist:fusing dist:include="true" />
       <dist:delivery>
         <dist:install-time>
           <dist:conditions>
diff --git a/docs/clang.md b/docs/clang.md
index 55b2fed6..d901dad 100644
--- a/docs/clang.md
+++ b/docs/clang.md
@@ -1,30 +1,18 @@
 # Clang
 
-[Clang](http://clang.llvm.org/) is the main supported compiler when
-building Chromium on all platforms.
+Chromium ships a prebuilt [clang](http://clang.llvm.org) binary.
+It's just upstream clang built at a known-good revision that we
+bump every two weeks or so.
 
-Known [clang bugs and feature
-requests](http://code.google.com/p/chromium/issues/list?q=label:clang).
+This is the only supported compiler for building Chromium.
 
 [TOC]
 
-## Building with clang
+## Using gcc on Linux
 
-This happens by default, with clang binaries being fetched by gclient
-during the `gclient runhooks` phase. To fetch them manually, or build
-a local custom clang, use
-
-    tools/clang/scripts/update.py
-
-Run `gn args` and make sure there is no `is_clang = false` in your args.gn file.
-
-Build: `ninja -C out/gn chrome`
-
-## Reverting to gcc on Linux or MSVC on Windows
-
-There are no bots that test this but `is_clang = false` will revert to
-gcc on Linux and to Visual Studio on Windows. There is no guarantee it
-will work.
+`is_clang = false` will make the build use system gcc on Linux. There are no
+bots that test this and there is no guarantee it will work, but we accept
+patches for this configuration.
 
 ## Mailing List
 
@@ -66,21 +54,13 @@
 
 ## Windows
 
-Since October 2017, clang is the default compiler on Windows. It uses
-MSVC's linker and SDK, so you still need to have Visual Studio with
-C++ support installed.
-
-To use MSVC's compiler (if it still works), use `is_clang = false`.
-
-Current brokenness:
-
-*   To get colored diagnostics, you need to be running
-    [ansicon](https://github.com/adoxa/ansicon/releases).
+clang is the default compiler on Windows. It uses MSVC's SDK, so you still need
+to have Visual Studio with C++ support installed.
 
 ## Using a custom clang binary
 
 Set `clang_base_path` in your args.gn to the llvm build directory containing
-`bin/clang` (i.e. the directory you ran cmake). This [must][1] be an absolute
+`bin/clang` (i.e. the directory you ran cmake). This must be an absolute
 path. You also need to disable chromium's clang plugin.
 
 Here's an example that also disables debug info and enables the component build
diff --git a/docs/clang_sheriffing.md b/docs/clang_sheriffing.md
index 8639313..a5ead03 100644
--- a/docs/clang_sheriffing.md
+++ b/docs/clang_sheriffing.md
@@ -6,8 +6,7 @@
 compiler](updating_clang.md) (roll clang), it has to be tested so that we can be
 confident that it works in the configurations that Chromium cares about.
 
-Fortunately, Chromium happens to be a pretty decent stress test for a C++
-compiler, so we maintain a [waterfall of
+We maintain a [waterfall of
 builders](https://ci.chromium.org/p/chromium/g/chromium.clang/console) that
 continuously build fresh versions of Clang and use them to build and test
 Chromium. "Clang sheriffing" is the process of monitoring that waterfall,
@@ -155,6 +154,36 @@
 
 ## Linker error
 
+`ld.lld`'s `--reproduce` flag makes LLD write a tar archive of all its inputs
+and a file `response.txt` that contains the link command. This allows people to
+work on linker bugs without having to have a Chromium build environment.
+
+To use `ld.lld`'s `--reproduce` flag, follow these steps:
+
+1. Locally (build Chromium with a locally-built
+   clang)[https://chromium.googlesource.com/chromium/src.git/+/master/docs/clang.md#Using-a-custom-clang-binary]
+
+1. After reproducing the link error, build just the failing target with
+   ninja's `-v -d keeprsp` flags added:
+  `ninja -C out/gn base_unittests -v -d keeprsp`.
+
+1. Copy the link command that ninja prints, `cd out/gn`, paste it, and manually
+   append `-Wl,--reproduce,repro.tar`. With `lld-link`, instead append
+   `/linkrepro:.`. (`ld.lld`'s `--reproduce` takes a filename and is invoked
+   through the `clang` driver, so it needs `-Wl` to pass the flag through to
+   the linker. `lld-link`'s `/linkrepro:` takes a directory name and creates
+   a file called `repro.tar` in that directory. It's called directly, so the
+   flag needs no prefix.)
+
+1. Zip up the tar file: `gzip repro.tar`. This will take a few minutes and
+   produce a .tar.gz file that's 0.5-1 GB.
+
+1. Upload the .tar.gz to Google Drive. If you're signed in with your @google
+   address, you won't be able to make a world-shareable link to it, so upload
+   it in a Window where you're signed in with your @chromium account.
+
+1. File an LLVM bug linking to the file. Example: http://llvm.org/PR43241
+
 TODO: Describe object file bisection, identify obj with symbol that no longer
 has the section.
 
diff --git a/docs/win_cross.md b/docs/win_cross.md
index e0a20ec..334defba 100644
--- a/docs/win_cross.md
+++ b/docs/win_cross.md
@@ -102,7 +102,7 @@
 
 You can run the Windows binaries you built on swarming, like so:
 
-    tools/run-swarmed.py -C out/gnwin -t base_unittests [ --gtest_filter=... ]
+    tools/run-swarmed.py out/gnwin base_unittests [ --gtest_filter=... ]
 
 See the contents of run-swarmed.py for how to do this manually.
 
diff --git a/docs/workflow/debugging-with-swarming.md b/docs/workflow/debugging-with-swarming.md
index a1a3f5b..b204b97 100644
--- a/docs/workflow/debugging-with-swarming.md
+++ b/docs/workflow/debugging-with-swarming.md
@@ -150,7 +150,7 @@
 like this:
 
 ```
-$ tools/run-swarmed.py -t $target --out-dir=$outdir
+$ tools/run-swarmed.py $outdir $target
 ```
 
 See the `--help` option of `run-swarmed.py` for more details about that script.
diff --git a/extensions/browser/updater/update_data_provider.cc b/extensions/browser/updater/update_data_provider.cc
index 4a5a84c..46ab1056 100644
--- a/extensions/browser/updater/update_data_provider.cc
+++ b/extensions/browser/updater/update_data_provider.cc
@@ -124,7 +124,9 @@
           crx_component->disabled_reasons.push_back(enum_value);
       }
     }
-    crx_component->install_source = extension_data.install_source;
+    crx_component->install_source = extension_data.is_corrupt_reinstall
+                                        ? "reinstall"
+                                        : extension_data.install_source;
     crx_component->install_location =
         ManifestFetchData::GetSimpleLocationString(extension->location());
   }
diff --git a/extensions/browser/updater/update_data_provider_unittest.cc b/extensions/browser/updater/update_data_provider_unittest.cc
index e08a1c87..4e752d56 100644
--- a/extensions/browser/updater/update_data_provider_unittest.cc
+++ b/extensions/browser/updater/update_data_provider_unittest.cc
@@ -184,7 +184,7 @@
 
   ASSERT_EQ(1UL, data.size());
   EXPECT_EQ("0.0.0.0", data[0]->version.GetString());
-  EXPECT_EQ("webstore", data[0]->install_source);
+  EXPECT_EQ("reinstall", data[0]->install_source);
   EXPECT_EQ("external", data[0]->install_location);
   EXPECT_NE(nullptr, data[0]->installer.get());
   EXPECT_EQ(0UL, data[0]->disabled_reasons.size());
@@ -428,7 +428,7 @@
   EXPECT_NE(nullptr, data[0]->installer.get());
   EXPECT_EQ(0UL, data[0]->disabled_reasons.size());
   EXPECT_EQ(initial_version, data[1]->version.GetString());
-  EXPECT_EQ("sideload", data[1]->install_source);
+  EXPECT_EQ("reinstall", data[1]->install_source);
   EXPECT_EQ("policy", data[1]->install_location);
   EXPECT_NE(nullptr, data[1]->installer.get());
   EXPECT_EQ(0UL, data[1]->disabled_reasons.size());
diff --git a/extensions/browser/updater/update_service.cc b/extensions/browser/updater/update_service.cc
index a848342..316b058 100644
--- a/extensions/browser/updater/update_service.cc
+++ b/extensions/browser/updater/update_service.cc
@@ -22,7 +22,6 @@
 #include "extensions/browser/updater/extension_update_data.h"
 #include "extensions/browser/updater/update_data_provider.h"
 #include "extensions/browser/updater/update_service_factory.h"
-#include "extensions/common/extension_features.h"
 #include "extensions/common/extension_updater_uma.h"
 #include "extensions/common/extension_urls.h"
 #include "extensions/common/manifest_url_handlers.h"
@@ -31,6 +30,8 @@
 
 namespace {
 
+UpdateService* update_service_override = nullptr;
+
 // 98% of update checks have 22 or less extensions.
 constexpr size_t kMaxExtensionsPerUpdate = 22;
 
@@ -81,8 +82,15 @@
     UpdateService::InProgressUpdate&& other) = default;
 
 // static
+void UpdateService::SupplyUpdateServiceForTest(UpdateService* service) {
+  update_service_override = service;
+}
+
+// static
 UpdateService* UpdateService::Get(content::BrowserContext* context) {
-  return UpdateServiceFactory::GetForBrowserContext(context);
+  return update_service_override == nullptr
+             ? UpdateServiceFactory::GetForBrowserContext(context)
+             : update_service_override;
 }
 
 void UpdateService::Shutdown() {
@@ -106,9 +114,6 @@
 }
 
 bool UpdateService::CanUpdate(const std::string& extension_id) const {
-  if (!base::FeatureList::IsEnabled(
-          extensions_features::kNewExtensionUpdaterService))
-    return false;
   // It's possible to change Webstore update URL from command line (through
   // apps-gallery-update-url command line switch). When Webstore update URL is
   // different the default Webstore update URL, we won't support updating
@@ -191,12 +196,15 @@
   }
 }
 
+bool UpdateService::IsBusy() const {
+  return !updating_extension_ids_.empty();
+}
+
 UpdateService::UpdateService(
     content::BrowserContext* browser_context,
     scoped_refptr<update_client::UpdateClient> update_client)
     : browser_context_(browser_context), update_client_(update_client) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
-  DCHECK(update_client_);
   update_data_provider_ =
       base::MakeRefCounted<UpdateDataProvider>(browser_context_);
   AddUpdateClientObserver(this);
diff --git a/extensions/browser/updater/update_service.h b/extensions/browser/updater/update_service.h
index a46d140..2c47ca71 100644
--- a/extensions/browser/updater/update_service.h
+++ b/extensions/browser/updater/update_service.h
@@ -38,46 +38,48 @@
 class UpdateDataProvider;
 class UpdateServiceFactory;
 
-// This service manages the autoupdate of extensions.  It should eventually
-// replace ExtensionUpdater in Chrome.
-// TODO(rockot): Replace ExtensionUpdater with this service.
+// An UpdateService provides functionality to update extensions.
+// Some methods are virtual for testing purposes.
 class UpdateService : public KeyedService,
                       update_client::UpdateClient::Observer {
  public:
   static UpdateService* Get(content::BrowserContext* context);
 
+  static void SupplyUpdateServiceForTest(UpdateService* service);
+
   void Shutdown() override;
 
-  void SendUninstallPing(const std::string& id,
-                         const base::Version& version,
-                         int reason);
+  virtual void SendUninstallPing(const std::string& id,
+                                 const base::Version& version,
+                                 int reason);
 
   // Starts an update check for each of extensions stored in |update_params|.
   // If there are any updates available, they will be downloaded, checked for
   // integrity, unpacked, and then passed off to the
   // ExtensionSystem::InstallUpdate method for install completion.
-  void StartUpdateCheck(const ExtensionUpdateCheckParams& update_params,
-                        base::OnceClosure callback);
+  virtual void StartUpdateCheck(const ExtensionUpdateCheckParams& update_params,
+                                base::OnceClosure callback);
 
   // This function verifies if the current implementation can update
   // |extension_id|.
-  bool CanUpdate(const std::string& extension_id) const;
+  virtual bool CanUpdate(const std::string& extension_id) const;
 
   // Overriden from |update_client::UpdateClient::Observer|.
   void OnEvent(Events event, const std::string& id) override;
 
   // Returns true if the update service is updating one or more extensions.
-  bool IsBusy() const { return !updating_extension_ids_.empty(); }
+  virtual bool IsBusy() const;
+
+ protected:
+  UpdateService(content::BrowserContext* context,
+                scoped_refptr<update_client::UpdateClient> update_client);
+  ~UpdateService() override;
 
  private:
   friend class ExtensionUpdateClientBaseTest;
   friend class UpdateServiceFactory;
   friend std::unique_ptr<UpdateService>::deleter_type;
 
-  UpdateService(content::BrowserContext* context,
-                scoped_refptr<update_client::UpdateClient> update_client);
-  ~UpdateService() override;
-
   // This function is executed by the update client after an update check
   // request has completed.
   void UpdateCheckComplete(update_client::Error error);
diff --git a/extensions/browser/updater/update_service_unittest.cc b/extensions/browser/updater/update_service_unittest.cc
index 6fd7b4a..8439e7e 100644
--- a/extensions/browser/updater/update_service_unittest.cc
+++ b/extensions/browser/updater/update_service_unittest.cc
@@ -930,8 +930,7 @@
   EXPECT_FALSE(update_service()->IsBusy());
 }
 
-class UpdateServiceCanUpdateTest : public UpdateServiceTest,
-                                   public ::testing::WithParamInterface<bool> {
+class UpdateServiceCanUpdateTest : public UpdateServiceTest {
  public:
   UpdateServiceCanUpdateTest() {}
   ~UpdateServiceCanUpdateTest() override {}
@@ -939,14 +938,6 @@
   void SetUp() override {
     UpdateServiceTest::SetUp();
 
-    if (GetParam()) {
-      scoped_feature_list_.InitAndEnableFeature(
-          extensions_features::kNewExtensionUpdaterService);
-    } else {
-      scoped_feature_list_.InitAndDisableFeature(
-          extensions_features::kNewExtensionUpdaterService);
-    }
-
     store_extension_ =
         ExtensionBuilder("store_extension")
             .MergeManifest(
@@ -1010,11 +1001,11 @@
   }
 };
 
-TEST_P(UpdateServiceCanUpdateTest, UpdateService_CanUpdate) {
+TEST_F(UpdateServiceCanUpdateTest, UpdateService_CanUpdate) {
   // Update service can only update webstore extensions when enabled.
-  EXPECT_EQ(GetParam(), update_service()->CanUpdate(store_extension_->id()));
+  EXPECT_TRUE(update_service()->CanUpdate(store_extension_->id()));
   // ... and extensions with empty update URL.
-  EXPECT_EQ(GetParam(), update_service()->CanUpdate(emptyurl_extension_->id()));
+  EXPECT_TRUE(update_service()->CanUpdate(emptyurl_extension_->id()));
   // It can't update off-store extrensions.
   EXPECT_FALSE(update_service()->CanUpdate(offstore_extension_->id()));
   // ... or extensions with empty update URL converted from user script.
@@ -1025,7 +1016,7 @@
   EXPECT_FALSE(update_service()->CanUpdate(""));
 }
 
-TEST_P(UpdateServiceCanUpdateFeatureEnabledNonDefaultUpdateUrl,
+TEST_F(UpdateServiceCanUpdateFeatureEnabledNonDefaultUpdateUrl,
        UpdateService_CanUpdate) {
   // Update service cannot update extensions when the default webstore update
   // url is changed.
@@ -1037,15 +1028,6 @@
   EXPECT_FALSE(update_service()->CanUpdate(""));
 }
 
-INSTANTIATE_TEST_SUITE_P(CanUpdateTest,
-                         UpdateServiceCanUpdateTest,
-                         ::testing::Bool());
-
-INSTANTIATE_TEST_SUITE_P(
-    CanUpdateTest,
-    UpdateServiceCanUpdateFeatureEnabledNonDefaultUpdateUrl,
-    ::testing::Bool());
-
 }  // namespace
 
 }  // namespace extensions
diff --git a/extensions/common/extension_features.cc b/extensions/common/extension_features.cc
index 6027451..3370c10 100644
--- a/extensions/common/extension_features.cc
+++ b/extensions/common/extension_features.cc
@@ -6,10 +6,6 @@
 
 namespace extensions_features {
 
-// Enables new extension updater service.
-const base::Feature kNewExtensionUpdaterService{
-    "NewExtensionUpdaterService", base::FEATURE_DISABLED_BY_DEFAULT};
-
 // Forces requests to go through WebRequestProxyingURLLoaderFactory.
 const base::Feature kForceWebRequestProxyForTest{
     "ForceWebRequestProxyForTest", base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/extensions/common/extension_features.h b/extensions/common/extension_features.h
index dfa5383..fab080e 100644
--- a/extensions/common/extension_features.h
+++ b/extensions/common/extension_features.h
@@ -9,7 +9,6 @@
 
 namespace extensions_features {
 
-extern const base::Feature kNewExtensionUpdaterService;
 extern const base::Feature kForceWebRequestProxyForTest;
 
 }  // namespace extensions_features
diff --git a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
index 28efcd3..4764da1 100644
--- a/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
+++ b/extensions/renderer/api/automation/automation_ax_tree_wrapper.cc
@@ -422,6 +422,11 @@
   return unignored_selection;
 }
 
+ui::AXNode* AutomationAXTreeWrapper::GetUnignoredNodeFromId(int32_t id) {
+  ui::AXNode* node = tree_.GetFromId(id);
+  return (node && !node->IsIgnored()) ? node : nullptr;
+}
+
 // static
 std::map<ui::AXTreeID, AutomationAXTreeWrapper*>&
 AutomationAXTreeWrapper::GetChildTreeIDReverseMap() {
diff --git a/extensions/renderer/api/automation/automation_ax_tree_wrapper.h b/extensions/renderer/api/automation/automation_ax_tree_wrapper.h
index 0413bed..c0cb833 100644
--- a/extensions/renderer/api/automation/automation_ax_tree_wrapper.h
+++ b/extensions/renderer/api/automation/automation_ax_tree_wrapper.h
@@ -27,6 +27,9 @@
   // child trees, if any.
   static AutomationAXTreeWrapper* GetParentOfTreeId(ui::AXTreeID tree_id);
 
+  static std::map<ui::AXTreeID, AutomationAXTreeWrapper*>&
+  GetChildTreeIDReverseMap();
+
   ui::AXTreeID tree_id() const { return tree_id_; }
   ui::AXTree* tree() { return &tree_; }
   AutomationInternalCustomBindings* owner() { return owner_; }
@@ -49,8 +52,9 @@
 
   ui::AXTree::Selection GetUnignoredSelection();
 
-  static std::map<ui::AXTreeID, AutomationAXTreeWrapper*>&
-  GetChildTreeIDReverseMap();
+  // Returns an AXNode from the underlying tree if it both exists and is not
+  // ignored.
+  ui::AXNode* GetUnignoredNodeFromId(int32_t id);
 
  private:
   // AXTreeObserver overrides.
diff --git a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
index 404c61a..b5b152f 100644
--- a/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
+++ b/extensions/renderer/api/automation/automation_internal_custom_bindings.cc
@@ -200,7 +200,7 @@
     if (!tree_wrapper)
       return;
 
-    ui::AXNode* node = tree_wrapper->tree()->GetFromId(node_id);
+    ui::AXNode* node = tree_wrapper->GetUnignoredNodeFromId(node_id);
     if (!node)
       return;
 
@@ -256,7 +256,7 @@
     if (!tree_wrapper)
       return;
 
-    ui::AXNode* node = tree_wrapper->tree()->GetFromId(node_id);
+    ui::AXNode* node = tree_wrapper->GetUnignoredNodeFromId(node_id);
     if (!node)
       return;
 
@@ -314,7 +314,7 @@
     if (!tree_wrapper)
       return;
 
-    ui::AXNode* node = tree_wrapper->tree()->GetFromId(node_id);
+    ui::AXNode* node = tree_wrapper->GetUnignoredNodeFromId(node_id);
     if (!node)
       return;
 
@@ -366,7 +366,7 @@
     if (!tree_wrapper)
       return;
 
-    ui::AXNode* node = tree_wrapper->tree()->GetFromId(node_id);
+    ui::AXNode* node = tree_wrapper->GetUnignoredNodeFromId(node_id);
     if (!node)
       return;
 
@@ -422,7 +422,7 @@
     if (!tree_wrapper)
       return;
 
-    ui::AXNode* node = tree_wrapper->tree()->GetFromId(node_id);
+    ui::AXNode* node = tree_wrapper->GetUnignoredNodeFromId(node_id);
     if (!node)
       return;
 
diff --git a/gpu/command_buffer/client/transfer_buffer.cc b/gpu/command_buffer/client/transfer_buffer.cc
index 2213dc3..5b843b07 100644
--- a/gpu/command_buffer/client/transfer_buffer.cc
+++ b/gpu/command_buffer/client/transfer_buffer.cc
@@ -8,6 +8,7 @@
 
 #include <stddef.h>
 #include <stdint.h>
+#include <climits>
 
 #include "base/bits.h"
 #include "base/logging.h"
@@ -134,7 +135,12 @@
 }
 
 static unsigned int ComputePOTSize(unsigned int dimension) {
-  return (dimension == 0) ? 0 : 1 << base::bits::Log2Ceiling(dimension);
+  // Avoid shifting by more than the size of an unsigned int - 1, because that's
+  // undefined behavior.
+  return (dimension == 0)
+             ? 0
+             : 1 << std::min(static_cast<int>(sizeof(dimension) * CHAR_BIT - 1),
+                             base::bits::Log2Ceiling(dimension));
 }
 
 void TransferBuffer::ReallocateRingBuffer(unsigned int size, bool shrink) {
diff --git a/gpu/command_buffer/service/shared_context_state.cc b/gpu/command_buffer/service/shared_context_state.cc
index 27eb0385..89a13dc5 100644
--- a/gpu/command_buffer/service/shared_context_state.cc
+++ b/gpu/command_buffer/service/shared_context_state.cc
@@ -155,7 +155,10 @@
     options.fGlyphCacheTextureMaximumBytes = glyph_cache_max_texture_bytes_;
     options.fPersistentCache = cache;
     options.fAvoidStencilBuffers = workarounds.avoid_stencil_buffers;
-    options.fDisallowGLSLBinaryCaching = workarounds.disable_program_disk_cache;
+    if (workarounds.disable_program_disk_cache) {
+      options.fShaderCacheStrategy =
+          GrContextOptions::ShaderCacheStrategy::kBackendSource;
+    }
     options.fShaderErrorHandler = this;
     // TODO(csmartdalton): enable internal multisampling after the related Skia
     // rolls are in.
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index b88e0378..2a1077d3 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -1126,6 +1126,12 @@
     }
 
     builders {
+      name: "Android FYI SkiaRenderer GL (Nexus 5X)"
+      mixins: "android-gpu-fyi-ci"
+      mixins: "goma-rbe-prod"
+    }
+
+    builders {
       name: "Android FYI SkiaRenderer Vulkan (Pixel 2)"
       mixins: "android-gpu-fyi-ci"
       mixins: "goma-rbe-prod"
@@ -1323,6 +1329,13 @@
     }
 
     builders {
+      name: "android-marshmallow-x86-fyi-rel"
+      mixins: "android-fyi-ci"
+      mixins: "builderless"
+      mixins: "linux-xenial"
+    }
+
+    builders {
       name: "android-mojo-webview-rel"
       mixins: "fyi-ci"
       mixins: "linux-xenial"
@@ -4031,6 +4044,12 @@
       dimensions: "ssd:1"
     }
     builders {
+      name: "android-marshmallow-x86-fyi-rel"
+      mixins: "android-try"
+      mixins: "builderless"
+      mixins: "linux-xenial"
+    }
+    builders {
       mixins: "android-try"
       name: "android-oreo-arm64-cts-networkservice-dbg"
       mixins: "linux-xenial"
@@ -4129,6 +4148,11 @@
     builders {
       mixins: "android-optional-gpu-try"
       mixins: "goma-rbe-prod"
+      name: "gpu-fyi-try-android-m-nexus-5x-skgl-64"
+    }
+    builders {
+      mixins: "android-optional-gpu-try"
+      mixins: "goma-rbe-prod"
       name: "gpu-fyi-try-android-m-nexus-6p-64"
     }
     builders {
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index b91b1f3..a024db1 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -1640,8 +1640,13 @@
     short_name: "ns"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/android-marshmallow-x86-fyi-rel"
+    category: "emulator|M|x86"
+    short_name: "rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/android-pie-x86-fyi-rel"
-    category: "emulator|pie|x86"
+    category: "emulator|P|x86"
     short_name: "rel"
   }
 }
@@ -3746,6 +3751,11 @@
     short_name: "P2"
   }
   builders {
+    name: "buildbucket/luci.chromium.ci/Android FYI SkiaRenderer GL (Nexus 5X)"
+    category: "Android|skgl|M64"
+    short_name: "N5X"
+  }
+  builders {
     name: "buildbucket/luci.chromium.ci/Android FYI SkiaRenderer Vulkan (Pixel 2)"
     category: "Android|skv|P32"
     short_name: "P2"
@@ -4314,6 +4324,9 @@
     name: "buildbucket/luci.chromium.try/android-marshmallow-arm64-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-marshmallow-x86-fyi-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-oreo-arm64-rel"
   }
   builders {
@@ -4341,6 +4354,9 @@
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-android-l-nexus-6-32"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/gpu-fyi-try-android-m-nexus-5x-skgl-64"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-android-m-nexus-5x-64"
   }
   builders {
@@ -4969,6 +4985,9 @@
     name: "buildbucket/luci.chromium.try/android-marshmallow-arm64-rel"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/android-marshmallow-x64-fyi-rel"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/android-oreo-arm64-cts-networkservice-dbg"
   }
   builders {
@@ -5068,6 +5087,9 @@
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-android-m-nexus-5x-deqp-64"
   }
   builders {
+    name: "buildbucket/luci.chromium.try/gpu-fyi-try-android-m-nexus-5x-skgl-64"
+  }
+  builders {
     name: "buildbucket/luci.chromium.try/gpu-fyi-try-android-m-nexus-6p-64"
   }
   builders {
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index c04c839..347f0fc 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -88,6 +88,7 @@
   triggers: "Android FYI Release (Nexus 6P)"
   triggers: "Android FYI Release (Nexus 9)"
   triggers: "Android FYI Release (Pixel 2)"
+  triggers: "Android FYI SkiaRenderer GL (Nexus 5X)"
   triggers: "Android FYI SkiaRenderer Vulkan (Pixel 2)"
   triggers: "Android FYI dEQP Release (Nexus 5X)"
   triggers: "Android Release (Nexus 5X)"
@@ -602,6 +603,17 @@
 }
 
 job {
+  id: "Android FYI SkiaRenderer GL (Nexus 5X)"
+  acl_sets: "default"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "Android FYI SkiaRenderer GL (Nexus 5X)"
+  }
+}
+
+
+job {
   id: "Android FYI SkiaRenderer Vulkan (Pixel 2)"
   acl_sets: "default"
   buildbucket: {
@@ -881,6 +893,19 @@
 }
 
 job {
+  id: "android-marshmallow-x86-fyi-rel"
+  acl_sets: "default"
+  # Only run nightly while we work out test issues and
+  # productionize emulator infra. crbug.com/961025
+  schedule: "0 7 * * *"
+  buildbucket: {
+    server: "cr-buildbucket.appspot.com"
+    bucket: "luci.chromium.ci"
+    builder: "android-marshmallow-x86-fyi-rel"
+  }
+}
+
+job {
   id: "android-mojo-webview-rel"
   acl_sets: "default"
   buildbucket: {
diff --git a/ios/chrome/browser/ui/presenters/non_modal_view_controller_presenter.mm b/ios/chrome/browser/ui/presenters/non_modal_view_controller_presenter.mm
index 38422a9..899b468d 100644
--- a/ios/chrome/browser/ui/presenters/non_modal_view_controller_presenter.mm
+++ b/ios/chrome/browser/ui/presenters/non_modal_view_controller_presenter.mm
@@ -61,42 +61,57 @@
 - (void)presentAnimated:(BOOL)animated {
   DCHECK(!self.animator) << "Presenting again is not supported.";
   __weak __typeof(self) weakSelf = self;
-  self.animator = [[UIViewPropertyAnimator alloc]
-      initWithDuration:animated ? kAnimationInDuration : 0
-                 curve:UIViewAnimationCurveEaseOut
-            animations:^{
-              weakSelf.containerView.alpha = 1.0;
-              weakSelf.containerView.transform = CGAffineTransformIdentity;
-            }];
-  [self.animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
+  auto animation = ^{
+    weakSelf.containerView.alpha = 1.0;
+    weakSelf.containerView.transform = CGAffineTransformIdentity;
+  };
+  auto completion = ^void(UIViewAnimatingPosition) {
     [weakSelf.presentedViewController
         didMoveToParentViewController:weakSelf.baseViewController];
     [weakSelf.delegate containedPresenterDidPresent:weakSelf];
-  }];
-  [self.animator startAnimation];
+  };
+
+  if (animated) {
+    self.animator = [[UIViewPropertyAnimator alloc]
+        initWithDuration:animated ? kAnimationInDuration : 0
+                   curve:UIViewAnimationCurveEaseOut
+              animations:animation];
+    [self.animator addCompletion:completion];
+    [self.animator startAnimation];
+  } else {
+    animation();
+    completion(UIViewAnimatingPositionEnd);
+  }
 }
 
 - (void)dismissAnimated:(BOOL)animated {
-  if (self.animator.state == UIViewAnimatingStateActive) {
+  if (self.animator.state == UIViewAnimatingStateActive)
     [self.animator stopAnimation:YES];
-  }
 
   [self.presentedViewController willMoveToParentViewController:nil];
 
   __weak __typeof(self) weakSelf = self;
-  UIViewPropertyAnimator* dismissAnimator = [[UIViewPropertyAnimator alloc]
-      initWithDuration:animated ? kAnimationOutDuration : 0
-                 curve:UIViewAnimationCurveEaseOut
-            animations:^{
-              weakSelf.containerView.alpha = 0.0;
-            }];
-  [dismissAnimator addCompletion:^(UIViewAnimatingPosition finalPosition) {
+  auto animation = ^{
+    weakSelf.containerView.alpha = 0.0;
+  };
+  auto completion = ^void(UIViewAnimatingPosition) {
     [weakSelf.presentedViewController.view removeFromSuperview];
     [weakSelf.presentedViewController removeFromParentViewController];
     [weakSelf.containerView removeFromSuperview];
     [weakSelf.delegate containedPresenterDidDismiss:weakSelf];
-  }];
-  [dismissAnimator startAnimation];
+  };
+
+  if (animated) {
+    UIViewPropertyAnimator* dismissAnimator = [[UIViewPropertyAnimator alloc]
+        initWithDuration:animated ? kAnimationOutDuration : 0
+                   curve:UIViewAnimationCurveEaseOut
+              animations:animation];
+    [dismissAnimator addCompletion:completion];
+    [dismissAnimator startAnimation];
+  } else {
+    animation();
+    completion(UIViewAnimatingPositionEnd);
+  }
 }
 
 @end
diff --git a/ios/chrome/browser/web/BUILD.gn b/ios/chrome/browser/web/BUILD.gn
index 5a5be98..bc6e75d 100644
--- a/ios/chrome/browser/web/BUILD.gn
+++ b/ios/chrome/browser/web/BUILD.gn
@@ -383,6 +383,7 @@
     "error_page_egtest.mm",
     "http_auth_egtest.mm",
     "js_print_egtest.mm",
+    "navigation_egtest.mm",
   ]
 
   deps = [
diff --git a/ios/chrome/browser/web/navigation_egtest.mm b/ios/chrome/browser/web/navigation_egtest.mm
index 288bb226..a9e6bc14 100644
--- a/ios/chrome/browser/web/navigation_egtest.mm
+++ b/ios/chrome/browser/web/navigation_egtest.mm
@@ -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 <EarlGrey/EarlGrey.h>
-#import <XCTest/XCTest.h>
-
 #include "base/bind.h"
 #include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/test/app/chrome_test_util.h"
 #import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
 #import "ios/chrome/test/earl_grey/chrome_matchers.h"
 #import "ios/chrome/test/earl_grey/chrome_test_case.h"
 #include "ios/net/url_test_util.h"
+#import "ios/testing/earl_grey/earl_grey_test.h"
 #include "net/test/embedded_test_server/http_request.h"
 #include "net/test/embedded_test_server/http_response.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -24,7 +20,6 @@
 using chrome_test_util::ContentSuggestionCollectionView;
 using chrome_test_util::BackButton;
 using chrome_test_util::ForwardButton;
-using chrome_test_util::PurgeCachedWebViewPages;
 using chrome_test_util::OmniboxText;
 
 namespace {
@@ -332,7 +327,9 @@
   // Tap the forward button and verify test page is loaded.
   [[EarlGrey selectElementWithMatcher:ForwardButton()]
       performAction:grey_tap()];
-  [ChromeEarlGrey waitForWebStateContainingText:"pony"];
+  const NSTimeInterval kWaitForWebStateTimeout = 10;
+  [ChromeEarlGrey waitForWebStateContainingText:"pony"
+                                        timeout:kWaitForWebStateTimeout];
 }
 
 #pragma mark window.location.hash operations
@@ -516,8 +513,7 @@
                      "});",
                     kNoHashChangeText, content.c_str()];
 
-  NSError* error = nil;
-  chrome_test_util::ExecuteJavaScript(script, &error);
+  [ChromeEarlGrey executeJavaScript:script];
 }
 
 - (void)verifyBackAndForwardAfterRedirect:(std::string)redirectLabel {
@@ -570,7 +566,7 @@
   [ChromeEarlGrey loadURL:destinationURL];
   [ChromeEarlGrey goBack];
 
-  GREYAssert(PurgeCachedWebViewPages(), @"History not restored");
+  [ChromeEarlGrey purgeCachedWebViewPages];
 
   [ChromeEarlGrey waitForWebStateContainingText:"Revision"];
   [[EarlGrey selectElementWithMatcher:OmniboxText("chrome://version")]
@@ -589,7 +585,7 @@
   [ChromeEarlGrey loadURL:destinationURL];
   [ChromeEarlGrey goBack];
 
-  GREYAssert(PurgeCachedWebViewPages(), @"History not restored");
+  [ChromeEarlGrey purgeCachedWebViewPages];
 
   [ChromeEarlGrey goForward];
   [ChromeEarlGrey waitForWebStateContainingText:"pony"];
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.h b/ios/chrome/test/earl_grey/chrome_earl_grey.h
index b9c3aa1..6bd00bc 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.h
@@ -282,6 +282,11 @@
 // not met within a timeout a GREYAssert is induced.
 - (void)waitForWebStateContainingText:(const std::string&)UTF8Text;
 
+// Waits for the current web state to contain |UTF8Text|. If the condition is
+// not met within the given |timeout| a GREYAssert is induced.
+- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
+                              timeout:(NSTimeInterval)timeout;
+
 // Waits for there to be no web state containing |UTF8Text|.
 // If the condition is not met within a timeout a GREYAssert is induced.
 - (void)waitForWebStateNotContainingText:(const std::string&)UTF8Text;
@@ -301,6 +306,12 @@
 // Returns the current web state's VisibleURL.
 - (GURL)webStateVisibleURL;
 
+// Purges cached web view pages, so the next time back navigation will not use
+// a cached page. Browsers don't have to use a fresh version for back/forward
+// navigation for HTTP pages and may serve a version from the cache even if the
+// Cache-Control response header says otherwise.
+- (void)purgeCachedWebViewPages;
+
 #pragma mark - Bookmarks Utilities (EG2)
 
 // Waits for the bookmark internal state to be done loading.
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey.mm b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
index 01791675..22b153eb 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey.mm
@@ -41,6 +41,8 @@
     @"Page did not finish loading";
 NSString* const kTypedURLError =
     @"Error occurred during typed URL verification.";
+NSString* const kWaitForRestoreSessionToFinishError =
+    @"Session restoration did not finish";
 }
 
 #if !defined(__has_feature) || !__has_feature(objc_arc)
@@ -51,6 +53,14 @@
 GREY_STUB_CLASS_IN_APP_MAIN_QUEUE(ChromeEarlGreyAppInterface)
 #endif  // defined(CHROME_EARL_GREY_2)
 
+@interface ChromeEarlGreyImpl ()
+
+// Waits for session restoration to finish within a timeout, or a GREYAssert is
+// induced.
+- (void)waitForRestoreSessionToFinish;
+
+@end
+
 @implementation ChromeEarlGreyImpl
 
 #pragma mark - Device Utilities
@@ -319,6 +329,19 @@
   EG_TEST_HELPER_ASSERT_TRUE(tabCountEqual, errorString);
 }
 
+- (void)waitForRestoreSessionToFinish {
+  GREYCondition* finishedRestoreSession = [GREYCondition
+      conditionWithName:kWaitForRestoreSessionToFinishError
+                  block:^{
+                    return !
+                        [ChromeEarlGreyAppInterface isRestoreSessionInProgress];
+                  }];
+  bool restoreSessionCompleted =
+      [finishedRestoreSession waitWithTimeout:kWaitForPageLoadTimeout];
+  EG_TEST_HELPER_ASSERT_TRUE(restoreSessionCompleted,
+                             kWaitForRestoreSessionToFinishError);
+}
+
 - (void)submitWebStateFormWithID:(const std::string&)UTF8FormID {
   NSString* formID = base::SysUTF8ToNSString(UTF8FormID);
   EG_TEST_HELPER_ASSERT_NO_ERROR(
@@ -326,6 +349,12 @@
 }
 
 - (void)waitForWebStateContainingText:(const std::string&)UTF8Text {
+  [self waitForWebStateContainingText:UTF8Text
+                              timeout:kWaitForUIElementTimeout];
+}
+
+- (void)waitForWebStateContainingText:(const std::string&)UTF8Text
+                              timeout:(NSTimeInterval)timeout {
   NSString* text = base::SysUTF8ToNSString(UTF8Text);
   NSString* errorString = [NSString
       stringWithFormat:@"Failed waiting for web state containing %@", text];
@@ -336,7 +365,7 @@
                     return
                         [ChromeEarlGreyAppInterface webStateContainsText:text];
                   }];
-  bool containsText = [waitForText waitWithTimeout:kWaitForUIElementTimeout];
+  bool containsText = [waitForText waitWithTimeout:timeout];
   EG_TEST_HELPER_ASSERT_TRUE(containsText, errorString);
 }
 
@@ -374,6 +403,12 @@
       base::SysNSStringToUTF8([ChromeEarlGreyAppInterface webStateVisibleURL]));
 }
 
+- (void)purgeCachedWebViewPages {
+  [ChromeEarlGreyAppInterface purgeCachedWebViewPages];
+  [self waitForRestoreSessionToFinish];
+  [self waitForPageToFinishLoading];
+}
+
 #pragma mark - Settings Utilities (EG2)
 
 - (void)setContentSettings:(ContentSetting)setting {
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
index 77ada6ab..05a25519 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h
@@ -154,6 +154,16 @@
 // Returns the current WebState's VisibleURL.
 + (NSString*)webStateVisibleURL;
 
+// Purges cached web view pages in the current web state, so the next time back
+// navigation will not use a cached page. Browsers don't have to use a fresh
+// version for back/forward navigation for HTTP pages and may serve a version
+// from the cache even if the Cache-Control response header says otherwise.
++ (void)purgeCachedWebViewPages;
+
+// Returns YES if the current WebState's navigation manager is currently
+// restoring session state.
++ (BOOL)isRestoreSessionInProgress;
+
 #pragma mark - Sync Utilities (EG2)
 
 // Clears the autofill profile for the given |GUID|.
diff --git a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
index fcc77ec..c5f290b 100644
--- a/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
+++ b/ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.mm
@@ -24,6 +24,7 @@
 #import "ios/testing/nserror_util.h"
 #import "ios/web/common/features.h"
 #import "ios/web/public/deprecated/crw_js_injection_receiver.h"
+#import "ios/web/public/navigation/navigation_manager.h"
 #import "ios/web/public/test/earl_grey/js_test_util.h"
 #import "ios/web/public/test/element_selector.h"
 #import "ios/web/public/test/web_view_content_test_util.h"
@@ -266,6 +267,18 @@
       chrome_test_util::GetCurrentWebState()->GetVisibleURL().spec());
 }
 
++ (void)purgeCachedWebViewPages {
+  web::WebState* web_state = chrome_test_util::GetCurrentWebState();
+  web_state->SetWebUsageEnabled(false);
+  web_state->SetWebUsageEnabled(true);
+  web_state->GetNavigationManager()->LoadIfNecessary();
+}
+
++ (BOOL)isRestoreSessionInProgress {
+  web::WebState* web_state = chrome_test_util::GetCurrentWebState();
+  return web_state->GetNavigationManager()->IsRestoreSessionInProgress();
+}
+
 #pragma mark - Sync Utilities (EG2)
 
 + (void)clearAutofillProfileWithGUID:(NSString*)GUID {
diff --git a/media/test/data/player.html b/media/test/data/player.html
index 948415a..7ff5f6e 100644
--- a/media/test/data/player.html
+++ b/media/test/data/player.html
@@ -204,6 +204,7 @@
 
   // Starts the player.
   player.loop = loop;
+  player.preload = 'auto';
   player.src = media_url;
   Log('Starting by calling play()');
   player.play().catch(function(error) {
diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm
index 121b010..45e0bbbdd 100644
--- a/printing/printing_context_mac.mm
+++ b/printing/printing_context_mac.mm
@@ -12,7 +12,6 @@
 
 #include "base/logging.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/sys_string_conversions.h"
 #include "base/strings/utf_string_conversions.h"
@@ -87,57 +86,57 @@
                                             PrintSettingsCallback callback) {
   // Exceptions can also happen when the NSPrintPanel is being
   // deallocated, so it must be autoreleased within this scope.
-  base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
+    DCHECK([NSThread isMainThread]);
 
-  DCHECK([NSThread isMainThread]);
+    // We deliberately don't feed max_pages into the dialog, because setting
+    // NSPrintLastPage makes the print dialog pre-select the option to only
+    // print a range.
 
-  // We deliberately don't feed max_pages into the dialog, because setting
-  // NSPrintLastPage makes the print dialog pre-select the option to only print
-  // a range.
+    // TODO(stuartmorgan): implement 'print selection only' (probably requires
+    // adding a new custom view to the panel on 10.5; 10.6 has
+    // NSPrintPanelShowsPrintSelection).
+    NSPrintPanel* panel = [NSPrintPanel printPanel];
+    NSPrintInfo* printInfo = print_info_.get();
 
-  // TODO(stuartmorgan): implement 'print selection only' (probably requires
-  // adding a new custom view to the panel on 10.5; 10.6 has
-  // NSPrintPanelShowsPrintSelection).
-  NSPrintPanel* panel = [NSPrintPanel printPanel];
-  NSPrintInfo* printInfo = print_info_.get();
+    NSPrintPanelOptions options = [panel options];
+    options |= NSPrintPanelShowsPaperSize;
+    options |= NSPrintPanelShowsOrientation;
+    options |= NSPrintPanelShowsScaling;
+    [panel setOptions:options];
 
-  NSPrintPanelOptions options = [panel options];
-  options |= NSPrintPanelShowsPaperSize;
-  options |= NSPrintPanelShowsOrientation;
-  options |= NSPrintPanelShowsScaling;
-  [panel setOptions:options];
-
-  // Set the print job title text.
-  gfx::NativeView parent_view = delegate_->GetParentView();
-  if (parent_view) {
-    NSString* job_title = [[parent_view.GetNativeNSView() window] title];
-    if (job_title) {
-      PMPrintSettings printSettings =
-          (PMPrintSettings)[printInfo PMPrintSettings];
-      PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
-      [printInfo updateFromPMPrintSettings];
+    // Set the print job title text.
+    gfx::NativeView parent_view = delegate_->GetParentView();
+    if (parent_view) {
+      NSString* job_title = [[parent_view.GetNativeNSView() window] title];
+      if (job_title) {
+        PMPrintSettings printSettings =
+            (PMPrintSettings)[printInfo PMPrintSettings];
+        PMPrintSettingsSetJobName(printSettings, (CFStringRef)job_title);
+        [printInfo updateFromPMPrintSettings];
+      }
     }
+
+    // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
+    // Will require restructuring the PrintingContext API to use a callback.
+
+    // This function may be called in the middle of a CATransaction, where
+    // running a modal panel is forbidden. That situation isn't ideal, but from
+    // this code's POV the right answer is to defer running the panel until
+    // after the current transaction. See https://crbug.com/849538.
+    __block auto block_callback = std::move(callback);
+    [CATransaction setCompletionBlock:^{
+      NSInteger selection = [panel runModalWithPrintInfo:printInfo];
+      if (selection == NSOKButton) {
+        print_info_.reset([[panel printInfo] retain]);
+        settings_->set_ranges(GetPageRangesFromPrintInfo());
+        InitPrintSettingsFromPrintInfo();
+        std::move(block_callback).Run(OK);
+      } else {
+        std::move(block_callback).Run(CANCEL);
+      }
+    }];
   }
-
-  // TODO(stuartmorgan): We really want a tab sheet here, not a modal window.
-  // Will require restructuring the PrintingContext API to use a callback.
-
-  // This function may be called in the middle of a CATransaction, where
-  // running a modal panel is forbidden. That situation isn't ideal, but from
-  // this code's POV the right answer is to defer running the panel until after
-  // the current transaction. See https://crbug.com/849538.
-  __block auto block_callback = std::move(callback);
-  [CATransaction setCompletionBlock:^{
-    NSInteger selection = [panel runModalWithPrintInfo:printInfo];
-    if (selection == NSOKButton) {
-      print_info_.reset([[panel printInfo] retain]);
-      settings_->set_ranges(GetPageRangesFromPrintInfo());
-      InitPrintSettingsFromPrintInfo();
-      std::move(block_callback).Run(OK);
-    } else {
-      std::move(block_callback).Run(CANCEL);
-    }
-  }];
 }
 
 gfx::Size PrintingContextMac::GetPdfPaperSizeDeviceUnits() {
diff --git a/remoting/base/breakpad_mac.mm b/remoting/base/breakpad_mac.mm
index 3cd6ef3..34e6024 100644
--- a/remoting/base/breakpad_mac.mm
+++ b/remoting/base/breakpad_mac.mm
@@ -7,61 +7,58 @@
 #include <Foundation/Foundation.h>
 
 #include "base/logging.h"
-#import "base/mac/scoped_nsautorelease_pool.h"
 #import "third_party/breakpad/breakpad/src/client/mac/Framework/Breakpad.h"
 
 namespace remoting {
 
 void InitializeCrashReporting() {
-  base::mac::ScopedNSAutoreleasePool autorelease_pool;
+  @autoreleasepool {
+    NSBundle* main_bundle = [NSBundle mainBundle];
 
-  NSBundle* main_bundle = [NSBundle mainBundle];
+    // Tell Breakpad where crash_inspector and crash_report_sender are.
+    NSString* resource_path = [main_bundle resourcePath];
+    NSString* inspector_location =
+        [resource_path stringByAppendingPathComponent:@"crash_inspector"];
+    NSString* reporter_bundle_location = [resource_path
+        stringByAppendingPathComponent:@"crash_report_sender.app"];
+    NSString* reporter_location =
+        [[NSBundle bundleWithPath:reporter_bundle_location] executablePath];
 
-  // Tell Breakpad where crash_inspector and crash_report_sender are.
-  NSString* resource_path = [main_bundle resourcePath];
-  NSString* inspector_location =
-      [resource_path stringByAppendingPathComponent:@"crash_inspector"];
-  NSString* reporter_bundle_location =
-      [resource_path stringByAppendingPathComponent:@"crash_report_sender.app"];
-  NSString* reporter_location =
-      [[NSBundle bundleWithPath:reporter_bundle_location] executablePath];
+    NSDictionary* info_dictionary = [main_bundle infoDictionary];
+    NSMutableDictionary* breakpad_config =
+        [[info_dictionary mutableCopy] autorelease];
+    [breakpad_config setObject:inspector_location
+                        forKey:@BREAKPAD_INSPECTOR_LOCATION];
+    [breakpad_config setObject:reporter_location
+                        forKey:@BREAKPAD_REPORTER_EXE_LOCATION];
 
-  NSDictionary* info_dictionary = [main_bundle infoDictionary];
-  NSMutableDictionary* breakpad_config =
-      [[info_dictionary mutableCopy] autorelease];
-  [breakpad_config setObject:inspector_location
-                      forKey:@BREAKPAD_INSPECTOR_LOCATION];
-  [breakpad_config setObject:reporter_location
-                      forKey:@BREAKPAD_REPORTER_EXE_LOCATION];
+    // Configure Breakpad settings here, if they are not already customized in
+    // the Info.plist. These settings should be added to the plist, but the
+    // problem is that the Breakpad URL contains a double-slash, which is broken
+    // by the INFOPLIST_PREPROCESS step.
+    // TODO(lambroslambrou): Add these to the Info.plist, similarly to what is
+    // done for Chrome Framework - see 'Tweak Info.plist' in
+    // chrome/chrome_dll_bundle.gypi.
+    if (![breakpad_config objectForKey:@BREAKPAD_SKIP_CONFIRM]) {
+      // Skip the upload confirmation dialog, since this is a remote-access
+      // service that shouldn't rely on a console user to dismiss any prompt.
+      // Also, this may be running in the LoginWindow context, where prompting
+      // might not be possible.
+      [breakpad_config setObject:@"YES" forKey:@BREAKPAD_SKIP_CONFIRM];
+    }
+    if (![breakpad_config objectForKey:@BREAKPAD_REPORT_INTERVAL]) {
+      // Set a minimum 6-hour interval between crash-reports, to match the
+      // throttling used on Windows.
+      [breakpad_config setObject:@"21600" forKey:@BREAKPAD_REPORT_INTERVAL];
+    }
+    if (![breakpad_config objectForKey:@BREAKPAD_URL]) {
+      [breakpad_config setObject:@"https://clients2.google.com/cr/report"
+                          forKey:@BREAKPAD_URL];
+    }
 
-  // Configure Breakpad settings here, if they are not already customized in
-  // the Info.plist. These settings should be added to the plist, but the
-  // problem is that the Breakpad URL contains a double-slash, which is broken
-  // by the INFOPLIST_PREPROCESS step.
-  // TODO(lambroslambrou): Add these to the Info.plist, similarly to what is
-  // done for Chrome Framework - see 'Tweak Info.plist' in
-  // chrome/chrome_dll_bundle.gypi.
-  if (![breakpad_config objectForKey:@BREAKPAD_SKIP_CONFIRM]) {
-    // Skip the upload confirmation dialog, since this is a remote-access
-    // service that shouldn't rely on a console user to dismiss any prompt.
-    // Also, this may be running in the LoginWindow context, where prompting
-    // might not be possible.
-    [breakpad_config setObject:@"YES"
-                        forKey:@BREAKPAD_SKIP_CONFIRM];
-  }
-  if (![breakpad_config objectForKey:@BREAKPAD_REPORT_INTERVAL]) {
-    // Set a minimum 6-hour interval between crash-reports, to match the
-    // throttling used on Windows.
-    [breakpad_config setObject:@"21600"
-                        forKey:@BREAKPAD_REPORT_INTERVAL];
-  }
-  if (![breakpad_config objectForKey:@BREAKPAD_URL]) {
-    [breakpad_config setObject:@"https://clients2.google.com/cr/report"
-                        forKey:@BREAKPAD_URL];
-  }
-
-  if (!BreakpadCreate(breakpad_config)) {
-    LOG(ERROR) << "Breakpad initialization failed";
+    if (!BreakpadCreate(breakpad_config)) {
+      LOG(ERROR) << "Breakpad initialization failed";
+    }
   }
 }
 
diff --git a/remoting/host/continue_window_mac.mm b/remoting/host/continue_window_mac.mm
index 9cf0bf6..a9954ac 100644
--- a/remoting/host/continue_window_mac.mm
+++ b/remoting/host/continue_window_mac.mm
@@ -10,7 +10,6 @@
 
 #include "base/compiler_specific.h"
 #include "base/logging.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
 #include "base/strings/sys_string_conversions.h"
@@ -62,17 +61,19 @@
 void ContinueWindowMac::ShowUi() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  base::mac::ScopedNSAutoreleasePool pool;
-  controller_.reset(
-      [[ContinueWindowMacController alloc] initWithWindow:this]);
-  [controller_ show];
+  @autoreleasepool {
+    controller_.reset(
+        [[ContinueWindowMacController alloc] initWithWindow:this]);
+    [controller_ show];
+  }
 }
 
 void ContinueWindowMac::HideUi() {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
 
-  base::mac::ScopedNSAutoreleasePool pool;
-  [controller_ hide];
+  @autoreleasepool {
+    [controller_ hide];
+  }
 }
 
 // static
diff --git a/remoting/host/it2me/it2me_confirmation_dialog_mac.mm b/remoting/host/it2me/it2me_confirmation_dialog_mac.mm
index 827ac18..4afeddff 100644
--- a/remoting/host/it2me/it2me_confirmation_dialog_mac.mm
+++ b/remoting/host/it2me/it2me_confirmation_dialog_mac.mm
@@ -14,7 +14,6 @@
 #include "base/callback.h"
 #include "base/i18n/message_formatter.h"
 #include "base/location.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
 #include "base/strings/sys_string_conversions.h"
@@ -76,8 +75,9 @@
   dialog_timer_.Stop();
 
   if (controller_) {
-    base::mac::ScopedNSAutoreleasePool pool;
-    [controller_ hide];
+    @autoreleasepool {
+      [controller_ hide];
+    }
   }
 }
 
@@ -92,20 +92,22 @@
   ResultCallback dialog_action_callback = base::Bind(
       &It2MeConfirmationDialogMac::OnDialogAction, base::Unretained(this));
 
-  base::mac::ScopedNSAutoreleasePool pool;
-  controller_.reset([[It2MeConfirmationDialogMacController alloc]
-      initWithCallback:dialog_action_callback
-              username:remote_user_email]);
-  [controller_ show];
+  @autoreleasepool {
+    controller_.reset([[It2MeConfirmationDialogMacController alloc]
+        initWithCallback:dialog_action_callback
+                username:remote_user_email]);
+    [controller_ show];
+  }
 }
 
 void It2MeConfirmationDialogMac::OnDialogAction(Result result) {
   dialog_timer_.Stop();
 
   if (controller_) {
-    base::mac::ScopedNSAutoreleasePool pool;
-    [controller_ hide];
-    controller_.reset();
+    @autoreleasepool {
+      [controller_ hide];
+      controller_.reset();
+    }
   }
 
   if (result_callback_) {
diff --git a/rlz/mac/lib/rlz_value_store_mac.mm b/rlz/mac/lib/rlz_value_store_mac.mm
index a8ba689..60ad170 100644
--- a/rlz/mac/lib/rlz_value_store_mac.mm
+++ b/rlz/mac/lib/rlz_value_store_mac.mm
@@ -353,15 +353,15 @@
 namespace testing {
 
 void SetRlzStoreDirectory(const base::FilePath& directory) {
-  base::mac::ScopedNSAutoreleasePool pool;
-
-  [g_test_folder release];
-  if (directory.empty()) {
-    g_test_folder = nil;
-  } else {
-    // Not Unsafe on OS X.
-    g_test_folder =
-      [[NSString alloc] initWithUTF8String:directory.AsUTF8Unsafe().c_str()];
+  @autoreleasepool {
+    [g_test_folder release];
+    if (directory.empty()) {
+      g_test_folder = nil;
+    } else {
+      // Not Unsafe on OS X.
+      g_test_folder = [[NSString alloc]
+          initWithUTF8String:directory.AsUTF8Unsafe().c_str()];
+    }
   }
 }
 
diff --git a/services/device/bluetooth/bluetooth_system_factory.cc b/services/device/bluetooth/bluetooth_system_factory.cc
index 47f731c..5cfdb60f 100644
--- a/services/device/bluetooth/bluetooth_system_factory.cc
+++ b/services/device/bluetooth/bluetooth_system_factory.cc
@@ -7,15 +7,15 @@
 #include <memory>
 #include <utility>
 
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/device/bluetooth/bluetooth_system.h"
 
 namespace device {
 
 void BluetoothSystemFactory::CreateFactory(
-    mojom::BluetoothSystemFactoryRequest request) {
-  mojo::MakeStrongBinding(std::make_unique<BluetoothSystemFactory>(),
-                          std::move(request));
+    mojo::PendingReceiver<mojom::BluetoothSystemFactory> receiver) {
+  mojo::MakeSelfOwnedReceiver(std::make_unique<BluetoothSystemFactory>(),
+                              std::move(receiver));
 }
 
 BluetoothSystemFactory::BluetoothSystemFactory() = default;
diff --git a/services/device/bluetooth/bluetooth_system_factory.h b/services/device/bluetooth/bluetooth_system_factory.h
index 05c4b821..e315e373 100644
--- a/services/device/bluetooth/bluetooth_system_factory.h
+++ b/services/device/bluetooth/bluetooth_system_factory.h
@@ -6,13 +6,15 @@
 #define SERVICES_DEVICE_BLUETOOTH_BLUETOOTH_SYSTEM_FACTORY_H_
 
 #include "base/macros.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "services/device/public/mojom/bluetooth_system.mojom.h"
 
 namespace device {
 
 class BluetoothSystemFactory : public mojom::BluetoothSystemFactory {
  public:
-  static void CreateFactory(mojom::BluetoothSystemFactoryRequest request);
+  static void CreateFactory(
+      mojo::PendingReceiver<mojom::BluetoothSystemFactory> receiver);
 
   BluetoothSystemFactory();
   ~BluetoothSystemFactory() override;
diff --git a/services/device/bluetooth/bluetooth_system_unittest.cc b/services/device/bluetooth/bluetooth_system_unittest.cc
index 7239df9..992e6705 100644
--- a/services/device/bluetooth/bluetooth_system_unittest.cc
+++ b/services/device/bluetooth/bluetooth_system_unittest.cc
@@ -22,6 +22,7 @@
 #include "device/bluetooth/dbus/bluetooth_device_client.h"
 #include "device/bluetooth/dbus/bluez_dbus_manager.h"
 #include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/device_service_test_base.h"
 #include "services/device/public/mojom/bluetooth_system.mojom-test-utils.h"
 #include "services/device/public/mojom/bluetooth_system.mojom.h"
@@ -649,7 +650,8 @@
 
   void SetUp() override {
     DeviceServiceTestBase::SetUp();
-    connector()->BindInterface(mojom::kServiceName, &system_factory_);
+    connector()->Connect(mojom::kServiceName,
+                         system_factory_.BindNewPipeAndPassReceiver());
 
     auto test_bluetooth_adapter_client =
         std::make_unique<TestBluetoothAdapterClient>();
@@ -761,7 +763,7 @@
   using ScanStateVector = std::vector<mojom::BluetoothSystem::ScanState>;
   ScanStateVector on_scan_state_changed_states_;
 
-  mojom::BluetoothSystemFactoryPtr system_factory_;
+  mojo::Remote<mojom::BluetoothSystemFactory> system_factory_;
 
   TestBluetoothAdapterClient* test_bluetooth_adapter_client_;
   TestBluetoothDeviceClient* test_bluetooth_device_client_;
diff --git a/services/device/device_service.cc b/services/device/device_service.cc
index c70c483..a517c10 100644
--- a/services/device/device_service.cc
+++ b/services/device/device_service.cc
@@ -210,7 +210,7 @@
 
 #if defined(OS_CHROMEOS)
   registry_.AddInterface<mojom::BluetoothSystemFactory>(
-      base::BindRepeating(&DeviceService::BindBluetoothSystemFactoryRequest,
+      base::BindRepeating(&DeviceService::BindBluetoothSystemFactoryReceiver,
                           base::Unretained(this)));
   registry_.AddInterface<mojom::MtpManager>(base::BindRepeating(
       &DeviceService::BindMtpManagerRequest, base::Unretained(this)));
@@ -254,9 +254,9 @@
 #endif
 
 #if defined(OS_CHROMEOS)
-void DeviceService::BindBluetoothSystemFactoryRequest(
-    mojom::BluetoothSystemFactoryRequest request) {
-  BluetoothSystemFactory::CreateFactory(std::move(request));
+void DeviceService::BindBluetoothSystemFactoryReceiver(
+    mojo::PendingReceiver<mojom::BluetoothSystemFactory> receiver) {
+  BluetoothSystemFactory::CreateFactory(std::move(receiver));
 }
 
 void DeviceService::BindMtpManagerRequest(mojom::MtpManagerRequest request) {
diff --git a/services/device/device_service.h b/services/device/device_service.h
index 2db5b1b3..b77e5a9 100644
--- a/services/device/device_service.h
+++ b/services/device/device_service.h
@@ -156,8 +156,8 @@
 #endif
 
 #if defined(OS_CHROMEOS)
-  void BindBluetoothSystemFactoryRequest(
-      mojom::BluetoothSystemFactoryRequest request);
+  void BindBluetoothSystemFactoryReceiver(
+      mojo::PendingReceiver<mojom::BluetoothSystemFactory> receiver);
   void BindMtpManagerRequest(mojom::MtpManagerRequest request);
 #endif
 
diff --git a/services/device/geolocation/wifi_data_provider_mac.mm b/services/device/geolocation/wifi_data_provider_mac.mm
index 023645f9..83cae22 100644
--- a/services/device/geolocation/wifi_data_provider_mac.mm
+++ b/services/device/geolocation/wifi_data_provider_mac.mm
@@ -13,7 +13,6 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
@@ -45,71 +44,73 @@
 };
 
 bool CoreWlanApi::GetAccessPointData(WifiData::AccessPointDataSet* data) {
-  base::mac::ScopedNSAutoreleasePool auto_pool;
-  // Initialize the scan parameters with scan key merging disabled, so we get
-  // every AP listed in the scan without any SSID de-duping logic.
-  NSDictionary* params = @{ kCWScanKeyMerge : @NO };
+  @autoreleasepool {  // Initialize the scan parameters with scan key merging
+                      // disabled, so we get
+    // every AP listed in the scan without any SSID de-duping logic.
+    NSDictionary* params = @{kCWScanKeyMerge : @NO};
 
-  NSSet* supported_interfaces = [CWInterface interfaceNames];
-  NSUInteger interface_error_count = 0;
-  for (NSString* interface_name in supported_interfaces) {
-    CWInterface* corewlan_interface =
-        [CWInterface interfaceWithName:interface_name];
-    if (!corewlan_interface) {
-      DLOG(WARNING) << interface_name << ": initWithName failed";
-      ++interface_error_count;
-      continue;
+    NSSet* supported_interfaces = [CWInterface interfaceNames];
+    NSUInteger interface_error_count = 0;
+    for (NSString* interface_name in supported_interfaces) {
+      CWInterface* corewlan_interface =
+          [CWInterface interfaceWithName:interface_name];
+      if (!corewlan_interface) {
+        DLOG(WARNING) << interface_name << ": initWithName failed";
+        ++interface_error_count;
+        continue;
+      }
+
+      const base::TimeTicks start_time = base::TimeTicks::Now();
+
+      NSError* err = nil;
+      NSArray* scan = [corewlan_interface scanForNetworksWithParameters:params
+                                                                  error:&err];
+      const int error_code = [err code];
+      const int count = [scan count];
+      // We could get an error code but count != 0 if the scan was interrupted,
+      // for example. For our purposes this is not fatal, so process as normal.
+      if (error_code && count == 0) {
+        DLOG(WARNING) << interface_name << ": CoreWLAN scan failed with error "
+                      << error_code;
+        ++interface_error_count;
+        continue;
+      }
+
+      const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
+
+      UMA_HISTOGRAM_CUSTOM_TIMES("Net.Wifi.ScanLatency", duration,
+                                 base::TimeDelta::FromMilliseconds(1),
+                                 base::TimeDelta::FromMinutes(1), 100);
+
+      DVLOG(1) << interface_name << ": found " << count << " wifi APs";
+
+      for (CWNetwork* network in scan) {
+        DCHECK(network);
+        AccessPointData access_point_data;
+        // -[CWNetwork bssid] uses colons to separate the components of the MAC
+        // address, but AccessPointData requires they be separated with a dash.
+        access_point_data.mac_address = base::SysNSStringToUTF16(
+            [[network bssid] stringByReplacingOccurrencesOfString:@":"
+                                                       withString:@"-"]);
+        access_point_data.radio_signal_strength = [network rssiValue];
+        access_point_data.channel = [[network wlanChannel] channelNumber];
+        access_point_data.signal_to_noise =
+            access_point_data.radio_signal_strength -
+            [network noiseMeasurement];
+        access_point_data.ssid = base::SysNSStringToUTF16([network ssid]);
+        data->insert(access_point_data);
+      }
     }
 
-    const base::TimeTicks start_time = base::TimeTicks::Now();
+    UMA_HISTOGRAM_CUSTOM_COUNTS(
+        "Net.Wifi.InterfaceCount",
+        [supported_interfaces count] - interface_error_count, 1, 5, 6);
 
-    NSError* err = nil;
-    NSArray* scan =
-        [corewlan_interface scanForNetworksWithParameters:params error:&err];
-    const int error_code = [err code];
-    const int count = [scan count];
-    // We could get an error code but count != 0 if the scan was interrupted,
-    // for example. For our purposes this is not fatal, so process as normal.
-    if (error_code && count == 0) {
-      DLOG(WARNING) << interface_name << ": CoreWLAN scan failed with error "
-                    << error_code;
-      ++interface_error_count;
-      continue;
-    }
-
-    const base::TimeDelta duration = base::TimeTicks::Now() - start_time;
-
-    UMA_HISTOGRAM_CUSTOM_TIMES("Net.Wifi.ScanLatency", duration,
-                               base::TimeDelta::FromMilliseconds(1),
-                               base::TimeDelta::FromMinutes(1), 100);
-
-    DVLOG(1) << interface_name << ": found " << count << " wifi APs";
-
-    for (CWNetwork* network in scan) {
-      DCHECK(network);
-      AccessPointData access_point_data;
-      // -[CWNetwork bssid] uses colons to separate the components of the MAC
-      // address, but AccessPointData requires they be separated with a dash.
-      access_point_data.mac_address = base::SysNSStringToUTF16([[network bssid]
-          stringByReplacingOccurrencesOfString:@":"
-                                    withString:@"-"]);
-      access_point_data.radio_signal_strength = [network rssiValue];
-      access_point_data.channel = [[network wlanChannel] channelNumber];
-      access_point_data.signal_to_noise =
-          access_point_data.radio_signal_strength - [network noiseMeasurement];
-      access_point_data.ssid = base::SysNSStringToUTF16([network ssid]);
-      data->insert(access_point_data);
-    }
+    // Return true even if some interfaces failed to scan, so long as at least
+    // one interface did not fail.
+    return interface_error_count == 0 ||
+           [supported_interfaces count] > interface_error_count;
   }
-
-  UMA_HISTOGRAM_CUSTOM_COUNTS(
-      "Net.Wifi.InterfaceCount",
-      [supported_interfaces count] - interface_error_count, 1, 5, 6);
-
-  // Return true even if some interfaces failed to scan, so long as at least
-  // one interface did not fail.
-  return interface_error_count == 0 ||
-         [supported_interfaces count] > interface_error_count;
 }
 
 // The time periods, in milliseconds, between successive polls of the wifi data.
diff --git a/services/service_manager/sandbox/mac/sandbox_mac.mm b/services/service_manager/sandbox/mac/sandbox_mac.mm
index 536038f..4a2bb0a 100644
--- a/services/service_manager/sandbox/mac/sandbox_mac.mm
+++ b/services/service_manager/sandbox/mac/sandbox_mac.mm
@@ -26,7 +26,6 @@
 #include "base/mac/mac_util.h"
 #include "base/mac/mach_port_rendezvous.h"
 #include "base/mac/scoped_cftyperef.h"
-#include "base/mac/scoped_nsautorelease_pool.h"
 #include "base/mac/scoped_nsobject.h"
 #include "base/rand_util.h"
 #include "base/stl_util.h"
@@ -83,56 +82,57 @@
 void SandboxMac::Warmup(SandboxType sandbox_type) {
   DCHECK_EQ(sandbox_type, SANDBOX_TYPE_GPU);
 
-  base::mac::ScopedNSAutoreleasePool scoped_pool;
+  @autoreleasepool {
+    {  // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
+      base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
+          CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
 
-  {  // CGColorSpaceCreateWithName(), CGBitmapContextCreate() - 10.5.6
-    base::ScopedCFTypeRef<CGColorSpaceRef> rgb_colorspace(
-        CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB));
+      // Allocate a 1x1 image.
+      char data[4];
+      base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
+          data, 1, 1, 8, 1 * 4, rgb_colorspace,
+          kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
 
-    // Allocate a 1x1 image.
-    char data[4];
-    base::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate(
-        data, 1, 1, 8, 1 * 4, rgb_colorspace,
-        kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
+      // Load in the color profiles we'll need (as a side effect).
+      ignore_result(base::mac::GetSRGBColorSpace());
+      ignore_result(base::mac::GetSystemColorSpace());
 
-    // Load in the color profiles we'll need (as a side effect).
-    ignore_result(base::mac::GetSRGBColorSpace());
-    ignore_result(base::mac::GetSystemColorSpace());
+      // CGColorSpaceCreateSystemDefaultCMYK - 10.6
+      base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
+          CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
+    }
 
-    // CGColorSpaceCreateSystemDefaultCMYK - 10.6
-    base::ScopedCFTypeRef<CGColorSpaceRef> cmyk_colorspace(
-        CGColorSpaceCreateWithName(kCGColorSpaceGenericCMYK));
-  }
+    {  // localtime() - 10.5.6
+      time_t tv = {0};
+      localtime(&tv);
+    }
 
-  {  // localtime() - 10.5.6
-    time_t tv = {0};
-    localtime(&tv);
-  }
+    {  // Gestalt() tries to read
+       // /System/Library/CoreServices/SystemVersion.plist
+      // on 10.5.6
+      int32_t tmp;
+      base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp);
+    }
 
-  {  // Gestalt() tries to read /System/Library/CoreServices/SystemVersion.plist
-    // on 10.5.6
-    int32_t tmp;
-    base::SysInfo::OperatingSystemVersionNumbers(&tmp, &tmp, &tmp);
-  }
+    {  // CGImageSourceGetStatus() - 10.6
+       // Create a png with just enough data to get everything warmed up...
+      char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
+      NSData* data = [NSData dataWithBytes:png_header
+                                    length:base::size(png_header)];
+      base::ScopedCFTypeRef<CGImageSourceRef> img(
+          CGImageSourceCreateWithData((CFDataRef)data, NULL));
+      CGImageSourceGetStatus(img);
+    }
 
-  {  // CGImageSourceGetStatus() - 10.6
-     // Create a png with just enough data to get everything warmed up...
-    char png_header[] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
-    NSData* data = [NSData dataWithBytes:png_header
-                                  length:base::size(png_header)];
-    base::ScopedCFTypeRef<CGImageSourceRef> img(
-        CGImageSourceCreateWithData((CFDataRef)data, NULL));
-    CGImageSourceGetStatus(img);
-  }
+    {
+      // Allow access to /dev/urandom.
+      base::GetUrandomFD();
+    }
 
-  {
-    // Allow access to /dev/urandom.
-    base::GetUrandomFD();
-  }
-
-  {  // IOSurfaceLookup() - 10.7
-    // Needed by zero-copy texture update framework - crbug.com/323338
-    base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceLookup(0));
+    {  // IOSurfaceLookup() - 10.7
+      // Needed by zero-copy texture update framework - crbug.com/323338
+      base::ScopedCFTypeRef<IOSurfaceRef> io_surface(IOSurfaceLookup(0));
+    }
   }
 }
 
diff --git a/services/tracing/public/cpp/trace_event_args_whitelist.cc b/services/tracing/public/cpp/trace_event_args_whitelist.cc
index 3892869..87e14ea3 100644
--- a/services/tracing/public/cpp/trace_event_args_whitelist.cc
+++ b/services/tracing/public/cpp/trace_event_args_whitelist.cc
@@ -26,7 +26,8 @@
 
 const char* const kScopedBlockingCallAllowedArgs[] = {"file_name",
                                                       "function_name", nullptr};
-const char* const kFallbackFontAllowedArgs[] = {"font_name", nullptr};
+const char* const kFallbackFontAllowedArgs[] = {"font_name",
+                                                "primary_font_name", nullptr};
 const char* const kGetFallbackFontsAllowedArgs[] = {"script", nullptr};
 const char* const kGPUAllowedArgs[] = {nullptr};
 const char* const kInputLatencyAllowedArgs[] = {"data", nullptr};
@@ -57,6 +58,7 @@
     {"browser", "KeyedServiceFactory::GetServiceForContext", nullptr},
     {"fonts", "CachedFontLinkSettings::GetLinkedFonts", nullptr},
     {"fonts", "QueryLinkedFontsFromRegistry", nullptr},
+    {"fonts", "RenderTextHarfBuzz::ItemizeTextToRuns::Runs", nullptr},
     {"GPU", "*", kGPUAllowedArgs},
     {"ipc", "GpuChannelHost::Send", nullptr},
     {"ipc", "SyncChannel::Send", nullptr},
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index b56c5243f..00f270b 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -412,6 +412,3963 @@
       }
     ]
   },
+  "android-marshmallow-x86-fyi-rel": {
+    "additional_compile_targets": [
+      "monochrome_static_initializers"
+    ],
+    "gtest_tests": [
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "android_browsertests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "android_webview_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "android_webview_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "angle_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "angle_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "base_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "base_util_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "base_util_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_common_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blink_common_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_heap_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blink_heap_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "blink_platform_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "blink_platform_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webkit_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "webkit_unit_tests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 2
+        },
+        "test": "blink_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "boringssl_crypto_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "boringssl_crypto_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "boringssl_ssl_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "boringssl_ssl_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "breakpad_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "breakpad_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cacheinvalidation_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "cacheinvalidation_unittests"
+      },
+      {
+        "args": [
+          "--gtest_filter=-*UsingRealWebcam*",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "capture_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "capture_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cast_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "cast_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "cc_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "cc_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "chrome_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "chrome_public_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 20
+        },
+        "test": "chrome_public_test_apk"
+      },
+      {
+        "args": [
+          "--disable-features=NetworkServiceInProcess",
+          "--gtest_filter=-org.chromium.chrome.browser.FeaturesAnnotationsTest#testFeaturesSetExistingFlags",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "network_service_out_of_process_chrome_public_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "network_service_out_of_process_chrome_public_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 20
+        },
+        "test": "chrome_public_test_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "components_browsertests"
+      },
+      {
+        "args": [
+          "--disable-features=NetworkServiceInProcess",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "network_service_out_of_process_components_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "network_service_out_of_process_components_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "components_browsertests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "components_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 4
+        },
+        "test": "components_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 9
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--disable-features=NetworkServiceInProcess",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "network_service_out_of_process_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "network_service_out_of_process_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 5
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--disable-perfetto",
+          "--gtest_filter=TracingControllerTest.*:BackgroundTracingManagerBrowserTest.*",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "nonperfetto_content_browsertests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "nonperfetto_content_browsertests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "content_browsertests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_shell_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 3
+        },
+        "test": "content_shell_test_apk"
+      },
+      {
+        "args": [
+          "--disable-features=NetworkServiceInProcess",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "network_service_out_of_process_content_shell_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "network_service_out_of_process_content_shell_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 5
+        },
+        "test": "content_shell_test_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "content_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 3
+        },
+        "test": "content_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "crypto_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "crypto_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "device_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "device_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "display_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "display_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "events_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "events_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gcm_unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gcm_unit_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gfx_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gfx_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gin_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gin_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gl_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gl_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gl_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gl_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "google_apis_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "google_apis_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gpu_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gpu_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "gwp_asan_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "gwp_asan_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ipc_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "ipc_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "jingle_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "jingle_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "latency_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "latency_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "libjingle_xmpp_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "libjingle_xmpp_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "media_blink_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "media_blink_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "media_service_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "media_service_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "media_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "media_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "midi_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "midi_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "mojo_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "mojo_test_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "mojo_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "mojo_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "net_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 3
+        },
+        "test": "net_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "perfetto_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "perfetto_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "sandbox_linux_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "sandbox_linux_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "services_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "services_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "shell_dialogs_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "shell_dialogs_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "skia_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "skia_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "sql_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "sql_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "storage_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "storage_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_android_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "ui_android_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_base_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "ui_base_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "ui_touch_selection_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "ui_touch_selection_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "unit_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 10
+        },
+        "test": "unit_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "url_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "url_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "viz_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "viz_unittests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_instrumentation_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 7
+        },
+        "test": "webview_instrumentation_test_apk"
+      },
+      {
+        "args": [
+          "--enable-features=NetworkService,NetworkServiceInProcess",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/mojo.fyi.network_webview_instrumentation_test_apk.filter",
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "network_service_webview_instrumentation_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "name": "network_service_webview_instrumentation_test_apk",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ],
+          "shards": 20
+        },
+        "test": "webview_instrumentation_test_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "wtf_unittests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "output_links": [
+            {
+              "link": [
+                "https://luci-logdog.appspot.com/v/?s",
+                "=android%2Fswarming%2Flogcats%2F",
+                "${TASK_ID}%2F%2B%2Funified_logcats"
+              ],
+              "name": "shard #${SHARD_INDEX} logcats"
+            }
+          ]
+        },
+        "test": "wtf_unittests"
+      }
+    ],
+    "isolated_scripts": [
+      {
+        "args": [
+          "--gtest-benchmark-name=components_perftests",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "isolate_name": "components_perftests",
+        "merge": {
+          "args": [
+            "--smoke-test-mode"
+          ],
+          "script": "//tools/perf/process_perf_results.py"
+        },
+        "name": "components_perftests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "--platform=android",
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "isolate_name": "content_shell_crash_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "content_shell_crash_test",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "isolate_name": "monochrome_apk_checker",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "monochrome_apk_checker",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ]
+        }
+      },
+      {
+        "args": [
+          "--avd-name=20190903T160000Z_android_23_google_apis_x86",
+          "--emulator-home=../../.android"
+        ],
+        "isolate_name": "telemetry_perf_unittests",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "telemetry_perf_unittests",
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86",
+              "location": ".android",
+              "revision": "cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/emulator",
+              "location": "third_party/android_sdk/public",
+              "revision": "f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC"
+            },
+            {
+              "cipd_package": "chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86",
+              "location": "third_party/android_sdk/public",
+              "revision": "npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "cpu": "x86-64",
+              "device_os": null,
+              "device_type": null,
+              "os": "Ubuntu-16.04"
+            }
+          ],
+          "idempotent": false,
+          "shards": 12
+        }
+      }
+    ]
+  },
   "android-pie-x86-fyi-rel": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index ce40396..de07e4e2 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -1321,7 +1321,8 @@
         "args": [
           "--disable-field-trials",
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--test-launcher-filter-file=../../testing/buildbot/filters/android.webview_instrumentation_tests_oreo.filter"
         ],
         "merge": {
           "args": [
@@ -1705,7 +1706,8 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--gtest-filter=-org.chromium.android_webview.test.AwAutofillTest.testJavascriptNotTriggerSelectControlChangeNotification"
         ],
         "merge": {
           "args": [
@@ -1752,7 +1754,8 @@
         "args": [
           "--disable-field-trials",
           "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
+          "--recover-devices",
+          "--gtest-filter=-org.chromium.android_webview.test.AwAutofillTest.testJavascriptNotTriggerSelectControlChangeNotification"
         ],
         "merge": {
           "args": [
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 9f89c574..acb6316 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -6885,6 +6885,252 @@
       }
     ]
   },
+  "Android FYI SkiaRenderer GL (Nexus 5X)": {
+    "isolated_scripts": [
+      {
+        "args": [
+          "context_lost",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "context_lost_tests",
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      },
+      {
+        "args": [
+          "depth_capture",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "depth_capture_tests",
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      },
+      {
+        "args": [
+          "gpu_process",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "gpu_process_launch_tests",
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      },
+      {
+        "args": [
+          "hardware_accelerated_feature",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "hardware_accelerated_feature_tests",
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      },
+      {
+        "args": [
+          "maps",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--dont-restore-color-profile-after-test",
+          "--os-type",
+          "android",
+          "--build-revision",
+          "${got_revision}",
+          "--test-machine-name",
+          "${buildername}"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "maps_pixel_test",
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      },
+      {
+        "args": [
+          "screenshot_sync",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--dont-restore-color-profile-after-test"
+        ],
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "screenshot_sync_tests",
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false
+        }
+      },
+      {
+        "args": [
+          "pixel",
+          "--show-stdout",
+          "--browser=android-chromium",
+          "--passthrough",
+          "-v",
+          "--extra-browser-args=--enable-logging=stderr --js-flags=--expose-gc --enable-features=VizDisplayCompositor,UseSkiaRenderer",
+          "--dont-restore-color-profile-after-test",
+          "--os-type",
+          "android",
+          "--build-revision",
+          "${got_revision}",
+          "--test-machine-name",
+          "${buildername}"
+        ],
+        "experiment_percentage": 100,
+        "isolate_name": "telemetry_gpu_integration_test",
+        "merge": {
+          "args": [],
+          "script": "//testing/merge_scripts/standard_isolated_script_merge.py"
+        },
+        "name": "skia_renderer_pixel_skia_gold_test",
+        "precommit_args": [
+          "--review-patch-issue",
+          "${patch_issue}",
+          "--review-patch-set",
+          "${patch_set}",
+          "--buildbucket-build-id",
+          "${buildbucket_build_id}"
+        ],
+        "should_retry_with_patch": false,
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "containment_type": "AUTO",
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android",
+              "pool": "Chrome-GPU"
+            }
+          ],
+          "idempotent": false,
+          "service_account": "chrome-gpu-gold@chops-service-accounts.iam.gserviceaccount.com"
+        }
+      }
+    ]
+  },
   "Android FYI SkiaRenderer Vulkan (Pixel 2)": {
     "gtest_tests": [
       {
diff --git a/testing/buildbot/mixins.pyl b/testing/buildbot/mixins.pyl
index 304b3d7..125b099 100644
--- a/testing/buildbot/mixins.pyl
+++ b/testing/buildbot/mixins.pyl
@@ -472,6 +472,37 @@
       },
     },
   },
+  'marshmallow-x86-emulator': {
+    '$mixin_append': {
+      'args': [
+        '--avd-name=20190903T160000Z_android_23_google_apis_x86',
+        '--emulator-home=../../.android',
+      ],
+    },
+    'swarming': {
+      'cipd_packages': [
+        {
+          'cipd_package': 'chromium/third_party/android_sdk/public/avds/android-23/google_apis/x86',
+          'location': '.android',
+          'revision': 'cfoclSzHrFOS_AcwBL09RQ5PvJByQHu3MX6EbC5aWZIC',
+        },
+        {
+          'cipd_package': 'chromium/third_party/android_sdk/public/emulator',
+          'location': 'third_party/android_sdk/public',
+          'revision': 'f4WdgkPvDdVCE8zBWPzcSIj4N9WFhKp3CSKDWylXuLEC',
+        },
+        {
+          'cipd_package': 'chromium/third_party/android_sdk/public/system-images/android-23/google_apis/x86',
+          'location': 'third_party/android_sdk/public',
+          'revision': 'npuCAATVbhmywZwGhI3tMoECTrBBzzyJLpjAPXqtmYYC',
+        },
+      ],
+      'dimensions': {
+        'device_os': None,
+        'device_type': None,
+      },
+    },
+  },
   'marshmallow_generic': {
     'swarming': {
       'dimensions': {
diff --git a/testing/buildbot/test_suite_exceptions.pyl b/testing/buildbot/test_suite_exceptions.pyl
index ad25807..7cc7301 100644
--- a/testing/buildbot/test_suite_exceptions.pyl
+++ b/testing/buildbot/test_suite_exceptions.pyl
@@ -334,6 +334,7 @@
       'Lollipop Tablet Tester',
       'Marshmallow Tablet Tester',
       # chromium.android.fyi
+      'android-marshmallow-x86-fyi-rel',
       'android-pie-x86-fyi-rel',
       # chromium.clang
       'ToTAndroid',
@@ -1630,18 +1631,21 @@
   'vr_android_unittests': {
     'remove_from': [
       # chromium.android.fyi
+      'android-marshmallow-x86-fyi-rel',
       'android-pie-x86-fyi-rel',
     ],
   },
   'vr_common_unittests': {
     'remove_from': [
       # chromium.android.fyi
+      'android-marshmallow-x86-fyi-rel',
       'android-pie-x86-fyi-rel',
     ],
   },
   'vr_pixeltests': {
     'remove_from': [
       # chromium.android.fyi
+      'android-marshmallow-x86-fyi-rel',
       'android-pie-x86-fyi-rel',
       # chromium.fyi
       'VR Linux',
@@ -2049,6 +2053,28 @@
           '--test-launcher-filter-file=../../testing/buildbot/filters/android.webview_instrumentation_tests_oreo.filter',
         ],
       },
+      'Android WebView P (dbg)': {
+        #TODO(crbug.com/1001309): Remove test filters once bug is resolved.
+        'args': [
+          '--gtest-filter=-org.chromium.android_webview.test.AwAutofillTest.testJavascriptNotTriggerSelectControlChangeNotification',
+        ],
+      },
+    },
+  },
+  'webview_instrumentation_test_apk_no_field_trial': {
+    'modifications': {
+      'Android WebView O (dbg)': {
+        #TODO(crbug.com/997362): Remove test filters once bug is resolved.
+        'args': [
+          '--test-launcher-filter-file=../../testing/buildbot/filters/android.webview_instrumentation_tests_oreo.filter',
+        ],
+      },
+      'Android WebView P (dbg)': {
+        #TODO(crbug.com/1001309): Remove test filters once bug is resolved.
+        'args': [
+          '--gtest-filter=-org.chromium.android_webview.test.AwAutofillTest.testJavascriptNotTriggerSelectControlChangeNotification',
+        ],
+      },
     },
   },
   'xr_browser_tests': {
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 05abfcc5..9d8cf9f 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -671,6 +671,21 @@
           'isolated_scripts': 'chromium_webkit_isolated_scripts',
         }
       },
+      'android-marshmallow-x86-fyi-rel': {
+        'mixins': [
+          'marshmallow-x86-emulator',
+          'linux-xenial',
+          'x86-64',
+        ],
+        'os_type': 'android',
+        'additional_compile_targets': [
+          'monochrome_static_initializers',
+        ],
+        'test_suites': {
+          'gtest_tests': 'chromium_android_gtests',
+          'isolated_scripts': 'marshmallow_isolated_scripts',
+        }
+      },
       'android-pie-x86-fyi-rel': {
         'mixins': [
           'pie-x86-emulator',
@@ -2553,6 +2568,19 @@
           'gpu_telemetry_tests': 'gpu_fyi_android_webgl2_and_gold_telemetry_tests',
         },
       },
+      'Android FYI SkiaRenderer GL (Nexus 5X)': {
+        'os_type': 'android',
+        'browser_config': 'android-chromium',
+        'skip_merge_script': True,
+        'mixins': [
+          'gpu_pool',
+          'marshmallow',
+          'bullhead',
+       ],
+       'test_suites': {
+         'gpu_telemetry_tests': 'gpu_skia_renderer_non_vulkan_telemetry_tests',
+       }
+      },
       'Android FYI SkiaRenderer Vulkan (Pixel 2)': {
         'os_type': 'android',
         'browser_config': 'android-chromium',
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index e334565..f9310e48 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -348,9 +348,8 @@
       return_code = xvfb.run_executable(
           command, env=env, stdoutfile=output_paths.logs)
     else:
-      with open(output_paths.logs, 'w') as handle:
-        return_code = test_env.run_command_output_to_handle(
-            command, handle, env=env)
+      return_code = test_env.run_command_with_output(
+          command, env=env, stdoutfile=output_paths.logs)
     expected_results_filename = os.path.join(temp_dir, 'test-results.json')
     if os.path.exists(expected_results_filename):
       shutil.move(expected_results_filename, output_paths.test_results)
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 3d79cff..319d4ec5 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -3637,24 +3637,6 @@
             ]
         }
     ],
-    "NewExtensionUpdaterService": [
-        {
-            "platforms": [
-                "chromeos",
-                "linux",
-                "mac",
-                "windows"
-            ],
-            "experiments": [
-                {
-                    "name": "Enabled",
-                    "enable_features": [
-                        "NewExtensionUpdaterService"
-                    ]
-                }
-            ]
-        }
-    ],
     "NewPasswordFormParsing": [
         {
             "platforms": [
diff --git a/third_party/android_deps/BUILD.gn b/third_party/android_deps/BUILD.gn
index a6991b0d..0cad478 100644
--- a/third_party/android_deps/BUILD.gn
+++ b/third_party/android_deps/BUILD.gn
@@ -1344,6 +1344,7 @@
     ":androidx_annotation_annotation_java",
     ":androidx_core_core_java",
   ]
+  create_srcjar = false
 }
 
 # This is generated, do not edit. Update BuildConfigGenerator.groovy instead.
diff --git a/third_party/blink/public/mojom/web_feature/web_feature.mojom b/third_party/blink/public/mojom/web_feature/web_feature.mojom
index 1b42ef84..050f7a3 100644
--- a/third_party/blink/public/mojom/web_feature/web_feature.mojom
+++ b/third_party/blink/public/mojom/web_feature/web_feature.mojom
@@ -2409,6 +2409,7 @@
   kDeprecatedFileSystemWrite = 3026,
   kPointerLockUnadjustedMovement = 3027,
   kCreateObjectBlob = 3028,
+  kQuotaRead = 3029,
 
   // Add new features immediately above this line. Don't change assigned
   // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/blink/renderer/core/BUILD.gn b/third_party/blink/renderer/core/BUILD.gn
index 01ac855..69f85ef 100644
--- a/third_party/blink/renderer/core/BUILD.gn
+++ b/third_party/blink/renderer/core/BUILD.gn
@@ -313,6 +313,8 @@
     "testing/wait_for_event.h",
     "testing/worker_internals.cc",
     "testing/worker_internals.h",
+    "timing/internals_profiler.cc",
+    "timing/internals_profiler.h",
   ]
 
   # Compile the sources produced by these IDL file lists.
diff --git a/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc b/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc
index ccdd965..e2abd92d 100644
--- a/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc
+++ b/third_party/blink/renderer/core/accessibility/apply_dark_mode.cc
@@ -80,39 +80,60 @@
              : frame_settings.GetDarkModeBackgroundBrightnessThreshold();
 }
 
+// A static wrapper to hold DarkModeSettings after initial creation.
+//
+// These settings are fixed after the browser launches, so they only need to be
+// constructed once for the "enabled" state and once for the "disabled" state.
+// Caching these values instead of re-calculating them should improve page load
+// performance.
+const DarkModeSettings& GetCachedEnabledSettings(
+    const Settings& frame_settings) {
+  static const DarkModeSettings* settings =
+      new DarkModeSettings([](const Settings& frame_settings) {
+        DarkModeSettings settings;
+        settings.mode = GetMode(frame_settings);
+        settings.image_policy = GetImagePolicy(frame_settings);
+        settings.text_brightness_threshold =
+            GetTextBrightnessThreshold(frame_settings);
+        settings.background_brightness_threshold =
+            GetBackgroundBrightnessThreshold(frame_settings);
+
+        settings.grayscale = frame_settings.GetDarkModeGrayscale();
+        settings.contrast = frame_settings.GetDarkModeContrast();
+        settings.image_grayscale_percent =
+            frame_settings.GetDarkModeImageGrayscale();
+        return settings;
+      }(frame_settings));
+  return *settings;
+}
+
+// In theory it should be sufficient for the disabled settings to set mode to
+// kOff (or to just return the default struct) without also setting
+// image_policy. However, this causes images to be inverted unexpectedly in
+// some cases (such as when toggling between the site's light and dark theme
+// on arstechnica.com).
+//
+// TODO(gilmanmh): Investigate unexpected image inversion behavior when
+// image_policy not set to kFilterNone.
+const DarkModeSettings& GetCachedDisabledSettings() {
+  static const DarkModeSettings* settings = new DarkModeSettings([]() {
+    DarkModeSettings settings;
+    settings.mode = DarkMode::kOff;
+    settings.image_policy = DarkModeImagePolicy::kFilterNone;
+    return settings;
+  }());
+  return *settings;
+}
+
 }  // namespace
 
 DarkModeSettings BuildDarkModeSettings(const Settings& frame_settings,
                                        const LayoutView& root) {
-  DarkModeSettings dark_mode_settings;
-
-  if (!ShouldApplyDarkModeFilterToPage(frame_settings.GetDarkModePagePolicy(),
-                                       root)) {
-    // In theory it should be sufficient to set mode to
-    // kOff (or to just return the default struct) without also setting
-    // image_policy. However, this causes images to be inverted unexpectedly in
-    // some cases (such as when toggling between the site's light and dark theme
-    // on arstechnica.com).
-    //
-    // TODO(gilmanmh): Investigate unexpected image inversion behavior when
-    // image_policy not set to kFilterNone.
-    dark_mode_settings.mode = DarkMode::kOff;
-    dark_mode_settings.image_policy = DarkModeImagePolicy::kFilterNone;
-    return dark_mode_settings;
+  if (ShouldApplyDarkModeFilterToPage(frame_settings.GetDarkModePagePolicy(),
+                                      root)) {
+    return GetCachedEnabledSettings(frame_settings);
   }
-
-  dark_mode_settings.mode = GetMode(frame_settings);
-  dark_mode_settings.image_policy = GetImagePolicy(frame_settings);
-  dark_mode_settings.text_brightness_threshold =
-      GetTextBrightnessThreshold(frame_settings);
-  dark_mode_settings.background_brightness_threshold =
-      GetBackgroundBrightnessThreshold(frame_settings);
-
-  dark_mode_settings.grayscale = frame_settings.GetDarkModeGrayscale();
-  dark_mode_settings.contrast = frame_settings.GetDarkModeContrast();
-  dark_mode_settings.image_grayscale_percent =
-      frame_settings.GetDarkModeImageGrayscale();
-  return dark_mode_settings;
+  return GetCachedDisabledSettings();
 }
 
 bool ShouldApplyDarkModeFilterToPage(DarkModePagePolicy policy,
diff --git a/third_party/blink/renderer/core/animation/string_keyframe.cc b/third_party/blink/renderer/core/animation/string_keyframe.cc
index 6439f73..7d3be39 100644
--- a/third_party/blink/renderer/core/animation/string_keyframe.cc
+++ b/third_party/blink/renderer/core/animation/string_keyframe.cc
@@ -42,7 +42,7 @@
   // which can be used to correctly serialize it given longhands that are
   // present in this set.
   return MakeGarbageCollected<CSSKeyframeShorthandValue>(
-      property_value_set.ImmutableCopyIfNeeded());
+      property.PropertyID(), property_value_set.ImmutableCopyIfNeeded());
 }
 
 }  // namespace
diff --git a/third_party/blink/renderer/core/core_idl_files.gni b/third_party/blink/renderer/core/core_idl_files.gni
index 1411e9e..d595fe5 100644
--- a/third_party/blink/renderer/core/core_idl_files.gni
+++ b/third_party/blink/renderer/core/core_idl_files.gni
@@ -751,6 +751,7 @@
                     "fetch/testing/internals_fetch.idl",
                     "fetch/testing/worker_internals_fetch.idl",
                     "testing/origin_trials_test_partial.idl",
+                    "timing/internals_profiler.idl",
                   ],
                   "abspath")
 
diff --git a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc
index a5d33566..7311cb6 100644
--- a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc
+++ b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.cc
@@ -3,25 +3,44 @@
 // found in the LICENSE file.
 
 #include "third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h"
+#include "third_party/blink/renderer/core/style_property_shorthand.h"
 
 namespace blink {
 
+#if DCHECK_IS_ON()
+namespace {
+bool ShorthandMatches(CSSPropertyID expected_shorthand,
+                      CSSPropertyID longhand) {
+  Vector<StylePropertyShorthand, 4> shorthands;
+  getMatchingShorthandsForLonghand(longhand, &shorthands);
+  for (unsigned i = 0; i < shorthands.size(); ++i) {
+    if (shorthands.at(i).id() == expected_shorthand)
+      return true;
+  }
+
+  return false;
+}
+
+}  // namespace
+#endif
+
 CSSKeyframeShorthandValue::CSSKeyframeShorthandValue(
+    CSSPropertyID shorthand,
     ImmutableCSSPropertyValueSet* properties)
-    : CSSValue(kKeyframeShorthandClass), properties_(properties) {}
+    : CSSValue(kKeyframeShorthandClass),
+      shorthand_(shorthand),
+      properties_(properties) {}
 
 String CSSKeyframeShorthandValue::CustomCSSText() const {
-  // All property/value pairs belong to the same shorthand so we grab the id
-  // from the first one.
-  CSSPropertyID my_shorthand = properties_->PropertyAt(0).ShorthandID();
 #if DCHECK_IS_ON()
+  // Check that all property/value pairs belong to the same shorthand.
   for (unsigned i = 0; i < properties_->PropertyCount(); i++) {
-    DCHECK_EQ(my_shorthand, properties_->PropertyAt(i).ShorthandID())
+    DCHECK(ShorthandMatches(shorthand_, properties_->PropertyAt(i).Id()))
         << "These are not the longhands you're looking for.";
   }
 #endif
 
-  return properties_->GetPropertyValue(my_shorthand);
+  return properties_->GetPropertyValue(shorthand_);
 }
 
 void CSSKeyframeShorthandValue::TraceAfterDispatch(blink::Visitor* visitor) {
diff --git a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h
index bcc149c4..43f849f4 100644
--- a/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h
+++ b/third_party/blink/renderer/core/css/css_keyframe_shorthand_value.h
@@ -36,17 +36,24 @@
  public:
   // Assumes that all property/value pairs that are present in the input set are
   // longhands for the same shorthand property/value pair.
-  CSSKeyframeShorthandValue(ImmutableCSSPropertyValueSet*);
+  CSSKeyframeShorthandValue(CSSPropertyID shorthand,
+                            ImmutableCSSPropertyValueSet*);
 
   String CustomCSSText() const;
 
   bool Equals(const CSSKeyframeShorthandValue& other) const {
-    return properties_ == other.properties_;
+    return shorthand_ == other.shorthand_ && properties_ == other.properties_;
   }
 
   void TraceAfterDispatch(blink::Visitor*);
 
  private:
+  // The shorthand property that these longhands belonged to.
+  // Note that a single longhand property may belong to multiple shorthands
+  // (e.g., border-left-style belongs to border-style and border) so we keep
+  // this value instead of trying to calculate the common shorthand given the
+  // longhands.
+  CSSPropertyID shorthand_;
   Member<ImmutableCSSPropertyValueSet> properties_;
 };
 
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 5635284..f3aa307 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -255,6 +255,7 @@
 #include "third_party/blink/renderer/core/page/scrolling/scroll_state_callback.h"
 #include "third_party/blink/renderer/core/page/scrolling/scrolling_coordinator.h"
 #include "third_party/blink/renderer/core/page/scrolling/snap_coordinator.h"
+#include "third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h"
 #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h"
 #include "third_party/blink/renderer/core/page/spatial_navigation_controller.h"
 #include "third_party/blink/renderer/core/paint/compositing/paint_layer_compositor.h"
@@ -4535,10 +4536,33 @@
 }
 
 void Document::SetURL(const KURL& url) {
-  const KURL& new_url = url.IsEmpty() ? BlankURL() : url;
+  KURL new_url = url.IsEmpty() ? BlankURL() : url;
   if (new_url == url_)
     return;
 
+  // If text fragment identifiers are enabled, we strip the fragment directive
+  // from the URL fragment.
+  // E.g. "#id##targetText=a" --> "#id"
+  if (RuntimeEnabledFeatures::TextFragmentIdentifiersEnabled(this)) {
+    // Add a hash to the beginning of the fragment as it is part of parsing the
+    // ## fragment directive prefix but is not included in FragmentIdentifier().
+    String fragment = "#" + new_url.FragmentIdentifier();
+    wtf_size_t start_pos = fragment.Find(kFragmentDirectivePrefix);
+    if (start_pos != kNotFound) {
+      fragment_directive_ =
+          fragment.Substring(start_pos + kFragmentDirectivePrefixStringLength);
+      if (start_pos == 0) {
+        // Remove the URL fragment if it is entirely a fragment directive.
+        new_url.RemoveFragmentIdentifier();
+      } else {
+        // The stripped fragment starts at position 1 and has length start_pos-1
+        // due to the added hash.
+        String stripped_fragment = fragment.Substring(1, start_pos - 1);
+        new_url.SetFragmentIdentifier(stripped_fragment);
+      }
+    }
+  }
+
   url_ = new_url;
   access_entry_from_url_ = nullptr;
   UpdateBaseURL();
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index 1d6e272..18822db 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1603,6 +1603,8 @@
 
   bool NeedsLayoutTreeRebuild() const;
 
+  String GetFragmentDirective() const { return fragment_directive_; }
+
  protected:
   void ClearXMLVersion() { xml_version_ = String(); }
 
@@ -2109,6 +2111,8 @@
 
   HeapHashMap<WeakMember<Element>, Member<ExplicitlySetAttrElementMap>>
       element_explicitly_set_attr_element_map_;
+
+  String fragment_directive_;
 };
 
 extern template class CORE_EXTERN_TEMPLATE_EXPORT Supplement<Document>;
diff --git a/third_party/blink/renderer/core/events/pointer_event.cc b/third_party/blink/renderer/core/events/pointer_event.cc
index 7cd8a98..9a44299 100644
--- a/third_party/blink/renderer/core/events/pointer_event.cc
+++ b/third_party/blink/renderer/core/events/pointer_event.cc
@@ -12,8 +12,14 @@
 
 PointerEvent::PointerEvent(const AtomicString& type,
                            const PointerEventInit* initializer,
-                           base::TimeTicks platform_time_stamp)
-    : MouseEvent(type, initializer, platform_time_stamp),
+                           base::TimeTicks platform_time_stamp,
+                           MouseEvent::SyntheticEventType synthetic_event_type,
+                           WebMenuSourceType menu_source_type)
+    : MouseEvent(type,
+                 initializer,
+                 platform_time_stamp,
+                 synthetic_event_type,
+                 menu_source_type),
       pointer_id_(0),
       width_(0),
       height_(0),
@@ -56,6 +62,13 @@
 }
 
 bool PointerEvent::IsMouseEvent() const {
+  if (RuntimeEnabledFeatures::ClickPointerEventEnabled() &&
+      (type() == event_type_names::kClick ||
+       type() == event_type_names::kAuxclick ||
+       type() == event_type_names::kContextmenu)) {
+    return true;
+  }
+
   return false;
 }
 
@@ -129,6 +142,13 @@
   if (type().IsEmpty())
     return DispatchEventResult::kNotCanceled;  // Shouldn't happen.
 
+  if (RuntimeEnabledFeatures::ClickPointerEventEnabled() &&
+      type() == event_type_names::kClick) {
+    // The MouseEvent::DispatchEvent will take care of sending dblclick event if
+    // needed.
+    return MouseEvent::DispatchEvent(dispatcher);
+  }
+
   DCHECK(!target() || target() != relatedTarget());
 
   GetEventPath().AdjustForRelatedTarget(dispatcher.GetNode(), relatedTarget());
diff --git a/third_party/blink/renderer/core/events/pointer_event.h b/third_party/blink/renderer/core/events/pointer_event.h
index ec3a1adb..b8b7bfe 100644
--- a/third_party/blink/renderer/core/events/pointer_event.h
+++ b/third_party/blink/renderer/core/events/pointer_event.h
@@ -14,11 +14,16 @@
   DEFINE_WRAPPERTYPEINFO();
 
  public:
-  static PointerEvent* Create(const AtomicString& type,
-                              const PointerEventInit* initializer,
-                              base::TimeTicks platform_time_stamp) {
-    return MakeGarbageCollected<PointerEvent>(type, initializer,
-                                              platform_time_stamp);
+  static PointerEvent* Create(
+      const AtomicString& type,
+      const PointerEventInit* initializer,
+      base::TimeTicks platform_time_stamp,
+      MouseEvent::SyntheticEventType synthetic_event_type =
+          kRealOrIndistinguishable,
+      WebMenuSourceType menu_source_type = kMenuSourceNone) {
+    return MakeGarbageCollected<PointerEvent>(
+        type, initializer, platform_time_stamp, synthetic_event_type,
+        menu_source_type);
   }
   static PointerEvent* Create(const AtomicString& type,
                               const PointerEventInit* initializer) {
@@ -27,7 +32,9 @@
 
   PointerEvent(const AtomicString&,
                const PointerEventInit*,
-               base::TimeTicks platform_time_stamp);
+               base::TimeTicks platform_time_stamp,
+               MouseEvent::SyntheticEventType synthetic_event_type,
+               WebMenuSourceType menu_source_type);
 
   PointerId pointerId() const { return pointer_id_; }
   double width() const { return width_; }
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.cc b/third_party/blink/renderer/core/input/mouse_event_manager.cc
index 63c08fd..8771efbda 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.cc
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.cc
@@ -97,6 +97,45 @@
   }
 }
 
+void SetMouseEventAttributes(MouseEventInit* initializer,
+                             Node* target_node,
+                             const AtomicString& mouse_event_type,
+                             const WebMouseEvent& mouse_event,
+                             const String& canvas_region_id,
+                             const FloatPoint* last_position,
+                             EventTarget* related_target,
+                             int click_count) {
+  bool is_mouse_enter_or_leave =
+      mouse_event_type == event_type_names::kMouseenter ||
+      mouse_event_type == event_type_names::kMouseleave;
+
+  initializer->setBubbles(!is_mouse_enter_or_leave);
+  initializer->setCancelable(!is_mouse_enter_or_leave);
+  MouseEvent::SetCoordinatesFromWebPointerProperties(
+      mouse_event.FlattenTransform(), target_node->GetDocument().domWindow(),
+      initializer);
+  UpdateMouseMovementXY(mouse_event, last_position,
+                        target_node->GetDocument().domWindow(), initializer);
+  initializer->setButton(static_cast<int16_t>(mouse_event.button));
+  initializer->setButtons(
+      MouseEvent::WebInputEventModifiersToButtons(mouse_event.GetModifiers()));
+  initializer->setView(target_node->GetDocument().domWindow());
+  initializer->setComposed(true);
+  initializer->setDetail(click_count);
+  initializer->setRegion(canvas_region_id);
+  initializer->setRelatedTarget(related_target);
+  UIEventWithKeyState::SetFromWebInputEventModifiers(
+      initializer,
+      static_cast<WebInputEvent::Modifiers>(mouse_event.GetModifiers()));
+  initializer->setSourceCapabilities(
+      target_node->GetDocument().domWindow()
+          ? target_node->GetDocument()
+                .domWindow()
+                ->GetInputDeviceCapabilities()
+                ->FiresTouchEvents(mouse_event.FromTouch())
+          : nullptr);
+}
+
 // The amount of time to wait before sending a fake mouse event triggered
 // during a scroll.
 constexpr base::TimeDelta kFakeMouseMoveIntervalDuringScroll =
@@ -249,7 +288,20 @@
     const String& canvas_region_id,
     const FloatPoint* last_position,
     EventTarget* related_target,
-    bool check_for_listener) {
+    bool check_for_listener,
+    const PointerId& pointer_id,
+    const String& pointer_type) {
+  DCHECK(mouse_event_type == event_type_names::kMouseup ||
+         mouse_event_type == event_type_names::kMousedown ||
+         mouse_event_type == event_type_names::kMousemove ||
+         mouse_event_type == event_type_names::kMouseout ||
+         mouse_event_type == event_type_names::kMouseover ||
+         mouse_event_type == event_type_names::kMouseleave ||
+         mouse_event_type == event_type_names::kMouseenter ||
+         mouse_event_type == event_type_names::kContextmenu ||
+         mouse_event_type == event_type_names::kClick ||
+         mouse_event_type == event_type_names::kAuxclick);
+
   if (target && target->ToNode() &&
       (!check_for_listener || target->HasEventListeners(mouse_event_type))) {
     Node* target_node = target->ToNode();
@@ -257,47 +309,42 @@
     if (mouse_event_type == event_type_names::kMouseup ||
         mouse_event_type == event_type_names::kMousedown ||
         mouse_event_type == event_type_names::kClick ||
-        mouse_event_type == event_type_names::kAuxclick ||
-        mouse_event_type == event_type_names::kDblclick) {
+        mouse_event_type == event_type_names::kAuxclick) {
       click_count = click_count_;
     }
-    bool is_mouse_enter_or_leave =
-        mouse_event_type == event_type_names::kMouseenter ||
-        mouse_event_type == event_type_names::kMouseleave;
-    MouseEventInit* initializer = MouseEventInit::Create();
-    initializer->setBubbles(!is_mouse_enter_or_leave);
-    initializer->setCancelable(!is_mouse_enter_or_leave);
-    MouseEvent::SetCoordinatesFromWebPointerProperties(
-        mouse_event.FlattenTransform(), target_node->GetDocument().domWindow(),
-        initializer);
-    UpdateMouseMovementXY(mouse_event, last_position,
-                          target_node->GetDocument().domWindow(), initializer);
-    initializer->setButton(static_cast<int16_t>(mouse_event.button));
-    initializer->setButtons(MouseEvent::WebInputEventModifiersToButtons(
-        mouse_event.GetModifiers()));
-    initializer->setView(target_node->GetDocument().domWindow());
-    initializer->setComposed(true);
-    initializer->setDetail(click_count);
-    initializer->setRegion(canvas_region_id);
-    initializer->setRelatedTarget(related_target);
-    UIEventWithKeyState::SetFromWebInputEventModifiers(
-        initializer,
-        static_cast<WebInputEvent::Modifiers>(mouse_event.GetModifiers()));
-    initializer->setSourceCapabilities(
-        target_node->GetDocument().domWindow()
-            ? target_node->GetDocument()
-                  .domWindow()
-                  ->GetInputDeviceCapabilities()
-                  ->FiresTouchEvents(mouse_event.FromTouch())
-            : nullptr);
 
-    MouseEvent* event = MouseEvent::Create(
-        mouse_event_type, initializer, mouse_event.TimeStamp(),
-        mouse_event.FromTouch() ? MouseEvent::kFromTouch
-                                : MouseEvent::kRealOrIndistinguishable,
-        mouse_event.menu_source_type);
+    DispatchEventResult dispatch_result;
 
-    DispatchEventResult dispatch_result = target->DispatchEvent(*event);
+    if (RuntimeEnabledFeatures::ClickPointerEventEnabled() &&
+        (mouse_event_type == event_type_names::kContextmenu ||
+         mouse_event_type == event_type_names::kClick ||
+         mouse_event_type == event_type_names::kAuxclick)) {
+      PointerEventInit* initializer = PointerEventInit::Create();
+      SetMouseEventAttributes(initializer, target_node, mouse_event_type,
+                              mouse_event, canvas_region_id, last_position,
+                              related_target, click_count);
+      initializer->setPointerId(pointer_id);
+      initializer->setPointerType(pointer_type);
+      PointerEvent* event = PointerEvent::Create(
+          mouse_event_type, initializer, mouse_event.TimeStamp(),
+          mouse_event.FromTouch() ? MouseEvent::kFromTouch
+                                  : MouseEvent::kRealOrIndistinguishable,
+          mouse_event.menu_source_type);
+      dispatch_result = target->DispatchEvent(*event);
+    } else {
+      MouseEventInit* initializer = MouseEventInit::Create();
+      SetMouseEventAttributes(initializer, target_node, mouse_event_type,
+                              mouse_event, canvas_region_id, last_position,
+                              related_target, click_count);
+      MouseEvent* event = MouseEvent::Create(
+          mouse_event_type, initializer, mouse_event.TimeStamp(),
+          mouse_event.FromTouch() ? MouseEvent::kFromTouch
+                                  : MouseEvent::kRealOrIndistinguishable,
+          mouse_event.menu_source_type);
+
+      dispatch_result = target->DispatchEvent(*event);
+    }
+
     return event_handling_util::ToWebInputEventResult(dispatch_result);
   }
   return WebInputEventResult::kNotHandled;
@@ -316,7 +363,9 @@
 WebInputEventResult MouseEventManager::DispatchMouseClickIfNeeded(
     Element* mouse_release_target,
     const WebMouseEvent& mouse_event,
-    const String& canvas_region_id) {
+    const String& canvas_region_id,
+    const PointerId& pointer_id,
+    const String& pointer_type) {
   // We only prevent click event when the click may cause contextmenu to popup.
   // However, we always send auxclick.
   bool context_menu_event = false;
@@ -383,7 +432,8 @@
         (mouse_event.button == WebPointerProperties::Button::kLeft)
             ? event_type_names::kClick
             : event_type_names::kAuxclick,
-        mouse_event, canvas_region_id, nullptr, nullptr);
+        mouse_event, canvas_region_id, nullptr, nullptr, false, pointer_id,
+        pointer_type);
   }
 
   return WebInputEventResult::kNotHandled;
diff --git a/third_party/blink/renderer/core/input/mouse_event_manager.h b/third_party/blink/renderer/core/input/mouse_event_manager.h
index 700a95c..feee8f95 100644
--- a/third_party/blink/renderer/core/input/mouse_event_manager.h
+++ b/third_party/blink/renderer/core/input/mouse_event_manager.h
@@ -50,7 +50,9 @@
                                          const String& canvas_region_id,
                                          const FloatPoint* last_position,
                                          EventTarget* related_target,
-                                         bool check_for_listener = false);
+                                         bool check_for_listener = false,
+                                         const PointerId& pointer_id = 0,
+                                         const String& pointer_type = "");
 
   WebInputEventResult SetMousePositionAndDispatchMouseEvent(
       Element* target_element,
@@ -61,7 +63,9 @@
   WebInputEventResult DispatchMouseClickIfNeeded(
       Element* mouse_release_target,
       const WebMouseEvent& mouse_event,
-      const String& canvas_region_id);
+      const String& canvas_region_id,
+      const PointerId& pointer_id,
+      const String& pointer_type);
 
   WebInputEventResult DispatchDragSrcEvent(const AtomicString& event_type,
                                            const WebMouseEvent&);
diff --git a/third_party/blink/renderer/core/input/pointer_event_manager.cc b/third_party/blink/renderer/core/input/pointer_event_manager.cc
index 1528667..6baf318 100644
--- a/third_party/blink/renderer/core/input/pointer_event_manager.cc
+++ b/third_party/blink/renderer/core/input/pointer_event_manager.cc
@@ -828,7 +828,8 @@
     if (!skip_click_dispatch && mouse_target &&
         event_type == WebInputEvent::kPointerUp) {
       mouse_event_manager_->DispatchMouseClickIfNeeded(
-          mouse_target, mouse_event, canvas_region_id);
+          mouse_target, mouse_event, canvas_region_id,
+          pointer_event->pointerId(), pointer_event->pointerType());
     }
   }
 
diff --git a/third_party/blink/renderer/core/inspector/browser_protocol.pdl b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
index f2d92ea..f661115 100644
--- a/third_party/blink/renderer/core/inspector/browser_protocol.pdl
+++ b/third_party/blink/renderer/core/inspector/browser_protocol.pdl
@@ -1730,7 +1730,7 @@
 
   # Returns node id at given location. Depending on whether DOM domain is enabled, nodeId is
   # either returned or not.
-  experimental command getNodeForLocation
+  command getNodeForLocation
     parameters
       # X coordinate.
       integer x
@@ -1738,9 +1738,13 @@
       integer y
       # False to skip to the nearest non-UA shadow root ancestor (default: false).
       optional boolean includeUserAgentShadowDOM
+      # Whether to ignore pointer-events: none on elements and hit test them.
+      optional boolean ignorePointerEventsNone
     returns
       # Resulting node.
       BackendNodeId backendNodeId
+      # Frame this node belongs to.
+      Page.FrameId frameId
       # Id of the node at given coordinates, only when enabled and requested document.
       optional NodeId nodeId
 
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
index d97813a..cf3169b 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.cc
@@ -1339,7 +1339,9 @@
     int x,
     int y,
     Maybe<bool> optional_include_user_agent_shadow_dom,
+    Maybe<bool> optional_ignore_pointer_events_none,
     int* backend_node_id,
+    String* frame_id,
     Maybe<int>* node_id) {
   bool include_user_agent_shadow_dom =
       optional_include_user_agent_shadow_dom.fromMaybe(false);
@@ -1347,8 +1349,12 @@
   PhysicalOffset document_point(
       LayoutUnit(x * inspected_frames_->Root()->PageZoomFactor()),
       LayoutUnit(y * inspected_frames_->Root()->PageZoomFactor()));
-  HitTestRequest request(HitTestRequest::kMove | HitTestRequest::kReadOnly |
-                         HitTestRequest::kAllowChildFrameContent);
+  HitTestRequest::HitTestRequestType hit_type =
+      HitTestRequest::kMove | HitTestRequest::kReadOnly |
+      HitTestRequest::kAllowChildFrameContent;
+  if (optional_ignore_pointer_events_none.fromMaybe(false))
+    hit_type |= HitTestRequest::kIgnorePointerEventsNone;
+  HitTestRequest request(hit_type);
   HitTestLocation location(document->View()->DocumentToFrame(document_point));
   HitTestResult result(request, location);
   document->GetFrame()->ContentLayoutObject()->HitTest(location, result);
@@ -1360,6 +1366,8 @@
   if (!node)
     return Response::Error("No node found at given location");
   *backend_node_id = IdentifiersFactory::IntIdForNode(node);
+  LocalFrame* frame = node->GetDocument().GetFrame();
+  *frame_id = IdentifiersFactory::FrameId(frame);
   if (enabled_.Get() && document_ &&
       document_node_to_id_map_->Contains(document_)) {
     *node_id = PushNodePathToFrontend(node);
diff --git a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
index bd9901a9..d3dbf93 100644
--- a/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
+++ b/third_party/blink/renderer/core/inspector/inspector_dom_agent.h
@@ -221,7 +221,9 @@
       int x,
       int y,
       protocol::Maybe<bool> include_user_agent_shadow_dom,
+      protocol::Maybe<bool> ignore_pointer_events_none,
       int* backend_node_id,
+      String* frame_id,
       protocol::Maybe<int>* node_id) override;
   protocol::Response getRelayoutBoundary(int node_id,
                                          int* out_node_id) override;
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.cc b/third_party/blink/renderer/core/layout/layout_replaced.cc
index ca21903..e2d48ad 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.cc
+++ b/third_party/blink/renderer/core/layout/layout_replaced.cc
@@ -161,7 +161,8 @@
 void LayoutReplaced::ComputeIntrinsicSizingInfoForReplacedContent(
     IntrinsicSizingInfo& intrinsic_sizing_info) const {
   if (ShouldApplySizeContainment()) {
-    intrinsic_sizing_info.size = FloatSize();
+    intrinsic_sizing_info.size =
+        FloatSize(ContentLogicalSizeForSizeContainment());
     return;
   }
   if (DisplayLockInducesSizeContainment()) {
diff --git a/third_party/blink/renderer/core/layout/layout_replaced.h b/third_party/blink/renderer/core/layout/layout_replaced.h
index e20d543..912479f 100644
--- a/third_party/blink/renderer/core/layout/layout_replaced.h
+++ b/third_party/blink/renderer/core/layout/layout_replaced.h
@@ -113,9 +113,8 @@
   void UpdateLayout() override;
 
   LayoutSize IntrinsicSize() const final {
-    // TODO(vmpstr): To address the intrinsic size of replaced element for
-    // display lock.
-    return ShouldApplySizeContainment() ? LayoutSize() : intrinsic_size_;
+    return ShouldApplySizeContainment() ? ContentLogicalSizeForSizeContainment()
+                                        : intrinsic_size_;
   }
 
   void ComputePositionedLogicalWidth(
diff --git a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
index 9385ed9..4e77033 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_block_layout_algorithm.cc
@@ -2137,8 +2137,7 @@
       return SoftBreak;
     }
   }
-  // TODO(mstensho): There are other break-inside values to consider here.
-  if (child.Style().BreakInside() != EBreakInside::kAvoid)
+  if (!IsAvoidBreakValue(ConstraintSpace(), child.Style().BreakInside()))
     return NoBreak;
 
   // The child broke, and we're not at the start of a fragmentainer, and we're
diff --git a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc
index c770506..18edf3f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_column_layout_algorithm_test.cc
@@ -798,6 +798,70 @@
   EXPECT_EQ(expectation, dump);
 }
 
+TEST_F(NGColumnLayoutAlgorithmTest, BreakInsideAvoidColumn) {
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      #parent {
+        columns: 3;
+        column-fill: auto;
+        column-gap: 10px;
+        width: 320px;
+        height: 100px;
+      }
+    </style>
+    <div id="container">
+      <div id="parent">
+        <div style="width:10px; height:50px;"></div>
+        <div style="break-inside:avoid-column; width:20px; height:70px;"></div>
+      </div>
+    </div>
+  )HTML");
+
+  String dump = DumpFragmentTree(GetElementById("container"));
+  String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
+  offset:unplaced size:1000x100
+    offset:0,0 size:320x100
+      offset:0,0 size:100x100
+        offset:0,0 size:10x50
+      offset:110,0 size:100x70
+        offset:0,0 size:20x70
+)DUMP";
+  EXPECT_EQ(expectation, dump);
+}
+
+TEST_F(NGColumnLayoutAlgorithmTest, BreakInsideAvoidPage) {
+  // break-inside:avoid-page has no effect, unless we're breaking into pages.
+  SetBodyInnerHTML(R"HTML(
+    <style>
+      #parent {
+        columns: 3;
+        column-fill: auto;
+        column-gap: 10px;
+        width: 320px;
+        height: 100px;
+      }
+    </style>
+    <div id="container">
+      <div id="parent">
+        <div style="width:10px; height:50px;"></div>
+        <div style="break-inside:avoid-page; width:20px; height:70px;"></div>
+      </div>
+    </div>
+  )HTML");
+
+  String dump = DumpFragmentTree(GetElementById("container"));
+  String expectation = R"DUMP(.:: LayoutNG Physical Fragment Tree ::.
+  offset:unplaced size:1000x100
+    offset:0,0 size:320x100
+      offset:0,0 size:100x100
+        offset:0,0 size:10x50
+        offset:0,50 size:20x50
+      offset:110,0 size:100x20
+        offset:0,0 size:20x20
+)DUMP";
+  EXPECT_EQ(expectation, dump);
+}
+
 TEST_F(NGColumnLayoutAlgorithmTest, BreakInsideAvoidTallBlock) {
   // The block that has break-inside:avoid is too tall to fit in one
   // fragmentainer. So a break is unavoidable. Let's check that:
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc
index 0b10af1..4fd21fd7 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.cc
@@ -64,6 +64,9 @@
                         EBreakBetween break_value) {
   if (break_value == EBreakBetween::kColumn)
     return constraint_space.BlockFragmentationType() == kFragmentColumn;
+  // TODO(mstensho): The innermost fragmentation type doesn't tell us everything
+  // here. We might want to force a break to the next page, even if we're in
+  // multicol (printing multicol, for instance).
   if (break_value == EBreakBetween::kLeft ||
       break_value == EBreakBetween::kPage ||
       break_value == EBreakBetween::kRecto ||
@@ -73,6 +76,20 @@
   return false;
 }
 
+bool IsAvoidBreakValue(const NGConstraintSpace& constraint_space,
+                       EBreakInside break_value) {
+  if (break_value == EBreakInside::kAvoid)
+    return constraint_space.HasBlockFragmentation();
+  if (break_value == EBreakInside::kAvoidColumn)
+    return constraint_space.BlockFragmentationType() == kFragmentColumn;
+  // TODO(mstensho): The innermost fragmentation type doesn't tell us everything
+  // here. We might want to avoid breaking to the next page, even if we're
+  // in multicol (printing multicol, for instance).
+  if (break_value == EBreakInside::kAvoidPage)
+    return constraint_space.BlockFragmentationType() == kFragmentPage;
+  return false;
+}
+
 void SetupFragmentation(const NGConstraintSpace& parent_space,
                         LayoutUnit new_bfc_block_offset,
                         NGConstraintSpaceBuilder* builder,
diff --git a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h
index 0d5a71d..432ed5f 100644
--- a/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h
+++ b/third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h
@@ -32,6 +32,10 @@
 // current fragmentation context.
 bool IsForcedBreakValue(const NGConstraintSpace&, EBreakBetween);
 
+// Return true if the specified break value means that we should avoid breaking,
+// given the current fragmentation context.
+bool IsAvoidBreakValue(const NGConstraintSpace&, EBreakInside);
+
 // Return true if we're resuming layout after a previous break.
 inline bool IsResumingLayout(const NGBlockBreakToken* token) {
   return token && !token->IsBreakBefore();
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
index ae6c9c58..daba5cc 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.cc
@@ -25,12 +25,8 @@
 
 namespace {
 
-constexpr char kFragmentDirectivePrefix[] = "##";
 constexpr char kTextFragmentIdentifierPrefix[] = "targetText=";
-
 // Subtract 1 because base::size includes the \0 string terminator.
-constexpr size_t kFragmentDirectivePrefixStringLength =
-    base::size(kFragmentDirectivePrefix) - 1;
 constexpr size_t kTextFragmentIdentifierPrefixStringLength =
     base::size(kTextFragmentIdentifierPrefix) - 1;
 
@@ -60,17 +56,6 @@
   return true;
 }
 
-// For fragment directive style text fragment anchors, we strip the directive
-// from the fragment string to avoid breaking pages that rely on the fragment.
-// E.g. "#id##targetText=a" --> "#id"
-String StripFragmentDirective(const String& fragment) {
-  size_t start_pos = fragment.Find(kFragmentDirectivePrefix);
-  if (start_pos == kNotFound)
-    return fragment;
-
-  return fragment.Substring(0, start_pos);
-}
-
 bool CheckSecurityRestrictions(LocalFrame& frame,
                                bool same_document_navigation) {
   // For security reasons, we only allow text fragments on the main frame of a
@@ -119,27 +104,15 @@
   if (!CheckSecurityRestrictions(frame, same_document_navigation))
     return nullptr;
 
+  if (!frame.GetDocument()->GetFragmentDirective())
+    return nullptr;
+
   Vector<TextFragmentSelector> selectors;
 
-  // Add the hash to the beginning of the fragment identifier as it is
-  // part of parsing the ##targetText= prefix but is not included by
-  // KURL::FragmentIdentifier().
-  String fragment = "#" + url.FragmentIdentifier();
-  size_t directive_pos = fragment.Find(kFragmentDirectivePrefix);
-  if (directive_pos == kNotFound)
+  if (!ParseTargetTextIdentifier(frame.GetDocument()->GetFragmentDirective(),
+                                 &selectors))
     return nullptr;
 
-  size_t start_pos = directive_pos + kFragmentDirectivePrefixStringLength;
-  if (!ParseTargetTextIdentifier(fragment.Substring(start_pos), &selectors))
-    return nullptr;
-
-  // Strip the fragment directive from the document URL so that the page cannot
-  // see the directive, to avoid breaking pages that rely on the fragment.
-  String stripped_fragment = StripFragmentDirective(fragment).Substring(1);
-  KURL stripped_url(url);
-  stripped_url.SetFragmentIdentifier(stripped_fragment);
-  frame.GetDocument()->SetURL(std::move(stripped_url));
-
   return MakeGarbageCollected<TextFragmentAnchor>(
       selectors, frame, TextFragmentFormat::FragmentDirective);
 }
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
index dd73ed00..20efaba0 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor.h
@@ -20,6 +20,11 @@
 class LocalFrame;
 class KURL;
 
+constexpr char kFragmentDirectivePrefix[] = "##";
+// Subtract 1 because base::size includes the \0 string terminator.
+constexpr size_t kFragmentDirectivePrefixStringLength =
+    base::size(kFragmentDirectivePrefix) - 1;
+
 enum class TextFragmentFormat { PlainFragment, FragmentDirective };
 
 class CORE_EXPORT TextFragmentAnchor final : public FragmentAnchor,
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc
index a378d56b..198cb4d 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_metrics_test.cc
@@ -273,13 +273,17 @@
   const int kUncountedOrNotFound = GetParam() ? kUncounted : kNotFound;
   const int kUncountedOrFound = GetParam() ? kUncounted : kFound;
 
+  // When the TextFragmentAnchors feature is on, we'll strip the fragment
+  // directive (i.e. anything after ##) leaving just the element anchor.
+  const int kFoundIfDirectiveStripped = GetParam() ? kFound : kNotFound;
+
   Vector<std::pair<String, int>> test_cases = {
       {"", kUncounted},
       {"#element", kFound},
       {"#doesntExist", kNotFound},
-      {"#element##foo", kNotFound},
+      {"#element##foo", kFoundIfDirectiveStripped},
       {"#doesntexist##foo", kNotFound},
-      {"##element", kNotFound},
+      {"##element", kUncountedOrNotFound},
       {"#element##targetText=doesntexist", kUncountedOrNotFound},
       {"#element##targetText=page", kUncountedOrNotFound},
       {"#targetText=doesntexist", kUncountedOrNotFound},
@@ -356,13 +360,18 @@
   const int kUncounted = 0;
   const int kCounted = 1;
 
+  // When the TextFragmentAnchors feature is on, the fragment directive is
+  // stripped and we won't count it as a double-hash use case. When it's
+  // off, we expect to count it.
+  const int kCountedOnlyIfDisabled = GetParam() ? kUncounted : kCounted;
+
   Vector<std::pair<String, int>> test_cases = {
       {"", kUncounted},
       {"#element", kUncounted},
       {"#doesntExist", kUncounted},
-      {"#element##foo", kCounted},
-      {"#doesntexist##foo", kCounted},
-      {"##element", kCounted},
+      {"#element##foo", kCountedOnlyIfDisabled},
+      {"#doesntexist##foo", kCountedOnlyIfDisabled},
+      {"##element", kCountedOnlyIfDisabled},
       {"#element#", kCounted},
       {"#foo#bar#", kCounted},
       {"#foo%23", kUncounted},
diff --git a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
index 475a005..9c134b0 100644
--- a/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
+++ b/third_party/blink/renderer/core/page/scrolling/text_fragment_anchor_test.cc
@@ -1143,7 +1143,7 @@
 
   EXPECT_EQ(1u, GetDocument().Markers().Markers().size());
 
-  EXPECT_EQ(GetDocument().Url(), "https://example.com/test.html#");
+  EXPECT_EQ(GetDocument().Url(), "https://example.com/test.html");
 }
 
 // Test that the ##targetText fragment directive is scrolled into view and is
@@ -1183,8 +1183,8 @@
       << LayoutViewport()->GetScrollOffset().ToString();
 }
 
-// If the fragment has a double hash, but the double hash isn't followed by a
-// valid targetText syntax, it should be interpreted as an element ID.
+// A double-hash should be interpreted as a fragment directive and should be
+// stripped from the URL, even if it is not a targetText.
 TEST_F(TextFragmentAnchorTest, IdFragmentWithDoubleHash) {
   SimRequest request("https://example.com/test.html#element##id", "text/html");
   LoadURL("https://example.com/test.html#element##id");
@@ -1210,7 +1210,7 @@
 
   RunAsyncMatchingTasks();
 
-  Element& div = *GetDocument().getElementById("element##id");
+  Element& div = *GetDocument().getElementById("element");
 
   EXPECT_TRUE(ViewportRect().Contains(BoundingRectInFrame(div)))
       << "Should have scrolled <div> into view but didn't, scroll offset: "
diff --git a/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc b/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
index e54e6d9..d55b63c 100644
--- a/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
+++ b/third_party/blink/renderer/core/svg/animation/svg_smil_element.cc
@@ -1132,22 +1132,8 @@
   base::Optional<SMILInterval> new_interval =
       CheckForNewRestartInterval(elapsed);
 
-  // This boolean is quite intricate. (Bug 379751)
-  //
-  // If we get a new interval we send all events
-  bool interval_did_start_or_end = (new_interval && *new_interval != interval_);
-
-  // This catches a tricky edge case where the interval began and ended
-  // on the same frame. So we check if the interval has passed, and
-  // that it's not the same interval as it was last time.
-  bool new_begin_time =
-      (!previous_interval_begin_.IsUnresolved() &&
-       interval_.begin != previous_interval_begin_ && interval_.end <= elapsed);
-
-  if (new_begin_time) {
-    previous_interval_begin_ = interval_.begin;
-    interval_did_start_or_end = true;
-  }
+  // Track "restarts" to handle active -> active transitions.
+  bool interval_restart = (new_interval && *new_interval != interval_);
 
   if (new_interval) {
     previous_interval_begin_ = interval_.begin;
@@ -1159,7 +1145,7 @@
   active_state_ = DetermineActiveState(elapsed);
 
   if (IsContributing(elapsed)) {
-    if (old_active_state == kInactive || interval_did_start_or_end) {
+    if (old_active_state == kInactive || interval_restart) {
       ScheduleEvent(event_type_names::kBeginEvent);
       StartedActiveInterval();
     }
@@ -1174,7 +1160,7 @@
   }
 
   if ((old_active_state == kActive && GetActiveState() != kActive) ||
-      interval_did_start_or_end) {
+      interval_restart) {
     ScheduleEvent(event_type_names::kEndEvent);
     EndedActiveInterval();
   }
diff --git a/third_party/blink/renderer/core/timing/internals_profiler.cc b/third_party/blink/renderer/core/timing/internals_profiler.cc
new file mode 100644
index 0000000..688de617
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/internals_profiler.cc
@@ -0,0 +1,16 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/core/timing/internals_profiler.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
+
+#include "v8/include/v8-profiler.h"
+
+namespace blink {
+
+void InternalsProfiler::collectSample(ScriptState* script_state, Internals&) {
+  v8::CpuProfiler::CollectSample(script_state->GetIsolate());
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/timing/internals_profiler.h b/third_party/blink/renderer/core/timing/internals_profiler.h
new file mode 100644
index 0000000..1dcaf63
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/internals_profiler.h
@@ -0,0 +1,24 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_INTERNALS_PROFILER_H_
+#define THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_INTERNALS_PROFILER_H_
+
+#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
+
+namespace blink {
+
+class Internals;
+class ScriptState;
+
+class InternalsProfiler {
+  STATIC_ONLY(InternalsProfiler);
+
+ public:
+  static void collectSample(ScriptState*, Internals&);
+};
+
+}  // namespace blink
+
+#endif  // THIRD_PARTY_BLINK_RENDERER_CORE_TIMING_INTERNALS_PROFILER_H_
diff --git a/third_party/blink/renderer/core/timing/internals_profiler.idl b/third_party/blink/renderer/core/timing/internals_profiler.idl
new file mode 100644
index 0000000..36e45565
--- /dev/null
+++ b/third_party/blink/renderer/core/timing/internals_profiler.idl
@@ -0,0 +1,11 @@
+// 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.
+
+[
+  ImplementedAs=InternalsProfiler
+]
+partial interface Internals {
+  // Collects a sample for all profilers attached to the invoker's isolate.
+  [CallWith=ScriptState] void collectSample();
+};
diff --git a/third_party/blink/renderer/devtools/front_end/elements/computedStyleSidebarPane.css b/third_party/blink/renderer/devtools/front_end/elements/computedStyleSidebarPane.css
index 21a0057e..259985e 100644
--- a/third_party/blink/renderer/devtools/front_end/elements/computedStyleSidebarPane.css
+++ b/third_party/blink/renderer/devtools/front_end/elements/computedStyleSidebarPane.css
@@ -32,3 +32,7 @@
 .styles-sidebar-pane-filter-box > input:not(:placeholder-shown) {
     box-shadow: var(--focus-ring-active-shadow);
 }
+
+.styles-sidebar-pane-filter-box > input::placeholder {
+    color: rgba(0, 0, 0, 0.54);
+}
diff --git a/third_party/blink/renderer/devtools/front_end/elements/stylesSidebarPane.css b/third_party/blink/renderer/devtools/front_end/elements/stylesSidebarPane.css
index d5be8fdc..eb881b48 100644
--- a/third_party/blink/renderer/devtools/front_end/elements/stylesSidebarPane.css
+++ b/third_party/blink/renderer/devtools/front_end/elements/stylesSidebarPane.css
@@ -188,6 +188,10 @@
     box-shadow: var(--focus-ring-active-shadow);
 }
 
+.styles-sidebar-pane-filter-box > input::placeholder {
+    color: rgba(0, 0, 0, 0.54);
+}
+
 .styles-section.styles-panel-hovered:not(.read-only) span.simple-selector:hover,
 .styles-section.styles-panel-hovered:not(.read-only) .media-text :hover{
     text-decoration: underline;
diff --git a/third_party/blink/renderer/devtools/front_end/inspector_main/RenderingOptions.js b/third_party/blink/renderer/devtools/front_end/inspector_main/RenderingOptions.js
index 42c7c8e0..f8253a4 100644
--- a/third_party/blink/renderer/devtools/front_end/inspector_main/RenderingOptions.js
+++ b/third_party/blink/renderer/devtools/front_end/inspector_main/RenderingOptions.js
@@ -63,13 +63,10 @@
         this.contentElement.createChild('div').classList.add('panel-section-separator');
 
         const mediaSetting = Common.moduleSetting('emulatedCSSMedia');
-        const mediaSelect = UI.SettingsUI.createControlForSetting(mediaSetting);
-        if (mediaSelect) {
-          const mediaRow = this.contentElement.createChild('span', 'media-row');
-          mediaRow.createChild('label').textContent = ls`Emulate CSS media`;
-          mediaRow.createChild('p').textContent = ls`Forces media type for testing print and screen styles.`;
-          mediaRow.appendChild(mediaSelect);
-        }
+        const selectSubtitle = ls`Forces media type for testing print and screen styles`;
+        const mediaSelect = UI.SettingsUI.createControlForSetting(mediaSetting, selectSubtitle);
+        if (mediaSelect)
+          this.contentElement.appendChild(mediaSelect);
   }
 
   /**
diff --git a/third_party/blink/renderer/devtools/front_end/inspector_main/inspector_main_strings.grdp b/third_party/blink/renderer/devtools/front_end/inspector_main/inspector_main_strings.grdp
index 6dd2a3f..5efd6b0b 100644
--- a/third_party/blink/renderer/devtools/front_end/inspector_main/inspector_main_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/inspector_main/inspector_main_strings.grdp
@@ -27,6 +27,9 @@
   <message name="IDS_DEVTOOLS_36bec95bf12fb795a5a100251f0cc421" desc="Checkbox subtitle for 'Layer boarders' in the Rendering tool">
     Shows layer borders (orange/olive) and tiles (cyan).
   </message>
+  <message name="IDS_DEVTOOLS_417316246f12b8235535182cc9eebe45" desc="Accessibility subtitle for media select element in Rendering tool">
+    Forces media type for testing print and screen styles
+  </message>
   <message name="IDS_DEVTOOLS_43f244627bf87f76a0dee86f364df473" desc="Checkbox subtitle for 'Layout Shift Regions' in the Rendering tool">
     Highlights areas of the page (blue) that were shifted. May not be suitable for people prone to photosensitive epilepsy.
   </message>
@@ -57,9 +60,6 @@
   <message name="IDS_DEVTOOLS_8634af2a16e41305fc8dca2d67360810" desc="Title of button in inspector main">
     Open dedicated DevTools for Node.js
   </message>
-  <message name="IDS_DEVTOOLS_869a8b1ed99306604574dca474a13994" desc="Text in Rendering Options">
-    Emulate CSS media
-  </message>
   <message name="IDS_DEVTOOLS_86f849e1a655c2df19f28cb3dfe07bc9" desc="Title of a setting under the Network category that can be invoked through the Command Menu">
     Block ads on this site
   </message>
@@ -81,15 +81,12 @@
   <message name="IDS_DEVTOOLS_a4c766a2e6eb33e7575331b6affd9778" desc="Checkbox subtitle for 'Paint flashing' in the Rendering tool">
     Highlights areas of the page (green) that need to be repainted. May not be suitable for people prone to photosensitive epilepsy.
   </message>
-  <message name="IDS_DEVTOOLS_a6e8f9aed2ac6481dc25a18a33342d03" desc="Title of the 'Rendering' tool in the bottom drawer">
+  <message name="IDS_DEVTOOLS_a6e8f9aed2ac6481dc25a18a33342d03" desc="Title of the Rendering tool in the bottom drawer">
     Rendering
   </message>
   <message name="IDS_DEVTOOLS_cb835af5f855f79e8611dd3f8fec6aac" desc="Title of an action in the inspector main tool to reload">
     Reload page
   </message>
-  <message name="IDS_DEVTOOLS_d013165b46f5c1f0f780ea3fc92c04f1" desc="Subtitle of 'Emulate CSS media' in the Rendering tool">
-    Forces media type for testing print and screen styles.
-  </message>
   <message name="IDS_DEVTOOLS_d543dbe64db0c952d6e13c9519218b3e" desc="Title of an action in the inspector main tool to hard reload">
     Hard reload page
   </message>
diff --git a/third_party/blink/renderer/devtools/front_end/inspector_main/renderingOptions.css b/third_party/blink/renderer/devtools/front_end/inspector_main/renderingOptions.css
index b478b44..22e748d 100644
--- a/third_party/blink/renderer/devtools/front_end/inspector_main/renderingOptions.css
+++ b/third_party/blink/renderer/devtools/front_end/inspector_main/renderingOptions.css
@@ -13,16 +13,6 @@
     flex: none;
 }
 
-.media-row {
-    margin-left: 22px;
-    flex: none;
-}
-
-.media-row p {
-    margin-top: 0;
-    color: gray;
-}
-
 .panel-section-separator {
     height: 1px;
     margin-bottom: 10px;
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/module.json b/third_party/blink/renderer/devtools/front_end/sdk/module.json
index 1fd3f4f..a6c1afc 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/module.json
+++ b/third_party/blink/renderer/devtools/front_end/sdk/module.json
@@ -264,7 +264,8 @@
                     "value": "screen"
                 }
             ],
-            "tags": "query"
+            "tags": "query",
+            "title": "Emulate CSS media"
         },
         {
             "type": "setting",
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp b/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp
index 2a703ce..ed3b138 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/sdk/sdk_strings.grdp
@@ -144,6 +144,9 @@
   <message name="IDS_DEVTOOLS_83f2229658949472d34f78e19475fcdd" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Show layer borders
   </message>
+  <message name="IDS_DEVTOOLS_869a8b1ed99306604574dca474a13994" desc="Title for emulatedCSSMedia setting">
+    Emulate CSS media
+  </message>
   <message name="IDS_DEVTOOLS_9028784c589c0c809700c7fbc66a5d96" desc="Text in Network Manager">
     Resource interpreted as <ph name="NETWORKREQUEST_RESOURCETYPE___TITLE__">$1s<ex>application</ex></ph> but transferred with MIME type <ph name="NETWORKREQUEST_MIMETYPE">$2s<ex>image</ex></ph>: &quot;<ph name="NETWORKREQUEST_URL__">$3s<ex>https://example.com</ex></ph>&quot;.
   </message>
diff --git a/third_party/blink/renderer/devtools/front_end/ui/SettingsUI.js b/third_party/blink/renderer/devtools/front_end/ui/SettingsUI.js
index fe24f07..5676b8d 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/SettingsUI.js
+++ b/third_party/blink/renderer/devtools/front_end/ui/SettingsUI.js
@@ -57,13 +57,18 @@
  * @param {string} name
  * @param {!Array<!{text: string, value: *, raw: (boolean|undefined)}>} options
  * @param {!Common.Setting} setting
+ * @param {string=} subtitle
  * @return {!Element}
  */
-UI.SettingsUI.createSettingSelect = function(name, options, setting) {
-  const p = createElement('p');
-  const label = p.createChild('label');
+UI.SettingsUI.createSettingSelect = function(name, options, setting, subtitle) {
+  const settingSelectElement = createElement('p');
+  const label = settingSelectElement.createChild('label');
+  const select = settingSelectElement.createChild('select', 'chrome-select');
   label.textContent = name;
-  const select = p.createChild('select', 'chrome-select');
+  if (subtitle) {
+    settingSelectElement.classList.add('chrome-select-label');
+    label.createChild('p').textContent = subtitle;
+  }
   UI.ARIAUtils.bindLabelToControl(label, select);
 
   for (let i = 0; i < options.length; ++i) {
@@ -76,7 +81,7 @@
   setting.addChangeListener(settingChanged);
   settingChanged();
   select.addEventListener('change', selectChanged, false);
-  return p;
+  return settingSelectElement;
 
   function settingChanged() {
     const newValue = setting.get();
@@ -128,9 +133,10 @@
 
 /**
  * @param {!Common.Setting} setting
+ * @param {string=} subtitle
  * @return {?Element}
  */
-UI.SettingsUI.createControlForSetting = function(setting) {
+UI.SettingsUI.createControlForSetting = function(setting, subtitle) {
   if (!setting.extension())
     return null;
   const descriptor = setting.extension().descriptor();
@@ -140,7 +146,7 @@
       return UI.SettingsUI.createSettingCheckbox(uiTitle, setting);
     case 'enum':
       if (Array.isArray(descriptor['options']))
-        return UI.SettingsUI.createSettingSelect(uiTitle, descriptor['options'], setting);
+        return UI.SettingsUI.createSettingSelect(uiTitle, descriptor['options'], setting, subtitle);
       console.error('Enum setting defined without options');
       return null;
     default:
diff --git a/third_party/blink/renderer/devtools/front_end/ui/inspectorCommon.css b/third_party/blink/renderer/devtools/front_end/ui/inspectorCommon.css
index bd83e0f6..a18ab33 100644
--- a/third_party/blink/renderer/devtools/front_end/ui/inspectorCommon.css
+++ b/third_party/blink/renderer/devtools/front_end/ui/inspectorCommon.css
@@ -269,6 +269,16 @@
     opacity: 0.38;
 }
 
+.chrome-select-label {
+  margin: 0px 22px;
+  flex: none;
+}
+
+.chrome-select-label p {
+  margin-top: 0;
+  color: gray;
+}
+
 .chrome-select optgroup,
 .chrome-select option {
     background-color: #EEEEEE;
@@ -457,4 +467,4 @@
     animation: ANIMATION-HACK 0s;
 }
 @keyframes ANIMATION-HACK {
-}
\ No newline at end of file
+}
diff --git a/third_party/blink/renderer/modules/quota/deprecated_storage_info.cc b/third_party/blink/renderer/modules/quota/deprecated_storage_info.cc
index c1ae9128..b02c623 100644
--- a/third_party/blink/renderer/modules/quota/deprecated_storage_info.cc
+++ b/third_party/blink/renderer/modules/quota/deprecated_storage_info.cc
@@ -33,8 +33,10 @@
 #include "base/location.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/modules/quota/deprecated_storage_quota.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 
 namespace blink {
 
@@ -45,6 +47,10 @@
     int storage_type,
     V8StorageUsageCallback* success_callback,
     V8StorageErrorCallback* error_callback) {
+  // The BlinkIDL definition for queryUsageAndQuota() already has a [Measure]
+  // attribute, so the kQuotaRead use counter must be explicitly updated.
+  UseCounter::Count(ExecutionContext::From(script_state),
+                    WebFeature::kQuotaRead);
   // Dispatching the request to DeprecatedStorageQuota, as this interface is
   // deprecated in favor of DeprecatedStorageQuota.
   DeprecatedStorageQuota* storage_quota = GetStorageQuota(storage_type);
@@ -64,6 +70,10 @@
     uint64_t new_quota_in_bytes,
     V8StorageQuotaCallback* success_callback,
     V8StorageErrorCallback* error_callback) {
+  // The BlinkIDL definition for requestQuota() already has a [Measure]
+  // attribute, so the kQuotaRead use counter must be explicitly updated.
+  UseCounter::Count(ExecutionContext::From(script_state),
+                    WebFeature::kQuotaRead);
   // Dispatching the request to DeprecatedStorageQuota, as this interface is
   // deprecated in favor of DeprecatedStorageQuota.
   DeprecatedStorageQuota* storage_quota = GetStorageQuota(storage_type);
diff --git a/third_party/blink/renderer/modules/quota/deprecated_storage_quota.cc b/third_party/blink/renderer/modules/quota/deprecated_storage_quota.cc
index c7bbd67..e86916ad 100644
--- a/third_party/blink/renderer/modules/quota/deprecated_storage_quota.cc
+++ b/third_party/blink/renderer/modules/quota/deprecated_storage_quota.cc
@@ -40,10 +40,12 @@
 #include "third_party/blink/renderer/bindings/modules/v8/v8_storage_usage_callback.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/modules/quota/dom_error.h"
 #include "third_party/blink/renderer/modules/quota/quota_utils.h"
 #include "third_party/blink/renderer/modules/quota/storage_estimate.h"
 #include "third_party/blink/renderer/platform/bindings/script_state.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/weborigin/kurl.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
@@ -131,6 +133,10 @@
   ExecutionContext* execution_context = ExecutionContext::From(script_state);
   DCHECK(execution_context);
 
+  // The BlinkIDL definition for queryUsageAndQuota() already has a [Measure]
+  // attribute, so the kQuotaRead use counter must be explicitly updated.
+  UseCounter::Count(execution_context, WebFeature::kQuotaRead);
+
   StorageType storage_type = GetStorageType(type_);
   if (storage_type != StorageType::kTemporary &&
       storage_type != StorageType::kPersistent) {
@@ -164,7 +170,10 @@
     uint64_t new_quota_in_bytes,
     V8StorageQuotaCallback* success_callback,
     V8StorageErrorCallback* error_callback) {
-  ExecutionContext& execution_context = *ExecutionContext::From(script_state);
+  ExecutionContext* execution_context = ExecutionContext::From(script_state);
+  // The BlinkIDL definition for requestQuota() already has a [Measure]
+  // attribute, so the kQuotaRead use counter must be explicitly updated.
+  UseCounter::Count(execution_context, WebFeature::kQuotaRead);
 
   StorageType storage_type = GetStorageType(type_);
   if (storage_type != StorageType::kTemporary &&
@@ -179,7 +188,7 @@
       WTF::Bind(&RequestStorageQuotaCallback, WrapPersistent(success_callback),
                 WrapPersistent(error_callback));
 
-  Document& document = To<Document>(execution_context);
+  Document& document = To<Document>(*execution_context);
   const SecurityOrigin* security_origin = document.GetSecurityOrigin();
   if (security_origin->IsOpaque()) {
     // Unique origins cannot store persistent state.
@@ -187,7 +196,7 @@
     return;
   }
 
-  GetQuotaHost(&execution_context)
+  GetQuotaHost(execution_context)
       ->RequestStorageQuota(
           WrapRefCounted(security_origin), storage_type, new_quota_in_bytes,
           mojo::WrapCallbackWithDefaultInvokeIfNotRun(
diff --git a/third_party/blink/renderer/modules/quota/storage_manager.cc b/third_party/blink/renderer/modules/quota/storage_manager.cc
index 7f487fb..c0ffed9 100644
--- a/third_party/blink/renderer/modules/quota/storage_manager.cc
+++ b/third_party/blink/renderer/modules/quota/storage_manager.cc
@@ -15,10 +15,12 @@
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
 #include "third_party/blink/renderer/core/frame/frame.h"
 #include "third_party/blink/renderer/core/frame/local_frame.h"
+#include "third_party/blink/renderer/core/frame/web_feature.h"
 #include "third_party/blink/renderer/modules/permissions/permission_utils.h"
 #include "third_party/blink/renderer/modules/quota/quota_utils.h"
 #include "third_party/blink/renderer/modules/quota/storage_estimate.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
 #include "third_party/blink/renderer/platform/wtf/assertions.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
 
@@ -132,6 +134,11 @@
   ScriptPromise promise = resolver->Promise();
   ExecutionContext* execution_context = ExecutionContext::From(script_state);
   DCHECK(execution_context->IsSecureContext());  // [SecureContext] in IDL
+
+  // The BlinkIDL definition for estimate() already has a [MeasureAs] attribute,
+  // so the kQuotaRead use counter must be explicitly updated.
+  UseCounter::Count(execution_context, WebFeature::kQuotaRead);
+
   const SecurityOrigin* security_origin =
       execution_context->GetSecurityOrigin();
   if (security_origin->IsOpaque()) {
diff --git a/third_party/blink/renderer/modules/scheduler/scheduler.cc b/third_party/blink/renderer/modules/scheduler/scheduler.cc
index 5c7398c..3a82fa8 100644
--- a/third_party/blink/renderer/modules/scheduler/scheduler.cc
+++ b/third_party/blink/renderer/modules/scheduler/scheduler.cc
@@ -68,7 +68,7 @@
 }
 
 TaskQueue* Scheduler::getTaskQueue(AtomicString priority) {
-  return GetTaskQueue(TaskQueue::WebSchedulingPriorityFromString(priority));
+  return GetTaskQueue(WebSchedulingPriorityFromString(priority));
 }
 
 TaskQueue* Scheduler::GetTaskQueue(WebSchedulingPriority priority) {
diff --git a/third_party/blink/renderer/modules/scheduler/task_queue.cc b/third_party/blink/renderer/modules/scheduler/task_queue.cc
index aa2f3d88..6e343f14f 100644
--- a/third_party/blink/renderer/modules/scheduler/task_queue.cc
+++ b/third_party/blink/renderer/modules/scheduler/task_queue.cc
@@ -24,35 +24,6 @@
 
 namespace blink {
 
-namespace {
-
-const AtomicString& ImmediatePriorityKeyword() {
-  DEFINE_STATIC_LOCAL(const AtomicString, immediate_priority, ("immediate"));
-  return immediate_priority;
-}
-
-const AtomicString& HighPriorityKeyword() {
-  DEFINE_STATIC_LOCAL(const AtomicString, high_priority, ("high"));
-  return high_priority;
-}
-
-const AtomicString& DefaultPriorityKeyword() {
-  DEFINE_STATIC_LOCAL(const AtomicString, default_priority, ("default"));
-  return default_priority;
-}
-
-const AtomicString& LowPriorityKeyword() {
-  DEFINE_STATIC_LOCAL(const AtomicString, low_priority, ("low"));
-  return low_priority;
-}
-
-const AtomicString& IdlePriorityKeyword() {
-  DEFINE_STATIC_LOCAL(const AtomicString, idle_priority, ("idle"));
-  return idle_priority;
-}
-
-}  // namespace
-
 TaskQueue::TaskQueue(Document* document,
                      WebSchedulingPriority priority,
                      Scheduler* scheduler)
@@ -109,42 +80,4 @@
   task->MoveTo(this);
 }
 
-// static
-AtomicString TaskQueue::WebSchedulingPriorityToString(
-    WebSchedulingPriority priority) {
-  switch (priority) {
-    case WebSchedulingPriority::kImmediatePriority:
-      return ImmediatePriorityKeyword();
-    case WebSchedulingPriority::kHighPriority:
-      return HighPriorityKeyword();
-    case WebSchedulingPriority::kDefaultPriority:
-      return DefaultPriorityKeyword();
-    case WebSchedulingPriority::kLowPriority:
-      return LowPriorityKeyword();
-    case WebSchedulingPriority::kIdlePriority:
-      return IdlePriorityKeyword();
-  }
-
-  NOTREACHED();
-  return g_empty_atom;
-}
-
-// static
-WebSchedulingPriority TaskQueue::WebSchedulingPriorityFromString(
-    AtomicString priority) {
-  if (priority == ImmediatePriorityKeyword())
-    return WebSchedulingPriority::kImmediatePriority;
-  if (priority == HighPriorityKeyword())
-    return WebSchedulingPriority::kHighPriority;
-  if (priority == DefaultPriorityKeyword())
-    return WebSchedulingPriority::kDefaultPriority;
-  if (priority == LowPriorityKeyword())
-    return WebSchedulingPriority::kLowPriority;
-  if (priority == IdlePriorityKeyword())
-    return WebSchedulingPriority::kIdlePriority;
-
-  NOTREACHED();
-  return WebSchedulingPriority::kDefaultPriority;
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/modules/scheduler/task_queue.h b/third_party/blink/renderer/modules/scheduler/task_queue.h
index 7d8ac56..0c7d70e 100644
--- a/third_party/blink/renderer/modules/scheduler/task_queue.h
+++ b/third_party/blink/renderer/modules/scheduler/task_queue.h
@@ -65,9 +65,6 @@
 
   void Trace(Visitor*) override;
 
-  static AtomicString WebSchedulingPriorityToString(WebSchedulingPriority);
-  static WebSchedulingPriority WebSchedulingPriorityFromString(AtomicString);
-
  private:
   void RunTaskCallback(Task*);
   void ScheduleTask(Task*, base::TimeDelta delay);
diff --git a/third_party/blink/renderer/modules/xr/xr_session.cc b/third_party/blink/renderer/modules/xr/xr_session.cc
index 54c599d1..8fa662c 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.cc
+++ b/third_party/blink/renderer/modules/xr/xr_session.cc
@@ -65,6 +65,8 @@
 
 const char kDeviceDisconnected[] = "The XR device has been disconnected.";
 
+const char kNotImplemented[] = "The operation has not been implemented yet.";
+
 const double kDegToRad = M_PI / 180.0;
 
 // Indices into the views array.
@@ -273,12 +275,14 @@
   SetXRDisplayInfo(std::move(display_info));
 }
 
-ScriptPromise XRSession::requestReferenceSpace(ScriptState* script_state,
-                                               const String& type) {
+ScriptPromise XRSession::requestReferenceSpace(
+    ScriptState* script_state,
+    const String& type,
+    ExceptionState& exception_state) {
   if (ended_) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state, MakeGarbageCollected<DOMException>(
-                          DOMExceptionCode::kInvalidStateError, kSessionEnded));
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      kSessionEnded);
+    return ScriptPromise();
   }
 
   XRReferenceSpace::Type requested_type =
@@ -289,10 +293,9 @@
 
   if (sensorless_session_ &&
       requested_type != XRReferenceSpace::Type::kTypeViewer) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state,
-        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotSupportedError,
-                                           kReferenceSpaceNotSupported));
+    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                      kReferenceSpaceNotSupported);
+    return ScriptPromise();
   }
 
   // If the session feature required by this reference space type is not
@@ -300,10 +303,9 @@
   auto type_as_feature = MapReferenceSpaceTypeToFeature(requested_type);
   if (!type_as_feature ||
       !enabled_features_.Contains(type_as_feature.value())) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state,
-        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotSupportedError,
-                                           kReferenceSpaceNotSupported));
+    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                      kReferenceSpaceNotSupported);
+    return ScriptPromise();
   }
 
   XRReferenceSpace* reference_space = nullptr;
@@ -341,10 +343,9 @@
   // If the above switch statement failed to assign to reference_space,
   // it's because the reference space wasn't supported by the device.
   if (!reference_space) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state,
-        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotSupportedError,
-                                           kReferenceSpaceNotSupported));
+    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                      kReferenceSpaceNotSupported);
+    return ScriptPromise();
   }
 
   DCHECK(reference_space);
@@ -362,13 +363,13 @@
 
 ScriptPromise XRSession::createAnchor(ScriptState* script_state,
                                       XRRigidTransform* initial_pose,
-                                      XRSpace* space) {
+                                      XRSpace* space,
+                                      ExceptionState& exception_state) {
   // TODO(https://crbug.com/992033): Implement anchor creation from a session
   // instead of rejecting the promise.
-  return ScriptPromise::RejectWithDOMException(
-      script_state,
-      MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotSupportedError,
-                                         kAnchorsNotSupported));
+  exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                    kAnchorsNotSupported);
+  return ScriptPromise();
 }
 
 int XRSession::requestAnimationFrame(V8XRFrameRequestCallback* callback) {
@@ -404,25 +405,24 @@
 
 ScriptPromise XRSession::requestHitTest(ScriptState* script_state,
                                         XRRay* ray,
-                                        XRSpace* space) {
+                                        XRSpace* space,
+                                        ExceptionState& exception_state) {
   if (ended_) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state, MakeGarbageCollected<DOMException>(
-                          DOMExceptionCode::kInvalidStateError, kSessionEnded));
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      kSessionEnded);
+    return ScriptPromise();
   }
 
   if (!space) {
-    return ScriptPromise::Reject(
-        script_state, V8ThrowException::CreateTypeError(
-                          script_state->GetIsolate(), kNoSpaceSpecified));
+    exception_state.ThrowTypeError(kNoSpaceSpecified);
+    return ScriptPromise();
   }
 
   // Reject the promise if device doesn't support the hit-test API.
   if (!xr_->xrEnvironmentProviderPtr()) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state,
-        MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotSupportedError,
-                                           kHitTestNotSupported));
+    exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError,
+                                      kHitTestNotSupported);
+    return ScriptPromise();
   }
 
   device::mojom::blink::XRRayPtr ray_mojo = device::mojom::blink::XRRay::New();
@@ -447,10 +447,11 @@
 }
 
 ScriptPromise XRSession::requestHitTestSource(ScriptState* script_state,
-                                              XRHitTestOptionsInit* options) {
-  return ScriptPromise::RejectWithDOMException(
-      script_state,
-      MakeGarbageCollected<DOMException>(DOMExceptionCode::kInvalidStateError));
+                                              XRHitTestOptionsInit* options,
+                                              ExceptionState& exception_state) {
+  exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                    kNotImplemented);
+  return ScriptPromise();
 }
 
 void XRSession::OnHitTestResults(
@@ -491,12 +492,13 @@
   }
 }
 
-ScriptPromise XRSession::end(ScriptState* script_state) {
+ScriptPromise XRSession::end(ScriptState* script_state,
+                             ExceptionState& exception_state) {
   // Don't allow a session to end twice.
   if (ended_) {
-    return ScriptPromise::RejectWithDOMException(
-        script_state, MakeGarbageCollected<DOMException>(
-                          DOMExceptionCode::kInvalidStateError, kSessionEnded));
+    exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+                                      kSessionEnded);
+    return ScriptPromise();
   }
 
   ForceEnd();
diff --git a/third_party/blink/renderer/modules/xr/xr_session.h b/third_party/blink/renderer/modules/xr/xr_session.h
index f7f1c9c..97ddda8 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.h
+++ b/third_party/blink/renderer/modules/xr/xr_session.h
@@ -98,11 +98,13 @@
       XRWorldTrackingStateInit* world_tracking_state_init,
       ExceptionState& exception_state);
   ScriptPromise requestReferenceSpace(ScriptState* script_state,
-                                      const String& type);
+                                      const String& type,
+                                      ExceptionState&);
 
   ScriptPromise createAnchor(ScriptState* script_state,
                              XRRigidTransform* initial_pose,
-                             XRSpace* space);
+                             XRSpace* space,
+                             ExceptionState&);
 
   int requestAnimationFrame(V8XRFrameRequestCallback* callback);
   void cancelAnimationFrame(int id);
@@ -110,14 +112,16 @@
   XRInputSourceArray* inputSources() const;
 
   ScriptPromise requestHitTestSource(ScriptState* script_state,
-                                     XRHitTestOptionsInit* options);
+                                     XRHitTestOptionsInit* options,
+                                     ExceptionState&);
 
   ScriptPromise requestHitTest(ScriptState* script_state,
                                XRRay* ray,
-                               XRSpace* space);
+                               XRSpace* space,
+                               ExceptionState&);
 
   // Called by JavaScript to manually end the session.
-  ScriptPromise end(ScriptState* script_state);
+  ScriptPromise end(ScriptState* script_state, ExceptionState&);
 
   bool ended() const { return ended_; }
 
diff --git a/third_party/blink/renderer/modules/xr/xr_session.idl b/third_party/blink/renderer/modules/xr/xr_session.idl
index 0266a59..1afffcb 100644
--- a/third_party/blink/renderer/modules/xr/xr_session.idl
+++ b/third_party/blink/renderer/modules/xr/xr_session.idl
@@ -42,21 +42,21 @@
 
   [RaisesException] void updateRenderState(optional XRRenderStateInit init);
 
-  [CallWith=ScriptState] Promise<XRReferenceSpace> requestReferenceSpace(XRReferenceSpaceType type);
+  [CallWith=ScriptState, RaisesException] Promise<XRReferenceSpace> requestReferenceSpace(XRReferenceSpaceType type);
 
   long requestAnimationFrame(XRFrameRequestCallback callback);
   void cancelAnimationFrame(long handle);
 
-  [RuntimeEnabled=WebXRHitTest, CallWith=ScriptState] Promise<FrozenArray<XRHitResult>> requestHitTest(XRRay ray, XRSpace space);
+  [RuntimeEnabled=WebXRHitTest, CallWith=ScriptState, RaisesException] Promise<FrozenArray<XRHitResult>> requestHitTest(XRRay ray, XRSpace space);
 
   // https://github.com/immersive-web/real-world-geometry/blob/master/plane-detection-explainer.md
   [RuntimeEnabled=WebXRPlaneDetection] readonly attribute XRWorldTrackingState worldTrackingState;
   [RuntimeEnabled=WebXRPlaneDetection, RaisesException] void updateWorldTrackingState(optional XRWorldTrackingStateInit state);
 
   [RuntimeEnabled=WebXRAnchors] readonly attribute XRAnchorSet trackedAnchors;
-  [RuntimeEnabled=WebXRAnchors, CallWith=ScriptState] Promise<XRAnchor> createAnchor(XRRigidTransform initial_pose, XRSpace space);
+  [RuntimeEnabled=WebXRAnchors, CallWith=ScriptState, RaisesException] Promise<XRAnchor> createAnchor(XRRigidTransform initial_pose, XRSpace space);
 
-  [CallWith=ScriptState, Measure] Promise<void> end();
+  [CallWith=ScriptState, Measure, RaisesException] Promise<void> end();
 
-  [RuntimeEnabled=WebXRHitTest, CallWith=ScriptState] Promise<XRHitTestSource> requestHitTestSource(XRHitTestOptionsInit options);
+  [RuntimeEnabled=WebXRHitTest, CallWith=ScriptState, RaisesException] Promise<XRHitTestSource> requestHitTestSource(XRHitTestOptionsInit options);
 };
diff --git a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
index d54fa99..d670a59 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc
@@ -557,9 +557,10 @@
     // to transferToImageBitmap are made back-to-back, or when the context gets
     // lost. We intentionally leave the transparent black image in legacy color
     // space.
-    sk_sp<SkSurface> surface =
-        SkSurface::MakeRasterN32Premul(size_.Width(), size_.Height());
-    return StaticBitmapImage::Create(surface->makeImageSnapshot());
+    SkBitmap black_bitmap;
+    black_bitmap.allocN32Pixels(size_.Width(), size_.Height());
+    black_bitmap.eraseARGB(0, 255, 255, 255);
+    return StaticBitmapImage::Create(SkImage::MakeFromBitmap(black_bitmap));
   }
 
   DCHECK_EQ(size_.Width(), transferable_resource.size.width());
diff --git a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
index 9f23d5d..ad10c54 100644
--- a/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
+++ b/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc
@@ -46,6 +46,11 @@
 void ImageLayerBridge::SetImage(scoped_refptr<StaticBitmapImage> image) {
   if (disposed_)
     return;
+  // There could be the case that the current SkImage (encapsulated in the image
+  // parameter of the function) is null, that means that something went wrong
+  // during the creation of the image and we should not try and setImage with it
+  if (image_ && !image_->PaintImageForCurrentFrame().GetSkImage())
+    return;
 
   image_ = std::move(image);
   if (image_) {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index b59f087..0b401a4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -268,6 +268,10 @@
       status: "experimental",
     },
     {
+      name: "ClickPointerEvent",
+      status: "experimental",
+    },
+    {
       name: "ClickRetargetting",
       status: "experimental",
     },
diff --git a/third_party/blink/renderer/platform/scheduler/BUILD.gn b/third_party/blink/renderer/platform/scheduler/BUILD.gn
index fb6e514..8385ba24 100644
--- a/third_party/blink/renderer/platform/scheduler/BUILD.gn
+++ b/third_party/blink/renderer/platform/scheduler/BUILD.gn
@@ -108,6 +108,7 @@
     "main_thread/user_model.cc",
     "main_thread/user_model.h",
     "main_thread/web_render_widget_scheduling_state.cc",
+    "main_thread/web_scheduling_priority.cc",
     "main_thread/web_scheduling_task_queue_impl.cc",
     "main_thread/web_scheduling_task_queue_impl.h",
     "main_thread/web_scoped_virtual_time_pauser.cc",
diff --git a/third_party/blink/renderer/platform/scheduler/main_thread/web_scheduling_priority.cc b/third_party/blink/renderer/platform/scheduler/main_thread/web_scheduling_priority.cc
new file mode 100644
index 0000000..c3c9844
--- /dev/null
+++ b/third_party/blink/renderer/platform/scheduler/main_thread/web_scheduling_priority.cc
@@ -0,0 +1,73 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h"
+
+namespace blink {
+
+namespace {
+
+const AtomicString& ImmediatePriorityKeyword() {
+  DEFINE_STATIC_LOCAL(const AtomicString, immediate_priority, ("immediate"));
+  return immediate_priority;
+}
+
+const AtomicString& HighPriorityKeyword() {
+  DEFINE_STATIC_LOCAL(const AtomicString, high_priority, ("high"));
+  return high_priority;
+}
+
+const AtomicString& DefaultPriorityKeyword() {
+  DEFINE_STATIC_LOCAL(const AtomicString, default_priority, ("default"));
+  return default_priority;
+}
+
+const AtomicString& LowPriorityKeyword() {
+  DEFINE_STATIC_LOCAL(const AtomicString, low_priority, ("low"));
+  return low_priority;
+}
+
+const AtomicString& IdlePriorityKeyword() {
+  DEFINE_STATIC_LOCAL(const AtomicString, idle_priority, ("idle"));
+  return idle_priority;
+}
+
+}  // namespace
+
+AtomicString WebSchedulingPriorityToString(WebSchedulingPriority priority) {
+  switch (priority) {
+    case WebSchedulingPriority::kImmediatePriority:
+      return ImmediatePriorityKeyword();
+    case WebSchedulingPriority::kHighPriority:
+      return HighPriorityKeyword();
+    case WebSchedulingPriority::kDefaultPriority:
+      return DefaultPriorityKeyword();
+    case WebSchedulingPriority::kLowPriority:
+      return LowPriorityKeyword();
+    case WebSchedulingPriority::kIdlePriority:
+      return IdlePriorityKeyword();
+  }
+
+  NOTREACHED();
+  return g_empty_atom;
+}
+
+WebSchedulingPriority WebSchedulingPriorityFromString(
+    const AtomicString& priority) {
+  if (priority == ImmediatePriorityKeyword())
+    return WebSchedulingPriority::kImmediatePriority;
+  if (priority == HighPriorityKeyword())
+    return WebSchedulingPriority::kHighPriority;
+  if (priority == DefaultPriorityKeyword())
+    return WebSchedulingPriority::kDefaultPriority;
+  if (priority == LowPriorityKeyword())
+    return WebSchedulingPriority::kLowPriority;
+  if (priority == IdlePriorityKeyword())
+    return WebSchedulingPriority::kIdlePriority;
+
+  NOTREACHED();
+  return WebSchedulingPriority::kDefaultPriority;
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h b/third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h
index 1244640..fde66f5 100644
--- a/third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h
+++ b/third_party/blink/renderer/platform/scheduler/public/web_scheduling_priority.h
@@ -5,6 +5,9 @@
 #ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_WEB_SCHEDULING_PRIORITY_H_
 #define THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_WEB_SCHEDULING_PRIORITY_H_
 
+#include "third_party/blink/renderer/platform/platform_export.h"
+#include "third_party/blink/renderer/platform/wtf/text/atomic_string.h"
+
 namespace blink {
 
 // Priorities for the experimental scheduling API (see
@@ -19,6 +22,11 @@
   kLastPriority = kIdlePriority
 };
 
+PLATFORM_EXPORT AtomicString
+    WebSchedulingPriorityToString(WebSchedulingPriority);
+PLATFORM_EXPORT WebSchedulingPriority
+WebSchedulingPriorityFromString(const AtomicString&);
+
 }  // namespace blink
 
 #endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_SCHEDULER_PUBLIC_WEB_SCHEDULING_PRIORITY_H_
diff --git a/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py b/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py
index 79f9842..98213870 100644
--- a/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py
+++ b/third_party/blink/tools/blinkpy/web_tests/models/test_expectations.py
@@ -73,7 +73,7 @@
     'Fuchsia',
     'IOS', 'IOS12.2', 'IOS13.0',
     'Linux',
-    'Mac', 'Mac10.10', 'Mac10.11', 'Retina', 'Mac10.12', 'Mac10.13',
+    'Mac', 'Retina', 'Mac10.10', 'Mac10.11', 'Mac10.12', 'Mac10.13', 'Mac10.14', 'Mac10.15',
     'Win', 'Win7', 'Win10'
 ]
 
diff --git a/third_party/blink/tools/blinkpy/web_tests/port/base.py b/third_party/blink/tools/blinkpy/web_tests/port/base.py
index 07f0dc83..8a01764e 100644
--- a/third_party/blink/tools/blinkpy/web_tests/port/base.py
+++ b/third_party/blink/tools/blinkpy/web_tests/port/base.py
@@ -129,6 +129,8 @@
         ('mac10.11', 'x86'),
         ('mac10.12', 'x86'),
         ('mac10.13', 'x86'),
+        ('mac10.14', 'x86'),
+        ('mac10.15', 'x86'),
         ('win7', 'x86'),
         ('win10', 'x86'),
         ('trusty', 'x86_64'),
@@ -140,7 +142,7 @@
     )
 
     CONFIGURATION_SPECIFIER_MACROS = {
-        'mac': ['retina', 'mac10.10', 'mac10.11', 'mac10.12', 'mac10.13'],
+        'mac': ['retina', 'mac10.10', 'mac10.11', 'mac10.12', 'mac10.13', 'mac10.14', 'mac10.15'],
         'win': ['win7', 'win10'],
         'linux': ['trusty'],
         'fuschia': ['fuchsia'],
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
index 05179c4..a53aa83 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_6.json
@@ -43255,6 +43255,30 @@
      {}
     ]
    ],
+   "css/css-flexbox/flex-minimum-height-flex-items-017.html": [
+    [
+     "css/css-flexbox/flex-minimum-height-flex-items-017.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
+   "css/css-flexbox/flex-minimum-height-flex-items-018.html": [
+    [
+     "css/css-flexbox/flex-minimum-height-flex-items-018.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square.xht",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-flexbox/flex-minimum-width-flex-items-001.xht": [
     [
      "css/css-flexbox/flex-minimum-width-flex-items-001.xht",
@@ -359518,6 +359542,14 @@
    "a5dc87633e7a8a32e8ec1b62c877244f72357339",
    "reftest"
   ],
+  "css/css-flexbox/flex-minimum-height-flex-items-017.html": [
+   "b5e194219ec9fb9566c0ed63b3def51661eb6866",
+   "reftest"
+  ],
+  "css/css-flexbox/flex-minimum-height-flex-items-018.html": [
+   "983cac476e83ae97355cc6ad3fc55c6f732a6bf2",
+   "reftest"
+  ],
   "css/css-flexbox/flex-minimum-width-flex-items-001.xht": [
    "cd18483ba414160c46e30bc282dec0c2fcd2f418",
    "reftest"
@@ -492839,7 +492871,7 @@
    "support"
   ],
   "tools/wpt/android.py": [
-   "f92146c75e4377683c3b65b85ee5c201948b6f83",
+   "1dc057ff502f2539afb848b45752ea5a2c3ddc61",
    "support"
   ],
   "tools/wpt/browser.py": [
diff --git a/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment-target.html b/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment-target.html
index fa229dd..bc513126 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment-target.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment-target.html
@@ -1,9 +1,23 @@
 <!doctype html>
 <title>Navigating to a text fragment anchor</title>
 <script>
+function isInView(element) {
+  return (element.offsetTop >= window.scrollY) &&
+         (element.offsetTop <= (window.scrollY + window.innerHeight));
+}
+
 function checkScroll() {
   let bc = new BroadcastChannel('scroll-to-text-fragment');
-  bc.postMessage({ didScrollToTarget: window.scrollY > 0 });
+
+  var position = 'unknown';
+  if (window.scrollY == 0)
+    position = 'top';
+  else if (isInView(document.getElementById('element')))
+    position = 'element';
+  else if (isInView(document.getElementById('text')))
+    position = 'text';
+
+  bc.postMessage({ scrollPosition: position });
   bc.close();
   window.close();
 }
@@ -21,7 +35,7 @@
     top: 2000px;
   }
 </style>
-<body onload="checkScroll()">
+<body onload="window.requestAnimationFrame(checkScroll)">
   <div id="element">Element</div>
   <p id="text">This is a test page</p>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment.html b/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment.html
index db1b1de..21db280 100644
--- a/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment.html
+++ b/third_party/blink/web_tests/external/wpt/scroll-to-text-fragment/scroll-to-text-fragment.html
@@ -7,33 +7,34 @@
 <script src="/resources/testdriver-vendor.js"></script>
 <script>
 let test_cases = [
-  { fragment: '#', expect_scroll: false },
-  { fragment: '##targetText=test', expect_scroll: true },
-  { fragment: '##targetText=this,page', expect_scroll: true },
-  { fragment: '##targetText=this-,is,test', expect_scroll: true },
-  { fragment: '##targetText=this-,is,test,-page', expect_scroll: true },
-  { fragment: '##targetText=this-,is,page,-none', expect_scroll: false },
-  { fragment: '##targetText=this,test,-page', expect_scroll: true },
-  { fragment: '##targetText=this%20is%20a%20test%20page', expect_scroll: true },
-  { fragment: '##targetText=this&targetText=test,page', expect_scroll: true },
-  { fragment: '#pagestate##targetText=test', expect_scroll: true },
-  { fragment: '#pagestate##targetText=nomatch', expect_scroll: false },
-  { fragment: '#element##targetText=nomatch', expect_scroll: true },
+  { fragment: '#', expect_position: 'top' },
+  { fragment: '##targetText=test', expect_position: 'text' },
+  { fragment: '##targetText=this,page', expect_position: 'text' },
+  { fragment: '##targetText=this-,is,test', expect_position: 'text' },
+  { fragment: '##targetText=this-,is,test,-page', expect_position: 'text' },
+  { fragment: '##targetText=this-,is,page,-none', expect_position: 'top' },
+  { fragment: '##targetText=this,test,-page', expect_position: 'text' },
+  { fragment: '##targetText=this%20is%20a%20test%20page', expect_position: 'text' },
+  { fragment: '##targetText=this&targetText=test,page', expect_position: 'text' },
+  { fragment: '#pagestate##targetText=test', expect_position: 'text' },
+  { fragment: '#pagestate##targetText=nomatch', expect_position: 'top' },
+  { fragment: '#element##targetText=nomatch', expect_position: 'element' },
+  { fragment: '#element##directive', expect_position: 'element' },
 ];
 
 for (const test_case of test_cases) {
   promise_test(t => new Promise(resolve => {
     let channel = new BroadcastChannel('scroll-to-text-fragment');
     channel.addEventListener("message", e => {
-      resolve(e.data.didScrollToTarget);
+      resolve(e.data.scrollPosition);
     }, {once: true});
 
     test_driver.bless('Open a URL with a text fragment anchor', () => {
       window.open('scroll-to-text-fragment-target.html' + test_case.fragment, '_blank', 'noopener');
     });
-  }).then(scroll => {
-    assert_equals(scroll, test_case.expect_scroll,
-                  'Expected ' + test_case.fragment + (test_case.expect_scroll ? ' to scroll.' : ' to not scroll.'));
+  }).then(scrollPosition => {
+    assert_equals(scrollPosition, test_case.expect_position,
+                  'Expected ' + test_case.fragment + ' to scroll to ' + test_case.expect_position);
   }), 'Test navigation with text fragment anchor ' + test_case.fragment);
 }
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/tools/wpt/android.py b/third_party/blink/web_tests/external/wpt/tools/wpt/android.py
index f92146c..1dc057f 100644
--- a/third_party/blink/web_tests/external/wpt/tools/wpt/android.py
+++ b/third_party/blink/web_tests/external/wpt/tools/wpt/android.py
@@ -5,18 +5,23 @@
 import subprocess
 
 import requests
-from mozrunner.devices import android_device
 
-android_device.TOOLTOOL_PATH = os.path.join(os.path.dirname(__file__),
-                                            os.pardir,
-                                            "third_party",
-                                            "tooltool",
-                                            "tooltool.py")
+android_device = None
 
 here = os.path.abspath(os.path.dirname(__file__))
 wpt_root = os.path.abspath(os.path.join(here, os.pardir, os.pardir))
 
 
+def do_delayed_imports():
+    global android_device
+    from mozrunner.devices import android_device
+    android_device.TOOLTOOL_PATH = os.path.join(os.path.dirname(__file__),
+                                                os.pardir,
+                                                "third_party",
+                                                "tooltool",
+                                                "tooltool.py")
+
+
 def get_parser_install():
     parser = argparse.ArgumentParser()
     parser.add_argument("--reinstall", action="store_true", default=False,
@@ -103,6 +108,8 @@
 
 
 def get_emulator(sdk_path):
+    if android_device is None:
+        do_delayed_imports()
     if "ANDROID_SDK_ROOT" not in os.environ:
         os.environ["ANDROID_SDK_ROOT"] = sdk_path
     substs = {"top_srcdir": wpt_root, "TARGET_CPU": "x86"}
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animatable/animate-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animatable/animate-expected.txt
index c277654..9c11920 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animatable/animate-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/Animatable/animate-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 137 tests; 134 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 138 tests; 135 PASS, 3 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Element.animate() creates an Animation object
 PASS Element.animate() creates an Animation object in the relevant realm of the target element
 PASS Element.animate() creates an Animation object with a KeyframeEffect
@@ -10,6 +10,7 @@
 PASS Element.animate() accepts a one property two value property-indexed keyframes specification
 PASS Element.animate() accepts a one shorthand property two value property-indexed keyframes specification
 PASS Element.animate() accepts a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification
+PASS Element.animate() accepts a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification
 PASS Element.animate() accepts a two property two value property-indexed keyframes specification
 PASS Element.animate() accepts a two property property-indexed keyframes specification with different numbers of values
 PASS Element.animate() accepts a property-indexed keyframes specification with an invalid value
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt
index 1ccc49f..3f9b9840 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/constructor-expected.txt
@@ -1,5 +1,5 @@
 This is a testharness.js-based test.
-Found 171 tests; 167 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 173 tests; 169 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS A KeyframeEffect can be constructed with no frames
 PASS easing values are parsed correctly when passed to the KeyframeEffect constructor in KeyframeEffectOptions
 PASS Invalid easing values are correctly rejected when passed to the KeyframeEffect constructor in KeyframeEffectOptions
@@ -12,6 +12,8 @@
 PASS A KeyframeEffect constructed with a one shorthand property two value property-indexed keyframes specification roundtrips
 PASS A KeyframeEffect can be constructed with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification
 PASS A KeyframeEffect constructed with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification roundtrips
+PASS A KeyframeEffect can be constructed with a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification
+PASS A KeyframeEffect constructed with a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification roundtrips
 PASS A KeyframeEffect can be constructed with a two property two value property-indexed keyframes specification
 PASS A KeyframeEffect constructed with a two property two value property-indexed keyframes specification roundtrips
 PASS A KeyframeEffect can be constructed with a two property property-indexed keyframes specification with different numbers of values
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt
index 6d66e81..6a5fee9 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt
@@ -1,9 +1,10 @@
 This is a testharness.js-based test.
-Found 78 tests; 77 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 79 tests; 78 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
 PASS Keyframes can be replaced with an empty keyframe
 PASS Keyframes can be replaced with a one property two value property-indexed keyframes specification
 PASS Keyframes can be replaced with a one shorthand property two value property-indexed keyframes specification
 PASS Keyframes can be replaced with a two property (one shorthand and one of its longhand components) two value property-indexed keyframes specification
+PASS Keyframes can be replaced with a two property (one shorthand and one of its shorthand components) two value property-indexed keyframes specification
 PASS Keyframes can be replaced with a two property two value property-indexed keyframes specification
 PASS Keyframes can be replaced with a two property property-indexed keyframes specification with different numbers of values
 PASS Keyframes can be replaced with a property-indexed keyframes specification with an invalid value
diff --git a/third_party/blink/web_tests/external/wpt/web-animations/resources/keyframe-tests.js b/third_party/blink/web_tests/external/wpt/web-animations/resources/keyframe-tests.js
index 43716801..3cf3cf22 100644
--- a/third_party/blink/web_tests/external/wpt/web-animations/resources/keyframe-tests.js
+++ b/third_party/blink/web_tests/external/wpt/web-animations/resources/keyframe-tests.js
@@ -86,6 +86,16 @@
                       { marginTop: '60px', margin: '10px 20px 30px 40px' })],
   },
   {
+    desc:   'a two property (one shorthand and one of its shorthand components)'
+            + ' two value property-indexed keyframes specification',
+    input:  { border: ['pink', '2px'],
+              borderColor: ['green', 'blue'] },
+    output: [keyframe(computedOffset(0),
+                      { border: 'pink', borderColor: 'green' }),
+             keyframe(computedOffset(1),
+                      { border: '2px', borderColor: 'blue' })],
+  },
+  {
     desc:   'a two property two value property-indexed keyframes specification',
     input:  { left: ['10px', '20px'],
               top: ['30px', '40px'] },
diff --git a/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll-expected.txt b/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll-expected.txt
index fde498e8..9b06021 100644
--- a/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll-expected.txt
+++ b/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll-expected.txt
@@ -5,10 +5,10 @@
 PASS event.pageY is 100
 
 Just zoomed
-PASS event.clientX is 83
-PASS event.clientY is 83
-PASS event.pageX is 83
-PASS event.pageY is 83
+PASS event.clientX is within 0.0001 of 83.3333
+PASS event.clientY is within 0.0001 of 83.3333
+PASS event.pageX is within 0.0001 of 83.3333
+PASS event.pageX is within 0.0001 of 83.3333
 
 Just scrolled
 PASS event.clientX is 100
@@ -17,10 +17,10 @@
 PASS event.pageY is 150
 
 Zoomed and scrolled
-PASS event.clientX is 83
-PASS event.clientY is 83
-PASS event.pageX is 133
-PASS event.pageY is 133
+PASS event.clientX is within 0.0001 of 83.3333
+PASS event.clientY is within 0.0001 of 83.3333
+PASS event.pageX is within 0.0001 of 133.3333
+PASS event.pageY is within 0.0001 of 133.3333
 
 RTL and scrolled
 PASS event.clientX is 100
diff --git a/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll.html b/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll.html
index 2dbe9fae..53122b8 100644
--- a/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll.html
+++ b/third_party/blink/web_tests/fast/events/clientXY-in-zoom-and-scroll.html
@@ -65,10 +65,10 @@
     {
         event = e;
         debug("\nJust zoomed");
-        shouldBe("event.clientX", "83");
-        shouldBe("event.clientY", "83");
-        shouldBe("event.pageX", "83");
-        shouldBe("event.pageY", "83");
+        shouldBeCloseTo("event.clientX", "83.3333", 0.0001);
+        shouldBeCloseTo("event.clientY", "83.3333", 0.0001);
+        shouldBeCloseTo("event.pageX", "83.3333", 0.0001);
+        shouldBeCloseTo("event.pageX", "83.3333", 0.0001);
     }
     window.addEventListener("click", justZoomed, false);
     zoomPageIn();
@@ -97,10 +97,10 @@
     {
         event = e;
         debug("\nZoomed and scrolled");
-        shouldBe("event.clientX", "83");
-        shouldBe("event.clientY", "83");
-        shouldBe("event.pageX", "133");
-        shouldBe("event.pageY", "133");
+        shouldBeCloseTo("event.clientX", "83.3333", 0.0001);
+        shouldBeCloseTo("event.clientY", "83.3333", 0.0001);
+        shouldBeCloseTo("event.pageX", "133.3333", 0.0001);
+        shouldBeCloseTo("event.pageY", "133.3333", 0.0001);
     }
     window.addEventListener("click", zoomedAndScrolled, false);
     zoomPageIn();
diff --git a/third_party/blink/web_tests/fast/forms/select/option-mouseevents-expected.txt b/third_party/blink/web_tests/fast/forms/select/option-mouseevents-expected.txt
index 97f3c76..3a47db9 100644
--- a/third_party/blink/web_tests/fast/forms/select/option-mouseevents-expected.txt
+++ b/third_party/blink/web_tests/fast/forms/select/option-mouseevents-expected.txt
@@ -49,7 +49,7 @@
 PASS: event.pageX should be 22 and is
 PASS: event.pageY should be 262 and is
 PASS: event.offsetX should be 13 and is
-PASS: event.offsetY should be 5 and is
+PASS: event.offsetY should be 4.8125 and is
 PASS: event.x should be 22 and is
 PASS: event.y should be 262 and is
 PASS: event type should be click and is
@@ -57,7 +57,7 @@
 PASS: event.pageX should be 22 and is
 PASS: event.pageY should be 262 and is
 PASS: event.offsetX should be 13 and is
-PASS: event.offsetY should be 5 and is
+PASS: event.offsetY should be 4.8125 and is
 PASS: event.x should be 22 and is
 PASS: event.y should be 262 and is
 PASS: event type should be dblclick and is
diff --git a/third_party/blink/web_tests/fast/forms/select/option-mouseevents.html b/third_party/blink/web_tests/fast/forms/select/option-mouseevents.html
index 10e24f08..5258974 100644
--- a/third_party/blink/web_tests/fast/forms/select/option-mouseevents.html
+++ b/third_party/blink/web_tests/fast/forms/select/option-mouseevents.html
@@ -94,9 +94,9 @@
 </select>
 </form>
 <form action="" method="post">
-<select style="position:absolute; top: 242;" size="2" onclick="mouseeventverify(event, 22, 262, 13, 5, document.getElementById('o3'), 'click')">
+<select style="position:absolute; top: 242;" size="2" onclick="mouseeventverify(event, 22, 262, 13, 4.8125, document.getElementById('o3'), 'click')">
 <option>One
-<option id="o3" onclick="mouseeventverify(event, 22, 262, 13, 5, this, 'click')">Two
+<option id="o3" onclick="mouseeventverify(event, 22, 262, 13, 4.8125, this, 'click')">Two
 </select>
 </form>
 <form action="" method="post">
diff --git a/third_party/blink/web_tests/http/tests/devtools/a11y-axe-core/rendering/basic-rendering-a11y-test-expected.txt b/third_party/blink/web_tests/http/tests/devtools/a11y-axe-core/rendering/basic-rendering-a11y-test-expected.txt
new file mode 100644
index 0000000..e6ab923
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/a11y-axe-core/rendering/basic-rendering-a11y-test-expected.txt
@@ -0,0 +1,4 @@
+Tests accessibility in the rendering view using the axe-core linter.
+aXe violations: []
+
+
diff --git a/third_party/blink/web_tests/http/tests/devtools/a11y-axe-core/rendering/basic-rendering-a11y-test.js b/third_party/blink/web_tests/http/tests/devtools/a11y-axe-core/rendering/basic-rendering-a11y-test.js
new file mode 100644
index 0000000..a171697e
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/devtools/a11y-axe-core/rendering/basic-rendering-a11y-test.js
@@ -0,0 +1,14 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+(async function () {
+  await TestRunner.loadModule('axe_core_test_runner');
+
+  TestRunner.addResult(`Tests accessibility in the rendering view using the axe-core linter.`);
+  await UI.viewManager.showView('rendering');
+  const renderingView = await UI.viewManager.view('rendering').widget();
+  await AxeCoreTestRunner.runValidation(renderingView.element);
+
+  TestRunner.completeTest();
+})();
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-expected.txt b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-expected.txt
index cece502..d1f0d7b 100644
--- a/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-expected.txt
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-expected.txt
@@ -13,4 +13,5 @@
     nodeValue : 
     parentId : <number>
 }
+FrameID matched: true
 
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-pointer-events-expected.txt b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-pointer-events-expected.txt
new file mode 100644
index 0000000..b4a815ed
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-pointer-events-expected.txt
@@ -0,0 +1,10 @@
+Tests DOM.getNodeForLocation method with ignorePointerEventsNone: true.
+Node: [
+    [0] : style
+    [1] : position:absolute;top:0;left:0;width:100;height:100
+]
+Overlay node: [
+    [0] : style
+    [1] : position:absolute;top:0;left:0;width:200;height:200;pointer-events:none
+]
+
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-pointer-events.js b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-pointer-events.js
new file mode 100644
index 0000000..31f2284
--- /dev/null
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation-pointer-events.js
@@ -0,0 +1,22 @@
+(async function(testRunner) {
+  var {page, session, dp} = await testRunner.startHTML(`
+    <div style='position:absolute;top:0;left:0;width:100;height:100'></div>
+    <div style='position:absolute;top:0;left:0;width:200;height:200;pointer-events:none'></div>
+  `, 'Tests DOM.getNodeForLocation method with ignorePointerEventsNone: true.');
+  var NodeTracker = await testRunner.loadScript('../resources/node-tracker.js');
+  var nodeTracker = new NodeTracker(dp);
+
+  var response = await dp.DOM.getNodeForLocation({x: 10, y: 10});
+  var backendNodeId = response.result.backendNodeId;
+  await dp.DOM.enable();
+  await dp.DOM.getDocument();
+  testRunner.log((await nodeTracker.nodeForBackendId(backendNodeId)).attributes, 'Node: ');
+
+  response = await dp.DOM.getNodeForLocation({x: 10, y: 10, ignorePointerEventsNone: true});
+  backendNodeId = response.result.backendNodeId;
+  await dp.DOM.enable();
+  await dp.DOM.getDocument();
+  testRunner.log((await nodeTracker.nodeForBackendId(backendNodeId)).attributes, 'Overlay node: ');
+
+  testRunner.completeTest();
+})
diff --git a/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation.js b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation.js
index f3048c1..cf2bfd1 100644
--- a/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation.js
+++ b/third_party/blink/web_tests/inspector-protocol/dom/dom-getNodeForLocation.js
@@ -1,6 +1,7 @@
 (async function(testRunner) {
   var {page, session, dp} = await testRunner.startHTML(`
     <div style='position:absolute;top:0;left:0;width:100;height:100'></div>
+    <div style='position:absolute;top:0;left:0;width:200;height:200;pointer-events:none'></div>
   `, 'Tests DOM.getNodeForLocation method.');
   var NodeTracker = await testRunner.loadScript('../resources/node-tracker.js');
   var nodeTracker = new NodeTracker(dp);
@@ -10,6 +11,10 @@
   await dp.DOM.enable();
   await dp.DOM.getDocument();
   testRunner.log(await nodeTracker.nodeForBackendId(backendNodeId), 'Node: ');
+
+  await dp.Page.enable();
+  var response2 = await dp.Page.getFrameTree({});
+  testRunner.log('FrameID matched: ' + (response2.result.frameTree.frame.id === response.result.frameId));
+
   testRunner.completeTest();
 })
-
diff --git a/third_party/blink/web_tests/media/track/track-css-author-settings-override-user-settings.html b/third_party/blink/web_tests/media/track/track-css-author-settings-override-user-settings.html
index 99ded26..cf044fc 100644
--- a/third_party/blink/web_tests/media/track/track-css-author-settings-override-user-settings.html
+++ b/third_party/blink/web_tests/media/track/track-css-author-settings-override-user-settings.html
@@ -29,37 +29,41 @@
 async_test(function(t) {
     var container = document.getElementById("container");
 
-    var video = addVideoTo(container);
-    video.oncanplaythrough = t.step_func_done(function() {
-        var cue = textTrackCueElementByIndex(video, 0).firstChild.firstElementChild;
+    // Test a video without user settings applied.
+    var video1 = addVideoTo(container);
+    video1.oncanplaythrough = t.step_func_done(function() {
+        var cue = textTrackCueElementByIndex(video1, 0).firstChild.firstElementChild;
         var cueStyle = getComputedStyle(cue);
         assert_equals(cueStyle.color, "rgb(255, 0, 0)");
         assert_equals(cueStyle.backgroundColor, "rgb(0, 128, 0)");
         assert_equals(cueStyle.textShadow, "rgb(0, 255, 0) 3px 3px 0px");
         assert_equals(cueStyle.fontSize, "20px");
         assert_equals(cueStyle.fontFamily, "arial");
+        testApplyUserSettings();
     });
 
     // Apply user settings and verify that the author settings are retained, when they exist.
-    internals.settings.setTextTrackTextColor("rgb(0, 255, 255)");
-    internals.settings.setTextTrackBackgroundColor("rgb(0, 255, 0)");
-    internals.settings.setTextTrackTextShadow("rgb(255, 0, 0) 2px 2px")
-    internals.settings.setTextTrackTextSize("14px");
-    internals.settings.setTextTrackFontFamily("fantasy");
-    internals.settings.setTextTrackFontStyle("italic");
-    internals.settings.setTextTrackFontVariant("small-caps");
+    function testApplyUserSettings() {
+        internals.settings.setTextTrackTextColor("rgb(0, 255, 255)");
+        internals.settings.setTextTrackBackgroundColor("rgb(0, 255, 0)");
+        internals.settings.setTextTrackTextShadow("rgb(255, 0, 0) 2px 2px")
+        internals.settings.setTextTrackTextSize("14px");
+        internals.settings.setTextTrackFontFamily("fantasy");
+        internals.settings.setTextTrackFontStyle("italic");
+        internals.settings.setTextTrackFontVariant("small-caps");
 
-    video = addVideoTo(container);
-    video.oncanplaythrough = t.step_func_done(function() {
-        var cue = textTrackCueElementByIndex(video, 0).firstChild.firstElementChild;
-        var cueStyle = getComputedStyle(cue);
-        assert_equals(cueStyle.color, "rgb(255, 0, 0)");
-        assert_equals(cueStyle.backgroundColor, "rgb(0, 128, 0)");
-        assert_equals(cueStyle.textShadow, "rgb(0, 255, 0) 3px 3px 0px");
-        assert_equals(cueStyle.fontSize, "20px");
-        assert_equals(cueStyle.fontFamily, "arial");
-        assert_equals(cueStyle.fontStyle, "italic");
-        assert_equals(cueStyle.fontVariant, "small-caps");
-    });
+        video2 = addVideoTo(container);
+        video2.oncanplaythrough = t.step_func_done(function() {
+            var cue = textTrackCueElementByIndex(video2, 0).firstChild.firstElementChild;
+            var cueStyle = getComputedStyle(cue);
+            assert_equals(cueStyle.color, "rgb(255, 0, 0)");
+            assert_equals(cueStyle.backgroundColor, "rgb(0, 128, 0)");
+            assert_equals(cueStyle.textShadow, "rgb(0, 255, 0) 3px 3px 0px");
+            assert_equals(cueStyle.fontSize, "20px");
+            assert_equals(cueStyle.fontFamily, "arial");
+            assert_equals(cueStyle.fontStyle, "italic");
+            assert_equals(cueStyle.fontVariant, "small-caps");
+        });
+    }
 });
 </script>
diff --git a/third_party/blink/web_tests/media/track/track-css-important-user-settings-override-author-settings.html b/third_party/blink/web_tests/media/track/track-css-important-user-settings-override-author-settings.html
index 5a4be0b5..917a851 100644
--- a/third_party/blink/web_tests/media/track/track-css-important-user-settings-override-author-settings.html
+++ b/third_party/blink/web_tests/media/track/track-css-important-user-settings-override-author-settings.html
@@ -29,37 +29,41 @@
 async_test(function(t) {
     var container = document.getElementById("container");
 
-    var video = addVideoTo(container);
-    video.oncanplaythrough = t.step_func_done(function() {
-        var cue = textTrackCueElementByIndex(video, 0).firstChild.firstElementChild;
+    // Test a video without user settings applied.
+    var video1 = addVideoTo(container);
+    video1.oncanplaythrough = t.step_func_done(function() {
+        var cue = textTrackCueElementByIndex(video1, 0).firstChild.firstElementChild;
         var cueStyle = getComputedStyle(cue);
         assert_equals(cueStyle.color, "rgb(255, 0, 0)");
         assert_equals(cueStyle.backgroundColor, "rgb(0, 128, 0)");
         assert_equals(cueStyle.textShadow, "rgb(0, 255, 0) 3px 3px 0px");
         assert_equals(cueStyle.fontSize, "20px");
         assert_equals(cueStyle.fontFamily, "arial");
+        testApplyUserSettings();
     });
 
     // Apply important user settings and verify they override author-specified settings.
-    internals.settings.setTextTrackTextColor("rgb(0, 255, 255) !important");
-    internals.settings.setTextTrackBackgroundColor("rgb(0, 255, 0) !important");
-    internals.settings.setTextTrackTextShadow("rgb(255, 0, 0) 2px 2px !important")
-    internals.settings.setTextTrackTextSize("14px !important");
-    internals.settings.setTextTrackFontFamily("fantasy !important");
-    internals.settings.setTextTrackFontStyle("italic !important");
-    internals.settings.setTextTrackFontVariant("small-caps !important");
+    function testApplyUserSettings() {
+        internals.settings.setTextTrackTextColor("rgb(0, 255, 255) !important");
+        internals.settings.setTextTrackBackgroundColor("rgb(0, 255, 0) !important");
+        internals.settings.setTextTrackTextShadow("rgb(255, 0, 0) 2px 2px !important")
+        internals.settings.setTextTrackTextSize("14px !important");
+        internals.settings.setTextTrackFontFamily("fantasy !important");
+        internals.settings.setTextTrackFontStyle("italic !important");
+        internals.settings.setTextTrackFontVariant("small-caps !important");
 
-    video = addVideoTo(container);
-    video.oncanplaythrough = t.step_func_done(function() {
-        var cue = textTrackCueElementByIndex(video, 0).firstChild.firstElementChild;
-        var cueStyle = getComputedStyle(cue);
-        assert_equals(cueStyle.color, "rgb(0, 255, 255)");
-        assert_equals(cueStyle.backgroundColor, "rgb(0, 255, 0)");
-        assert_equals(cueStyle.textShadow, "rgb(255, 0, 0) 2px 2px");
-        assert_equals(cueStyle.fontSize, "14px");
-        assert_equals(cueStyle.fontFamily, "fantasy");
-        assert_equals(cueStyle.fontStyle, "italic");
-        assert_equals(cueStyle.fontVariant, "small-caps");
-    });
+        video2 = addVideoTo(container);
+        video2.oncanplaythrough = t.step_func_done(function() {
+            var cue = textTrackCueElementByIndex(video2, 0).firstChild.firstElementChild;
+            var cueStyle = getComputedStyle(cue);
+            assert_equals(cueStyle.color, "rgb(0, 255, 255)");
+            assert_equals(cueStyle.backgroundColor, "rgb(0, 255, 0)");
+            assert_equals(cueStyle.textShadow, "rgb(255, 0, 0) 2px 2px");
+            assert_equals(cueStyle.fontSize, "14px");
+            assert_equals(cueStyle.fontFamily, "fantasy");
+            assert_equals(cueStyle.fontStyle, "italic");
+            assert_equals(cueStyle.fontVariant, "small-caps");
+        });
+    }
 });
 </script>
diff --git a/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html b/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html
index 066ccb1e..e6082d7 100644
--- a/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html
+++ b/third_party/blink/web_tests/media/track/track-css-user-settings-override-internal-settings.html
@@ -20,9 +20,10 @@
 async_test(function(t) {
     var container = document.getElementById("container");
 
-    var video = addVideoTo(container);
-    video.oncanplaythrough = t.step_func_done(function() {
-        var displayTree = textTrackCueElementByIndex(video, 0);
+    // Test a video without user settings applied.
+    var video1 = addVideoTo(container);
+    video1.oncanplaythrough = t.step_func_done(function() {
+        var displayTree = textTrackCueElementByIndex(video1, 0);
         var displayTreeStyle = getComputedStyle(displayTree);
         var cue = displayTree.firstChild;
         var cueStyle = getComputedStyle(cue);
@@ -33,27 +34,29 @@
         assert_equals(cueStyle.fontFamily, "sans-serif");
         assert_equals(displayTreeStyle.backgroundColor, "rgba(0, 0, 0, 0)");
         assert_equals(displayTreeStyle.padding, "0px");
+        testApplyUserSettings();
     });
 
     // Apply user settings for color and font-size and verify that the other internal settings are retained.
-    internals.settings.setTextTrackTextColor("rgb(0, 255, 255)");
-    internals.settings.setTextTrackTextSize("14px");
-    internals.settings.setTextTrackWindowColor("rgba(0, 0, 0, 0.8)");
-    internals.settings.setTextTrackWindowPadding("5px");
-
-    var video = addVideoTo(container);
-    video.oncanplaythrough = t.step_func_done(function() {
-        var displayTree = textTrackCueElementByIndex(video, 0);
-        var displayTreeStyle = getComputedStyle(displayTree);
-        var cue = displayTree.firstChild;
-        var cueStyle = getComputedStyle(cue);
-        assert_equals(cueStyle.color, "rgb(0, 255, 255)");
-        assert_equals(cueStyle.fontSize, "14px");
-        assert_equals(displayTreeStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
-        assert_equals(displayTreeStyle.padding, "5px");
-        // When there is no user setting specified for background-color and font-family, the internal settings are applied.
-        assert_equals(cueStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
-        assert_equals(cueStyle.fontFamily, "sans-serif");
-    });
+    function testApplyUserSettings() {
+        internals.settings.setTextTrackTextColor("rgb(0, 255, 255)");
+        internals.settings.setTextTrackTextSize("14px");
+        internals.settings.setTextTrackWindowColor("rgba(0, 0, 0, 0.8)");
+        internals.settings.setTextTrackWindowPadding("5px");
+        var video2 = addVideoTo(container);
+        video2.oncanplaythrough = t.step_func_done(function() {
+            var displayTree = textTrackCueElementByIndex(video2, 0);
+            var displayTreeStyle = getComputedStyle(displayTree);
+            var cue = displayTree.firstChild;
+            var cueStyle = getComputedStyle(cue);
+            assert_equals(cueStyle.color, "rgb(0, 255, 255)");
+            assert_equals(cueStyle.fontSize, "14px");
+            assert_equals(displayTreeStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
+            assert_equals(displayTreeStyle.padding, "5px");
+            // When there is no user setting specified for background-color and font-family, the internal settings are applied.
+            assert_equals(cueStyle.backgroundColor, "rgba(0, 0, 0, 0.8)");
+            assert_equals(cueStyle.fontFamily, "sans-serif");
+        });
+    }
 });
 </script>
diff --git a/third_party/blink/web_tests/platform/win/fast/forms/select/option-mouseevents-expected.txt b/third_party/blink/web_tests/platform/win/fast/forms/select/option-mouseevents-expected.txt
index 077c2cc6..856cbaa3 100644
--- a/third_party/blink/web_tests/platform/win/fast/forms/select/option-mouseevents-expected.txt
+++ b/third_party/blink/web_tests/platform/win/fast/forms/select/option-mouseevents-expected.txt
@@ -49,7 +49,7 @@
 PASS: event.pageX should be 22 and is
 PASS: event.pageY should be 262 and is
 PASS: event.offsetX should be 13 and is
-FAIL: event.offsetY should be 5 but instead is 2
+FAIL: event.offsetY should be 4.8125 but instead is 2
 PASS: event.x should be 22 and is
 PASS: event.y should be 262 and is
 PASS: event type should be click and is
@@ -57,7 +57,7 @@
 PASS: event.pageX should be 22 and is
 PASS: event.pageY should be 262 and is
 PASS: event.offsetX should be 13 and is
-FAIL: event.offsetY should be 5 but instead is 2
+FAIL: event.offsetY should be 4.8125 but instead is 2
 PASS: event.x should be 22 and is
 PASS: event.y should be 262 and is
 PASS: event type should be dblclick and is
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-content-size/content-size-012-ref.html b/third_party/blink/web_tests/wpt_internal/css/css-content-size/content-size-012-ref.html
new file mode 100644
index 0000000..08f987c
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-content-size/content-size-012-ref.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf8">
+<title>CSS content-size: replaced content</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="http://tabatkins.github.io/specs/css-content-size/">
+
+<style>
+#target {
+  width: 100px;
+  height: 200px;
+}
+</style>
+
+<img id=target src="resources/dice.png"></img>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-content-size/content-size-012.html b/third_party/blink/web_tests/wpt_internal/css/css-content-size/content-size-012.html
new file mode 100644
index 0000000..c79c373
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-content-size/content-size-012.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<meta charset="utf8">
+<title>CSS content-size: replaced content</title>
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
+<link rel="help" href="http://tabatkins.github.io/specs/css-content-size/">
+<link rel="match" href="content-size-012-ref.html">
+
+<style>
+#target {
+  content-size: 100px 200px;
+  contain: size;
+}
+</style>
+
+<img id=target src="resources/dice.png"></img>
diff --git a/third_party/blink/web_tests/wpt_internal/css/css-content-size/resources/dice.png b/third_party/blink/web_tests/wpt_internal/css/css-content-size/resources/dice.png
new file mode 100644
index 0000000..c82d015
--- /dev/null
+++ b/third_party/blink/web_tests/wpt_internal/css/css-content-size/resources/dice.png
Binary files differ
diff --git a/third_party/closure_compiler/externs/pending.js b/third_party/closure_compiler/externs/pending.js
index 75ac54a..4af5097 100644
--- a/third_party/closure_compiler/externs/pending.js
+++ b/third_party/closure_compiler/externs/pending.js
@@ -76,6 +76,23 @@
 
 /**
  * @see
+ * https://polymer-library.polymer-project.org/2.0/api/namespaces/Polymer.Templatize
+ * @constructor
+ * TODO(rbpotter): Remove this once it is added to Closure Compiler itself.
+ */
+Polymer.Templatize = function() {};
+
+/**
+ * @param {!HTMLTemplateElement} template
+ * @param {Object=} owner
+ * @param {Object=} options
+ * @return {!Function}
+ * TODO(rbpotter): Remove this once it is added to Closure Compiler itself.
+ */
+Polymer.Templatize.templatize = function(template, owner, options) {};
+
+/**
+ * @see
  * https://www.webcomponents.org/element/@polymer/iron-iconset-svg
  * Polymer iconset of SVGs.
  * @implements {Polymer.Iconset}
diff --git a/third_party/instrumented_libraries/OWNERS b/third_party/instrumented_libraries/OWNERS
index bde379aea..925143c 100644
--- a/third_party/instrumented_libraries/OWNERS
+++ b/third_party/instrumented_libraries/OWNERS
@@ -1,3 +1,5 @@
 eugenis@chromium.org
 glider@chromium.org
 thomasanderson@chromium.org
+
+# COMPONENT: Internals>Instrumentation
diff --git a/tools/android/roll/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy b/tools/android/roll/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
index afc176a..6f8f56e 100644
--- a/tools/android/roll/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
+++ b/tools/android/roll/android_deps/buildSrc/src/main/groovy/BuildConfigGenerator.groovy
@@ -254,6 +254,11 @@
                 // the library is present: b/70887421
                 sb.append('  deps += [":androidx_fragment_fragment_java"]\n')
                 break
+            case 'androidx_vectordrawable_vectordrawable':
+            case 'com_android_support_support_vector_drawable':
+                // Target has AIDL, but we don't support it yet: http://crbug.com/644439
+                sb.append('  create_srcjar = false\n')
+                break
             case 'android_arch_lifecycle_runtime':
             case 'android_arch_lifecycle_viewmodel':
                 sb.append('  # https://crbug.com/887942#c1\n')
@@ -271,10 +276,6 @@
                 sb.append('  # https://crbug.com/989505\n')
                 sb.append('  jar_excluded_patterns = ["META-INF/proguard/*"]\n')
                 break
-            case 'com_android_support_support_vector_drawable':
-                // Target has AIDL, but we don't support it yet: http://crbug.com/644439
-                sb.append('  create_srcjar = false\n')
-                break
             case 'com_android_support_transition':
                 // Not specified in the POM, compileOnly dependency not supposed to be used unless
                 // the library is present: b/70887421
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index d4baf47..52af5585 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -76,6 +76,7 @@
 
     'chromium.android.fyi': {
       'Memory Infra Tester': 'android_release_thumb_bot',
+      'android-marshmallow-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86_resource_whitelisting',
       'android-pie-x86-fyi-rel': 'android_release_bot_minimal_symbols_x86',
       'android-bfcache-rel': 'android_release_bot_minimal_symbols',
     },
@@ -390,6 +391,7 @@
       'Android FYI 64 Vk Release (Pixel 2)': 'gpu_tests_android_vulkan_release_trybot_arm64',
       'Android FYI 32 dEQP Vk Release (Pixel 2)': 'deqp_android_vulkan_release_trybot',
       'Android FYI 64 dEQP Vk Release (Pixel 2)': 'deqp_android_vulkan_release_trybot_arm64',
+      'Android FYI SkiaRenderer GL (Nexus 5X)': 'gpu_tests_android_release_trybot_arm64',
       'Android FYI SkiaRenderer Vulkan (Pixel 2)': 'gpu_tests_android_release_trybot',
       'GPU FYI Linux Builder': 'gpu_fyi_tests_release_trybot',
       'GPU FYI Linux Ozone Builder': 'gpu_fyi_tests_ozone_linux_system_gbm_libdrm_release_trybot',
@@ -644,6 +646,7 @@
       'android-kitkat-arm-coverage-rel': 'android_release_trybot_java_coverage',
       'android-kitkat-arm-rel': 'android_release_trybot',
       'android-marshmallow-arm64-rel': 'gpu_tests_android_release_trybot_arm64_resource_whitelisting',
+      'android-marshmallow-x86-fyi-rel': 'android_release_trybot_x86_resource_whitelisting',
       'android-oreo-arm64-cts-networkservice-dbg': 'android_debug_trybot_arm64',
       'android-oreo-arm64-rel': 'android_release_trybot_arm64_webview_google',
       'android-pie-x86-fyi-rel': 'android_release_trybot_x86',
@@ -672,6 +675,7 @@
       'gpu-fyi-try-android-l-nexus-6-32': 'gpu_tests_android_release_trybot',
       'gpu-fyi-try-android-m-nexus-5x-64': 'gpu_tests_android_release_trybot_arm64',
       'gpu-fyi-try-android-m-nexus-5x-deqp-64': 'deqp_android_release_trybot_arm64',
+      'gpu-fyi-try-android-m-nexus-5x-skgl-64': 'gpu_tests_android_release_trybot_arm64',
       'gpu-fyi-try-android-m-nexus-6p-64': 'gpu_tests_android_release_trybot_arm64',
       'gpu-fyi-try-android-m-nexus-9-64': 'gpu_tests_android_release_trybot_arm64',
       'gpu-fyi-try-android-n-nvidia-shield-tv-64': 'gpu_tests_android_release_trybot_arm64',
@@ -1101,6 +1105,11 @@
       'strip_debug_info',
     ],
 
+    'android_release_bot_minimal_symbols_x86_resource_whitelisting': [
+      'android', 'release_bot', 'minimal_symbols', 'x86',
+      'strip_debug_info', 'resource_whitelisting',
+    ],
+
     'android_release_thumb_bot': [
       'android', 'release_bot', 'arm_thumb',
     ],
@@ -1126,6 +1135,11 @@
       'android', 'release_trybot', 'strip_debug_info', 'x86',
     ],
 
+    'android_release_trybot_x86_resource_whitelisting': [
+      'android', 'release_trybot', 'strip_debug_info', 'x86',
+      'resource_whitelisting',
+    ],
+
     'android_webview_google_debug_static_bot': [
       'android', 'debug_static_bot', 'webview_google',
     ],
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 5f961ee..ca96e32 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -24606,6 +24606,7 @@
   <int value="3026" label="DeprecatedFileSystemWrite"/>
   <int value="3027" label="PointerLockUnadjustedMovement"/>
   <int value="3028" label="CreateObjectBlob"/>
+  <int value="3029" label="QuotaRead"/>
 </enum>
 
 <enum name="FeaturePolicyAllowlistType">
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 5d54597d..9db4ecd4 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -61110,6 +61110,17 @@
   </summary>
 </histogram>
 
+<histogram name="Media.GlobalMediaControls.UserActionFocus"
+    enum="BooleanFocused" expires_after="2020-10-22">
+  <owner>steimel@chromium.org</owner>
+  <owner>media-dev@chromium.org</owner>
+  <summary>
+    The focused state of the WebContents associated with the MediaSession that
+    the user is interacting with via the Global Media Controls. Recorded when
+    the user presses an action on the Global Media Controls dialog (e.g. play).
+  </summary>
+</histogram>
+
 <histogram name="Media.GPU.HasEverLostContext" enum="ContextProviderPhase"
     expires_after="M77">
   <owner>dcastagna@chromium.org</owner>
diff --git a/tools/metrics/ukm/ukm.xml b/tools/metrics/ukm/ukm.xml
index a1cebb9..f7ddcb0a 100644
--- a/tools/metrics/ukm/ukm.xml
+++ b/tools/metrics/ukm/ukm.xml
@@ -1019,6 +1019,7 @@
     <aggregation>
       <history>
         <index fields="metrics.PredictedType"/>
+        <index fields="metrics.PredictedType,metrics.PredictionSource"/>
         <statistics>
           <enumeration/>
         </statistics>
@@ -1055,6 +1056,8 @@
     <aggregation>
       <history>
         <index fields="metrics.ActualType"/>
+        <index fields="metrics.ActualType,metrics.PredictionSource"/>
+        <index fields="metrics.PredictionSource"/>
         <statistics>
           <enumeration/>
         </statistics>
diff --git a/tools/perf/process_perf_results.py b/tools/perf/process_perf_results.py
index 89cf185d..f2aeb4b 100755
--- a/tools/perf/process_perf_results.py
+++ b/tools/perf/process_perf_results.py
@@ -54,6 +54,7 @@
 # are okay with potentially encountering issues.
 GTEST_CONVERSION_WHITELIST = [
   'angle_perftests',
+  'cc_perftests',
   'gpu_perftests',
   'xr.vr.common_perftests',
 ]
diff --git a/tools/run-swarmed.py b/tools/run-swarmed.py
index a958ec1a2f..eecdeb0 100755
--- a/tools/run-swarmed.py
+++ b/tools/run-swarmed.py
@@ -4,18 +4,20 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Runs a Fuchsia gtest-based test on Swarming, optionally many times,
-collecting the output of the runs into a directory. Useful for flake checking,
-and faster than using trybots by avoiding repeated bot_update, compile, archive,
-etc. and allowing greater parallelism.
+"""Runs a gtest-based test on Swarming, optionally many times, collecting the
+output of the runs into a directory. Useful for flake checking, and faster than
+using trybots by avoiding repeated bot_update, compile, archive, etc. and
+allowing greater parallelism.
 
 To use, run in a new shell (it blocks until all Swarming jobs complete):
 
-  tools/run-swarmed.py -t content_unittests --out-dir=out/fuch
+  tools/run-swarmed.py out/rel base_unittests
 
 The logs of the runs will be stored in results/ (or specify a results directory
 with --results=some_dir). You can then do something like `grep -L SUCCESS
 results/*` to find the tests that failed or otherwise process the log files.
+
+See //docs/workflow/debugging-with-swarming.md for more details.
 """
 
 import argparse
@@ -49,16 +51,14 @@
       '-d', 'pool', args.pool,
       '-s', isolated_hash,
       '--dump-json', json_file,
+      '-d', 'os', args.swarming_os,
   ]
   if args.target_os == 'fuchsia':
     trigger_args += [
-      '-d', 'os', 'Linux',
       '-d', 'kvm', '1',
       '-d', 'gpu', 'none',
       '-d', 'cpu', args.arch,
     ]
-  elif args.target_os == 'win':
-    trigger_args += [ '-d', 'os', 'Windows' ]
   elif args.target_os == 'android':
     # The canonical version numbers are stored in the infra repository here:
     # build/scripts/slave/recipe_modules/swarming/api.py
@@ -74,7 +74,6 @@
         '.swarming_module:infra/tools/luci/vpython/${platform}:' +
         vpython_version)
     trigger_args += [
-        '-d', 'os', 'Android',
         '-d', 'device_os', args.device_os,
         '--cipd-package', cpython_pkg,
         '--cipd-package', vpython_native_pkg,
@@ -92,7 +91,7 @@
     trigger_args.append('--gtest_filter=' + args.gtest_filter)
   elif args.target_os == 'fuchsia':
     filter_file = \
-        'testing/buildbot/filters/fuchsia.' + args.test_name + '.filter'
+        'testing/buildbot/filters/fuchsia.' + args.target_name + '.filter'
     if os.path.isfile(filter_file):
       trigger_args.append('--test-launcher-filter-file=../../' + filter_file)
   with open(os.devnull, 'w') as nul:
@@ -123,11 +122,8 @@
 
 def main():
   parser = argparse.ArgumentParser()
-  parser.add_argument('-C', '--out-dir', default='out/fuch',
-                      help='Build directory.')
+  parser.add_argument('--swarming-os', help='OS specifier for Swarming.')
   parser.add_argument('--target-os', default='detect', help='gn target_os')
-  parser.add_argument('--test-name', '-t', required=True,
-                      help='Name of test to run.')
   parser.add_argument('--arch', '-a', default='detect',
                       help='CPU architecture of the test binary.')
   parser.add_argument('--copies', '-n', type=int, default=1,
@@ -141,6 +137,8 @@
   parser.add_argument('--gtest_filter',
                       help='Use the given gtest_filter, rather than the '
                            'default filter file, if any.')
+  parser.add_argument('out_dir', type=str, help='Build directory.')
+  parser.add_argument('target_name', type=str, help='Name of target to run.')
 
   args = parser.parse_args()
 
@@ -158,17 +156,26 @@
       args.target_os = { 'darwin': 'mac', 'linux2': 'linux', 'win32': 'win' }[
                            sys.platform]
 
+  if args.swarming_os is None:
+    args.swarming_os = {
+      'mac': 'Mac',
+      'win': 'Windows',
+      'linux': 'Linux',
+      'android': 'Android',
+      'fuchsia': 'Linux'
+    }[args.target_os]
+
   # Determine the CPU architecture of the test binary, if not specified.
   if args.arch == 'detect' and args.target_os == 'fuchsia':
     executable_info = subprocess.check_output(
-        ['file', os.path.join(args.out_dir, args.test_name)])
+        ['file', os.path.join(args.out_dir, args.target_name)])
     if 'ARM aarch64' in executable_info:
       args.arch = 'arm64',
     else:
       args.arch = 'x86-64'
 
   subprocess.check_call(
-      ['tools/mb/mb.py', 'isolate', '//' + args.out_dir, args.test_name])
+      ['tools/mb/mb.py', 'isolate', '//' + args.out_dir, args.target_name])
 
   print 'If you get authentication errors, follow:'
   print '  https://www.chromium.org/developers/testing/isolated-testing/for-swes#TOC-Login-on-the-services'
@@ -177,8 +184,8 @@
   archive_output = subprocess.check_output(
       ['tools/swarming_client/isolate.py', 'archive',
        '-I', 'https://isolateserver.appspot.com',
-       '-i', os.path.join(args.out_dir, args.test_name + '.isolate'),
-       '-s', os.path.join(args.out_dir, args.test_name + '.isolated')])
+       '-i', os.path.join(args.out_dir, args.target_name + '.isolate'),
+       '-s', os.path.join(args.out_dir, args.target_name + '.isolated')])
   isolated_hash = archive_output.split()[0]
 
   if os.path.isdir(args.results):
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
index 2c2c2e7..d67b89c 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win.cc
@@ -458,13 +458,16 @@
   UIA_VALIDATE_TEXTRANGEPROVIDER_CALL();
 
   AXPositionInstance common_ancestor = start_->LowestCommonAncestor(*end_);
-
-  AXPlatformNodeDelegate* delegate = GetDelegate(common_ancestor.get());
-  DCHECK(delegate);
-
-  delegate->GetFromNodeID(common_ancestor->anchor_id())
-      ->GetNativeViewAccessible()
-      ->QueryInterface(IID_PPV_ARGS(element));
+  AXPlatformNode* node = GetDelegate(common_ancestor.get())
+                             ->GetFromNodeID(common_ancestor->anchor_id());
+  DCHECK(node);
+  while (ui::IsIgnored(node->GetDelegate()->GetData())) {
+    node = static_cast<AXPlatformNodeWin*>(
+        AXPlatformNode::FromNativeViewAccessible(
+            node->GetDelegate()->GetParent()));
+    DCHECK(node);
+  }
+  node->GetNativeViewAccessible()->QueryInterface(IID_PPV_ARGS(element));
 
   DCHECK(*element);
   return S_OK;
@@ -765,14 +768,19 @@
   AXPositionInstance common_ancestor =
       start_->LowestCommonAncestor(*end_.get());
 
-  if (!common_ancestor->GetAnchor()->children().empty()) {
-    AXPlatformNodeDelegate* delegate = GetDelegate(common_ancestor.get());
-    DCHECK(delegate);
-
-    descendants = delegate->GetFromNodeID(common_ancestor->anchor_id())
-                      ->GetDelegate()
-                      ->GetDescendants();
+  AXPlatformNodeDelegate* delegate =
+      GetDelegate(common_ancestor.get())
+          ->GetFromNodeID(common_ancestor->anchor_id())
+          ->GetDelegate();
+  DCHECK(delegate);
+  while (ui::IsIgnored(delegate->GetData())) {
+    auto* node = static_cast<AXPlatformNodeWin*>(
+        AXPlatformNode::FromNativeViewAccessible(delegate->GetParent()));
+    DCHECK(node);
+    delegate = node->GetDelegate();
   }
+  if (delegate->GetChildCount())
+    descendants = delegate->GetDescendants();
 
   SAFEARRAY* safe_array =
       SafeArrayCreateVector(VT_UNKNOWN, 0, descendants.size());
diff --git a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
index 48a16080..4381ff23 100644
--- a/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_textrangeprovider_win_unittest.cc
@@ -167,6 +167,18 @@
     EXPECT_UIA_TEXTRANGE_EQ(text_range_provider, expected_text);         \
   }
 
+#define EXPECT_ENCLOSING_ELEMENT(ax_node_given, ax_node_expected)            \
+  {                                                                          \
+    ComPtr<ITextRangeProvider> text_range_provider;                          \
+    GetTextRangeProviderFromTextNode(text_range_provider, ax_node_given);    \
+    ComPtr<IRawElementProviderSimple> enclosing_element;                     \
+    ASSERT_HRESULT_SUCCEEDED(                                                \
+        text_range_provider->GetEnclosingElement(&enclosing_element));       \
+    ComPtr<IRawElementProviderSimple> expected_text_provider =               \
+        QueryInterfaceFromNode<IRawElementProviderSimple>(ax_node_expected); \
+    EXPECT_EQ(expected_text_provider.Get(), enclosing_element.Get());        \
+  }
+
 class AXPlatformNodeTextRangeProviderTest : public ui::AXPlatformNodeWinTest {
  public:
   const AXNodePosition::AXPositionInstance& GetStart(
@@ -2973,32 +2985,46 @@
 TEST_F(AXPlatformNodeTextRangeProviderTest, TestITextRangeProviderGetChildren) {
   // Set up ax tree with the following structure:
   //
-  // root______________________
-  // |                         |
-  // text_node1___             text_node2
+  // root
+  // |
+  // document(ignored)_________________________
+  // |                         |              |
+  // text_node1___             text_node2     ignored_text
   // |            |
   // text_node3   text_node4
   ui::AXNodeData root_data;
   root_data.id = 1;
   root_data.role = ax::mojom::Role::kRootWebArea;
 
+  ui::AXNodeData document_data;
+  document_data.id = 2;
+  document_data.role = ax::mojom::Role::kDocument;
+  document_data.AddState(ax::mojom::State::kIgnored);
+  root_data.child_ids.push_back(document_data.id);
+
   ui::AXNodeData text_node1;
-  text_node1.id = 2;
+  text_node1.id = 3;
   text_node1.role = ax::mojom::Role::kStaticText;
-  root_data.child_ids.push_back(text_node1.id);
+  document_data.child_ids.push_back(text_node1.id);
 
   ui::AXNodeData text_node2;
-  text_node2.id = 3;
+  text_node2.id = 4;
   text_node2.role = ax::mojom::Role::kStaticText;
-  root_data.child_ids.push_back(text_node2.id);
+  document_data.child_ids.push_back(text_node2.id);
+
+  ui::AXNodeData ignored_text;
+  ignored_text.id = 5;
+  ignored_text.role = ax::mojom::Role::kStaticText;
+  ignored_text.AddState(ax::mojom::State::kIgnored);
+  document_data.child_ids.push_back(ignored_text.id);
 
   ui::AXNodeData text_node3;
-  text_node3.id = 4;
+  text_node3.id = 6;
   text_node3.role = ax::mojom::Role::kStaticText;
   text_node1.child_ids.push_back(text_node3.id);
 
   ui::AXNodeData text_node4;
-  text_node4.id = 5;
+  text_node4.id = 7;
   text_node4.role = ax::mojom::Role::kStaticText;
   text_node1.child_ids.push_back(text_node4.id);
 
@@ -3009,23 +3035,25 @@
   update.has_tree_data = true;
   update.root_id = root_data.id;
   update.nodes.push_back(root_data);
+  update.nodes.push_back(document_data);
   update.nodes.push_back(text_node1);
   update.nodes.push_back(text_node2);
+  update.nodes.push_back(ignored_text);
   update.nodes.push_back(text_node3);
   update.nodes.push_back(text_node4);
 
   Init(update);
 
   // Set up variables from the tree for testing.
-  AXNode* rootnode = GetRootNode();
+  AXNode* document_node = GetRootNode()->children()[0];
   AXNodePosition::SetTree(tree_.get());
-  AXNode* node1 = rootnode->children()[0];
-  AXNode* node2 = rootnode->children()[1];
+  AXNode* node1 = document_node->children()[0];
+  AXNode* node2 = document_node->children()[1];
   AXNode* node3 = node1->children()[0];
   AXNode* node4 = node1->children()[1];
 
-  ComPtr<IRawElementProviderSimple> root_node_raw =
-      QueryInterfaceFromNode<IRawElementProviderSimple>(rootnode);
+  ComPtr<IRawElementProviderSimple> document_node_raw =
+      QueryInterfaceFromNode<IRawElementProviderSimple>(document_node);
   ComPtr<IRawElementProviderSimple> text_node_raw1 =
       QueryInterfaceFromNode<IRawElementProviderSimple>(node1);
   ComPtr<IRawElementProviderSimple> text_node_raw2 =
@@ -3081,7 +3109,7 @@
   // Test root_node - children should include the entire left subtree and
   // the entire right subtree.
   EXPECT_HRESULT_SUCCEEDED(
-      root_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
+      document_node_raw->GetPatternProvider(UIA_TextPatternId, &text_provider));
 
   EXPECT_HRESULT_SUCCEEDED(
       text_provider->get_DocumentRange(&text_range_provider));
@@ -4194,4 +4222,112 @@
   ASSERT_EQ(static_cast<HRESULT>(UIA_E_ELEMENTNOTAVAILABLE),
             text_range_provider->ScrollIntoView(bool_arg));
 }
+
+TEST_F(AXPlatformNodeTextRangeProviderTest,
+       TestITextRangeProviderIgnoredNodes) {
+  // Parent Tree
+  // 1
+  // |
+  // 2(i)
+  // |________________________________
+  // |   |   |    |      |           |
+  // 3   4   5    6      7(i)        8(i)
+  //              |      |________
+  //              |      |       |
+  //              9(i)   10(i)   11
+  //              |      |____
+  //              |      |   |
+  //              12    13   14
+
+  ui::AXTreeUpdate tree_update;
+  ui::AXTreeID tree_id = ui::AXTreeID::CreateNewAXTreeID();
+  tree_update.tree_data.tree_id = tree_id;
+  tree_update.has_tree_data = true;
+  tree_update.root_id = 1;
+  tree_update.nodes.resize(14);
+  tree_update.nodes[0].id = 1;
+  tree_update.nodes[0].child_ids = {2};
+  tree_update.nodes[0].role = ax::mojom::Role::kRootWebArea;
+
+  tree_update.nodes[1].id = 2;
+  tree_update.nodes[1].child_ids = {3, 4, 5, 6, 7, 8};
+  tree_update.nodes[1].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[1].role = ax::mojom::Role::kDocument;
+
+  tree_update.nodes[2].id = 3;
+  tree_update.nodes[2].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[2].SetName(".3.");
+
+  tree_update.nodes[3].id = 4;
+  tree_update.nodes[3].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[3].SetName(".4.");
+
+  tree_update.nodes[4].id = 5;
+  tree_update.nodes[4].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[4].SetName(".5.");
+
+  tree_update.nodes[5].id = 6;
+  tree_update.nodes[5].role = ax::mojom::Role::kGenericContainer;
+  tree_update.nodes[5].child_ids = {9};
+
+  tree_update.nodes[6].id = 7;
+  tree_update.nodes[6].child_ids = {10, 11};
+  tree_update.nodes[6].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[6].role = ax::mojom::Role::kGenericContainer;
+
+  tree_update.nodes[7].id = 8;
+  tree_update.nodes[7].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[7].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[7].SetName(".8.");
+
+  tree_update.nodes[8].id = 9;
+  tree_update.nodes[8].child_ids = {12};
+  tree_update.nodes[8].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
+
+  tree_update.nodes[9].id = 10;
+  tree_update.nodes[9].child_ids = {13, 14};
+  tree_update.nodes[9].AddState(ax::mojom::State::kIgnored);
+  tree_update.nodes[8].role = ax::mojom::Role::kGenericContainer;
+
+  tree_update.nodes[10].id = 11;
+  tree_update.nodes[10].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[10].SetName(".11.");
+
+  tree_update.nodes[11].id = 12;
+  tree_update.nodes[11].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[11].SetName(".12.");
+
+  tree_update.nodes[12].id = 13;
+  tree_update.nodes[12].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[12].SetName(".13.");
+
+  tree_update.nodes[13].id = 14;
+  tree_update.nodes[13].role = ax::mojom::Role::kStaticText;
+  tree_update.nodes[13].SetName(".14.");
+
+  Init(tree_update);
+  AXNodePosition::SetTree(tree_.get());
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 1),
+                           GetNodeFromTree(tree_id, 1));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 2),
+                           GetNodeFromTree(tree_id, 1));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 3),
+                           GetNodeFromTree(tree_id, 3));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 4),
+                           GetNodeFromTree(tree_id, 4));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 5),
+                           GetNodeFromTree(tree_id, 5));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 8),
+                           GetNodeFromTree(tree_id, 1));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 11),
+                           GetNodeFromTree(tree_id, 11));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 12),
+                           GetNodeFromTree(tree_id, 12));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 13),
+                           GetNodeFromTree(tree_id, 13));
+  EXPECT_ENCLOSING_ELEMENT(GetNodeFromTree(tree_id, 14),
+                           GetNodeFromTree(tree_id, 14));
+}  // namespace ui
+
 }  // namespace ui
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 602f83b..50c118d 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -111,7 +111,8 @@
 }
 
 gfx::NativeViewAccessible TestAXNodeWrapper::GetParent() {
-  TestAXNodeWrapper* parent_wrapper = GetOrCreate(tree_, node_->parent());
+  TestAXNodeWrapper* parent_wrapper =
+      GetOrCreate(tree_, node_->GetUnignoredParent());
   return parent_wrapper ?
       parent_wrapper->ax_platform_node()->GetNativeViewAccessible() :
       nullptr;
@@ -744,13 +745,13 @@
 void TestAXNodeWrapper::Descendants(
     const AXNode* node,
     std::vector<gfx::NativeViewAccessible>* descendants) const {
-  std::vector<AXNode*> child_nodes = node->children();
-  for (AXNode* child : child_nodes) {
+  for (auto it = node->UnignoredChildrenBegin();
+       it != node->UnignoredChildrenEnd(); ++it) {
     descendants->emplace_back(ax_platform_node()
                                   ->GetDelegate()
-                                  ->GetFromNodeID(child->id())
+                                  ->GetFromNodeID(it->id())
                                   ->GetNativeViewAccessible());
-    Descendants(child, descendants);
+    Descendants(it.get(), descendants);
   }
 }
 
diff --git a/ui/base/clipboard/clipboard_format_type_win.cc b/ui/base/clipboard/clipboard_format_type_win.cc
index 1e1c427..3538eefc 100644
--- a/ui/base/clipboard/clipboard_format_type_win.cc
+++ b/ui/base/clipboard/clipboard_format_type_win.cc
@@ -183,8 +183,10 @@
   // TODO(https://crbug.com/950756): Should TYMED_ISTREAM / TYMED_ISTORAGE be
   // used instead of TYMED_HGLOBAL in
   // OSExchangeDataProviderWin::SetFileContents.
+  // The 0 constructor argument is used with CFSTR_FILECONTENTS to specify file
+  // content.
   static base::NoDestructor<ClipboardFormatType> format(
-      ::RegisterClipboardFormat(CFSTR_FILECONTENTS));
+      ::RegisterClipboardFormat(CFSTR_FILECONTENTS), 0);
   return *format;
 }
 
diff --git a/ui/base/cursor/ozone/bitmap_cursor_factory_ozone.cc b/ui/base/cursor/ozone/bitmap_cursor_factory_ozone.cc
index a64e32e..d4afe5c 100644
--- a/ui/base/cursor/ozone/bitmap_cursor_factory_ozone.cc
+++ b/ui/base/cursor/ozone/bitmap_cursor_factory_ozone.cc
@@ -38,7 +38,8 @@
 BitmapCursorOzone::BitmapCursorOzone(const SkBitmap& bitmap,
                                      const gfx::Point& hotspot)
     : hotspot_(hotspot), frame_delay_ms_(0) {
-  bitmaps_.push_back(bitmap);
+  if (!bitmap.isNull())
+    bitmaps_.push_back(bitmap);
 }
 
 BitmapCursorOzone::BitmapCursorOzone(const std::vector<SkBitmap>& bitmaps,
@@ -47,6 +48,11 @@
     : bitmaps_(bitmaps), hotspot_(hotspot), frame_delay_ms_(frame_delay_ms) {
   DCHECK_LT(0U, bitmaps.size());
   DCHECK_LE(0, frame_delay_ms);
+  // No null bitmap should be in the list. Blank cursors should just be an empty
+  // vector.
+  DCHECK(std::find_if(bitmaps_.begin(), bitmaps_.end(),
+                      [](const SkBitmap& bitmap) { return bitmap.isNull(); }) ==
+         bitmaps_.end());
 }
 
 BitmapCursorOzone::~BitmapCursorOzone() {
diff --git a/ui/base/dragdrop/os_exchange_data_win_unittest.cc b/ui/base/dragdrop/os_exchange_data_win_unittest.cc
index 3a595ce..7d3b33a 100644
--- a/ui/base/dragdrop/os_exchange_data_win_unittest.cc
+++ b/ui/base/dragdrop/os_exchange_data_win_unittest.cc
@@ -312,7 +312,8 @@
   {
     CLIPFORMAT cfstr_file_contents =
         RegisterClipboardFormat(CFSTR_FILECONTENTS);
-    FORMATETC format_etc = {cfstr_file_contents, nullptr, DVASPECT_CONTENT, -1,
+    // format_etc.lindex value 0 used for file drop.
+    FORMATETC format_etc = {cfstr_file_contents, nullptr, DVASPECT_CONTENT, 0,
                             TYMED_HGLOBAL};
     EXPECT_EQ(S_OK, com_data->QueryGetData(&format_etc));
 
diff --git a/ui/base/test/cocoa_helper.mm b/ui/base/test/cocoa_helper.mm
index 70461929..e80f0d2 100644
--- a/ui/base/test/cocoa_helper.mm
+++ b/ui/base/test/cocoa_helper.mm
@@ -212,8 +212,7 @@
           ([start_date timeIntervalSinceNow] > -kCloseTimeoutSeconds);
 
       // Autorelease anything thrown up by the event loop.
-      {
-        base::mac::ScopedNSAutoreleasePool pool;
+      @autoreleasepool {
         ++spins;
         NSEvent* next_event = [NSApp nextEventMatchingMask:NSAnyEventMask
                                                  untilDate:nil
@@ -253,12 +252,13 @@
 
   // Must create a pool here because [NSApp windows] has created an array
   // with retains on all the windows in it.
-  base::mac::ScopedNSAutoreleasePool pool;
-  NSArray* appWindows = [NSApp windows];
-  for (NSWindow* window in appWindows) {
-    windows.insert(window);
+  @autoreleasepool {
+    NSArray* appWindows = [NSApp windows];
+    for (NSWindow* window in appWindows) {
+      windows.insert(window);
+    }
+    return windows;
   }
-  return windows;
 }
 
 std::set<NSWindow*> CocoaTest::WindowsLeft() {
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
index 2572060..5b180453 100644
--- a/ui/native_theme/native_theme_win.cc
+++ b/ui/native_theme/native_theme_win.cc
@@ -377,71 +377,185 @@
                                  State state,
                                  const gfx::Rect& rect,
                                  const ExtraParams& extra) const {
+  if (part == kScrollbarCorner) {
+    // Special-cased here since there is no theme name for kScrollbarCorner.
+    destination_canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
+    return;
+  }
+
+  RECT rect_win = rect.ToRECT();
+  if (part == kTrackbarTrack) {
+    // Make the channel be 4 px thick in the center of the supplied rect.  (4 px
+    // matches what XP does in various menus; GetThemePartSize() doesn't seem to
+    // return good values here.)
+    constexpr int kChannelThickness = 4;
+    if (extra.trackbar.vertical) {
+      rect_win.top += (rect_win.bottom - rect_win.top - kChannelThickness) / 2;
+      rect_win.bottom = rect_win.top + kChannelThickness;
+    } else {
+      rect_win.left += (rect_win.right - rect_win.left - kChannelThickness) / 2;
+      rect_win.right = rect_win.left + kChannelThickness;
+    }
+  }
+
+  // Most parts can be drawn simply when there is a theme handle.
+  const HANDLE handle = GetThemeHandle(GetThemeName(part));
+  const int part_id = GetWindowsPart(part, state, extra);
+  const int state_id = GetWindowsState(part, state, extra);
+  if (handle) {
+    switch (part) {
+      case kMenuPopupArrow:
+        // The right-pointing arrow can use the common code, but the
+        // left-pointing one needs custom code.
+        if (!extra.menu_arrow.pointing_right) {
+          PaintLeftMenuArrowThemed(hdc, handle, part_id, state_id, rect);
+          return;
+        }
+        FALLTHROUGH;
+      case kCheckbox:
+      case kInnerSpinButton:
+      case kMenuCheck:
+      case kMenuCheckBackground:
+      case kMenuList:
+      case kProgressBar:
+      case kPushButton:
+      case kRadio:
+      case kScrollbarHorizontalTrack:
+      case kScrollbarVerticalTrack:
+      case kTabPanelBackground:
+      case kTrackbarThumb:
+      case kTrackbarTrack:
+      case kWindowResizeGripper:
+        DrawThemeBackground(handle, hdc, part_id, state_id, &rect_win, nullptr);
+        if (part == kProgressBar)
+          break;  // Further painting to do below.
+        return;
+      case kScrollbarDownArrow:
+      case kScrollbarHorizontalGripper:
+      case kScrollbarHorizontalThumb:
+      case kScrollbarLeftArrow:
+      case kScrollbarRightArrow:
+      case kScrollbarUpArrow:
+      case kScrollbarVerticalGripper:
+      case kScrollbarVerticalThumb:
+        PaintScaledTheme(handle, hdc, part_id, state_id, rect);
+        return;
+      case kTextField:
+        break;  // Handled entirely below.
+      case kMenuItemBackground:
+      case kMenuPopupBackground:
+      case kMenuPopupGutter:
+      case kMenuPopupSeparator:
+      case kScrollbarCorner:
+      case kSliderTrack:
+      case kSliderThumb:
+      case kMaxPart:
+        NOTREACHED();
+    }
+  }
+
+  // Do any further painting the common code couldn't handle.
   switch (part) {
     case kCheckbox:
-      PaintCheckbox(hdc, part, state, rect, extra.button);
+    case kPushButton:
+    case kRadio:
+      PaintButtonClassic(hdc, part, state, &rect_win, extra.button);
       return;
     case kInnerSpinButton:
-      PaintSpinButton(hdc, part, state, rect, extra.inner_spin);
-      return;
-    case kMenuList:
-      PaintMenuList(hdc, state, rect, extra.menu_list);
+      DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
+                       extra.inner_spin.classic_state);
       return;
     case kMenuCheck:
-      PaintMenuCheck(hdc, state, rect, extra.menu_check);
+      PaintFrameControl(
+          hdc, rect, DFC_MENU,
+          extra.menu_check.is_radio ? DFCS_MENUBULLET : DFCS_MENUCHECK,
+          extra.menu_check.is_selected, state);
       return;
-    case kMenuCheckBackground:
-      PaintMenuCheckBackground(hdc, state, rect);
+    case kMenuList:
+      DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
+                       DFCS_SCROLLCOMBOBOX | extra.menu_list.classic_state);
       return;
     case kMenuPopupArrow:
-      PaintMenuArrow(hdc, state, rect, extra.menu_arrow);
+      // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate
+      // a left pointing arrow.
+      PaintFrameControl(hdc, rect, DFC_MENU,
+                        extra.menu_arrow.pointing_right ? DFCS_MENUARROW
+                                                        : DFCS_MENUARROWRIGHT,
+                        extra.menu_arrow.is_selected, state);
       return;
-    case kProgressBar:
-      PaintProgressBar(hdc, rect, extra.progress_bar);
+    case kProgressBar: {
+      RECT value_rect = gfx::Rect(extra.progress_bar.value_rect_x,
+                                  extra.progress_bar.value_rect_y,
+                                  extra.progress_bar.value_rect_width,
+                                  extra.progress_bar.value_rect_height)
+                            .ToRECT();
+      if (handle) {
+        PaintProgressBarOverlayThemed(hdc, handle, &rect_win, &value_rect,
+                                      extra.progress_bar);
+      } else {
+        FillRect(hdc, &rect_win, GetSysColorBrush(COLOR_BTNFACE));
+        FillRect(hdc, &value_rect, GetSysColorBrush(COLOR_BTNSHADOW));
+        DrawEdge(hdc, &rect_win, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+      }
       return;
-    case kPushButton:
-      PaintPushButton(hdc, part, state, rect, extra.button);
-      return;
-    case kRadio:
-      PaintRadioButton(hdc, part, state, rect, extra.button);
-      return;
+    }
     case kScrollbarDownArrow:
-    case kScrollbarUpArrow:
     case kScrollbarLeftArrow:
     case kScrollbarRightArrow:
-      PaintScrollbarArrow(hdc, part, state, rect, extra.scrollbar_arrow);
+    case kScrollbarUpArrow:
+      PaintScrollbarArrowClassic(hdc, part, state, &rect_win);
       return;
     case kScrollbarHorizontalThumb:
     case kScrollbarVerticalThumb:
-    case kScrollbarHorizontalGripper:
-    case kScrollbarVerticalGripper:
-      PaintScrollbarThumb(hdc, part, state, rect, extra.scrollbar_thumb);
+      DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_MIDDLE);
       return;
     case kScrollbarHorizontalTrack:
     case kScrollbarVerticalTrack:
-      PaintScrollbarTrack(destination_canvas, hdc, part, state, rect,
-                          extra.scrollbar_track);
-      return;
-    case kScrollbarCorner:
-      destination_canvas->drawColor(SK_ColorWHITE, SkBlendMode::kSrc);
+      PaintScrollbarTrackClassic(destination_canvas, hdc, &rect_win,
+                                 extra.scrollbar_track);
       return;
     case kTabPanelBackground:
-      PaintTabPanelBackground(hdc, rect);
+      // Classic just renders a flat color background.
+      FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
       return;
-    case kTextField:
-      PaintTextField(hdc, part, state, rect, extra.text_field);
+    case kTextField: {
+      // TODO(mpcomplete): can we detect if the color is specified by the user,
+      // and if not, just use the system color?
+      // CreateSolidBrush() accepts a RGB value but alpha must be 0.
+      base::win::ScopedGDIObject<HBRUSH> bg_brush(CreateSolidBrush(
+          skia::SkColorToCOLORREF(extra.text_field.background_color)));
+      if (handle) {
+        PaintTextFieldThemed(hdc, handle, bg_brush.get(), part_id, state_id,
+                             &rect_win, extra.text_field);
+      } else {
+        PaintTextFieldClassic(hdc, bg_brush.get(), &rect_win, extra.text_field);
+      }
       return;
+    }
     case kTrackbarThumb:
+      if (extra.trackbar.vertical) {
+        DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE);
+      } else {
+        PaintHorizontalTrackbarThumbClassic(destination_canvas, hdc, rect_win,
+                                            extra.trackbar);
+      }
+      return;
     case kTrackbarTrack:
-      PaintTrackbar(destination_canvas, hdc, part, state, rect, extra.trackbar);
+      DrawEdge(hdc, &rect_win, EDGE_SUNKEN, BF_RECT);
       return;
     case kWindowResizeGripper:
-      PaintWindowResizeGripper(hdc, rect);
+      // Draw a windows classic scrollbar gripper.
+      DrawFrameControl(hdc, &rect_win, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
       return;
+    case kMenuCheckBackground:
+    case kScrollbarHorizontalGripper:
+    case kScrollbarVerticalGripper:
+      return;  // No further painting necessary.
+    case kMenuItemBackground:
     case kMenuPopupBackground:
     case kMenuPopupGutter:
     case kMenuPopupSeparator:
-    case kMenuItemBackground:
+    case kScrollbarCorner:
     case kSliderTrack:
     case kSliderThumb:
     case kMaxPart:
@@ -609,23 +723,20 @@
 
 NativeTheme::PreferredColorScheme
 NativeThemeWin::CalculatePreferredColorScheme() const {
-  if (UsesHighContrastColors()) {
-    // The Windows SystemParametersInfo API will return the high contrast theme
-    // as a string. However, this string is language dependent. Instead, to
-    // account for non-English systems, sniff out the system colors to
-    // determine the high contrast color scheme.
-    SkColor fg_color = system_colors_[SystemThemeColor::kWindowText];
-    SkColor bg_color = system_colors_[SystemThemeColor::kWindow];
-    if (bg_color == SK_ColorWHITE && fg_color == SK_ColorBLACK) {
-      return NativeTheme::PreferredColorScheme::kLight;
-    }
-    if (bg_color == SK_ColorBLACK && fg_color == SK_ColorWHITE) {
-      return NativeTheme::PreferredColorScheme::kDark;
-    }
-    return NativeTheme::PreferredColorScheme::kNoPreference;
-  }
+  if (!UsesHighContrastColors())
+    return NativeTheme::CalculatePreferredColorScheme();
 
-  return NativeTheme::CalculatePreferredColorScheme();
+  // The Windows SystemParametersInfo API will return the high contrast theme
+  // as a string. However, this string is language dependent. Instead, to
+  // account for non-English systems, sniff out the system colors to
+  // determine the high contrast color scheme.
+  SkColor fg_color = system_colors_[SystemThemeColor::kWindowText];
+  SkColor bg_color = system_colors_[SystemThemeColor::kWindow];
+  if (bg_color == SK_ColorWHITE && fg_color == SK_ColorBLACK)
+    return NativeTheme::PreferredColorScheme::kLight;
+  if (bg_color == SK_ColorBLACK && fg_color == SK_ColorWHITE)
+    return NativeTheme::PreferredColorScheme::kDark;
+  return NativeTheme::PreferredColorScheme::kNoPreference;
 }
 
 void NativeThemeWin::PaintIndirect(cc::PaintCanvas* destination_canvas,
@@ -727,26 +838,20 @@
       rect.y());
 }
 
-HRESULT NativeThemeWin::PaintButton(HDC hdc,
-                                    State state,
-                                    const ButtonExtraParams& extra,
-                                    int part_id,
-                                    int state_id,
-                                    RECT* rect) const {
-  HANDLE handle = GetThemeHandle(BUTTON);
-  if (handle)
-    return DrawThemeBackground(handle, hdc, part_id, state_id, rect, nullptr);
-
-  // Adjust classic_state based on part, state, and extras.
+void NativeThemeWin::PaintButtonClassic(HDC hdc,
+                                        Part part,
+                                        State state,
+                                        RECT* rect,
+                                        const ButtonExtraParams& extra) const {
   int classic_state = extra.classic_state;
-  switch (part_id) {
-    case BP_CHECKBOX:
+  switch (part) {
+    case kCheckbox:
       classic_state |= DFCS_BUTTONCHECK;
       break;
-    case BP_RADIOBUTTON:
+    case kPushButton:
       classic_state |= DFCS_BUTTONRADIO;
       break;
-    case BP_PUSHBUTTON:
+    case kRadio:
       classic_state |= DFCS_BUTTONPUSH;
       break;
     default:
@@ -754,316 +859,74 @@
       break;
   }
 
-  switch (state) {
-    case kDisabled:
-      classic_state |= DFCS_INACTIVE;
-      break;
-    case kHovered:
-    case kNormal:
-      break;
-    case kPressed:
-      classic_state |= DFCS_PUSHED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
+  if (state == kDisabled)
+    classic_state |= DFCS_INACTIVE;
+  else if (state == kPressed)
+    classic_state |= DFCS_PUSHED;
 
   if (extra.checked)
     classic_state |= DFCS_CHECKED;
 
-  // Draw it manually.
-  // All pressed states have both low bits set, and no other states do.
-  const bool focused = ((state_id & ETS_FOCUSED) == ETS_FOCUSED);
-  const bool pressed = ((state_id & PBS_PRESSED) == PBS_PRESSED);
-  if ((BP_PUSHBUTTON == part_id) && (pressed || focused)) {
-    // BP_PUSHBUTTON has a focus rect drawn around the outer edge, and the
-    // button itself is shrunk by 1 pixel.
+  if ((part == kPushButton) && ((state == kPressed) || extra.is_default)) {
+    // Pressed or defaulted buttons have a shadow replacing the outer 1 px.
     HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW);
     if (brush) {
       FrameRect(hdc, rect, brush);
       InflateRect(rect, -1, -1);
     }
   }
+
   DrawFrameControl(hdc, rect, DFC_BUTTON, classic_state);
 
-  // Draw the focus rectangle (the dotted line box) only on buttons.  For radio
-  // and checkboxes, we let webkit draw the focus rectangle (orange glow).
-  if ((BP_PUSHBUTTON == part_id) && focused) {
-    // The focus rect is inside the button.  The exact number of pixels depends
-    // on whether we're in classic mode or using uxtheme.
-    if (handle) {
-      GetThemeBackgroundContentRect(handle, hdc, part_id, state_id, rect, rect);
-    } else {
-      InflateRect(rect, -GetSystemMetrics(SM_CXEDGE),
-                  -GetSystemMetrics(SM_CYEDGE));
-    }
+  // Draw a focus rectangle (the dotted line box) on defaulted buttons.
+  if ((part == kPushButton) && extra.is_default) {
+    InflateRect(rect, -GetSystemMetrics(SM_CXEDGE),
+                -GetSystemMetrics(SM_CYEDGE));
     DrawFocusRect(hdc, rect);
   }
 
-  // Classic theme doesn't support indeterminate checkboxes.  We draw
-  // a recangle inside a checkbox like IE10 does.
-  if (part_id == BP_CHECKBOX && extra.indeterminate) {
+  // Classic theme doesn't support indeterminate checkboxes.  We draw a
+  // recangle inside a checkbox like IE10 does.
+  if ((part == kCheckbox) && extra.indeterminate) {
     RECT inner_rect = *rect;
     // "4 / 13" is same as IE10 in classic theme.
     int padding = (inner_rect.right - inner_rect.left) * 4 / 13;
     InflateRect(&inner_rect, -padding, -padding);
-    int color_index = state == kDisabled ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT;
+    int color_index = (state == kDisabled) ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT;
     FillRect(hdc, &inner_rect, GetSysColorBrush(color_index));
   }
-
-  return S_OK;
 }
 
-HRESULT NativeThemeWin::PaintMenuArrow(
-    HDC hdc,
-    State state,
-    const gfx::Rect& rect,
-    const MenuArrowExtraParams& extra) const {
-  int state_id = MSM_NORMAL;
-  if (state == kDisabled)
-    state_id = MSM_DISABLED;
-
-  HANDLE handle = GetThemeHandle(MENU);
-  RECT rect_win = rect.ToRECT();
-  if (handle) {
-    if (extra.pointing_right) {
-      return DrawThemeBackground(handle, hdc, MENU_POPUPSUBMENU, state_id,
-                                 &rect_win, nullptr);
-    }
-    // There is no way to tell the uxtheme API to draw a left pointing arrow; it
-    // doesn't have a flag equivalent to DFCS_MENUARROWRIGHT.  But they are
-    // needed for RTL locales on Vista.  So use a memory DC and mirror the
-    // region with GDI's StretchBlt.
-    gfx::Rect r(rect);
-    base::win::ScopedCreateDC mem_dc(CreateCompatibleDC(hdc));
-    base::win::ScopedBitmap mem_bitmap(CreateCompatibleBitmap(hdc, r.width(),
-                                                              r.height()));
-    base::win::ScopedSelectObject select_bitmap(mem_dc.Get(), mem_bitmap.get());
-    // Copy and horizontally mirror the background from hdc into mem_dc. Use
-    // a negative-width source rect, starting at the rightmost pixel.
-    StretchBlt(mem_dc.Get(), 0, 0, r.width(), r.height(),
-               hdc, r.right()-1, r.y(), -r.width(), r.height(), SRCCOPY);
-    // Draw the arrow.
-    RECT theme_rect = {0, 0, r.width(), r.height()};
-    HRESULT result =
-        DrawThemeBackground(handle, mem_dc.Get(), MENU_POPUPSUBMENU, state_id,
-                            &theme_rect, nullptr);
-    // Copy and mirror the result back into mem_dc.
-    StretchBlt(hdc, r.x(), r.y(), r.width(), r.height(),
-               mem_dc.Get(), r.width()-1, 0, -r.width(), r.height(), SRCCOPY);
-    return result;
-  }
-
-  // For some reason, Windows uses the name DFCS_MENUARROWRIGHT to indicate a
-  // left pointing arrow. This makes the following statement counterintuitive.
-  UINT pfc_state = extra.pointing_right ? DFCS_MENUARROW : DFCS_MENUARROWRIGHT;
-  return PaintFrameControl(hdc, rect, DFC_MENU, pfc_state, extra.is_selected,
-                           state);
+void NativeThemeWin::PaintLeftMenuArrowThemed(HDC hdc,
+                                              HANDLE handle,
+                                              int part_id,
+                                              int state_id,
+                                              const gfx::Rect& rect) const {
+  // There is no way to tell the uxtheme API to draw a left pointing arrow; it
+  // doesn't have a flag equivalent to DFCS_MENUARROWRIGHT.  But they are needed
+  // for RTL locales on Vista.  So use a memory DC and mirror the region with
+  // GDI's StretchBlt.
+  base::win::ScopedCreateDC mem_dc(CreateCompatibleDC(hdc));
+  base::win::ScopedBitmap mem_bitmap(
+      CreateCompatibleBitmap(hdc, rect.width(), rect.height()));
+  base::win::ScopedSelectObject select_bitmap(mem_dc.Get(), mem_bitmap.get());
+  // Copy and horizontally mirror the background from hdc into mem_dc. Use a
+  // negative-width source rect, starting at the rightmost pixel.
+  StretchBlt(mem_dc.Get(), 0, 0, rect.width(), rect.height(), hdc,
+             rect.right() - 1, rect.y(), -rect.width(), rect.height(), SRCCOPY);
+  // Draw the arrow.
+  RECT theme_rect = {0, 0, rect.width(), rect.height()};
+  DrawThemeBackground(handle, mem_dc.Get(), part_id, state_id, &theme_rect,
+                      nullptr);
+  // Copy and mirror the result back into mem_dc.
+  StretchBlt(hdc, rect.x(), rect.y(), rect.width(), rect.height(), mem_dc.Get(),
+             rect.width() - 1, 0, -rect.width(), rect.height(), SRCCOPY);
 }
 
-HRESULT NativeThemeWin::PaintMenuCheck(
-    HDC hdc,
-    State state,
-    const gfx::Rect& rect,
-    const MenuCheckExtraParams& extra) const {
-  HANDLE handle = GetThemeHandle(MENU);
-  if (handle) {
-    const int state_id = extra.is_radio ?
-        ((state == kDisabled) ? MC_BULLETDISABLED : MC_BULLETNORMAL) :
-        ((state == kDisabled) ? MC_CHECKMARKDISABLED : MC_CHECKMARKNORMAL);
-    RECT rect_win = rect.ToRECT();
-    return DrawThemeBackground(handle, hdc, MENU_POPUPCHECK, state_id,
-                               &rect_win, nullptr);
-  }
-
-  return PaintFrameControl(hdc, rect, DFC_MENU,
-                           extra.is_radio ? DFCS_MENUBULLET : DFCS_MENUCHECK,
-                           extra.is_selected, state);
-}
-
-HRESULT NativeThemeWin::PaintMenuCheckBackground(HDC hdc,
-                                                 State state,
-                                                 const gfx::Rect& rect) const {
-  HANDLE handle = GetThemeHandle(MENU);
-  if (!handle)
-    return S_OK;  // Nothing to do for background.
-
-  int state_id = state == kDisabled ? MCB_DISABLED : MCB_NORMAL;
-  RECT rect_win = rect.ToRECT();
-  return DrawThemeBackground(handle, hdc, MENU_POPUPCHECKBACKGROUND, state_id,
-                             &rect_win, nullptr);
-}
-
-HRESULT NativeThemeWin::PaintPushButton(HDC hdc,
-                                        Part part,
-                                        State state,
-                                        const gfx::Rect& rect,
-                                        const ButtonExtraParams& extra) const {
-  int state_id = extra.is_default ? PBS_DEFAULTED : PBS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = PBS_DISABLED;
-      break;
-    case kHovered:
-      state_id = PBS_HOT;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = PBS_PRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  RECT rect_win = rect.ToRECT();
-  return PaintButton(hdc, state, extra, BP_PUSHBUTTON, state_id, &rect_win);
-}
-
-HRESULT NativeThemeWin::PaintRadioButton(HDC hdc,
-                                         Part part,
-                                         State state,
-                                         const gfx::Rect& rect,
-                                         const ButtonExtraParams& extra) const {
-  int state_id = extra.checked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = extra.checked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED;
-      break;
-    case kHovered:
-      state_id = extra.checked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = extra.checked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  RECT rect_win = rect.ToRECT();
-  return PaintButton(hdc, state, extra, BP_RADIOBUTTON, state_id, &rect_win);
-}
-
-HRESULT NativeThemeWin::PaintCheckbox(HDC hdc,
-                                      Part part,
-                                      State state,
-                                      const gfx::Rect& rect,
-                                      const ButtonExtraParams& extra) const {
-  int state_id = extra.checked ?
-      CBS_CHECKEDNORMAL :
-      (extra.indeterminate ? CBS_MIXEDNORMAL : CBS_UNCHECKEDNORMAL);
-  switch (state) {
-    case kDisabled:
-      state_id = extra.checked ?
-          CBS_CHECKEDDISABLED :
-          (extra.indeterminate ? CBS_MIXEDDISABLED : CBS_UNCHECKEDDISABLED);
-      break;
-    case kHovered:
-      state_id = extra.checked ?
-          CBS_CHECKEDHOT :
-          (extra.indeterminate ? CBS_MIXEDHOT : CBS_UNCHECKEDHOT);
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = extra.checked ?
-          CBS_CHECKEDPRESSED :
-          (extra.indeterminate ? CBS_MIXEDPRESSED : CBS_UNCHECKEDPRESSED);
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  RECT rect_win = rect.ToRECT();
-  return PaintButton(hdc, state, extra, BP_CHECKBOX, state_id, &rect_win);
-}
-
-HRESULT NativeThemeWin::PaintMenuList(HDC hdc,
-                                      State state,
-                                      const gfx::Rect& rect,
-                                      const MenuListExtraParams& extra) const {
-  HANDLE handle = GetThemeHandle(MENULIST);
-  RECT rect_win = rect.ToRECT();
-  int state_id = CBXS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = CBXS_DISABLED;
-      break;
-    case kHovered:
-      state_id = CBXS_HOT;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = CBXS_PRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  if (handle) {
-    return DrawThemeBackground(handle, hdc, CP_DROPDOWNBUTTON, state_id,
-                               &rect_win, nullptr);
-  }
-
-  // Draw it manually.
-  DrawFrameControl(hdc, &rect_win, DFC_SCROLL,
-                   DFCS_SCROLLCOMBOBOX | extra.classic_state);
-  return S_OK;
-}
-
-HRESULT NativeThemeWin::PaintScrollbarArrow(
-    HDC hdc,
-    Part part,
-    State state,
-    const gfx::Rect& rect,
-    const ScrollbarArrowExtraParams& extra) const {
-  static const int state_id_matrix[4][kNumStates] = {
-      {ABS_DOWNDISABLED, ABS_DOWNHOT, ABS_DOWNNORMAL, ABS_DOWNPRESSED},
-      {ABS_LEFTDISABLED, ABS_LEFTHOT, ABS_LEFTNORMAL, ABS_LEFTPRESSED},
-      {ABS_RIGHTDISABLED, ABS_RIGHTHOT, ABS_RIGHTNORMAL, ABS_RIGHTPRESSED},
-      {ABS_UPDISABLED, ABS_UPHOT, ABS_UPNORMAL, ABS_UPPRESSED},
-  };
-  HANDLE handle = GetThemeHandle(SCROLLBAR);
-  RECT rect_win = rect.ToRECT();
-  if (handle) {
-    int index = part - kScrollbarDownArrow;
-    DCHECK_GE(index, 0);
-    DCHECK_LT(static_cast<size_t>(index), base::size(state_id_matrix));
-    int state_id = state_id_matrix[index][state];
-
-    // Hovering means that the cursor is over the scroolbar, but not over the
-    // specific arrow itself.  We don't want to show it "hot" mode, but only
-    // in "hover" mode.
-    if (state == kHovered && extra.is_hovering) {
-      switch (part) {
-        case kScrollbarDownArrow:
-          state_id = ABS_DOWNHOVER;
-          break;
-        case kScrollbarLeftArrow:
-          state_id = ABS_LEFTHOVER;
-          break;
-        case kScrollbarRightArrow:
-          state_id = ABS_RIGHTHOVER;
-          break;
-        case kScrollbarUpArrow:
-          state_id = ABS_UPHOVER;
-          break;
-        default:
-          NOTREACHED();
-          break;
-      }
-    }
-    return PaintScaledTheme(handle, hdc, SBP_ARROWBTN, state_id, rect);
-  }
-
+void NativeThemeWin::PaintScrollbarArrowClassic(HDC hdc,
+                                                Part part,
+                                                State state,
+                                                RECT* rect) const {
   int classic_state = DFCS_SCROLLDOWN;
   switch (part) {
     case kScrollbarDownArrow:
@@ -1097,451 +960,178 @@
       NOTREACHED();
       break;
   }
-  DrawFrameControl(hdc, &rect_win, DFC_SCROLL, classic_state);
-  return S_OK;
+  DrawFrameControl(hdc, rect, DFC_SCROLL, classic_state);
 }
 
-HRESULT NativeThemeWin::PaintScrollbarThumb(
-    HDC hdc,
-    Part part,
-    State state,
-    const gfx::Rect& rect,
-    const ScrollbarThumbExtraParams& extra) const {
-  HANDLE handle = GetThemeHandle(SCROLLBAR);
-  RECT rect_win = rect.ToRECT();
-
-  int part_id = SBP_THUMBBTNVERT;
-  switch (part) {
-    case kScrollbarHorizontalThumb:
-      part_id = SBP_THUMBBTNHORZ;
-      break;
-    case kScrollbarVerticalThumb:
-      break;
-    case kScrollbarHorizontalGripper:
-      part_id = SBP_GRIPPERHORZ;
-      break;
-    case kScrollbarVerticalGripper:
-      part_id = SBP_GRIPPERVERT;
-      break;
-    default:
-      NOTREACHED();
-      break;
-  }
-
-  int state_id = SCRBS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = SCRBS_DISABLED;
-      break;
-    case kHovered:
-      state_id = extra.is_hovering ? SCRBS_HOVER : SCRBS_HOT;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = SCRBS_PRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  if (handle)
-    return PaintScaledTheme(handle, hdc, part_id, state_id, rect);
-
-  // Draw it manually.
-  if ((part_id == SBP_THUMBBTNHORZ) || (part_id == SBP_THUMBBTNVERT))
-    DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_MIDDLE);
-  // Classic mode doesn't have a gripper.
-  return S_OK;
-}
-
-HRESULT NativeThemeWin::PaintScrollbarTrack(
+void NativeThemeWin::PaintScrollbarTrackClassic(
     SkCanvas* canvas,
     HDC hdc,
-    Part part,
-    State state,
-    const gfx::Rect& rect,
+    RECT* rect,
     const ScrollbarTrackExtraParams& extra) const {
-  HANDLE handle = GetThemeHandle(SCROLLBAR);
-  RECT rect_win = rect.ToRECT();
-
-  const int part_id = extra.is_upper ?
-      ((part == kScrollbarHorizontalTrack) ?
-          SBP_UPPERTRACKHORZ : SBP_UPPERTRACKVERT) :
-      ((part == kScrollbarHorizontalTrack) ?
-          SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT);
-
-  int state_id = SCRBS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = SCRBS_DISABLED;
-      break;
-    case kHovered:
-      state_id = SCRBS_HOVER;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = SCRBS_PRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  if (handle) {
-    return DrawThemeBackground(handle, hdc, part_id, state_id, &rect_win,
-                               nullptr);
-  }
-
-  // Draw it manually.
   if ((system_colors_[SystemThemeColor::kScrollbar] !=
        system_colors_[SystemThemeColor::kButtonFace]) &&
       (system_colors_[SystemThemeColor::kScrollbar] !=
        system_colors_[SystemThemeColor::kWindow])) {
-    FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1));
+    FillRect(hdc, rect, reinterpret_cast<HBRUSH>(COLOR_SCROLLBAR + 1));
   } else {
     SkPaint paint;
     RECT align_rect = gfx::Rect(extra.track_x, extra.track_y, extra.track_width,
-                                extra.track_height).ToRECT();
+                                extra.track_height)
+                          .ToRECT();
     SetCheckerboardShader(&paint, align_rect);
-    canvas->drawIRect(skia::RECTToSkIRect(rect_win), paint);
+    canvas->drawIRect(skia::RECTToSkIRect(*rect), paint);
   }
   if (extra.classic_state & DFCS_PUSHED)
-    InvertRect(hdc, &rect_win);
-  return S_OK;
+    InvertRect(hdc, rect);
 }
 
-HRESULT NativeThemeWin::PaintSpinButton(
+void NativeThemeWin::PaintHorizontalTrackbarThumbClassic(
+    SkCanvas* canvas,
     HDC hdc,
-    Part part,
-    State state,
-    const gfx::Rect& rect,
-    const InnerSpinButtonExtraParams& extra) const {
-  HANDLE handle = GetThemeHandle(SPIN);
-  RECT rect_win = rect.ToRECT();
-  int part_id = extra.spin_up ? SPNP_UP : SPNP_DOWN;
-  int state_id = extra.spin_up ? UPS_NORMAL : DNS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = extra.spin_up ? UPS_DISABLED : DNS_DISABLED;
-      break;
-    case kHovered:
-      state_id = extra.spin_up ? UPS_HOT : DNS_HOT;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = extra.spin_up ? UPS_PRESSED : DNS_PRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
+    const RECT& rect,
+    const TrackbarExtraParams& extra) const {
+  // Split rect into top and bottom pieces.
+  RECT top_section = rect;
+  RECT bottom_section = rect;
+  top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2);
+  bottom_section.top = top_section.bottom;
+  DrawEdge(hdc, &top_section, EDGE_RAISED,
+           BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
 
-  if (handle) {
-    return DrawThemeBackground(handle, hdc, part_id, state_id, &rect_win,
-                               nullptr);
-  }
+  // Split triangular piece into two diagonals.
+  RECT& left_half = bottom_section;
+  RECT right_half = bottom_section;
+  right_half.left += ((bottom_section.right - bottom_section.left) / 2);
+  left_half.right = right_half.left;
+  DrawEdge(hdc, &left_half, EDGE_RAISED,
+           BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
+  DrawEdge(hdc, &right_half, EDGE_RAISED,
+           BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
 
-  DrawFrameControl(hdc, &rect_win, DFC_SCROLL, extra.classic_state);
-  return S_OK;
+  // If the button is pressed, draw hatching.
+  if (extra.classic_state & DFCS_PUSHED) {
+    SkPaint paint;
+    SetCheckerboardShader(&paint, rect);
+
+    // Fill all three pieces with the pattern.
+    canvas->drawIRect(skia::RECTToSkIRect(top_section), paint);
+
+    SkScalar left_triangle_top = SkIntToScalar(left_half.top);
+    SkScalar left_triangle_right = SkIntToScalar(left_half.right);
+    SkPath left_triangle;
+    left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top);
+    left_triangle.lineTo(left_triangle_right, left_triangle_top);
+    left_triangle.lineTo(left_triangle_right, SkIntToScalar(left_half.bottom));
+    left_triangle.close();
+    canvas->drawPath(left_triangle, paint);
+
+    SkScalar right_triangle_left = SkIntToScalar(right_half.left);
+    SkScalar right_triangle_top = SkIntToScalar(right_half.top);
+    SkPath right_triangle;
+    right_triangle.moveTo(right_triangle_left, right_triangle_top);
+    right_triangle.lineTo(SkIntToScalar(right_half.right), right_triangle_top);
+    right_triangle.lineTo(right_triangle_left,
+                          SkIntToScalar(right_half.bottom));
+    right_triangle.close();
+    canvas->drawPath(right_triangle, paint);
+  }
 }
 
-HRESULT NativeThemeWin::PaintTrackbar(SkCanvas* canvas,
-                                      HDC hdc,
-                                      Part part,
-                                      State state,
-                                      const gfx::Rect& rect,
-                                      const TrackbarExtraParams& extra) const {
-  const int part_id = extra.vertical ?
-      ((part == kTrackbarTrack) ? TKP_TRACKVERT : TKP_THUMBVERT) :
-      ((part == kTrackbarTrack) ? TKP_TRACK : TKP_THUMBBOTTOM);
-
-  int state_id = TUS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = TUS_DISABLED;
-      break;
-    case kHovered:
-      state_id = TUS_HOT;
-      break;
-    case kNormal:
-      break;
-    case kPressed:
-      state_id = TUS_PRESSED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
-
-  // Make the channel be 4 px thick in the center of the supplied rect.  (4 px
-  // matches what XP does in various menus; GetThemePartSize() doesn't seem to
-  // return good values here.)
-  RECT rect_win = rect.ToRECT();
-  RECT channel_rect = rect.ToRECT();
-  const int channel_thickness = 4;
-  if (part_id == TKP_TRACK) {
-    channel_rect.top +=
-        ((channel_rect.bottom - channel_rect.top - channel_thickness) / 2);
-    channel_rect.bottom = channel_rect.top + channel_thickness;
-  } else if (part_id == TKP_TRACKVERT) {
-    channel_rect.left +=
-        ((channel_rect.right - channel_rect.left - channel_thickness) / 2);
-    channel_rect.right = channel_rect.left + channel_thickness;
-  }  // else this isn't actually a channel, so |channel_rect| == |rect|.
-
-  HANDLE handle = GetThemeHandle(TRACKBAR);
-  if (handle) {
-    return DrawThemeBackground(handle, hdc, part_id, state_id, &channel_rect,
-                               nullptr);
-  }
-
-  // Classic mode, draw it manually.
-  if ((part_id == TKP_TRACK) || (part_id == TKP_TRACKVERT)) {
-    DrawEdge(hdc, &channel_rect, EDGE_SUNKEN, BF_RECT);
-  } else if (part_id == TKP_THUMBVERT) {
-    DrawEdge(hdc, &rect_win, EDGE_RAISED, BF_RECT | BF_SOFT | BF_MIDDLE);
-  } else {
-    // Split rect into top and bottom pieces.
-    RECT top_section = rect.ToRECT();
-    RECT bottom_section = rect.ToRECT();
-    top_section.bottom -= ((bottom_section.right - bottom_section.left) / 2);
-    bottom_section.top = top_section.bottom;
-    DrawEdge(hdc, &top_section, EDGE_RAISED,
-             BF_LEFT | BF_TOP | BF_RIGHT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
-
-    // Split triangular piece into two diagonals.
-    RECT& left_half = bottom_section;
-    RECT right_half = bottom_section;
-    right_half.left += ((bottom_section.right - bottom_section.left) / 2);
-    left_half.right = right_half.left;
-    DrawEdge(hdc, &left_half, EDGE_RAISED,
-             BF_DIAGONAL_ENDTOPLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
-    DrawEdge(hdc, &right_half, EDGE_RAISED,
-             BF_DIAGONAL_ENDBOTTOMLEFT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
-
-    // If the button is pressed, draw hatching.
-    if (extra.classic_state & DFCS_PUSHED) {
-      SkPaint paint;
-      SetCheckerboardShader(&paint, rect_win);
-
-      // Fill all three pieces with the pattern.
-      canvas->drawIRect(skia::RECTToSkIRect(top_section), paint);
-
-      SkScalar left_triangle_top = SkIntToScalar(left_half.top);
-      SkScalar left_triangle_right = SkIntToScalar(left_half.right);
-      SkPath left_triangle;
-      left_triangle.moveTo(SkIntToScalar(left_half.left), left_triangle_top);
-      left_triangle.lineTo(left_triangle_right, left_triangle_top);
-      left_triangle.lineTo(left_triangle_right,
-                           SkIntToScalar(left_half.bottom));
-      left_triangle.close();
-      canvas->drawPath(left_triangle, paint);
-
-      SkScalar right_triangle_left = SkIntToScalar(right_half.left);
-      SkScalar right_triangle_top = SkIntToScalar(right_half.top);
-      SkPath right_triangle;
-      right_triangle.moveTo(right_triangle_left, right_triangle_top);
-      right_triangle.lineTo(SkIntToScalar(right_half.right),
-                            right_triangle_top);
-      right_triangle.lineTo(right_triangle_left,
-                            SkIntToScalar(right_half.bottom));
-      right_triangle.close();
-      canvas->drawPath(right_triangle, paint);
-    }
-  }
-  return S_OK;
-}
-
-HRESULT NativeThemeWin::PaintProgressBar(
+void NativeThemeWin::PaintProgressBarOverlayThemed(
     HDC hdc,
-    const gfx::Rect& rect,
+    HANDLE handle,
+    RECT* bar_rect,
+    RECT* value_rect,
     const ProgressBarExtraParams& extra) const {
   // There is no documentation about the animation speed, frame-rate, nor
   // size of moving overlay of the indeterminate progress bar.
   // So we just observed real-world programs and guessed following parameters.
-  const int kDeterminateOverlayPixelsPerSecond = 300;
-  const int kDeterminateOverlayWidth = 120;
-  const int kIndeterminateOverlayPixelsPerSecond =  175;
-  const int kIndeterminateOverlayWidth = 120;
+  constexpr int kDeterminateOverlayWidth = 120;
+  constexpr int kDeterminateOverlayPixelsPerSecond = 300;
+  constexpr int kIndeterminateOverlayWidth = 120;
+  constexpr int kIndeterminateOverlayPixelsPerSecond = 175;
 
-  RECT bar_rect = rect.ToRECT();
-  RECT value_rect = gfx::Rect(extra.value_rect_x,
-                              extra.value_rect_y,
-                              extra.value_rect_width,
-                              extra.value_rect_height).ToRECT();
-
-  HANDLE handle = GetThemeHandle(PROGRESS);
-  if (!handle) {
-    FillRect(hdc, &bar_rect, GetSysColorBrush(COLOR_BTNFACE));
-    FillRect(hdc, &value_rect, GetSysColorBrush(COLOR_BTNSHADOW));
-    DrawEdge(hdc, &bar_rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
-    return S_OK;
-  }
-
-  DrawThemeBackground(handle, hdc, PP_BAR, 0, &bar_rect, nullptr);
-
-  int bar_width = bar_rect.right - bar_rect.left;
+  int bar_width = bar_rect->right - bar_rect->left;
   if (!extra.determinate) {
     // The glossy overlay for the indeterminate progress bar has a small pause
     // after each animation. We emulate this by adding an invisible margin the
     // animation has to traverse.
     int width_with_margin = bar_width + kIndeterminateOverlayPixelsPerSecond;
     int overlay_width = kIndeterminateOverlayWidth;
-    RECT overlay_rect = bar_rect;
+    RECT overlay_rect = *bar_rect;
     overlay_rect.left += ComputeAnimationProgress(
         width_with_margin, overlay_width, kIndeterminateOverlayPixelsPerSecond,
         extra.animated_seconds);
     overlay_rect.right = overlay_rect.left + overlay_width;
     DrawThemeBackground(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect,
-                        &bar_rect);
-    return S_OK;
+                        bar_rect);
+    return;
   }
 
   // We care about the direction here because PP_CHUNK painting is asymmetric.
   // TODO(morrita): This RTL guess can be wrong.  We should pass in the
   // direction from WebKit.
-  const DTBGOPTS value_draw_options = {
-    sizeof(DTBGOPTS),
-    (bar_rect.right == value_rect.right && bar_rect.left != value_rect.left) ?
-        DTBG_MIRRORDC : 0u,
-    bar_rect
-  };
+  const bool mirror = bar_rect->right == value_rect->right &&
+                      bar_rect->left != value_rect->left;
+  const DTBGOPTS value_draw_options = {sizeof(DTBGOPTS),
+                                       mirror ? DTBG_MIRRORDC : 0u, *bar_rect};
 
   // On Vista or later, the progress bar part has a single-block value part
   // and a glossy effect. The value part has exactly same height as the bar
   // part, so we don't need to shrink the rect.
-  DrawThemeBackgroundEx(handle, hdc, PP_FILL, 0, &value_rect,
+  DrawThemeBackgroundEx(handle, hdc, PP_FILL, 0, value_rect,
                         &value_draw_options);
 
-  RECT overlay_rect = value_rect;
+  RECT overlay_rect = *value_rect;
   overlay_rect.left += ComputeAnimationProgress(
       bar_width, kDeterminateOverlayWidth, kDeterminateOverlayPixelsPerSecond,
       extra.animated_seconds);
   overlay_rect.right = overlay_rect.left + kDeterminateOverlayWidth;
   DrawThemeBackground(handle, hdc, PP_MOVEOVERLAY, 0, &overlay_rect,
-                      &value_rect);
-  return S_OK;
+                      value_rect);
 }
 
-HRESULT NativeThemeWin::PaintWindowResizeGripper(HDC hdc,
-                                                 const gfx::Rect& rect) const {
-  HANDLE handle = GetThemeHandle(STATUS);
-  RECT rect_win = rect.ToRECT();
-  if (handle) {
-    // Paint the status bar gripper.  There doesn't seem to be a standard
-    // gripper in Windows for the space between scrollbars.  This is pretty
-    // close, but it's supposed to be painted over a status bar.
-    return DrawThemeBackground(handle, hdc, SP_GRIPPER, 0, &rect_win, nullptr);
-  }
-
-  // Draw a windows classic scrollbar gripper.
-  DrawFrameControl(hdc, &rect_win, DFC_SCROLL, DFCS_SCROLLSIZEGRIP);
-  return S_OK;
-}
-
-HRESULT NativeThemeWin::PaintTabPanelBackground(HDC hdc,
-                                                const gfx::Rect& rect) const {
-  HANDLE handle = GetThemeHandle(TAB);
-  RECT rect_win = rect.ToRECT();
-  if (handle)
-    return DrawThemeBackground(handle, hdc, TABP_BODY, 0, &rect_win, nullptr);
-
-  // Classic just renders a flat color background.
-  FillRect(hdc, &rect_win, reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1));
-  return S_OK;
-}
-
-HRESULT NativeThemeWin::PaintTextField(
+void NativeThemeWin::PaintTextFieldThemed(
     HDC hdc,
-    Part part,
-    State state,
-    const gfx::Rect& rect,
+    HANDLE handle,
+    HBRUSH bg_brush,
+    int part_id,
+    int state_id,
+    RECT* rect,
     const TextFieldExtraParams& extra) const {
-  int state_id = ETS_NORMAL;
-  switch (state) {
-    case kDisabled:
-      state_id = ETS_DISABLED;
-      break;
-    case kHovered:
-      state_id = ETS_HOT;
-      break;
-    case kNormal:
-      if (extra.is_read_only)
-        state_id = ETS_READONLY;
-      else if (extra.is_focused)
-        state_id = ETS_FOCUSED;
-      break;
-    case kPressed:
-      state_id = ETS_SELECTED;
-      break;
-    case kNumStates:
-      NOTREACHED();
-      break;
-  }
+  static constexpr DTBGOPTS kOmitBorderOptions = {
+      sizeof(DTBGOPTS), DTBG_OMITBORDER, {0, 0, 0, 0}};
+  DrawThemeBackgroundEx(handle, hdc, part_id, state_id, rect,
+                        extra.draw_edges ? nullptr : &kOmitBorderOptions);
 
-  RECT rect_win = rect.ToRECT();
-  return PaintTextField(hdc, EP_EDITTEXT, state_id, extra.classic_state,
-                        &rect_win,
-                        skia::SkColorToCOLORREF(extra.background_color),
-                        extra.fill_content_area, extra.draw_edges);
-}
-
-HRESULT NativeThemeWin::PaintTextField(HDC hdc,
-                                       int part_id,
-                                       int state_id,
-                                       int classic_state,
-                                       RECT* rect,
-                                       COLORREF color,
-                                       bool fill_content_area,
-                                       bool draw_edges) const {
-  // TODO(ojan): http://b/1210017 Figure out how to give the ability to
-  // exclude individual edges from being drawn.
-
-  HANDLE handle = GetThemeHandle(TEXTFIELD);
-  // TODO(mpcomplete): can we detect if the color is specified by the user,
-  // and if not, just use the system color?
-  // CreateSolidBrush() accepts a RGB value but alpha must be 0.
-  base::win::ScopedGDIObject<HBRUSH> bg_brush(CreateSolidBrush(color));
-  if (!handle) {
-    // Draw it manually.
-    if (draw_edges)
-      DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
-
-    if (fill_content_area) {
-      FillRect(hdc, rect, (classic_state & DFCS_INACTIVE)
-                              ? reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1)
-                              : bg_brush.get());
-    }
-    return S_OK;
-  }
-
-  static const DTBGOPTS omit_border_options = {
-    sizeof(DTBGOPTS),
-    DTBG_OMITBORDER,
-    { 0, 0, 0, 0 }
-  };
-  HRESULT hr =
-      DrawThemeBackgroundEx(handle, hdc, part_id, state_id, rect,
-                            draw_edges ? nullptr : &omit_border_options);
-
-  if (fill_content_area) {
+  if (extra.fill_content_area) {
     RECT content_rect;
-    hr = GetThemeBackgroundContentRect(handle, hdc, part_id, state_id, rect,
-                                       &content_rect);
-    FillRect(hdc, &content_rect, bg_brush.get());
+    GetThemeBackgroundContentRect(handle, hdc, part_id, state_id, rect,
+                                  &content_rect);
+    FillRect(hdc, &content_rect, bg_brush);
   }
-  return hr;
 }
 
-HRESULT NativeThemeWin::PaintScaledTheme(HANDLE theme,
-                                         HDC hdc,
-                                         int part_id,
-                                         int state_id,
-                                         const gfx::Rect& rect) const {
+void NativeThemeWin::PaintTextFieldClassic(
+    HDC hdc,
+    HBRUSH bg_brush,
+    RECT* rect,
+    const TextFieldExtraParams& extra) const {
+  if (extra.draw_edges)
+    DrawEdge(hdc, rect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
+
+  if (extra.fill_content_area) {
+    if (extra.classic_state & DFCS_INACTIVE)
+      bg_brush = reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1);
+    FillRect(hdc, rect, bg_brush);
+  }
+}
+
+void NativeThemeWin::PaintScaledTheme(HANDLE theme,
+                                      HDC hdc,
+                                      int part_id,
+                                      int state_id,
+                                      const gfx::Rect& rect) const {
   // Correct the scaling and positioning of sub-components such as scrollbar
   // arrows and thumb grippers in the event that the world transform applies
   // scaling (e.g. in high-DPI mode).
@@ -1553,14 +1143,13 @@
       gfx::Rect scaled_rect = gfx::ScaleToEnclosedRect(rect, scale);
       scaled_rect.Offset(save_transform.eDx, save_transform.eDy);
       RECT bounds = scaled_rect.ToRECT();
-      HRESULT result =
-          DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
+      DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
       SetWorldTransform(hdc, &save_transform);
-      return result;
+      return;
     }
   }
   RECT bounds = rect.ToRECT();
-  return DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
+  DrawThemeBackground(theme, hdc, part_id, state_id, &bounds, nullptr);
 }
 
 // static
@@ -1570,10 +1159,9 @@
     case kPushButton:
     case kRadio:
       return BUTTON;
-    case kInnerSpinButton:
-      return SPIN;
     case kMenuList:
     case kMenuCheck:
+    case kMenuCheckBackground:
     case kMenuPopupArrow:
     case kMenuPopupGutter:
     case kMenuPopupSeparator:
@@ -1584,27 +1172,29 @@
     case kScrollbarLeftArrow:
     case kScrollbarRightArrow:
     case kScrollbarUpArrow:
+    case kScrollbarHorizontalGripper:
+    case kScrollbarVerticalGripper:
     case kScrollbarHorizontalThumb:
     case kScrollbarVerticalThumb:
     case kScrollbarHorizontalTrack:
     case kScrollbarVerticalTrack:
       return SCROLLBAR;
-    case kSliderTrack:
-    case kSliderThumb:
-      return TRACKBAR;
-    case kTextField:
-      return TEXTFIELD;
+    case kInnerSpinButton:
+      return SPIN;
     case kWindowResizeGripper:
       return STATUS;
-    case kMenuCheckBackground:
-    case kMenuPopupBackground:
-    case kMenuItemBackground:
-    case kScrollbarHorizontalGripper:
-    case kScrollbarVerticalGripper:
-    case kScrollbarCorner:
     case kTabPanelBackground:
+      return TAB;
+    case kTextField:
+      return TEXTFIELD;
     case kTrackbarThumb:
     case kTrackbarTrack:
+      return TRACKBAR;
+    case kMenuPopupBackground:
+    case kMenuItemBackground:
+    case kScrollbarCorner:
+    case kSliderTrack:
+    case kSliderThumb:
     case kMaxPart:
       NOTREACHED();
   }
@@ -1618,46 +1208,63 @@
   switch (part) {
     case kCheckbox:
       return BP_CHECKBOX;
-    case kMenuCheck:
-      return MENU_POPUPCHECK;
-    case kMenuPopupArrow:
-      return MENU_POPUPSUBMENU;
-    case kMenuPopupGutter:
-      return MENU_POPUPGUTTER;
-    case kMenuPopupSeparator:
-      return MENU_POPUPSEPARATOR;
     case kPushButton:
       return BP_PUSHBUTTON;
     case kRadio:
       return BP_RADIOBUTTON;
+    case kMenuList:
+      return CP_DROPDOWNBUTTON;
+    case kTextField:
+      return EP_EDITTEXT;
+    case kMenuCheck:
+      return MENU_POPUPCHECK;
+    case kMenuCheckBackground:
+      return MENU_POPUPCHECKBACKGROUND;
+    case kMenuPopupGutter:
+      return MENU_POPUPGUTTER;
+    case kMenuPopupSeparator:
+      return MENU_POPUPSEPARATOR;
+    case kMenuPopupArrow:
+      return MENU_POPUPSUBMENU;
+    case kProgressBar:
+      return PP_BAR;
     case kScrollbarDownArrow:
     case kScrollbarLeftArrow:
     case kScrollbarRightArrow:
     case kScrollbarUpArrow:
       return SBP_ARROWBTN;
+    case kScrollbarHorizontalGripper:
+      return SBP_GRIPPERHORZ;
+    case kScrollbarVerticalGripper:
+      return SBP_GRIPPERVERT;
     case kScrollbarHorizontalThumb:
       return SBP_THUMBBTNHORZ;
     case kScrollbarVerticalThumb:
       return SBP_THUMBBTNVERT;
+    case kScrollbarHorizontalTrack:
+      return extra.scrollbar_track.is_upper ? SBP_UPPERTRACKHORZ
+                                            : SBP_LOWERTRACKHORZ;
+    case kScrollbarVerticalTrack:
+      return extra.scrollbar_track.is_upper ? SBP_UPPERTRACKVERT
+                                            : SBP_LOWERTRACKVERT;
     case kWindowResizeGripper:
+      // Use the status bar gripper.  There doesn't seem to be a standard
+      // gripper in Windows for the space between scrollbars.  This is pretty
+      // close, but it's supposed to be painted over a status bar.
       return SP_GRIPPER;
     case kInnerSpinButton:
-    case kMenuList:
-    case kMenuCheckBackground:
+      return extra.inner_spin.spin_up ? SPNP_UP : SPNP_DOWN;
+    case kTabPanelBackground:
+      return TABP_BODY;
+    case kTrackbarThumb:
+      return extra.trackbar.vertical ? TKP_THUMBVERT : TKP_THUMBBOTTOM;
+    case kTrackbarTrack:
+      return extra.trackbar.vertical ? TKP_TRACKVERT : TKP_TRACK;
     case kMenuPopupBackground:
     case kMenuItemBackground:
-    case kProgressBar:
-    case kScrollbarHorizontalTrack:
-    case kScrollbarVerticalTrack:
-    case kScrollbarHorizontalGripper:
-    case kScrollbarVerticalGripper:
     case kScrollbarCorner:
     case kSliderTrack:
     case kSliderThumb:
-    case kTabPanelBackground:
-    case kTextField:
-    case kTrackbarThumb:
-    case kTrackbarTrack:
     case kMaxPart:
       NOTREACHED();
   }
@@ -1668,84 +1275,13 @@
                                     State state,
                                     const ExtraParams& extra) {
   switch (part) {
-    case kCheckbox:
-      switch (state) {
-        case kDisabled:
-          return CBS_UNCHECKEDDISABLED;
-        case kHovered:
-          return CBS_UNCHECKEDHOT;
-        case kNormal:
-          return CBS_UNCHECKEDNORMAL;
-        case kPressed:
-          return CBS_UNCHECKEDPRESSED;
-        case kNumStates:
-          NOTREACHED();
-          return 0;
-      }
-    case kMenuCheck:
-      switch (state) {
-        case kDisabled:
-          return extra.menu_check.is_radio ?
-              MC_BULLETDISABLED : MC_CHECKMARKDISABLED;
-        case kHovered:
-        case kNormal:
-        case kPressed:
-          return extra.menu_check.is_radio ?
-              MC_BULLETNORMAL : MC_CHECKMARKNORMAL;
-        case kNumStates:
-          NOTREACHED();
-          return 0;
-      }
-    case kMenuPopupArrow:
-    case kMenuPopupGutter:
-    case kMenuPopupSeparator:
-      switch (state) {
-        case kDisabled:
-          return MBI_DISABLED;
-        case kHovered:
-          return MBI_HOT;
-        case kNormal:
-          return MBI_NORMAL;
-        case kPressed:
-          return MBI_PUSHED;
-        case kNumStates:
-          NOTREACHED();
-          return 0;
-      }
-    case kPushButton:
-      switch (state) {
-        case kDisabled:
-          return PBS_DISABLED;
-        case kHovered:
-          return PBS_HOT;
-        case kNormal:
-          return PBS_NORMAL;
-        case kPressed:
-          return PBS_PRESSED;
-        case kNumStates:
-          NOTREACHED();
-          return 0;
-      }
-    case kRadio:
-      switch (state) {
-        case kDisabled:
-          return RBS_UNCHECKEDDISABLED;
-        case kHovered:
-          return RBS_UNCHECKEDHOT;
-        case kNormal:
-          return RBS_UNCHECKEDNORMAL;
-        case kPressed:
-          return RBS_UNCHECKEDPRESSED;
-        case kNumStates:
-          NOTREACHED();
-          return 0;
-      }
     case kScrollbarDownArrow:
       switch (state) {
         case kDisabled:
           return ABS_DOWNDISABLED;
         case kHovered:
-          return ABS_DOWNHOVER;
+          return extra.scrollbar_arrow.is_hovering ? ABS_DOWNHOVER
+                                                   : ABS_DOWNHOT;
         case kNormal:
           return ABS_DOWNNORMAL;
         case kPressed:
@@ -1759,7 +1295,8 @@
         case kDisabled:
           return ABS_LEFTDISABLED;
         case kHovered:
-          return ABS_LEFTHOVER;
+          return extra.scrollbar_arrow.is_hovering ? ABS_LEFTHOVER
+                                                   : ABS_LEFTHOT;
         case kNormal:
           return ABS_LEFTNORMAL;
         case kPressed:
@@ -1773,7 +1310,8 @@
         case kDisabled:
           return ABS_RIGHTDISABLED;
         case kHovered:
-          return ABS_RIGHTHOVER;
+          return extra.scrollbar_arrow.is_hovering ? ABS_RIGHTHOVER
+                                                   : ABS_RIGHTHOT;
         case kNormal:
           return ABS_RIGHTNORMAL;
         case kPressed:
@@ -1782,13 +1320,12 @@
           NOTREACHED();
           return 0;
       }
-      break;
     case kScrollbarUpArrow:
       switch (state) {
         case kDisabled:
           return ABS_UPDISABLED;
         case kHovered:
-          return ABS_UPHOVER;
+          return extra.scrollbar_arrow.is_hovering ? ABS_UPHOVER : ABS_UPHOT;
         case kNormal:
           return ABS_UPNORMAL;
         case kPressed:
@@ -1797,9 +1334,110 @@
           NOTREACHED();
           return 0;
       }
-      break;
+    case kCheckbox: {
+      const ButtonExtraParams& button = extra.button;
+      switch (state) {
+        case kDisabled:
+          return button.checked
+                     ? CBS_CHECKEDDISABLED
+                     : (button.indeterminate ? CBS_MIXEDDISABLED
+                                             : CBS_UNCHECKEDDISABLED);
+        case kHovered:
+          return button.checked
+                     ? CBS_CHECKEDHOT
+                     : (button.indeterminate ? CBS_MIXEDHOT : CBS_UNCHECKEDHOT);
+        case kNormal:
+          return button.checked ? CBS_CHECKEDNORMAL
+                                : (button.indeterminate ? CBS_MIXEDNORMAL
+                                                        : CBS_UNCHECKEDNORMAL);
+        case kPressed:
+          return button.checked ? CBS_CHECKEDPRESSED
+                                : (button.indeterminate ? CBS_MIXEDPRESSED
+                                                        : CBS_UNCHECKEDPRESSED);
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    }
+    case kMenuList:
+      switch (state) {
+        case kDisabled:
+          return CBXS_DISABLED;
+        case kHovered:
+          return CBXS_HOT;
+        case kNormal:
+          return CBXS_NORMAL;
+        case kPressed:
+          return CBXS_PRESSED;
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    case kTextField:
+      switch (state) {
+        case kDisabled:
+          return ETS_DISABLED;
+        case kHovered:
+          return ETS_HOT;
+        case kNormal:
+          if (extra.text_field.is_read_only)
+            return ETS_READONLY;
+          return extra.text_field.is_focused ? ETS_FOCUSED : ETS_NORMAL;
+        case kPressed:
+          return ETS_SELECTED;
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    case kMenuPopupArrow:
+      return (state == kDisabled) ? MSM_DISABLED : MSM_NORMAL;
+    case kMenuCheck:
+      if (state == kDisabled) {
+        return extra.menu_check.is_radio ? MC_BULLETDISABLED
+                                         : MC_CHECKMARKDISABLED;
+      }
+      return extra.menu_check.is_radio ? MC_BULLETNORMAL : MC_CHECKMARKNORMAL;
+    case kMenuCheckBackground:
+      return (state == kDisabled) ? MCB_DISABLED : MCB_NORMAL;
+    case kPushButton:
+      switch (state) {
+        case kDisabled:
+          return PBS_DISABLED;
+        case kHovered:
+          return PBS_HOT;
+        case kNormal:
+          return extra.button.is_default ? PBS_DEFAULTED : PBS_NORMAL;
+        case kPressed:
+          return PBS_PRESSED;
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    case kRadio: {
+      const ButtonExtraParams& button = extra.button;
+      switch (state) {
+        case kDisabled:
+          return button.checked ? RBS_CHECKEDDISABLED : RBS_UNCHECKEDDISABLED;
+        case kHovered:
+          return button.checked ? RBS_CHECKEDHOT : RBS_UNCHECKEDHOT;
+        case kNormal:
+          return button.checked ? RBS_CHECKEDNORMAL : RBS_UNCHECKEDNORMAL;
+        case kPressed:
+          return button.checked ? RBS_CHECKEDPRESSED : RBS_UNCHECKEDPRESSED;
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    }
+    case kScrollbarHorizontalGripper:
+    case kScrollbarVerticalGripper:
     case kScrollbarHorizontalThumb:
     case kScrollbarVerticalThumb:
+      if ((state == kHovered) && !extra.scrollbar_thumb.is_hovering)
+        return SCRBS_HOT;
+      FALLTHROUGH;
+    case kScrollbarHorizontalTrack:
+    case kScrollbarVerticalTrack:
       switch (state) {
         case kDisabled:
           return SCRBS_DISABLED;
@@ -1813,34 +1451,55 @@
           NOTREACHED();
           return 0;
       }
+    case kTrackbarThumb:
+    case kTrackbarTrack:
+      switch (state) {
+        case kDisabled:
+          return TUS_DISABLED;
+        case kHovered:
+          return TUS_HOT;
+        case kNormal:
+          return TUS_NORMAL;
+        case kPressed:
+          return TUS_PRESSED;
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    case kInnerSpinButton:
+      switch (state) {
+        case kDisabled:
+          return extra.inner_spin.spin_up ? UPS_DISABLED : DNS_DISABLED;
+        case kHovered:
+          return extra.inner_spin.spin_up ? UPS_HOT : DNS_HOT;
+        case kNormal:
+          return extra.inner_spin.spin_up ? UPS_NORMAL : DNS_NORMAL;
+        case kPressed:
+          return extra.inner_spin.spin_up ? UPS_PRESSED : DNS_PRESSED;
+        case kNumStates:
+          NOTREACHED();
+          return 0;
+      }
+    case kMenuPopupGutter:
+    case kMenuPopupSeparator:
+    case kProgressBar:
+    case kTabPanelBackground:
     case kWindowResizeGripper:
       switch (state) {
         case kDisabled:
         case kHovered:
         case kNormal:
         case kPressed:
-          return 1;  // gripper has no windows state
+          return 0;
         case kNumStates:
           NOTREACHED();
           return 0;
       }
-    case kInnerSpinButton:
-    case kMenuList:
-    case kMenuCheckBackground:
     case kMenuPopupBackground:
     case kMenuItemBackground:
-    case kProgressBar:
-    case kScrollbarHorizontalTrack:
-    case kScrollbarVerticalTrack:
-    case kScrollbarHorizontalGripper:
-    case kScrollbarVerticalGripper:
     case kScrollbarCorner:
     case kSliderTrack:
     case kSliderThumb:
-    case kTabPanelBackground:
-    case kTextField:
-    case kTrackbarThumb:
-    case kTrackbarTrack:
     case kMaxPart:
       NOTREACHED();
   }
diff --git a/ui/native_theme/native_theme_win.h b/ui/native_theme/native_theme_win.h
index 50481e31..9c2cd88 100644
--- a/ui/native_theme/native_theme_win.h
+++ b/ui/native_theme/native_theme_win.h
@@ -54,15 +54,6 @@
   // for a theme change.
   static void CloseHandles();
 
-  HRESULT PaintTextField(HDC hdc,
-                         int part_id,
-                         int state_id,
-                         int classic_state,
-                         RECT* rect,
-                         COLORREF color,
-                         bool fill_content_area,
-                         bool draw_edges) const;
-
   // NativeTheme implementation:
   gfx::Size GetPartSize(Part part,
                         State state,
@@ -129,115 +120,57 @@
                      const gfx::Rect& rect,
                      const ExtraParams& extra) const;
 
-  HRESULT PaintButton(HDC hdc,
-                      State state,
-                      const ButtonExtraParams& extra,
-                      int part_id,
-                      int state_id,
-                      RECT* rect) const;
-
-  // |arrow_direction| determines whether the arrow is pointing to the left or
-  // to the right. In RTL locales, sub-menus open from right to left and
-  // therefore the menu arrow should point to the left and not to the right.
-  HRESULT PaintMenuArrow(HDC hdc,
-                         State state,
-                         const gfx::Rect& rect,
-                         const MenuArrowExtraParams& extra) const;
-
-  HRESULT PaintMenuCheck(HDC hdc,
-                         State state,
-                         const gfx::Rect& rect,
-                         const MenuCheckExtraParams& extra) const;
-
-  HRESULT PaintMenuCheckBackground(HDC hdc,
-                                   State state,
-                                   const gfx::Rect& rect) const;
-
-  HRESULT PaintPushButton(HDC hdc,
+  // Various helpers to paint specific parts.
+  void PaintButtonClassic(HDC hdc,
                           Part part,
                           State state,
-                          const gfx::Rect& rect,
+                          RECT* rect,
                           const ButtonExtraParams& extra) const;
-
-  HRESULT PaintRadioButton(HDC hdc,
-                           Part part,
-                           State state,
-                           const gfx::Rect& rect,
-                           const ButtonExtraParams& extra) const;
-
-  HRESULT PaintCheckbox(HDC hdc,
-                        Part part,
-                        State state,
-                        const gfx::Rect& rect,
-                        const ButtonExtraParams& extra) const;
-
-  HRESULT PaintMenuList(HDC hdc,
-                        State state,
-                        const gfx::Rect& rect,
-                        const MenuListExtraParams& extra) const;
-
-  // Paints a scrollbar arrow.  |classic_state| should have the appropriate
-  // classic part number ORed in already.
-  HRESULT PaintScrollbarArrow(HDC hdc,
-                              Part part,
-                              State state,
-                              const gfx::Rect& rect,
-                              const ScrollbarArrowExtraParams& extra) const;
-
-  HRESULT PaintScrollbarThumb(HDC hdc,
-                              Part part,
-                              State state,
-                              const gfx::Rect& rect,
-                              const ScrollbarThumbExtraParams& extra) const;
-
-  // This method is deprecated and will be removed in the near future.
-  // Paints a scrollbar track section.  |align_rect| is only used in classic
-  // mode, and makes sure the checkerboard pattern in |target_rect| is aligned
-  // with one presumed to be in |align_rect|.
-  HRESULT PaintScrollbarTrack(SkCanvas* canvas,
-                              HDC hdc,
-                              Part part,
-                              State state,
-                              const gfx::Rect& rect,
-                              const ScrollbarTrackExtraParams& extra) const;
-
-  HRESULT PaintSpinButton(HDC hdc,
-                          Part part,
-                          State state,
-                          const gfx::Rect& rect,
-                          const InnerSpinButtonExtraParams& extra) const;
-
-  HRESULT PaintTrackbar(SkCanvas* canvas,
-                        HDC hdc,
-                        Part part,
-                        State state,
-                        const gfx::Rect& rect,
-                        const TrackbarExtraParams& extra) const;
-
-  HRESULT PaintProgressBar(HDC hdc,
-                           const gfx::Rect& rect,
-                           const ProgressBarExtraParams& extra) const;
-
-  HRESULT PaintWindowResizeGripper(HDC hdc, const gfx::Rect& rect) const;
-
-  HRESULT PaintTabPanelBackground(HDC hdc, const gfx::Rect& rect) const;
-
-  HRESULT PaintTextField(HDC hdc,
-                         Part part,
-                         State state,
-                         const gfx::Rect& rect,
-                         const TextFieldExtraParams& extra) const;
+  void PaintLeftMenuArrowThemed(HDC hdc,
+                                HANDLE handle,
+                                int part_id,
+                                int state_id,
+                                const gfx::Rect& rect) const;
+  void PaintScrollbarArrowClassic(HDC hdc,
+                                  Part part,
+                                  State state,
+                                  RECT* rect) const;
+  void PaintScrollbarTrackClassic(SkCanvas* canvas,
+                                  HDC hdc,
+                                  RECT* rect,
+                                  const ScrollbarTrackExtraParams& extra) const;
+  void PaintHorizontalTrackbarThumbClassic(
+      SkCanvas* canvas,
+      HDC hdc,
+      const RECT& rect,
+      const TrackbarExtraParams& extra) const;
+  void PaintProgressBarOverlayThemed(HDC hdc,
+                                     HANDLE handle,
+                                     RECT* bar_rect,
+                                     RECT* value_rect,
+                                     const ProgressBarExtraParams& extra) const;
+  void PaintTextFieldThemed(HDC hdc,
+                            HANDLE handle,
+                            HBRUSH bg_brush,
+                            int part_id,
+                            int state_id,
+                            RECT* rect,
+                            const TextFieldExtraParams& extra) const;
+  void PaintTextFieldClassic(HDC hdc,
+                             HBRUSH bg_brush,
+                             RECT* rect,
+                             const TextFieldExtraParams& extra) const;
 
   // Paints a theme part, with support for scene scaling in high-DPI mode.
   // |theme| is the theme handle. |hdc| is the handle for the device context.
   // |part_id| is the identifier for the part (e.g. thumb gripper). |state_id|
   // is the identifier for the rendering state of the part (e.g. hover). |rect|
   // is the bounds for rendering, expressed in logical coordinates.
-  HRESULT PaintScaledTheme(HANDLE theme,
-                           HDC hdc,
-                           int part_id,
-                           int state_id,
-                           const gfx::Rect& rect) const;
+  void PaintScaledTheme(HANDLE theme,
+                        HDC hdc,
+                        int part_id,
+                        int state_id,
+                        const gfx::Rect& rect) const;
 
   // Get the windows theme name/part/state.  These three helper functions are
   // used only by GetPartSize(), as each of the corresponding PaintXXX()
diff --git a/ui/views/controls/native/native_view_host_mac_unittest.mm b/ui/views/controls/native/native_view_host_mac_unittest.mm
index 56d26ce2..5543803 100644
--- a/ui/views/controls/native/native_view_host_mac_unittest.mm
+++ b/ui/views/controls/native/native_view_host_mac_unittest.mm
@@ -8,7 +8,6 @@
 
 #include <memory>
 
-#import "base/mac/scoped_nsautorelease_pool.h"
 #import "base/mac/scoped_nsobject.h"
 #include "base/macros.h"
 #import "testing/gtest_mac.h"
@@ -220,8 +219,7 @@
 // Check that we can destroy cleanly even if the native view has already been
 // released.
 TEST_F(NativeViewHostMacTest, NativeViewReleased) {
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
     CreateHost();
     // In practice the native view is a WebContentsViewCocoa which is retained
     // by its superview (a TabContentsContainerView) and by WebContentsViewMac.
diff --git a/ui/views/test/views_test_helper_mac.mm b/ui/views/test/views_test_helper_mac.mm
index 9ae53aeb..6493262 100644
--- a/ui/views/test/views_test_helper_mac.mm
+++ b/ui/views/test/views_test_helper_mac.mm
@@ -7,7 +7,6 @@
 #import <Cocoa/Cocoa.h>
 
 #include "base/bind.h"
-#import "base/mac/scoped_nsautorelease_pool.h"
 #include "ui/base/test/scoped_fake_full_keyboard_access.h"
 #include "ui/base/test/scoped_fake_nswindow_focus.h"
 #include "ui/base/test/scoped_fake_nswindow_fullscreen.h"
@@ -63,13 +62,14 @@
   // this is handled automatically by views::Widget::CloseAllSecondaryWidgets().
   // Unit tests on Aura may create Widgets owned by a RootWindow that gets torn
   // down, but on Mac we need to be more explicit.
-  base::mac::ScopedNSAutoreleasePool pool;  // Ensure the NSArray is released.
-  NSArray* native_windows = [NSApp windows];
-  for (NSWindow* window : native_windows)
-    DCHECK(!Widget::GetWidgetForNativeWindow(window)) << "Widget not closed.";
+  @autoreleasepool {
+    NSArray* native_windows = [NSApp windows];
+    for (NSWindow* window : native_windows)
+      DCHECK(!Widget::GetWidgetForNativeWindow(window)) << "Widget not closed.";
 
-  ui::test::EventGeneratorDelegate::SetFactoryFunction(
-      ui::test::EventGeneratorDelegate::FactoryFunction());
+    ui::test::EventGeneratorDelegate::SetFactoryFunction(
+        ui::test::EventGeneratorDelegate::FactoryFunction());
+  }
 }
 
 }  // namespace views
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index d552790..82df06d7 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -10,7 +10,6 @@
 #include "base/callback.h"
 #import "base/mac/foundation_util.h"
 #include "base/mac/mac_util.h"
-#import "base/mac/scoped_nsautorelease_pool.h"
 #import "base/mac/scoped_nsobject.h"
 #import "base/mac/scoped_objc_class_swizzler.h"
 #include "base/macros.h"
@@ -817,10 +816,9 @@
 TEST_F(NativeWidgetMacTest, CloseAllSecondaryWidgetsValidState) {
   NativeWidgetMacTestWindow* last_window = nil;
   bool window_deallocated = false;
-  {
+  @autoreleasepool {
     // First verify the behavior of CloseAllSecondaryWidgets in the normal case,
     // and how [NSApp windows] changes in response to Widget closure.
-    base::mac::ScopedNSAutoreleasePool pool;
     Widget* widget = CreateWidgetWithTestWindow(
         Widget::InitParams(Widget::InitParams::TYPE_WINDOW), &last_window);
     last_window.deallocFlag = &window_deallocated;
@@ -833,10 +831,9 @@
   EXPECT_TRUE(window_deallocated);
   window_deallocated = false;
 
-  {
+  @autoreleasepool {
     // Repeat, but now retain a reference and close the window before
     // CloseAllSecondaryWidgets().
-    base::mac::ScopedNSAutoreleasePool pool;
     Widget* widget = CreateWidgetWithTestWindow(
         Widget::InitParams(Widget::InitParams::TYPE_WINDOW), &last_window);
     last_window.deallocFlag = &window_deallocated;
@@ -847,8 +844,7 @@
   }
 
   EXPECT_FALSE(window_deallocated);
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
     Widget::CloseAllSecondaryWidgets();
     [last_window release];
   }
@@ -880,8 +876,7 @@
   bool child_dealloced = false;
   bool native_parent_dealloced = false;
   NativeWidgetMacTestWindow* native_parent = nil;
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
     native_parent = MakeBorderlessNativeParent();
     [native_parent setDeallocFlag:&native_parent_dealloced];
 
@@ -893,13 +888,12 @@
     CreateWidgetWithTestWindow(std::move(init_params), &window);
     [window setDeallocFlag:&child_dealloced];
   }
-  {
+  @autoreleasepool {
     // On 10.11, closing a weak reference on the parent window works, but older
     // versions of AppKit get upset if things are released inside -[NSWindow
     // close]. This test tries to establish a situation where the last reference
     // to the child window is released inside WidgetOwnerNSWindowAdapter::
     // OnWindowWillClose().
-    base::mac::ScopedNSAutoreleasePool pool;
     [native_parent close];
     EXPECT_TRUE(child_dealloced);
   }
@@ -1511,8 +1505,7 @@
 
   // Test another hypothetical: What if -sheetDidEnd: was invoked somehow
   // without going through [NSApp endSheet:] or -[NSWindow endSheet:].
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
     Widget* sheet_widget = ShowWindowModalWidget(native_parent);
     NSWindow* sheet_window =
         sheet_widget->GetNativeWindow().GetNativeNSWindow();
@@ -1548,8 +1541,7 @@
 // https://crbug.com/851376.
 TEST_F(NativeWidgetMacTest, CloseWindowModalSheetWithoutSheetParent) {
   NSWindow* native_parent = MakeClosableTitledNativeParent();
-  {
-    base::mac::ScopedNSAutoreleasePool pool;
+  @autoreleasepool {
     Widget* sheet_widget = ShowWindowModalWidget(native_parent);
     NSWindow* sheet_window =
         sheet_widget->GetNativeWindow().GetNativeNSWindow();
diff --git a/ui/webui/resources/cr_elements/cr_lazy_render/BUILD.gn b/ui/webui/resources/cr_elements/cr_lazy_render/BUILD.gn
index e6815657..b79b032 100644
--- a/ui/webui/resources/cr_elements/cr_lazy_render/BUILD.gn
+++ b/ui/webui/resources/cr_elements/cr_lazy_render/BUILD.gn
@@ -11,4 +11,5 @@
 }
 
 js_library("cr_lazy_render") {
+  externs_list = [ "$externs_path/pending.js" ]
 }
diff --git a/ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.js b/ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.js
index 6c7df6f7..05c0d1b06 100644
--- a/ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.js
+++ b/ui/webui/resources/cr_elements/cr_lazy_render/cr_lazy_render.js
@@ -14,16 +14,11 @@
  *   </cr-lazy-render>
  *
  *   this.$.menu.get().show();
- *
- * TODO(calamity): Use Polymer.Templatize instead of inheriting the
- * Polymer.Templatizer behavior once Polymer 2.0 is available.
  */
 
 Polymer({
   is: 'cr-lazy-render',
 
-  behaviors: [Polymer.Templatizer],
-
   /** @private {?Element} */
   child_: null,
 
@@ -51,41 +46,21 @@
 
   /** @private */
   render_: function() {
-    const template = this.getContentChildren()[0];
-    if (!this.ctor) {
-      this.templatize(template);
-    }
+    const template =
+        /** @type {!HTMLTemplateElement} */ (this.getContentChildren()[0]);
+    const TemplateClass = Polymer.Templatize.templatize(template, this, {
+      mutableData: false,
+      forwardHostProp: this._forwardHostPropV2,
+    });
     const parentNode = this.parentNode;
     if (parentNode && !this.child_) {
-      this.instance_ = this.stamp({});
+      this.instance_ = new TemplateClass({});
       this.child_ = this.instance_.root.firstElementChild;
       parentNode.insertBefore(this.instance_.root, this);
     }
   },
 
   /**
-   * TODO(dpapad): Delete this method once migration to Polymer 2 has finished.
-   * @param {string} prop
-   * @param {Object} value
-   */
-  _forwardParentProp: function(prop, value) {
-    if (this.child_) {
-      this.child_._templateInstance[prop] = value;
-    }
-  },
-
-  /**
-   * TODO(dpapad): Delete this method once migration to Polymer 2 has finished.
-   * @param {string} path
-   * @param {Object} value
-   */
-  _forwardParentPath: function(path, value) {
-    if (this.child_) {
-      this.child_._templateInstance.notifyPath(path, value, true);
-    }
-  },
-
-  /**
    * @param {string} prop
    * @param {Object} value
    */
diff --git a/weblayer/BUILD.gn b/weblayer/BUILD.gn
index fd63200..b42f6d8 100644
--- a/weblayer/BUILD.gn
+++ b/weblayer/BUILD.gn
@@ -135,6 +135,8 @@
       "//weblayer/browser/java:jni",
     ]
     sources += [
+      "app/jni_onload.cc",
+      "app/jni_onload.h",
       "browser/browser_observer_proxy.cc",
       "browser/browser_observer_proxy.h",
     ]
@@ -160,7 +162,7 @@
 if (is_android) {
   shared_library("libweblayer") {
     sources = [
-      "app/jni_onload.cc",
+      "app/entry_point.cc",
     ]
     deps = [
       ":weblayer_lib",
diff --git a/weblayer/app/DEPS b/weblayer/app/DEPS
index 566fd69..30479e1 100644
--- a/weblayer/app/DEPS
+++ b/weblayer/app/DEPS
@@ -1,4 +1,5 @@
 include_rules = [
+  "+components/version_info",
   "+content/public",
   "+sandbox",
   "+ui/base",
diff --git a/weblayer/app/entry_point.cc b/weblayer/app/entry_point.cc
new file mode 100644
index 0000000..2b8535d
--- /dev/null
+++ b/weblayer/app/entry_point.cc
@@ -0,0 +1,21 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/android/jni_android.h"
+#include "base/android/library_loader/library_loader_hooks.h"
+#include "weblayer/app/jni_onload.h"
+
+namespace {
+
+bool NativeInit(base::android::LibraryProcessType) {
+  return weblayer::OnJNIOnLoadInit();
+}
+
+}  // namespace
+
+JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
+  base::android::InitVM(vm);
+  base::android::SetNativeInitializationHook(&NativeInit);
+  return JNI_VERSION_1_4;
+}
diff --git a/weblayer/app/jni_onload.cc b/weblayer/app/jni_onload.cc
index ee531e6f..737b0bf 100644
--- a/weblayer/app/jni_onload.cc
+++ b/weblayer/app/jni_onload.cc
@@ -2,31 +2,34 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "base/android/jni_android.h"
+#include "base/android/library_loader/library_loader_hooks.h"
 #include "base/logging.h"
+#include "components/version_info/version_info_values.h"
 #include "content/public/app/content_jni_onload.h"
 #include "content/public/app/content_main.h"
 #include "weblayer/app/content_main_delegate_impl.h"
 
 namespace weblayer {
+
 class MainDelegateImpl : public MainDelegate {
  public:
   void PreMainMessageLoopRun() override {}
   void SetMainMessageLoopQuitClosure(base::OnceClosure quit_closure) override {}
 };
-}  // namespace weblayer
 
 // This is called by the VM when the shared library is first loaded.
-JNI_EXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
-  base::android::InitVM(vm);
+bool OnJNIOnLoadInit() {
   if (!content::android::OnJNIOnLoadInit())
-    return -1;
+    return false;
   weblayer::MainParams params;
   params.delegate = new weblayer::MainDelegateImpl;
   params.pak_name = "weblayer_support.pak";
   params.brand = "WebLayer";
 
+  base::android::SetVersionNumber(PRODUCT_VERSION);
   content::SetContentMainDelegate(
       new weblayer::ContentMainDelegateImpl(params));
-  return JNI_VERSION_1_4;
+  return true;
 }
+
+}  // namespace weblayer
diff --git a/weblayer/app/jni_onload.h b/weblayer/app/jni_onload.h
new file mode 100644
index 0000000..515ea7a7f
--- /dev/null
+++ b/weblayer/app/jni_onload.h
@@ -0,0 +1,14 @@
+// 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 WEBLAYER_APP_JNI_ONLOAD_H_
+#define WEBLAYER_APP_JNI_ONLOAD_H_
+
+namespace weblayer {
+
+bool OnJNIOnLoadInit();
+
+}  // namespace weblayer
+
+#endif  // WEBLAYER_APP_JNI_ONLOAD_H_
diff --git a/weblayer/shell/android/BUILD.gn b/weblayer/shell/android/BUILD.gn
index c425264..3aea576 100644
--- a/weblayer/shell/android/BUILD.gn
+++ b/weblayer/shell/android/BUILD.gn
@@ -54,6 +54,9 @@
   min_sdk_version = 21
   target_sdk_version = 28
   android_manifest_dep = ":weblayer_shell_manifest"
+
+  # Add a native lib so the ABI is compatible with the implementation APK.
+  native_lib_placeholders = [ "libdummy.so" ]
 }
 
 weblayer_support_manifest =
@@ -105,6 +108,12 @@
   min_sdk_version = 21
   target_sdk_version = 28
   android_manifest_dep = ":weblayer_support_manifest"
+
+  native_lib_version_rule = "//build/util:chrome_version_json"
+  _native_lib_file =
+      rebase_path("$root_gen_dir/CHROME_VERSION.json", root_build_dir)
+  native_lib_version_arg = "@FileArg($_native_lib_file:full-quoted)"
+
   shared_libraries = [ "//weblayer:libweblayer" ]
 }
 
diff --git a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
index bb53378..ade4fbe 100644
--- a/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
+++ b/weblayer/shell/android/shell_apk/src/org/chromium/weblayer/shell/WebLayerShellActivity.java
@@ -66,6 +66,7 @@
         mUrlView = new EditText(this);
         mUrlView.setSelectAllOnFocus(true);
         mUrlView.setInputType(InputType.TYPE_TEXT_VARIATION_URI);
+        mUrlView.setImeOptions(EditorInfo.IME_ACTION_GO);
         mUrlView.setOnEditorActionListener(new OnEditorActionListener() {
             @Override
             public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
@@ -109,6 +110,17 @@
     }
 
     private void loadUrl(String url) {
-        mBrowserController.getNavigationController().navigate(Uri.parse(url));
+        mBrowserController.getNavigationController().navigate(Uri.parse(sanitizeUrl(url)));
+    }
+
+    /**
+     * Given an URL, this performs minimal sanitizing to ensure it will be valid.
+     * @param url The url to be sanitized.
+     * @return The sanitized URL.
+     */
+    public static String sanitizeUrl(String url) {
+        if (url == null) return null;
+        if (url.startsWith("www.") || url.indexOf(":") == -1) url = "http://" + url;
+        return url;
     }
 }