diff --git a/.gitignore b/.gitignore
index 3b38a0f..7bcfd2ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@
 .cipd
 .clang-coverage
 .classpath
+.code-coverage
 .cproject
 .gdb_history
 .gdbinit
diff --git a/DEPS b/DEPS
index eac50e7b..5607bfd3 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': 'c30f1a936d84d476bb08bd9d0a6e56eddb6fe984',
+  'skia_revision': 'f433336585ed83606f45520699241c4df0934d9f',
   # 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': '32d6006bf2ef9ea1cc5a705df3493ed7cca16821',
+  'angle_revision': 'a7ff7df26f281e956b59fad52f0408c3528648ed',
   # 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': '374b99bd6607afd7fa200a7f205d3cc937329d32',
+  'swiftshader_revision': '44e6523d7ff3e77d82b55e09fb2ef169839b9200',
   # 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': 'fd9b7e654b0586ffc67748b81ee6eff0fc5d3957',
+  'pdfium_revision': 'eb590e0e22e9119779befd7d5d6763b0dac91119',
   # 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': '19f3c21a61d3895dd16d836b6856c77ddedb4480',
+  'catapult_revision': 'fcd6915ca2835406610c37f80f312a514e4ce403',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling libFuzzer
   # and whatever else without interference from each other.
@@ -241,7 +241,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.
-  'feed_revision': '760bb171ed66f8385aa3720d90ca532ae51354cc',
+  'feed_revision': '8b5b413652b197f591d4d9ec03e366225954e868',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling android_sdk_build-tools_version
   # and whatever else without interference from each other.
@@ -301,7 +301,7 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling feed
   # and whatever else without interference from each other.
-  'quiche_revision': '43652ca1735ad8b913dd37de2ae0f5d69789c116',
+  'quiche_revision': 'b2486b8ca513974c0df2479d07078e80cd8481d1',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling ios_webkit
   # and whatever else without interference from each other.
@@ -867,7 +867,7 @@
   },
 
   'src/third_party/depot_tools':
-    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + '4ebfe4643bf06794bbf2620e4d2d8dda21f62987',
+    Var('chromium_git') + '/chromium/tools/depot_tools.git' + '@' + 'efce0d1b7657c440c90f0f4bce614b96672b9e0b',
 
   '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' + '@' + '9bf9b907280b4b10447bed4725bc604a363ded6b',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' + '57774b7f079d9b1409dd9d1019944ad1dfa01529',
 
   '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' + '@' + 'b64d65e67bff3c9a0acdc56d9398c5aad49d3117',
+    Var('webrtc_git') + '/src.git' + '@' + 'be2e5f78b3498858cd8694fa6d00a28ba8093f26',
 
   '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@b6cad5f3d3380916d65eb4ea66e26559b264ccc1',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@83d6b266312b51217ce7d43e30b8fdf89dcc61a3',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/WATCHLISTS b/WATCHLISTS
index aea137a5..25994345 100644
--- a/WATCHLISTS
+++ b/WATCHLISTS
@@ -2426,7 +2426,7 @@
     'metrics': ['asvitkine+watch@chromium.org'],
     'metrics_xml_files': ['asvitkine+watchxml@chromium.org'],
     'midi': ['toyoshim+midi@chromium.org'],
-    'mojo': ['darin@chromium.org'],
+    'mojo': ['darin+watch@chromium.org'],
     'multidevice': ['hansberry+watch-multidevice@chromium.org',
                     'hsuregan+watch-multidevice@chromium.org',
                     'jhawkins+watch-multidevice@chromium.org',
@@ -2670,7 +2670,8 @@
     'webauthn': ['webauthn-reviews@chromium.org'],
     'webgpu': ['cwallez+watch@chromium.org',
                'kainino+watch@chromium.org'],
-    'weblayer': ['cricke+watch@chromium.org'],
+    'weblayer': ['cricke+watch@chromium.org',
+                 'darin+watch@chromium.org'],
     'webrtc_browser_tests': ['phoglund+watch@chromium.org'],
     'website_settings': ['dullweber+watch@chromium.org',
                          'msramek+watch@chromium.org'],
diff --git a/android_webview/browser/aw_metrics_service_client.cc b/android_webview/browser/aw_metrics_service_client.cc
index 8a6ad92..fe9cee8 100644
--- a/android_webview/browser/aw_metrics_service_client.cc
+++ b/android_webview/browser/aw_metrics_service_client.cc
@@ -40,6 +40,19 @@
 
 namespace {
 
+// IMPORTANT: DO NOT CHANGE sample rates without first ensuring the Chrome
+// Metrics team has the appropriate backend bandwidth and storage.
+
+// Sample at 2%, based on storage concerns. We sample at a different rate than
+// Chrome because we have more metrics "clients" (each app on the device counts
+// as a separate client).
+const double kStableSampledInRate = 0.02;
+
+// Sample non-stable channels also at 2%. We intend to raise this to 99% in the
+// future (for consistency with Chrome and to exercise the out-of-sample code
+// path).
+const double kBetaDevCanarySampledInRate = 0.02;
+
 // Callbacks for metrics::MetricsStateManager::Create. Store/LoadClientInfo
 // allow Windows Chrome to back up ClientInfo. They're no-ops for WebView.
 
@@ -50,10 +63,9 @@
   return client_info;
 }
 
-// WebView metrics are sampled at 2%, based on the client ID. Since including
-// app package names in WebView's metrics, as a matter of policy, the sample
-// rate must not exceed 10%. Sampling is hard-coded (rather than controlled via
-// variations, as in Chrome) because:
+// WebView metrics are sampled at (possibly) different rates depending on
+// channel, based on the client ID. Sampling is hard-coded (rather than
+// controlled via variations, as in Chrome) because:
 // - WebView is slow to download the variations seed and propagate it to each
 //   app, so we'd miss metrics from the first few runs of each app.
 // - WebView uses the low-entropy source for all studies, so there would be
@@ -61,6 +73,16 @@
 bool IsInSample(const std::string& client_id) {
   DCHECK(!client_id.empty());
 
+  double sampled_in_rate = kBetaDevCanarySampledInRate;
+
+  // Down-sample unknown channel as a precaution in case it ends up being
+  // shipped to Stable users.
+  version_info::Channel channel = version_info::android::GetChannel();
+  if (channel == version_info::Channel::STABLE ||
+      channel == version_info::Channel::UNKNOWN) {
+    sampled_in_rate = kStableSampledInRate;
+  }
+
   // client_id comes from base::GenerateGUID(), so its value is random/uniform,
   // except for a few bit positions with fixed values, and some hyphens. Rather
   // than separating the random payload from the fixed bits, just hash the whole
@@ -68,8 +90,13 @@
   uint32_t hash = base::PersistentHash(client_id);
 
   // Since hashing is ~uniform, the chance that the value falls in the bottom
-  // 2% (1/50th) of possible values is 2%.
-  return hash < UINT32_MAX / 50u;
+  // X% of possible values is X%. UINT32_MAX fits within the range of integers
+  // that can be expressed precisely by a 64-bit double. Casting back to a
+  // uint32_t means the effective sample rate is within a 1/UINT32_MAX error
+  // margin.
+  uint32_t sampled_in_threshold =
+      static_cast<uint32_t>(static_cast<double>(UINT32_MAX) * sampled_in_rate);
+  return hash < sampled_in_threshold;
 }
 
 std::unique_ptr<metrics::MetricsService> CreateMetricsService(
diff --git a/android_webview/browser/gfx/surfaces_instance.cc b/android_webview/browser/gfx/surfaces_instance.cc
index c6062ef..fd84a79 100644
--- a/android_webview/browser/gfx/surfaces_instance.cc
+++ b/android_webview/browser/gfx/surfaces_instance.cc
@@ -124,6 +124,13 @@
           share_group_, gl_surface_, std::move(gl_context),
           false /* use_virtualized_gl_contexts */,
           base::BindOnce(&OnContextLost), vulkan_context_provider.get());
+      if (!enable_vulkan) {
+        auto feature_info = base::MakeRefCounted<gpu::gles2::FeatureInfo>(
+            workarounds, GpuServiceWebView::GetInstance()->gpu_feature_info());
+        shared_context_state_->InitializeGL(
+            GpuServiceWebView::GetInstance()->gpu_preferences(),
+            std::move(feature_info));
+      }
       shared_context_state_->InitializeGrContext(workarounds,
                                                  nullptr /* gr_shader_cache */);
     }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
index a354c27d..a928f69 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwContentsTest.java
@@ -779,10 +779,7 @@
         Assert.assertEquals(0, consoleHelper.getMessages().size());
     }
 
-    @Test
-    @Feature({"AndroidWebView"})
-    @SmallTest
-    public void testHardwareRenderingSmokeTest() throws Throwable {
+    private void doHardwareRenderingSmokeTest() throws Throwable {
         AwTestContainerView testView =
                 mActivityTestRule.createAwTestContainerViewOnMainSync(mContentsClient);
         final AwContents awContents = testView.getAwContents();
@@ -844,6 +841,21 @@
     @Test
     @Feature({"AndroidWebView"})
     @SmallTest
+    public void testHardwareRenderingSmokeTest() throws Throwable {
+        doHardwareRenderingSmokeTest();
+    }
+
+    @Test
+    @Feature({"AndroidWebView"})
+    @SmallTest
+    @CommandLineFlags.Add({"enable-features=UseSkiaRenderer", "disable-oop-rasterization"})
+    public void testHardwareRenderingSmokeTestSkiaRenderer() throws Throwable {
+        doHardwareRenderingSmokeTest();
+    }
+
+    @Test
+    @Feature({"AndroidWebView"})
+    @SmallTest
     public void testFixupOctothorpesInLoadDataContent() {
         // If there are no octothorpes the function should have no effect.
         final String noOctothorpeString = "<div id='foo1'>This content has no octothorpe</div>";
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 2f89bc0..1b9654f 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
@@ -4,6 +4,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
+    android:id="@+id/rootLayout"
     android:weightSum="1">
   <WebView
       android:layout_width="match_parent"
@@ -13,44 +14,4 @@
       android:layout_alignParentStart="true"
       android:layout_marginStart="0dp"
       />
-  <LinearLayout
-      android:orientation="vertical"
-      android:visibility="invisible"
-      android:background="@android:color/black"
-      android:layout_width="match_parent"
-      android:layout_height="match_parent"
-      android:id="@+id/childLayout"
-      android:weightSum="1">
-    <LinearLayout
-        android:orientation="horizontal"
-        android:layout_width="match_parent"
-        android:padding="0dp"
-        android:layout_height="wrap_content"
-        android:layout_marginStart="0dp"
-        android:layout_marginTop="0dp"
-        android:id="@+id/childTopControls">
-      <Button
-          android:text="@string/close_button"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_margin="5dp"
-          android:padding="0dp"
-          android:paddingTop="0dp"
-          android:id="@+id/childCloseButton"/>
-      <TextView
-          android:id="@+id/childTitleText"
-          android:layout_marginTop="5dp"
-          android:layout_marginBottom="5dp"
-          android:layout_marginStart="0dp"
-          android:layout_marginEnd="65dp"
-          android:layout_width="match_parent"
-          android:layout_height="30dp"
-          android:gravity="center"
-          android:scrollHorizontally="true"
-          android:ellipsize="end"
-          android:maxLines="1"
-          android:textStyle="bold"
-          />
-    </LinearLayout>
-  </LinearLayout>
 </RelativeLayout>
diff --git a/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests_child.xml b/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests_child.xml
new file mode 100644
index 0000000..5fa8e8c
--- /dev/null
+++ b/android_webview/tools/system_webview_shell/apk/res/layout/activity_web_platform_tests_child.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="UselessParent"
+    android:orientation="vertical"
+    android:visibility="invisible"
+    android:background="@android:color/black"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/childLayout">
+  <LinearLayout
+      android:orientation="horizontal"
+      android:layout_width="match_parent"
+      android:padding="0dp"
+      android:layout_height="wrap_content"
+      android:layout_marginStart="0dp"
+      android:layout_marginTop="0dp"
+      android:id="@+id/childTopControls">
+    <Button
+        android:text="@string/close_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="5dp"
+        android:padding="0dp"
+        android:paddingTop="0dp"
+        android:id="@+id/childCloseButton"/>
+    <TextView
+        android:id="@+id/childTitleText"
+        android:layout_marginTop="5dp"
+        android:layout_marginBottom="5dp"
+        android:layout_marginStart="0dp"
+        android:layout_marginEnd="65dp"
+        android:layout_width="match_parent"
+        android:layout_height="30dp"
+        android:gravity="center"
+        android:scrollHorizontally="true"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textStyle="bold"
+        android:textColor="@android:color/white"
+        />
+  </LinearLayout>
+</LinearLayout>
\ No newline at end of file
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 8063a42..bc2f630d 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
@@ -5,8 +5,10 @@
 package org.chromium.webview_shell;
 
 import android.app.Activity;
+import android.content.Context;
 import android.os.Bundle;
 import android.os.Message;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.webkit.WebChromeClient;
@@ -15,6 +17,7 @@
 import android.webkit.WebViewClient;
 import android.widget.Button;
 import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import org.chromium.base.Log;
@@ -44,10 +47,11 @@
         void onChildLayoutInvisible();
     }
 
+    private LayoutInflater mLayoutInflater;
+    private RelativeLayout mRootLayout;
     private WebView mWebView;
     private WebView mChildWebView;
     private LinearLayout mChildLayout;
-    private Button mChildCloseButton;
     private TextView mChildTitleText;
     private TestCallback mTestCallback;
 
@@ -56,7 +60,8 @@
         public boolean onCreateWindow(
                 WebView webView, boolean isDialog, boolean isUserGesture, Message resultMsg) {
             removeAndDestroyChildWebView();
-            // Now create a new WebView
+            // Note that WebView is not inflated but created programmatically
+            // such that it can be destroyed separately.
             mChildWebView = new WebView(WebPlatformTestsActivity.this);
             WebSettings settings = mChildWebView.getSettings();
             setUpWebSettings(settings);
@@ -114,34 +119,36 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         WebView.setWebContentsDebuggingEnabled(true);
+        mLayoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         setContentView(R.layout.activity_web_platform_tests);
-        setUpWidgets();
+        mRootLayout = findViewById(R.id.rootLayout);
+        mWebView = findViewById(R.id.webview);
+        mChildLayout = createChildLayout();
+
         String url = getUrlFromIntent();
         if (url == null) {
             // This is equivalent to Chrome's WPT setup.
-            setUpWebView("about:blank");
+            setUpMainWebView("about:blank");
         } else {
             Log.w(TAG,
                     "Handling a non-empty intent. This should only be used for testing. URL: "
                             + url);
-            setUpWebView(url);
+            setUpMainWebView(url);
         }
     }
 
-    private void setUpWidgets() {
-        mChildLayout = findViewById(R.id.childLayout);
-        mChildTitleText = findViewById(R.id.childTitleText);
-        mChildCloseButton = findViewById(R.id.childCloseButton);
-        mChildCloseButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View view) {
-                switch (view.getId()) {
-                    case R.id.childCloseButton:
-                        closeChild();
-                        break;
-                }
-            }
-        });
+    private LinearLayout createChildLayout() {
+        // Provide parent such that MATCH_PARENT layout params can work. Ignore the return value
+        // which is mRootLayout.
+        mLayoutInflater.inflate(R.layout.activity_web_platform_tests_child, mRootLayout);
+        // Choose what has just been added.
+        LinearLayout childLayout =
+                (LinearLayout) mRootLayout.getChildAt(mRootLayout.getChildCount() - 1);
+
+        mChildTitleText = childLayout.findViewById(R.id.childTitleText);
+        Button childCloseButton = childLayout.findViewById(R.id.childCloseButton);
+        childCloseButton.setOnClickListener((View v) -> { closeChild(); });
+        return childLayout;
     }
 
     private void setUpWebSettings(WebSettings settings) {
@@ -151,12 +158,9 @@
         settings.setSupportMultipleWindows(true);
     }
 
-    private void setUpWebView(String url) {
-        mWebView = findViewById(R.id.webview);
+    private void setUpMainWebView(String url) {
         setUpWebSettings(mWebView.getSettings());
-
         mWebView.setWebChromeClient(new MultiWindowWebChromeClient());
-
         mWebView.loadUrl(url);
     }
 
diff --git a/ash/shelf/OWNERS b/ash/shelf/OWNERS
index 52e16419..ec2623b8 100644
--- a/ash/shelf/OWNERS
+++ b/ash/shelf/OWNERS
@@ -1,3 +1,4 @@
+manucornet@chromium.org
 msw@chromium.org
 skuhne@chromium.org
 xiyuan@chromium.org
diff --git a/ash/system/message_center/arc/arc_notification_manager_unittest.cc b/ash/system/message_center/arc/arc_notification_manager_unittest.cc
index cdc845ac..d900529 100644
--- a/ash/system/message_center/arc/arc_notification_manager_unittest.cc
+++ b/ash/system/message_center/arc/arc_notification_manager_unittest.cc
@@ -167,7 +167,7 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   TestArcAppIdProvider app_id_provider_;
   std::unique_ptr<arc::FakeNotificationsInstance> arc_notifications_instance_;
   std::unique_ptr<mojo::Binding<arc::mojom::NotificationsInstance>> binding_;
diff --git a/ash/wm/overview/overview_animation_type.h b/ash/wm/overview/overview_animation_type.h
index f2398b3..1f2a367c 100644
--- a/ash/wm/overview/overview_animation_type.h
+++ b/ash/wm/overview/overview_animation_type.h
@@ -9,8 +9,6 @@
 
 // Enumeration of the different overview mode animations.
 enum OverviewAnimationType {
-  // TODO(bruthig): Remove OVERVIEW_ANIMATION_NONE value and replace it with
-  // correct animation type actions.
   OVERVIEW_ANIMATION_NONE,
   // Used to fade in the close button and label.
   OVERVIEW_ANIMATION_ENTER_OVERVIEW_MODE_FADE_IN,
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index c52ae83a..ecca698 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -1561,17 +1561,33 @@
   // ignored item, |window_position| remains where the item was as to then
   // reposition the other window's bounds in place of that item.
 
+  // This function may be called as the result of closing an overview item. If
+  // the closed item is the last item in the list, adjust |scroll_offset_| so
+  // that the grid is right aligned.
+  float right_most = 0.f;
+  for (const auto& window : window_list_) {
+    if (window->animating_to_close() || ignored_items.contains(window.get()))
+      continue;
+    right_most = std::max(right_most, window->target_bounds().right());
+  }
+  if (right_most != 0.f && right_most < total_bounds.right())
+    scroll_offset_ = -(right_most - scroll_offset_ - total_bounds.right());
+
+  // Map which contains up to |kTabletLayoutRow| entries with information on the
+  // last items right bound per row. Used so we can place the next item directly
+  // next to the last item. The key is the y-value of the row, and the value is
+  // the rightmost x-value.
+  base::flat_map<float, float> right_edge_map;
+
   // Since the number of rows is limited, windows are laid out column-wise so
   // that the most recently used windows are displayed first.
   const int height = total_bounds.height() / kTabletLayoutRow;
   int window_position = 0;
   std::vector<gfx::RectF> rects;
-
-  int i = 0;
-  for (const auto& window : window_list_) {
-    if (window->animating_to_close() || ignored_items.contains(window.get())) {
+  for (size_t i = 0; i < window_list_.size(); ++i) {
+    OverviewItem* item = window_list_[i].get();
+    if (item->animating_to_close() || ignored_items.contains(item)) {
       rects.push_back(gfx::RectF());
-      ++i;
       continue;
     }
 
@@ -1579,29 +1595,23 @@
     // original height will be shrunk to fit into |height|, minus the margin and
     // overview header.
     const float ratio = float{height - kHeaderHeightDp - kOverviewMargin} /
-                        window->GetWindow()->bounds().height();
+                        item->GetWindow()->bounds().height();
     const int width =
-        window->GetWindow()->bounds().width() * ratio + kOverviewMargin;
+        item->GetWindow()->bounds().width() * ratio + kOverviewMargin;
     const int y =
         height * (window_position % kTabletLayoutRow) + total_bounds.y();
 
-    // TODO(sammiequon): Remove this loop and cache the values of the last
-    // bounds for each row.
-    // Search for closest window in the same row to the
-    // left of where the current window would be placed and set the current
-    // window's |x| value to the right of the other window.
-    int x = total_bounds.x() + scroll_offset_;
-    for (int j = i - 1; j >= 0; --j) {
-      if (rects[j].y() == y) {
-        x = rects[j].right();
-        break;
-      }
-    }
+    // Use the right bounds of the item next to in the row as the x position, if
+    // that item exists.
+    const int x = right_edge_map.contains(y)
+                      ? right_edge_map[y]
+                      : total_bounds.x() + scroll_offset_;
+    right_edge_map[y] = x + width;
+    DCHECK_LE(int{right_edge_map.size()}, kTabletLayoutRow);
 
     const gfx::RectF bounds(x, y, width, height);
     rects.push_back(bounds);
     ++window_position;
-    ++i;
   }
 
   return rects;
diff --git a/ash/wm/overview/overview_grid_event_handler.cc b/ash/wm/overview/overview_grid_event_handler.cc
index f55ed3f0..e00638d 100644
--- a/ash/wm/overview/overview_grid_event_handler.cc
+++ b/ash/wm/overview/overview_grid_event_handler.cc
@@ -60,6 +60,7 @@
     case ui::ET_SCROLL_FLING_START: {
       if (!ShouldUseTabletModeGridLayout())
         return;
+
       HandleFlingScroll(event);
       event->SetHandled();
       break;
@@ -67,6 +68,7 @@
     case ui::ET_GESTURE_SCROLL_BEGIN: {
       if (!ShouldUseTabletModeGridLayout())
         return;
+
       EndFling();
       grid_->StartScroll();
       event->SetHandled();
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index 78040d45..33c78e6 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -815,12 +815,6 @@
     ui::GestureEvent* event) {
   const gfx::PointF location = event->details().bounding_box_f().CenterPoint();
   switch (event->type()) {
-    case ui::ET_GESTURE_SCROLL_UPDATE:
-      if (IsDragItem())
-        HandleDragEvent(location);
-      else
-        overview_grid()->grid_event_handler()->OnGestureEvent(event);
-      break;
     case ui::ET_SCROLL_FLING_START:
       if (IsDragItem()) {
         HandleFlingStartEvent(location, event->details().velocity_x(),
@@ -829,6 +823,20 @@
         overview_grid()->grid_event_handler()->OnGestureEvent(event);
       }
       break;
+    case ui::ET_GESTURE_SCROLL_BEGIN:
+      if (std::abs(event->details().scroll_y_hint()) >
+          std::abs(event->details().scroll_x_hint())) {
+        HandlePressEvent(location, /*from_touch_gesture=*/true);
+      } else {
+        overview_grid()->grid_event_handler()->OnGestureEvent(event);
+      }
+      break;
+    case ui::ET_GESTURE_SCROLL_UPDATE:
+      if (IsDragItem())
+        HandleDragEvent(location);
+      else
+        overview_grid()->grid_event_handler()->OnGestureEvent(event);
+      break;
     case ui::ET_GESTURE_SCROLL_END:
       if (IsDragItem())
         HandleReleaseEvent(location);
@@ -837,6 +845,7 @@
       break;
     case ui::ET_GESTURE_LONG_PRESS:
       HandlePressEvent(location, /*from_touch_gesture=*/true);
+      HandleLongPressEvent(location);
       break;
     case ui::ET_GESTURE_TAP:
       overview_session_->SelectWindow(this);
@@ -915,9 +924,7 @@
 void OverviewItem::OnWindowTitleChanged(aura::Window* window) {
   if (window != GetWindow())
     return;
-  // TODO(flackr): Maybe add the new title to a vector of titles so that we
-  // can filter any of the titles the window had while in the overview
-  // session.
+
   caption_container_view_->SetTitle(window->GetTitle());
 }
 
diff --git a/ash/wm/overview/overview_session_unittest.cc b/ash/wm/overview/overview_session_unittest.cc
index 9275183..323d2ee 100644
--- a/ash/wm/overview/overview_session_unittest.cc
+++ b/ash/wm/overview/overview_session_unittest.cc
@@ -2794,12 +2794,12 @@
     return Shell::Get()->split_view_controller();
   }
 
+ protected:
   void GenerateScrollSequence(const gfx::Point& start, const gfx::Point& end) {
     GetEventGenerator()->GestureScrollSequence(
         start, end, base::TimeDelta::FromMilliseconds(100), 1000);
   }
 
- protected:
   void DispatchLongPress(OverviewItem* item) {
     ui::TouchEvent long_press(
         ui::ET_GESTURE_LONG_PRESS,
@@ -2974,7 +2974,7 @@
 
 // Test that scrolling occurs if started on top of a window using the window's
 // center-point as a start.
-TEST_F(OverviewSessionNewLayoutTest, CheckScrollingOnWindowItems) {
+TEST_F(OverviewSessionNewLayoutTest, HorizontalScrollingOnOverviewItem) {
   auto windows = CreateTestWindows(8);
   ToggleOverview();
   ASSERT_TRUE(InOverviewSession());
@@ -2988,6 +2988,26 @@
   EXPECT_LT(leftmost_window->target_bounds(), left_bounds);
 }
 
+// Tests that a vertical scroll sequence will close the window it is scrolled
+// on.
+TEST_F(OverviewSessionNewLayoutTest, VerticalScrollingOnOverviewItem) {
+  constexpr int kNumWidgets = 8;
+  std::vector<std::unique_ptr<views::Widget>> widgets(kNumWidgets);
+  for (int i = kNumWidgets - 1; i >= 0; --i)
+    widgets[i] = CreateTestWidget();
+  ToggleOverview();
+  ASSERT_TRUE(InOverviewSession());
+
+  OverviewItem* leftmost_window =
+      GetOverviewItemForWindow(widgets[0]->GetNativeWindow());
+  const gfx::Point topleft_window_center =
+      gfx::ToRoundedPoint(leftmost_window->target_bounds().CenterPoint());
+  const gfx::Point end_point = topleft_window_center - gfx::Vector2d(0, 300);
+
+  GenerateScrollSequence(topleft_window_center, end_point);
+  EXPECT_TRUE(widgets[0]->IsClosed());
+}
+
 // Test that scrolling occurs if we hit the associated keyboard shortcut.
 TEST_F(OverviewSessionNewLayoutTest, CheckScrollingWithKeyboardShortcut) {
   auto windows = CreateTestWindows(8);
diff --git a/base/BUILD.gn b/base/BUILD.gn
index 314b7d88..3c8b77d 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -631,13 +631,14 @@
     "profiler/stack_sampler_win.cc",
     "profiler/stack_sampling_profiler.cc",
     "profiler/stack_sampling_profiler.h",
+    "profiler/suspendable_thread_delegate.h",
+    "profiler/suspendable_thread_delegate_mac.cc",
+    "profiler/suspendable_thread_delegate_mac.h",
+    "profiler/suspendable_thread_delegate_win.cc",
+    "profiler/suspendable_thread_delegate_win.h",
     "profiler/thread_delegate.h",
     "profiler/thread_delegate_android.cc",
     "profiler/thread_delegate_android.h",
-    "profiler/thread_delegate_mac.cc",
-    "profiler/thread_delegate_mac.h",
-    "profiler/thread_delegate_win.cc",
-    "profiler/thread_delegate_win.h",
     "profiler/unwinder.h",
     "rand_util.cc",
     "rand_util.h",
diff --git a/base/callback.h b/base/callback.h
index e08f9b8..1427faa 100644
--- a/base/callback.h
+++ b/base/callback.h
@@ -140,7 +140,7 @@
     RepeatingCallback cb = std::move(*this);
     PolymorphicInvoke f =
         reinterpret_cast<PolymorphicInvoke>(cb.polymorphic_invoke());
-    return f(cb.bind_state_.get(), std::forward<Args>(args)...);
+    return f(std::move(cb).bind_state_.get(), std::forward<Args>(args)...);
   }
 };
 
diff --git a/base/fuchsia/service_directory_test_base.h b/base/fuchsia/service_directory_test_base.h
index 3ff6006..8cfbeb3 100644
--- a/base/fuchsia/service_directory_test_base.h
+++ b/base/fuchsia/service_directory_test_base.h
@@ -31,9 +31,8 @@
  protected:
   const RunLoop::ScopedRunTimeoutForTest run_timeout_;
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
 
   std::unique_ptr<sys::OutgoingDirectory> outgoing_directory_;
   TestInterfaceImpl test_service_;
diff --git a/base/fuchsia/service_provider_impl_unittest.cc b/base/fuchsia/service_provider_impl_unittest.cc
index dcb8c0b7..bc77af0 100644
--- a/base/fuchsia/service_provider_impl_unittest.cc
+++ b/base/fuchsia/service_provider_impl_unittest.cc
@@ -55,9 +55,8 @@
   }
 
  protected:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   TestInterfaceImpl test_service_;
 
   sys::OutgoingDirectory service_directory_;
diff --git a/base/profiler/stack_copier_signal.cc b/base/profiler/stack_copier_signal.cc
index cd5940eb..e49d00a 100644
--- a/base/profiler/stack_copier_signal.cc
+++ b/base/profiler/stack_copier_signal.cc
@@ -7,7 +7,7 @@
 #include "base/profiler/metadata_recorder.h"
 #include "base/profiler/sample_metadata.h"
 #include "base/profiler/stack_buffer.h"
-#include "base/profiler/thread_delegate.h"
+#include "base/profiler/suspendable_thread_delegate.h"
 
 namespace base {
 
diff --git a/base/profiler/stack_copier_suspend.cc b/base/profiler/stack_copier_suspend.cc
index 2de3a61..a12aba5 100644
--- a/base/profiler/stack_copier_suspend.cc
+++ b/base/profiler/stack_copier_suspend.cc
@@ -7,12 +7,12 @@
 #include "base/profiler/metadata_recorder.h"
 #include "base/profiler/sample_metadata.h"
 #include "base/profiler/stack_buffer.h"
-#include "base/profiler/thread_delegate.h"
+#include "base/profiler/suspendable_thread_delegate.h"
 
 namespace base {
 
 StackCopierSuspend::StackCopierSuspend(
-    std::unique_ptr<ThreadDelegate> thread_delegate)
+    std::unique_ptr<SuspendableThreadDelegate> thread_delegate)
     : thread_delegate_(std::move(thread_delegate)) {}
 
 StackCopierSuspend::~StackCopierSuspend() = default;
@@ -39,8 +39,8 @@
 
     // 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();
+    std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
+        suspend_thread = thread_delegate_->CreateScopedSuspendThread();
 
     if (!suspend_thread->WasSuccessful())
       return false;
diff --git a/base/profiler/stack_copier_suspend.h b/base/profiler/stack_copier_suspend.h
index d2da8f0..6797625 100644
--- a/base/profiler/stack_copier_suspend.h
+++ b/base/profiler/stack_copier_suspend.h
@@ -12,14 +12,15 @@
 
 namespace base {
 
-class ThreadDelegate;
+class SuspendableThreadDelegate;
 
 // 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(
+      std::unique_ptr<SuspendableThreadDelegate> thread_delegate);
   ~StackCopierSuspend() override;
 
   // StackCopier:
@@ -29,7 +30,7 @@
                  RegisterContext* thread_context) override;
 
  private:
-  std::unique_ptr<ThreadDelegate> thread_delegate_;
+  std::unique_ptr<SuspendableThreadDelegate> thread_delegate_;
 };
 
 }  // namespace base
diff --git a/base/profiler/stack_copier_suspend_unittest.cc b/base/profiler/stack_copier_suspend_unittest.cc
index bc56abb..a496ade 100644
--- a/base/profiler/stack_copier_suspend_unittest.cc
+++ b/base/profiler/stack_copier_suspend_unittest.cc
@@ -11,7 +11,7 @@
 #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/profiler/suspendable_thread_delegate.h"
 #include "base/stl_util.h"
 #include "build/build_config.h"
 #include "testing/gmock/include/gmock/gmock.h"
@@ -25,9 +25,10 @@
 
 // A thread delegate for use in tests that provides the expected behavior when
 // operating on the supplied fake stack.
-class TestThreadDelegate : public ThreadDelegate {
+class TestSuspendableThreadDelegate : public SuspendableThreadDelegate {
  public:
-  class TestScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
+  class TestScopedSuspendThread
+      : public SuspendableThreadDelegate::ScopedSuspendThread {
    public:
     TestScopedSuspendThread() = default;
 
@@ -37,14 +38,15 @@
     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)
+  TestSuspendableThreadDelegate(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;
+  TestSuspendableThreadDelegate(const TestSuspendableThreadDelegate&) = delete;
+  TestSuspendableThreadDelegate& operator=(
+      const TestSuspendableThreadDelegate&) = delete;
 
   std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() override {
     return std::make_unique<TestScopedSuspendThread>();
@@ -107,7 +109,7 @@
 TEST(StackCopierSuspendTest, CopyStack) {
   const std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
   StackCopierSuspend stack_copier_suspend(
-      std::make_unique<TestThreadDelegate>(stack));
+      std::make_unique<TestSuspendableThreadDelegate>(stack));
 
   std::unique_ptr<StackBuffer> stack_buffer =
       std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
@@ -127,7 +129,7 @@
 TEST(StackCopierSuspendTest, CopyStackBufferTooSmall) {
   std::vector<uintptr_t> stack = {0, 1, 2, 3, 4};
   StackCopierSuspend stack_copier_suspend(
-      std::make_unique<TestThreadDelegate>(stack));
+      std::make_unique<TestSuspendableThreadDelegate>(stack));
 
   std::unique_ptr<StackBuffer> stack_buffer =
       std::make_unique<StackBuffer>((stack.size() - 1) * sizeof(uintptr_t));
@@ -154,7 +156,7 @@
   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::make_unique<TestSuspendableThreadDelegate>(stack));
 
   std::unique_ptr<StackBuffer> stack_buffer =
       std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
@@ -180,7 +182,8 @@
   RegisterContextFramePointer(&register_context) =
       reinterpret_cast<uintptr_t>(&stack[1]);
   StackCopierSuspend stack_copier_suspend(
-      std::make_unique<TestThreadDelegate>(stack, &register_context));
+      std::make_unique<TestSuspendableThreadDelegate>(stack,
+                                                      &register_context));
 
   std::unique_ptr<StackBuffer> stack_buffer =
       std::make_unique<StackBuffer>(stack.size() * sizeof(uintptr_t));
diff --git a/base/profiler/stack_sampler_impl.cc b/base/profiler/stack_sampler_impl.cc
index 2152070..f80e9c91 100644
--- a/base/profiler/stack_sampler_impl.cc
+++ b/base/profiler/stack_sampler_impl.cc
@@ -11,7 +11,7 @@
 #include "base/profiler/sample_metadata.h"
 #include "base/profiler/stack_buffer.h"
 #include "base/profiler/stack_copier.h"
-#include "base/profiler/thread_delegate.h"
+#include "base/profiler/suspendable_thread_delegate.h"
 #include "base/profiler/unwinder.h"
 
 // IMPORTANT NOTE: Some functions within this implementation are invoked while
diff --git a/base/profiler/stack_sampler_impl_unittest.cc b/base/profiler/stack_sampler_impl_unittest.cc
index 090821d..88956a1 100644
--- a/base/profiler/stack_sampler_impl_unittest.cc
+++ b/base/profiler/stack_sampler_impl_unittest.cc
@@ -12,7 +12,7 @@
 #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/suspendable_thread_delegate.h"
 #include "base/profiler/unwinder.h"
 #include "base/sampling_heap_profiler/module_cache.h"
 #include "base/stl_util.h"
diff --git a/base/profiler/stack_sampler_mac.cc b/base/profiler/stack_sampler_mac.cc
index 2f5de9f..1ab0eaf 100644
--- a/base/profiler/stack_sampler_mac.cc
+++ b/base/profiler/stack_sampler_mac.cc
@@ -7,7 +7,7 @@
 #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"
+#include "base/profiler/suspendable_thread_delegate_mac.h"
 
 namespace base {
 
@@ -18,7 +18,7 @@
     StackSamplerTestDelegate* test_delegate) {
   return std::make_unique<StackSamplerImpl>(
       std::make_unique<StackCopierSuspend>(
-          std::make_unique<ThreadDelegateMac>(thread_id)),
+          std::make_unique<SuspendableThreadDelegateMac>(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 d5d2e50..83307609 100644
--- a/base/profiler/stack_sampler_win.cc
+++ b/base/profiler/stack_sampler_win.cc
@@ -7,7 +7,7 @@
 #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 "base/profiler/suspendable_thread_delegate_win.h"
 #include "build/build_config.h"
 
 namespace base {
@@ -20,7 +20,7 @@
 #if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64)
   return std::make_unique<StackSamplerImpl>(
       std::make_unique<StackCopierSuspend>(
-          std::make_unique<ThreadDelegateWin>(thread_id)),
+          std::make_unique<SuspendableThreadDelegateWin>(thread_id)),
       std::make_unique<NativeUnwinderWin>(), module_cache, test_delegate);
 #else
   return nullptr;
diff --git a/base/profiler/suspendable_thread_delegate.h b/base/profiler/suspendable_thread_delegate.h
new file mode 100644
index 0000000..b5dfcd7
--- /dev/null
+++ b/base/profiler/suspendable_thread_delegate.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 BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_H_
+#define BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_H_
+
+#include <vector>
+
+#include "base/base_export.h"
+#include "base/profiler/register_context.h"
+#include "base/profiler/thread_delegate.h"
+
+namespace base {
+
+// Platform-specific thread and stack manipulation delegate, for use by the
+// platform-independent stack copying/walking implementation in
+// StackSamplerImpl for suspension-based stack copying.
+//
+// IMPORTANT NOTE: Most methods in this interface are invoked while the target
+// thread is suspended so must not do any allocation from the heap, including
+// indirectly via use of DCHECK/CHECK or other logging statements. Otherwise the
+// implementation can deadlock on heap locks acquired by the target thread
+// before it was suspended. These functions are commented with "NO HEAP
+// ALLOCATIONS".
+class BASE_EXPORT SuspendableThreadDelegate : public ThreadDelegate {
+ public:
+  // Implementations of this interface should suspend the thread for the
+  // object's lifetime. NO HEAP ALLOCATIONS between the time the thread is
+  // suspended and resumed.
+  class BASE_EXPORT ScopedSuspendThread {
+   public:
+    ScopedSuspendThread() = default;
+    virtual ~ScopedSuspendThread() = default;
+
+    ScopedSuspendThread(const ScopedSuspendThread&) = delete;
+    ScopedSuspendThread& operator=(const ScopedSuspendThread&) = delete;
+
+    virtual bool WasSuccessful() const = 0;
+  };
+
+  SuspendableThreadDelegate() = default;
+
+  // Creates an object that holds the thread suspended for its lifetime.
+  virtual std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() = 0;
+
+  // Gets the register context for the thread.
+  // NO HEAP ALLOCATIONS.
+  virtual bool GetThreadContext(RegisterContext* thread_context) = 0;
+
+  // Returns true if the thread's stack can be copied, where the bottom address
+  // of the thread is at |stack_pointer|.
+  // NO HEAP ALLOCATIONS.
+  virtual bool CanCopyStack(uintptr_t stack_pointer) = 0;
+};
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_H_
diff --git a/base/profiler/thread_delegate_mac.cc b/base/profiler/suspendable_thread_delegate_mac.cc
similarity index 75%
rename from base/profiler/thread_delegate_mac.cc
rename to base/profiler/suspendable_thread_delegate_mac.cc
index 7632c4ef..81323969 100644
--- a/base/profiler/thread_delegate_mac.cc
+++ b/base/profiler/suspendable_thread_delegate_mac.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 "base/profiler/thread_delegate_mac.h"
+#include "base/profiler/suspendable_thread_delegate_mac.h"
 
 #include <mach/mach.h>
 #include <mach/thread_act.h>
@@ -36,7 +36,7 @@
 // ScopedSuspendThread --------------------------------------------------------
 
 // NO HEAP ALLOCATIONS after thread_suspend.
-ThreadDelegateMac::ScopedSuspendThread::ScopedSuspendThread(
+SuspendableThreadDelegateMac::ScopedSuspendThread::ScopedSuspendThread(
     mach_port_t thread_port)
     : thread_port_(thread_suspend(thread_port) == KERN_SUCCESS
                        ? thread_port
@@ -44,7 +44,7 @@
 
 // NO HEAP ALLOCATIONS. The MACH_CHECK is OK because it provides a more noisy
 // failure mode than deadlocking.
-ThreadDelegateMac::ScopedSuspendThread::~ScopedSuspendThread() {
+SuspendableThreadDelegateMac::ScopedSuspendThread::~ScopedSuspendThread() {
   if (!WasSuccessful())
     return;
 
@@ -52,13 +52,14 @@
   MACH_CHECK(kr == KERN_SUCCESS, kr) << "thread_resume";
 }
 
-bool ThreadDelegateMac::ScopedSuspendThread::WasSuccessful() const {
+bool SuspendableThreadDelegateMac::ScopedSuspendThread::WasSuccessful() const {
   return thread_port_ != MACH_PORT_NULL;
 }
 
-// ThreadDelegateMac ----------------------------------------------------------
+// SuspendableThreadDelegateMac -----------------------------------------------
 
-ThreadDelegateMac::ThreadDelegateMac(mach_port_t thread_port)
+SuspendableThreadDelegateMac::SuspendableThreadDelegateMac(
+    mach_port_t thread_port)
     : thread_port_(thread_port),
       thread_stack_base_address_(reinterpret_cast<uintptr_t>(
           pthread_get_stackaddr_np(pthread_from_mach_thread_np(thread_port)))) {
@@ -70,29 +71,30 @@
   GetThreadState(thread_port_, &thread_state);
 }
 
-ThreadDelegateMac::~ThreadDelegateMac() = default;
+SuspendableThreadDelegateMac::~SuspendableThreadDelegateMac() = default;
 
-std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
-ThreadDelegateMac::CreateScopedSuspendThread() {
+std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
+SuspendableThreadDelegateMac::CreateScopedSuspendThread() {
   return std::make_unique<ScopedSuspendThread>(thread_port_);
 }
 
 // NO HEAP ALLOCATIONS.
-bool ThreadDelegateMac::GetThreadContext(x86_thread_state64_t* thread_context) {
+bool SuspendableThreadDelegateMac::GetThreadContext(
+    x86_thread_state64_t* thread_context) {
   return GetThreadState(thread_port_, thread_context);
 }
 
 // NO HEAP ALLOCATIONS.
-uintptr_t ThreadDelegateMac::GetStackBaseAddress() const {
+uintptr_t SuspendableThreadDelegateMac::GetStackBaseAddress() const {
   return thread_stack_base_address_;
 }
 
 // NO HEAP ALLOCATIONS.
-bool ThreadDelegateMac::CanCopyStack(uintptr_t stack_pointer) {
+bool SuspendableThreadDelegateMac::CanCopyStack(uintptr_t stack_pointer) {
   return true;
 }
 
-std::vector<uintptr_t*> ThreadDelegateMac::GetRegistersToRewrite(
+std::vector<uintptr_t*> SuspendableThreadDelegateMac::GetRegistersToRewrite(
     x86_thread_state64_t* thread_context) {
   return {
       &AsUintPtr(&thread_context->__rbx), &AsUintPtr(&thread_context->__rbp),
diff --git a/base/profiler/thread_delegate_mac.h b/base/profiler/suspendable_thread_delegate_mac.h
similarity index 63%
rename from base/profiler/thread_delegate_mac.h
rename to base/profiler/suspendable_thread_delegate_mac.h
index 1ff2497..de3f3066 100644
--- a/base/profiler/thread_delegate_mac.h
+++ b/base/profiler/suspendable_thread_delegate_mac.h
@@ -2,14 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef BASE_PROFILER_THREAD_DELEGATE_MAC_H_
-#define BASE_PROFILER_THREAD_DELEGATE_MAC_H_
+#ifndef BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_MAC_H_
+#define BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_MAC_H_
 
 #include <mach/mach.h>
 
 #include "base/base_export.h"
 #include "base/profiler/native_unwinder_mac.h"
-#include "base/profiler/thread_delegate.h"
+#include "base/profiler/suspendable_thread_delegate.h"
 #include "base/sampling_heap_profiler/module_cache.h"
 #include "base/threading/platform_thread.h"
 
@@ -17,9 +17,11 @@
 
 // Platform- and thread-specific implementation in support of stack sampling on
 // Mac.
-class BASE_EXPORT ThreadDelegateMac : public ThreadDelegate {
+class BASE_EXPORT SuspendableThreadDelegateMac
+    : public SuspendableThreadDelegate {
  public:
-  class ScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
+  class ScopedSuspendThread
+      : public SuspendableThreadDelegate::ScopedSuspendThread {
    public:
     explicit ScopedSuspendThread(mach_port_t thread_port);
     ~ScopedSuspendThread() override;
@@ -33,14 +35,15 @@
     mach_port_t thread_port_;
   };
 
-  ThreadDelegateMac(mach_port_t thread_port);
-  ~ThreadDelegateMac() override;
+  SuspendableThreadDelegateMac(mach_port_t thread_port);
+  ~SuspendableThreadDelegateMac() override;
 
-  ThreadDelegateMac(const ThreadDelegateMac&) = delete;
-  ThreadDelegateMac& operator=(const ThreadDelegateMac&) = delete;
+  SuspendableThreadDelegateMac(const SuspendableThreadDelegateMac&) = delete;
+  SuspendableThreadDelegateMac& operator=(const SuspendableThreadDelegateMac&) =
+      delete;
 
-  // ThreadDelegate
-  std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
+  // SuspendableThreadDelegate
+  std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
   CreateScopedSuspendThread() override;
   bool GetThreadContext(x86_thread_state64_t* thread_context) override;
   uintptr_t GetStackBaseAddress() const override;
@@ -58,4 +61,4 @@
 
 }  // namespace base
 
-#endif  // BASE_PROFILER_THREAD_DELEGATE_MAC_H_
+#endif  // BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_MAC_H_
diff --git a/base/profiler/thread_delegate_win.cc b/base/profiler/suspendable_thread_delegate_win.cc
similarity index 87%
rename from base/profiler/thread_delegate_win.cc
rename to base/profiler/suspendable_thread_delegate_win.cc
index 71ba5aa..c2afb25 100644
--- a/base/profiler/thread_delegate_win.cc
+++ b/base/profiler/suspendable_thread_delegate_win.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 "base/profiler/thread_delegate_win.h"
+#include "base/profiler/suspendable_thread_delegate_win.h"
 
 #include <windows.h>
 #include <winternl.h>
@@ -136,7 +136,7 @@
 // ScopedSuspendThread --------------------------------------------------------
 
 // NO HEAP ALLOCATIONS after ::SuspendThread.
-ThreadDelegateWin::ScopedSuspendThread::ScopedSuspendThread(
+SuspendableThreadDelegateWin::ScopedSuspendThread::ScopedSuspendThread(
     HANDLE thread_handle)
     : thread_handle_(thread_handle),
       was_successful_(::SuspendThread(thread_handle) !=
@@ -144,7 +144,7 @@
 
 // NO HEAP ALLOCATIONS. The CHECK is OK because it provides a more noisy failure
 // mode than deadlocking.
-ThreadDelegateWin::ScopedSuspendThread::~ScopedSuspendThread() {
+SuspendableThreadDelegateWin::ScopedSuspendThread::~ScopedSuspendThread() {
   if (!was_successful_)
     return;
 
@@ -164,46 +164,48 @@
   CHECK(resume_thread_succeeded) << "ResumeThread failed: " << GetLastError();
 }
 
-bool ThreadDelegateWin::ScopedSuspendThread::WasSuccessful() const {
+bool SuspendableThreadDelegateWin::ScopedSuspendThread::WasSuccessful() const {
   return was_successful_;
 }
 
-// ThreadDelegateWin ----------------------------------------------------------
+// SuspendableThreadDelegateWin
+// ----------------------------------------------------------
 
-ThreadDelegateWin::ThreadDelegateWin(PlatformThreadId thread_id)
+SuspendableThreadDelegateWin::SuspendableThreadDelegateWin(
+    PlatformThreadId thread_id)
     : thread_handle_(GetThreadHandle(thread_id)),
       thread_stack_base_address_(reinterpret_cast<uintptr_t>(
           GetThreadEnvironmentBlock(thread_handle_.Get())->Tib.StackBase)) {}
 
-ThreadDelegateWin::~ThreadDelegateWin() = default;
+SuspendableThreadDelegateWin::~SuspendableThreadDelegateWin() = default;
 
-std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
-ThreadDelegateWin::CreateScopedSuspendThread() {
+std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
+SuspendableThreadDelegateWin::CreateScopedSuspendThread() {
   return std::make_unique<ScopedSuspendThread>(thread_handle_.Get());
 }
 
 // NO HEAP ALLOCATIONS.
-bool ThreadDelegateWin::GetThreadContext(CONTEXT* thread_context) {
+bool SuspendableThreadDelegateWin::GetThreadContext(CONTEXT* thread_context) {
   *thread_context = {0};
   thread_context->ContextFlags = CONTEXT_FULL;
   return ::GetThreadContext(thread_handle_.Get(), thread_context) != 0;
 }
 
 // NO HEAP ALLOCATIONS.
-uintptr_t ThreadDelegateWin::GetStackBaseAddress() const {
+uintptr_t SuspendableThreadDelegateWin::GetStackBaseAddress() const {
   return thread_stack_base_address_;
 }
 
 // Tests whether |stack_pointer| points to a location in the guard page. NO HEAP
 // ALLOCATIONS.
-bool ThreadDelegateWin::CanCopyStack(uintptr_t stack_pointer) {
+bool SuspendableThreadDelegateWin::CanCopyStack(uintptr_t stack_pointer) {
   // Dereferencing a pointer in the guard page in a thread that doesn't own the
   // stack results in a STATUS_GUARD_PAGE_VIOLATION exception and a crash. This
   // occurs very rarely, but reliably over the population.
   return !PointsToGuardPage(stack_pointer);
 }
 
-std::vector<uintptr_t*> ThreadDelegateWin::GetRegistersToRewrite(
+std::vector<uintptr_t*> SuspendableThreadDelegateWin::GetRegistersToRewrite(
     CONTEXT* thread_context) {
   // Return the set of non-volatile registers.
   return {
diff --git a/base/profiler/suspendable_thread_delegate_win.h b/base/profiler/suspendable_thread_delegate_win.h
new file mode 100644
index 0000000..2452dbe
--- /dev/null
+++ b/base/profiler/suspendable_thread_delegate_win.h
@@ -0,0 +1,60 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_WIN_H_
+#define BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_WIN_H_
+
+#include <windows.h>
+
+#include "base/base_export.h"
+#include "base/profiler/suspendable_thread_delegate.h"
+#include "base/threading/platform_thread.h"
+#include "base/win/scoped_handle.h"
+
+namespace base {
+
+// Platform- and thread-specific implementation in support of stack sampling on
+// Windows.
+class BASE_EXPORT SuspendableThreadDelegateWin
+    : public SuspendableThreadDelegate {
+ public:
+  class ScopedSuspendThread
+      : public SuspendableThreadDelegate::ScopedSuspendThread {
+   public:
+    explicit ScopedSuspendThread(HANDLE thread_handle);
+    ~ScopedSuspendThread() override;
+
+    bool WasSuccessful() const override;
+
+   private:
+    HANDLE thread_handle_;
+    bool was_successful_;
+
+    DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread);
+  };
+
+  explicit SuspendableThreadDelegateWin(PlatformThreadId thread_id);
+  ~SuspendableThreadDelegateWin() override;
+
+  SuspendableThreadDelegateWin(const SuspendableThreadDelegateWin&) = delete;
+  SuspendableThreadDelegateWin& operator=(const SuspendableThreadDelegateWin&) =
+      delete;
+
+  // SuspendableThreadDelegate
+  std::unique_ptr<SuspendableThreadDelegate::ScopedSuspendThread>
+  CreateScopedSuspendThread() override;
+  bool GetThreadContext(CONTEXT* thread_context) override;
+  uintptr_t GetStackBaseAddress() const override;
+  bool CanCopyStack(uintptr_t stack_pointer) override;
+  std::vector<uintptr_t*> GetRegistersToRewrite(
+      CONTEXT* thread_context) override;
+
+ private:
+  win::ScopedHandle thread_handle_;
+  const uintptr_t thread_stack_base_address_;
+};
+
+}  // namespace base
+
+#endif  // BASE_PROFILER_SUSPENDABLE_THREAD_DELEGATE_WIN_H_
diff --git a/base/profiler/thread_delegate.h b/base/profiler/thread_delegate.h
index d0f0f02..bb8b50b 100644
--- a/base/profiler/thread_delegate.h
+++ b/base/profiler/thread_delegate.h
@@ -8,58 +8,25 @@
 #include <vector>
 
 #include "base/base_export.h"
-#include "base/profiler/frame.h"
 #include "base/profiler/register_context.h"
 
 namespace base {
 
 // Platform-specific thread and stack manipulation delegate, for use by the
 // platform-independent stack copying/walking implementation in
-// StackSamplerImpl.
-//
-// IMPORTANT NOTE: Most methods in this interface are invoked while the target
-// thread is suspended so must not do any allocation from the heap, including
-// indirectly via use of DCHECK/CHECK or other logging statements. Otherwise the
-// implementation can deadlock on heap locks acquired by the target thread
-// before it was suspended. These functions are commented with "NO HEAP
-// ALLOCATIONS".
+// StackSamplerImpl. Provides the common interface across signal- and
+// suspend-based stack copy implementations.
 class BASE_EXPORT ThreadDelegate {
  public:
-  // Implementations of this interface should suspend the thread for the
-  // object's lifetime. NO HEAP ALLOCATIONS between the time the thread is
-  // suspended and resumed.
-  class BASE_EXPORT ScopedSuspendThread {
-   public:
-    ScopedSuspendThread() = default;
-    virtual ~ScopedSuspendThread() = default;
-
-    ScopedSuspendThread(const ScopedSuspendThread&) = delete;
-    ScopedSuspendThread& operator=(const ScopedSuspendThread&) = delete;
-
-    virtual bool WasSuccessful() const = 0;
-  };
-
   ThreadDelegate() = default;
   virtual ~ThreadDelegate() = default;
 
   ThreadDelegate(const ThreadDelegate&) = delete;
   ThreadDelegate& operator=(const ThreadDelegate&) = delete;
 
-  // Creates an object that holds the thread suspended for its lifetime.
-  virtual std::unique_ptr<ScopedSuspendThread> CreateScopedSuspendThread() = 0;
-
-  // Gets the register context for the thread.
-  // NO HEAP ALLOCATIONS.
-  virtual bool GetThreadContext(RegisterContext* thread_context) = 0;
-
   // Gets the base address of the thread's stack.
   virtual uintptr_t GetStackBaseAddress() const = 0;
 
-  // Returns true if the thread's stack can be copied, where the bottom address
-  // of the thread is at |stack_pointer|.
-  // NO HEAP ALLOCATIONS.
-  virtual bool CanCopyStack(uintptr_t stack_pointer) = 0;
-
   // Returns a list of registers that should be rewritten to point into the
   // stack copy, if they originally pointed into the original stack.
   // May heap allocate.
diff --git a/base/profiler/thread_delegate_android.cc b/base/profiler/thread_delegate_android.cc
index f61f51f..669529a 100644
--- a/base/profiler/thread_delegate_android.cc
+++ b/base/profiler/thread_delegate_android.cc
@@ -13,36 +13,12 @@
 
 namespace base {
 
-// ScopedSuspendThread --------------------------------------------------------
-
-bool ThreadDelegateAndroid::ScopedSuspendThread::WasSuccessful() const {
-  return false;
-}
-
-// ThreadDelegateAndroid ------------------------------------------------------
-
-std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
-ThreadDelegateAndroid::CreateScopedSuspendThread() {
-  return std::make_unique<ScopedSuspendThread>();
-}
-
-// NO HEAP ALLOCATIONS.
-bool ThreadDelegateAndroid::GetThreadContext(RegisterContext* thread_context) {
-  return false;
-}
-
-// NO HEAP ALLOCATIONS.
 uintptr_t ThreadDelegateAndroid::GetStackBaseAddress() const {
   // It's okay for the stub to return zero here: GetStackBaseAddress() if
   // ScopedSuspendThread fails, which it always will in the stub.
   return 0;
 }
 
-// NO HEAP ALLOCATIONS.
-bool ThreadDelegateAndroid::CanCopyStack(uintptr_t stack_pointer) {
-  return false;
-}
-
 std::vector<uintptr_t*> ThreadDelegateAndroid::GetRegistersToRewrite(
     RegisterContext* thread_context) {
   return {};
diff --git a/base/profiler/thread_delegate_android.h b/base/profiler/thread_delegate_android.h
index 59ff674..6a842c0 100644
--- a/base/profiler/thread_delegate_android.h
+++ b/base/profiler/thread_delegate_android.h
@@ -13,33 +13,16 @@
 // Platform- and thread-specific implementation in support of stack sampling on
 // Android.
 //
-// TODO(charliea): Implement this class.
-// See: https://crbug.com/988574
+// TODO(https://crbug.com/988579): Implement this class.
 class BASE_EXPORT ThreadDelegateAndroid : public ThreadDelegate {
  public:
-  class ScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
-   public:
-    ScopedSuspendThread() = default;
-    ~ScopedSuspendThread() override = default;
-
-    ScopedSuspendThread(const ScopedSuspendThread&) = delete;
-    ScopedSuspendThread& operator=(const ScopedSuspendThread&) = delete;
-
-    bool WasSuccessful() const override;
-  };
-
   ThreadDelegateAndroid() = default;
-  ~ThreadDelegateAndroid() override = default;
 
   ThreadDelegateAndroid(const ThreadDelegateAndroid&) = delete;
   ThreadDelegateAndroid& operator=(const ThreadDelegateAndroid&) = delete;
 
   // ThreadDelegate
-  std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
-  CreateScopedSuspendThread() override;
-  bool GetThreadContext(RegisterContext* thread_context) override;
   uintptr_t GetStackBaseAddress() const override;
-  bool CanCopyStack(uintptr_t stack_pointer) override;
   std::vector<uintptr_t*> GetRegistersToRewrite(
       RegisterContext* thread_context) override;
 };
diff --git a/base/profiler/thread_delegate_win.h b/base/profiler/thread_delegate_win.h
deleted file mode 100644
index 9e1d781..0000000
--- a/base/profiler/thread_delegate_win.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef BASE_PROFILER_THREAD_DELEGATE_WIN_H_
-#define BASE_PROFILER_THREAD_DELEGATE_WIN_H_
-
-#include <windows.h>
-
-#include "base/base_export.h"
-#include "base/profiler/thread_delegate.h"
-#include "base/threading/platform_thread.h"
-#include "base/win/scoped_handle.h"
-
-namespace base {
-
-// Platform- and thread-specific implementation in support of stack sampling on
-// Windows.
-class BASE_EXPORT ThreadDelegateWin : public ThreadDelegate {
- public:
-  class ScopedSuspendThread : public ThreadDelegate::ScopedSuspendThread {
-   public:
-    explicit ScopedSuspendThread(HANDLE thread_handle);
-    ~ScopedSuspendThread() override;
-
-    bool WasSuccessful() const override;
-
-   private:
-    HANDLE thread_handle_;
-    bool was_successful_;
-
-    DISALLOW_COPY_AND_ASSIGN(ScopedSuspendThread);
-  };
-
-  explicit ThreadDelegateWin(PlatformThreadId thread_id);
-  ~ThreadDelegateWin() override;
-
-  ThreadDelegateWin(const ThreadDelegateWin&) = delete;
-  ThreadDelegateWin& operator=(const ThreadDelegateWin&) = delete;
-
-  // ThreadDelegate
-  std::unique_ptr<ThreadDelegate::ScopedSuspendThread>
-  CreateScopedSuspendThread() override;
-  bool GetThreadContext(CONTEXT* thread_context) override;
-  uintptr_t GetStackBaseAddress() const override;
-  bool CanCopyStack(uintptr_t stack_pointer) override;
-  std::vector<uintptr_t*> GetRegistersToRewrite(
-      CONTEXT* thread_context) override;
-
- private:
-  win::ScopedHandle thread_handle_;
-  const uintptr_t thread_stack_base_address_;
-};
-
-}  // namespace base
-
-#endif  // BASE_PROFILER_THREAD_DELEGATE_WIN_H_
diff --git a/base/test/test_suite.cc b/base/test/test_suite.cc
index 76983d9..3b1de80 100644
--- a/base/test/test_suite.cc
+++ b/base/test/test_suite.cc
@@ -27,6 +27,8 @@
 #include "base/path_service.h"
 #include "base/process/launch.h"
 #include "base/process/memory.h"
+#include "base/process/process.h"
+#include "base/process/process_handle.h"
 #include "base/task/thread_pool/thread_pool_instance.h"
 #include "base/test/gtest_xml_unittest_result_printer.h"
 #include "base/test/gtest_xml_util.h"
@@ -43,6 +45,7 @@
 
 #if defined(OS_MACOSX)
 #include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/process/port_provider_mac.h"
 #if defined(OS_IOS)
 #include "base/test/test_listener_ios.h"
 #endif  // OS_IOS
@@ -144,6 +147,47 @@
   DISALLOW_COPY_AND_ASSIGN(CheckForLeakedGlobals);
 };
 
+// base::Process is not available on iOS
+#if !defined(OS_IOS)
+class CheckProcessPriority : public testing::EmptyTestEventListener {
+ public:
+  CheckProcessPriority() { CHECK(!IsProcessBackgrounded()); }
+
+  void OnTestStart(const testing::TestInfo& test) override {
+    EXPECT_FALSE(IsProcessBackgrounded());
+  }
+  void OnTestEnd(const testing::TestInfo& test) override {
+#if !defined(OS_MACOSX)
+    // Flakes are found on Mac OS 10.11. See https://crbug.com/931721#c7.
+    EXPECT_FALSE(IsProcessBackgrounded());
+#endif
+  }
+
+ private:
+#if defined(OS_MACOSX)
+  // Returns the calling process's task port, ignoring its argument.
+  class CurrentProcessPortProvider : public PortProvider {
+    mach_port_t TaskForPid(ProcessHandle process) const override {
+      // This PortProvider implementation only works for the current process.
+      CHECK_EQ(process, base::GetCurrentProcessHandle());
+      return mach_task_self();
+    }
+  };
+#endif
+
+  bool IsProcessBackgrounded() const {
+#if defined(OS_MACOSX)
+    CurrentProcessPortProvider port_provider;
+    return Process::Current().IsProcessBackgrounded(&port_provider);
+#else
+    return Process::Current().IsProcessBackgrounded();
+#endif
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(CheckProcessPriority);
+};
+#endif  // !defined(OS_IOS)
+
 const std::string& GetProfileName() {
   static const NoDestructor<std::string> profile_name([]() {
     const CommandLine& command_line = *CommandLine::ForCurrentProcess();
@@ -188,7 +232,7 @@
 
 }  // namespace
 
-int RunUnitTestsUsingBaseTestSuite(int argc, char **argv) {
+int RunUnitTestsUsingBaseTestSuite(int argc, char** argv) {
   TestSuite test_suite(argc, argv);
   return LaunchUnitTests(argc, argv,
                          BindOnce(&TestSuite::Run, Unretained(&test_suite)));
@@ -332,6 +376,11 @@
   check_for_leaked_globals_ = false;
 }
 
+void TestSuite::DisableCheckForProcessPriority() {
+  DCHECK(!is_initialized_);
+  check_for_process_priority_ = false;
+}
+
 void TestSuite::UnitTestAssertHandler(const char* file,
                                       int line,
                                       const StringPiece summary,
@@ -401,9 +450,8 @@
 
 void TestSuite::SuppressErrorDialogs() {
 #if defined(OS_WIN)
-  UINT new_flags = SEM_FAILCRITICALERRORS |
-                   SEM_NOGPFAULTERRORBOX |
-                   SEM_NOOPENFILEERRORBOX;
+  UINT new_flags =
+      SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX;
 
   // Preserve existing error mode, as discussed at
   // http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx
@@ -519,6 +567,10 @@
   listeners.Append(new ResetCommandLineBetweenTests);
   if (check_for_leaked_globals_)
     listeners.Append(new CheckForLeakedGlobals);
+#if !defined(OS_IOS)
+  if (check_for_process_priority_)
+    listeners.Append(new CheckProcessPriority);
+#endif
 
   AddTestLauncherResultPrinter();
 
diff --git a/base/test/test_suite.h b/base/test/test_suite.h
index 664e2e3..6e1e750 100644
--- a/base/test/test_suite.h
+++ b/base/test/test_suite.h
@@ -28,7 +28,7 @@
 class XmlUnitTestResultPrinter;
 
 // Instantiates TestSuite, runs it and returns exit code.
-int RunUnitTestsUsingBaseTestSuite(int argc, char **argv);
+int RunUnitTestsUsingBaseTestSuite(int argc, char** argv);
 
 class TestSuite {
  public:
@@ -43,6 +43,9 @@
 
   int Run();
 
+  // Disables checks for process priority. Most tests should not use this.
+  void DisableCheckForProcessPriority();
+
   // Disables checks for certain global objects being leaked across tests.
   void DisableCheckForLeakedGlobals();
 
@@ -90,6 +93,7 @@
   std::unique_ptr<logging::ScopedLogAssertHandler> assert_handler_;
 
   bool check_for_leaked_globals_ = true;
+  bool check_for_process_priority_ = true;
 
   bool is_initialized_ = false;
 
diff --git a/build/config/aix/BUILD.gn b/build/config/aix/BUILD.gn
index e3f21c39..6c8749ab 100644
--- a/build/config/aix/BUILD.gn
+++ b/build/config/aix/BUILD.gn
@@ -35,7 +35,6 @@
   ]
 
   cflags_cc = [
-    "-std=gnu++11",
     "-fno-rtti",
     "-fno-exceptions",
     "-Wno-narrowing",
diff --git a/build/config/compiler/BUILD.gn b/build/config/compiler/BUILD.gn
index 66c359d..b5cbbb6 100644
--- a/build/config/compiler/BUILD.gn
+++ b/build/config/compiler/BUILD.gn
@@ -104,6 +104,9 @@
   clang_use_default_sample_profile = build_with_chromium && is_official_build &&
                                      (is_android || is_desktop_linux)
 
+  # Emit debug information for profiling wile building with clang.
+  clang_emit_debug_info_for_profiling = false
+
   # Turn this on to have the compiler output extra timing information.
   compiler_timing = false
 
@@ -1512,26 +1515,22 @@
 
           # TODO(https://crbug.com/995200): Clean up and enable.
           "-Wno-xor-used-as-pow",
+
+          # TODO(https://crbug.com/999871): Decide if we want to clean up the
+          # codebase or just disable this.  Doesn't seem super useful, but
+          # also fires in only 4 files.
+          "-Wno-c99-designator",
+
+          # This is a side effect of -Wc99-designator; easier to clean up.
+          "-Wno-reorder-init-list",
+
+          # TODO(https://crbug.com/999886): Clean up, enable.
+          "-Wno-final-dtor-non-final-class",
         ]
         cflags_c += [
           # TODO(https://crbug.com/995993): Clean up and enable.
           "-Wno-implicit-fallthrough",
         ]
-
-        if (llvm_force_head_revision) {
-          cflags += [
-            # TODO(https://crbug.com/999871): Decide if we want to clean up the
-            # codebase or just disable this.  Doesn't seem super useful, but
-            # also fires in only 4 files.
-            "-Wno-c99-designator",
-
-            # This is a side effect of -Wc99-designator; easier to clean up.
-            "-Wno-reorder-init-list",
-
-            # TODO(https://crbug.com/999886): Clean up, enable.
-            "-Wno-final-dtor-non-final-class",
-          ]
-        }
       }
     }
   }
@@ -2185,10 +2184,18 @@
 # config to allow AFDO to be disabled per-target.
 config("afdo") {
   if (is_clang) {
+    cflags = []
+    if (clang_emit_debug_info_for_profiling) {
+      # Add the following flags to generate debug info for profiling.
+      cflags += [
+        "-fdebug-info-for-profiling",
+        "-gline-tables-only",
+      ]
+    }
     if (_clang_sample_profile != "") {
       rebased_clang_sample_profile =
           rebase_path(_clang_sample_profile, root_build_dir)
-      cflags = [ "-fprofile-sample-use=${rebased_clang_sample_profile}" ]
+      cflags += [ "-fprofile-sample-use=${rebased_clang_sample_profile}" ]
       inputs = [
         _clang_sample_profile,
       ]
diff --git a/build/partitioned_shared_library.gni b/build/partitioned_shared_library.gni
index 40d5a7b..582afc4 100644
--- a/build/partitioned_shared_library.gni
+++ b/build/partitioned_shared_library.gni
@@ -65,10 +65,6 @@
       "--link-only",
     ]
 
-    # https://crbug.com/1000665: Force partitions to be aligned. This can be
-    # removed once LLVM rolls in an alignment fix.
-    ldflags += [ "-Wl,-z,separate-code" ]
-
     # This shared library is an intermediate artifact that should not packaged
     # into the final build. Therefore, reset metadata.
     metadata = {
diff --git a/cc/layers/layer_impl_test_properties.cc b/cc/layers/layer_impl_test_properties.cc
index 838bfb9..caa5e2f 100644
--- a/cc/layers/layer_impl_test_properties.cc
+++ b/cc/layers/layer_impl_test_properties.cc
@@ -46,6 +46,13 @@
   return layer;
 }
 
+void LayerImplTestProperties::RemoveAllChildren() {
+  auto children_to_be_removed = std::move(children);
+  for (auto* child : children_to_be_removed)
+    owning_layer->layer_tree_impl()->RemoveLayer(child->id());
+  owning_layer->layer_tree_impl()->BuildLayerListForTesting();
+}
+
 void LayerImplTestProperties::SetMaskLayer(std::unique_ptr<LayerImpl> mask) {
   if (mask_layer)
     owning_layer->layer_tree_impl()->RemoveLayer(mask_layer->id());
diff --git a/cc/layers/layer_impl_test_properties.h b/cc/layers/layer_impl_test_properties.h
index d5405f0d..db947b5f 100644
--- a/cc/layers/layer_impl_test_properties.h
+++ b/cc/layers/layer_impl_test_properties.h
@@ -31,6 +31,7 @@
 
   void AddChild(std::unique_ptr<LayerImpl> child);
   std::unique_ptr<LayerImpl> RemoveChild(LayerImpl* child);
+  void RemoveAllChildren();
   void SetMaskLayer(std::unique_ptr<LayerImpl> mask);
 
   LayerImpl* owning_layer;
diff --git a/cc/test/fake_picture_layer_impl.h b/cc/test/fake_picture_layer_impl.h
index f2139ac..0581e25a 100644
--- a/cc/test/fake_picture_layer_impl.h
+++ b/cc/test/fake_picture_layer_impl.h
@@ -87,14 +87,14 @@
   size_t CountTilesRequiredForDraw() const;
 
   using PictureLayerImpl::AddTiling;
-  using PictureLayerImpl::CleanUpTilingsOnActiveLayer;
   using PictureLayerImpl::CanHaveTilings;
+  using PictureLayerImpl::CleanUpTilingsOnActiveLayer;
   using PictureLayerImpl::MinimumContentsScale;
   using PictureLayerImpl::SanityCheckTilingState;
   using PictureLayerImpl::UpdateRasterSource;
 
-  using PictureLayerImpl::UpdateIdealScales;
   using PictureLayerImpl::MaximumTilingContentsScale;
+  using PictureLayerImpl::UpdateIdealScales;
 
   void AddTilingUntilNextDraw(float scale) {
     last_append_quads_tilings_.push_back(
@@ -179,6 +179,22 @@
   size_t release_tile_resources_count_ = 0;
 };
 
+// An adapter so that FakePictureLayerImpl::CreateWithRasterSource can be used
+// as FakePictureLayerImplWithRasterSource::Create() in some templates.
+class FakePictureLayerImplWithRasterSource : public FakePictureLayerImpl {
+ public:
+  // Create layer from a raster source that covers the entire layer.
+  static std::unique_ptr<FakePictureLayerImplWithRasterSource> Create(
+      LayerTreeImpl* tree_impl,
+      int id,
+      scoped_refptr<RasterSource> raster_source) {
+    return base::WrapUnique(static_cast<FakePictureLayerImplWithRasterSource*>(
+        FakePictureLayerImpl::CreateWithRasterSource(tree_impl, id,
+                                                     std::move(raster_source))
+            .release()));
+  }
+};
+
 }  // namespace cc
 
 #endif  // CC_TEST_FAKE_PICTURE_LAYER_IMPL_H_
diff --git a/cc/test/layer_test_common.cc b/cc/test/layer_test_common.cc
index 0ac06e9..025525f6 100644
--- a/cc/test/layer_test_common.cc
+++ b/cc/test/layer_test_common.cc
@@ -60,9 +60,9 @@
 
     gfx::Rect quad_rect = gfx::ToEnclosingRect(quad_rectf);
 
-    EXPECT_TRUE(rect.Contains(quad_rect)) << quad_string << iter.index()
-                                          << " rect: " << rect.ToString()
-                                          << " quad: " << quad_rect.ToString();
+    EXPECT_TRUE(rect.Contains(quad_rect))
+        << quad_string << iter.index() << " rect: " << rect.ToString()
+        << " quad: " << quad_rect.ToString();
     EXPECT_TRUE(remaining.Contains(quad_rect))
         << quad_string << iter.index() << " remaining: " << remaining.ToString()
         << " quad: " << quad_rect.ToString();
@@ -286,68 +286,6 @@
   LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs);
 }
 
-void LayerTestCommon::SetupBrowserControlsAndScrollLayerWithVirtualViewport(
-    LayerTreeHostImpl* host_impl,
-    LayerTreeImpl* tree_impl,
-    float top_controls_height,
-    const gfx::Size& inner_viewport_size,
-    const gfx::Size& outer_viewport_size,
-    const gfx::Size& scroll_layer_size) {
-  tree_impl->SetRootLayerForTesting(nullptr);
-  tree_impl->set_browser_controls_shrink_blink_size(true);
-  tree_impl->SetTopControlsHeight(top_controls_height);
-  tree_impl->SetCurrentBrowserControlsShownRatio(1.f);
-  tree_impl->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-  host_impl->DidChangeBrowserControlsPosition();
-
-  std::unique_ptr<LayerImpl> root = LayerImpl::Create(tree_impl, 1);
-  std::unique_ptr<LayerImpl> root_clip = LayerImpl::Create(tree_impl, 2);
-  std::unique_ptr<LayerImpl> page_scale = LayerImpl::Create(tree_impl, 3);
-
-  std::unique_ptr<LayerImpl> outer_scroll = LayerImpl::Create(tree_impl, 4);
-  std::unique_ptr<LayerImpl> outer_clip = LayerImpl::Create(tree_impl, 5);
-
-  root_clip->SetBounds(inner_viewport_size);
-  root->SetScrollable(inner_viewport_size);
-  root->SetElementId(LayerIdToElementIdForTesting(root->id()));
-  root->SetBounds(outer_viewport_size);
-  root->SetDrawsContent(false);
-  root_clip->test_properties()->force_render_surface = true;
-  root->test_properties()->is_container_for_fixed_position_layers = true;
-  outer_clip->SetBounds(outer_viewport_size);
-  outer_scroll->SetScrollable(outer_viewport_size);
-  outer_scroll->SetElementId(LayerIdToElementIdForTesting(outer_scroll->id()));
-  outer_scroll->SetBounds(scroll_layer_size);
-  outer_scroll->SetDrawsContent(false);
-  outer_scroll->test_properties()->is_container_for_fixed_position_layers =
-      true;
-
-  int inner_viewport_container_layer_id = root_clip->id();
-  int outer_viewport_container_layer_id = outer_clip->id();
-  int inner_viewport_scroll_layer_id = root->id();
-  int outer_viewport_scroll_layer_id = outer_scroll->id();
-  int page_scale_layer_id = page_scale->id();
-
-  outer_clip->test_properties()->AddChild(std::move(outer_scroll));
-  root->test_properties()->AddChild(std::move(outer_clip));
-  page_scale->test_properties()->AddChild(std::move(root));
-  root_clip->test_properties()->AddChild(std::move(page_scale));
-
-  tree_impl->SetRootLayerForTesting(std::move(root_clip));
-  LayerTreeImpl::ViewportLayerIds viewport_ids;
-  viewport_ids.page_scale = page_scale_layer_id;
-  viewport_ids.inner_viewport_container = inner_viewport_container_layer_id;
-  viewport_ids.outer_viewport_container = outer_viewport_container_layer_id;
-  viewport_ids.inner_viewport_scroll = inner_viewport_scroll_layer_id;
-  viewport_ids.outer_viewport_scroll = outer_viewport_scroll_layer_id;
-  tree_impl->SetViewportLayersFromIds(viewport_ids);
-  tree_impl->BuildPropertyTreesForTesting();
-
-  tree_impl->SetDeviceViewportRect(gfx::Rect(inner_viewport_size));
-  LayerImpl* root_clip_ptr = tree_impl->root_layer_for_testing();
-  EXPECT_EQ(inner_viewport_size, root_clip_ptr->bounds());
-}
-
 void LayerTestCommon::LayerImplTest::UpdateDrawProperties(
     LayerTreeImpl* layer_tree_impl) {
   LayerTreeHostCommon::PrepareForUpdateDrawPropertiesForTesting(
diff --git a/cc/test/layer_test_common.h b/cc/test/layer_test_common.h
index 6f19b00..257711d3 100644
--- a/cc/test/layer_test_common.h
+++ b/cc/test/layer_test_common.h
@@ -34,7 +34,9 @@
     Mock::VerifyAndClearExpectations(layer_tree_host_.get());               \
   } while (false)
 
-namespace gfx { class Rect; }
+namespace gfx {
+class Rect;
+}
 
 namespace viz {
 class QuadList;
@@ -61,14 +63,6 @@
                                      const gfx::Rect& occluded,
                                      size_t* partially_occluded_count);
 
-  static void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
-      LayerTreeHostImpl* host_impl,
-      LayerTreeImpl* tree_impl,
-      float top_controls_height,
-      const gfx::Size& inner_viewport_size,
-      const gfx::Size& outer_viewport_size,
-      const gfx::Size& scroll_layer_size);
-
   class LayerImplTest {
    public:
     LayerImplTest();
diff --git a/cc/test/property_tree_test_utils.cc b/cc/test/property_tree_test_utils.cc
index 95e115b..3b87cac 100644
--- a/cc/test/property_tree_test_utils.cc
+++ b/cc/test/property_tree_test_utils.cc
@@ -333,7 +333,9 @@
                    const gfx::Size& outer_viewport_size,
                    const gfx::Size& content_size) {
   DCHECK(root);
+
   LayerTreeImpl* layer_tree_impl = root->layer_tree_impl();
+  DCHECK(!layer_tree_impl->InnerViewportScrollLayer());
   DCHECK(layer_tree_impl->settings().use_layer_lists);
 
   std::unique_ptr<LayerImpl> inner_viewport_container_layer =
diff --git a/cc/trees/layer_tree_host_common.cc b/cc/trees/layer_tree_host_common.cc
index e91bd95..fb1d40684 100644
--- a/cc/trees/layer_tree_host_common.cc
+++ b/cc/trees/layer_tree_host_common.cc
@@ -649,20 +649,23 @@
 
 void LayerTreeHostCommon::PrepareForUpdateDrawPropertiesForTesting(
     LayerTreeImpl* layer_tree_impl) {
-  if (layer_tree_impl->settings().use_layer_lists) {
-    // TODO(wangxianzhu): We should DCHECK(!needs_rebuild) after we remove all
-    // unnecessary setting of the flag in layer list mode.
-    auto* property_trees = layer_tree_impl->property_trees();
-    property_trees->needs_rebuild = false;
-    // The following are needed for tests that modify impl-side property trees.
-    // In production code impl-side property trees are pushed from the main
-    // thread and the following are done in other ways.
-    std::vector<std::unique_ptr<RenderSurfaceImpl>> old_render_surfaces;
-    property_trees->effect_tree.TakeRenderSurfaces(&old_render_surfaces);
-    property_trees->effect_tree.CreateOrReuseRenderSurfaces(
-        &old_render_surfaces, layer_tree_impl);
-    property_trees->ResetCachedData();
-  }
+  if (!layer_tree_impl->settings().use_layer_lists)
+    return;
+
+  // TODO(wangxianzhu): We should DCHECK(!needs_rebuild) after we remove all
+  // unnecessary setting of the flag in layer list mode.
+  auto* property_trees = layer_tree_impl->property_trees();
+  property_trees->needs_rebuild = false;
+
+  // The following are needed for tests that modify impl-side property trees.
+  // In production code impl-side property trees are pushed from the main
+  // thread and the following are done in other ways.
+  std::vector<std::unique_ptr<RenderSurfaceImpl>> old_render_surfaces;
+  property_trees->effect_tree.TakeRenderSurfaces(&old_render_surfaces);
+  property_trees->effect_tree.CreateOrReuseRenderSurfaces(&old_render_surfaces,
+                                                          layer_tree_impl);
+  layer_tree_impl->MoveChangeTrackingToLayers();
+  property_trees->ResetCachedData();
 }
 
 void LayerTreeHostCommon::CalculateDrawPropertiesForTesting(
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
index 3db48469..1a7a64a 100644
--- a/cc/trees/layer_tree_host_impl.cc
+++ b/cc/trees/layer_tree_host_impl.cc
@@ -756,8 +756,8 @@
   did_animate |= AnimateScrollbars(monotonic_time);
   did_animate |= AnimateBrowserControls(monotonic_time);
 
-    // Animating stuff can change the root scroll offset, so inform the
-    // synchronous input handler.
+  // Animating stuff can change the root scroll offset, so inform the
+  // synchronous input handler.
   UpdateRootLayerStateForSynchronousInputHandler();
   if (did_animate) {
     // If the tree changed, then we want to draw at the end of the current
@@ -766,7 +766,6 @@
   }
 }
 
-
 bool LayerTreeHostImpl::PrepareTiles() {
   if (!tile_priorities_dirty_)
     return false;
@@ -2846,6 +2845,10 @@
   client_->SetNeedsCommitOnImplThread();
 }
 
+LayerImpl* LayerTreeHostImpl::PageScaleLayer() const {
+  return active_tree_->PageScaleLayer();
+}
+
 LayerImpl* LayerTreeHostImpl::InnerViewportContainerLayer() const {
   return active_tree_->InnerViewportContainerLayer();
 }
diff --git a/cc/trees/layer_tree_host_impl.h b/cc/trees/layer_tree_host_impl.h
index 4a10681..c6939a5f 100644
--- a/cc/trees/layer_tree_host_impl.h
+++ b/cc/trees/layer_tree_host_impl.h
@@ -68,7 +68,7 @@
 namespace viz {
 class CompositorFrame;
 class CompositorFrameMetadata;
-}
+}  // namespace viz
 
 namespace cc {
 
@@ -565,6 +565,7 @@
   virtual void ActivateSyncTree();
 
   // Shortcuts to layers/nodes on the active tree.
+  LayerImpl* PageScaleLayer() const;
   LayerImpl* InnerViewportContainerLayer() const;
   LayerImpl* InnerViewportScrollLayer() const;
   ScrollNode* InnerViewportScrollNode() const;
diff --git a/cc/trees/layer_tree_host_impl_unittest.cc b/cc/trees/layer_tree_host_impl_unittest.cc
index f695646..9595997 100644
--- a/cc/trees/layer_tree_host_impl_unittest.cc
+++ b/cc/trees/layer_tree_host_impl_unittest.cc
@@ -145,7 +145,7 @@
   }
 
   LayerTreeSettings DefaultSettings() {
-    LayerTreeSettings settings;
+    LayerListSettings settings;
     settings.enable_surface_synchronization = true;
     settings.minimum_occlusion_tracking_size = gfx::Size();
     return settings;
@@ -247,27 +247,19 @@
     reduce_memory_result_ = reduce_memory_result;
   }
 
-  virtual bool CreateHostImpl(
-      const LayerTreeSettings& settings,
-      std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink) {
-    return CreateHostImplWithTaskRunnerProvider(
-        settings, std::move(layer_tree_frame_sink), &task_runner_provider_);
-  }
-
   AnimationHost* GetImplAnimationHost() const {
     return static_cast<AnimationHost*>(host_impl_->mutator_host());
   }
 
-  virtual bool CreateHostImplWithTaskRunnerProvider(
+  virtual bool CreateHostImpl(
       const LayerTreeSettings& settings,
-      std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink,
-      TaskRunnerProvider* task_runner_provider) {
+      std::unique_ptr<LayerTreeFrameSink> layer_tree_frame_sink) {
     if (host_impl_)
       host_impl_->ReleaseLayerTreeFrameSink();
     host_impl_.reset();
     InitializeImageWorker(settings);
     host_impl_ = LayerTreeHostImpl::Create(
-        settings, this, task_runner_provider, &stats_instrumentation_,
+        settings, this, &task_runner_provider_, &stats_instrumentation_,
         &task_graph_runner_,
         AnimationHost::CreateForTesting(ThreadInstance::IMPL), 0,
         image_worker_ ? image_worker_->task_runner() : nullptr);
@@ -293,13 +285,34 @@
     return init;
   }
 
-  void SetupRootLayerImpl(std::unique_ptr<LayerImpl> root) {
-    root->test_properties()->position = gfx::PointF();
-    root->SetBounds(gfx::Size(10, 10));
-    root->SetDrawsContent(true);
-    root->draw_properties().visible_layer_rect = gfx::Rect(0, 0, 10, 10);
-    root->test_properties()->force_render_surface = true;
-    host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
+  template <typename T, typename... Args>
+  T* SetupRootLayer(LayerTreeImpl* layer_tree_impl,
+                    const gfx::Size& viewport_size,
+                    Args&&... args) {
+    const int kRootLayerId = 1;
+    DCHECK(!layer_tree_impl->root_layer_for_testing());
+    DCHECK(!layer_tree_impl->LayerById(kRootLayerId));
+    layer_tree_impl->SetRootLayerForTesting(
+        T::Create(layer_tree_impl, kRootLayerId, std::forward<Args>(args)...));
+    auto* root = layer_tree_impl->root_layer_for_testing();
+    root->SetBounds(viewport_size);
+    layer_tree_impl->SetDeviceViewportRect(
+        gfx::Rect(DipSizeToPixelSize(viewport_size)));
+    SetupRootProperties(root);
+    return static_cast<T*>(root);
+  }
+
+  LayerImpl* SetupDefaultRootLayer(const gfx::Size& viewport_size) {
+    return SetupRootLayer<LayerImpl>(host_impl_->active_tree(), viewport_size);
+  }
+
+  LayerImpl* root_layer() {
+    return host_impl_->active_tree()->root_layer_for_testing();
+  }
+
+  gfx::Size DipSizeToPixelSize(const gfx::Size& size) {
+    return gfx::ScaleToRoundedSize(
+        size, host_impl_->active_tree()->device_scale_factor());
   }
 
   static gfx::Vector2dF ScrollDelta(LayerImpl* layer_impl) {
@@ -359,138 +372,103 @@
     ASSERT_EQ(0, times_encountered);
   }
 
-  LayerImpl* CreateScrollAndContentsLayers(LayerTreeImpl* layer_tree_impl,
-                                           const gfx::Size& content_size) {
-    // Clear any existing viewport layers that were setup so this function can
-    // be called multiple times.
-    layer_tree_impl->ClearViewportLayers();
+  template <typename T, typename... Args>
+  T* AddLayer(LayerTreeImpl* layer_tree_impl, Args&&... args) {
+    std::unique_ptr<T> layer = T::Create(layer_tree_impl, next_layer_id_++,
+                                         std::forward<Args>(args)...);
+    T* result = layer.get();
+    layer_tree_impl->root_layer_for_testing()->test_properties()->AddChild(
+        std::move(layer));
+    return result;
+  }
 
-    // Create both an inner viewport scroll layer and an outer viewport scroll
-    // layer. The MaxScrollOffset of the outer viewport scroll layer will be
-    // 0x0, so the scrolls will be applied directly to the inner viewport.
-    const int kOuterViewportClipLayerId = 116;
-    const int kOuterViewportScrollLayerId = 117;
-    const int kContentLayerId = 118;
-    const int kInnerViewportScrollLayerId = 2;
-    const int kInnerViewportClipLayerId = 4;
-    const int kPageScaleLayerId = 5;
+  LayerImpl* AddLayer() {
+    return AddLayer<LayerImpl>(host_impl_->active_tree());
+  }
 
-    std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
-    root->SetBounds(content_size);
-    root->test_properties()->position = gfx::PointF();
-    root->test_properties()->force_render_surface = true;
+  void SetupViewportLayers(LayerTreeImpl* layer_tree_impl,
+                           const gfx::Size& inner_viewport_size,
+                           const gfx::Size& outer_viewport_size,
+                           const gfx::Size& content_size) {
+    DCHECK(!layer_tree_impl->root_layer_for_testing());
+    auto* root =
+        SetupRootLayer<LayerImpl>(layer_tree_impl, inner_viewport_size);
+    SetupViewport(root, outer_viewport_size, content_size);
 
-    std::unique_ptr<LayerImpl> inner_scroll =
-        LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId);
-    inner_scroll->test_properties()->is_container_for_fixed_position_layers =
-        true;
-    inner_scroll->layer_tree_impl()
-        ->property_trees()
-        ->scroll_tree.UpdateScrollOffsetBaseForTesting(
-            inner_scroll->element_id(), gfx::ScrollOffset());
-
-    std::unique_ptr<LayerImpl> inner_clip =
-        LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId);
-    gfx::Size viewport_scroll_bounds =
-        gfx::Size(content_size.width() / 2, content_size.height() / 2);
-    inner_clip->SetBounds(viewport_scroll_bounds);
-
-    std::unique_ptr<LayerImpl> page_scale =
-        LayerImpl::Create(layer_tree_impl, kPageScaleLayerId);
-
-    inner_scroll->SetScrollable(viewport_scroll_bounds);
-    inner_scroll->SetHitTestable(true);
-    inner_scroll->SetElementId(
-        LayerIdToElementIdForTesting(inner_scroll->id()));
-    inner_scroll->SetBounds(content_size);
-    inner_scroll->test_properties()->position = gfx::PointF();
-
-    std::unique_ptr<LayerImpl> outer_clip =
-        LayerImpl::Create(layer_tree_impl, kOuterViewportClipLayerId);
-    outer_clip->SetBounds(content_size);
-    outer_clip->test_properties()->is_container_for_fixed_position_layers =
-        true;
-
-    std::unique_ptr<LayerImpl> outer_scroll =
-        LayerImpl::Create(layer_tree_impl, kOuterViewportScrollLayerId);
-    outer_scroll->SetScrollable(content_size);
-    outer_scroll->SetHitTestable(true);
-    outer_scroll->SetElementId(
-        LayerIdToElementIdForTesting(outer_scroll->id()));
-    outer_scroll->layer_tree_impl()
-        ->property_trees()
-        ->scroll_tree.UpdateScrollOffsetBaseForTesting(
-            outer_scroll->element_id(), gfx::ScrollOffset());
-    outer_scroll->SetBounds(content_size);
-    outer_scroll->test_properties()->position = gfx::PointF();
-
-    std::unique_ptr<LayerImpl> contents =
-        LayerImpl::Create(layer_tree_impl, kContentLayerId);
-    contents->SetDrawsContent(true);
-    contents->SetBounds(content_size);
-    contents->test_properties()->position = gfx::PointF();
-
-    outer_scroll->test_properties()->AddChild(std::move(contents));
-    outer_clip->test_properties()->AddChild(std::move(outer_scroll));
-    inner_scroll->test_properties()->AddChild(std::move(outer_clip));
-    page_scale->test_properties()->AddChild(std::move(inner_scroll));
-    inner_clip->test_properties()->AddChild(std::move(page_scale));
-    root->test_properties()->AddChild(std::move(inner_clip));
-
-    layer_tree_impl->SetRootLayerForTesting(std::move(root));
-    layer_tree_impl->BuildPropertyTreesForTesting();
-    LayerTreeImpl::ViewportLayerIds viewport_ids;
-    viewport_ids.page_scale = kPageScaleLayerId;
-    viewport_ids.inner_viewport_container = kInnerViewportClipLayerId;
-    viewport_ids.outer_viewport_container = kOuterViewportClipLayerId;
-    viewport_ids.inner_viewport_scroll = kInnerViewportScrollLayerId;
-    viewport_ids.outer_viewport_scroll = kOuterViewportScrollLayerId;
-    layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
-
+    UpdateDrawProperties(layer_tree_impl);
     layer_tree_impl->DidBecomeActive();
-    return layer_tree_impl->InnerViewportScrollLayer();
   }
 
-  LayerImpl* SetupScrollAndContentsLayers(const gfx::Size& content_size) {
-    LayerImpl* scroll_layer =
-        CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-    host_impl_->active_tree()->DidBecomeActive();
-    return scroll_layer;
+  // Calls SetupViewportLayers() passing |content_size| as that function's
+  // |outer_viewport_size| and |content_size|, which makes the outer viewport
+  // not scrollable, and scrolls will be applied directly to the inner viewport.
+  void SetupViewportLayersInnerScrolls(const gfx::Size& inner_viewport_size,
+                                       const gfx::Size& content_size) {
+    const auto& outer_viewport_size = content_size;
+    SetupViewportLayers(host_impl_->active_tree(), inner_viewport_size,
+                        outer_viewport_size, content_size);
   }
 
-  void CreateAndTestNonScrollableLayers(const bool& transparent_layer) {
+  // Calls SetupViewportLayers() passing |viewport_size| as that function's
+  // |inner_viewport_size| and |outer_viewport_size|, which makes the inner
+  // viewport not scrollable, and scrolls will be applied to the outer
+  // viewport only.
+  void SetupViewportLayersOuterScrolls(const gfx::Size& viewport_size,
+                                       const gfx::Size& content_size) {
+    SetupViewportLayers(host_impl_->active_tree(), viewport_size, viewport_size,
+                        content_size);
+  }
+
+  LayerImpl* AddContentLayer() {
+    LayerImpl* scroll_layer = host_impl_->OuterViewportScrollLayer();
+    DCHECK(scroll_layer);
+    LayerImpl* layer = AddLayer();
+    layer->SetBounds(host_impl_->OuterViewportScrollLayer()->bounds());
+    layer->SetDrawsContent(true);
+    CopyProperties(scroll_layer, layer);
+    return layer;
+  }
+
+  // Calls SetupViewportLayers() with the same |viewport_bounds|,
+  // |inner_scroll_bounds| and |outer_scroll_bounds|, which makes neither
+  // of inner viewport and outer viewport scrollable.
+  void SetupViewportLayersNoScrolls(const gfx::Size& bounds) {
+    SetupViewportLayers(host_impl_->active_tree(), bounds, bounds, bounds);
+  }
+
+  void CreateAndTestNonScrollableLayers(bool transparent_layer) {
     LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
     gfx::Size content_size = gfx::Size(360, 600);
     gfx::Size scroll_content_size = gfx::Size(345, 3800);
     gfx::Size scrollbar_size = gfx::Size(15, 600);
 
-    host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(content_size));
-    std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
-    root->SetBounds(content_size);
-    root->test_properties()->position = gfx::PointF();
+    LayerImpl* root = SetupRootLayer<LayerImpl>(layer_tree_impl, content_size);
+    LayerImpl* scroll =
+        AddScrollableLayer(root, content_size, scroll_content_size);
 
-    std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 3);
-    scroll->SetBounds(scroll_content_size);
-    scroll->SetScrollable(content_size);
-    scroll->SetHitTestable(true);
-    scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-    scroll->SetDrawsContent(true);
+    auto* squash2 = AddLayer<LayerImpl>(layer_tree_impl);
+    squash2->SetBounds(gfx::Size(140, 300));
+    squash2->SetDrawsContent(true);
+    squash2->SetHitTestable(true);
+    CopyProperties(scroll, squash2);
+    squash2->SetOffsetToTransformParent(gfx::Vector2dF(220, 300));
 
-    std::unique_ptr<PaintedScrollbarLayerImpl> scrollbar =
-        PaintedScrollbarLayerImpl::Create(layer_tree_impl, 4, VERTICAL, false,
-                                          true);
+    auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+        layer_tree_impl, VERTICAL, false, true);
     scrollbar->SetBounds(scrollbar_size);
-    scrollbar->test_properties()->position = gfx::PointF(345, 0);
     scrollbar->SetScrollElementId(scroll->element_id());
     scrollbar->SetDrawsContent(true);
     scrollbar->SetHitTestable(true);
-    scrollbar->test_properties()->opacity = 1.f;
+    CopyProperties(root, scrollbar);
+    scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
+    CreateEffectNode(scrollbar).opacity = 1.f;
 
-    std::unique_ptr<LayerImpl> squash1 = LayerImpl::Create(layer_tree_impl, 5);
+    auto* squash1 = AddLayer<LayerImpl>(layer_tree_impl);
     squash1->SetBounds(gfx::Size(140, 300));
-    squash1->test_properties()->position = gfx::PointF(220, 0);
+    CopyProperties(root, squash1);
+    squash1->SetOffsetToTransformParent(gfx::Vector2dF(220, 0));
     if (transparent_layer) {
-      squash1->test_properties()->opacity = 0.0f;
+      CreateEffectNode(squash1).opacity = 0.0f;
       // The transparent layer should still participate in hit testing even
       // through it does not draw content.
       squash1->SetHitTestable(true);
@@ -499,19 +477,7 @@
       squash1->SetHitTestable(true);
     }
 
-    std::unique_ptr<LayerImpl> squash2 = LayerImpl::Create(layer_tree_impl, 6);
-    squash2->SetBounds(gfx::Size(140, 300));
-    squash2->test_properties()->position = gfx::PointF(220, 300);
-    squash2->SetDrawsContent(true);
-    squash2->SetHitTestable(true);
-
-    scroll->test_properties()->AddChild(std::move(squash2));
-    root->test_properties()->AddChild(std::move(scroll));
-    root->test_properties()->AddChild(std::move(scrollbar));
-    root->test_properties()->AddChild(std::move(squash1));
-
-    layer_tree_impl->SetRootLayerForTesting(std::move(root));
-    layer_tree_impl->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(layer_tree_impl);
     layer_tree_impl->DidBecomeActive();
 
     // The point hits squash1 layer and also scroll layer, because scroll layer
@@ -536,55 +502,18 @@
     ASSERT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
   }
 
-  // Sets up a typical virtual viewport setup with one child content layer.
-  // Returns a pointer to the content layer.
-  LayerImpl* CreateBasicVirtualViewportLayers(const gfx::Size& viewport_size,
-                                              const gfx::Size& content_size) {
-    // CreateScrollAndContentsLayers makes the outer viewport unscrollable and
-    // the inner a different size from the outer. We'll reuse its layer
-    // hierarchy but adjust the sizing to our needs.
-    CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-
-    LayerImpl* content_layer = host_impl_->OuterViewportScrollLayer()
-                                   ->test_properties()
-                                   ->children.back();
-    content_layer->SetBounds(content_size);
-    host_impl_->OuterViewportScrollLayer()->SetBounds(content_size);
-    host_impl_->OuterViewportScrollLayer()->SetScrollable(viewport_size);
-    host_impl_->OuterViewportScrollLayer()->SetHitTestable(true);
-
-    LayerImpl* outer_clip =
-        host_impl_->OuterViewportScrollLayer()->test_properties()->parent;
-    outer_clip->SetBounds(viewport_size);
-
-    LayerImpl* inner_clip_layer = host_impl_->InnerViewportScrollLayer()
-                                      ->test_properties()
-                                      ->parent->test_properties()
-                                      ->parent;
-    inner_clip_layer->SetBounds(viewport_size);
-    host_impl_->InnerViewportScrollLayer()->SetBounds(viewport_size);
-    host_impl_->InnerViewportScrollLayer()->SetScrollable(viewport_size);
-    host_impl_->InnerViewportScrollLayer()->SetHitTestable(true);
-
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-    host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
-    host_impl_->active_tree()->DidBecomeActive();
-
-    return content_layer;
-  }
-
-  std::unique_ptr<LayerImpl> CreateScrollableLayer(int id,
-                                                   const gfx::Size& size) {
-    std::unique_ptr<LayerImpl> layer =
-        LayerImpl::Create(host_impl_->active_tree(), id);
+  LayerImpl* AddScrollableLayer(LayerImpl* container,
+                                const gfx::Size& scroll_container_bounds,
+                                const gfx::Size& content_size) {
+    LayerImpl* layer = AddLayer<LayerImpl>(container->layer_tree_impl());
     layer->SetElementId(LayerIdToElementIdForTesting(layer->id()));
     layer->SetDrawsContent(true);
-    layer->SetBounds(size);
-    gfx::Size scroll_container_bounds =
-        gfx::Size(size.width() / 2, size.height() / 2);
+    layer->SetBounds(content_size);
     layer->SetScrollable(scroll_container_bounds);
     layer->SetHitTestable(true);
+    CopyProperties(container, layer);
+    CreateTransformNode(layer);
+    CreateScrollNode(layer);
     return layer;
   }
 
@@ -618,7 +547,15 @@
     return scroll_state;
   }
 
+  void UpdateDrawProperties(LayerTreeImpl* layer_tree_impl) {
+    LayerTreeHostCommon::PrepareForUpdateDrawPropertiesForTesting(
+        layer_tree_impl);
+    layer_tree_impl->UpdateDrawProperties();
+  }
+
   void DrawFrame() {
+    LayerTreeHostCommon::PrepareForUpdateDrawPropertiesForTesting(
+        host_impl_->active_tree());
     TestFrameData frame;
     EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
     host_impl_->DrawLayers(&frame);
@@ -645,14 +582,10 @@
     // Create the pending tree.
     host_impl_->BeginCommit();
     LayerTreeImpl* pending_tree = host_impl_->pending_tree();
-    pending_tree->SetDeviceViewportRect(gfx::Rect(layer_size));
-    pending_tree->SetRootLayerForTesting(
-        FakePictureLayerImpl::CreateWithRasterSource(pending_tree, 1,
-                                                     raster_source));
-    auto* root = static_cast<FakePictureLayerImpl*>(*pending_tree->begin());
-    root->SetBounds(layer_size);
+    LayerImpl* root = SetupRootLayer<FakePictureLayerImplWithRasterSource>(
+        pending_tree, layer_size, raster_source);
     root->SetDrawsContent(true);
-    pending_tree->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(pending_tree);
 
     // CompleteCommit which should perform a PrepareTiles, adding tilings for
     // the root layer, each one having a raster task.
@@ -669,10 +602,8 @@
 
   void WhiteListedTouchActionTestHelper(float device_scale_factor,
                                         float page_scale_factor) {
-    LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(200, 200));
-    host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
+    SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
     DrawFrame();
-    LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
 
     // Just hard code some random number, we care about the actual page scale
     // factor on the active tree.
@@ -682,18 +613,14 @@
         page_scale_factor, min_page_scale_factor, max_page_scale_factor);
     host_impl_->active_tree()->SetDeviceScaleFactor(device_scale_factor);
 
-    std::unique_ptr<LayerImpl> child_layer =
-        LayerImpl::Create(host_impl_->active_tree(), 6);
-    LayerImpl* child = child_layer.get();
-    child_layer->SetDrawsContent(true);
-    child_layer->test_properties()->position = gfx::PointF(0, 0);
-    child_layer->SetBounds(gfx::Size(25, 25));
-    scroll->test_properties()->AddChild(std::move(child_layer));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    LayerImpl* child = AddLayer();
+    child->SetDrawsContent(true);
+    child->SetBounds(gfx::Size(25, 25));
+    CopyProperties(host_impl_->InnerViewportScrollLayer(), child);
 
     TouchActionRegion root_touch_action_region;
     root_touch_action_region.Union(kTouchActionPanX, gfx::Rect(0, 0, 50, 50));
-    root->SetTouchActionRegion(root_touch_action_region);
+    root_layer()->SetTouchActionRegion(root_touch_action_region);
     TouchActionRegion child_touch_action_region;
     child_touch_action_region.Union(kTouchActionPanLeft,
                                     gfx::Rect(0, 0, 25, 25));
@@ -722,38 +649,30 @@
   }
 
   LayerImpl* CreateLayerForSnapping() {
-    SetupScrollAndContentsLayers(gfx::Size(200, 200));
-    LayerImpl* scroll_layer =
-        host_impl_->active_tree()->OuterViewportScrollLayer();
-
-    host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
+    SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
 
     gfx::Size overflow_size(400, 400);
-    EXPECT_EQ(1u, scroll_layer->test_properties()->children.size());
-    LayerImpl* overflow = scroll_layer->test_properties()->children[0];
-    overflow->SetBounds(overflow_size);
-    overflow->SetScrollable(gfx::Size(100, 100));
-    overflow->SetHitTestable(true);
-    overflow->SetElementId(LayerIdToElementIdForTesting(overflow->id()));
-    overflow->layer_tree_impl()
-        ->property_trees()
-        ->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->element_id(),
-                                                       gfx::ScrollOffset());
-    overflow->test_properties()->position = gfx::PointF(0, 0);
-
+    LayerImpl* overflow =
+        AddScrollableLayer(host_impl_->OuterViewportScrollLayer(),
+                           gfx::Size(100, 100), overflow_size);
     SnapContainerData container_data(
         ScrollSnapType(false, SnapAxis::kBoth, SnapStrictness::kMandatory),
         gfx::RectF(0, 0, 200, 200), gfx::ScrollOffset(300, 300));
     SnapAreaData area_data(ScrollSnapAlign(SnapAlignment::kStart),
                            gfx::RectF(50, 50, 100, 100), false);
     container_data.AddSnapAreaData(area_data);
-    overflow->test_properties()->snap_container_data.emplace(container_data);
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    GetScrollNode(overflow)->snap_container_data.emplace(container_data);
     DrawFrame();
 
     return overflow;
   }
 
+  void ClearLayersAndPropertyTrees(LayerTreeImpl* layer_tree_impl) {
+    layer_tree_impl->SetRootLayerForTesting(nullptr);
+    layer_tree_impl->DetachLayers();
+    layer_tree_impl->property_trees()->clear();
+  }
+
   void pinch_zoom_pan_viewport_forces_commit_redraw(float device_scale_factor);
   void pinch_zoom_pan_viewport_test(float device_scale_factor);
   void pinch_zoom_pan_viewport_and_scroll_test(float device_scale_factor);
@@ -772,6 +691,8 @@
   }
 
   void DrawOneFrame() {
+    LayerTreeHostCommon::PrepareForUpdateDrawPropertiesForTesting(
+        host_impl_->active_tree());
     TestFrameData frame_data;
     host_impl_->PrepareToDraw(&frame_data);
     host_impl_->DidDrawAllLayers(frame_data);
@@ -828,6 +749,7 @@
   viz::RenderPassList last_on_draw_render_passes_;
   scoped_refptr<AnimationTimeline> timeline_;
   std::unique_ptr<base::Thread> image_worker_;
+  int next_layer_id_ = 2;
 };
 
 class CommitToPendingTreeLayerTreeHostImplTest : public LayerTreeHostImplTest {
@@ -931,19 +853,18 @@
   on_can_draw_state_changed_called_ = false;
 
   // Set up the root layer, which allows us to draw.
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   EXPECT_TRUE(host_impl_->CanDraw());
   EXPECT_TRUE(on_can_draw_state_changed_called_);
   on_can_draw_state_changed_called_ = false;
 
   // Toggle the root layer to make sure it toggles can_draw
-  host_impl_->active_tree()->SetRootLayerForTesting(nullptr);
-  host_impl_->active_tree()->DetachLayers();
+  ClearLayersAndPropertyTrees(host_impl_->active_tree());
   EXPECT_FALSE(host_impl_->CanDraw());
   EXPECT_TRUE(on_can_draw_state_changed_called_);
   on_can_draw_state_changed_called_ = false;
 
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   EXPECT_TRUE(host_impl_->CanDraw());
   EXPECT_TRUE(on_can_draw_state_changed_called_);
   on_can_draw_state_changed_called_ = false;
@@ -962,8 +883,7 @@
 
 TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport) {
   CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware());
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
 
   EXPECT_TRUE(host_impl_->CanDraw());
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect());
@@ -980,7 +900,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers) {
-  ASSERT_FALSE(host_impl_->active_tree()->root_layer_for_testing());
+  host_impl_->active_tree()->SetRootLayerForTesting(nullptr);
 
   std::unique_ptr<ScrollAndScaleSet> scroll_info =
       host_impl_->ProcessScrollDeltas();
@@ -988,26 +908,19 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges) {
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
   {
-    std::unique_ptr<LayerImpl> root =
-        LayerImpl::Create(host_impl_->active_tree(), 1);
-    root->test_properties()->AddChild(
-        LayerImpl::Create(host_impl_->active_tree(), 2));
-    root->test_properties()->AddChild(
-        LayerImpl::Create(host_impl_->active_tree(), 3));
-    root->test_properties()->children[1]->test_properties()->AddChild(
-        LayerImpl::Create(host_impl_->active_tree(), 4));
-    root->test_properties()->children[1]->test_properties()->AddChild(
-        LayerImpl::Create(host_impl_->active_tree(), 5));
-    root->test_properties()
-        ->children[1]
-        ->test_properties()
-        ->children[0]
-        ->test_properties()
-        ->AddChild(LayerImpl::Create(host_impl_->active_tree(), 6));
-    host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
+    LayerImpl* child1 = AddLayer();
+    CopyProperties(root, child1);
+    LayerImpl* child2 = AddLayer();
+    CopyProperties(root, child2);
+    LayerImpl* grand_child1 = AddLayer();
+    CopyProperties(child2, grand_child1);
+    LayerImpl* grand_child2 = AddLayer();
+    CopyProperties(child2, grand_child2);
+    LayerImpl* great_grand_child = AddLayer();
+    CopyProperties(grand_child1, great_grand_child);
   }
-  LayerImpl* root = *host_impl_->active_tree()->begin();
 
   ExpectClearedScrollDeltasRecursive(root);
 
@@ -1026,19 +939,15 @@
   gfx::ScrollOffset scroll_offset(20, 30);
   gfx::ScrollOffset scroll_delta(11, -15);
 
-  auto root_owned = LayerImpl::Create(host_impl_->active_tree(), 1);
-  auto* root = root_owned.get();
-
-  root->SetBounds(gfx::Size(110, 110));
-  root->SetScrollable(gfx::Size(10, 10));
+  auto* root = SetupDefaultRootLayer(gfx::Size(110, 110));
   root->SetHitTestable(true);
-  root->SetElementId(LayerIdToElementIdForTesting(root->id()));
+  root->SetScrollable(gfx::Size(10, 10));
   root->layer_tree_impl()
       ->property_trees()
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(root->element_id(),
                                                      scroll_offset);
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_owned));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CreateScrollNode(root);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   std::unique_ptr<ScrollAndScaleSet> scroll_info;
 
@@ -1102,10 +1011,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
@@ -1124,10 +1030,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollActiveOnlyAfterScrollMovement) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
@@ -1162,7 +1065,7 @@
       CreateHostImpl(DefaultSettings(),
                      FakeLayerTreeFrameSink::Create3d(std::move(gl_owned))));
 
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
 
   // We should not crash when trying to scroll after the renderer initialization
   // fails.
@@ -1174,8 +1077,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   // We should not crash if the tree is replaced while we are scrolling.
@@ -1184,9 +1086,10 @@
       host_impl_
           ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
           .thread);
-  host_impl_->active_tree()->DetachLayers();
+  ClearLayersAndPropertyTrees(host_impl_->active_tree());
 
-  scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
 
   // We should still be scrolling, because the scrolled layer also exists in the
   // new tree.
@@ -1202,8 +1105,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers) {
-  LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll = host_impl_->InnerViewportScrollLayer();
   scroll->SetWheelEventHandlerRegion(Region(gfx::Rect(20, 20)));
   DrawFrame();
 
@@ -1229,22 +1132,16 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers) {
-  LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
 
-  LayerImpl* child = nullptr;
-  {
-    std::unique_ptr<LayerImpl> child_layer =
-        LayerImpl::Create(host_impl_->active_tree(), 6);
-    child = child_layer.get();
-    child_layer->SetDrawsContent(true);
-    child_layer->test_properties()->position = gfx::PointF(0, 20);
-    child_layer->SetBounds(gfx::Size(50, 50));
-    scroll->test_properties()->AddChild(std::move(child_layer));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  }
+  LayerImpl* root = root_layer();
+  LayerImpl* scroll = host_impl_->InnerViewportScrollLayer();
+  LayerImpl* child = AddLayer();
+  child->SetDrawsContent(true);
+  child->SetBounds(gfx::Size(50, 50));
+  CopyProperties(scroll, child);
+  child->SetOffsetToTransformParent(gfx::Vector2dF(0, 20));
 
   // Touch handler regions determine whether touch events block scroll.
   TouchAction touch_action;
@@ -1283,13 +1180,9 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
-
-  root->test_properties()->main_thread_scrolling_reasons =
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  host_impl_->InnerViewportScrollNode()->main_thread_scrolling_reasons =
       MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
 
   InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
@@ -1320,41 +1213,28 @@
   gfx::Size scroll_content_size = gfx::Size(345, 3800);
   gfx::Size scrollbar_size = gfx::Size(15, 600);
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(content_size));
-  std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
-  root->SetBounds(content_size);
-  root->test_properties()->position = gfx::PointF();
+  LayerImpl* root = SetupDefaultRootLayer(content_size);
+  LayerImpl* scroll =
+      AddScrollableLayer(root, content_size, scroll_content_size);
 
-  std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 3);
-  scroll->SetBounds(scroll_content_size);
-  scroll->SetScrollable(content_size);
-  scroll->SetHitTestable(true);
-  scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-  scroll->SetDrawsContent(true);
-  scroll->SetHitTestable(true);
-
-  std::unique_ptr<PaintedScrollbarLayerImpl> drawn_scrollbar =
-      PaintedScrollbarLayerImpl::Create(layer_tree_impl, 4, VERTICAL, false,
-                                        true);
+  auto* drawn_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      layer_tree_impl, VERTICAL, false, true);
   drawn_scrollbar->SetBounds(scrollbar_size);
-  drawn_scrollbar->test_properties()->position = gfx::PointF(345, 0);
   drawn_scrollbar->SetScrollElementId(scroll->element_id());
   drawn_scrollbar->SetDrawsContent(true);
   drawn_scrollbar->SetHitTestable(true);
-  drawn_scrollbar->test_properties()->opacity = 1.f;
+  CopyProperties(scroll, drawn_scrollbar);
+  drawn_scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
+  CreateEffectNode(drawn_scrollbar).opacity = 1.f;
 
-  std::unique_ptr<LayerImpl> squash = LayerImpl::Create(layer_tree_impl, 5);
+  LayerImpl* squash = AddLayer();
   squash->SetBounds(gfx::Size(140, 300));
-  squash->test_properties()->position = gfx::PointF(220, 0);
   squash->SetDrawsContent(true);
   squash->SetHitTestable(true);
+  CopyProperties(scroll, squash);
+  squash->SetOffsetToTransformParent(gfx::Vector2dF(220, 0));
 
-  scroll->test_properties()->AddChild(std::move(drawn_scrollbar));
-  scroll->test_properties()->AddChild(std::move(squash));
-  root->test_properties()->AddChild(std::move(scroll));
-
-  layer_tree_impl->SetRootLayerForTesting(std::move(root));
-  layer_tree_impl->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(layer_tree_impl);
   layer_tree_impl->DidBecomeActive();
 
   // The point hits squash layer and also scrollbar layer, but because the
@@ -1375,13 +1255,11 @@
 }
 
 TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic) {
-  SetupScrollAndContentsLayers(gfx::Size(200, 200));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
 
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   outer_scroll->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
 
   // All scroll types inside the non-fast scrollable region should fail.
@@ -1424,15 +1302,13 @@
 }
 
 TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset) {
-  SetupScrollAndContentsLayers(gfx::Size(200, 200));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
 
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   outer_scroll->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
-  outer_scroll->test_properties()->position = gfx::PointF(-25.f, 0.f);
+  SetPostTranslation(outer_scroll, gfx::Vector2dF(-25.f, 0.f));
   outer_scroll->SetDrawsContent(true);
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
 
   // This point would fall into the non-fast scrollable region except that we've
@@ -1456,9 +1332,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent) {
-  SetupScrollAndContentsLayers(gfx::Size(200, 200));
+  SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
   EXPECT_FALSE(host_impl_->active_tree()->have_scroll_event_handlers());
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
   DrawFrame();
 
   EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
@@ -1470,9 +1345,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent) {
-  SetupScrollAndContentsLayers(gfx::Size(200, 200));
+  SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
   host_impl_->active_tree()->set_have_scroll_event_handlers(true);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
   DrawFrame();
 
   EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
@@ -1484,9 +1358,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollByReturnsCorrectValue) {
-  SetupScrollAndContentsLayers(gfx::Size(200, 200));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
-
+  SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
   DrawFrame();
 
   InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
@@ -1759,26 +1631,15 @@
 TEST_F(LayerTreeHostImplTest, OverscrollBehaviorPreventsPropagation) {
   const gfx::Size kViewportSize(100, 100);
   const gfx::Size kContentSize(200, 200);
-  CreateBasicVirtualViewportLayers(kViewportSize, kContentSize);
+  SetupViewportLayersOuterScrolls(kViewportSize, kContentSize);
 
-  LayerImpl* scroll_layer =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(kViewportSize));
+  LayerImpl* scroll_layer = host_impl_->OuterViewportScrollLayer();
 
   gfx::Size overflow_size(400, 400);
-  ASSERT_EQ(1u, scroll_layer->test_properties()->children.size());
-  LayerImpl* overflow = scroll_layer->test_properties()->children[0];
-  overflow->SetBounds(overflow_size);
-  overflow->SetScrollable(gfx::Size(100, 100));
-  overflow->SetHitTestable(true);
-  overflow->SetElementId(LayerIdToElementIdForTesting(overflow->id()));
-  overflow->layer_tree_impl()
-      ->property_trees()
-      ->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->element_id(),
-                                                     gfx::ScrollOffset());
-  overflow->test_properties()->position = gfx::PointF(40, 40);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset(30, 30));
+  LayerImpl* overflow =
+      AddScrollableLayer(host_impl_->OuterViewportScrollLayer(),
+                         gfx::Size(100, 100), overflow_size);
+  SetScrollOffset(scroll_layer, gfx::ScrollOffset(30, 30));
 
   DrawFrame();
   gfx::Point pointer_position(50, 50);
@@ -1931,25 +1792,13 @@
 TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers) {
   const gfx::Size kViewportSize(100, 100);
   const gfx::Size kContentSize(200, 200);
-  CreateBasicVirtualViewportLayers(kViewportSize, kContentSize);
+  SetupViewportLayersOuterScrolls(kViewportSize, kContentSize);
 
-  LayerImpl* scroll_layer =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(kViewportSize));
+  LayerImpl* scroll_layer = host_impl_->OuterViewportScrollLayer();
 
   gfx::Size overflow_size(400, 400);
-  ASSERT_EQ(1u, scroll_layer->test_properties()->children.size());
-  LayerImpl* overflow = scroll_layer->test_properties()->children[0];
-  overflow->SetBounds(overflow_size);
-  overflow->SetScrollable(gfx::Size(100, 100));
-  overflow->SetHitTestable(true);
-  overflow->SetElementId(LayerIdToElementIdForTesting(overflow->id()));
-  overflow->layer_tree_impl()
-      ->property_trees()
-      ->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->element_id(),
-                                                     gfx::ScrollOffset());
-  overflow->test_properties()->position = gfx::PointF();
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  LayerImpl* overflow =
+      AddScrollableLayer(scroll_layer, gfx::Size(100, 100), overflow_size);
 
   DrawFrame();
   gfx::Point scroll_position(10, 10);
@@ -1968,8 +1817,7 @@
   EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());
 
-  overflow->test_properties()->user_scrollable_horizontal = false;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  GetScrollNode(overflow)->user_scrollable_horizontal = false;
 
   DrawFrame();
 
@@ -1986,8 +1834,7 @@
   EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), scroll_layer->CurrentScrollOffset());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
 
-  overflow->test_properties()->user_scrollable_vertical = false;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  GetScrollNode(overflow)->user_scrollable_vertical = false;
   DrawFrame();
 
   EXPECT_EQ(
@@ -2005,13 +1852,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ForceMainThreadScrollWithoutScrollLayer) {
-  SetupScrollAndContentsLayers(gfx::Size(200, 200));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
-
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  ScrollNode* scroll_node =
-      host_impl_->active_tree()->property_trees()->scroll_tree.Node(
-          host_impl_->OuterViewportScrollLayer()->scroll_tree_index());
+  SetupViewportLayersInnerScrolls(gfx::Size(100, 100), gfx::Size(200, 200));
+  ScrollNode* scroll_node = host_impl_->OuterViewportScrollNode();
   // Change the scroll node so that it no longer has an associated layer.
   scroll_node->element_id = ElementId(42);
 
@@ -2028,29 +1870,23 @@
        AnimationSchedulingPendingTree) {
   EXPECT_FALSE(host_impl_->CommitToActiveTree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-
   CreatePendingTree();
-  auto root_owned = LayerImpl::Create(host_impl_->pending_tree(), 1);
-  auto* root = root_owned.get();
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(root_owned));
-  root->SetBounds(gfx::Size(50, 50));
-  root->test_properties()->force_render_surface = true;
+  auto* root =
+      SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), gfx::Size(50, 50));
   root->SetNeedsPushProperties();
 
-  root->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->pending_tree(), 2));
-  LayerImpl* child = root->test_properties()->children[0];
+  auto* child = AddLayer<LayerImpl>(host_impl_->pending_tree());
   child->SetBounds(gfx::Size(10, 10));
-  child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
   child->SetDrawsContent(true);
   child->SetNeedsPushProperties();
 
   host_impl_->pending_tree()->SetElementIdsForTesting();
+  CopyProperties(root, child);
+  CreateTransformNode(child);
 
   AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
                                              10.0, 3, 0);
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->pending_tree());
 
   EXPECT_FALSE(did_request_next_frame_);
   EXPECT_FALSE(did_request_redraw_);
@@ -2088,21 +1924,14 @@
        AnimationSchedulingActiveTree) {
   EXPECT_FALSE(host_impl_->CommitToActiveTree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      LayerImpl::Create(host_impl_->active_tree(), 1));
-  LayerImpl* root = *host_impl_->active_tree()->begin();
-  root->SetBounds(gfx::Size(50, 50));
-  root->test_properties()->force_render_surface = true;
-
-  root->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->active_tree(), 2));
-  LayerImpl* child = root->test_properties()->children[0];
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(50, 50));
+  LayerImpl* child = AddLayer();
   child->SetBounds(gfx::Size(10, 10));
-  child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
   child->SetDrawsContent(true);
+
   host_impl_->active_tree()->SetElementIdsForTesting();
+  CopyProperties(root, child);
+  CreateTransformNode(child);
 
   // Add a translate from 6,7 to 8,9.
   TransformOperations start;
@@ -2111,7 +1940,7 @@
   end.AppendTranslate(8.f, 9.f, 0.f);
   AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
                                              4.0, start, end);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   base::TimeTicks now = base::TimeTicks::Now();
   host_impl_->WillBeginImplFrame(
@@ -2150,26 +1979,17 @@
 }
 
 TEST_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree) {
-  FakeImplTaskRunnerProvider provider(nullptr);
-  CreateHostImplWithTaskRunnerProvider(DefaultSettings(),
-                                       CreateLayerTreeFrameSink(), &provider);
   EXPECT_TRUE(host_impl_->CommitToActiveTree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  auto* root = SetupDefaultRootLayer(gfx::Size(50, 50));
 
-  auto root_owned = LayerImpl::Create(host_impl_->active_tree(), 1);
-  auto* root = root_owned.get();
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_owned));
-  root->SetBounds(gfx::Size(50, 50));
-
-  auto child_owned = LayerImpl::Create(host_impl_->active_tree(), 2);
-  auto* child = child_owned.get();
-  root->test_properties()->AddChild(std::move(child_owned));
+  auto* child = AddLayer();
   child->SetBounds(gfx::Size(10, 10));
-  child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
   child->SetDrawsContent(true);
 
   host_impl_->active_tree()->SetElementIdsForTesting();
+  CopyProperties(root, child);
+  CreateTransformNode(child);
 
   AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
                                              10.0, 3, 0);
@@ -2199,21 +2019,15 @@
 }
 
 TEST_F(LayerTreeHostImplTest, AnimationSchedulingOnLayerDestruction) {
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(50, 50));
 
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      LayerImpl::Create(host_impl_->active_tree(), 1));
-  LayerImpl* root = *host_impl_->active_tree()->begin();
-  root->SetBounds(gfx::Size(50, 50));
-
-  root->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->active_tree(), 2));
-  LayerImpl* child = root->test_properties()->children[0];
+  LayerImpl* child = AddLayer();
   child->SetBounds(gfx::Size(10, 10));
-  child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
   child->SetDrawsContent(true);
 
   host_impl_->active_tree()->SetElementIdsForTesting();
+  CopyProperties(root, child);
+  CreateTransformNode(child);
 
   // Add a translate animation.
   TransformOperations start;
@@ -2222,7 +2036,7 @@
   end.AppendTranslate(8.f, 9.f, 0.f);
   AddAnimatedTransformToElementWithAnimation(child->element_id(), timeline(),
                                              4.0, start, end);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   base::TimeTicks now = base::TimeTicks::Now();
   host_impl_->WillBeginImplFrame(
@@ -2251,10 +2065,10 @@
   // remove the property tree nodes for the removed layer. In the real code,
   // you cannot remove a child on LayerImpl, but a child removed on Layer
   // will force a full tree sync which will rebuild property trees without that
-  // child's property tree nodes. Call BuildPropertyTrees to simulate the
-  // rebuild that would happen during the commit.
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  child = nullptr;
+  // child's property tree nodes. Clear property trees to simulate the rebuild
+  // that would happen before/during the commit.
+  host_impl_->active_tree()->property_trees()->clear();
+  host_impl_->UpdateElements(ElementListType::ACTIVE);
 
   // On updating state, we will send an animation event and request one last
   // frame.
@@ -2289,13 +2103,10 @@
 };
 
 TEST_F(LayerTreeHostImplTest, ImplPinchZoom) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
-  EXPECT_EQ(scroll_layer, host_impl_->InnerViewportScrollLayer());
+  LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
   LayerImpl* container_layer = host_impl_->InnerViewportContainerLayer();
   EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());
 
@@ -2383,50 +2194,36 @@
   // size.
   const gfx::Size outer_viewport_size = content_size;
 
-  PaintedScrollbarLayerImpl* v_scrollbar;
-  PaintedScrollbarLayerImpl* h_scrollbar;
-
   // Setup
-  {
-    LayerTreeSettings settings = DefaultSettings();
-    CreateHostImpl(settings, CreateLayerTreeFrameSink());
-    LayerTreeImpl* active_tree = host_impl_->active_tree();
-    active_tree->PushPageScaleFromMainThread(1.f, minimum_scale, 4.f);
+  LayerTreeImpl* active_tree = host_impl_->active_tree();
+  active_tree->PushPageScaleFromMainThread(1.f, minimum_scale, 4.f);
 
-    CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  // When Chrome on Android loads a non-mobile page, it resizes the main
+  // frame (outer viewport) such that it matches the width of the content,
+  // preventing horizontal scrolling. Replicate that behavior here.
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
 
-    // When Chrome on Android loads a non-mobile page, it resizes the main
-    // frame (outer viewport) such that it matches the width of the content,
-    // preventing horizontal scrolling. Replicate that behavior here.
-    host_impl_->OuterViewportScrollLayer()->SetScrollable(outer_viewport_size);
-    host_impl_->OuterViewportScrollLayer()->SetHitTestable(true);
-    LayerImpl* outer_clip =
-        host_impl_->OuterViewportScrollLayer()->test_properties()->parent;
-    outer_clip->SetBounds(outer_viewport_size);
+  LayerImpl* scroll = active_tree->OuterViewportScrollLayer();
+  ASSERT_EQ(scroll->scroll_container_bounds(), outer_viewport_size);
+  host_impl_->OuterViewportScrollLayer()->SetHitTestable(true);
+  LayerImpl* outer_clip = active_tree->OuterViewportContainerLayer();
+  ASSERT_EQ(outer_viewport_size, outer_clip->bounds());
 
-    // Add scrollbars. They will always exist - even if unscrollable - but their
-    // visibility will be determined by whether the content can be scrolled.
-    {
-      std::unique_ptr<PaintedScrollbarLayerImpl> v_scrollbar_unique =
-          PaintedScrollbarLayerImpl::Create(active_tree, 400, VERTICAL, false,
-                                            true);
-      std::unique_ptr<PaintedScrollbarLayerImpl> h_scrollbar_unique =
-          PaintedScrollbarLayerImpl::Create(active_tree, 401, HORIZONTAL, false,
-                                            true);
-      v_scrollbar = v_scrollbar_unique.get();
-      h_scrollbar = h_scrollbar_unique.get();
+  // Add scrollbars. They will always exist - even if unscrollable - but their
+  // visibility will be determined by whether the content can be scrolled.
+  auto* v_scrollbar =
+      AddLayer<PaintedScrollbarLayerImpl>(active_tree, VERTICAL, false, true);
+  auto* h_scrollbar =
+      AddLayer<PaintedScrollbarLayerImpl>(active_tree, HORIZONTAL, false, true);
 
-      LayerImpl* scroll = active_tree->OuterViewportScrollLayer();
-      LayerImpl* root = active_tree->InnerViewportContainerLayer();
-      v_scrollbar_unique->SetScrollElementId(scroll->element_id());
-      h_scrollbar_unique->SetScrollElementId(scroll->element_id());
-      root->test_properties()->AddChild(std::move(v_scrollbar_unique));
-      root->test_properties()->AddChild(std::move(h_scrollbar_unique));
-    }
+  v_scrollbar->SetScrollElementId(scroll->element_id());
+  h_scrollbar->SetScrollElementId(scroll->element_id());
 
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
-    host_impl_->active_tree()->DidBecomeActive();
-  }
+  auto* inner_viewport_container = active_tree->InnerViewportContainerLayer();
+  CopyProperties(inner_viewport_container, v_scrollbar);
+  CopyProperties(inner_viewport_container, h_scrollbar);
+
+  host_impl_->active_tree()->DidBecomeActive();
 
   // Zoom out to the minimum scale. The scrollbars shoud not be scrollable.
   host_impl_->active_tree()->SetPageScaleOnActiveTree(0.f);
@@ -2446,13 +2243,13 @@
 
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   outer_scroll_layer->SetDrawsContent(true);
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
   inner_scroll_layer->SetDrawsContent(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_VECTOR_EQ(gfx::Vector2dF(500, 500),
                    outer_scroll_layer->MaxScrollOffset());
@@ -2499,19 +2296,17 @@
 // Make sure scrolls smaller than a unit applied to the viewport don't get
 // dropped. crbug.com/539334.
 TEST_F(LayerTreeHostImplTest, ScrollViewportWithFractionalAmounts) {
-  LayerTreeSettings settings = DefaultSettings();
-  CreateHostImpl(settings, CreateLayerTreeFrameSink());
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
 
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   outer_scroll_layer->SetDrawsContent(true);
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
   inner_scroll_layer->SetDrawsContent(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Sanity checks.
   EXPECT_VECTOR_EQ(gfx::Vector2dF(500, 500),
@@ -2554,13 +2349,13 @@
 
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   outer_scroll_layer->SetDrawsContent(true);
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
   inner_scroll_layer->SetDrawsContent(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_VECTOR_EQ(gfx::Vector2dF(500, 500),
                    outer_scroll_layer->MaxScrollOffset());
@@ -2608,7 +2403,7 @@
 
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   int offsetFromEdge = Viewport::kPinchZoomSnapMarginDips - 5;
   gfx::Point anchor(viewport_size.width() - offsetFromEdge,
@@ -2685,7 +2480,7 @@
 TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports) {
   const gfx::Size content_size(200, 200);
   const gfx::Size viewport_size(100, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -2743,7 +2538,7 @@
   std::unique_ptr<SwapPromise> swap_promise(
       new LatencyInfoSwapPromise(latency_info));
 
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
             host_impl_
                 ->ScrollBegin(BeginState(gfx::Point()).get(),
@@ -2763,45 +2558,23 @@
 // Test that scrolls targeting a layer with a non-null scroll_parent() don't
 // bubble up.
 TEST_F(LayerTreeHostImplTest, ScrollDoesntBubble) {
-  LayerImpl* viewport_scroll =
-      SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  // This is to build property tree for viewport layers. The remaining part of
-  // this test is in layer list mode.
-  // TODO(crbug.com/994361): Avoid PropertyTreeBuilder.
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  LayerImpl* viewport_scroll = host_impl_->InnerViewportScrollLayer();
 
   // Set up two scrolling children of the root, one of which is a scroll parent
   // to the other. Scrolls shouldn't bubbling from the child.
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
-  LayerImpl* parent;
-  LayerImpl* child;
-  LayerImpl* child_clip;
+  LayerImpl* scroll_parent =
+      AddScrollableLayer(viewport_scroll, gfx::Size(5, 5), gfx::Size(10, 10));
 
-  std::unique_ptr<LayerImpl> scroll_parent =
-      CreateScrollableLayer(7, gfx::Size(10, 10));
-  parent = scroll_parent.get();
-  root->test_properties()->AddChild(std::move(scroll_parent));
-  CopyProperties(viewport_scroll, parent);
-  CreateTransformNode(parent);
-  CreateScrollNode(parent);
+  LayerImpl* scroll_child_clip = AddLayer();
+  // scroll_child_clip scrolls in scroll_parent, but under viewport_scroll's
+  // effect.
+  CopyProperties(scroll_parent, scroll_child_clip);
+  scroll_child_clip->SetEffectTreeIndex(viewport_scroll->effect_tree_index());
 
-  std::unique_ptr<LayerImpl> scroll_child_clip =
-      LayerImpl::Create(host_impl_->active_tree(), 8);
-  child_clip = scroll_child_clip.get();
-  root->test_properties()->AddChild(std::move(scroll_child_clip));
-  CopyProperties(viewport_scroll, child_clip);
-  // child_clip scrolls in scroll_parent.
-  child_clip->SetScrollTreeIndex(parent->scroll_tree_index());
-  child_clip->SetTransformTreeIndex(parent->transform_tree_index());
-
-  std::unique_ptr<LayerImpl> scroll_child =
-      CreateScrollableLayer(9, gfx::Size(10, 10));
-  child = scroll_child.get();
-  root->test_properties()->AddChild(std::move(scroll_child));
-  CopyProperties(child_clip, child);
-  CreateTransformNode(child).post_translation = gfx::Vector2d(20, 20);
-  CreateScrollNode(child);
+  LayerImpl* scroll_child =
+      AddScrollableLayer(scroll_child_clip, gfx::Size(5, 5), gfx::Size(10, 10));
+  GetTransformNode(scroll_child)->post_translation = gfx::Vector2d(20, 20);
 
   DrawFrame();
 
@@ -2815,10 +2588,11 @@
     host_impl_->ScrollEnd(EndState().get());
 
     // The child should be fully scrolled by the first ScrollBy.
-    EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 5), child->CurrentScrollOffset());
+    EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 5), scroll_child->CurrentScrollOffset());
 
     // The scroll_parent shouldn't receive the second ScrollBy.
-    EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), parent->CurrentScrollOffset());
+    EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
+                     scroll_parent->CurrentScrollOffset());
 
     // The viewport shouldn't have been scrolled at all.
     EXPECT_VECTOR_EQ(
@@ -2843,7 +2617,8 @@
     host_impl_->ScrollEnd(EndState().get());
 
     // The ScrollBy's should scroll the parent to its extent.
-    EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 5), parent->CurrentScrollOffset());
+    EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 5),
+                     scroll_parent->CurrentScrollOffset());
 
     // The viewport shouldn't receive any scroll delta.
     EXPECT_VECTOR_EQ(
@@ -2856,9 +2631,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, PinchGesture) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -3031,9 +2804,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollDelta) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -3079,8 +2850,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, SyncSubpixelScrollFromFractionalActiveBase) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -3115,8 +2885,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, PinchZoomTriggersPageScaleAnimation) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   float min_page_scale = 1.f;
@@ -3238,8 +3007,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, PageScaleAnimation) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -3368,8 +3136,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -3430,9 +3197,8 @@
 TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate) {
   CreatePendingTree();
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-  CreateScrollAndContentsLayers(host_impl_->pending_tree(),
-                                gfx::Size(100, 100));
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50),
+                      gfx::Size(100, 100), gfx::Size(100, 100));
   host_impl_->ActivateSyncTree();
   DrawFrame();
 
@@ -3553,8 +3319,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   DrawFrame();
 
   LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -3603,10 +3368,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, MaxScrollOffsetAffectedByViewportBoundsDelta) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
 
   LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
@@ -3622,7 +3385,7 @@
   inner_container->SetViewportBoundsDelta(gfx::Vector2dF());
   inner_scroll->SetViewportBoundsDelta(gfx::Vector2dF());
   inner_scroll->SetBounds(gfx::Size());
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  GetScrollNode(inner_scroll)->bounds = inner_scroll->bounds();
   DrawFrame();
 
   inner_scroll->SetViewportBoundsDelta(gfx::Vector2dF(60.f, 60.f));
@@ -3665,6 +3428,7 @@
     host_impl_->ReleaseLayerTreeFrameSink();
     host_impl_ = nullptr;
 
+    gfx::Size viewport_size(50, 50);
     gfx::Size content_size(100, 100);
 
     LayerTreeHostImplOverridePhysicalTime* host_impl_override_time =
@@ -3676,28 +3440,23 @@
     host_impl_->SetVisible(true);
     host_impl_->InitializeFrameSink(layer_tree_frame_sink_.get());
 
-    SetupScrollAndContentsLayers(content_size);
+    SetupViewportLayersInnerScrolls(viewport_size, content_size);
     host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 4.f);
-    host_impl_->active_tree()->SetDeviceViewportRect(
-        gfx::Rect(content_size.width() / 2, content_size.height() / 2));
 
-    std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
-        SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 400,
-                                             VERTICAL, 10, 0, false, true);
-    scrollbar->test_properties()->opacity = 0.f;
-    EXPECT_FLOAT_EQ(0.f, scrollbar->test_properties()->opacity);
+    auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+        host_impl_->active_tree(), VERTICAL, 10, 0, false, true);
+    scrollbar->SetScrollElementId(
+        host_impl_->OuterViewportScrollLayer()->element_id());
+    CopyProperties(host_impl_->InnerViewportContainerLayer(), scrollbar);
+    CreateEffectNode(scrollbar).opacity = 0.f;
 
-    LayerImpl* scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
-    LayerImpl* root = host_impl_->active_tree()->InnerViewportContainerLayer();
-    scrollbar->SetScrollElementId(scroll->element_id());
-    root->test_properties()->AddChild(std::move(scrollbar));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
     host_impl_->active_tree()->DidBecomeActive();
     host_impl_->active_tree()->HandleScrollbarShowRequestsFromMain();
     host_impl_->active_tree()->SetLocalSurfaceIdAllocationFromParent(
         viz::LocalSurfaceIdAllocation(
             viz::LocalSurfaceId(1, base::UnguessableToken::Deserialize(2u, 3u)),
             base::TimeTicks::Now()));
+
     DrawFrame();
 
     // SetScrollElementId will initialize the scrollbar which will cause it to
@@ -3888,6 +3647,7 @@
     settings.scrollbar_animator = animator;
     settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20);
     settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(20);
+    gfx::Size viewport_size(50, 50);
     gfx::Size content_size(100, 100);
 
     // If no animator is set, scrollbar won't show and no animation is expected.
@@ -3895,30 +3655,31 @@
 
     CreateHostImpl(settings, CreateLayerTreeFrameSink());
     CreatePendingTree();
-    CreateScrollAndContentsLayers(host_impl_->pending_tree(), content_size);
-    std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
-        SolidColorScrollbarLayerImpl::Create(host_impl_->pending_tree(), 400,
-                                             VERTICAL, 10, 0, false, true);
-    scrollbar->test_properties()->opacity = 0.f;
-    LayerImpl* scroll = host_impl_->pending_tree()->OuterViewportScrollLayer();
+    SetupViewportLayers(host_impl_->pending_tree(), viewport_size, content_size,
+                        content_size);
+
     LayerImpl* container =
         host_impl_->pending_tree()->InnerViewportContainerLayer();
+    LayerImpl* scroll = host_impl_->pending_tree()->OuterViewportScrollLayer();
+    auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+        host_impl_->pending_tree(), VERTICAL, 10, 0, false, true);
+    host_impl_->pending_tree()->SetElementIdsForTesting();
+
     scrollbar->SetScrollElementId(scroll->element_id());
     scrollbar->SetBounds(gfx::Size(10, 100));
-    scrollbar->test_properties()->position = gfx::PointF(90, 0);
+    CopyProperties(container, scrollbar);
+    scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0));
+    CreateEffectNode(scrollbar).opacity = 0.f;
     scrollbar->SetNeedsPushProperties();
-    container->test_properties()->AddChild(std::move(scrollbar));
 
     host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-    host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->pending_tree());
     host_impl_->ActivateSyncTree();
 
     LayerImpl* active_scrollbar_layer =
-        host_impl_->active_tree()->LayerById(400);
+        host_impl_->active_tree()->LayerById(scrollbar->id());
 
-    EffectNode* active_tree_node =
-        host_impl_->active_tree()->property_trees()->effect_tree.Node(
-            active_scrollbar_layer->effect_tree_index());
+    EffectNode* active_tree_node = GetEffectNode(active_scrollbar_layer);
     EXPECT_FLOAT_EQ(active_scrollbar_layer->Opacity(),
                     active_tree_node->opacity);
 
@@ -3933,22 +3694,23 @@
                             InputHandler::WHEEL);
     host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2dF(0, 5)).get());
     host_impl_->ScrollEnd(EndState().get());
+
     CreatePendingTree();
     // To test the case where the effect tree index of scrollbar layer changes,
     // we force the container layer to create a render surface.
     container = host_impl_->pending_tree()->InnerViewportContainerLayer();
-    container->test_properties()->force_render_surface = true;
     container->SetBounds(gfx::Size(10, 10));
-    container->SetNeedsPushProperties();
-
-    host_impl_->pending_tree()->BuildPropertyTreesForTesting();
-
+    CreateEffectNode(container).render_surface_reason =
+        RenderSurfaceReason::kTest;
     LayerImpl* pending_scrollbar_layer =
-        host_impl_->pending_tree()->LayerById(400);
+        host_impl_->pending_tree()->LayerById(scrollbar->id());
+    GetEffectNode(pending_scrollbar_layer)->parent_id =
+        container->effect_tree_index();
+    container->SetNeedsPushProperties();
     pending_scrollbar_layer->SetNeedsPushProperties();
-    EffectNode* pending_tree_node =
-        host_impl_->pending_tree()->property_trees()->effect_tree.Node(
-            pending_scrollbar_layer->effect_tree_index());
+    UpdateDrawProperties(host_impl_->pending_tree());
+
+    EffectNode* pending_tree_node = GetEffectNode(pending_scrollbar_layer);
     if (expecting_animations) {
       EXPECT_FLOAT_EQ(1.f, active_tree_node->opacity);
       EXPECT_FLOAT_EQ(1.f, active_scrollbar_layer->Opacity());
@@ -3957,10 +3719,9 @@
       EXPECT_FLOAT_EQ(0.f, active_scrollbar_layer->Opacity());
     }
     EXPECT_FLOAT_EQ(0.f, pending_tree_node->opacity);
+
     host_impl_->ActivateSyncTree();
-    active_tree_node =
-        host_impl_->active_tree()->property_trees()->effect_tree.Node(
-            active_scrollbar_layer->effect_tree_index());
+    active_tree_node = GetEffectNode(active_scrollbar_layer);
     if (expecting_animations) {
       EXPECT_FLOAT_EQ(1.f, active_tree_node->opacity);
       EXPECT_FLOAT_EQ(1.f, active_scrollbar_layer->Opacity());
@@ -3994,65 +3755,41 @@
     gfx::Size scrollbar_size_1(gfx::Size(15, viewport_size.height()));
     gfx::Size scrollbar_size_2(gfx::Size(15, child_layer_size.height()));
 
-    const int scrollbar_1_id = 10;
-    const int scrollbar_2_id = 11;
-    const int child_scroll_id = 13;
-
     CreateHostImpl(settings, CreateLayerTreeFrameSink());
     host_impl_->active_tree()->SetDeviceScaleFactor(1);
-    host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
-    CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-    host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
-        viewport_size);
-    LayerImpl* root_scroll =
-        host_impl_->active_tree()->OuterViewportScrollLayer();
+    SetupViewportLayers(host_impl_->active_tree(), viewport_size, content_size,
+                        content_size);
+    host_impl_->InnerViewportContainerLayer()->SetBounds(viewport_size);
+    LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
 
     // scrollbar_1 on root scroll.
-    std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar_1 =
-        SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                             scrollbar_1_id, VERTICAL, 15, 0,
-                                             true, true);
-    scrollbar_1_ = scrollbar_1.get();
-    scrollbar_1->SetScrollElementId(root_scroll->element_id());
-    scrollbar_1->SetDrawsContent(true);
-    scrollbar_1->SetBounds(scrollbar_size_1);
+    scrollbar_1_ = AddLayer<SolidColorScrollbarLayerImpl>(
+        host_impl_->active_tree(), VERTICAL, 15, 0, true, true);
+    scrollbar_1_->SetScrollElementId(root_scroll->element_id());
+    scrollbar_1_->SetDrawsContent(true);
+    scrollbar_1_->SetBounds(scrollbar_size_1);
     TouchActionRegion touch_action_region;
     touch_action_region.Union(kTouchActionNone, gfx::Rect(scrollbar_size_1));
-    scrollbar_1->SetTouchActionRegion(touch_action_region);
-    scrollbar_1->SetCurrentPos(0);
-    scrollbar_1->test_properties()->position = gfx::PointF(0, 0);
-    host_impl_->active_tree()
-        ->InnerViewportContainerLayer()
-        ->test_properties()
-        ->AddChild(std::move(scrollbar_1));
+    scrollbar_1_->SetTouchActionRegion(touch_action_region);
+    scrollbar_1_->SetCurrentPos(0);
+    CopyProperties(host_impl_->InnerViewportContainerLayer(), scrollbar_1_);
+    CreateEffectNode(scrollbar_1_);
 
     // scrollbar_2 on child.
-    std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar_2 =
-        SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                             scrollbar_2_id, VERTICAL, 15, 0,
-                                             true, true);
-    scrollbar_2_ = scrollbar_2.get();
-    std::unique_ptr<LayerImpl> child =
-        LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
-    child->test_properties()->position = gfx::PointF(50, 50);
-    child->SetBounds(child_layer_size);
-    child->SetDrawsContent(true);
-    child->SetHitTestable(true);
-    child->SetScrollable(gfx::Size(100, 100));
-    child->SetHitTestable(true);
-    child->SetElementId(LayerIdToElementIdForTesting(child->id()));
-    ElementId child_element_id = child->element_id();
+    auto* child =
+        AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size);
+    GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50);
 
-    scrollbar_2->SetScrollElementId(child_element_id);
-    scrollbar_2->SetDrawsContent(true);
-    scrollbar_2->SetBounds(scrollbar_size_2);
-    scrollbar_2->SetCurrentPos(0);
-    scrollbar_2->test_properties()->position = gfx::PointF(0, 0);
+    scrollbar_2_ = AddLayer<SolidColorScrollbarLayerImpl>(
+        host_impl_->active_tree(), VERTICAL, 15, 0, true, true);
+    scrollbar_2_->SetScrollElementId(child->element_id());
+    scrollbar_2_->SetDrawsContent(true);
+    scrollbar_2_->SetBounds(scrollbar_size_2);
+    scrollbar_2_->SetCurrentPos(0);
+    CopyProperties(child, scrollbar_2_);
+    CreateEffectNode(scrollbar_2_);
 
-    child->test_properties()->AddChild(std::move(scrollbar_2));
-    root_scroll->test_properties()->AddChild(std::move(child));
-
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
     host_impl_->active_tree()->UpdateScrollbarGeometries();
     host_impl_->active_tree()->DidBecomeActive();
 
@@ -4060,10 +3797,9 @@
   }
 
   void ResetScrollbars() {
-    scrollbar_1_->test_properties()->opacity = 0.f;
-    scrollbar_2_->test_properties()->opacity = 0.f;
-
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    GetEffectNode(scrollbar_1_)->opacity = 0.f;
+    GetEffectNode(scrollbar_2_)->opacity = 0.f;
+    UpdateDrawProperties(host_impl_->active_tree());
 
     if (is_aura_scrollbar_)
       animation_task_.Reset();
@@ -4149,23 +3885,14 @@
   gfx::Size scrollbar_size_1(gfx::Size(15, viewport_size.height()));
   gfx::Size scrollbar_size_2(gfx::Size(15, child_layer_size.height()));
 
-  const int scrollbar_1_id = 10;
-  const int scrollbar_2_id = 11;
-  const int child_scroll_id = 13;
-
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
   host_impl_->active_tree()->SetDeviceScaleFactor(1);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-  host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
-      viewport_size);
-  LayerImpl* root_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
+  LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
 
   // scrollbar_1 on root scroll.
-  std::unique_ptr<PaintedScrollbarLayerImpl> scrollbar_1 =
-      PaintedScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                        scrollbar_1_id, VERTICAL, true, true);
+  auto* scrollbar_1 = AddLayer<PaintedScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, true, true);
   scrollbar_1->SetScrollElementId(root_scroll->element_id());
   scrollbar_1->SetDrawsContent(true);
   scrollbar_1->SetHitTestable(true);
@@ -4174,41 +3901,25 @@
   touch_action_region.Union(kTouchActionNone, gfx::Rect(scrollbar_size_1));
   scrollbar_1->SetTouchActionRegion(touch_action_region);
   scrollbar_1->SetCurrentPos(0);
-  scrollbar_1->test_properties()->position = gfx::PointF(0, 0);
-  scrollbar_1->test_properties()->opacity = 0.f;
-  host_impl_->active_tree()
-      ->InnerViewportContainerLayer()
-      ->test_properties()
-      ->AddChild(std::move(scrollbar_1));
+  CopyProperties(host_impl_->InnerViewportContainerLayer(), scrollbar_1);
+  CreateEffectNode(scrollbar_1).opacity = 0.f;
+
+  LayerImpl* child =
+      AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size);
+  GetTransformNode(child)->post_translation = gfx::Vector2dF(50, 50);
 
   // scrollbar_2 on child.
-  std::unique_ptr<PaintedScrollbarLayerImpl> scrollbar_2 =
-      PaintedScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                        scrollbar_2_id, VERTICAL, true, true);
-  std::unique_ptr<LayerImpl> child =
-      LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
-  child->test_properties()->position = gfx::PointF(50, 50);
-  child->SetBounds(child_layer_size);
-  child->SetDrawsContent(true);
-  child->SetHitTestable(true);
-  child->SetScrollable(gfx::Size(100, 100));
-  child->SetHitTestable(true);
-  child->SetElementId(LayerIdToElementIdForTesting(child->id()));
-  ElementId child_element_id = child->element_id();
-
-  scrollbar_2->SetScrollElementId(child_element_id);
+  auto* scrollbar_2 = AddLayer<PaintedScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, true, true);
+  scrollbar_2->SetScrollElementId(child->element_id());
   scrollbar_2->SetDrawsContent(true);
   scrollbar_2->SetHitTestable(true);
   scrollbar_2->SetBounds(scrollbar_size_2);
   scrollbar_2->SetCurrentPos(0);
-  scrollbar_2->test_properties()->position = gfx::PointF(0, 0);
-  scrollbar_2->test_properties()->opacity = 0.f;
+  CopyProperties(child, scrollbar_2);
+  CreateEffectNode(scrollbar_2).opacity = 0.f;
 
-  child->test_properties()->AddChild(std::move(scrollbar_2));
-  root_scroll->test_properties()->AddChild(std::move(child));
-
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->UpdateScrollbarGeometries();
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
 
   // Wheel scroll on root scrollbar should process on impl thread.
@@ -4251,26 +3962,26 @@
   settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
   settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20);
   settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(20);
+  gfx::Size viewport_size(50, 50);
   gfx::Size content_size(100, 100);
 
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
   CreatePendingTree();
-  CreateScrollAndContentsLayers(host_impl_->pending_tree(), content_size);
-  std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
-      SolidColorScrollbarLayerImpl::Create(host_impl_->pending_tree(), 400,
-                                           VERTICAL, 10, 0, false, true);
-  scrollbar->test_properties()->opacity = 0.f;
+  SetupViewportLayers(host_impl_->pending_tree(), viewport_size, content_size,
+                      content_size);
+  auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->pending_tree(), VERTICAL, 10, 0, false, true);
   LayerImpl* scroll = host_impl_->pending_tree()->OuterViewportScrollLayer();
   LayerImpl* container =
       host_impl_->pending_tree()->InnerViewportContainerLayer();
   scrollbar->SetScrollElementId(scroll->element_id());
   scrollbar->SetBounds(gfx::Size(10, 100));
-  scrollbar->test_properties()->position = gfx::PointF(90, 0);
   scrollbar->SetNeedsPushProperties();
-  container->test_properties()->AddChild(std::move(scrollbar));
+  CopyProperties(container, scrollbar);
+  scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(90, 0));
+  CreateEffectNode(scrollbar).opacity = 0.f;
 
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
   host_impl_->ActivateSyncTree();
 
   ScrollbarAnimationController* scrollbar_controller =
@@ -4327,32 +4038,17 @@
   gfx::Size inner_viewport_size(315, 200);
   gfx::Size outer_viewport_size(300, 200);
   gfx::Size content_size(1000, 1000);
-
-  const int horiz_id = 11;
-  const int child_scroll_id = 15;
-
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-  host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
-      inner_viewport_size);
-  host_impl_->active_tree()->InnerViewportScrollLayer()->SetScrollable(
-      inner_viewport_size);
-  host_impl_->active_tree()->OuterViewportContainerLayer()->SetBounds(
-      outer_viewport_size);
-  host_impl_->active_tree()->OuterViewportScrollLayer()->SetScrollable(
-      outer_viewport_size);
-  LayerImpl* root_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
-  std::unique_ptr<PaintedScrollbarLayerImpl> horiz_scrollbar =
-      PaintedScrollbarLayerImpl::Create(host_impl_->active_tree(), horiz_id,
-                                        HORIZONTAL, true, true);
-  std::unique_ptr<LayerImpl> child =
-      LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport_size,
+                      outer_viewport_size, content_size);
+  LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
+  auto* horiz_scrollbar = AddLayer<PaintedScrollbarLayerImpl>(
+      host_impl_->active_tree(), HORIZONTAL, true, true);
+  LayerImpl* child = AddLayer();
   child->SetBounds(content_size);
   child->SetBounds(inner_viewport_size);
 
   horiz_scrollbar->SetScrollElementId(root_scroll->element_id());
 
-  host_impl_->active_tree()->BuildLayerListAndPropertyTreesForTesting();
   host_impl_->active_tree()->UpdateScrollbarGeometries();
 
   EXPECT_EQ(300, horiz_scrollbar->clip_layer_length());
@@ -4368,45 +4064,27 @@
   gfx::Size viewport_size(300, 200);
   gfx::Size content_size(1000, 1000);
 
-  const int vert_1_id = 10;
-  const int horiz_1_id = 11;
-  const int vert_2_id = 12;
-  const int horiz_2_id = 13;
-  const int child_scroll_id = 15;
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
 
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-  LayerImpl* container =
-      host_impl_->active_tree()->InnerViewportContainerLayer();
-  container->SetBounds(viewport_size);
-  LayerImpl* root_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  auto* container = host_impl_->InnerViewportContainerLayer();
+  auto* root_scroll = host_impl_->OuterViewportScrollLayer();
+  auto* vert_1_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, 5, 5, true, true);
+  CopyProperties(container, vert_1_scrollbar);
 
-  container->test_properties()->AddChild(SolidColorScrollbarLayerImpl::Create(
-      host_impl_->active_tree(), vert_1_id, VERTICAL, 5, 5, true, true));
-  auto* vert_1_scrollbar = static_cast<SolidColorScrollbarLayerImpl*>(
-      container->test_properties()->children[1]);
+  auto* horiz_1_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), HORIZONTAL, 5, 5, true, true);
+  CopyProperties(container, horiz_1_scrollbar);
 
-  container->test_properties()->AddChild(SolidColorScrollbarLayerImpl::Create(
-      host_impl_->active_tree(), horiz_1_id, HORIZONTAL, 5, 5, true, true));
-  auto* horiz_1_scrollbar = static_cast<SolidColorScrollbarLayerImpl*>(
-      container->test_properties()->children[2]);
+  auto* vert_2_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, 5, 5, true, true);
+  CopyProperties(container, vert_2_scrollbar);
 
-  container->test_properties()->AddChild(SolidColorScrollbarLayerImpl::Create(
-      host_impl_->active_tree(), vert_2_id, VERTICAL, 5, 5, true, true));
-  auto* vert_2_scrollbar = static_cast<SolidColorScrollbarLayerImpl*>(
-      container->test_properties()->children[3]);
+  auto* horiz_2_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), HORIZONTAL, 5, 5, true, true);
+  CopyProperties(container, horiz_2_scrollbar);
 
-  container->test_properties()->AddChild(SolidColorScrollbarLayerImpl::Create(
-      host_impl_->active_tree(), horiz_2_id, HORIZONTAL, 5, 5, true, true));
-  auto* horiz_2_scrollbar = static_cast<SolidColorScrollbarLayerImpl*>(
-      container->test_properties()->children[4]);
-
-  std::unique_ptr<LayerImpl> child =
-      LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
-  child->SetBounds(viewport_size);
-  LayerImpl* child_ptr = child.get();
-
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Check scrollbar registration on the viewport layers.
   EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(root_scroll->element_id()).size());
@@ -4430,11 +4108,9 @@
   animation_task_.Reset();
 
   // Check scrollbar registration on a sublayer.
-  child->SetScrollable(viewport_size);
-  child->SetHitTestable(true);
-  child->SetElementId(LayerIdToElementIdForTesting(child->id()));
+  LayerImpl* child =
+      AddScrollableLayer(root_scroll, viewport_size, gfx::Size(200, 200));
   ElementId child_scroll_element_id = child->element_id();
-  root_scroll->test_properties()->AddChild(std::move(child));
   EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(child_scroll_element_id).size());
   EXPECT_EQ(nullptr, host_impl_->ScrollbarAnimationControllerForElementId(
                          child_scroll_element_id));
@@ -4450,38 +4126,37 @@
   // Changing one of the child layers should result in a scrollbar animation
   // update.
   animation_task_.Reset();
-  child_ptr->SetBounds(gfx::Size(200, 200));
-  child_ptr->set_needs_show_scrollbars(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  child->set_needs_show_scrollbars(true);
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->HandleScrollbarShowRequestsFromMain();
   EXPECT_FALSE(animation_task_.is_null());
   animation_task_.Reset();
 
   // Check scrollbar unregistration.
-  container->test_properties()->RemoveChild(vert_1_scrollbar);
+  root_layer()->test_properties()->RemoveChild(vert_1_scrollbar);
   EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(root_scroll->element_id()).size());
   EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForElementId(
       root_scroll->element_id()));
-  container->test_properties()->RemoveChild(horiz_1_scrollbar);
+  root_layer()->test_properties()->RemoveChild(horiz_1_scrollbar);
   EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(root_scroll->element_id()).size());
   EXPECT_EQ(nullptr, host_impl_->ScrollbarAnimationControllerForElementId(
                          root_scroll->element_id()));
 
   EXPECT_EQ(2ul, host_impl_->ScrollbarsFor(child_scroll_element_id).size());
-  container->test_properties()->RemoveChild(vert_2_scrollbar);
+  root_layer()->test_properties()->RemoveChild(vert_2_scrollbar);
   EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(child_scroll_element_id).size());
   EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForElementId(
       child_scroll_element_id));
-  container->test_properties()->RemoveChild(horiz_2_scrollbar);
+  root_layer()->test_properties()->RemoveChild(horiz_2_scrollbar);
   EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(child_scroll_element_id).size());
   EXPECT_EQ(nullptr, host_impl_->ScrollbarAnimationControllerForElementId(
                          root_scroll->element_id()));
 
   // Changing scroll offset should no longer trigger any animation.
-  host_impl_->active_tree()->InnerViewportScrollLayer()->SetCurrentScrollOffset(
+  host_impl_->InnerViewportScrollLayer()->SetCurrentScrollOffset(
       gfx::ScrollOffset(20, 20));
   EXPECT_TRUE(animation_task_.is_null());
-  child_ptr->SetCurrentScrollOffset(gfx::ScrollOffset(20, 20));
+  child->SetCurrentScrollOffset(gfx::ScrollOffset(20, 20));
   EXPECT_TRUE(animation_task_.is_null());
 }
 
@@ -4495,23 +4170,19 @@
   gfx::Size viewport_size(300, 200);
   gfx::Size content_size(1000, 1000);
 
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
   auto* container = host_impl_->active_tree()->InnerViewportContainerLayer();
-  container->SetBounds(viewport_size);
-  auto* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
+  auto* root_scroll = host_impl_->OuterViewportScrollLayer();
 
-  container->test_properties()->AddChild(SolidColorScrollbarLayerImpl::Create(
-      host_impl_->active_tree(), 10, VERTICAL, 5, 0, false, true));
-  auto* vert_scrollbar = static_cast<SolidColorScrollbarLayerImpl*>(
-      container->test_properties()->children[1]);
-
+  auto* vert_scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, 5, 0, false, true);
   vert_scrollbar->SetScrollElementId(root_scroll->element_id());
   vert_scrollbar->SetBounds(gfx::Size(10, 200));
-  vert_scrollbar->test_properties()->position = gfx::PointF(300, 0);
-  vert_scrollbar->test_properties()->opacity_can_animate = true;
   vert_scrollbar->SetCurrentPos(0);
+  CopyProperties(container, vert_scrollbar);
+  CreateEffectNode(vert_scrollbar).has_potential_opacity_animation = true;
+  vert_scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(300, 0));
 
-  host_impl_->active_tree()->BuildLayerListAndPropertyTreesForTesting();
   host_impl_->active_tree()->UpdateScrollbarGeometries();
 
   EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(root_scroll->element_id()).size());
@@ -4559,40 +4230,26 @@
   settings.scrollbar_animator = LayerTreeSettings::AURA_OVERLAY;
 
   gfx::Size viewport_size(300, 200);
-  gfx::Size device_viewport_size =
-      gfx::ScaleToFlooredSize(viewport_size, device_scale_factor);
   gfx::Size content_size(1000, 1000);
   gfx::Size scrollbar_size(gfx::Size(15, viewport_size.height()));
 
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
   host_impl_->active_tree()->SetDeviceScaleFactor(device_scale_factor);
-  host_impl_->active_tree()->SetDeviceViewportRect(
-      gfx::Rect(device_viewport_size));
-
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-  host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
-      viewport_size);
-  LayerImpl* root_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
+  LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
   // The scrollbar is on the left side.
-  std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
-      SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 6,
-                                           VERTICAL, 15, 0, true, true);
+  auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, 15, 0, true, true);
   scrollbar->SetScrollElementId(root_scroll->element_id());
   scrollbar->SetDrawsContent(true);
   scrollbar->SetBounds(scrollbar_size);
   TouchActionRegion touch_action_region;
   touch_action_region.Union(kTouchActionNone, gfx::Rect(scrollbar_size));
   scrollbar->SetTouchActionRegion(touch_action_region);
-  host_impl_->active_tree()
-      ->InnerViewportContainerLayer()
-      ->test_properties()
-      ->AddChild(std::move(scrollbar));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(host_impl_->InnerViewportContainerLayer(), scrollbar);
   host_impl_->active_tree()->DidBecomeActive();
 
   DrawFrame();
-  host_impl_->active_tree()->UpdateDrawProperties();
 
   ScrollbarAnimationController* scrollbar_animation_controller =
       host_impl_->ScrollbarAnimationControllerForElementId(
@@ -4649,9 +4306,8 @@
 // that are different are included in viz::CompositorFrameMetadata's
 // |activation_dependencies|.
 TEST_F(LayerTreeHostImplTest, ActivationDependenciesInMetadata) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  LayerImpl* root = root_layer();
 
   std::vector<viz::SurfaceId> primary_surfaces = {
       MakeSurfaceId(viz::FrameSinkId(1, 1), 1),
@@ -4664,14 +4320,13 @@
       MakeSurfaceId(viz::FrameSinkId(4, 4), 3)};
 
   for (size_t i = 0; i < primary_surfaces.size(); ++i) {
-    std::unique_ptr<SurfaceLayerImpl> child =
-        SurfaceLayerImpl::Create(host_impl_->active_tree(), i + 6);
-    child->test_properties()->position = gfx::PointF(25.f * i, 0.f);
+    auto* child = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
     child->SetBounds(gfx::Size(1, 1));
     child->SetDrawsContent(true);
     child->SetRange(
         viz::SurfaceRange(fallback_surfaces[i], primary_surfaces[i]), 2u);
-    root->test_properties()->AddChild(std::move(child));
+    CopyProperties(root, child);
+    child->SetOffsetToTransformParent(gfx::Vector2dF(25.f * i, 0.f));
   }
 
   base::flat_set<viz::SurfaceRange> surfaces_set;
@@ -4681,7 +4336,6 @@
         viz::SurfaceRange(fallback_surfaces[i], primary_surfaces[i]));
   }
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   host_impl_->active_tree()->SetSurfaceRanges(std::move(surfaces_set));
   host_impl_->SetFullViewportDamage();
   DrawFrame();
@@ -4706,7 +4360,6 @@
 
   // Verify that on the next frame generation that the deadline is reset.
   host_impl_->SetFullViewportDamage();
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
 
   {
@@ -4732,13 +4385,11 @@
 // causes a new CompositorFrame to be submitted, even if there is no other
 // damage.
 TEST_F(LayerTreeHostImplTest, SurfaceReferencesChangeCausesDamage) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   auto* fake_layer_tree_frame_sink =
       static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink());
 
   // Submit an initial CompositorFrame with an empty set of referenced surfaces.
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   host_impl_->active_tree()->SetSurfaceRanges({});
   host_impl_->SetFullViewportDamage();
   DrawFrame();
@@ -4754,7 +4405,6 @@
   // Update the set of referenced surfaces to contain |surface_id| but don't
   // make any other changes that would cause damage. This mimics updating the
   // SurfaceLayer for an offscreen tab.
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   host_impl_->active_tree()->SetSurfaceRanges({viz::SurfaceRange(surface_id)});
   DrawFrame();
 
@@ -4767,8 +4417,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, CompositorFrameMetadata) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
   DrawFrame();
   {
@@ -4831,7 +4480,8 @@
 
 class DidDrawCheckLayer : public LayerImpl {
  public:
-  static std::unique_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id) {
+  static std::unique_ptr<DidDrawCheckLayer> Create(LayerTreeImpl* tree_impl,
+                                                   int id) {
     return base::WrapUnique(new DidDrawCheckLayer(tree_impl, id));
   }
 
@@ -4868,11 +4518,6 @@
     did_draw_called_ = false;
   }
 
-  void AddCopyRequest() {
-    test_properties()->copy_requests.push_back(
-        viz::CopyOutputRequest::CreateStubForTesting());
-  }
-
  protected:
   DidDrawCheckLayer(LayerTreeImpl* tree_impl, int id)
       : LayerImpl(tree_impl, id),
@@ -4893,22 +4538,17 @@
 };
 
 TEST_F(LayerTreeHostImplTest, DamageShouldNotCareAboutContributingLayers) {
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(10, 10));
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
-  auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
+  auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(),
+                                                 gfx::Size(10, 10));
 
   // Make a child layer that draws.
-  root->test_properties()->AddChild(
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), 2));
-  auto* layer =
-      static_cast<SolidColorLayerImpl*>(root->test_properties()->children[0]);
+  auto* layer = AddLayer<SolidColorLayerImpl>(host_impl_->active_tree());
   layer->SetBounds(gfx::Size(10, 10));
   layer->SetDrawsContent(true);
   layer->SetBackgroundColor(SK_ColorRED);
+  CopyProperties(root, layer);
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   {
     TestFrameData frame;
@@ -4927,9 +4567,9 @@
   // Stops the child layer from drawing. We should have damage from this but
   // should not have any quads. This should clear the damaged area.
   layer->SetDrawsContent(false);
-  root->test_properties()->opacity = 0.f;
+  GetEffectNode(root)->opacity = 0.f;
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
   // The background is default to transparent. If the background is opaque, we
   // would fill the frame with background colour when no layers are contributing
   // quads. This means we would end up with 0 quad.
@@ -4969,73 +4609,43 @@
 TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall) {
   // The root layer is always drawn, so run this test on a child layer that
   // will be masked out by the root layer's bounds.
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
-  auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
+  auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(),
+                                                 gfx::Size(10, 10));
+  auto* layer = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
+  CopyProperties(root, layer);
 
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
-  root->test_properties()->force_render_surface = true;
-  auto* layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  {
-    TestFrameData frame;
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    host_impl_->DrawLayers(&frame);
-    host_impl_->DidDrawAllLayers(frame);
-
-    EXPECT_TRUE(layer->will_draw_returned_true());
-    EXPECT_TRUE(layer->append_quads_called());
-    EXPECT_TRUE(layer->did_draw_called());
-  }
+  DrawFrame();
+  EXPECT_TRUE(layer->will_draw_returned_true());
+  EXPECT_TRUE(layer->append_quads_called());
+  EXPECT_TRUE(layer->did_draw_called());
 
   host_impl_->SetViewportDamage(gfx::Rect(10, 10));
 
-  {
-    TestFrameData frame;
-
-    layer->set_will_draw_returns_false();
-    layer->ClearDidDrawCheck();
-
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    host_impl_->DrawLayers(&frame);
-    host_impl_->DidDrawAllLayers(frame);
-
-    EXPECT_FALSE(layer->will_draw_returned_true());
-    EXPECT_FALSE(layer->append_quads_called());
-    EXPECT_FALSE(layer->did_draw_called());
-  }
+  layer->set_will_draw_returns_false();
+  layer->ClearDidDrawCheck();
+  DrawFrame();
+  EXPECT_FALSE(layer->will_draw_returned_true());
+  EXPECT_FALSE(layer->append_quads_called());
+  EXPECT_FALSE(layer->did_draw_called());
 }
 
 TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer) {
   // The root layer is always drawn, so run this test on a child layer that
   // will be masked out by the root layer's bounds.
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
-  auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
-  root->SetMasksToBounds(true);
-  root->test_properties()->force_render_surface = true;
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
-  auto* layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);
-  // Ensure visible_layer_rect for layer is empty.
-  layer->test_properties()->position = gfx::PointF(100.f, 100.f);
+  auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(),
+                                                 gfx::Size(10, 10));
+  CreateClipNode(root);
+  auto* layer = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
   layer->SetBounds(gfx::Size(10, 10));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  TestFrameData frame;
+  CopyProperties(root, layer);
+  // Ensure visible_layer_rect for layer is not empty
+  layer->SetOffsetToTransformParent(gfx::Vector2dF(100.f, 100.f));
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_FALSE(layer->will_draw_returned_true());
   EXPECT_FALSE(layer->did_draw_called());
 
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   EXPECT_FALSE(layer->will_draw_returned_true());
   EXPECT_FALSE(layer->did_draw_called());
@@ -5043,16 +4653,14 @@
   EXPECT_TRUE(layer->visible_layer_rect().IsEmpty());
 
   // Ensure visible_layer_rect for layer is not empty
-  layer->test_properties()->position = gfx::PointF();
+  layer->SetOffsetToTransformParent(gfx::Vector2dF());
   layer->NoteLayerPropertyChanged();
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_FALSE(layer->will_draw_returned_true());
   EXPECT_FALSE(layer->did_draw_called());
 
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   EXPECT_TRUE(layer->will_draw_returned_true());
   EXPECT_TRUE(layer->did_draw_called());
@@ -5062,39 +4670,25 @@
 
 TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer) {
   gfx::Size big_size(1000, 1000);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(big_size));
-
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
   auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
+      SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), big_size);
 
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
-  auto* occluded_layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);
-
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
-  root->test_properties()->force_render_surface = true;
-  auto* top_layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children[1]);
+  auto* occluded_layer = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
+  CopyProperties(root, occluded_layer);
+  auto* top_layer = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
   // This layer covers the occluded_layer above. Make this layer large so it can
   // occlude.
   top_layer->SetBounds(big_size);
   top_layer->SetContentsOpaque(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  TestFrameData frame;
+  CopyProperties(occluded_layer, top_layer);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_FALSE(occluded_layer->will_draw_returned_true());
   EXPECT_FALSE(occluded_layer->did_draw_called());
   EXPECT_FALSE(top_layer->will_draw_returned_true());
   EXPECT_FALSE(top_layer->did_draw_called());
 
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   EXPECT_FALSE(occluded_layer->will_draw_returned_true());
   EXPECT_FALSE(occluded_layer->did_draw_called());
@@ -5103,34 +4697,23 @@
 }
 
 TEST_F(LayerTreeHostImplTest, DidDrawCalledOnAllLayers) {
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
-  auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
+  auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(),
+                                                 gfx::Size(10, 10));
+  auto* layer1 = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
+  auto* layer2 = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
 
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
-  root->test_properties()->force_render_surface = true;
-  auto* layer1 =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);
+  CopyProperties(root, layer1);
+  CreateTransformNode(layer1).flattens_inherited_transform = true;
+  CreateEffectNode(layer1).render_surface_reason = RenderSurfaceReason::kTest;
+  CopyProperties(layer1, layer2);
 
-  layer1->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
-  auto* layer2 =
-      static_cast<DidDrawCheckLayer*>(layer1->test_properties()->children[0]);
-
-  layer1->test_properties()->force_render_surface = true;
-  layer1->test_properties()->should_flatten_transform = true;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_FALSE(root->did_draw_called());
   EXPECT_FALSE(layer1->did_draw_called());
   EXPECT_FALSE(layer2->did_draw_called());
 
-  TestFrameData frame;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   EXPECT_TRUE(root->did_draw_called());
   EXPECT_TRUE(layer1->did_draw_called());
@@ -5142,7 +4725,7 @@
 
 class MissingTextureAnimatingLayer : public DidDrawCheckLayer {
  public:
-  static std::unique_ptr<LayerImpl> Create(
+  static std::unique_ptr<MissingTextureAnimatingLayer> Create(
       LayerTreeImpl* tree_impl,
       int id,
       bool tile_missing,
@@ -5191,7 +4774,6 @@
     bool has_missing_tile = false;
     bool has_incomplete_tile = false;
     bool is_animating = false;
-    bool has_copy_request = false;
   };
 
   bool high_res_required = false;
@@ -5201,24 +4783,26 @@
   DrawResult expected_result;
 };
 
-static void CreateLayerFromState(
-    DidDrawCheckLayer* root,
-    const scoped_refptr<AnimationTimeline>& timeline,
-    const PrepareToDrawSuccessTestCase::State& state) {
-  static int layer_id = 2;
-  root->test_properties()->AddChild(MissingTextureAnimatingLayer::Create(
-      root->layer_tree_impl(), layer_id++, state.has_missing_tile,
-      state.has_incomplete_tile, state.is_animating, timeline));
-  auto* layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children.back());
-  if (state.has_copy_request)
-    layer->AddCopyRequest();
-}
+class LayerTreeHostImplPrepareToDrawTest : public LayerTreeHostImplTest {
+ public:
+  void CreateLayerFromState(DidDrawCheckLayer* root,
+                            const scoped_refptr<AnimationTimeline>& timeline,
+                            const PrepareToDrawSuccessTestCase::State& state) {
+    auto* layer = AddLayer<MissingTextureAnimatingLayer>(
+        root->layer_tree_impl(), state.has_missing_tile,
+        state.has_incomplete_tile, state.is_animating, timeline);
+    CopyProperties(root, layer);
+    if (state.is_animating)
+      CreateTransformNode(layer).has_potential_animation = true;
+  }
+};
 
-TEST_F(CommitToPendingTreeLayerTreeHostImplTest,
-       PrepareToDrawSucceedsAndFails) {
+TEST_F(LayerTreeHostImplPrepareToDrawTest, PrepareToDrawSucceedsAndFails) {
+  LayerTreeSettings settings = DefaultSettings();
+  settings.commit_to_active_tree = false;
+  CreateHostImpl(settings, CreateLayerTreeFrameSink());
+
   std::vector<PrepareToDrawSuccessTestCase> cases;
-
   // 0. Default case.
   cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
   // 1. Animated layer first.
@@ -5287,28 +4871,16 @@
   cases.back().layer_before.has_missing_tile = true;
   cases.back().layer_before.is_animating = true;
 
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
-  auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
-  root->test_properties()->force_render_surface = true;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(),
+                                                 gfx::Size(10, 10));
+  UpdateDrawProperties(host_impl_->active_tree());
 
-  {
-    TestFrameData frame;
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    host_impl_->DrawLayers(&frame);
-    host_impl_->DidDrawAllLayers(frame);
-  }
+  DrawFrame();
 
   for (size_t i = 0; i < cases.size(); ++i) {
     // Clean up host_impl_ state.
     const auto& testcase = cases[i];
-    std::vector<LayerImpl*> to_remove;
-    for (auto* child : root->test_properties()->children)
-      to_remove.push_back(child);
-    for (auto* child : to_remove)
-      root->test_properties()->RemoveChild(child);
+    root->test_properties()->RemoveAllChildren();
     timeline()->ClearAnimations();
 
     std::ostringstream scope;
@@ -5318,7 +4890,7 @@
     CreateLayerFromState(root, timeline(), testcase.layer_before);
     CreateLayerFromState(root, timeline(), testcase.layer_between);
     CreateLayerFromState(root, timeline(), testcase.layer_after);
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
 
     if (testcase.high_res_required)
       host_impl_->SetRequiresHighResToDraw();
@@ -5330,7 +4902,7 @@
   }
 }
 
-TEST_F(LayerTreeHostImplTest,
+TEST_F(LayerTreeHostImplPrepareToDrawTest,
        PrepareToDrawWhenDrawAndSwapFullViewportEveryFrame) {
   CreateHostImpl(DefaultSettings(), FakeLayerTreeFrameSink::CreateSoftware());
 
@@ -5357,23 +4929,16 @@
   cases.back().high_res_required = true;
   cases.back().layer_between.has_missing_tile = true;
 
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
-  auto* root = static_cast<DidDrawCheckLayer*>(
-      host_impl_->active_tree()->root_layer_for_testing());
-  root->test_properties()->force_render_surface = true;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  auto* root = SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(),
+                                                 gfx::Size(10, 10));
+  UpdateDrawProperties(host_impl_->active_tree());
 
   host_impl_->OnDraw(external_transform, external_viewport,
                      resourceless_software_draw, false);
 
   for (size_t i = 0; i < cases.size(); ++i) {
     const auto& testcase = cases[i];
-    std::vector<LayerImpl*> to_remove;
-    for (auto* child : root->test_properties()->children)
-      to_remove.push_back(child);
-    for (auto* child : to_remove)
-      root->test_properties()->RemoveChild(child);
+    root->test_properties()->RemoveAllChildren();
 
     std::ostringstream scope;
     scope << "Test case: " << i;
@@ -5382,7 +4947,6 @@
     CreateLayerFromState(root, timeline(), testcase.layer_before);
     CreateLayerFromState(root, timeline(), testcase.layer_between);
     CreateLayerFromState(root, timeline(), testcase.layer_after);
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
 
     if (testcase.high_res_required)
       host_impl_->SetRequiresHighResToDraw();
@@ -5393,12 +4957,7 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollRootIgnored) {
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  root->test_properties()->force_render_surface = true;
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
+  SetupDefaultRootLayer(gfx::Size(10, 10));
   DrawFrame();
 
   // Scroll event is ignored because layer is not scrollable.
@@ -5414,15 +4973,13 @@
 TEST_F(LayerTreeHostImplTest, ClampingAfterActivation) {
   CreatePendingTree();
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-  CreateScrollAndContentsLayers(host_impl_->pending_tree(),
-                                gfx::Size(100, 100));
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50),
+                      gfx::Size(100, 100), gfx::Size(100, 100));
   host_impl_->ActivateSyncTree();
 
   CreatePendingTree();
   const gfx::ScrollOffset pending_scroll = gfx::ScrollOffset(-100, -100);
-  LayerImpl* active_outer_layer =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  LayerImpl* active_outer_layer = host_impl_->OuterViewportScrollLayer();
   LayerImpl* pending_outer_layer =
       host_impl_->pending_tree()->OuterViewportScrollLayer();
   pending_outer_layer->layer_tree_impl()
@@ -5440,9 +4997,7 @@
   LayerTreeHostImplBrowserControlsTest()
       // Make the clip size the same as the layer (content) size so the layer is
       // non-scrollable.
-      : layer_size_(10, 10),
-        clip_size_(layer_size_),
-        top_controls_height_(50) {
+      : layer_size_(10, 10), clip_size_(layer_size_), top_controls_height_(50) {
     viewport_size_ = gfx::Size(clip_size_.width(),
                                clip_size_.height() + top_controls_height_);
   }
@@ -5460,30 +5015,29 @@
     return init;
   }
 
+ protected:
   void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
       const gfx::Size& inner_viewport_size,
       const gfx::Size& outer_viewport_size,
-      const gfx::Size& scroll_layer_size,
-      base::OnceCallback<void(LayerTreeSettings*)> modify_settings =
-          base::DoNothing()) {
-    settings_ = DefaultSettings();
-    settings_.use_layer_lists = true;
-    std::move(modify_settings).Run(&settings_);
-    CreateHostImpl(settings_, CreateLayerTreeFrameSink());
+      const gfx::Size& scroll_layer_size) {
     SetupBrowserControlsAndScrollLayerWithVirtualViewport(
         host_impl_->active_tree(), inner_viewport_size, outer_viewport_size,
         scroll_layer_size);
   }
 
- protected:
   void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
       LayerTreeImpl* tree_impl,
       const gfx::Size& inner_viewport_size,
       const gfx::Size& outer_viewport_size,
       const gfx::Size& scroll_layer_size) {
-    LayerTestCommon::SetupBrowserControlsAndScrollLayerWithVirtualViewport(
-        host_impl_.get(), tree_impl, top_controls_height_, inner_viewport_size,
-        outer_viewport_size, scroll_layer_size);
+    tree_impl->set_browser_controls_shrink_blink_size(true);
+    tree_impl->SetTopControlsHeight(top_controls_height_);
+    tree_impl->SetCurrentBrowserControlsShownRatio(1.f);
+    tree_impl->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
+    host_impl_->DidChangeBrowserControlsPosition();
+
+    SetupViewportLayers(tree_impl, inner_viewport_size, outer_viewport_size,
+                        scroll_layer_size);
   }
 
   gfx::Size layer_size_;
@@ -5507,14 +5061,7 @@
   LayerTreeImpl* active_tree = host_impl_->active_tree();
 
   // Create a content layer beneath the outer viewport scroll layer.
-  int id = host_impl_->OuterViewportScrollLayer()->id();
-  host_impl_->OuterViewportScrollLayer()->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->active_tree(), id + 2));
-  LayerImpl* content =
-      active_tree->OuterViewportScrollLayer()->test_properties()->children[0];
-  content->SetBounds(gfx::Size(50, 50));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
+  LayerImpl* content = AddContentLayer();
   DrawFrame();
 
   LayerImpl* inner_container = active_tree->InnerViewportContainerLayer();
@@ -5582,14 +5129,10 @@
   LayerTreeImpl* active_tree = host_impl_->active_tree();
 
   // Create a content layer beneath the outer viewport scroll layer.
-  int id = host_impl_->OuterViewportScrollLayer()->id();
-  host_impl_->OuterViewportScrollLayer()->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->active_tree(), id + 2));
-  LayerImpl* content =
-      active_tree->OuterViewportScrollLayer()->test_properties()->children[0];
+  LayerImpl* content = AddLayer();
   content->SetBounds(gfx::Size(100, 100));
+  CopyProperties(active_tree->OuterViewportScrollLayer(), content);
   host_impl_->active_tree()->PushPageScaleFromMainThread(0.5f, 0.5f, 4.f);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
 
   DrawFrame();
 
@@ -5637,34 +5180,26 @@
   LayerTreeImpl* active_tree = host_impl_->active_tree();
 
   // Create a horizontal scrollbar.
-  const int scrollbar_id = 23;
   gfx::Size scrollbar_size(gfx::Size(50, 15));
-  std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
-      SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                           scrollbar_id, HORIZONTAL, 3, 20,
-                                           false, true);
-  scrollbar->SetScrollElementId(
+  auto* scrollbar_layer = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), HORIZONTAL, 3, 20, false, true);
+  scrollbar_layer->SetScrollElementId(
       host_impl_->OuterViewportScrollLayer()->element_id());
-  scrollbar->SetDrawsContent(true);
-  scrollbar->SetBounds(scrollbar_size);
+  scrollbar_layer->SetDrawsContent(true);
+  scrollbar_layer->SetBounds(scrollbar_size);
   TouchActionRegion touch_action_region;
   touch_action_region.Union(kTouchActionNone, gfx::Rect(scrollbar_size));
-  scrollbar->SetTouchActionRegion(touch_action_region);
-  scrollbar->SetCurrentPos(0);
-  scrollbar->test_properties()->position = gfx::PointF(0, 35);
-  host_impl_->active_tree()
-      ->InnerViewportContainerLayer()
-      ->test_properties()
-      ->AddChild(std::move(scrollbar));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  scrollbar_layer->SetTouchActionRegion(touch_action_region);
+  scrollbar_layer->SetCurrentPos(0);
+  CopyProperties(host_impl_->InnerViewportContainerLayer(), scrollbar_layer);
+  scrollbar_layer->SetOffsetToTransformParent(gfx::Vector2dF(0, 35));
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->UpdateScrollbarGeometries();
 
   DrawFrame();
 
   LayerImpl* inner_container = active_tree->InnerViewportContainerLayer();
   LayerImpl* outer_container = active_tree->OuterViewportContainerLayer();
-  auto* scrollbar_layer = static_cast<SolidColorScrollbarLayerImpl*>(
-      active_tree->LayerById(scrollbar_id));
 
   // The browser controls should start off showing so the viewport should be
   // shrunk.
@@ -5758,16 +5293,12 @@
       gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100));
   DrawFrame();
 
-  LayerImpl* inner_scroll =
-      host_impl_->active_tree()->InnerViewportScrollLayer();
+  LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
   inner_scroll->SetDrawsContent(true);
-  LayerImpl* inner_container =
-      host_impl_->active_tree()->InnerViewportContainerLayer();
-  LayerImpl* outer_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  LayerImpl* inner_container = host_impl_->InnerViewportContainerLayer();
+  LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   outer_scroll->SetDrawsContent(true);
-  LayerImpl* outer_container =
-      host_impl_->active_tree()->OuterViewportContainerLayer();
+  LayerImpl* outer_container = host_impl_->OuterViewportContainerLayer();
 
   // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
   outer_scroll->SetDrawsContent(true);
@@ -5899,11 +5430,9 @@
   DrawFrame();
 
   // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
-  LayerImpl* inner_scroll =
-      host_impl_->active_tree()->InnerViewportScrollLayer();
+  LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
   inner_scroll->SetDrawsContent(true);
-  LayerImpl* outer_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   outer_scroll->SetDrawsContent(true);
 
   host_impl_->active_tree()->PushBrowserControlsFromMainThread(1);
@@ -5933,25 +5462,22 @@
   EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
 
   LayerImpl* outer_viewport_scroll_layer =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
-  int id = outer_viewport_scroll_layer->id();
-  std::unique_ptr<LayerImpl> child =
-      LayerImpl::Create(host_impl_->active_tree(), id + 2);
+      host_impl_->OuterViewportScrollLayer();
+  LayerImpl* child = AddLayer();
 
   child->SetScrollable(sub_content_layer_size);
   child->SetHitTestable(true);
   child->SetElementId(LayerIdToElementIdForTesting(child->id()));
   child->SetBounds(sub_content_size);
-  child->test_properties()->position = gfx::PointF();
   child->SetDrawsContent(true);
-  child->test_properties()->is_container_for_fixed_position_layers = true;
 
-  LayerImpl* child_ptr = child.get();
-  outer_viewport_scroll_layer->test_properties()->AddChild(std::move(child));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(outer_viewport_scroll_layer, child);
+  CreateTransformNode(child);
+  CreateScrollNode(child);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Scroll child to the limit.
-  SetScrollOffsetDelta(child_ptr, gfx::Vector2dF(0, 100.f));
+  SetScrollOffsetDelta(child, gfx::Vector2dF(0, 100.f));
 
   // Scroll 25px to hide browser controls
   gfx::Vector2dF scroll_delta(0.f, 25.f);
@@ -5996,11 +5522,8 @@
   host_impl_->DidChangeBrowserControlsPosition();
 
   // Now that browser controls have moved, expect the clip to resize.
-  LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
-                                  ->test_properties()
-                                  ->parent->test_properties()
-                                  ->parent;
-  EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
+  LayerImpl* inner_clip = host_impl_->InnerViewportContainerLayer();
+  EXPECT_EQ(viewport_size_, inner_clip->bounds());
 }
 
 // Ensure setting the browser controls position explicitly using the setters on
@@ -6069,21 +5592,15 @@
       15.f / top_controls_height_);
 
   host_impl_->DidChangeBrowserControlsPosition();
-  LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
-                                  ->test_properties()
-                                  ->parent->test_properties()
-                                  ->parent;
-  EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
+  LayerImpl* inner_clip = host_impl_->InnerViewportContainerLayer();
+  EXPECT_EQ(viewport_size_, inner_clip->bounds());
   EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
 
   host_impl_->ActivateSyncTree();
 
-  inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
-                       ->test_properties()
-                       ->parent->test_properties()
-                       ->parent;
+  inner_clip = host_impl_->InnerViewportContainerLayer();
   EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
-  EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
+  EXPECT_EQ(viewport_size_, inner_clip->bounds());
 
   EXPECT_FLOAT_EQ(
       -15.f, host_impl_->active_tree()->top_controls_shown_ratio()->Delta() *
@@ -6112,31 +5629,24 @@
   host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.f);
 
   host_impl_->DidChangeBrowserControlsPosition();
-  LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
-                                  ->test_properties()
-                                  ->parent->test_properties()
-                                  ->parent;
-  EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
+  LayerImpl* inner_clip = host_impl_->InnerViewportContainerLayer();
+  EXPECT_EQ(viewport_size_, inner_clip->bounds());
   EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
 
-  host_impl_->sync_tree()->root_layer_for_testing()->SetBounds(
-      gfx::Size(inner_clip_ptr->bounds().width(),
-                inner_clip_ptr->bounds().height() - 50.f));
+  host_impl_->sync_tree()->root_layer_for_testing()->SetBounds(gfx::Size(
+      inner_clip->bounds().width(), inner_clip->bounds().height() - 50.f));
 
   host_impl_->ActivateSyncTree();
 
-  inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
-                       ->test_properties()
-                       ->parent->test_properties()
-                       ->parent;
+  inner_clip = host_impl_->InnerViewportContainerLayer();
   EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
 
   // The total bounds should remain unchanged since the bounds delta should
   // account for the difference between the layout height and the current
   // browser controls offset.
-  EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
+  EXPECT_EQ(viewport_size_, inner_clip->bounds());
   EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 50.f),
-                   inner_clip_ptr->ViewportBoundsDelta());
+                   inner_clip->ViewportBoundsDelta());
 
   host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f);
   host_impl_->DidChangeBrowserControlsPosition();
@@ -6145,10 +5655,9 @@
             host_impl_->browser_controls_manager()->TopControlsShownRatio());
   EXPECT_EQ(50.f, host_impl_->browser_controls_manager()->TopControlsHeight());
   EXPECT_EQ(50.f, host_impl_->browser_controls_manager()->ContentTopOffset());
-  EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 0.f),
-                   inner_clip_ptr->ViewportBoundsDelta());
+  EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 0.f), inner_clip->ViewportBoundsDelta());
   EXPECT_EQ(gfx::Size(viewport_size_.width(), viewport_size_.height() - 50.f),
-            inner_clip_ptr->bounds());
+            inner_clip->bounds());
 }
 
 // Test that showing/hiding the browser controls when the viewport is fully
@@ -6250,10 +5759,8 @@
 
   // Browser controls were hidden by 25px so the inner viewport should have
   // expanded by that much.
-  LayerImpl* outer_container =
-      host_impl_->active_tree()->OuterViewportContainerLayer();
-  LayerImpl* inner_container =
-      host_impl_->active_tree()->InnerViewportContainerLayer();
+  LayerImpl* outer_container = host_impl_->OuterViewportContainerLayer();
+  LayerImpl* inner_container = host_impl_->InnerViewportContainerLayer();
   EXPECT_EQ(gfx::SizeF(100.f, 100.f + 25.f),
             inner_container->BoundsForScrolling());
 
@@ -6351,11 +5858,8 @@
   host_impl_->browser_controls_manager()->ScrollEnd();
   EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
   // Now that browser controls have moved, expect the clip to resize.
-  LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
-                                  ->test_properties()
-                                  ->parent->test_properties()
-                                  ->parent;
-  EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
+  LayerImpl* inner_clip = host_impl_->InnerViewportContainerLayer();
+  EXPECT_EQ(viewport_size_, inner_clip->bounds());
 
   host_impl_->ScrollEnd(EndState().get());
 
@@ -6374,7 +5878,7 @@
   // Now that browser controls have moved, expect the clip to resize.
   EXPECT_EQ(gfx::Size(viewport_size_.width(),
                       viewport_size_.height() + scroll_increment_y),
-            inner_clip_ptr->bounds());
+            inner_clip->bounds());
 
   host_impl_->browser_controls_manager()->ScrollBy(
       gfx::Vector2dF(0.f, scroll_increment_y));
@@ -6382,14 +5886,13 @@
   EXPECT_FLOAT_EQ(-2 * scroll_increment_y,
                   host_impl_->browser_controls_manager()->ContentTopOffset());
   // Now that browser controls have moved, expect the clip to resize.
-  EXPECT_EQ(clip_size_, inner_clip_ptr->bounds());
+  EXPECT_EQ(clip_size_, inner_clip->bounds());
 
   host_impl_->ScrollEnd(EndState().get());
 
   // Verify the layer is once-again non-scrollable.
-  EXPECT_EQ(
-      gfx::ScrollOffset(),
-      host_impl_->active_tree()->InnerViewportScrollLayer()->MaxScrollOffset());
+  EXPECT_EQ(gfx::ScrollOffset(),
+            host_impl_->InnerViewportScrollLayer()->MaxScrollOffset());
 
   EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
             host_impl_
@@ -6424,6 +5927,7 @@
         host_impl_->pending_tree(), inner_viewport_size, outer_viewport_size,
         content_size);
     host_impl_->pending_tree()->set_browser_controls_shrink_blink_size(false);
+    UpdateDrawProperties(host_impl_->pending_tree());
 
     // Fully scroll the viewport.
     host_impl_->ScrollBegin(BeginState(gfx::Point(75, 75)).get(),
@@ -6433,8 +5937,7 @@
     host_impl_->ScrollEnd(EndState().get());
   }
 
-  LayerImpl* outer_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
 
   ASSERT_FLOAT_EQ(0,
                   host_impl_->browser_controls_manager()->ContentTopOffset());
@@ -6527,29 +6030,20 @@
   gfx::Size surface_size(10, 10);
   gfx::Size contents_size(20, 20);
 
-  std::unique_ptr<LayerImpl> content_layer =
-      LayerImpl::Create(host_impl_->active_tree(), 11);
+  SetupViewportLayersNoScrolls(surface_size);
+
+  LayerImpl* scroll_container_layer = AddContentLayer();
+  CreateEffectNode(scroll_container_layer).render_surface_reason =
+      RenderSurfaceReason::kTest;
+
+  LayerImpl* scroll_layer =
+      AddScrollableLayer(scroll_container_layer, surface_size, contents_size);
+
+  LayerImpl* content_layer = AddLayer();
   content_layer->SetDrawsContent(true);
-  content_layer->test_properties()->position = gfx::PointF();
   content_layer->SetBounds(contents_size);
+  CopyProperties(scroll_layer, content_layer);
 
-  LayerImpl* scroll_container_layer =
-      CreateBasicVirtualViewportLayers(surface_size, surface_size);
-
-  std::unique_ptr<LayerImpl> scroll_layer =
-      LayerImpl::Create(host_impl_->active_tree(), 12);
-  scroll_layer->SetScrollable(surface_size);
-  scroll_layer->SetHitTestable(true);
-  scroll_layer->SetElementId(LayerIdToElementIdForTesting(scroll_layer->id()));
-  scroll_layer->SetBounds(contents_size);
-  scroll_layer->test_properties()->position = gfx::PointF();
-  scroll_layer->test_properties()->AddChild(std::move(content_layer));
-  scroll_container_layer->test_properties()->AddChild(std::move(scroll_layer));
-
-  scroll_container_layer->test_properties()->force_render_surface = true;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
 
   EXPECT_EQ(
@@ -6567,14 +6061,13 @@
   gfx::Size surface_size(10, 10);
   gfx::Size contents_size(20, 20);
 
-  LayerImpl* root =
-      CreateBasicVirtualViewportLayers(surface_size, surface_size);
+  SetupViewportLayersNoScrolls(surface_size);
 
-  root->test_properties()->AddChild(CreateScrollableLayer(12, contents_size));
-  root->test_properties()->force_render_surface = true;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  LayerImpl* content_root = AddContentLayer();
+  CreateEffectNode(content_root).render_surface_reason =
+      RenderSurfaceReason::kTest;
+  AddScrollableLayer(content_root, surface_size, contents_size);
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
 
   EXPECT_EQ(
@@ -6589,15 +6082,10 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollMissesChild) {
+  gfx::Size viewport_size(5, 5);
   gfx::Size surface_size(10, 10);
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  root->test_properties()->AddChild(CreateScrollableLayer(2, surface_size));
-  root->test_properties()->force_render_surface = true;
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
+  LayerImpl* root = SetupDefaultRootLayer(surface_size);
+  AddScrollableLayer(root, viewport_size, surface_size);
   DrawFrame();
 
   // Scroll event is ignored because the input coordinate is outside the layer
@@ -6613,22 +6101,16 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild) {
+  gfx::Size viewport_size(5, 5);
   gfx::Size surface_size(10, 10);
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  root->test_properties()->force_render_surface = true;
-  std::unique_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size);
+  LayerImpl* root = SetupDefaultRootLayer(viewport_size);
+  LayerImpl* child = AddScrollableLayer(root, viewport_size, surface_size);
 
   gfx::Transform matrix;
   matrix.RotateAboutXAxis(180.0);
-  child->test_properties()->transform = matrix;
-  child->test_properties()->double_sided = false;
 
-  root->test_properties()->AddChild(std::move(child));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
+  GetTransformNode(child)->local = matrix;
+  CreateEffectNode(child).double_sided = false;
   DrawFrame();
 
   // Scroll event is ignored because the scrollable layer is not facing the
@@ -6644,21 +6126,18 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer) {
+  gfx::Size scroll_container_size(5, 5);
   gfx::Size surface_size(10, 10);
-  std::unique_ptr<LayerImpl> content_layer =
-      CreateScrollableLayer(1, surface_size);
-  content_layer->test_properties()->main_thread_scrolling_reasons =
-      MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
+  LayerImpl* root = SetupDefaultRootLayer(surface_size);
 
   // Note: we can use the same clip layer for both since both calls to
-  // CreateScrollableLayer() use the same surface size.
-  std::unique_ptr<LayerImpl> scroll_layer =
-      CreateScrollableLayer(2, surface_size);
-  scroll_layer->test_properties()->AddChild(std::move(content_layer));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(scroll_layer));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
+  // AddScrollableLayer() use the same surface size.
+  LayerImpl* scroll_layer =
+      AddScrollableLayer(root, scroll_container_size, surface_size);
+  LayerImpl* content_layer =
+      AddScrollableLayer(scroll_layer, scroll_container_size, surface_size);
+  GetScrollNode(content_layer)->main_thread_scrolling_reasons =
+      MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
   DrawFrame();
 
   // Scrolling fails because the content layer is asking to be scrolled on the
@@ -6671,25 +6150,18 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread) {
-  gfx::Size viewport_size(20, 20);
-  float page_scale = 2.f;
-
-  SetupScrollAndContentsLayers(viewport_size);
-
-  // Setup the layers so that the outer viewport is scrollable.
-  host_impl_->InnerViewportScrollLayer()->test_properties()->parent->SetBounds(
-      viewport_size);
-  host_impl_->OuterViewportScrollLayer()->SetBounds(gfx::Size(40, 40));
+  gfx::Size inner_viewport_size(20, 20);
+  gfx::Size outer_viewport_size(40, 40);
+  gfx::Size content_size(80, 80);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport_size,
+                      outer_viewport_size, content_size);
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
   DrawFrame();
 
-  LayerImpl* root_container = host_impl_->OuterViewportContainerLayer();
-  EXPECT_EQ(viewport_size, root_container->bounds());
-
   gfx::Vector2d scroll_delta(0, 10);
   gfx::ScrollOffset expected_scroll_delta(scroll_delta);
-  LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
-  gfx::ScrollOffset expected_max_scroll = root_scroll->MaxScrollOffset();
+  LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
+  gfx::ScrollOffset expected_max_scroll = outer_scroll->MaxScrollOffset();
   EXPECT_EQ(
       InputHandler::SCROLL_ON_IMPL_THREAD,
       host_impl_
@@ -6699,6 +6171,7 @@
   host_impl_->ScrollEnd(EndState().get());
 
   // Set new page scale from main thread.
+  float page_scale = 2.f;
   host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f, 2.f);
 
   std::unique_ptr<ScrollAndScaleSet> scroll_info =
@@ -6708,7 +6181,7 @@
                                  expected_scroll_delta));
 
   // The scroll range should also have been updated.
-  EXPECT_EQ(expected_max_scroll, root_scroll->MaxScrollOffset());
+  EXPECT_EQ(expected_max_scroll, outer_scroll->MaxScrollOffset());
 
   // The page scale delta remains constant because the impl thread did not
   // scale.
@@ -6716,27 +6189,18 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread) {
-  gfx::Size viewport_size(20, 20);
-  float page_scale = 2.f;
-
-  SetupScrollAndContentsLayers(viewport_size);
-
-  // Setup the layers so that the outer viewport is scrollable.
-  host_impl_->InnerViewportScrollLayer()->test_properties()->parent->SetBounds(
-      viewport_size);
-  host_impl_->OuterViewportScrollLayer()->SetBounds(gfx::Size(40, 40));
+  gfx::Size inner_viewport_size(20, 20);
+  gfx::Size outer_viewport_size(40, 40);
+  gfx::Size content_size(80, 80);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport_size,
+                      outer_viewport_size, content_size);
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   DrawFrame();
 
-  LayerImpl* root_container = host_impl_->OuterViewportContainerLayer();
-  EXPECT_EQ(viewport_size, root_container->bounds());
-
   gfx::Vector2d scroll_delta(0, 10);
   gfx::ScrollOffset expected_scroll_delta(scroll_delta);
-  LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
-  gfx::ScrollOffset expected_max_scroll = root_scroll->MaxScrollOffset();
+  LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
+  gfx::ScrollOffset expected_max_scroll = outer_scroll->MaxScrollOffset();
   EXPECT_EQ(
       InputHandler::SCROLL_ON_IMPL_THREAD,
       host_impl_
@@ -6746,6 +6210,7 @@
   host_impl_->ScrollEnd(EndState().get());
 
   // Set new page scale on impl thread by pinching.
+  float page_scale = 2.f;
   host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                           InputHandler::TOUCHSCREEN);
   host_impl_->PinchGestureBegin();
@@ -6763,7 +6228,7 @@
                                  expected_scroll_delta));
 
   // The scroll range should also have been updated.
-  EXPECT_EQ(expected_max_scroll, root_scroll->MaxScrollOffset());
+  EXPECT_EQ(expected_max_scroll, outer_scroll->MaxScrollOffset());
 
   // The page scale delta should match the new scale on the impl side.
   EXPECT_EQ(page_scale, host_impl_->active_tree()->current_page_scale_factor());
@@ -6771,6 +6236,7 @@
 
 TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly) {
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
+  gfx::Size viewport_size(5, 5);
   gfx::Size surface_size(10, 10);
   float default_page_scale = 1.f;
   gfx::Transform default_page_scale_matrix;
@@ -6780,23 +6246,17 @@
   gfx::Transform new_page_scale_matrix;
   new_page_scale_matrix.Scale(new_page_scale, new_page_scale);
 
-  // Create a normal scrollable root layer and another scrollable child layer.
-  LayerImpl* scroll = SetupScrollAndContentsLayers(surface_size);
-  scroll->SetDrawsContent(true);
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
-  LayerImpl* child = scroll->test_properties()->children[0];
-  child->SetDrawsContent(true);
+  SetupViewportLayersInnerScrolls(viewport_size, surface_size);
+  LayerImpl* root = root_layer();
+  auto* scroll = host_impl_->InnerViewportScrollLayer();
+  auto* child = host_impl_->OuterViewportContainerLayer();
+  auto* grand_child = host_impl_->OuterViewportScrollLayer();
 
-  std::unique_ptr<LayerImpl> scrollable_child_clip =
-      LayerImpl::Create(host_impl_->active_tree(), 6);
-  std::unique_ptr<LayerImpl> scrollable_child =
-      CreateScrollableLayer(7, surface_size);
-  scrollable_child_clip->test_properties()->AddChild(
-      std::move(scrollable_child));
-  child->test_properties()->AddChild(std::move(scrollable_child_clip));
-  LayerImpl* grand_child = child->test_properties()->children[0];
-  grand_child->SetDrawsContent(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  // Create a normal scrollable root layer and another scrollable child layer.
+  LayerImpl* scrollable_child_clip = AddLayer();
+  CopyProperties(child, scrollable_child_clip);
+  AddScrollableLayer(scrollable_child_clip, viewport_size, surface_size);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Set new page scale on impl thread by pinching.
   host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
@@ -6809,10 +6269,7 @@
 
   // Make sure all the layers are drawn with the page scale delta applied, i.e.,
   // the page scale delta on the root layer is applied hierarchically.
-  TestFrameData frame;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   EXPECT_EQ(1.f, root->DrawTransform().matrix().getDouble(0, 0));
   EXPECT_EQ(1.f, root->DrawTransform().matrix().getDouble(1, 1));
@@ -6827,15 +6284,10 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread) {
-  SetupScrollAndContentsLayers(gfx::Size(30, 30));
-
+  SetupViewportLayers(host_impl_->active_tree(), gfx::Size(15, 15),
+                      gfx::Size(30, 30), gfx::Size(50, 50));
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
-
-  // Make the outer scroll layer scrollable.
-  outer_scroll->SetBounds(gfx::Size(50, 50));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   DrawFrame();
 
   gfx::Vector2d scroll_delta(0, 10);
@@ -6852,8 +6304,6 @@
   float page_scale = 2.f;
   host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f,
                                                          page_scale);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   DrawOneFrame();
 
   std::unique_ptr<ScrollAndScaleSet> scroll_info =
@@ -6875,21 +6325,14 @@
   gfx::Size surface_size(10, 10);
   gfx::Size content_size(20, 20);
 
-  LayerImpl* root =
-      CreateBasicVirtualViewportLayers(surface_size, surface_size);
+  SetupViewportLayersNoScrolls(surface_size);
+  LayerImpl* top = AddContentLayer();
+  CreateEffectNode(top).render_surface_reason = RenderSurfaceReason::kTest;
+  LayerImpl* child_layer = AddScrollableLayer(top, surface_size, content_size);
+  LayerImpl* grand_child_layer =
+      AddScrollableLayer(child_layer, surface_size, content_size);
 
-  root->test_properties()->force_render_surface = true;
-
-  std::unique_ptr<LayerImpl> grand_child =
-      CreateScrollableLayer(13, content_size);
-
-  std::unique_ptr<LayerImpl> child = CreateScrollableLayer(12, content_size);
-  LayerImpl* grand_child_layer = grand_child.get();
-  child->test_properties()->AddChild(std::move(grand_child));
-
-  LayerImpl* child_layer = child.get();
-  root->test_properties()->AddChild(std::move(child));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
 
   grand_child_layer->layer_tree_impl()
@@ -6901,7 +6344,6 @@
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(child_layer->element_id(),
                                                      gfx::ScrollOffset(3, 0));
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
   {
     gfx::Vector2d scroll_delta(-8, -7);
@@ -6932,20 +6374,14 @@
   gfx::Size surface_size(100, 100);
   gfx::Size content_size(150, 150);
 
-  LayerImpl* root =
-      CreateBasicVirtualViewportLayers(surface_size, surface_size);
-  root->test_properties()->force_render_surface = true;
-  std::unique_ptr<LayerImpl> grand_child =
-      CreateScrollableLayer(13, content_size);
+  SetupViewportLayersNoScrolls(surface_size);
+  LayerImpl* top = AddContentLayer();
+  CreateEffectNode(top).render_surface_reason = RenderSurfaceReason::kTest;
+  LayerImpl* child_layer = AddScrollableLayer(top, surface_size, content_size);
+  LayerImpl* grand_child_layer =
+      AddScrollableLayer(child_layer, surface_size, content_size);
 
-  std::unique_ptr<LayerImpl> child = CreateScrollableLayer(12, content_size);
-  LayerImpl* grand_child_layer = grand_child.get();
-  child->test_properties()->AddChild(std::move(grand_child));
-
-  LayerImpl* child_layer = child.get();
-  root->test_properties()->AddChild(std::move(child));
-  host_impl_->active_tree()->SetElementIdsForTesting();
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
 
   grand_child_layer->layer_tree_impl()
@@ -6957,7 +6393,6 @@
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(child_layer->element_id(),
                                                      gfx::ScrollOffset(0, 50));
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
 
   base::TimeTicks start_time =
@@ -7027,43 +6462,13 @@
   // the scroll doesn't bubble up to the parent layer.
   gfx::Size surface_size(20, 20);
   gfx::Size viewport_size(10, 10);
-  const int kRootLayerId = 1;
-  const int kPageScaleLayerId = 2;
-  const int kViewportClipLayerId = 3;
-  const int kViewportScrollLayerId = 4;
-  std::unique_ptr<LayerImpl> root_ptr =
-      LayerImpl::Create(host_impl_->active_tree(), kRootLayerId);
-  std::unique_ptr<LayerImpl> page_scale =
-      LayerImpl::Create(host_impl_->active_tree(), kPageScaleLayerId);
-  std::unique_ptr<LayerImpl> root_clip =
-      LayerImpl::Create(host_impl_->active_tree(), kViewportClipLayerId);
-  root_clip->test_properties()->force_render_surface = true;
-  std::unique_ptr<LayerImpl> root_scrolling =
-      CreateScrollableLayer(kViewportScrollLayerId, surface_size);
-  root_scrolling->test_properties()->is_container_for_fixed_position_layers =
-      true;
-
-  std::unique_ptr<LayerImpl> grand_child =
-      CreateScrollableLayer(5, surface_size);
-
-  std::unique_ptr<LayerImpl> child = CreateScrollableLayer(6, surface_size);
-  LayerImpl* grand_child_layer = grand_child.get();
-  child->test_properties()->AddChild(std::move(grand_child));
-
-  LayerImpl* child_layer = child.get();
-  root_scrolling->test_properties()->AddChild(std::move(child));
-  root_clip->test_properties()->AddChild(std::move(root_scrolling));
-  page_scale->test_properties()->AddChild(std::move(root_clip));
-  root_ptr->test_properties()->AddChild(std::move(page_scale));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr));
-  LayerTreeImpl::ViewportLayerIds viewport_ids;
-  viewport_ids.page_scale = kPageScaleLayerId;
-  viewport_ids.inner_viewport_container = kViewportClipLayerId;
-  viewport_ids.inner_viewport_scroll = kViewportScrollLayerId;
-  host_impl_->active_tree()->SetViewportLayersFromIds(viewport_ids);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersNoScrolls(surface_size);
+  LayerImpl* child_layer = AddScrollableLayer(
+      host_impl_->InnerViewportScrollLayer(), viewport_size, surface_size);
+  LayerImpl* grand_child_layer =
+      AddScrollableLayer(child_layer, viewport_size, surface_size);
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
 
   grand_child_layer->layer_tree_impl()
       ->property_trees()
@@ -7174,30 +6579,18 @@
 }
 
 // Ensure that layers who's scroll parent is the InnerViewportScrollNode are
-// still able to scroll on thte compositor.
+// still able to scroll on the compositor.
 TEST_F(LayerTreeHostImplTest, ChildrenOfInnerScrollNodeCanScrollOnThread) {
   gfx::Size viewport_size(10, 10);
   gfx::Size content_size(20, 20);
-  LayerImpl* content_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, content_size);
-
-  constexpr int kFixedLayerId = 300;
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   // Simulate adding a "fixed" layer to the tree.
   {
-    std::unique_ptr<LayerImpl> fixed_layer =
-        LayerImpl::Create(host_impl_->active_tree(), kFixedLayerId);
+    LayerImpl* fixed_layer = AddLayer();
     fixed_layer->SetBounds(viewport_size);
     fixed_layer->SetDrawsContent(true);
-    content_layer->test_properties()->AddChild(std::move(fixed_layer));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
-    // This is very hackish but we want to simulate the kind of property tree
-    // that blink would create where a fixed layer's ScrollNode is parented to
-    // the inner viewport, rather than the outer.
-    host_impl_->active_tree()
-        ->LayerById(kFixedLayerId)
-        ->SetScrollTreeIndex(
-            host_impl_->active_tree()->InnerViewportScrollNode()->id);
+    CopyProperties(host_impl_->InnerViewportScrollLayer(), fixed_layer);
   }
 
   host_impl_->active_tree()->DidBecomeActive();
@@ -7221,8 +6614,7 @@
     // The outer viewport should have scrolled.
     ASSERT_EQ(scroll_info->scrolls.size(), 1u);
     EXPECT_TRUE(ScrollInfoContains(
-        *scroll_info.get(),
-        host_impl_->active_tree()->OuterViewportScrollNode()->element_id,
+        *scroll_info.get(), host_impl_->OuterViewportScrollNode()->element_id,
         scroll_delta));
   }
 }
@@ -7232,26 +6624,14 @@
   // should be applied to one of its ancestors if possible.
   gfx::Size viewport_size(10, 10);
   gfx::Size content_size(20, 20);
-  LayerImpl* content_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, content_size);
-
-  constexpr int kScrollChildClipId = 300;
-  constexpr int kScrollChildScrollId = 301;
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   // Add a scroller whose scroll bounds and scroll container bounds are equal.
   // Since the max_scroll_offset is 0, scrolls will bubble.
-  std::unique_ptr<LayerImpl> scroll_child_clip =
-      LayerImpl::Create(host_impl_->active_tree(), kScrollChildClipId);
-  std::unique_ptr<LayerImpl> scroll_child =
-      CreateScrollableLayer(kScrollChildScrollId, gfx::Size(10, 10));
-  scroll_child->test_properties()->is_container_for_fixed_position_layers =
-      true;
-  scroll_child->SetScrollable(gfx::Size(10, 10));
+  LayerImpl* scroll_child_clip = AddContentLayer();
+  AddScrollableLayer(scroll_child_clip, gfx::Size(10, 10), gfx::Size(10, 10));
 
-  scroll_child_clip->test_properties()->AddChild(std::move(scroll_child));
-  content_layer->test_properties()->AddChild(std::move(scroll_child_clip));
-
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
   DrawFrame();
   {
@@ -7272,87 +6652,23 @@
     // Only the root scroll should have scrolled.
     ASSERT_EQ(scroll_info->scrolls.size(), 1u);
     EXPECT_TRUE(ScrollInfoContains(
-        *scroll_info.get(),
-        host_impl_->active_tree()->OuterViewportScrollNode()->element_id,
+        *scroll_info.get(), host_impl_->OuterViewportScrollNode()->element_id,
         scroll_delta));
   }
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw) {
-  const int kRootLayerId = 1;
-  const int kInnerViewportClipLayerId = 2;
-  const int kOuterViewportClipLayerId = 7;
-  const int kInnerViewportScrollLayerId = 3;
-  const int kOuterViewportScrollLayerId = 8;
   gfx::Size surface_size(10, 10);
-  std::unique_ptr<LayerImpl> root_ptr =
-      LayerImpl::Create(host_impl_->active_tree(), kRootLayerId);
-  std::unique_ptr<LayerImpl> inner_clip =
-      LayerImpl::Create(host_impl_->active_tree(), kInnerViewportClipLayerId);
-  std::unique_ptr<LayerImpl> inner_scroll =
-      CreateScrollableLayer(kInnerViewportScrollLayerId, surface_size);
-  std::unique_ptr<LayerImpl> outer_clip =
-      LayerImpl::Create(host_impl_->active_tree(), kOuterViewportClipLayerId);
-  std::unique_ptr<LayerImpl> outer_scroll =
-      CreateScrollableLayer(kOuterViewportScrollLayerId, surface_size);
-  inner_clip->test_properties()->force_render_surface = true;
-  inner_scroll->test_properties()->is_container_for_fixed_position_layers =
-      true;
-  outer_scroll->test_properties()->is_container_for_fixed_position_layers =
-      true;
-  outer_clip->test_properties()->AddChild(std::move(outer_scroll));
-  inner_scroll->test_properties()->AddChild(std::move(outer_clip));
-  inner_clip->test_properties()->AddChild(std::move(inner_scroll));
-  root_ptr->test_properties()->AddChild(std::move(inner_clip));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr));
-  LayerTreeImpl::ViewportLayerIds viewport_ids;
-  viewport_ids.inner_viewport_container = kInnerViewportClipLayerId;
-  viewport_ids.outer_viewport_container = kOuterViewportClipLayerId;
-  viewport_ids.inner_viewport_scroll = kInnerViewportScrollLayerId;
-  viewport_ids.outer_viewport_scroll = kOuterViewportScrollLayerId;
-  host_impl_->active_tree()->SetViewportLayersFromIds(viewport_ids);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersNoScrolls(surface_size);
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
-
   // Draw one frame and then immediately rebuild the layer tree to mimic a tree
   // synchronization.
   DrawFrame();
 
-  const int kInnerViewportClipLayerId2 = 5;
-  const int kOuterViewportClipLayerId2 = 9;
-  const int kInnerViewportScrollLayerId2 = 6;
-  const int kOuterViewportScrollLayerId2 = 10;
-  host_impl_->active_tree()->DetachLayers();
-  std::unique_ptr<LayerImpl> root_ptr2 =
-      LayerImpl::Create(host_impl_->active_tree(), 4);
-  std::unique_ptr<LayerImpl> inner_clip2 =
-      LayerImpl::Create(host_impl_->active_tree(), kInnerViewportClipLayerId2);
-  std::unique_ptr<LayerImpl> inner_scroll2 =
-      CreateScrollableLayer(kInnerViewportScrollLayerId2, surface_size);
-  std::unique_ptr<LayerImpl> outer_clip2 =
-      LayerImpl::Create(host_impl_->active_tree(), kOuterViewportClipLayerId2);
-  std::unique_ptr<LayerImpl> outer_scroll2 =
-      CreateScrollableLayer(kOuterViewportScrollLayerId2, surface_size);
-  inner_scroll2->test_properties()->is_container_for_fixed_position_layers =
-      true;
-  outer_scroll2->test_properties()->is_container_for_fixed_position_layers =
-      true;
-  outer_clip2->test_properties()->AddChild(std::move(outer_scroll2));
-  inner_scroll2->test_properties()->AddChild(std::move(outer_clip2));
-  inner_clip2->test_properties()->AddChild(std::move(inner_scroll2));
-  inner_clip2->test_properties()->force_render_surface = true;
-  root_ptr2->test_properties()->AddChild(std::move(inner_clip2));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr2));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  LayerTreeImpl::ViewportLayerIds viewport_ids2;
-  viewport_ids2.inner_viewport_container = kInnerViewportClipLayerId2;
-  viewport_ids2.outer_viewport_container = kOuterViewportClipLayerId2;
-  viewport_ids2.inner_viewport_scroll = kInnerViewportScrollLayerId2;
-  viewport_ids2.outer_viewport_scroll = kOuterViewportScrollLayerId2;
-  host_impl_->active_tree()->SetViewportLayersFromIds(viewport_ids2);
-  host_impl_->active_tree()->DidBecomeActive();
+  ClearLayersAndPropertyTrees(host_impl_->active_tree());
+  SetupViewportLayersNoScrolls(surface_size);
 
   // Scrolling should still work even though we did not draw yet.
   EXPECT_EQ(
@@ -7363,7 +6679,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
   scroll_layer->SetDrawsContent(true);
 
   // Rotate the root layer 90 degrees counter-clockwise about its center.
@@ -7371,10 +6688,6 @@
   rotate_transform.Rotate(-90.0);
   // Set external transform.
   host_impl_->OnDraw(rotate_transform, gfx::Rect(0, 0, 50, 50), false, false);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  gfx::Size surface_size(50, 50);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
 
   // Scroll to the right in screen coordinates with a gesture.
@@ -7414,43 +6727,35 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  int child_clip_layer_id = 6;
-  int child_layer_id = 7;
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
   float child_layer_angle = -20.f;
 
   // Create a child layer that is rotated to a non-axis-aligned angle.
-  std::unique_ptr<LayerImpl> clip_layer =
-      LayerImpl::Create(host_impl_->active_tree(), child_clip_layer_id);
-  std::unique_ptr<LayerImpl> child =
-      CreateScrollableLayer(child_layer_id, scroll_layer->bounds());
+  // Only allow vertical scrolling.
+  gfx::Size content_size = scroll_layer->bounds();
+  gfx::Size scroll_container_bounds(content_size.width(),
+                                    content_size.height() / 2);
+  LayerImpl* clip_layer = AddLayer();
+  clip_layer->SetBounds(scroll_container_bounds);
+  CopyProperties(scroll_layer, clip_layer);
   gfx::Transform rotate_transform;
   rotate_transform.Translate(-50.0, -50.0);
   rotate_transform.Rotate(child_layer_angle);
   rotate_transform.Translate(50.0, 50.0);
-  clip_layer->test_properties()->transform = rotate_transform;
-
-  // Only allow vertical scrolling.
-  gfx::Size scroll_container_bounds =
-      gfx::Size(child->bounds().width(), child->bounds().height() / 2);
-  clip_layer->SetBounds(scroll_container_bounds);
-  child->SetScrollable(scroll_container_bounds);
-  child->SetHitTestable(true);
+  auto& clip_layer_transform_node = CreateTransformNode(clip_layer);
   // The rotation depends on the layer's transform origin, and the child layer
   // is a different size than the clip, so make sure the clip layer's origin
   // lines up over the child.
-  clip_layer->test_properties()->transform_origin = gfx::Point3F(
+  clip_layer_transform_node.origin = gfx::Point3F(
       clip_layer->bounds().width() * 0.5f, clip_layer->bounds().height(), 0.f);
-  LayerImpl* child_ptr = child.get();
-  clip_layer->test_properties()->AddChild(std::move(child));
-  // TODO(pdr): Shouldn't clip_layer be scroll_layer's parent?
-  scroll_layer->test_properties()->AddChild(std::move(clip_layer));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  clip_layer_transform_node.local = rotate_transform;
 
-  ElementId child_scroll_id = LayerIdToElementIdForTesting(child_layer_id);
+  LayerImpl* child =
+      AddScrollableLayer(clip_layer, scroll_container_bounds, content_size);
 
-  gfx::Size surface_size(50, 50);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
+  ElementId child_scroll_id = child->element_id();
+
   DrawFrame();
   {
     // Scroll down in screen coordinates with a gesture.
@@ -7479,7 +6784,7 @@
   }
   {
     // Now reset and scroll the same amount horizontally.
-    SetScrollOffsetDelta(child_ptr, gfx::Vector2dF());
+    SetScrollOffsetDelta(child, gfx::Vector2dF());
     gfx::Vector2d gesture_scroll_delta(10, 0);
     EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
               host_impl_
@@ -7507,36 +6812,30 @@
 TEST_F(LayerTreeHostImplTest, ScrollPerspectiveTransformedLayer) {
   // When scrolling an element with perspective, the distance scrolled
   // depends on the point at which the scroll begins.
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  int child_clip_layer_id = 6;
-  int child_layer_id = 7;
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
 
   // Create a child layer that is rotated on its x axis, with perspective.
-  std::unique_ptr<LayerImpl> clip_layer =
-      LayerImpl::Create(host_impl_->active_tree(), child_clip_layer_id);
-  std::unique_ptr<LayerImpl> child =
-      CreateScrollableLayer(child_layer_id, scroll_layer->bounds());
-  LayerImpl* child_ptr = child.get();
+  LayerImpl* clip_layer = AddLayer();
+  clip_layer->SetBounds(gfx::Size(50, 50));
+  CopyProperties(scroll_layer, clip_layer);
   gfx::Transform perspective_transform;
   perspective_transform.Translate(-50.0, -50.0);
   perspective_transform.ApplyPerspectiveDepth(20);
   perspective_transform.RotateAboutXAxis(45);
   perspective_transform.Translate(50.0, 50.0);
-  clip_layer->test_properties()->transform = perspective_transform;
-
-  clip_layer->SetBounds(gfx::Size(child_ptr->bounds().width() / 2,
-                                  child_ptr->bounds().height() / 2));
+  auto& clip_layer_transform_node = CreateTransformNode(clip_layer);
   // The transform depends on the layer's transform origin, and the child layer
   // is a different size than the clip, so make sure the clip layer's origin
   // lines up over the child.
-  clip_layer->test_properties()->transform_origin = gfx::Point3F(
+  clip_layer_transform_node.origin = gfx::Point3F(
       clip_layer->bounds().width(), clip_layer->bounds().height(), 0.f);
-  clip_layer->test_properties()->AddChild(std::move(child));
-  scroll_layer->test_properties()->AddChild(std::move(clip_layer));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  clip_layer_transform_node.local = perspective_transform;
 
-  gfx::Size surface_size(50, 50);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
+  LayerImpl* child = AddScrollableLayer(clip_layer, clip_layer->bounds(),
+                                        scroll_layer->bounds());
+
+  UpdateDrawProperties(host_impl_->active_tree());
 
   std::unique_ptr<ScrollAndScaleSet> scroll_info;
 
@@ -7562,7 +6861,7 @@
   // where the previous scroll ended, but the scroll position is reset
   // for each scroll.
   for (int i = 0; i < 4; ++i) {
-    SetScrollOffsetDelta(child_ptr, gfx::Vector2dF());
+    SetScrollOffsetDelta(child, gfx::Vector2dF());
     DrawFrame();
     EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
               host_impl_
@@ -7578,8 +6877,7 @@
     host_impl_->ScrollEnd(EndState().get());
 
     scroll_info = host_impl_->ProcessScrollDeltas();
-    ElementId child_scroll_id = LayerIdToElementIdForTesting(child_layer_id);
-    EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child_scroll_id,
+    EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child->element_id(),
                                    expected_scroll_deltas[i]));
 
     // The root scroll layer should not have scrolled, because the input delta
@@ -7589,7 +6887,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollScaledLayer) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
 
   // Scale the layer to twice its normal size.
   int scale = 2;
@@ -7597,10 +6896,6 @@
   scale_transform.Scale(scale, scale);
   // Set external transform above root.
   host_impl_->OnDraw(scale_transform, gfx::Rect(0, 0, 50, 50), false, false);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  gfx::Size surface_size(50, 50);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
 
   // Scroll down in screen coordinates with a gesture.
@@ -7644,33 +6939,24 @@
   int width = 332;
   int height = 20;
   int scale = 3;
-  SetupScrollAndContentsLayers(gfx::Size(width, height));
   gfx::Size container_bounds = gfx::Size(width * scale - 1, height * scale);
-  host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
-      container_bounds);
-  host_impl_->active_tree()->InnerViewportScrollLayer()->SetScrollable(
-      container_bounds);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(container_bounds, gfx::Size(width, height));
+  UpdateDrawProperties(host_impl_->active_tree());
 
   host_impl_->active_tree()->SetDeviceScaleFactor(scale);
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
 
   LayerImpl* inner_viewport_scroll_layer =
-      host_impl_->active_tree()->InnerViewportScrollLayer();
+      host_impl_->InnerViewportScrollLayer();
   EXPECT_EQ(gfx::ScrollOffset(0, 0),
             inner_viewport_scroll_layer->MaxScrollOffset());
 }
 
 TEST_F(LayerTreeHostImplTest, RootLayerScrollOffsetDelegation) {
   TestInputHandlerClient scroll_watcher;
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(10, 20));
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  LayerImpl* clip_layer =
-      scroll_layer->test_properties()->parent->test_properties()->parent;
-  clip_layer->SetBounds(gfx::Size(10, 20));
-  scroll_layer->SetScrollable(gfx::Size(10, 20));
-  scroll_layer->SetHitTestable(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   host_impl_->BindToClient(&scroll_watcher);
 
@@ -7752,13 +7038,14 @@
 
   // Forces a full tree synchronization and ensures that the scroll delegate
   // sees the correct size of the new tree.
-  gfx::Size new_size(42, 24);
+  gfx::Size new_viewport_size(21, 12);
+  gfx::Size new_content_size(42, 24);
   CreatePendingTree();
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-  CreateScrollAndContentsLayers(host_impl_->pending_tree(), new_size);
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayers(host_impl_->pending_tree(), new_viewport_size,
+                      new_content_size, new_content_size);
   host_impl_->ActivateSyncTree();
-  EXPECT_EQ(gfx::SizeF(new_size), scroll_watcher.scrollable_size());
+  EXPECT_EQ(gfx::SizeF(new_content_size), scroll_watcher.scrollable_size());
 
   // Tear down the LayerTreeHostImpl before the InputHandlerClient.
   host_impl_->ReleaseLayerTreeFrameSink();
@@ -7777,13 +7064,8 @@
 
 TEST_F(LayerTreeHostImplTest,
        ExternalRootLayerScrollOffsetDelegationReflectedInNextDraw) {
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(10, 20));
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  LayerImpl* clip_layer =
-      scroll_layer->test_properties()->parent->test_properties()->parent;
-  clip_layer->SetBounds(gfx::Size(10, 20));
-  scroll_layer->SetScrollable(gfx::Size(10, 20));
-  scroll_layer->SetHitTestable(true);
+  SetupViewportLayersInnerScrolls(gfx::Size(10, 20), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
   scroll_layer->SetDrawsContent(true);
 
   // Draw first frame to clear any pending draws and check scroll.
@@ -7794,7 +7076,8 @@
   // Set external scroll delta on delegate and notify LayerTreeHost.
   gfx::ScrollOffset scroll_offset(10.f, 10.f);
   host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CheckLayerScrollDelta(scroll_layer, gfx::Vector2dF(0.f, 0.f));
+  EXPECT_TRUE(host_impl_->active_tree()->needs_update_draw_properties());
 
   // Check scroll delta reflected in layer.
   TestFrameData frame;
@@ -7812,17 +7095,16 @@
 TEST_F(LayerTreeHostImplTest, SetRootScrollOffsetUserScrollable) {
   gfx::Size viewport_size(100, 100);
   gfx::Size content_size(200, 200);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
-  auto* outer_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
-  auto* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
+  auto* outer_scroll = host_impl_->OuterViewportScrollLayer();
+  auto* inner_scroll = host_impl_->InnerViewportScrollLayer();
 
   ScrollTree& scroll_tree =
       host_impl_->active_tree()->property_trees()->scroll_tree;
   ElementId inner_element_id = inner_scroll->element_id();
   ElementId outer_element_id = outer_scroll->element_id();
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   DrawFrame();
 
   // Ensure that the scroll offset is interpreted as a content offset so it
@@ -7835,9 +7117,8 @@
   // Disable scrolling the inner viewport. Only the outer should scroll.
   {
     ASSERT_FALSE(did_request_redraw_);
-    inner_scroll->test_properties()->user_scrollable_vertical = false;
-    inner_scroll->test_properties()->user_scrollable_horizontal = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    GetScrollNode(inner_scroll)->user_scrollable_vertical = false;
+    GetScrollNode(inner_scroll)->user_scrollable_horizontal = false;
 
     gfx::ScrollOffset scroll_offset(25.f, 30.f);
     host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
@@ -7849,18 +7130,17 @@
 
     // Reset
     did_request_redraw_ = false;
-    inner_scroll->test_properties()->user_scrollable_vertical = true;
-    inner_scroll->test_properties()->user_scrollable_horizontal = true;
-    outer_scroll->SetCurrentScrollOffset(gfx::ScrollOffset(0, 0));
+    GetScrollNode(inner_scroll)->user_scrollable_vertical = true;
+    GetScrollNode(inner_scroll)->user_scrollable_horizontal = true;
+    SetScrollOffset(outer_scroll, gfx::ScrollOffset(0, 0));
   }
 
   // Disable scrolling the outer viewport. The inner should scroll to its
   // extent but there should be no bubbling over to the outer viewport.
   {
     ASSERT_FALSE(did_request_redraw_);
-    outer_scroll->test_properties()->user_scrollable_vertical = false;
-    outer_scroll->test_properties()->user_scrollable_horizontal = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    GetScrollNode(outer_scroll)->user_scrollable_vertical = false;
+    GetScrollNode(outer_scroll)->user_scrollable_horizontal = false;
 
     gfx::ScrollOffset scroll_offset(120.f, 140.f);
     host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
@@ -7872,20 +7152,19 @@
 
     // Reset
     did_request_redraw_ = false;
-    inner_scroll->test_properties()->user_scrollable_vertical = true;
-    inner_scroll->test_properties()->user_scrollable_horizontal = true;
-    inner_scroll->SetCurrentScrollOffset(gfx::ScrollOffset(0, 0));
+    GetScrollNode(outer_scroll)->user_scrollable_vertical = true;
+    GetScrollNode(outer_scroll)->user_scrollable_horizontal = true;
+    SetScrollOffset(inner_scroll, gfx::ScrollOffset(0, 0));
   }
 
   // Disable both viewports. No scrolling should take place, no redraw should
   // be requested.
   {
     ASSERT_FALSE(did_request_redraw_);
-    outer_scroll->test_properties()->user_scrollable_vertical = false;
-    outer_scroll->test_properties()->user_scrollable_horizontal = false;
-    inner_scroll->test_properties()->user_scrollable_vertical = false;
-    inner_scroll->test_properties()->user_scrollable_horizontal = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    GetScrollNode(inner_scroll)->user_scrollable_vertical = false;
+    GetScrollNode(inner_scroll)->user_scrollable_horizontal = false;
+    GetScrollNode(outer_scroll)->user_scrollable_vertical = false;
+    GetScrollNode(outer_scroll)->user_scrollable_horizontal = false;
 
     gfx::ScrollOffset scroll_offset(60.f, 70.f);
     host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
@@ -7896,20 +7175,19 @@
     EXPECT_FALSE(did_request_redraw_);
 
     // Reset
-    inner_scroll->test_properties()->user_scrollable_vertical = true;
-    inner_scroll->test_properties()->user_scrollable_horizontal = true;
-    outer_scroll->test_properties()->user_scrollable_vertical = true;
-    outer_scroll->test_properties()->user_scrollable_horizontal = true;
+    GetScrollNode(inner_scroll)->user_scrollable_vertical = true;
+    GetScrollNode(inner_scroll)->user_scrollable_horizontal = true;
+    GetScrollNode(outer_scroll)->user_scrollable_vertical = true;
+    GetScrollNode(outer_scroll)->user_scrollable_horizontal = true;
   }
 
   // If the inner is at its extent but the outer cannot scroll, we shouldn't
   // request a redraw.
   {
     ASSERT_FALSE(did_request_redraw_);
-    outer_scroll->test_properties()->user_scrollable_vertical = false;
-    outer_scroll->test_properties()->user_scrollable_horizontal = false;
-    inner_scroll->SetCurrentScrollOffset(gfx::ScrollOffset(50.f, 50.f));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    GetScrollNode(outer_scroll)->user_scrollable_vertical = false;
+    GetScrollNode(outer_scroll)->user_scrollable_horizontal = false;
+    SetScrollOffset(inner_scroll, gfx::ScrollOffset(50.f, 50.f));
 
     gfx::ScrollOffset scroll_offset(60.f, 70.f);
     host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
@@ -7920,8 +7198,8 @@
     EXPECT_FALSE(did_request_redraw_);
 
     // Reset
-    outer_scroll->test_properties()->user_scrollable_vertical = true;
-    outer_scroll->test_properties()->user_scrollable_horizontal = true;
+    GetScrollNode(outer_scroll)->user_scrollable_vertical = true;
+    GetScrollNode(outer_scroll)->user_scrollable_horizontal = true;
   }
 }
 
@@ -7931,7 +7209,7 @@
   host_impl_->active_tree()->ClearViewportLayers();
   host_impl_->active_tree()->DidBecomeActive();
 
-  auto* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
+  auto* inner_scroll = host_impl_->InnerViewportScrollLayer();
   ASSERT_FALSE(inner_scroll);
   gfx::ScrollOffset scroll_offset(25.f, 30.f);
   host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
@@ -7939,10 +7217,8 @@
 
 TEST_F(LayerTreeHostImplTest, OverscrollRoot) {
   InputHandlerScrollResult scroll_result;
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
   DrawFrame();
   EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
@@ -8071,33 +7347,22 @@
   // Scroll child layers beyond their maximum scroll range and make sure root
   // overscroll does not accumulate.
   InputHandlerScrollResult scroll_result;
+  gfx::Size scroll_container_size(5, 5);
   gfx::Size surface_size(10, 10);
-  const int kInnerViewportClipLayerId = 4;
-  const int kInnerViewportScrollLayerId = 1;
-  std::unique_ptr<LayerImpl> root_clip =
-      LayerImpl::Create(host_impl_->active_tree(), kInnerViewportClipLayerId);
-  root_clip->test_properties()->force_render_surface = true;
-
-  std::unique_ptr<LayerImpl> root =
-      CreateScrollableLayer(kInnerViewportScrollLayerId, surface_size);
-
-  std::unique_ptr<LayerImpl> grand_child =
-      CreateScrollableLayer(3, surface_size);
-
-  std::unique_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size);
-  LayerImpl* grand_child_layer = grand_child.get();
-  child->test_properties()->AddChild(std::move(grand_child));
+  LayerImpl* root_clip = SetupDefaultRootLayer(surface_size);
+  LayerImpl* root =
+      AddScrollableLayer(root_clip, scroll_container_size, surface_size);
+  LayerImpl* child_layer =
+      AddScrollableLayer(root, scroll_container_size, surface_size);
+  LayerImpl* grand_child_layer =
+      AddScrollableLayer(child_layer, scroll_container_size, surface_size);
 
   LayerTreeImpl::ViewportLayerIds viewport_ids;
-  viewport_ids.inner_viewport_container = kInnerViewportClipLayerId;
-  viewport_ids.inner_viewport_scroll = kInnerViewportScrollLayerId;
+  viewport_ids.inner_viewport_container = root_clip->id();
+  viewport_ids.inner_viewport_scroll = root->id();
   host_impl_->active_tree()->SetViewportLayersFromIds(viewport_ids);
 
-  LayerImpl* child_layer = child.get();
-  root->test_properties()->AddChild(std::move(child));
-  root_clip->test_properties()->AddChild(std::move(root));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_clip));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->DidBecomeActive();
 
   child_layer->layer_tree_impl()
@@ -8109,7 +7374,6 @@
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(
           grand_child_layer->element_id(), gfx::ScrollOffset(0, 2));
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(surface_size));
   DrawFrame();
   {
     gfx::Vector2d scroll_delta(0, -10);
@@ -8177,9 +7441,7 @@
   // should be applied to one of its ancestors if possible. Overscroll should
   // be reflected only when it has bubbled up to the root scrolling layer.
   InputHandlerScrollResult scroll_result;
-  SetupScrollAndContentsLayers(gfx::Size(20, 20));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
+  SetupViewportLayersInnerScrolls(gfx::Size(10, 10), gfx::Size(20, 20));
   DrawFrame();
   {
     gfx::Vector2d scroll_delta(0, 8);
@@ -8212,16 +7474,9 @@
   LayerTreeSettings settings = DefaultSettings();
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
 
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(50, 50));
-  LayerImpl* clip_layer =
-      scroll_layer->test_properties()->parent->test_properties()->parent;
+  SetupViewportLayersNoScrolls(gfx::Size(50, 50));
+  UpdateDrawProperties(host_impl_->active_tree());
 
-  clip_layer->SetBounds(gfx::Size(50, 50));
-  scroll_layer->SetScrollable(gfx::Size(50, 50));
-  scroll_layer->SetHitTestable(true);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
   DrawFrame();
   EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
@@ -8243,13 +7498,7 @@
   InputHandlerScrollResult scroll_result;
   gfx::Size viewport_size(100, 100);
   gfx::Size content_size(200, 200);
-  LayerImpl* root_scroll_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, viewport_size);
-  host_impl_->active_tree()->OuterViewportScrollLayer()->SetBounds(
-      content_size);
-  root_scroll_layer->SetBounds(content_size);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
   DrawFrame();
   {
     // Edge glow effect should be applicable only upon reaching Edges
@@ -8315,27 +7564,12 @@
   const gfx::Size content_size(200, 200);
   const gfx::Size viewport_size(100, 100);
 
-  LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-
-  LayerImpl* content_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
-  LayerImpl* scroll_layer = nullptr;
-
+  LayerImpl* content_layer = AddContentLayer();
   // Initialization: Add a nested scrolling layer, simulating a scrolling div.
-  {
-    std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
-    scroll->SetBounds(gfx::Size(400, 400));
-    scroll->SetScrollable(content_size);
-    scroll->SetHitTestable(true);
-    scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-    scroll->SetDrawsContent(true);
-
-    scroll_layer = scroll.get();
-
-    content_layer->test_properties()->AddChild(std::move(scroll));
-    layer_tree_impl->BuildPropertyTreesForTesting();
-  }
+  LayerImpl* scroll_layer =
+      AddScrollableLayer(content_layer, content_size, gfx::Size(400, 400));
 
   InputHandlerScrollResult scroll_result;
   DrawFrame();
@@ -8387,23 +7621,16 @@
   LayerTreeSettings settings = DefaultSettings();
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
 
-  const gfx::Size content_size(50, 50);
   const gfx::Size viewport_size(50, 50);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersNoScrolls(viewport_size);
 
-  host_impl_->active_tree()
-      ->InnerViewportScrollLayer()
-      ->test_properties()
+  GetScrollNode(host_impl_->InnerViewportScrollLayer())
       ->main_thread_scrolling_reasons =
       MainThreadScrollingReason::kThreadedScrollingDisabled;
-  host_impl_->active_tree()
-      ->OuterViewportScrollLayer()
-      ->test_properties()
+  GetScrollNode(host_impl_->OuterViewportScrollLayer())
       ->main_thread_scrolling_reasons =
       MainThreadScrollingReason::kThreadedScrollingDisabled;
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   DrawFrame();
 
   // Overscroll initiated outside layers will be handled by the main thread.
@@ -8430,47 +7657,25 @@
 TEST_F(LayerTreeHostImplTest, ScrollFromOuterViewportSibling) {
   const gfx::Size viewport_size(100, 100);
 
-  LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-
-  CreateBasicVirtualViewportLayers(viewport_size, viewport_size);
+  SetupViewportLayersNoScrolls(viewport_size);
   host_impl_->active_tree()->SetTopControlsHeight(10);
   host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f);
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
 
-  LayerImpl* scroll_layer = nullptr;
-
   // Create a scrolling layer that's parented directly to the inner viewport.
   // This will test that scrolls that chain up to the inner viewport without
   // passing through the outer viewport still scroll correctly and affect
   // browser controls.
-  {
-    std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
-    scroll->SetBounds(gfx::Size(400, 400));
-    scroll->SetScrollable(viewport_size);
-    scroll->SetHitTestable(true);
-    scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-    scroll->SetDrawsContent(true);
+  LayerImpl* scroll_layer = AddScrollableLayer(
+      inner_scroll_layer, viewport_size, gfx::Size(400, 400));
 
-    scroll_layer = scroll.get();
-
-    inner_scroll_layer->test_properties()->AddChild(std::move(scroll));
-
-    // Move the outer viewport layer away so that scrolls won't target it.
-    host_impl_->active_tree()
-        ->OuterViewportContainerLayer()
-        ->test_properties()
-        ->position = gfx::PointF(400, 400);
-
-    layer_tree_impl->BuildPropertyTreesForTesting();
-
-    float min_page_scale = 1.f, max_page_scale = 4.f;
-    float page_scale_factor = 2.f;
-    host_impl_->active_tree()->PushPageScaleFromMainThread(
-        page_scale_factor, min_page_scale, max_page_scale);
-    host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
-  }
+  float min_page_scale = 1.f, max_page_scale = 4.f;
+  float page_scale_factor = 2.f;
+  host_impl_->active_tree()->PushPageScaleFromMainThread(
+      page_scale_factor, min_page_scale, max_page_scale);
+  host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
 
   // Fully scroll the child.
   {
@@ -8523,44 +7728,26 @@
 
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
 
-  LayerImpl* content_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
-
-  LayerImpl* scroll_layer = nullptr;
-  LayerImpl* child_scroll_layer = nullptr;
+  LayerImpl* content_layer = AddContentLayer();
 
   // Initialization: Add two nested scrolling layers, simulating a scrolling div
   // with another scrolling div inside it. Set the outer "div" to be the outer
   // viewport.
-  {
-    std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
-    scroll->SetBounds(gfx::Size(400, 400));
-    scroll->SetScrollable(content_size);
-    scroll->SetHitTestable(true);
-    scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-    scroll->SetDrawsContent(true);
+  LayerImpl* scroll_layer =
+      AddScrollableLayer(content_layer, content_size, gfx::Size(400, 400));
+  GetScrollNode(scroll_layer)->scrolls_outer_viewport = true;
+  LayerImpl* child_scroll_layer = AddScrollableLayer(
+      scroll_layer, gfx::Size(300, 300), gfx::Size(500, 500));
 
-    std::unique_ptr<LayerImpl> scroll2 = LayerImpl::Create(layer_tree_impl, 13);
-    scroll2->SetBounds(gfx::Size(500, 500));
-    scroll2->SetScrollable(gfx::Size(300, 300));
-    scroll2->SetHitTestable(true);
-    scroll2->SetElementId(LayerIdToElementIdForTesting(scroll2->id()));
-    scroll2->SetDrawsContent(true);
-
-    scroll_layer = scroll.get();
-    child_scroll_layer = scroll2.get();
-
-    scroll->test_properties()->AddChild(std::move(scroll2));
-    content_layer->test_properties()->AddChild(std::move(scroll));
-    LayerTreeImpl::ViewportLayerIds viewport_ids;
-    viewport_ids.page_scale = layer_tree_impl->PageScaleLayer()->id();
-    viewport_ids.inner_viewport_scroll = inner_scroll_layer->id();
-    viewport_ids.outer_viewport_scroll = scroll_layer->id();
-    layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
-    layer_tree_impl->BuildPropertyTreesForTesting();
-  }
+  LayerTreeImpl::ViewportLayerIds viewport_ids;
+  viewport_ids.page_scale = layer_tree_impl->PageScaleLayer()->id();
+  viewport_ids.inner_viewport_scroll = inner_scroll_layer->id();
+  viewport_ids.outer_viewport_scroll = scroll_layer->id();
+  layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
+  UpdateDrawProperties(layer_tree_impl);
 
   // Scroll should target the nested scrolling layer in the content and then
   // chain to the parent scrolling layer which is now set as the outer
@@ -8639,6 +7826,7 @@
                      scroll_layer->CurrentScrollOffset());
   }
 }
+
 // Test that scrolls chain correctly when a child scroller on the page (e.g. a
 // scrolling div) is set as the outer viewport but scrolls start from a layer
 // that's not a descendant of the outer viewport. This happens in the
@@ -8649,52 +7837,28 @@
 
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
 
-  LayerImpl* content_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
-
-  LayerImpl* outer_scroll_layer = nullptr;
-  LayerImpl* sibling_scroll_layer = nullptr;
+  LayerImpl* content_layer = AddContentLayer();
 
   // Initialization: Add a scrolling layer, simulating an ordinary DIV, to be
   // set as the outer viewport. Add a sibling scrolling layer that isn't a child
   // of the outer viewport scroll layer.
-  {
-    std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
-    scroll->SetBounds(gfx::Size(1200, 1200));
-    scroll->SetScrollable(content_size);
-    scroll->SetHitTestable(true);
-    scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-    scroll->SetDrawsContent(true);
+  LayerImpl* outer_scroll_layer =
+      AddScrollableLayer(content_layer, content_size, gfx::Size(1200, 1200));
+  GetScrollNode(outer_scroll_layer)->scrolls_outer_viewport = true;
+  LayerImpl* sibling_scroll_layer = AddScrollableLayer(
+      content_layer, gfx::Size(600, 600), gfx::Size(1200, 1200));
 
-    outer_scroll_layer = scroll.get();
+  LayerImpl* inner_container = layer_tree_impl->InnerViewportContainerLayer();
+  LayerTreeImpl::ViewportLayerIds viewport_ids;
+  viewport_ids.page_scale = layer_tree_impl->PageScaleLayer()->id();
+  viewport_ids.inner_viewport_container = inner_container->id();
+  viewport_ids.inner_viewport_scroll = inner_scroll_layer->id();
+  viewport_ids.outer_viewport_scroll = outer_scroll_layer->id();
+  layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
 
-    content_layer->test_properties()->AddChild(std::move(scroll));
-
-    // Create the non-descendant.
-    std::unique_ptr<LayerImpl> scroll2 = LayerImpl::Create(layer_tree_impl, 15);
-    scroll2->SetBounds(gfx::Size(1200, 1200));
-    scroll2->SetScrollable(gfx::Size(600, 600));
-    scroll2->SetHitTestable(true);
-    scroll2->SetElementId(LayerIdToElementIdForTesting(scroll2->id()));
-    scroll2->SetDrawsContent(true);
-
-    sibling_scroll_layer = scroll2.get();
-
-    content_layer->test_properties()->AddChild(std::move(scroll2));
-
-    LayerImpl* inner_container =
-        host_impl_->active_tree()->InnerViewportContainerLayer();
-    LayerTreeImpl::ViewportLayerIds viewport_ids;
-    viewport_ids.page_scale = layer_tree_impl->PageScaleLayer()->id();
-    viewport_ids.inner_viewport_container = inner_container->id();
-    viewport_ids.inner_viewport_scroll = inner_scroll_layer->id();
-    viewport_ids.outer_viewport_scroll = outer_scroll_layer->id();
-    layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
-    layer_tree_impl->BuildPropertyTreesForTesting();
-
-    ASSERT_EQ(outer_scroll_layer, layer_tree_impl->OuterViewportScrollLayer());
-  }
+  ASSERT_EQ(outer_scroll_layer, layer_tree_impl->OuterViewportScrollLayer());
 
   // Scrolls should target the non-descendant scroller. Chaining should not
   // propagate to the outer viewport scroll layer.
@@ -8819,12 +7983,10 @@
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
 
   const gfx::Size content_size(50, 50);
-  const gfx::Size viewport_size(50, 50);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersNoScrolls(content_size);
 
   // By default, no main thread scrolling reasons should exist.
-  LayerImpl* scroll_layer =
-      host_impl_->active_tree()->InnerViewportScrollLayer();
+  LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
   ScrollNode* scroll_node =
       host_impl_->active_tree()->property_trees()->scroll_tree.Node(
           scroll_layer->scroll_tree_index());
@@ -8854,6 +8016,14 @@
 
 class BlendStateCheckLayer : public LayerImpl {
  public:
+  static std::unique_ptr<BlendStateCheckLayer> Create(
+      LayerTreeImpl* tree_impl,
+      int id,
+      viz::ClientResourceProvider* resource_provider) {
+    return base::WrapUnique(
+        new BlendStateCheckLayer(tree_impl, id, resource_provider));
+  }
+
   BlendStateCheckLayer(LayerTreeImpl* tree_impl,
                        int id,
                        viz::ClientResourceProvider* resource_provider)
@@ -8935,93 +8105,63 @@
 };
 
 TEST_F(LayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers) {
-  {
-    std::unique_ptr<LayerImpl> root =
-        LayerImpl::Create(host_impl_->active_tree(), 1);
-    root->SetBounds(gfx::Size(10, 10));
-    root->SetDrawsContent(false);
-    root->test_properties()->force_render_surface = true;
-    host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  }
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
+  root->SetDrawsContent(false);
 
-  root->test_properties()->AddChild(std::make_unique<BlendStateCheckLayer>(
-      host_impl_->active_tree(), 2, host_impl_->resource_provider()));
-  auto* layer1 =
-      static_cast<BlendStateCheckLayer*>(root->test_properties()->children[0]);
-  layer1->test_properties()->position = gfx::PointF(2.f, 2.f);
-
-  TestFrameData frame;
+  auto* layer1 = AddLayer<BlendStateCheckLayer>(
+      host_impl_->active_tree(), host_impl_->resource_provider());
+  CopyProperties(root, layer1);
+  CreateTransformNode(layer1).post_translation = gfx::Vector2dF(2.f, 2.f);
+  CreateEffectNode(layer1);
 
   // Opaque layer, drawn without blending.
   layer1->SetContentsOpaque(true);
   layer1->SetExpectation(false, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with translucent content and painting, so drawn with blending.
   layer1->SetContentsOpaque(false);
   layer1->SetExpectation(true, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with translucent opacity, drawn with blending.
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 0.5f;
-  layer1->NoteLayerPropertyChanged();
+  SetOpacity(layer1, 0.5f);
   layer1->SetExpectation(true, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with translucent opacity and painting, drawn with blending.
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 0.5f;
-  layer1->NoteLayerPropertyChanged();
+  SetOpacity(layer1, 0.5f);
   layer1->SetExpectation(true, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
-  layer1->test_properties()->AddChild(std::make_unique<BlendStateCheckLayer>(
-      host_impl_->active_tree(), 3, host_impl_->resource_provider()));
-  auto* layer2 = static_cast<BlendStateCheckLayer*>(
-      layer1->test_properties()->children[0]);
-  layer2->test_properties()->position = gfx::PointF(4.f, 4.f);
+  auto* layer2 = AddLayer<BlendStateCheckLayer>(
+      host_impl_->active_tree(), host_impl_->resource_provider());
+  CopyProperties(layer1, layer2);
+  CreateTransformNode(layer2).post_translation = gfx::Vector2dF(4.f, 4.f);
+  CreateEffectNode(layer2);
 
   // 2 opaque layers, drawn without blending.
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 1.f;
-  layer1->NoteLayerPropertyChanged();
+  SetOpacity(layer1, 1.f);
   layer1->SetExpectation(false, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetContentsOpaque(true);
-  layer2->test_properties()->opacity = 1.f;
-  layer2->NoteLayerPropertyChanged();
+  SetOpacity(layer2, 1.f);
   layer2->SetExpectation(false, false, root);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Parent layer with translucent content, drawn with blending.
   // Child layer with opaque content, drawn without blending.
@@ -9030,13 +8170,9 @@
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetExpectation(false, false, root);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Parent layer with translucent content but opaque painting, drawn without
   // blending.
@@ -9046,12 +8182,9 @@
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetExpectation(false, false, root);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Parent layer with translucent opacity and opaque content. Since it has a
   // drawing child, it's drawn to a render surface which carries the opacity,
@@ -9059,76 +8192,57 @@
   // Child layer with opaque content, drawn without blending (parent surface
   // carries the inherited opacity).
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 0.5f;
-  layer1->NoteLayerPropertyChanged();
-  layer1->test_properties()->force_render_surface = true;
+  SetOpacity(layer1, 0.5f);
+  GetEffectNode(layer1)->render_surface_reason = RenderSurfaceReason::kTest;
   layer1->SetExpectation(false, true, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetExpectation(false, false, layer1);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
-  layer1->test_properties()->force_render_surface = false;
+  GetEffectNode(layer1)->render_surface_reason = RenderSurfaceReason::kNone;
 
   // Draw again, but with child non-opaque, to make sure
   // layer1 not culled.
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 1.f;
-  layer1->NoteLayerPropertyChanged();
+  SetOpacity(layer1, 1.f);
   layer1->SetExpectation(false, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetContentsOpaque(true);
-  layer2->test_properties()->opacity = 0.5f;
-  layer2->NoteLayerPropertyChanged();
+  SetOpacity(layer2, 0.5f);
   layer2->SetExpectation(true, false, layer1);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // A second way of making the child non-opaque.
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 1.f;
-  layer1->NoteLayerPropertyChanged();
+  SetOpacity(layer1, 1.f);
   layer1->SetExpectation(false, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetContentsOpaque(false);
-  layer2->test_properties()->opacity = 1.f;
-  layer2->NoteLayerPropertyChanged();
+  SetOpacity(layer2, 1.f);
   layer2->SetExpectation(true, false, root);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // And when the layer says its not opaque but is painted opaque, it is not
   // blended.
   layer1->SetContentsOpaque(true);
-  layer1->test_properties()->opacity = 1.f;
-  layer1->NoteLayerPropertyChanged();
+  SetOpacity(layer1, 1.f);
   layer1->SetExpectation(false, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
   layer2->SetContentsOpaque(true);
-  layer2->test_properties()->opacity = 1.f;
-  layer2->NoteLayerPropertyChanged();
+  SetOpacity(layer2, 1.f);
   layer2->SetExpectation(false, false, root);
   layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
   EXPECT_TRUE(layer2->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with partially opaque contents, drawn with blending.
   layer1->SetContentsOpaque(false);
@@ -9137,12 +8251,8 @@
   layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
   layer1->SetExpectation(true, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with partially opaque contents partially culled, drawn with blending.
   layer1->SetContentsOpaque(false);
@@ -9151,12 +8261,8 @@
   layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
   layer1->SetExpectation(true, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with partially opaque contents culled, drawn with blending.
   layer1->SetContentsOpaque(false);
@@ -9165,12 +8271,8 @@
   layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
   layer1->SetExpectation(true, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 
   // Layer with partially opaque contents and translucent contents culled, drawn
   // without blending.
@@ -9180,16 +8282,11 @@
   layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
   layer1->SetExpectation(false, false, root);
   layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->set_needs_update_draw_properties();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
+  DrawFrame();
   EXPECT_TRUE(layer1->quads_appended());
-  host_impl_->DidDrawAllLayers(frame);
 }
 
 static bool MayContainVideoBitSetOnFrameData(LayerTreeHostImpl* host_impl) {
-  host_impl->active_tree()->BuildPropertyTreesForTesting();
   host_impl->active_tree()->set_needs_update_draw_properties();
   TestFrameData frame;
   EXPECT_EQ(DRAW_SUCCESS, host_impl->PrepareToDraw(&frame));
@@ -9200,40 +8297,35 @@
 
 TEST_F(LayerTreeHostImplTest, MayContainVideo) {
   gfx::Size big_size(1000, 1000);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(big_size));
-
-  int layer_id = 1;
-  host_impl_->active_tree()->SetRootLayerForTesting(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), layer_id++));
   auto* root =
-      static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
-
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), layer_id++));
-  auto* video_layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children.back());
+      SetupRootLayer<DidDrawCheckLayer>(host_impl_->active_tree(), big_size);
+  auto* video_layer = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
   video_layer->set_may_contain_video(true);
+  CopyProperties(root, video_layer);
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_TRUE(MayContainVideoBitSetOnFrameData(host_impl_.get()));
 
   // Test with the video layer occluded.
-  root->test_properties()->AddChild(
-      DidDrawCheckLayer::Create(host_impl_->active_tree(), layer_id++));
-  auto* large_layer =
-      static_cast<DidDrawCheckLayer*>(root->test_properties()->children.back());
+  auto* large_layer = AddLayer<DidDrawCheckLayer>(host_impl_->active_tree());
   large_layer->SetBounds(big_size);
   large_layer->SetContentsOpaque(true);
+  CopyProperties(root, large_layer);
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_FALSE(MayContainVideoBitSetOnFrameData(host_impl_.get()));
 
   // Remove the large layer.
   root->test_properties()->RemoveChild(large_layer);
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_TRUE(MayContainVideoBitSetOnFrameData(host_impl_.get()));
 
   // Move the video layer so it goes beyond the root.
-  video_layer->test_properties()->position = gfx::PointF(100.f, 100.f);
+  video_layer->SetOffsetToTransformParent(gfx::Vector2dF(100.f, 100.f));
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_FALSE(MayContainVideoBitSetOnFrameData(host_impl_.get()));
 
-  video_layer->test_properties()->position = gfx::PointF(0.f, 0.f);
+  video_layer->SetOffsetToTransformParent(gfx::Vector2dF(0.f, 0.f));
   video_layer->NoteLayerPropertyChanged();
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_TRUE(MayContainVideoBitSetOnFrameData(host_impl_.get()));
 }
 
@@ -9253,34 +8345,26 @@
 
   void SetupActiveTreeLayers() {
     host_impl_->active_tree()->set_background_color(SK_ColorGRAY);
-    host_impl_->active_tree()->SetRootLayerForTesting(
-        LayerImpl::Create(host_impl_->active_tree(), 1));
-    host_impl_->active_tree()
-        ->root_layer_for_testing()
-        ->test_properties()
-        ->force_render_surface = true;
-    host_impl_->active_tree()
-        ->root_layer_for_testing()
-        ->test_properties()
-        ->AddChild(std::make_unique<BlendStateCheckLayer>(
-            host_impl_->active_tree(), 2, host_impl_->resource_provider()));
-    child_ = static_cast<BlendStateCheckLayer*>(host_impl_->active_tree()
-                                                    ->root_layer_for_testing()
-                                                    ->test_properties()
-                                                    ->children[0]);
-    child_->SetExpectation(false, false,
-                           host_impl_->active_tree()->root_layer_for_testing());
+    LayerImpl* root = SetupDefaultRootLayer(viewport_size_);
+    child_ = AddLayer<BlendStateCheckLayer>(host_impl_->active_tree(),
+                                            host_impl_->resource_provider());
+    child_->SetExpectation(false, false, root);
     child_->SetContentsOpaque(true);
+    CopyProperties(root, child_);
+    UpdateDrawProperties(host_impl_->active_tree());
+  }
+
+  void SetLayerGeometry(const gfx::Rect& layer_rect) {
+    child_->SetBounds(layer_rect.size());
+    child_->SetQuadRect(gfx::Rect(layer_rect.size()));
+    child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
+    child_->SetOffsetToTransformParent(
+        gfx::Vector2dF(layer_rect.OffsetFromOrigin()));
   }
 
   // Expect no gutter rects.
   void TestLayerCoversFullViewport() {
-    gfx::Rect layer_rect(viewport_size_);
-    child_->test_properties()->position = gfx::PointF(layer_rect.origin());
-    child_->SetBounds(layer_rect.size());
-    child_->SetQuadRect(gfx::Rect(layer_rect.size()));
-    child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    SetLayerGeometry(gfx::Rect(viewport_size_));
 
     TestFrameData frame;
     EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
@@ -9295,14 +8379,7 @@
   }
 
   // Expect fullscreen gutter rect.
-  void SetUpEmptylayer() {
-    gfx::Rect layer_rect(0, 0, 0, 0);
-    child_->test_properties()->position = gfx::PointF(layer_rect.origin());
-    child_->SetBounds(layer_rect.size());
-    child_->SetQuadRect(gfx::Rect(layer_rect.size()));
-    child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  }
+  void SetUpEmptylayer() { SetLayerGeometry(gfx::Rect()); }
 
   void VerifyEmptyLayerRenderPasses(const viz::RenderPassList& render_passes) {
     ASSERT_EQ(1u, render_passes.size());
@@ -9316,10 +8393,7 @@
 
   void TestEmptyLayer() {
     SetUpEmptylayer();
-    TestFrameData frame;
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    VerifyEmptyLayerRenderPasses(frame.render_passes);
-    host_impl_->DidDrawAllLayers(frame);
+    DrawFrame();
   }
 
   void TestEmptyLayerWithOnDraw() {
@@ -9333,12 +8407,7 @@
 
   // Expect four surrounding gutter rects.
   void SetUpLayerInMiddleOfViewport() {
-    gfx::Rect layer_rect(500, 500, 200, 200);
-    child_->test_properties()->position = gfx::PointF(layer_rect.origin());
-    child_->SetBounds(layer_rect.size());
-    child_->SetQuadRect(gfx::Rect(layer_rect.size()));
-    child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    SetLayerGeometry(gfx::Rect(500, 500, 200, 200));
   }
 
   void VerifyLayerInMiddleOfViewport(const viz::RenderPassList& render_passes) {
@@ -9353,10 +8422,7 @@
 
   void TestLayerInMiddleOfViewport() {
     SetUpLayerInMiddleOfViewport();
-    TestFrameData frame;
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    VerifyLayerInMiddleOfViewport(frame.render_passes);
-    host_impl_->DidDrawAllLayers(frame);
+    DrawFrame();
   }
 
   void TestLayerInMiddleOfViewportWithOnDraw() {
@@ -9370,13 +8436,8 @@
 
   // Expect no gutter rects.
   void SetUpLayerIsLargerThanViewport() {
-    gfx::Rect layer_rect(viewport_size_.width() + 10,
-                         viewport_size_.height() + 10);
-    child_->test_properties()->position = gfx::PointF(layer_rect.origin());
-    child_->SetBounds(layer_rect.size());
-    child_->SetQuadRect(gfx::Rect(layer_rect.size()));
-    child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    SetLayerGeometry(
+        gfx::Rect(viewport_size_.width() + 10, viewport_size_.height() + 10));
   }
 
   void VerifyLayerIsLargerThanViewport(
@@ -9390,10 +8451,7 @@
 
   void TestLayerIsLargerThanViewport() {
     SetUpLayerIsLargerThanViewport();
-    TestFrameData frame;
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    VerifyLayerIsLargerThanViewport(frame.render_passes);
-    host_impl_->DidDrawAllLayers(frame);
+    DrawFrame();
   }
 
   void TestLayerIsLargerThanViewportWithOnDraw() {
@@ -9454,11 +8512,6 @@
     }
   }
 
-  gfx::Size DipSizeToPixelSize(const gfx::Size& size) {
-    return gfx::ScaleToRoundedSize(
-        size, host_impl_->active_tree()->device_scale_factor());
-  }
-
   viz::DrawQuad::Material gutter_quad_material_;
   gfx::Size gutter_texture_size_;
   gfx::Size viewport_size_;
@@ -9471,9 +8524,6 @@
 
   bool software = false;
   CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software));
-
-  host_impl_->active_tree()->SetDeviceViewportRect(
-      gfx::Rect(DipSizeToPixelSize(viewport_size_)));
   SetupActiveTreeLayers();
   EXPECT_SCOPED(TestLayerCoversFullViewport());
   EXPECT_SCOPED(TestEmptyLayer());
@@ -9488,8 +8538,6 @@
   CreateHostImpl(DefaultSettings(), CreateFakeLayerTreeFrameSink(software));
 
   host_impl_->active_tree()->SetDeviceScaleFactor(2.f);
-  host_impl_->active_tree()->SetDeviceViewportRect(
-      gfx::Rect(DipSizeToPixelSize(viewport_size_)));
   SetupActiveTreeLayers();
   EXPECT_SCOPED(TestLayerCoversFullViewport());
   EXPECT_SCOPED(TestEmptyLayer());
@@ -9505,8 +8553,6 @@
 
   // Pending tree to force active_tree size invalid. Not used otherwise.
   CreatePendingTree();
-  host_impl_->active_tree()->SetDeviceViewportRect(
-      gfx::Rect(DipSizeToPixelSize(viewport_size_)));
 
   SetupActiveTreeLayers();
   EXPECT_SCOPED(TestEmptyLayerWithOnDraw());
@@ -9578,26 +8624,19 @@
   layer_tree_host_impl->InitializeFrameSink(layer_tree_frame_sink.get());
   layer_tree_host_impl->WillBeginImplFrame(
       viz::CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2));
-  layer_tree_host_impl->active_tree()->SetDeviceViewportRect(
-      gfx::Rect(500, 500));
 
-  std::unique_ptr<LayerImpl> root =
-      FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 1);
-  root->test_properties()->force_render_surface = true;
-  std::unique_ptr<LayerImpl> child =
-      FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 2);
-  child->test_properties()->position = gfx::PointF(12.f, 13.f);
+  LayerImpl* root = SetupRootLayer<LayerImpl>(
+      layer_tree_host_impl->active_tree(), gfx::Size(500, 500));
+  LayerImpl* child = AddLayer<LayerImpl>(layer_tree_host_impl->active_tree());
   child->SetBounds(gfx::Size(14, 15));
   child->SetDrawsContent(true);
-  root->SetBounds(gfx::Size(500, 500));
-  root->SetDrawsContent(true);
-  root->test_properties()->AddChild(std::move(child));
-  layer_tree_host_impl->active_tree()->SetRootLayerForTesting(std::move(root));
-  layer_tree_host_impl->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(root, child);
+  child->SetOffsetToTransformParent(gfx::Vector2dF(12.f, 13.f));
   layer_tree_host_impl->active_tree()->SetLocalSurfaceIdAllocationFromParent(
       viz::LocalSurfaceIdAllocation(
           viz::LocalSurfaceId(1, base::UnguessableToken::Deserialize(2u, 3u)),
           base::TimeTicks::Now()));
+  UpdateDrawProperties(layer_tree_host_impl->active_tree());
 
   TestFrameData frame;
 
@@ -9611,18 +8650,8 @@
 
   // Second frame, only the damaged area should get swapped. Damage should be
   // the union of old and new child rects: gfx::Rect(26, 28).
-  layer_tree_host_impl->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->children[0]
-      ->test_properties()
-      ->position = gfx::PointF();
-  layer_tree_host_impl->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->children[0]
-      ->NoteLayerPropertyChanged();
-  layer_tree_host_impl->active_tree()->BuildPropertyTreesForTesting();
+  child->SetOffsetToTransformParent(gfx::Vector2dF());
+  child->NoteLayerPropertyChanged();
   EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame));
   layer_tree_host_impl->DrawLayers(&frame);
   host_impl_->DidDrawAllLayers(frame);
@@ -9633,9 +8662,7 @@
 
   layer_tree_host_impl->active_tree()->SetDeviceViewportRect(gfx::Rect(10, 10));
   // This will damage everything.
-  layer_tree_host_impl->active_tree()
-      ->root_layer_for_testing()
-      ->SetBackgroundColor(SK_ColorBLACK);
+  root->SetBackgroundColor(SK_ColorBLACK);
   EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame));
   layer_tree_host_impl->DrawLayers(&frame);
   host_impl_->DidDrawAllLayers(frame);
@@ -9648,19 +8675,15 @@
 }
 
 TEST_F(LayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface) {
-  std::unique_ptr<LayerImpl> root =
-      FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 1);
-  std::unique_ptr<LayerImpl> child =
-      FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 2);
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
+  LayerImpl* child = AddLayer();
   child->SetBounds(gfx::Size(10, 10));
   child->SetDrawsContent(true);
   root->SetBounds(gfx::Size(10, 10));
   root->SetDrawsContent(true);
-  root->test_properties()->force_render_surface = true;
-  root->test_properties()->AddChild(std::move(child));
+  CopyProperties(root, child);
 
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   TestFrameData frame;
 
@@ -9704,45 +8727,36 @@
       FakeLayerTreeFrameSink::Create3d(context_provider));
   CreateHostImpl(DefaultSettings(), std::move(layer_tree_frame_sink));
 
-  std::unique_ptr<LayerImpl> root_layer =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  root_layer->SetBounds(gfx::Size(10, 10));
-  root_layer->test_properties()->force_render_surface = true;
+  LayerImpl* root_layer = SetupDefaultRootLayer(gfx::Size(10, 10));
 
   scoped_refptr<VideoFrame> softwareFrame = media::VideoFrame::CreateColorFrame(
       gfx::Size(4, 4), 0x80, 0x80, 0x80, base::TimeDelta());
   FakeVideoFrameProvider provider;
   provider.set_frame(softwareFrame);
-  std::unique_ptr<VideoLayerImpl> video_layer = VideoLayerImpl::Create(
-      host_impl_->active_tree(), 4, &provider, media::VIDEO_ROTATION_0);
+  auto* video_layer = AddLayer<VideoLayerImpl>(
+      host_impl_->active_tree(), &provider, media::VIDEO_ROTATION_0);
   video_layer->SetBounds(gfx::Size(10, 10));
   video_layer->SetDrawsContent(true);
-  root_layer->test_properties()->AddChild(std::move(video_layer));
+  CopyProperties(root_layer, video_layer);
 
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_layer));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_EQ(0u, sii->shared_image_count());
 
-  TestFrameData frame;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   EXPECT_GT(sii->shared_image_count(), 0u);
 
   // Kill the layer tree.
-  host_impl_->active_tree()->DetachLayers();
+  ClearLayersAndPropertyTrees(host_impl_->active_tree());
   // There should be no textures left in use after.
   EXPECT_EQ(0u, sii->shared_image_count());
 }
 
-
 TEST_F(LayerTreeHostImplTest, HasTransparentBackground) {
-  SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
+  SetupDefaultRootLayer(gfx::Size(10, 10));
   host_impl_->active_tree()->set_background_color(SK_ColorWHITE);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Verify one quad is drawn when transparent background set is not set.
   TestFrameData frame;
@@ -9791,7 +8805,8 @@
     return FakeLayerTreeFrameSink::Create3d();
   }
 
-  void DrawFrameAndTestDamage(const gfx::Rect& expected_damage) {
+  void DrawFrameAndTestDamage(const gfx::Rect& expected_damage,
+                              const LayerImpl* child) {
     bool expect_to_draw = !expected_damage.IsEmpty();
 
     TestFrameData frame;
@@ -9812,15 +8827,11 @@
       // culled.
       ASSERT_EQ(2u, root_render_pass->quad_list.size());
 
-      LayerImpl* child = host_impl_->active_tree()
-                             ->root_layer_for_testing()
-                             ->test_properties()
-                             ->children[0];
       gfx::Rect expected_child_visible_rect(child->bounds());
       EXPECT_EQ(expected_child_visible_rect,
                 root_render_pass->quad_list.front()->visible_rect);
 
-      LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
+      LayerImpl* root = root_layer();
       gfx::Rect expected_root_visible_rect(root->bounds());
       EXPECT_EQ(expected_root_visible_rect,
                 root_render_pass->quad_list.ElementAt(1)->visible_rect);
@@ -9832,41 +8843,35 @@
 };
 
 TEST_F(LayerTreeHostImplTestDrawAndTestDamage, FrameIncludesDamageRect) {
-  std::unique_ptr<SolidColorLayerImpl> root =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
-  root->test_properties()->position = gfx::PointF();
-  root->SetBounds(gfx::Size(10, 10));
+  auto* root = SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(),
+                                                   gfx::Size(10, 10));
   root->SetDrawsContent(true);
   root->SetBackgroundColor(SK_ColorRED);
-  root->test_properties()->force_render_surface = true;
 
   // Child layer is in the bottom right corner.
-  std::unique_ptr<SolidColorLayerImpl> child =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), 2);
-  child->test_properties()->position = gfx::PointF(9.f, 9.f);
+  auto* child = AddLayer<SolidColorLayerImpl>(host_impl_->active_tree());
   child->SetBounds(gfx::Size(1, 1));
   child->SetDrawsContent(true);
   child->SetBackgroundColor(SK_ColorRED);
-  root->test_properties()->AddChild(std::move(child));
+  CopyProperties(root, child);
+  child->SetOffsetToTransformParent(gfx::Vector2dF(9.f, 9.f));
 
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Draw a frame. In the first frame, the entire viewport should be damaged.
   gfx::Rect full_frame_damage(
       host_impl_->active_tree()->GetDeviceViewport().size());
-  DrawFrameAndTestDamage(full_frame_damage);
+  DrawFrameAndTestDamage(full_frame_damage, child);
 
   // The second frame has damage that doesn't touch the child layer. Its quads
   // should still be generated.
   gfx::Rect small_damage = gfx::Rect(0, 0, 1, 1);
-  host_impl_->active_tree()->root_layer_for_testing()->SetUpdateRect(
-      small_damage);
-  DrawFrameAndTestDamage(small_damage);
+  root->SetUpdateRect(small_damage);
+  DrawFrameAndTestDamage(small_damage, child);
 
   // The third frame should have no damage, so no quads should be generated.
   gfx::Rect no_damage;
-  DrawFrameAndTestDamage(no_damage);
+  DrawFrameAndTestDamage(no_damage, child);
 }
 
 class GLRendererWithSetupQuadForAntialiasing : public viz::GLRenderer {
@@ -9879,59 +8884,39 @@
   // away quads can end up thinking they need AA.
   float device_scale_factor = 4.f / 3.f;
   gfx::Size root_size(2000, 1000);
-  gfx::Size device_viewport_size =
-      gfx::ScaleToCeiledSize(root_size, device_scale_factor);
-  host_impl_->active_tree()->SetDeviceViewportRect(
-      gfx::Rect(device_viewport_size));
-
   CreatePendingTree();
   host_impl_->pending_tree()->SetDeviceScaleFactor(device_scale_factor);
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f / 16.f,
                                                           16.f);
 
-  std::unique_ptr<LayerImpl> scoped_root =
-      LayerImpl::Create(host_impl_->pending_tree(), 1);
-  LayerImpl* root = scoped_root.get();
-  root->test_properties()->force_render_surface = true;
+  auto* root = SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), root_size);
   root->SetNeedsPushProperties();
 
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(scoped_root));
-
-  std::unique_ptr<LayerImpl> scoped_scrolling_layer =
-      LayerImpl::Create(host_impl_->pending_tree(), 2);
-  LayerImpl* scrolling_layer = scoped_scrolling_layer.get();
-  root->test_properties()->AddChild(std::move(scoped_scrolling_layer));
+  gfx::Size content_layer_bounds(100001, 100);
+  auto* scrolling_layer =
+      AddScrollableLayer(root, content_layer_bounds, gfx::Size());
   scrolling_layer->SetNeedsPushProperties();
 
-  gfx::Size content_layer_bounds(100001, 100);
   scoped_refptr<FakeRasterSource> raster_source(
       FakeRasterSource::CreateFilled(content_layer_bounds));
 
-  std::unique_ptr<FakePictureLayerImpl> scoped_content_layer =
-      FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
-                                                   3, raster_source);
-  LayerImpl* content_layer = scoped_content_layer.get();
-  scrolling_layer->test_properties()->AddChild(std::move(scoped_content_layer));
+  auto* content_layer = AddLayer<FakePictureLayerImplWithRasterSource>(
+      host_impl_->pending_tree(), raster_source);
+  CopyProperties(scrolling_layer, content_layer);
   content_layer->SetBounds(content_layer_bounds);
   content_layer->SetDrawsContent(true);
   content_layer->SetNeedsPushProperties();
 
-  root->SetBounds(root_size);
+  UpdateDrawProperties(host_impl_->pending_tree());
 
   gfx::ScrollOffset scroll_offset(100000, 0);
-  scrolling_layer->SetScrollable(content_layer_bounds);
-  scrolling_layer->SetHitTestable(true);
-  scrolling_layer->SetElementId(
-      LayerIdToElementIdForTesting(scrolling_layer->id()));
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
-
   scrolling_layer->layer_tree_impl()
       ->property_trees()
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(
           scrolling_layer->element_id(), scroll_offset);
   host_impl_->ActivateSyncTree();
 
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   ASSERT_EQ(1u, host_impl_->active_tree()->GetRenderSurfaceList().size());
 
   TestFrameData frame;
@@ -9966,14 +8951,10 @@
 };
 
 TEST_F(CompositorFrameMetadataTest, CompositorFrameAckCountsAsSwapComplete) {
-  SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  {
-    TestFrameData frame;
-    EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-    host_impl_->DrawLayers(&frame);
-    host_impl_->DidDrawAllLayers(frame);
-  }
+  SetupRootLayer<FakeLayerWithQuads>(host_impl_->active_tree(),
+                                     gfx::Size(10, 10));
+  UpdateDrawProperties(host_impl_->active_tree());
+  DrawFrame();
   host_impl_->ReclaimResources(std::vector<viz::ReturnedResource>());
   host_impl_->DidReceiveCompositorFrameAck();
   EXPECT_EQ(acks_received_, 1);
@@ -10008,18 +8989,18 @@
                                                  external_transform);
 
   // SolidColorLayerImpl will be drawn.
-  std::unique_ptr<SolidColorLayerImpl> root_layer =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
+  auto* root = SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(),
+                                                   gfx::Size(10, 10));
+  root->SetDrawsContent(true);
 
   // VideoLayerImpl will not be drawn.
   FakeVideoFrameProvider provider;
-  std::unique_ptr<VideoLayerImpl> video_layer = VideoLayerImpl::Create(
-      host_impl_->active_tree(), 2, &provider, media::VIDEO_ROTATION_0);
+  LayerImpl* video_layer = AddLayer<VideoLayerImpl>(
+      host_impl_->active_tree(), &provider, media::VIDEO_ROTATION_0);
   video_layer->SetBounds(gfx::Size(10, 10));
   video_layer->SetDrawsContent(true);
-  root_layer->test_properties()->AddChild(std::move(video_layer));
-  SetupRootLayerImpl(std::move(root_layer));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(root, video_layer);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   host_impl_->OnDraw(external_transform, external_viewport,
                      resourceless_software_draw, false);
@@ -10130,12 +9111,10 @@
        RequireHighResAndRedrawWhenVisible) {
   ASSERT_TRUE(host_impl_->active_tree());
 
-  std::unique_ptr<SolidColorLayerImpl> root =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
+  LayerImpl* root = SetupRootLayer<SolidColorLayerImpl>(
+      host_impl_->active_tree(), gfx::Size(10, 10));
   root->SetBackgroundColor(SK_ColorRED);
-  SetupRootLayerImpl(std::move(root));
-
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // RequiresHighResToDraw is set when new output surface is used.
   EXPECT_TRUE(host_impl_->RequiresHighResToDraw());
@@ -10466,30 +9445,26 @@
 
   CreateHostImpl(DefaultSettings(), std::move(layer_tree_frame_sink));
 
-  SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
-
-  LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
   struct Helper {
     std::unique_ptr<viz::CopyOutputResult> unprocessed_result;
     void OnResult(std::unique_ptr<viz::CopyOutputResult> result) {
       unprocessed_result = std::move(result);
     }
   } helper;
-  root->test_properties()->copy_requests.push_back(
+
+  GetEffectNode(root)->has_copy_request = true;
+  GetPropertyTrees(root)->effect_tree.AddCopyRequest(
+      root->effect_tree_index(),
       std::make_unique<viz::CopyOutputRequest>(
           viz::CopyOutputRequest::ResultFormat::RGBA_TEXTURE,
           base::BindOnce(&Helper::OnResult, base::Unretained(&helper))));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
-  TestFrameData frame;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  host_impl_->DrawLayers(&frame);
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   auto* sii = context_provider->SharedImageInterface();
   // The CopyOutputResult has a ref on the viz::ContextProvider and a shared
   // image allocated.
-  ASSERT_TRUE(helper.unprocessed_result);
+  EXPECT_TRUE(helper.unprocessed_result);
   EXPECT_FALSE(context_provider->HasOneRef());
   EXPECT_EQ(1u, sii->shared_image_count());
 
@@ -10509,30 +9484,19 @@
 TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain) {
   // If we ray cast a scroller that is not on the first layer's ancestor chain,
   // we should return SCROLL_UNKNOWN.
+  gfx::Size viewport_size(50, 50);
   gfx::Size content_size(100, 100);
-  SetupScrollAndContentsLayers(content_size);
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
 
-  int scroll_layer_id = 2;
-  LayerImpl* scroll_layer =
-      host_impl_->active_tree()->LayerById(scroll_layer_id);
-  scroll_layer->SetDrawsContent(true);
-  scroll_layer->SetHitTestable(true);
+  LayerImpl* page_scale_layer = host_impl_->PageScaleLayer();
 
-  int page_scale_layer_id = 5;
-  LayerImpl* page_scale_layer =
-      host_impl_->active_tree()->LayerById(page_scale_layer_id);
-
-  int occluder_layer_id = 6;
-  std::unique_ptr<LayerImpl> occluder_layer =
-      LayerImpl::Create(host_impl_->active_tree(), occluder_layer_id);
+  LayerImpl* occluder_layer = AddLayer();
   occluder_layer->SetDrawsContent(true);
   occluder_layer->SetHitTestable(true);
   occluder_layer->SetBounds(content_size);
-  occluder_layer->test_properties()->position = gfx::PointF();
 
   // The parent of the occluder is *above* the scroller.
-  page_scale_layer->test_properties()->AddChild(std::move(occluder_layer));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(page_scale_layer, occluder_layer);
 
   DrawFrame();
 
@@ -10547,37 +9511,25 @@
   // If we ray cast a scroller this is on the first layer's ancestor chain, but
   // is not the first scroller we encounter when walking up from the layer, we
   // should also return SCROLL_UNKNOWN.
+  gfx::Size viewport_size(50, 50);
   gfx::Size content_size(100, 100);
-  SetupScrollAndContentsLayers(content_size);
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
 
-  int scroll_layer_id = 2;
-  LayerImpl* scroll_layer =
-      host_impl_->active_tree()->LayerById(scroll_layer_id);
-  scroll_layer->SetDrawsContent(true);
-  scroll_layer->SetHitTestable(true);
+  LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
 
-  int occluder_layer_id = 6;
-  std::unique_ptr<LayerImpl> occluder_layer =
-      LayerImpl::Create(host_impl_->active_tree(), occluder_layer_id);
+  LayerImpl* child_scroll_clip = AddLayer();
+  CopyProperties(scroll_layer, child_scroll_clip);
+
+  LayerImpl* child_scroll =
+      AddScrollableLayer(child_scroll_clip, viewport_size, content_size);
+  child_scroll->SetOffsetToTransformParent(gfx::Vector2dF(10.f, 10.f));
+
+  LayerImpl* occluder_layer = AddLayer();
   occluder_layer->SetDrawsContent(true);
   occluder_layer->SetHitTestable(true);
   occluder_layer->SetBounds(content_size);
-  occluder_layer->test_properties()->position = gfx::PointF(-10.f, -10.f);
-
-  int child_scroll_clip_layer_id = 7;
-  std::unique_ptr<LayerImpl> child_scroll_clip =
-      LayerImpl::Create(host_impl_->active_tree(), child_scroll_clip_layer_id);
-
-  int child_scroll_layer_id = 8;
-  std::unique_ptr<LayerImpl> child_scroll =
-      CreateScrollableLayer(child_scroll_layer_id, content_size);
-
-  child_scroll->test_properties()->position = gfx::PointF(10.f, 10.f);
-
-  child_scroll->test_properties()->AddChild(std::move(occluder_layer));
-  child_scroll_clip->test_properties()->AddChild(std::move(child_scroll));
-  scroll_layer->test_properties()->AddChild(std::move(child_scroll_clip));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(child_scroll, occluder_layer);
+  occluder_layer->SetOffsetToTransformParent(gfx::Vector2dF(-10.f, -10.f));
 
   DrawFrame();
 
@@ -10589,21 +9541,15 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller) {
+  gfx::Size viewport_size(50, 50);
   gfx::Size content_size(100, 100);
-  SetupScrollAndContentsLayers(content_size);
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
 
-  int scroll_layer_id = 2;
-  LayerImpl* scroll_layer =
-      host_impl_->active_tree()->LayerById(scroll_layer_id);
-
-  int child_scroll_layer_id = 7;
-  std::unique_ptr<LayerImpl> child_scroll =
-      CreateScrollableLayer(child_scroll_layer_id, content_size);
+  LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
+  LayerImpl* child_scroll =
+      AddScrollableLayer(scroll_layer, viewport_size, content_size);
   child_scroll->SetDrawsContent(false);
 
-  scroll_layer->test_properties()->AddChild(std::move(child_scroll));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   DrawFrame();
 
   // We should have scrolled |child_scroll| even though it does not move
@@ -10614,7 +9560,7 @@
           ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
           .thread);
 
-  EXPECT_EQ(host_impl_->active_tree()->LayerById(7)->scroll_tree_index(),
+  EXPECT_EQ(child_scroll->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 }
 
@@ -10625,16 +9571,9 @@
     LayerTreeSettings settings = DefaultSettings();
     settings.commit_to_active_tree = commit_to_active_tree;
     CreateHostImpl(settings, CreateLayerTreeFrameSink());
-
-    std::unique_ptr<SolidColorLayerImpl> root =
-        SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
-    root->test_properties()->position = gfx::PointF();
-    root->SetBounds(gfx::Size(10, 10));
-    root->SetDrawsContent(true);
-    root->test_properties()->force_render_surface = true;
-
-    host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    SetupRootLayer<SolidColorLayerImpl>(host_impl_->active_tree(),
+                                        gfx::Size(10, 10));
+    UpdateDrawProperties(host_impl_->active_tree());
   }
 };
 
@@ -10649,10 +9588,7 @@
       static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink());
 
   // The first frame should only have the default BeginFrame component.
-  TestFrameData frame1;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame1));
-  EXPECT_TRUE(host_impl_->DrawLayers(&frame1));
-  host_impl_->DidDrawAllLayers(frame1);
+  DrawFrame();
 
   const std::vector<ui::LatencyInfo>& metadata_latency_after1 =
       fake_layer_tree_frame_sink->last_sent_frame()->metadata.latency_info;
@@ -10671,12 +9607,9 @@
       new LatencyInfoSwapPromise(latency_info));
   host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise));
 
-  TestFrameData frame2;
   host_impl_->SetFullViewportDamage();
   host_impl_->SetNeedsRedraw();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame2));
-  EXPECT_TRUE(host_impl_->DrawLayers(&frame2));
-  host_impl_->DidDrawAllLayers(frame2);
+  DrawFrame();
 
   const std::vector<ui::LatencyInfo>& metadata_latency_after2 =
       fake_layer_tree_frame_sink->last_sent_frame()->metadata.latency_info;
@@ -10704,10 +9637,7 @@
       static_cast<FakeLayerTreeFrameSink*>(host_impl_->layer_tree_frame_sink());
 
   // The first frame should only have the default BeginFrame component.
-  TestFrameData frame1;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame1));
-  EXPECT_TRUE(host_impl_->DrawLayers(&frame1));
-  host_impl_->DidDrawAllLayers(frame1);
+  DrawFrame();
 
   const std::vector<ui::LatencyInfo>& metadata_latency_after1 =
       fake_layer_tree_frame_sink->last_sent_frame()->metadata.latency_info;
@@ -10726,12 +9656,9 @@
       new LatencyInfoSwapPromise(latency_info));
   host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise));
 
-  TestFrameData frame2;
   host_impl_->SetFullViewportDamage();
   host_impl_->SetNeedsRedraw();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame2));
-  EXPECT_TRUE(host_impl_->DrawLayers(&frame2));
-  host_impl_->DidDrawAllLayers(frame2);
+  DrawFrame();
 
   const std::vector<ui::LatencyInfo>& metadata_latency_after2 =
       fake_layer_tree_frame_sink->last_sent_frame()->metadata.latency_info;
@@ -10750,23 +9677,16 @@
 
 #if defined(OS_ANDROID)
 TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata) {
-  int root_layer_id = 1;
-  std::unique_ptr<SolidColorLayerImpl> root =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), root_layer_id);
-  root->test_properties()->position = gfx::PointF();
-  root->SetBounds(gfx::Size(10, 10));
-  root->SetDrawsContent(true);
-  root->test_properties()->force_render_surface = true;
-
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  LayerImpl* root = SetupRootLayer<SolidColorLayerImpl>(
+      host_impl_->active_tree(), gfx::Size(10, 10));
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Plumb the layer-local selection bounds.
   gfx::Point selection_top(5, 0);
   gfx::Point selection_bottom(5, 5);
   LayerSelection selection;
   selection.start.type = gfx::SelectionBound::CENTER;
-  selection.start.layer_id = root_layer_id;
+  selection.start.layer_id = root->id();
   selection.start.edge_bottom = selection_bottom;
   selection.start.edge_top = selection_top;
   selection.end = selection.start;
@@ -10787,16 +9707,9 @@
 }
 
 TEST_F(LayerTreeHostImplTest, HiddenSelectionBoundsStayHidden) {
-  int root_layer_id = 1;
-  std::unique_ptr<SolidColorLayerImpl> root =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), root_layer_id);
-  root->test_properties()->position = gfx::PointF();
-  root->SetBounds(gfx::Size(10, 10));
-  root->SetDrawsContent(true);
-  root->test_properties()->force_render_surface = true;
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
 
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Plumb the layer-local selection bounds.
   gfx::Point selection_top(5, 0);
@@ -10807,7 +9720,7 @@
   selection.start.hidden = true;
 
   selection.start.type = gfx::SelectionBound::CENTER;
-  selection.start.layer_id = root_layer_id;
+  selection.start.layer_id = root->id();
   selection.start.edge_bottom = selection_bottom;
   selection.start.edge_top = selection_top;
   selection.end = selection.start;
@@ -10916,7 +9829,7 @@
         new SimpleSwapPromiseMonitor(
             nullptr, host_impl_.get(), &set_needs_commit_count,
             &set_needs_redraw_count, &forward_to_main_count));
-    SetupScrollAndContentsLayers(gfx::Size(100, 100));
+    SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
 
     // Scrolling normally should not trigger any forwarding.
     EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
@@ -10971,7 +9884,8 @@
 const int LayerTreeHostImplWithBrowserControlsTest::top_controls_height_ = 50;
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest, NoIdleAnimations) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
   scroll_layer->layer_tree_impl()
       ->property_trees()
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->element_id(),
@@ -10986,7 +9900,7 @@
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsHeightIsCommitted) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   EXPECT_FALSE(did_request_redraw_);
   CreatePendingTree();
   host_impl_->sync_tree()->SetTopControlsHeight(100);
@@ -10996,7 +9910,7 @@
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsStayFullyVisibleOnHeightChange) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ControlsTopOffset());
 
   CreatePendingTree();
@@ -11012,7 +9926,8 @@
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationScheduling) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
   scroll_layer->layer_tree_impl()
       ->property_trees()
       ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->element_id(),
@@ -11025,10 +9940,10 @@
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        ScrollHandledByBrowserControls) {
   InputHandlerScrollResult result;
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 100), gfx::Size(100, 200));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
+  UpdateDrawProperties(host_impl_->active_tree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
   host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
       BrowserControlsState::kBoth, BrowserControlsState::kShown, false);
   DrawFrame();
@@ -11101,8 +10016,7 @@
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        WheelUnhandledByBrowserControls) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 200));
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 100), gfx::Size(100, 200));
   host_impl_->active_tree()->set_browser_controls_shrink_blink_size(true);
   host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
       BrowserControlsState::kBoth, BrowserControlsState::kShown, false);
@@ -11142,10 +10056,10 @@
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationAtOrigin) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 100), gfx::Size(100, 200));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
+  UpdateDrawProperties(host_impl_->active_tree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 200));
   host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
       BrowserControlsState::kBoth, BrowserControlsState::kShown, false);
   DrawFrame();
@@ -11222,10 +10136,10 @@
 
 TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationAfterScroll) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 100), gfx::Size(100, 200));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
+  UpdateDrawProperties(host_impl_->active_tree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
   host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
       BrowserControlsState::kBoth, BrowserControlsState::kShown, false);
   float initial_scroll_offset = 50;
@@ -11302,10 +10216,10 @@
        BrowserControlsScrollDeltaInOverScroll) {
   // Verifies that the overscroll delta should not have accumulated in
   // the browser controls if we do a hide and show without releasing finger.
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 100), gfx::Size(100, 200));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
+  UpdateDrawProperties(host_impl_->active_tree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
   host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
       BrowserControlsState::kBoth, BrowserControlsState::kShown, false);
   DrawFrame();
@@ -11393,40 +10307,27 @@
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  LayerImpl* scroll_layer = nullptr;
-  LayerImpl* clip_layer = nullptr;
 
   // Initialization: Add a child scrolling layer to the outer scroll layer and
   // set its scroll layer as the outer viewport. This simulates setting a
   // scrolling element as the root scroller on the page.
-  {
-    std::unique_ptr<LayerImpl> clip = LayerImpl::Create(layer_tree_impl, 10);
-    clip->SetBounds(root_layer_size);
-    clip->test_properties()->position = gfx::PointF();
+  LayerImpl* clip_layer = AddLayer();
+  clip_layer->SetBounds(root_layer_size);
+  CopyProperties(outer_scroll, clip_layer);
+  CreateClipNode(clip_layer);
+  LayerImpl* scroll_layer =
+      AddScrollableLayer(clip_layer, root_layer_size, scroll_content_size);
+  GetScrollNode(scroll_layer)->scrolls_outer_viewport = true;
 
-    std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
-    scroll->SetBounds(scroll_content_size);
-    scroll->SetScrollable(root_layer_size);
-    scroll->SetHitTestable(true);
-    scroll->SetElementId(LayerIdToElementIdForTesting(scroll->id()));
-    scroll->SetDrawsContent(true);
-
-    scroll_layer = scroll.get();
-    clip_layer = clip.get();
-
-    clip->test_properties()->AddChild(std::move(scroll));
-    outer_scroll->test_properties()->AddChild(std::move(clip));
-    LayerTreeImpl::ViewportLayerIds viewport_ids;
-    viewport_ids.page_scale = layer_tree_impl->PageScaleLayer()->id();
-    viewport_ids.inner_viewport_container =
-        layer_tree_impl->InnerViewportContainerLayer()->id();
-    viewport_ids.outer_viewport_container = clip_layer->id();
-    viewport_ids.inner_viewport_scroll = inner_scroll->id();
-    viewport_ids.outer_viewport_scroll = scroll_layer->id();
-    layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
-    layer_tree_impl->BuildPropertyTreesForTesting();
-    DrawFrame();
-  }
+  LayerTreeImpl::ViewportLayerIds viewport_ids;
+  viewport_ids.page_scale = layer_tree_impl->PageScaleLayer()->id();
+  viewport_ids.inner_viewport_container =
+      layer_tree_impl->InnerViewportContainerLayer()->id();
+  viewport_ids.outer_viewport_container = clip_layer->id();
+  viewport_ids.inner_viewport_scroll = inner_scroll->id();
+  viewport_ids.outer_viewport_scroll = scroll_layer->id();
+  layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
+  DrawFrame();
 
   ASSERT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
 
@@ -11447,92 +10348,15 @@
   }
 }
 
-class LayerTreeHostImplVirtualViewportTest : public LayerTreeHostImplTest {
- public:
-  void SetupVirtualViewportLayers(const gfx::Size& content_size,
-                                  const gfx::Size& outer_viewport,
-                                  const gfx::Size& inner_viewport) {
-    LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-    const int kOuterViewportClipLayerId = 6;
-    const int kOuterViewportScrollLayerId = 7;
-    const int kInnerViewportScrollLayerId = 2;
-    const int kInnerViewportClipLayerId = 4;
-    const int kPageScaleLayerId = 5;
-
-    std::unique_ptr<LayerImpl> inner_scroll =
-        LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId);
-    inner_scroll->test_properties()->is_container_for_fixed_position_layers =
-        true;
-    inner_scroll->layer_tree_impl()
-        ->property_trees()
-        ->scroll_tree.UpdateScrollOffsetBaseForTesting(
-            inner_scroll->element_id(), gfx::ScrollOffset());
-
-    std::unique_ptr<LayerImpl> inner_clip =
-        LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId);
-    inner_clip->SetBounds(inner_viewport);
-
-    std::unique_ptr<LayerImpl> page_scale =
-        LayerImpl::Create(layer_tree_impl, kPageScaleLayerId);
-
-    inner_scroll->SetScrollable(inner_viewport);
-    inner_scroll->SetHitTestable(true);
-    inner_scroll->SetElementId(
-        LayerIdToElementIdForTesting(inner_scroll->id()));
-    inner_scroll->SetBounds(outer_viewport);
-    inner_scroll->test_properties()->position = gfx::PointF();
-
-    std::unique_ptr<LayerImpl> outer_clip =
-        LayerImpl::Create(layer_tree_impl, kOuterViewportClipLayerId);
-    outer_clip->SetBounds(outer_viewport);
-    outer_clip->test_properties()->is_container_for_fixed_position_layers =
-        true;
-
-    std::unique_ptr<LayerImpl> outer_scroll =
-        LayerImpl::Create(layer_tree_impl, kOuterViewportScrollLayerId);
-    outer_scroll->SetScrollable(outer_viewport);
-    outer_scroll->SetHitTestable(true);
-    outer_scroll->SetElementId(
-        LayerIdToElementIdForTesting(outer_scroll->id()));
-    outer_scroll->layer_tree_impl()
-        ->property_trees()
-        ->scroll_tree.UpdateScrollOffsetBaseForTesting(
-            outer_scroll->element_id(), gfx::ScrollOffset());
-    outer_scroll->SetBounds(content_size);
-    outer_scroll->test_properties()->position = gfx::PointF();
-
-    std::unique_ptr<LayerImpl> contents = LayerImpl::Create(layer_tree_impl, 8);
-    contents->SetDrawsContent(true);
-    contents->SetBounds(content_size);
-    contents->test_properties()->position = gfx::PointF();
-
-    outer_scroll->test_properties()->AddChild(std::move(contents));
-    outer_clip->test_properties()->AddChild(std::move(outer_scroll));
-    inner_scroll->test_properties()->AddChild(std::move(outer_clip));
-    page_scale->test_properties()->AddChild(std::move(inner_scroll));
-    inner_clip->test_properties()->AddChild(std::move(page_scale));
-
-    inner_clip->test_properties()->force_render_surface = true;
-    layer_tree_impl->SetRootLayerForTesting(std::move(inner_clip));
-    LayerTreeImpl::ViewportLayerIds viewport_ids;
-    viewport_ids.page_scale = kPageScaleLayerId;
-    viewport_ids.inner_viewport_container = kInnerViewportClipLayerId;
-    viewport_ids.outer_viewport_container = kOuterViewportClipLayerId;
-    viewport_ids.inner_viewport_scroll = kInnerViewportScrollLayerId;
-    viewport_ids.outer_viewport_scroll = kOuterViewportScrollLayerId;
-    layer_tree_impl->SetViewportLayersFromIds(viewport_ids);
-
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
-    host_impl_->active_tree()->DidBecomeActive();
-  }
-};
+using LayerTreeHostImplVirtualViewportTest = LayerTreeHostImplTest;
 
 TEST_F(LayerTreeHostImplVirtualViewportTest, RootScrollBothInnerAndOuterLayer) {
   gfx::Size content_size = gfx::Size(100, 160);
   gfx::Size outer_viewport = gfx::Size(50, 80);
   gfx::Size inner_viewport = gfx::Size(25, 40);
 
-  SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport, outer_viewport,
+                      content_size);
 
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
@@ -11565,7 +10389,8 @@
   gfx::Size outer_viewport = gfx::Size(100, 160);
   gfx::Size inner_viewport = gfx::Size(50, 80);
 
-  SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport, outer_viewport,
+                      content_size);
 
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
@@ -11617,16 +10442,14 @@
   gfx::Size outer_viewport = gfx::Size(50, 80);
   gfx::Size inner_viewport = gfx::Size(25, 40);
 
-  SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport, outer_viewport,
+                      content_size);
 
   LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
+  LayerImpl* child_scroll =
+      AddScrollableLayer(outer_scroll, inner_viewport, outer_viewport);
 
-  std::unique_ptr<LayerImpl> child = CreateScrollableLayer(10, outer_viewport);
-  LayerImpl* child_scroll = child.get();
-  outer_scroll->test_properties()->children[0]->test_properties()->AddChild(
-      std::move(child));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
+  UpdateDrawProperties(host_impl_->active_tree());
   DrawFrame();
 
   EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
@@ -11653,12 +10476,12 @@
   gfx::Size content_size = gfx::Size(100, 160);
   gfx::Size outer_viewport = gfx::Size(50, 80);
   gfx::Size inner_viewport = gfx::Size(25, 40);
-  SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);
+  SetupViewportLayers(host_impl_->active_tree(), inner_viewport, outer_viewport,
+                      content_size);
   // Make inner viewport unscrollable.
   LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
-  inner_scroll->test_properties()->user_scrollable_horizontal = false;
-  inner_scroll->test_properties()->user_scrollable_vertical = false;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  GetScrollNode(inner_scroll)->user_scrollable_horizontal = false;
+  GetScrollNode(inner_scroll)->user_scrollable_vertical = false;
 
   DrawFrame();
 
@@ -11701,11 +10524,13 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw) {
+  const gfx::Size viewport_size(50, 50);
   const gfx::Size layer_size(100, 100);
   gfx::Transform external_transform;
   const gfx::Rect external_viewport(layer_size);
   const bool resourceless_software_draw = false;
-  LayerImpl* layer = SetupScrollAndContentsLayers(layer_size);
+  SetupViewportLayersInnerScrolls(viewport_size, layer_size);
+  auto* layer = host_impl_->InnerViewportScrollLayer();
   layer->SetDrawsContent(true);
 
   host_impl_->SetExternalTilePriorityConstraints(external_viewport,
@@ -11725,11 +10550,9 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ExternalTransformSetNeedsRedraw) {
-  SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   const gfx::Size viewport_size(100, 100);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
+  SetupDefaultRootLayer(viewport_size);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   const gfx::Transform transform_for_tile_priority;
   const gfx::Transform draw_transform;
@@ -11778,11 +10601,9 @@
 }
 
 TEST_F(LayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw) {
-  SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   const gfx::Size viewport_size(100, 100);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
+  SetupDefaultRootLayer(viewport_size);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   const gfx::Transform draw_transform;
   const gfx::Rect draw_viewport1(viewport_size);
@@ -11813,11 +10634,9 @@
 // This test verifies that the viewport damage rect is the full viewport and not
 // just part of the viewport in the presence of an external viewport.
 TEST_F(LayerTreeHostImplTest, FullViewportDamageAfterOnDraw) {
-  SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   const gfx::Size viewport_size(100, 100);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
+  SetupDefaultRootLayer(viewport_size);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   const gfx::Transform draw_transform;
   const gfx::Rect draw_viewport(gfx::Point(5, 5), viewport_size);
@@ -11841,11 +10660,9 @@
 
 TEST_F(ResourcelessSoftwareLayerTreeHostImplTest,
        ResourcelessSoftwareSetNeedsRedraw) {
-  SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-
   const gfx::Size viewport_size(100, 100);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
+  SetupDefaultRootLayer(viewport_size);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   const gfx::Transform draw_transform;
   const gfx::Rect draw_viewport(viewport_size);
@@ -11873,19 +10690,16 @@
 TEST_F(ResourcelessSoftwareLayerTreeHostImplTest,
        ResourcelessSoftwareDrawSkipsUpdateTiles) {
   const gfx::Size viewport_size(100, 100);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
 
   CreatePendingTree();
   scoped_refptr<FakeRasterSource> raster_source(
       FakeRasterSource::CreateFilled(viewport_size));
-  std::unique_ptr<FakePictureLayerImpl> layer(
-      FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
-                                                   11, raster_source));
-  layer->SetBounds(viewport_size);
-  layer->SetDrawsContent(true);
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(layer));
+  auto* root = SetupRootLayer<FakePictureLayerImplWithRasterSource>(
+      host_impl_->pending_tree(), viewport_size, raster_source);
+  root->SetBounds(viewport_size);
+  root->SetDrawsContent(true);
 
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->pending_tree());
   host_impl_->ActivateSyncTree();
 
   const gfx::Transform draw_transform;
@@ -11912,25 +10726,21 @@
        ExternalTileConstraintReflectedInPendingTree) {
   EXPECT_FALSE(host_impl_->CommitToActiveTree());
   const gfx::Size layer_size(100, 100);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(layer_size));
 
   // Set up active and pending tree.
   CreatePendingTree();
-  host_impl_->pending_tree()->SetRootLayerForTesting(
-      LayerImpl::Create(host_impl_->pending_tree(), 1));
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
-  host_impl_->pending_tree()->UpdateDrawProperties();
+  SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_size);
+  UpdateDrawProperties(host_impl_->pending_tree());
   host_impl_->pending_tree()
       ->root_layer_for_testing()
       ->SetNeedsPushProperties();
 
   host_impl_->ActivateSyncTree();
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   CreatePendingTree();
-  host_impl_->pending_tree()->UpdateDrawProperties();
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->pending_tree());
+  UpdateDrawProperties(host_impl_->active_tree());
 
   EXPECT_FALSE(host_impl_->pending_tree()->needs_update_draw_properties());
   EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());
@@ -11946,15 +10756,13 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ExternalViewportAffectsVisibleRects) {
+  const gfx::Size viewport_size(50, 50);
   const gfx::Size layer_size(100, 100);
-  SetupScrollAndContentsLayers(layer_size);
-  LayerImpl* content_layer = host_impl_->active_tree()
-                                 ->OuterViewportScrollLayer()
-                                 ->test_properties()
-                                 ->children[0];
+  SetupViewportLayersInnerScrolls(viewport_size, layer_size);
+  LayerImpl* content_layer = AddContentLayer();
 
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(90, 90));
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_EQ(gfx::Rect(90, 90), content_layer->visible_layer_rect());
 
   gfx::Transform external_transform;
@@ -11977,15 +10785,13 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsVisibleRects) {
+  const gfx::Size viewport_size(50, 50);
   const gfx::Size layer_size(100, 100);
-  SetupScrollAndContentsLayers(layer_size);
-  LayerImpl* content_layer = host_impl_->active_tree()
-                                 ->OuterViewportScrollLayer()
-                                 ->test_properties()
-                                 ->children[0];
+  SetupViewportLayersInnerScrolls(viewport_size, layer_size);
+  LayerImpl* content_layer = AddContentLayer();
 
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   EXPECT_EQ(gfx::Rect(50, 50), content_layer->visible_layer_rect());
 
   gfx::Transform external_transform;
@@ -12013,25 +10819,18 @@
 }
 
 TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsSublayerScaleFactor) {
+  const gfx::Size viewport_size(50, 50);
   const gfx::Size layer_size(100, 100);
-  SetupScrollAndContentsLayers(layer_size);
-  LayerImpl* content_layer = host_impl_->active_tree()
-                                 ->OuterViewportScrollLayer()
-                                 ->test_properties()
-                                 ->children[0];
-  content_layer->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->active_tree(), 100));
-  LayerImpl* test_layer = host_impl_->active_tree()->LayerById(100);
-  test_layer->test_properties()->force_render_surface = true;
-  test_layer->SetDrawsContent(true);
-  test_layer->SetBounds(layer_size);
+  SetupViewportLayersInnerScrolls(viewport_size, layer_size);
+  LayerImpl* test_layer = AddContentLayer();
   gfx::Transform perspective_transform;
   perspective_transform.ApplyPerspectiveDepth(2);
-  test_layer->test_properties()->transform = perspective_transform;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CreateTransformNode(test_layer).local = perspective_transform;
+  CreateEffectNode(test_layer).render_surface_reason =
+      RenderSurfaceReason::kTest;
 
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   EffectNode* node =
       host_impl_->active_tree()->property_trees()->effect_tree.Node(
           test_layer->effect_tree_index());
@@ -12067,7 +10866,7 @@
 TEST_F(LayerTreeHostImplTest, ScrollAnimated) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(50, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   DrawFrame();
 
@@ -12174,7 +10973,7 @@
 TEST_F(LayerTreeHostImplTest, ScrollAnimatedWhileZoomed) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(50, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
   LayerImpl* scrolling_layer = host_impl_->InnerViewportScrollLayer();
 
   DrawFrame();
@@ -12246,21 +11045,18 @@
   // Setup the viewport.
   const gfx::Size viewport_size = gfx::Size(360, 600);
   const gfx::Size content_size = gfx::Size(345, 3800);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
   LayerImpl* scroll_layer = host_impl_->OuterViewportScrollLayer();
 
   // Set up the scrollbar and its dimensions.
   LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
-  std::unique_ptr<PaintedScrollbarLayerImpl> scrollbar =
-      PaintedScrollbarLayerImpl::Create(layer_tree_impl, 99, VERTICAL, false,
-                                        true);
+  auto* scrollbar = AddLayer<PaintedScrollbarLayerImpl>(layer_tree_impl,
+                                                        VERTICAL, false, true);
   const gfx::Size scrollbar_size = gfx::Size(15, 600);
   scrollbar->SetBounds(scrollbar_size);
-  scrollbar->test_properties()->position = gfx::PointF(345, 0);
   scrollbar->SetScrollElementId(scroll_layer->element_id());
   scrollbar->SetDrawsContent(true);
   scrollbar->SetHitTestable(true);
-  scrollbar->test_properties()->opacity = 1.f;
 
   // Set up the thumb dimensions.
   scrollbar->SetThumbThickness(15);
@@ -12275,9 +11071,10 @@
       gfx::Rect(gfx::Point(345, 570), gfx::Size(15, 15)));
 
   // Add the scrollbar to the outer viewport.
-  scroll_layer->test_properties()->AddChild(std::move(scrollbar));
+  CopyProperties(scroll_layer, scrollbar);
+  scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
+  CreateEffectNode(scrollbar).opacity = 1.f;
 
-  host_impl_->active_tree()->BuildLayerListAndPropertyTreesForTesting();
   host_impl_->ScrollBegin(BeginState(gfx::Point(350, 18)).get(),
                           InputHandler::SCROLLBAR);
   TestInputHandlerClient input_handler_client;
@@ -12333,7 +11130,7 @@
 TEST_F(LayerTreeHostImplTest, SecondScrollAnimatedBeginNotIgnored) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(50, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   EXPECT_EQ(
       InputHandler::SCROLL_ON_IMPL_THREAD,
@@ -12350,7 +11147,7 @@
 TEST_F(LayerTreeHostImplTest, AnimatedScrollUpdateTargetBeforeStarting) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(50, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   DrawFrame();
 
@@ -12403,7 +11200,7 @@
 TEST_F(LayerTreeHostImplTest, ScrollAnimatedWithDelay) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(50, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   DrawFrame();
 
@@ -12465,7 +11262,7 @@
 TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedAborted) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   DrawFrame();
 
@@ -12535,7 +11332,7 @@
 TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimated) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   DrawFrame();
 
@@ -12609,8 +11406,8 @@
 TEST_F(LayerTreeHostImplTimelinesTest, ImplPinchZoomScrollAnimated) {
   const gfx::Size content_size(200, 200);
   const gfx::Size viewport_size(100, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
+  UpdateDrawProperties(host_impl_->active_tree());
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -12707,7 +11504,7 @@
 TEST_F(LayerTreeHostImplTimelinesTest, ImplPinchZoomScrollAnimatedUpdate) {
   const gfx::Size content_size(200, 200);
   const gfx::Size viewport_size(100, 100);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
   LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
   LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
@@ -12767,15 +11564,11 @@
 TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedNotUserScrollable) {
   const gfx::Size content_size(1000, 1000);
   const gfx::Size viewport_size(500, 500);
-  CreateBasicVirtualViewportLayers(viewport_size, content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, content_size);
 
-  host_impl_->OuterViewportScrollLayer()
-      ->test_properties()
-      ->user_scrollable_vertical = true;
-  host_impl_->OuterViewportScrollLayer()
-      ->test_properties()
-      ->user_scrollable_horizontal = false;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  LayerImpl* scrolling_layer = host_impl_->OuterViewportScrollLayer();
+  GetScrollNode(scrolling_layer)->user_scrollable_vertical = true;
+  GetScrollNode(scrolling_layer)->user_scrollable_horizontal = false;
 
   DrawFrame();
 
@@ -12789,7 +11582,6 @@
       InputHandler::SCROLL_ON_IMPL_THREAD,
       host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(50, 50)).thread);
 
-  LayerImpl* scrolling_layer = host_impl_->OuterViewportScrollLayer();
   EXPECT_EQ(scrolling_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
@@ -12852,8 +11644,9 @@
   const gfx::Size new_content_size(750, 750);
   const gfx::Size viewport_size(500, 500);
 
-  LayerImpl* content_layer =
-      CreateBasicVirtualViewportLayers(viewport_size, old_content_size);
+  SetupViewportLayersOuterScrolls(viewport_size, old_content_size);
+  LayerImpl* scrolling_layer = host_impl_->OuterViewportScrollLayer();
+  LayerImpl* content_layer = AddContentLayer();
 
   DrawFrame();
 
@@ -12864,7 +11657,6 @@
 
   host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(500, 500));
 
-  LayerImpl* scrolling_layer = host_impl_->OuterViewportScrollLayer();
   EXPECT_EQ(scrolling_layer->scroll_tree_index(),
             host_impl_->CurrentlyScrollingNode()->id);
 
@@ -12877,7 +11669,7 @@
 
   content_layer->SetBounds(new_content_size);
   scrolling_layer->SetBounds(new_content_size);
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  GetScrollNode(scrolling_layer)->bounds = new_content_size;
 
   DrawFrame();
 
@@ -12900,9 +11692,8 @@
   scoped_refptr<RasterSource> raster_source_with_tiles(
       FakeRasterSource::CreateFilled(gfx::Size(10, 10)));
 
-  std::unique_ptr<FakePictureLayerImpl> layer =
-      FakePictureLayerImpl::Create(host_impl_->pending_tree(), 11);
-  layer->SetBounds(gfx::Size(10, 10));
+  auto* layer = SetupRootLayer<FakePictureLayerImpl>(host_impl_->pending_tree(),
+                                                     gfx::Size(10, 10));
   layer->set_gpu_raster_max_texture_size(
       host_impl_->active_tree()->GetDeviceViewport().size());
   layer->SetDrawsContent(true);
@@ -12914,18 +11705,14 @@
   layer->tilings()->tiling_at(0)->CreateAllTilesForTesting();
   layer->tilings()->UpdateTilePriorities(gfx::Rect(gfx::Size(10, 10)), 1.f, 1.0,
                                          Occlusion(), true);
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(layer));
 
-  auto* root_layer = static_cast<FakePictureLayerImpl*>(
-      host_impl_->pending_tree()->root_layer_for_testing());
-
-  root_layer->set_has_valid_tile_priorities(true);
+  layer->set_has_valid_tile_priorities(true);
   std::unique_ptr<RasterTilePriorityQueue> non_empty_raster_priority_queue_all =
       host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES,
                                    RasterTilePriorityQueue::Type::ALL);
   EXPECT_FALSE(non_empty_raster_priority_queue_all->IsEmpty());
 
-  root_layer->set_has_valid_tile_priorities(false);
+  layer->set_has_valid_tile_priorities(false);
   std::unique_ptr<RasterTilePriorityQueue> empty_raster_priority_queue_all =
       host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES,
                                    RasterTilePriorityQueue::Type::ALL);
@@ -12939,40 +11726,39 @@
 
   LayerTreeImpl* pending_tree = host_impl_->pending_tree();
 
-  std::unique_ptr<FakePictureLayerImpl> pending_layer =
-      FakePictureLayerImpl::Create(pending_tree, 10);
-  FakePictureLayerImpl* raw_pending_layer = pending_layer.get();
-  pending_tree->SetRootLayerForTesting(std::move(pending_layer));
-  ASSERT_EQ(raw_pending_layer, pending_tree->root_layer_for_testing());
+  auto* pending_layer =
+      SetupRootLayer<FakePictureLayerImpl>(pending_tree, gfx::Size(10, 10));
 
-  EXPECT_EQ(0u, raw_pending_layer->did_become_active_call_count());
+  EXPECT_EQ(0u, pending_layer->did_become_active_call_count());
   pending_tree->DidBecomeActive();
-  EXPECT_EQ(1u, raw_pending_layer->did_become_active_call_count());
+  EXPECT_EQ(1u, pending_layer->did_become_active_call_count());
 
   std::unique_ptr<FakePictureLayerImpl> mask_layer =
-      FakePictureLayerImpl::Create(pending_tree, 11);
+      FakePictureLayerImpl::Create(pending_tree, next_layer_id_++);
   FakePictureLayerImpl* raw_mask_layer = mask_layer.get();
-  raw_pending_layer->test_properties()->SetMaskLayer(std::move(mask_layer));
-  ASSERT_EQ(raw_mask_layer, raw_pending_layer->test_properties()->mask_layer);
-  pending_tree->BuildPropertyTreesForTesting();
+  pending_layer->test_properties()->SetMaskLayer(std::move(mask_layer));
+  GetEffectNode(pending_layer)->mask_layer_id = raw_mask_layer->id();
+  GetEffectNode(pending_layer)->is_masked = true;
+  GetPropertyTrees(pending_layer)
+      ->effect_tree.AddMaskLayerId(raw_mask_layer->id());
+  ASSERT_EQ(raw_mask_layer, pending_layer->test_properties()->mask_layer);
 
-  EXPECT_EQ(1u, raw_pending_layer->did_become_active_call_count());
+  EXPECT_EQ(1u, pending_layer->did_become_active_call_count());
   EXPECT_EQ(0u, raw_mask_layer->did_become_active_call_count());
   pending_tree->DidBecomeActive();
-  EXPECT_EQ(2u, raw_pending_layer->did_become_active_call_count());
+  EXPECT_EQ(2u, pending_layer->did_become_active_call_count());
   EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count());
 
-  pending_tree->BuildPropertyTreesForTesting();
-
-  EXPECT_EQ(2u, raw_pending_layer->did_become_active_call_count());
+  EXPECT_EQ(2u, pending_layer->did_become_active_call_count());
   EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count());
   pending_tree->DidBecomeActive();
-  EXPECT_EQ(3u, raw_pending_layer->did_become_active_call_count());
+  EXPECT_EQ(3u, pending_layer->did_become_active_call_count());
   EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count());
 }
 
 TEST_F(LayerTreeHostImplTest, WheelScrollWithPageScaleFactorOnInnerLayer) {
-  LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->InnerViewportScrollLayer();
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
   DrawFrame();
 
@@ -13501,9 +12287,8 @@
   // on the active tree.
   CreatePendingTree();
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 3.f);
-  CreateScrollAndContentsLayers(host_impl_->pending_tree(),
-                                gfx::Size(100, 100));
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50),
+                      gfx::Size(100, 100), gfx::Size(100, 100));
   host_impl_->ActivateSyncTree();
   DrawFrame();
 
@@ -13511,35 +12296,36 @@
   host_impl_->active_tree()->SetPageScaleOnActiveTree(2.f);
   LayerImpl* page_scale_layer = host_impl_->active_tree()->PageScaleLayer();
 
-  TransformNode* active_tree_node =
-      host_impl_->active_tree()->property_trees()->transform_tree.Node(
-          page_scale_layer->transform_tree_index());
+  TransformNode* active_tree_node = GetTransformNode(page_scale_layer);
   // SetPageScaleOnActiveTree also updates the factors in property trees.
   EXPECT_TRUE(active_tree_node->local.IsScale2d());
   EXPECT_EQ(gfx::Vector2dF(2.f, 2.f), active_tree_node->local.Scale2d());
   EXPECT_EQ(gfx::Point3F(), active_tree_node->origin);
-  EXPECT_EQ(host_impl_->active_tree()->current_page_scale_factor(), 2.f);
+  EXPECT_EQ(2.f, host_impl_->active_tree()->current_page_scale_factor());
 
   TransformNode* pending_tree_node =
-      host_impl_->pending_tree()->property_trees()->transform_tree.Node(
-          page_scale_layer->transform_tree_index());
+      GetTransformNode(host_impl_->pending_tree()->PageScaleLayer());
+  // Before pending tree updates draw properties, its properties are still
+  // based on 1.0 page scale, except for current_page_scale_factor() which is a
+  // shared data between the active and pending trees.
   EXPECT_TRUE(pending_tree_node->local.IsIdentity());
   EXPECT_EQ(gfx::Point3F(), pending_tree_node->origin);
-  EXPECT_EQ(host_impl_->pending_tree()->current_page_scale_factor(), 2.f);
+  EXPECT_EQ(2.f, host_impl_->pending_tree()->current_page_scale_factor());
+  EXPECT_EQ(1.f, host_impl_->pending_tree()
+                     ->property_trees()
+                     ->transform_tree.page_scale_factor());
 
-  host_impl_->pending_tree()->UpdateDrawProperties();
+  host_impl_->pending_tree()->set_needs_update_draw_properties();
+  UpdateDrawProperties(host_impl_->pending_tree());
   pending_tree_node =
-      host_impl_->pending_tree()->property_trees()->transform_tree.Node(
-          page_scale_layer->transform_tree_index());
+      GetTransformNode(host_impl_->pending_tree()->PageScaleLayer());
   EXPECT_TRUE(pending_tree_node->local.IsScale2d());
   EXPECT_EQ(gfx::Vector2dF(2.f, 2.f), pending_tree_node->local.Scale2d());
   EXPECT_EQ(gfx::Point3F(), pending_tree_node->origin);
 
   host_impl_->ActivateSyncTree();
-  host_impl_->active_tree()->UpdateDrawProperties();
-  active_tree_node =
-      host_impl_->active_tree()->property_trees()->transform_tree.Node(
-          page_scale_layer->transform_tree_index());
+  UpdateDrawProperties(host_impl_->active_tree());
+  active_tree_node = GetTransformNode(page_scale_layer);
   EXPECT_TRUE(active_tree_node->local.IsScale2d());
   EXPECT_EQ(gfx::Vector2dF(2.f, 2.f), active_tree_node->local.Scale2d());
   EXPECT_EQ(gfx::Point3F(), active_tree_node->origin);
@@ -13549,31 +12335,23 @@
   // Checks that the sublayer scale of a transform node in the subtree of the
   // page scale layer is updated without a property tree rebuild.
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 3.f);
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   LayerImpl* page_scale_layer = host_impl_->active_tree()->PageScaleLayer();
-  page_scale_layer->test_properties()->AddChild(
-      LayerImpl::Create(host_impl_->active_tree(), 100));
-
-  LayerImpl* in_subtree_of_page_scale_layer =
-      host_impl_->active_tree()->LayerById(100);
-  in_subtree_of_page_scale_layer->test_properties()->force_render_surface =
-      true;
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  LayerImpl* in_subtree_of_page_scale_layer = AddLayer();
+  CopyProperties(page_scale_layer, in_subtree_of_page_scale_layer);
+  CreateEffectNode(in_subtree_of_page_scale_layer).render_surface_reason =
+      RenderSurfaceReason::kTest;
 
   DrawFrame();
 
-  EffectNode* node =
-      host_impl_->active_tree()->property_trees()->effect_tree.Node(
-          in_subtree_of_page_scale_layer->effect_tree_index());
+  EffectNode* node = GetEffectNode(in_subtree_of_page_scale_layer);
   EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(1.f, 1.f));
 
   host_impl_->active_tree()->SetPageScaleOnActiveTree(2.f);
 
   DrawFrame();
 
-  in_subtree_of_page_scale_layer = host_impl_->active_tree()->LayerById(100);
-  node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
-      in_subtree_of_page_scale_layer->effect_tree_index());
+  node = GetEffectNode(in_subtree_of_page_scale_layer);
   EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(2.f, 2.f));
 }
 
@@ -13581,66 +12359,45 @@
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(100, 100));
 
   CreatePendingTree();
-  CreateScrollAndContentsLayers(host_impl_->pending_tree(),
-                                gfx::Size(100, 100));
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayers(host_impl_->pending_tree(), gfx::Size(50, 50),
+                      gfx::Size(100, 100), gfx::Size(100, 100));
+  auto* scroll_layer = host_impl_->pending_tree()->InnerViewportScrollLayer();
+  auto* content_layer = AddLayer<LayerImpl>(host_impl_->pending_tree());
+  content_layer->SetBounds(gfx::Size(100, 100));
+  content_layer->SetDrawsContent(true);
+  CopyProperties(host_impl_->pending_tree()->OuterViewportScrollLayer(),
+                 content_layer);
+  UpdateDrawProperties(host_impl_->pending_tree());
 
   host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
   const int scroll = 5;
   int accumulated_scroll = 0;
-  for (int i = 0; i < host_impl_->pending_tree()->kFixedPointHitsThreshold + 1;
-       ++i) {
+  for (int i = 0; i < LayerTreeImpl::kFixedPointHitsThreshold + 1; ++i) {
     host_impl_->ActivateSyncTree();
-    host_impl_->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
-                            InputHandler::TOUCHSCREEN);
-    host_impl_->ScrollBy(
-        UpdateState(gfx::Point(), gfx::Vector2dF(0, scroll)).get());
     accumulated_scroll += scroll;
-    host_impl_->ScrollEnd(EndState().get());
-    host_impl_->active_tree()->UpdateDrawProperties();
+    SetScrollOffset(host_impl_->InnerViewportScrollLayer(),
+                    gfx::ScrollOffset(0, accumulated_scroll));
+    UpdateDrawProperties(host_impl_->active_tree());
 
     CreatePendingTree();
-    host_impl_->pending_tree()->set_source_frame_number(i + 1);
-    LayerImpl* content_layer = host_impl_->pending_tree()
-                                   ->OuterViewportScrollLayer()
-                                   ->test_properties()
-                                   ->children[0];
-    // The scroll done on the active tree is undone on the pending tree.
-    gfx::Transform translate;
-    translate.Translate(0, accumulated_scroll);
-    content_layer->test_properties()->transform = translate;
-
     LayerTreeImpl* pending_tree = host_impl_->pending_tree();
+    pending_tree->set_source_frame_number(i + 1);
     pending_tree->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
-    LayerImpl* last_scrolled_layer = pending_tree->LayerById(
-        host_impl_->active_tree()->InnerViewportScrollLayer()->id());
-
-    // When building property trees from impl side, the builder uses the scroll
-    // offset of layer_impl to initialize the scroll offset in scroll tree:
-    //  scroll_tree.synced_scroll_offset.PushMainToPending(
-    //                                   layer->CurrentScrollOffset()).
-    // However, layer_impl does not store scroll_offset, so it is using scroll
-    // tree's scroll offset to initialize itself. Usually this approach works
-    // because this is a simple assignment. However if scroll_offset's pending
-    // delta is not zero, the delta would be counted twice.
-    // This hacking here is to restore the damaged scroll offset.
-    gfx::ScrollOffset pending_base =
-        pending_tree->property_trees()
-            ->scroll_tree.GetScrollOffsetBaseForTesting(
-                last_scrolled_layer->element_id());
-    pending_tree->BuildPropertyTreesForTesting();
-    pending_tree->property_trees()
-        ->scroll_tree.UpdateScrollOffsetBaseForTesting(
-            last_scrolled_layer->element_id(), pending_base);
-    pending_tree->LayerById(content_layer->id())->SetNeedsPushProperties();
-
-    pending_tree->set_needs_update_draw_properties();
+    // Simulate scroll offset pushed from the main thread.
+    SetScrollOffset(scroll_layer, gfx::ScrollOffset(0, accumulated_scroll));
+    // The scroll done on the active tree is undone on the pending tree.
+    content_layer->SetOffsetToTransformParent(
+        gfx::Vector2dF(0, accumulated_scroll));
+    content_layer->SetNeedsPushProperties();
     pending_tree->UpdateDrawProperties();
+
     float jitter = LayerTreeHostCommon::CalculateLayerJitter(content_layer);
     // There should not be any jitter measured till we hit the fixed point hits
-    // threshold.
+    // threshold. 250 is sqrt(50 * 50) * 5. 50x50 is the visible bounds of
+    // content (clipped by the viewport). 5 is the distance between the
+    // locations of the content in the pending tree and the active tree.
     float expected_jitter =
-        (i == pending_tree->kFixedPointHitsThreshold) ? 500 : 0;
+        (i == pending_tree->kFixedPointHitsThreshold) ? 250 : 0;
     EXPECT_EQ(jitter, expected_jitter);
   }
 }
@@ -13685,29 +12442,19 @@
   gfx::Size scrollbar_size_1(gfx::Size(15, viewport_size.height()));
   gfx::Size scrollbar_size_2(gfx::Size(15, child_layer_size.height()));
 
-  const int scrollbar_1_id = 10;
-  const int scrollbar_2_id = 11;
-  const int child_scroll_id = 13;
-
   CreateHostImpl(settings, CreateLayerTreeFrameSink());
   host_impl_->active_tree()->SetDeviceScaleFactor(1);
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
-  CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
-  host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
-      viewport_size);
-  LayerImpl* root_scroll =
-      host_impl_->active_tree()->OuterViewportScrollLayer();
+  SetupViewportLayersInnerScrolls(viewport_size, content_size);
+  LayerImpl* root_scroll = host_impl_->OuterViewportScrollLayer();
 
   if (main_thread_scrolling) {
-    root_scroll->test_properties()->main_thread_scrolling_reasons =
+    GetScrollNode(root_scroll)->main_thread_scrolling_reasons =
         MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
   }
 
   // scrollbar_1 on root scroll.
-  std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar_1 =
-      SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                           scrollbar_1_id, VERTICAL, 15, 0,
-                                           true, true);
+  auto* scrollbar_1 = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, 15, 0, true, true);
   scrollbar_1->SetScrollElementId(root_scroll->element_id());
   scrollbar_1->SetDrawsContent(true);
   scrollbar_1->SetBounds(scrollbar_size_1);
@@ -13715,18 +12462,13 @@
   touch_action_region.Union(kTouchActionNone, gfx::Rect(scrollbar_size_1));
   scrollbar_1->SetTouchActionRegion(touch_action_region);
   scrollbar_1->SetCurrentPos(0);
-  scrollbar_1->test_properties()->position = gfx::PointF(0, 0);
-  host_impl_->active_tree()
-      ->InnerViewportContainerLayer()
-      ->test_properties()
-      ->AddChild(std::move(scrollbar_1));
+  CopyProperties(host_impl_->InnerViewportContainerLayer(), scrollbar_1);
+  CreateEffectNode(scrollbar_1);
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
   host_impl_->active_tree()->UpdateScrollbarGeometries();
   host_impl_->active_tree()->DidBecomeActive();
 
   DrawFrame();
-  host_impl_->active_tree()->UpdateDrawProperties();
 
   ScrollbarAnimationController* scrollbar_1_animation_controller =
       host_impl_->ScrollbarAnimationControllerForElementId(
@@ -13800,40 +12542,30 @@
       scrollbar_1_animation_controller->MouseIsOverScrollbarThumb(VERTICAL));
 
   // scrollbar_2 on child.
-  std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar_2 =
-      SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
-                                           scrollbar_2_id, VERTICAL, 15, 0,
-                                           true, true);
-  std::unique_ptr<LayerImpl> child =
-      LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
-  child->test_properties()->position = gfx::PointF(50, 50);
-  child->SetBounds(child_layer_size);
-  child->SetDrawsContent(true);
-  child->SetScrollable(gfx::Size(100, 100));
-  child->SetHitTestable(true);
-  child->SetElementId(LayerIdToElementIdForTesting(child->id()));
-  ElementId child_element_id = child->element_id();
-
+  auto* scrollbar_2 = AddLayer<SolidColorScrollbarLayerImpl>(
+      host_impl_->active_tree(), VERTICAL, 15, 0, true, true);
+  LayerImpl* child =
+      AddScrollableLayer(root_scroll, gfx::Size(100, 100), child_layer_size);
+  child->SetOffsetToTransformParent(gfx::Vector2dF(50, 50));
   if (main_thread_scrolling) {
-    child->test_properties()->main_thread_scrolling_reasons =
+    GetScrollNode(child)->main_thread_scrolling_reasons =
         MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects;
   }
 
-  scrollbar_2->SetScrollElementId(child_element_id);
+  scrollbar_2->SetScrollElementId(child->element_id());
   scrollbar_2->SetDrawsContent(true);
   scrollbar_2->SetBounds(scrollbar_size_2);
   scrollbar_2->SetCurrentPos(0);
-  scrollbar_2->test_properties()->position = gfx::PointF(0, 0);
+  CopyProperties(child, scrollbar_2);
+  scrollbar_2->SetOffsetToTransformParent(child->offset_to_transform_parent());
+  CreateEffectNode(scrollbar_2);
 
-  child->test_properties()->AddChild(std::move(scrollbar_2));
-  root_scroll->test_properties()->AddChild(std::move(child));
-
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
   host_impl_->active_tree()->UpdateScrollbarGeometries();
   host_impl_->active_tree()->DidBecomeActive();
 
   ScrollbarAnimationController* scrollbar_2_animation_controller =
-      host_impl_->ScrollbarAnimationControllerForElementId(child_element_id);
+      host_impl_->ScrollbarAnimationControllerForElementId(child->element_id());
   EXPECT_TRUE(scrollbar_2_animation_controller);
 
   // Mouse goes over scrollbar_2, moves close to scrollbar_2, moves close to
@@ -13966,14 +12698,10 @@
   // Create the pending tree.
   host_impl_->BeginCommit();
   LayerTreeImpl* pending_tree = host_impl_->pending_tree();
-  pending_tree->SetDeviceViewportRect(gfx::Rect(layer_size));
-  pending_tree->SetRootLayerForTesting(
-      FakePictureLayerImpl::CreateWithRasterSource(pending_tree, 1,
-                                                   raster_source));
-  auto* root = static_cast<FakePictureLayerImpl*>(*pending_tree->begin());
-  root->SetBounds(layer_size);
+  auto* root = SetupRootLayer<FakePictureLayerImplWithRasterSource>(
+      pending_tree, layer_size, raster_source);
   root->SetDrawsContent(true);
-  pending_tree->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(pending_tree);
 
   // Update the decoding state map for the tracker so it knows the correct
   // decoding preferences for the image.
@@ -14004,7 +12732,8 @@
   // invalidated on the pending tree.
   host_impl_->InvalidateContentOnImplSide();
   pending_tree = host_impl_->pending_tree();
-  root = static_cast<FakePictureLayerImpl*>(*pending_tree->begin());
+  root = static_cast<FakePictureLayerImplWithRasterSource*>(
+      pending_tree->root_layer_for_testing());
   for (auto* tile : root->tilings()->tiling_at(0)->AllTilesForTesting()) {
     if (tile->tiling_i_index() < 2 && tile->tiling_j_index() < 2)
       EXPECT_TRUE(tile->HasRasterTask());
@@ -14043,34 +12772,30 @@
 TEST_F(LayerTreeHostImplTest, UpdatedTilingsForNonDrawingLayers) {
   gfx::Size layer_bounds(500, 500);
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(layer_bounds));
   CreatePendingTree();
-  std::unique_ptr<LayerImpl> scoped_root =
-      LayerImpl::Create(host_impl_->pending_tree(), 1);
-  scoped_root->SetBounds(layer_bounds);
-  LayerImpl* root = scoped_root.get();
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(scoped_root));
+  auto* root =
+      SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_bounds);
 
   scoped_refptr<FakeRasterSource> raster_source(
       FakeRasterSource::CreateFilled(layer_bounds));
-  std::unique_ptr<FakePictureLayerImpl> scoped_animated_transform_layer =
-      FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
-                                                   2, raster_source);
-  scoped_animated_transform_layer->SetBounds(layer_bounds);
-  scoped_animated_transform_layer->SetDrawsContent(true);
+  auto* animated_transform_layer =
+      AddLayer<FakePictureLayerImplWithRasterSource>(host_impl_->pending_tree(),
+                                                     raster_source);
+  animated_transform_layer->SetBounds(layer_bounds);
+  animated_transform_layer->SetDrawsContent(true);
+
+  host_impl_->pending_tree()->SetElementIdsForTesting();
   gfx::Transform singular;
   singular.Scale3d(6.f, 6.f, 0.f);
-  scoped_animated_transform_layer->test_properties()->transform = singular;
-  FakePictureLayerImpl* animated_transform_layer =
-      scoped_animated_transform_layer.get();
-  root->test_properties()->AddChild(std::move(scoped_animated_transform_layer));
+  CopyProperties(root, animated_transform_layer);
+  CreateTransformNode(animated_transform_layer).local = singular;
 
   // A layer with a non-invertible transform is not drawn or rasterized. Since
   // this layer is not rasterized, we shouldn't be creating any tilings for it.
-  host_impl_->pending_tree()->BuildLayerListAndPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->pending_tree());
   EXPECT_FALSE(animated_transform_layer->HasValidTilePriorities());
   EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 0u);
-  host_impl_->pending_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->pending_tree());
   EXPECT_FALSE(animated_transform_layer->raster_even_if_not_drawn());
   EXPECT_FALSE(animated_transform_layer->contributes_to_drawn_render_surface());
   EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 0u);
@@ -14078,7 +12803,6 @@
   // Now add a transform animation to this layer. While we don't drawn layers
   // with non-invertible transforms, we still raster them if there is a
   // transform animation.
-  host_impl_->pending_tree()->SetElementIdsForTesting();
   TransformOperations start_transform_operations;
   start_transform_operations.AppendMatrix(singular);
   TransformOperations end_transform_operations;
@@ -14089,11 +12813,10 @@
   // The layer is still not drawn, but it will be rasterized. Since the layer is
   // rasterized, we should be creating tilings for it in UpdateDrawProperties.
   // However, none of these tiles should be required for activation.
-  host_impl_->pending_tree()->BuildLayerListAndPropertyTreesForTesting();
-  host_impl_->pending_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->pending_tree());
   EXPECT_TRUE(animated_transform_layer->raster_even_if_not_drawn());
   EXPECT_FALSE(animated_transform_layer->contributes_to_drawn_render_surface());
-  EXPECT_EQ(animated_transform_layer->tilings()->num_tilings(), 1u);
+  ASSERT_EQ(animated_transform_layer->tilings()->num_tilings(), 1u);
   EXPECT_FALSE(animated_transform_layer->tilings()
                    ->tiling_at(0)
                    ->can_require_tiles_for_activation());
@@ -14101,38 +12824,29 @@
 
 TEST_F(LayerTreeHostImplTest, RasterTilePrioritizationForNonDrawingLayers) {
   gfx::Size layer_bounds(500, 500);
-
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(layer_bounds));
   CreatePendingTree();
-  std::unique_ptr<LayerImpl> scoped_root =
-      LayerImpl::Create(host_impl_->pending_tree(), 1);
-  scoped_root->SetBounds(layer_bounds);
-  LayerImpl* root = scoped_root.get();
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(scoped_root));
+  auto* root =
+      SetupRootLayer<LayerImpl>(host_impl_->pending_tree(), layer_bounds);
+  root->SetBounds(layer_bounds);
 
   scoped_refptr<FakeRasterSource> raster_source(
       FakeRasterSource::CreateFilled(layer_bounds));
 
-  std::unique_ptr<FakePictureLayerImpl> scoped_hidden_layer =
-      FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
-                                                   2, raster_source);
-  scoped_hidden_layer->SetBounds(layer_bounds);
-  scoped_hidden_layer->SetDrawsContent(true);
-  scoped_hidden_layer->set_contributes_to_drawn_render_surface(true);
-  FakePictureLayerImpl* hidden_layer = scoped_hidden_layer.get();
-  root->test_properties()->AddChild(std::move(scoped_hidden_layer));
+  auto* hidden_layer = AddLayer<FakePictureLayerImplWithRasterSource>(
+      host_impl_->pending_tree(), raster_source);
+  hidden_layer->SetBounds(layer_bounds);
+  hidden_layer->SetDrawsContent(true);
+  hidden_layer->set_contributes_to_drawn_render_surface(true);
+  CopyProperties(root, hidden_layer);
 
-  std::unique_ptr<FakePictureLayerImpl> scoped_drawing_layer =
-      FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
-                                                   3, raster_source);
-  scoped_drawing_layer->SetBounds(layer_bounds);
-  scoped_drawing_layer->SetDrawsContent(true);
-  scoped_drawing_layer->set_contributes_to_drawn_render_surface(true);
-  FakePictureLayerImpl* drawing_layer = scoped_drawing_layer.get();
-  root->test_properties()->AddChild(std::move(scoped_drawing_layer));
+  auto* drawing_layer = AddLayer<FakePictureLayerImplWithRasterSource>(
+      host_impl_->pending_tree(), raster_source);
+  drawing_layer->SetBounds(layer_bounds);
+  drawing_layer->SetDrawsContent(true);
+  drawing_layer->set_contributes_to_drawn_render_surface(true);
+  CopyProperties(root, drawing_layer);
 
   gfx::Rect layer_rect(0, 0, 500, 500);
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
 
   hidden_layer->tilings()->AddTiling(gfx::AxisTransform2d(), raster_source);
   PictureLayerTiling* hidden_tiling = hidden_layer->tilings()->tiling_at(0);
@@ -14179,19 +12893,13 @@
   gfx::Size bounds(100, 100);
   scoped_refptr<FakeRasterSource> raster_source(
       FakeRasterSource::CreateFilled(bounds));
-  {
-    std::unique_ptr<FakePictureLayerImpl> scoped_layer =
-        FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
-                                                     1, raster_source);
-    scoped_layer->SetBounds(bounds);
-    scoped_layer->SetDrawsContent(true);
-    host_impl_->pending_tree()->SetRootLayerForTesting(std::move(scoped_layer));
-  }
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  auto* root = SetupRootLayer<FakePictureLayerImplWithRasterSource>(
+      host_impl_->pending_tree(), bounds, raster_source);
+  root->SetDrawsContent(true);
   host_impl_->ActivateSyncTree();
 
   FakePictureLayerImpl* layer = static_cast<FakePictureLayerImpl*>(
-      host_impl_->active_tree()->FindActiveTreeLayerById(1));
+      host_impl_->active_tree()->FindActiveTreeLayerById(root->id()));
 
   DrawFrame();
   EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());
@@ -14278,7 +12986,7 @@
 };
 
 TEST_F(LayerTreeHostImplTest, RenderFrameMetadata) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(50, 50));
   host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
 
@@ -14319,22 +13027,16 @@
   // Root "overflow: hidden" properties should be reflected on the outer
   // viewport scroll layer.
   {
-    host_impl_->active_tree()
-        ->OuterViewportScrollLayer()
-        ->test_properties()
-        ->user_scrollable_horizontal = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
+    host_impl_->OuterViewportScrollNode()->user_scrollable_horizontal = false;
 
     RenderFrameMetadata metadata = StartDrawAndProduceRenderFrameMetadata();
     EXPECT_FALSE(metadata.root_overflow_y_hidden);
   }
 
   {
-    host_impl_->active_tree()
-        ->OuterViewportScrollLayer()
-        ->test_properties()
-        ->user_scrollable_vertical = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
+    host_impl_->OuterViewportScrollNode()->user_scrollable_vertical = false;
 
     RenderFrameMetadata metadata = StartDrawAndProduceRenderFrameMetadata();
     EXPECT_TRUE(metadata.root_overflow_y_hidden);
@@ -14343,15 +13045,9 @@
   // Re-enable scrollability and verify that overflows are no longer
   // hidden.
   {
-    host_impl_->active_tree()
-        ->OuterViewportScrollLayer()
-        ->test_properties()
-        ->user_scrollable_horizontal = true;
-    host_impl_->active_tree()
-        ->OuterViewportScrollLayer()
-        ->test_properties()
-        ->user_scrollable_vertical = true;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
+    host_impl_->OuterViewportScrollNode()->user_scrollable_horizontal = true;
+    host_impl_->OuterViewportScrollNode()->user_scrollable_vertical = true;
 
     RenderFrameMetadata metadata = StartDrawAndProduceRenderFrameMetadata();
     EXPECT_FALSE(metadata.root_overflow_y_hidden);
@@ -14360,22 +13056,16 @@
   // Root "overflow: hidden" properties should also be reflected on the
   // inner viewport scroll layer.
   {
-    host_impl_->active_tree()
-        ->InnerViewportScrollLayer()
-        ->test_properties()
-        ->user_scrollable_horizontal = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
+    host_impl_->OuterViewportScrollNode()->user_scrollable_horizontal = false;
 
     RenderFrameMetadata metadata = StartDrawAndProduceRenderFrameMetadata();
     EXPECT_FALSE(metadata.root_overflow_y_hidden);
   }
 
   {
-    host_impl_->active_tree()
-        ->InnerViewportScrollLayer()
-        ->test_properties()
-        ->user_scrollable_vertical = false;
-    host_impl_->active_tree()->BuildPropertyTreesForTesting();
+    UpdateDrawProperties(host_impl_->active_tree());
+    host_impl_->OuterViewportScrollNode()->user_scrollable_vertical = false;
 
     RenderFrameMetadata metadata = StartDrawAndProduceRenderFrameMetadata();
     EXPECT_TRUE(metadata.root_overflow_y_hidden);
@@ -14423,16 +13113,8 @@
 }
 
 TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToRenderFrameMetadata) {
-  const int root_layer_id = 1;
-  std::unique_ptr<SolidColorLayerImpl> root =
-      SolidColorLayerImpl::Create(host_impl_->active_tree(), root_layer_id);
-  root->test_properties()->position = gfx::PointF();
-  root->SetBounds(gfx::Size(10, 10));
-  root->SetDrawsContent(true);
-  root->test_properties()->force_render_surface = true;
-
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(10, 10));
+  UpdateDrawProperties(host_impl_->active_tree());
 
   auto observer = std::make_unique<TestRenderFrameMetadataObserver>(false);
   auto* observer_ptr = observer.get();
@@ -14441,10 +13123,7 @@
 
   // Trigger a draw-swap sequence.
   host_impl_->SetNeedsRedraw();
-  TestFrameData frame;
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  EXPECT_TRUE(host_impl_->DrawLayers(&frame));
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   // Ensure the selection bounds propagated to the render frame metadata
   // represent an empty selection.
@@ -14463,7 +13142,7 @@
   gfx::Point selection_bottom(5, 5);
   LayerSelection selection;
   selection.start.type = gfx::SelectionBound::CENTER;
-  selection.start.layer_id = root_layer_id;
+  selection.start.layer_id = root->id();
   selection.start.edge_bottom = selection_bottom;
   selection.start.edge_top = selection_top;
   selection.end = selection.start;
@@ -14471,9 +13150,7 @@
 
   // Trigger a draw-swap sequence.
   host_impl_->SetNeedsRedraw();
-  EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
-  EXPECT_TRUE(host_impl_->DrawLayers(&frame));
-  host_impl_->DidDrawAllLayers(frame);
+  DrawFrame();
 
   // Ensure the selection bounds have propagated to the render frame metadata.
   ASSERT_TRUE(observer_ptr->last_metadata());
@@ -14490,8 +13167,8 @@
 // Tests ScrollBy() to see if the method sets the scroll tree's currently
 // scrolling node and the ScrollState properly.
 TEST_F(LayerTreeHostImplTest, ScrollByScrollingNode) {
-  SetupScrollAndContentsLayers(gfx::Size(100, 100));
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  UpdateDrawProperties(host_impl_->active_tree());
 
   // Create a ScrollState object with no scrolling element.
   ScrollStateData scroll_state_data;
@@ -14544,8 +13221,8 @@
     // Enable hit test data generation with the CompositorFrame.
     LayerTreeSettings new_settings = settings;
     new_settings.build_hit_test_data = true;
-    return CreateHostImplWithTaskRunnerProvider(
-        new_settings, std::move(layer_tree_frame_sink), &task_runner_provider_);
+    return LayerTreeHostImplTest::CreateHostImpl(
+        new_settings, std::move(layer_tree_frame_sink));
   }
 };
 
@@ -14556,22 +13233,18 @@
   // Setup surface layers in LayerTreeHostImpl.
   host_impl_->CreatePendingTree();
   host_impl_->ActivateSyncTree();
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(1024, 768));
 
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  std::unique_ptr<SurfaceLayerImpl> surface_child =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 3);
+  auto* root = SetupDefaultRootLayer(gfx::Size(1024, 768));
+  auto* surface_child = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
 
-  surface_child->test_properties()->position = gfx::PointF(50, 50);
   surface_child->SetBounds(gfx::Size(100, 100));
   surface_child->SetDrawsContent(true);
   surface_child->SetSurfaceHitTestable(true);
 
-  root->test_properties()->AddChild(std::move(surface_child));
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
+  CopyProperties(root, surface_child);
+  surface_child->SetOffsetToTransformParent(gfx::Vector2dF(50, 50));
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->active_tree());
 
   base::Optional<viz::HitTestRegionList> hit_test_region_list =
       host_impl_->BuildHitTestData();
@@ -14591,36 +13264,26 @@
   // +-----surface_child1 (50, 50), 100x100, Rotate(45)
   // +---surface_child2 (450, 300), 100x100
   // +---overlapping_layer (500, 350), 200x200
-  std::unique_ptr<LayerImpl> intermediate_layer =
-      LayerImpl::Create(host_impl_->active_tree(), 2);
-  std::unique_ptr<SurfaceLayerImpl> surface_child1 =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 3);
-  std::unique_ptr<SurfaceLayerImpl> surface_child2 =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 4);
-  std::unique_ptr<LayerImpl> overlapping_layer =
-      LayerImpl::Create(host_impl_->active_tree(), 5);
+  auto* root = SetupDefaultRootLayer(gfx::Size(1024, 768));
+  auto* intermediate_layer = AddLayer();
+  auto* surface_child1 = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
+  auto* surface_child2 = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
+  auto* overlapping_layer = AddLayer();
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(1024, 768));
-
-  intermediate_layer->test_properties()->position = gfx::PointF(200, 300);
   intermediate_layer->SetBounds(gfx::Size(200, 200));
 
-  surface_child1->test_properties()->position = gfx::PointF(50, 50);
   surface_child1->SetBounds(gfx::Size(100, 100));
   gfx::Transform rotate;
   rotate.Rotate(45);
-  surface_child1->test_properties()->transform = rotate;
   surface_child1->SetDrawsContent(true);
   surface_child1->SetHitTestable(true);
   surface_child1->SetSurfaceHitTestable(true);
 
-  surface_child2->test_properties()->position = gfx::PointF(450, 300);
   surface_child2->SetBounds(gfx::Size(100, 100));
   surface_child2->SetDrawsContent(true);
   surface_child2->SetHitTestable(true);
   surface_child2->SetSurfaceHitTestable(true);
 
-  overlapping_layer->test_properties()->position = gfx::PointF(500, 350);
   overlapping_layer->SetBounds(gfx::Size(200, 200));
   overlapping_layer->SetDrawsContent(true);
   overlapping_layer->SetHitTestable(true);
@@ -14634,24 +13297,20 @@
   surface_child2->SetRange(viz::SurfaceRange(base::nullopt, child_surface_id),
                            base::nullopt);
 
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  intermediate_layer->test_properties()->AddChild(std::move(surface_child1));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(intermediate_layer));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(surface_child2));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(overlapping_layer));
+  CopyProperties(root, intermediate_layer);
+  intermediate_layer->SetOffsetToTransformParent(gfx::Vector2dF(200, 300));
+  CopyProperties(root, surface_child2);
+  surface_child2->SetOffsetToTransformParent(gfx::Vector2dF(450, 300));
+  CopyProperties(root, overlapping_layer);
+  overlapping_layer->SetOffsetToTransformParent(gfx::Vector2dF(500, 350));
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
+  CopyProperties(intermediate_layer, surface_child1);
+  auto& surface_child1_transform_node = CreateTransformNode(surface_child1);
+  // The post_translation includes offset of intermediate_layer.
+  surface_child1_transform_node.post_translation = gfx::Vector2dF(250, 350);
+  surface_child1_transform_node.local = rotate;
+
+  UpdateDrawProperties(host_impl_->active_tree());
   draw_property_utils::ComputeEffects(
       &host_impl_->active_tree()->property_trees()->effect_tree);
 
@@ -14710,26 +13369,24 @@
   // +---surface_child1 (0, 0), 100x100
   // +---overlapping_surface_child2 (50, 50), 100x100, pointer-events: none,
   // does not generate hit test region
-  std::unique_ptr<SurfaceLayerImpl> surface_child1 =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 2);
-  std::unique_ptr<SurfaceLayerImpl> surface_child2 =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 3);
+  auto* root = SetupDefaultRootLayer(gfx::Size(1024, 768));
+  auto* surface_child1 = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
+  auto* surface_child2 = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(1024, 768));
-
-  surface_child1->test_properties()->position = gfx::PointF(0, 0);
   surface_child1->SetBounds(gfx::Size(100, 100));
   surface_child1->SetDrawsContent(true);
   surface_child1->SetHitTestable(true);
   surface_child1->SetSurfaceHitTestable(true);
   surface_child1->SetHasPointerEventsNone(false);
+  CopyProperties(root, surface_child1);
 
-  surface_child2->test_properties()->position = gfx::PointF(50, 50);
   surface_child2->SetBounds(gfx::Size(100, 100));
   surface_child2->SetDrawsContent(true);
   surface_child2->SetHitTestable(true);
   surface_child2->SetSurfaceHitTestable(false);
   surface_child2->SetHasPointerEventsNone(true);
+  CopyProperties(root, surface_child2);
+  surface_child2->SetOffsetToTransformParent(gfx::Vector2dF(50, 50));
 
   viz::LocalSurfaceId child_local_surface_id(2,
                                              base::UnguessableToken::Create());
@@ -14738,22 +13395,9 @@
   surface_child1->SetRange(viz::SurfaceRange(base::nullopt, child_surface_id),
                            base::nullopt);
 
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(surface_child1));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(surface_child2));
-
   constexpr gfx::Rect kFrameRect(0, 0, 1024, 768);
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   base::Optional<viz::HitTestRegionList> hit_test_region_list =
       host_impl_->BuildHitTestData();
   // Generating HitTestRegionList should have been enabled for this test.
@@ -14792,12 +13436,9 @@
   // +-Root (1024x768)
   // +---surface_child (0, 0), 100x100
   // +---100x non overlapping layers (110, 110), 1x1
-  std::unique_ptr<SurfaceLayerImpl> surface_child =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 2);
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(1024, 768));
+  auto* surface_child = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(1024, 768));
-
-  surface_child->test_properties()->position = gfx::PointF(0, 0);
   surface_child->SetBounds(gfx::Size(100, 100));
   surface_child->SetDrawsContent(true);
   surface_child->SetHitTestable(true);
@@ -14811,32 +13452,20 @@
   surface_child->SetRange(viz::SurfaceRange(base::nullopt, child_surface_id),
                           base::nullopt);
 
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(surface_child));
+  CopyProperties(root, surface_child);
 
   // Create 101 non overlapping layers.
   for (size_t i = 0; i <= 100; ++i) {
-    std::unique_ptr<LayerImpl> layer =
-        LayerImpl::Create(host_impl_->active_tree(), i + 3);
-    layer->test_properties()->position = gfx::PointF(110, 110);
+    LayerImpl* layer = AddLayer();
     layer->SetBounds(gfx::Size(1, 1));
     layer->SetDrawsContent(true);
     layer->SetHitTestable(true);
-    host_impl_->active_tree()
-        ->root_layer_for_testing()
-        ->test_properties()
-        ->AddChild(std::move(layer));
+    CopyProperties(root, layer);
   }
 
   constexpr gfx::Rect kFrameRect(0, 0, 1024, 768);
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   base::Optional<viz::HitTestRegionList> hit_test_region_list =
       host_impl_->BuildHitTestData();
   // Generating HitTestRegionList should have been enabled for this test.
@@ -14875,17 +13504,17 @@
   // +-Root (1024x768)
   // +---surface_child1 (0, 0), 100x100
   // +---surface_child2 (0, 0), 50x50, frame_sink_id = (0, 0)
-  std::unique_ptr<SurfaceLayerImpl> surface_child1 =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 2);
+  LayerImpl* root = SetupDefaultRootLayer(gfx::Size(1024, 768));
+  auto* surface_child1 = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
 
   host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(1024, 768));
 
-  surface_child1->test_properties()->position = gfx::PointF(0, 0);
   surface_child1->SetBounds(gfx::Size(100, 100));
   surface_child1->SetDrawsContent(true);
   surface_child1->SetHitTestable(true);
   surface_child1->SetSurfaceHitTestable(true);
   surface_child1->SetHasPointerEventsNone(false);
+  CopyProperties(root, surface_child1);
 
   viz::LocalSurfaceId child_local_surface_id(2,
                                              base::UnguessableToken::Create());
@@ -14894,36 +13523,21 @@
   surface_child1->SetRange(viz::SurfaceRange(base::nullopt, child_surface_id),
                            base::nullopt);
 
-  std::unique_ptr<SurfaceLayerImpl> surface_child2 =
-      SurfaceLayerImpl::Create(host_impl_->active_tree(), 3);
+  auto* surface_child2 = AddLayer<SurfaceLayerImpl>(host_impl_->active_tree());
 
-  surface_child2->test_properties()->position = gfx::PointF(0, 0);
   surface_child2->SetBounds(gfx::Size(50, 50));
   surface_child2->SetDrawsContent(true);
   surface_child2->SetHitTestable(true);
   surface_child2->SetSurfaceHitTestable(true);
   surface_child2->SetHasPointerEventsNone(false);
+  CopyProperties(root, surface_child2);
 
   surface_child2->SetRange(viz::SurfaceRange(base::nullopt, viz::SurfaceId()),
                            base::nullopt);
 
-  std::unique_ptr<LayerImpl> root =
-      LayerImpl::Create(host_impl_->active_tree(), 1);
-  host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(surface_child1));
-
-  host_impl_->active_tree()
-      ->root_layer_for_testing()
-      ->test_properties()
-      ->AddChild(std::move(surface_child2));
-
   constexpr gfx::Rect kFrameRect(0, 0, 1024, 768);
 
-  host_impl_->active_tree()->BuildPropertyTreesForTesting();
-  host_impl_->active_tree()->UpdateDrawProperties();
+  UpdateDrawProperties(host_impl_->active_tree());
   base::Optional<viz::HitTestRegionList> hit_test_region_list =
       host_impl_->BuildHitTestData();
   // Generating HitTestRegionList should have been enabled for this test.
@@ -14977,7 +13591,8 @@
 TEST_F(LayerTreeHostImplTest, SkipOnDrawDoesNotUpdateDrawParams) {
   EXPECT_TRUE(CreateHostImpl(DefaultSettings(),
                              FakeLayerTreeFrameSink::CreateSoftware()));
-  LayerImpl* layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
+  SetupViewportLayersInnerScrolls(gfx::Size(50, 50), gfx::Size(100, 100));
+  auto* layer = host_impl_->InnerViewportScrollLayer();
   layer->SetDrawsContent(true);
   gfx::Transform transform;
   transform.Translate(20, 20);
@@ -15008,32 +13623,20 @@
   gfx::Size scroll_content_size = gfx::Size(360, 3800);
   gfx::Size scrollbar_size = gfx::Size(15, 600);
 
-  host_impl_->active_tree()->SetDeviceViewportRect(gfx::Rect(viewport_size));
-  std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
-  root->SetBounds(viewport_size);
-  root->test_properties()->position = gfx::PointF();
+  LayerImpl* root = SetupDefaultRootLayer(viewport_size);
+  LayerImpl* content =
+      AddScrollableLayer(root, viewport_size, scroll_content_size);
 
-  std::unique_ptr<LayerImpl> content = LayerImpl::Create(layer_tree_impl, 2);
-  content->SetBounds(scroll_content_size);
-  content->SetScrollable(viewport_size);
-  content->SetHitTestable(true);
-  content->SetElementId(LayerIdToElementIdForTesting(content->id()));
-  content->SetDrawsContent(true);
-
-  std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar =
-      SolidColorScrollbarLayerImpl::Create(layer_tree_impl, 3, VERTICAL, 10, 0,
-                                           false, true);
+  auto* scrollbar = AddLayer<SolidColorScrollbarLayerImpl>(
+      layer_tree_impl, VERTICAL, 10, 0, false, true);
   scrollbar->SetBounds(scrollbar_size);
-  scrollbar->test_properties()->position = gfx::PointF(345, 0);
   scrollbar->SetScrollElementId(content->element_id());
   scrollbar->SetDrawsContent(true);
-  scrollbar->test_properties()->opacity = 1.f;
+  CopyProperties(root, scrollbar);
+  scrollbar->SetOffsetToTransformParent(gfx::Vector2dF(345, 0));
+  CreateEffectNode(scrollbar).opacity = 1.f;
 
-  root->test_properties()->AddChild(std::move(content));
-  root->test_properties()->AddChild(std::move(scrollbar));
-
-  layer_tree_impl->SetRootLayerForTesting(std::move(root));
-  layer_tree_impl->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(layer_tree_impl);
   layer_tree_impl->DidBecomeActive();
 
   // Do a scroll over the scrollbar layer as well as the content layer, which
@@ -15076,13 +13679,9 @@
   // Setup the pending tree with a PictureLayerImpl that will contain
   // PaintWorklets.
   host_impl_->CreatePendingTree();
-  std::unique_ptr<PictureLayerImpl> root_owned = PictureLayerImpl::Create(
-      host_impl_->pending_tree(), 1, Layer::LayerMaskType::NOT_MASK);
-  PictureLayerImpl* root = root_owned.get();
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(root_owned));
-
-  root->SetBounds(gfx::Size(100, 100));
-  root->test_properties()->force_render_surface = true;
+  auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(),
+                                                gfx::Size(100, 100),
+                                                Layer::LayerMaskType::NOT_MASK);
   root->SetNeedsPushProperties();
 
   // Add a PaintWorkletInput to the PictureLayerImpl.
@@ -15092,8 +13691,7 @@
   root->UpdateRasterSource(raster_source_with_pws, &empty_invalidation, nullptr,
                            nullptr);
 
-  host_impl_->pending_tree()->SetElementIdsForTesting();
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->pending_tree());
 
   // Since we have dirty PaintWorklets, committing will not cause tile
   // preparation to happen. Instead, it will be delayed until the callback
@@ -15131,13 +13729,9 @@
       std::make_unique<TestPaintWorkletLayerPainter>());
 
   host_impl_->CreatePendingTree();
-  std::unique_ptr<PictureLayerImpl> root_owned = PictureLayerImpl::Create(
-      host_impl_->pending_tree(), 1, Layer::LayerMaskType::NOT_MASK);
-  PictureLayerImpl* root = root_owned.get();
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(root_owned));
-
-  root->SetBounds(gfx::Size(100, 100));
-  root->test_properties()->force_render_surface = true;
+  auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(),
+                                                gfx::Size(100, 100),
+                                                Layer::LayerMaskType::NOT_MASK);
   root->SetNeedsPushProperties();
 
   // Add some PaintWorklets.
@@ -15147,8 +13741,7 @@
   root->UpdateRasterSource(raster_source_with_pws, &empty_invalidation, nullptr,
                            nullptr);
 
-  host_impl_->pending_tree()->SetElementIdsForTesting();
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->pending_tree());
 
   // Pretend that our worklets were already painted.
   ASSERT_EQ(root->GetPaintWorkletRecordMap().size(), 1u);
@@ -15184,13 +13777,9 @@
   // Setup the pending tree with a PictureLayerImpl that will contain
   // PaintWorklets.
   host_impl_->CreatePendingTree();
-  std::unique_ptr<PictureLayerImpl> root_owned = PictureLayerImpl::Create(
-      host_impl_->pending_tree(), 1, Layer::LayerMaskType::NOT_MASK);
-  PictureLayerImpl* root = root_owned.get();
-  host_impl_->pending_tree()->SetRootLayerForTesting(std::move(root_owned));
-
-  root->SetBounds(gfx::Size(100, 100));
-  root->test_properties()->force_render_surface = true;
+  auto* root = SetupRootLayer<PictureLayerImpl>(host_impl_->pending_tree(),
+                                                gfx::Size(100, 100),
+                                                Layer::LayerMaskType::NOT_MASK);
   root->SetNeedsPushProperties();
 
   // Add a PaintWorkletInput to the PictureLayerImpl.
@@ -15200,8 +13789,7 @@
   root->UpdateRasterSource(raster_source_with_pws, &empty_invalidation, nullptr,
                            nullptr);
 
-  host_impl_->pending_tree()->SetElementIdsForTesting();
-  host_impl_->pending_tree()->BuildPropertyTreesForTesting();
+  UpdateDrawProperties(host_impl_->pending_tree());
 
   // Since we have dirty PaintWorklets, committing will not cause tile
   // preparation to happen. Instead, it will be delayed until the callback
diff --git a/chrome/VERSION b/chrome/VERSION
index 218e098..6779854 100644
--- a/chrome/VERSION
+++ b/chrome/VERSION
@@ -1,4 +1,4 @@
-MAJOR=78
+MAJOR=79
 MINOR=0
-BUILD=3905
+BUILD=3906
 PATCH=0
diff --git a/chrome/android/BUILD.gn b/chrome/android/BUILD.gn
index 7c406a2..09724dd 100644
--- a/chrome/android/BUILD.gn
+++ b/chrome/android/BUILD.gn
@@ -2108,11 +2108,24 @@
 }
 
 instrumentation_test_runner("chrome_public_smoke_test") {
-  apk_under_test = "//chrome/android:chrome_public_apk"
+  apk_under_test = ":chrome_public_apk"
   android_test_apk = ":chrome_smoke_test_apk"
   android_test_apk_name = "ChromeSmokeTest"
 }
 
+instrumentation_test_runner("chrome_modern_public_smoke_test") {
+  apk_under_test = ":chrome_modern_public_apk"
+  android_test_apk = ":chrome_smoke_test_apk"
+  android_test_apk_name = "ChromeSmokeTest"
+}
+
+instrumentation_test_runner("monochrome_public_smoke_test") {
+  apk_under_test = ":monochrome_public_apk"
+  android_test_apk = ":chrome_smoke_test_apk"
+  android_test_apk_name = "ChromeSmokeTest"
+  never_incremental = true
+}
+
 android_test_apk("chrome_bundle_smoke_test_apk") {
   apk_name = "ChromeBundleSmokeTest"
   android_manifest =
@@ -2550,6 +2563,7 @@
     "java/src/org/chromium/chrome/browser/locale/LocaleManager.java",
     "java/src/org/chromium/chrome/browser/locale/LocaleTemplateUrlLoader.java",
     "java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java",
+    "java/src/org/chromium/chrome/browser/media/MediaCaptureDevicesDispatcherAndroid.java",
     "java/src/org/chromium/chrome/browser/media/PictureInPictureActivity.java",
     "java/src/org/chromium/chrome/browser/media/remote/RecordCastAction.java",
     "java/src/org/chromium/chrome/browser/metrics/BackgroundTaskMemoryMetricsEmitter.java",
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
index d902253..e53d3c94 100644
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -866,6 +866,7 @@
   "java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java",
   "java/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandler.java",
   "java/src/org/chromium/chrome/browser/login/LoginPrompt.java",
+  "java/src/org/chromium/chrome/browser/media/MediaCaptureDevicesDispatcherAndroid.java",
   "java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java",
   "java/src/org/chromium/chrome/browser/media/MediaLauncherActivity.java",
   "java/src/org/chromium/chrome/browser/media/MediaViewerUtils.java",
diff --git a/chrome/android/features/start_surface/internal/dummy/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java b/chrome/android/features/start_surface/internal/dummy/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
index f6b2e4ff..5c5d5af 100644
--- a/chrome/android/features/start_surface/internal/dummy/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
+++ b/chrome/android/features/start_surface/internal/dummy/java/src/org/chromium/chrome/features/start_surface/ExploreSurfaceCoordinator.java
@@ -29,9 +29,8 @@
     interface FeedSurfaceCreator {
         /**
          * Creates the {@link FeedSurfaceCoordinator} for the specified mode.
-         * @param isIncognito Whether it is in incognito mode.
          * @return The {@link FeedSurfaceCoordinator}.
          */
-        FeedSurfaceCoordinator createFeedSurfaceCoordinator(boolean isIncognito);
+        FeedSurfaceCoordinator createFeedSurfaceCoordinator();
     }
 }
\ No newline at end of file
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_hovered_card_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_hovered_card_animation.xml
index 5cac16d..e8b037c 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_hovered_card_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_hovered_card_animation.xml
@@ -1,43 +1,77 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <objectAnimator
-        android:propertyName="scaleX"
-        android:startOffset="2350"
-        android:duration="300"
-        android:valueFrom="1f"
-        android:valueTo="0.6f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:propertyName="scaleY"
-        android:startOffset="2350"
-        android:duration="300"
-        android:valueFrom="1f"
-        android:valueTo="0.6f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:propertyName="scaleX"
-        android:duration="300"
-        android:startOffset="3650"
-        android:valueFrom=".6f"
-        android:valueTo="0.4f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:propertyName="scaleY"
-        android:duration="300"
-        android:startOffset="3650"
-        android:valueFrom=".6f"
-        android:valueTo="0.4f"
-        android:valueType="floatType" />
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <!--Dummy animation to restore the shape.-->
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="translateX"
-        android:startOffset="3650"
-        android:duration="300"
+        android:duration="2350"
         android:valueFrom="0"
-        android:valueTo="-55" />
+        android:valueTo="0" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="300"
+            android:valueFrom="1f"
+            android:valueTo="0.6f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="300"
+            android:valueFrom="1f"
+            android:valueTo="0.6f"
+            android:valueType="floatType" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
-        android:propertyName="translateY"
-        android:startOffset="3650"
-        android:duration="300"
+        android:propertyName="translateX"
+        android:duration="1000"
         android:valueFrom="0"
-        android:valueTo="-60" />
+        android:valueTo="0" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="300"
+            android:valueFrom="0.6f"
+            android:valueTo="0.4f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="300"
+            android:valueFrom="0.6f"
+            android:valueTo="0.4f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="-55" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="-60" />
+    </set>
 </set>
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_animation.xml
index 97f2330..8cd571b7 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_animation.xml
@@ -1,41 +1,75 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <!--Dummy animation to restore the shape.-->
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="translateX"
-        android:startOffset="2300"
-        android:duration="300"
+        android:duration="2300"
         android:valueFrom="0"
-        android:valueTo="-200" />
-    <objectAnimator
-        android:propertyName="translateY"
-        android:startOffset="2300"
-        android:duration="300"
-        android:valueFrom="0"
-        android:valueTo="100" />
-    <objectAnimator
-        android:propertyName="scaleX"
-        android:duration="300"
-        android:startOffset="3650"
-        android:valueFrom="1f"
-        android:valueTo="0.4f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:propertyName="scaleY"
-        android:duration="300"
-        android:startOffset="3650"
-        android:valueFrom="1f"
-        android:valueTo="0.4f"
-        android:valueType="floatType" />
+        android:valueTo="0" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="-200" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="100" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="translateX"
-        android:duration="300"
-        android:startOffset="3650"
+        android:duration="1050"
         android:valueFrom="-200"
-        android:valueTo="-245" />
-    <objectAnimator
-        android:propertyName="translateY"
-        android:duration="300"
-        android:startOffset="3650"
-        android:valueFrom="100"
-        android:valueTo="-60" />
+        android:valueTo="-200" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="300"
+            android:valueFrom="1f"
+            android:valueTo="0.4f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="300"
+            android:valueFrom="1f"
+            android:valueTo="0.4f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="300"
+            android:valueFrom="-200"
+            android:valueTo="-245" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="300"
+            android:valueFrom="100"
+            android:valueTo="-60" />
+    </set>
 </set>
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_color_change_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_color_change_animation.xml
index 27417739..c233361f 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_color_change_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_selected_card_color_change_animation.xml
@@ -1,17 +1,30 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <!--TODO(crbug.com/1001666): The fillColor animation is not smooth on certain versions. Add real
+     color transition animation when figuring out solution.-->
+    <objectAnimator
+        android:propertyName="fillColor"
+        android:duration="1000"
+        android:valueFrom="@color/default_icon_color_white"
+        android:valueTo="@color/default_icon_color_white"
+        android:valueType="intType" />
     <objectAnimator
         android:propertyName="fillColor"
         android:duration="300"
-        android:startOffset="1000"
-        android:valueFrom="@color/default_icon_color_white"
+        android:valueFrom="@color/google_blue_50"
+        android:valueTo="@color/google_blue_50"
+        android:valueType="intType" />
+    <objectAnimator
+        android:propertyName="fillColor"
+        android:duration="2350"
+        android:valueFrom="@color/google_blue_50"
         android:valueTo="@color/google_blue_50"
         android:valueType="intType" />
     <objectAnimator
         android:propertyName="fillColor"
         android:duration="300"
-        android:startOffset="3650"
-        android:valueFrom="@color/google_blue_50"
+        android:valueFrom="@color/default_icon_color_white"
         android:valueTo="@color/default_icon_color_white"
         android:valueType="intType" />
 </set>
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_alpha_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_alpha_animation.xml
index 5ec9622..d8a8b7b 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_alpha_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_alpha_animation.xml
@@ -1,15 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="fillAlpha"
-        android:startOffset="700"
+        android:duration="700"
+        android:valueFrom="0f"
+        android:valueTo="0f"
+        android:valueType="floatType" />
+    <objectAnimator
+        android:propertyName="fillAlpha"
         android:duration="300"
         android:valueFrom="0f"
         android:valueTo="0.8f"
         android:valueType="floatType" />
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="fillAlpha"
-        android:startOffset="3650"
+        android:duration="2650"
+        android:valueFrom="0.8f"
+        android:valueTo="0.8f"
+        android:valueType="floatType" />
+    <objectAnimator
+        android:propertyName="fillAlpha"
         android:duration="300"
         android:valueFrom="0.8f"
         android:valueTo="0f"
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_animation.xml
index 2efbfb3..9731129 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_animation.xml
@@ -1,29 +1,68 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <objectAnimator
-        android:propertyName="scaleX"
-        android:duration="300"
-        android:startOffset="700"
-        android:valueFrom="0.75f"
-        android:valueTo="1f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:propertyName="scaleY"
-        android:duration="300"
-        android:startOffset="700"
-        android:valueFrom="0.75f"
-        android:valueTo="1f"
-        android:valueType="floatType" />
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <!--Dummy animation to restore the shape.-->
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="translateX"
-        android:startOffset="2300"
-        android:duration="300"
-        android:valueFrom="0"
-        android:valueTo="-200" />
+        android:duration="700"
+        android:valueFrom="0f"
+        android:valueTo="0f"
+        android:valueType="floatType" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="300"
+            android:valueFrom="0.75f"
+            android:valueTo="1f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="300"
+            android:valueFrom="0.75f"
+            android:valueTo="1f"
+            android:valueType="floatType" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
-        android:propertyName="translateY"
-        android:startOffset="2300"
-        android:duration="300"
-        android:valueFrom="0"
-        android:valueTo="100" />
+        android:propertyName="translateX"
+        android:duration="1300"
+        android:valueFrom="0f"
+        android:valueTo="0f"
+        android:valueType="floatType" />
+
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="-200" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="100" />
+    </set>
 </set>
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_alpha_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_alpha_animation.xml
index 1c69b07..5f3d783 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_alpha_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_alpha_animation.xml
@@ -1,15 +1,27 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
     <objectAnimator
         android:propertyName="fillAlpha"
-        android:startOffset="900"
+        android:duration="900"
+        android:valueFrom="0f"
+        android:valueTo="0f"
+        android:valueType="floatType" />
+    <objectAnimator
+        android:propertyName="fillAlpha"
         android:duration="400"
         android:valueFrom="0f"
         android:valueTo="0.5f"
         android:valueType="floatType" />
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="fillAlpha"
-        android:startOffset="3650"
+        android:duration="2350"
+        android:valueFrom="0.5f"
+        android:valueTo="0.5f"
+        android:valueType="floatType" />
+    <objectAnimator
+        android:propertyName="fillAlpha"
         android:duration="300"
         android:valueFrom="0.5f"
         android:valueTo="0f"
diff --git a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_animation.xml b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_animation.xml
index 505a84d..c2cb69d6 100644
--- a/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_animation.xml
+++ b/chrome/android/features/tab_ui/java/res/anim/iph_touch_point_background_animation.xml
@@ -1,29 +1,67 @@
 <?xml version="1.0" encoding="utf-8"?>
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-    <objectAnimator
-        android:propertyName="scaleX"
-        android:duration="400"
-        android:startOffset="900"
-        android:valueFrom="0.75f"
-        android:valueTo="1f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:propertyName="scaleY"
-        android:duration="400"
-        android:startOffset="900"
-        android:valueFrom="0.75f"
-        android:valueTo="1f"
-        android:valueType="floatType" />
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:ordering="sequentially">
+    <!--Dummy animation to restore the shape.-->
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="0"
+            android:valueFrom="1f"
+            android:valueTo="1f" />
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="0"
+            android:valueFrom="0f"
+            android:valueTo="0f" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
         android:propertyName="translateX"
-        android:startOffset="2300"
-        android:duration="300"
-        android:valueFrom="0"
-        android:valueTo="-200" />
+        android:duration="900"
+        android:valueFrom="0f"
+        android:valueTo="0f"
+        android:valueType="floatType" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="scaleX"
+            android:duration="400"
+            android:valueFrom="0.75f"
+            android:valueTo="1f"
+            android:valueType="floatType" />
+        <objectAnimator
+            android:propertyName="scaleY"
+            android:duration="400"
+            android:valueFrom="0.75f"
+            android:valueTo="1f"
+            android:valueType="floatType" />
+    </set>
+    <!--Dummy animation to work as start offset.-->
     <objectAnimator
-        android:propertyName="translateY"
-        android:startOffset="2300"
-        android:duration="300"
-        android:valueFrom="0"
-        android:valueTo="100" />
+        android:propertyName="translateX"
+        android:duration="1000"
+        android:valueFrom="0f"
+        android:valueTo="0f"
+        android:valueType="floatType" />
+    <set android:ordering="together">
+        <objectAnimator
+            android:propertyName="translateX"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="-200" />
+        <objectAnimator
+            android:propertyName="translateY"
+            android:duration="300"
+            android:valueFrom="0"
+            android:valueTo="100" />
+    </set>
 </set>
diff --git a/chrome/android/java/res/drawable/infobar_wrapper_bg.xml b/chrome/android/java/res/drawable/infobar_wrapper_bg.xml
index 8d0afaf..e119c11 100644
--- a/chrome/android/java/res/drawable/infobar_wrapper_bg.xml
+++ b/chrome/android/java/res/drawable/infobar_wrapper_bg.xml
@@ -12,5 +12,5 @@
     </item>
     <item
         android:top="@dimen/infobar_shadow_height"
-        android:drawable="@color/modern_primary_color" />
+        android:drawable="@color/infobar_background_color" />
 </layer-list>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureDevicesDispatcherAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureDevicesDispatcherAndroid.java
new file mode 100644
index 0000000..2acee4c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureDevicesDispatcherAndroid.java
@@ -0,0 +1,38 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chrome.browser.media;
+
+import org.chromium.content_public.browser.WebContents;
+
+/**
+ * Java access point for MediaCaptureDevicesDispatcher, allowing for querying and manipulation of
+ * media capture state.
+ */
+public class MediaCaptureDevicesDispatcherAndroid {
+    public static boolean isCapturingAudio(WebContents webContents) {
+        if (webContents == null) return false;
+        return nativeIsCapturingAudio(webContents);
+    }
+
+    public static boolean isCapturingVideo(WebContents webContents) {
+        if (webContents == null) return false;
+        return nativeIsCapturingVideo(webContents);
+    }
+
+    public static boolean isCapturingScreen(WebContents webContents) {
+        if (webContents == null) return false;
+        return nativeIsCapturingScreen(webContents);
+    }
+
+    public static void notifyStopped(WebContents webContents) {
+        if (webContents == null) return;
+        nativeNotifyStopped(webContents);
+    }
+
+    private static native boolean nativeIsCapturingAudio(WebContents webContents);
+    private static native boolean nativeIsCapturingVideo(WebContents webContents);
+    private static native boolean nativeIsCapturingScreen(WebContents webContents);
+    private static native void nativeNotifyStopped(WebContents webContents);
+}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java
index e764c9c4..626e0dba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/MediaCaptureNotificationService.java
@@ -11,6 +11,8 @@
 import android.content.SharedPreferences;
 import android.os.Build;
 import android.os.IBinder;
+import android.support.annotation.IntDef;
+import android.support.annotation.Nullable;
 import android.support.v4.app.NotificationCompat;
 import android.util.SparseIntArray;
 
@@ -27,9 +29,9 @@
 import org.chromium.chrome.browser.notifications.PendingIntentProvider;
 import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 import org.chromium.chrome.browser.tab.Tab;
-import org.chromium.chrome.browser.tab.TabWebContentsDelegateAndroid;
 import org.chromium.chrome.browser.tabmodel.TabWindowManager;
 import org.chromium.chrome.browser.util.IntentUtils;
+import org.chromium.content_public.browser.WebContents;
 
 import java.net.MalformedURLException;
 import java.net.URL;
@@ -56,11 +58,15 @@
     private static final String WEBRTC_NOTIFICATION_IDS = "WebRTCNotificationIds";
     private static final String TAG = "MediaCapture";
 
-    private static final int MEDIATYPE_NO_MEDIA = 0;
-    private static final int MEDIATYPE_AUDIO_AND_VIDEO = 1;
-    private static final int MEDIATYPE_VIDEO_ONLY = 2;
-    private static final int MEDIATYPE_AUDIO_ONLY = 3;
-    private static final int MEDIATYPE_SCREEN_CAPTURE = 4;
+    @IntDef({MediaType.NO_MEDIA, MediaType.AUDIO_AND_VIDEO, MediaType.VIDEO_ONLY,
+            MediaType.AUDIO_ONLY, MediaType.SCREEN_CAPTURE})
+    @interface MediaType {
+        int NO_MEDIA = 0;
+        int AUDIO_AND_VIDEO = 1;
+        int VIDEO_ONLY = 2;
+        int AUDIO_ONLY = 3;
+        int SCREEN_CAPTURE = 4;
+    }
 
     private NotificationManagerProxy mNotificationManager;
     private SharedPreferences mSharedPreferences;
@@ -80,7 +86,7 @@
      * @return Whether the notification has already been created for provided notification id and
      *         mediaType.
      */
-    private boolean doesNotificationNeedUpdate(int notificationId, int mediaType) {
+    private boolean doesNotificationNeedUpdate(int notificationId, @MediaType int mediaType) {
         return mNotifications.get(notificationId) != mediaType;
     }
 
@@ -100,7 +106,7 @@
         } else {
             String action = intent.getAction();
             int notificationId = intent.getIntExtra(NOTIFICATION_ID_EXTRA, Tab.INVALID_TAB_ID);
-            int mediaType = intent.getIntExtra(NOTIFICATION_MEDIA_TYPE_EXTRA, MEDIATYPE_NO_MEDIA);
+            int mediaType = intent.getIntExtra(NOTIFICATION_MEDIA_TYPE_EXTRA, MediaType.NO_MEDIA);
             String url = intent.getStringExtra(NOTIFICATION_MEDIA_URL_EXTRA);
             boolean isIncognito = intent.getBooleanExtra(NOTIFICATION_MEDIA_IS_INCOGNITO, false);
 
@@ -109,7 +115,10 @@
             } else if (ACTION_SCREEN_CAPTURE_STOP.equals(action)) {
                 // Notify native to stop screen capture when the STOP button in notification
                 // is clicked.
-                TabWebContentsDelegateAndroid.notifyStopped(notificationId);
+                final Tab tab = TabWindowManager.getInstance().getTabById(notificationId);
+                if (tab != null) {
+                    MediaCaptureDevicesDispatcherAndroid.notifyStopped(tab.getWebContents());
+                }
             }
         }
         return super.onStartCommand(intent, flags, startId);
@@ -140,13 +149,13 @@
      * @param url Url of the current webrtc call.
      */
     private void updateNotification(
-            int notificationId, int mediaType, String url, boolean isIncognito) {
+            int notificationId, @MediaType int mediaType, String url, boolean isIncognito) {
         if (doesNotificationExist(notificationId)
                 && !doesNotificationNeedUpdate(notificationId, mediaType))  {
             return;
         }
         destroyNotification(notificationId);
-        if (mediaType != MEDIATYPE_NO_MEDIA) {
+        if (mediaType != MediaType.NO_MEDIA) {
             createNotification(notificationId, mediaType, url, isIncognito);
         }
         if (mNotifications.size() == 0) stopSelf();
@@ -175,8 +184,8 @@
      * @param url Url of the current webrtc call.
      */
     private void createNotification(
-            int notificationId, int mediaType, String url, boolean isIncognito) {
-        final String channelId = mediaType == MEDIATYPE_SCREEN_CAPTURE
+            int notificationId, @MediaType int mediaType, String url, boolean isIncognito) {
+        final String channelId = mediaType == MediaType.SCREEN_CAPTURE
                 ? ChannelDefinitions.ChannelId.SCREEN_CAPTURE
                 : ChannelDefinitions.ChannelId.MEDIA;
 
@@ -197,7 +206,7 @@
             PendingIntentProvider contentIntent = PendingIntentProvider.getActivity(
                     ContextUtils.getApplicationContext(), notificationId, tabIntent, 0);
             builder.setContentIntent(contentIntent);
-            if (mediaType == MEDIATYPE_SCREEN_CAPTURE) {
+            if (mediaType == MediaType.SCREEN_CAPTURE) {
                 // Add a "Stop" button to the screen capture notification and turn the notification
                 // into a high priority one.
                 builder.setPriorityBeforeO(NotificationCompat.PRIORITY_HIGH);
@@ -228,7 +237,7 @@
         } else {
             if (tabIntent == null) {
                 descriptionText.append(" ").append(url);
-            } else if (mediaType != MEDIATYPE_SCREEN_CAPTURE) {
+            } else if (mediaType != MediaType.SCREEN_CAPTURE) {
                 descriptionText.append(" ").append(
                         ContextUtils.getApplicationContext().getResources().getString(
                                 R.string.media_notification_link_text, url));
@@ -255,8 +264,9 @@
      * @param url Url of the current webrtc call.
      * @return A string builder initialized to the contents of the specified string.
      */
-    private String getNotificationContentText(int mediaType, String url, boolean hideUserData) {
-        if (mediaType == MEDIATYPE_SCREEN_CAPTURE) {
+    private String getNotificationContentText(
+            @MediaType int mediaType, String url, boolean hideUserData) {
+        if (mediaType == MediaType.SCREEN_CAPTURE) {
             return ContextUtils.getApplicationContext().getResources().getString(hideUserData
                             ? R.string.screen_capture_incognito_notification_text
                             : R.string.screen_capture_notification_text,
@@ -264,15 +274,15 @@
         }
 
         int notificationContentTextId = 0;
-        if (mediaType == MEDIATYPE_AUDIO_AND_VIDEO) {
+        if (mediaType == MediaType.AUDIO_AND_VIDEO) {
             notificationContentTextId = hideUserData
                     ? R.string.video_audio_call_incognito_notification_text_2
                     : R.string.video_audio_call_notification_text_2;
-        } else if (mediaType == MEDIATYPE_VIDEO_ONLY) {
+        } else if (mediaType == MediaType.VIDEO_ONLY) {
             notificationContentTextId = hideUserData
                     ? R.string.video_call_incognito_notification_text_2
                     : R.string.video_call_notification_text_2;
-        } else if (mediaType == MEDIATYPE_AUDIO_ONLY) {
+        } else if (mediaType == MediaType.AUDIO_ONLY) {
             notificationContentTextId = hideUserData
                     ? R.string.audio_call_incognito_notification_text_2
                     : R.string.audio_call_notification_text_2;
@@ -286,15 +296,15 @@
      * @param mediaType Media type of the notification.
      * @return An icon id of the provided mediaType.
      */
-    private int getNotificationIconId(int mediaType) {
+    private int getNotificationIconId(@MediaType int mediaType) {
         int notificationIconId = 0;
-        if (mediaType == MEDIATYPE_AUDIO_AND_VIDEO) {
+        if (mediaType == MediaType.AUDIO_AND_VIDEO) {
             notificationIconId = R.drawable.webrtc_video;
-        } else if (mediaType == MEDIATYPE_VIDEO_ONLY) {
+        } else if (mediaType == MediaType.VIDEO_ONLY) {
             notificationIconId = R.drawable.webrtc_video;
-        } else if (mediaType == MEDIATYPE_AUDIO_ONLY) {
+        } else if (mediaType == MediaType.AUDIO_ONLY) {
             notificationIconId = R.drawable.webrtc_audio;
-        } else if (mediaType == MEDIATYPE_SCREEN_CAPTURE) {
+        } else if (mediaType == MediaType.SCREEN_CAPTURE) {
             notificationIconId = R.drawable.webrtc_video;
         }
         return notificationIconId;
@@ -338,27 +348,34 @@
     }
 
     /**
-     * @param audio If audio is being captured.
-     * @param video If video is being captured.
-     * @param screen If screen is being captured.
-     * @return A constant identify what media is being captured.
+     * @param webContents the webContents for the tab. Used to query the media capture state.
+     * @return A constant identifying what media is being captured.
      */
-    public static int getMediaType(boolean audio, boolean video, boolean screen) {
-        if (screen) {
-            return MEDIATYPE_SCREEN_CAPTURE;
-        } else if (audio && video) {
-            return MEDIATYPE_AUDIO_AND_VIDEO;
+    private static int getMediaType(@Nullable WebContents webContents) {
+        if (webContents == null) {
+            return MediaType.NO_MEDIA;
+        }
+
+        if (MediaCaptureDevicesDispatcherAndroid.isCapturingScreen(webContents)) {
+            return MediaType.SCREEN_CAPTURE;
+        }
+
+        boolean audio = MediaCaptureDevicesDispatcherAndroid.isCapturingAudio(webContents);
+        boolean video = MediaCaptureDevicesDispatcherAndroid.isCapturingVideo(webContents);
+        if (audio && video) {
+            return MediaType.AUDIO_AND_VIDEO;
         } else if (audio) {
-            return MEDIATYPE_AUDIO_ONLY;
+            return MediaType.AUDIO_ONLY;
         } else if (video) {
-            return MEDIATYPE_VIDEO_ONLY;
+            return MediaType.VIDEO_ONLY;
         } else {
-            return MEDIATYPE_NO_MEDIA;
+            return MediaType.NO_MEDIA;
         }
     }
 
-    private static boolean shouldStartService(Context context, int mediaType, int tabId) {
-        if (mediaType != MEDIATYPE_NO_MEDIA) return true;
+    private static boolean shouldStartService(
+            Context context, @MediaType int mediaType, int tabId) {
+        if (mediaType != MediaType.NO_MEDIA) return true;
         SharedPreferences sharedPreferences =
                 ContextUtils.getAppSharedPreferences();
         Set<String> notificationIds =
@@ -375,11 +392,13 @@
      * Send an intent to MediaCaptureNotificationService to either create, update or destroy the
      * notification identified by tabId.
      * @param tabId Unique notification id.
-     * @param mediaType The media type that is being captured.
+     * @param webContents The webContents of the tab; used to get the current media type.
      * @param fullUrl Url of the current webrtc call.
      */
     public static void updateMediaNotificationForTab(
-            Context context, int tabId, int mediaType, String fullUrl) {
+            Context context, int tabId, @Nullable WebContents webContents, String fullUrl) {
+        @MediaType
+        int mediaType = getMediaType(webContents);
         if (!shouldStartService(context, mediaType, tabId)) return;
         Intent intent = new Intent(context, MediaCaptureNotificationService.class);
         intent.setAction(ACTION_MEDIA_CAPTURE_UPDATE);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
index 212beb6..4bfb626a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentRequestImpl.java
@@ -403,12 +403,19 @@
     private boolean mArePaymentMethodsSupported;
 
     /**
-     * True after at least one usable payment instrument has been found. Should be read only after
-     * all payment apps have been queried.
+     * True after at least one usable payment instrument has been found and the setting allows
+     * querying this value. This value can be used to respond to hasEnrolledInstrument(). Should be
+     * read only after all payment apps have been queried.
      */
     private boolean mHasEnrolledInstrument;
 
     /**
+     * Whether there's at least one instrument that is not autofill. Should be read only after all
+     * payment apps have been queried.
+     */
+    private boolean mHasNonAutofillInstrument;
+
+    /**
      * True if we should skip showing PaymentRequest UI.
      *
      * <p>In cases where there is a single payment app and the merchant does not request shipping
@@ -2126,6 +2133,7 @@
                             instrument.isServerAutofillInstrumentReplacement();
                     instrument.setHaveRequestedAutofillData(mHaveRequestedAutofillData);
                     mHasEnrolledInstrument |= instrument.canMakePayment();
+                    mHasNonAutofillInstrument |= !instrument.isAutofillInstrument();
                     mPendingInstruments.add(instrument);
                 } else {
                     instrument.dismissInstrument();
@@ -2227,6 +2235,8 @@
 
         mPendingInstruments.clear();
 
+        if (disconnectIfNoPaymentMethodsSupported()) return;
+
         updateInstrumentModifiedTotals();
 
         // UI has requested the full list of payment instruments. Provide it now.
@@ -2241,8 +2251,7 @@
 
     /**
      * If no payment methods are supported, disconnect from the client and return true.
-     *
-     * @return True if no payment methods are supported
+     * @return Whether client has been disconnected.
      */
     private boolean disconnectIfNoPaymentMethodsSupported() {
         if (!isFinishedQueryingPaymentApps() || !mIsCurrentPaymentRequestShowing) return false;
@@ -2280,7 +2289,28 @@
             return true;
         }
 
-        return false;
+        return disconnectForStrictShow();
+    }
+
+    /**
+     * If strict show() conditions are not satisfied, disconnect from client and return true.
+     * @return Whether client has been disconnected.
+     */
+    private boolean disconnectForStrictShow() {
+        if (!mIsUserGestureShow || !mMethodData.containsKey("basic-card") || mHasEnrolledInstrument
+                || mHasNonAutofillInstrument
+                || !PaymentsExperimentalFeatures.isEnabled(
+                        ChromeFeatureList.STRICT_HAS_ENROLLED_AUTOFILL_INSTRUMENT)) {
+            return false;
+        }
+
+        mRejectShowErrorMessage = ErrorStrings.STRICT_BASIC_CARD_SHOW_REJECT;
+        disconnectFromClientWithDebugMessage(
+                ErrorStrings.GENERIC_PAYMENT_METHOD_NOT_SUPPORTED_MESSAGE + " "
+                        + mRejectShowErrorMessage,
+                PaymentErrorReason.NOT_SUPPORTED);
+
+        return true;
     }
 
     /** @return True after payment apps have been queried. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
index 6e3d9fb..4b7eff67 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsDelegateAndroid.java
@@ -19,7 +19,6 @@
 import org.chromium.chrome.browser.fullscreen.FullscreenOptions;
 import org.chromium.chrome.browser.media.MediaCaptureNotificationService;
 import org.chromium.chrome.browser.policy.PolicyAuditor;
-import org.chromium.chrome.browser.tabmodel.TabWindowManager;
 import org.chromium.components.embedder_support.delegate.WebContentsDelegateAndroid;
 import org.chromium.content_public.browser.InvalidateTypes;
 import org.chromium.content_public.browser.WebContents;
@@ -132,10 +131,9 @@
     @Override
     public void navigationStateChanged(int flags) {
         if ((flags & InvalidateTypes.TAB) != 0) {
-            int mediaType = MediaCaptureNotificationService.getMediaType(
-                    isCapturingAudio(), isCapturingVideo(), isCapturingScreen());
             MediaCaptureNotificationService.updateMediaNotificationForTab(
-                    mTab.getApplicationContext(), mTab.getId(), mediaType, mTab.getUrl());
+                    mTab.getApplicationContext(), mTab.getId(), mTab.getWebContents(),
+                    mTab.getUrl());
         }
         if ((flags & InvalidateTypes.TITLE) != 0) {
             // Update cached title then notify observers.
@@ -219,36 +217,6 @@
     }
 
     /**
-     * @return Whether audio is being captured.
-     */
-    private boolean isCapturingAudio() {
-        return !mTab.isClosing() && nativeIsCapturingAudio(mTab.getWebContents());
-    }
-
-    /**
-     * @return Whether video is being captured.
-     */
-    private boolean isCapturingVideo() {
-        return !mTab.isClosing() && nativeIsCapturingVideo(mTab.getWebContents());
-    }
-
-    /**
-     * @return Whether screen is being captured.
-     */
-    private boolean isCapturingScreen() {
-        return !mTab.isClosing() && nativeIsCapturingScreen(mTab.getWebContents());
-    }
-
-    /**
-     * When STOP button in the media capture notification is clicked, pass the event to native
-     * to stop the media capture.
-     */
-    public static void notifyStopped(int tabId) {
-        final Tab tab = TabWindowManager.getInstance().getTabById(tabId);
-        if (tab != null) nativeNotifyStopped(tab.getWebContents());
-    }
-
-    /**
      * Sets the overlay mode.
      * Overlay mode means that we are currently using AndroidOverlays to display video, and
      * that the compositor's surface should support alpha and not be marked as opaque.
@@ -310,9 +278,5 @@
 
     private static native void nativeOnRendererUnresponsive(WebContents webContents);
     private static native void nativeOnRendererResponsive(WebContents webContents);
-    private static native boolean nativeIsCapturingAudio(WebContents webContents);
-    private static native boolean nativeIsCapturingVideo(WebContents webContents);
-    private static native boolean nativeIsCapturingScreen(WebContents webContents);
-    private static native void nativeNotifyStopped(WebContents webContents);
     private static native void nativeShowFramebustBlockInfoBar(WebContents webContents, String url);
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
index ccf96a6..d594c54 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
@@ -343,7 +343,7 @@
         @Override
         public void destroy() {
             MediaCaptureNotificationService.updateMediaNotificationForTab(
-                    mTab.getApplicationContext(), mTab.getId(), 0, mTab.getUrl());
+                    mTab.getApplicationContext(), mTab.getId(), null, mTab.getUrl());
             super.destroy();
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/SuspendedTab.java b/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/SuspendedTab.java
index 6b0984d..85c9163 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/SuspendedTab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/usage_stats/SuspendedTab.java
@@ -22,6 +22,7 @@
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.infobar.InfoBarContainer;
+import org.chromium.chrome.browser.media.MediaCaptureDevicesDispatcherAndroid;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.WebContents;
@@ -81,6 +82,11 @@
             webContents.suspendAllMediaPlayers();
             webContents.setAudioMuted(true);
             WebContentsAccessibility.fromWebContents(webContents).setObscuredByAnotherView(true);
+            if (MediaCaptureDevicesDispatcherAndroid.isCapturingAudio(webContents)
+                    || MediaCaptureDevicesDispatcherAndroid.isCapturingVideo(webContents)
+                    || MediaCaptureDevicesDispatcherAndroid.isCapturingScreen(webContents)) {
+                MediaCaptureDevicesDispatcherAndroid.notifyStopped(webContents);
+            }
         }
 
         InfoBarContainer infoBarContainer = InfoBarContainer.get(mTab);
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OLauncher.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OLauncher.java
index 296e54b..503c94c 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OLauncher.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OLauncher.java
@@ -62,9 +62,18 @@
                 Intent.FLAG_ACTIVITY_NO_ANIMATION, true /* expectResult */);
     }
 
-    /** Launches the given component, passing extras from the given intent. */
+    /**
+     * Launches the given component, passing extras from the given intent.
+     *
+     * @param context
+     * @param intentToCopy Intent whose extras should be copied.
+     * @param selectedShareTargetActivity Class name of the share activity that the user selected.
+     * @param launchTimeMs Timestamp of when WebAPK's initial activity was launched. -1 if the time
+     *                     is unknown.
+     * @param launchComponent Component to launch.
+     */
     public static void copyIntentExtrasAndLaunch(Context context, Intent intentToCopy,
-            String selectedShareTargetActivity, ComponentName launchComponent) {
+            String selectedShareTargetActivity, long launchTimeMs, ComponentName launchComponent) {
         Intent intent = new Intent(Intent.ACTION_VIEW, intentToCopy.getData());
         intent.setComponent(launchComponent);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -80,6 +89,10 @@
                     selectedShareTargetActivity);
         }
 
+        if (launchTimeMs != -1) {
+            intent.putExtra(WebApkConstants.EXTRA_WEBAPK_LAUNCH_TIME, launchTimeMs);
+        }
+
         context.startActivity(intent);
     }
 
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OOpaqueMainActivity.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OOpaqueMainActivity.java
index 605d1020..5869dcf4 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OOpaqueMainActivity.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OOpaqueMainActivity.java
@@ -9,6 +9,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.os.SystemClock;
 
 /**
  * Launches {@link SplashActivity}. SplashActivity does not handle android.intent.action.MAIN
@@ -30,11 +31,12 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
+        final long launchTimeMs = SystemClock.elapsedRealtime();
         super.onCreate(savedInstanceState);
         Context appContext = getApplicationContext();
         overridePendingTransition(0, 0);
-        H2OLauncher.copyIntentExtrasAndLaunch(
-                appContext, getIntent(), null, new ComponentName(appContext, SplashActivity.class));
+        H2OLauncher.copyIntentExtrasAndLaunch(appContext, getIntent(), null, launchTimeMs,
+                new ComponentName(appContext, SplashActivity.class));
         finish();
     }
 }
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OTransparentLauncherActivity.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OTransparentLauncherActivity.java
index 66bd43b1..f978934 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OTransparentLauncherActivity.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/H2OTransparentLauncherActivity.java
@@ -39,7 +39,7 @@
             // new activity stack.
             Context appContext = getApplicationContext();
             H2OLauncher.copyIntentExtrasAndLaunch(appContext, getIntent(),
-                    params.getSelectedShareTargetActivityClassName(),
+                    params.getSelectedShareTargetActivityClassName(), params.getLaunchTimeMs(),
                     new ComponentName(appContext, SplashActivity.class));
             return;
         }
@@ -76,7 +76,8 @@
         }
 
         H2OLauncher.copyIntentExtrasAndLaunch(getApplicationContext(), getIntent(),
-                params.getSelectedShareTargetActivityClassName(), relaunchComponent);
+                params.getSelectedShareTargetActivityClassName(), -1 /* launchTimeMs */,
+                relaunchComponent);
         return true;
     }
 }
diff --git a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java
index 7f68c80..14b4359 100644
--- a/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java
+++ b/chrome/android/webapk/shell_apk/src/org/chromium/webapk/shell_apk/h2o/SplashActivity.java
@@ -13,7 +13,6 @@
 import android.graphics.Color;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.support.annotation.IntDef;
 import android.view.View;
 import android.view.ViewTreeObserver;
@@ -56,7 +55,6 @@
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        final long activityStartTimeMs = SystemClock.elapsedRealtime();
         super.onCreate(savedInstanceState);
 
         showSplashScreen();
@@ -74,7 +72,7 @@
         }
 
         mPendingLaunch = true;
-        selectHostBrowser(activityStartTimeMs);
+        selectHostBrowser();
     }
 
     @Override
@@ -97,7 +95,7 @@
 
         mPendingLaunch = true;
 
-        selectHostBrowser(-1);
+        selectHostBrowser();
     }
 
     @Override
@@ -129,7 +127,7 @@
         super.onDestroy();
     }
 
-    private void selectHostBrowser(final long activityStartTimeMs) {
+    private void selectHostBrowser() {
         new LaunchHostBrowserSelector(this).selectHostBrowser(
                 new LaunchHostBrowserSelector.Callback() {
                     @Override
@@ -142,7 +140,7 @@
                         HostBrowserLauncherParams params =
                                 HostBrowserLauncherParams.createForIntent(SplashActivity.this,
                                         getIntent(), hostBrowserPackageName, dialogShown,
-                                        activityStartTimeMs);
+                                        -1 /* launchTimeMs */);
                         onHostBrowserSelected(params);
                     }
                 });
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 7ba7a4d..b8bb72aa 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -6187,6 +6187,12 @@
           Open Browser Window
         </message>
       </if>
+      <message name="IDS_APP_MENU_BUTTON_UPDATE" desc="Short label next to app-menu button when an update is available.">
+        Update
+      </message>
+      <message name="IDS_APP_MENU_BUTTON_ERROR" desc="Short label next to app-menu button when the user needs to resolve something.">
+        Error
+      </message>
 
       <!-- Screen Capture strings -->
       <message name="IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE" desc="Title for the prompt shown when screen capturing is requrested by an app.">
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index b559f4c3..4173ed31 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -2540,6 +2540,7 @@
       "android/location_settings_impl.h",
       "android/logo_bridge.cc",
       "android/logo_bridge.h",
+      "android/media/media_capture_devices_dispatcher_android.cc",
       "android/metrics/android_profile_session_durations_service.cc",
       "android/metrics/android_profile_session_durations_service.h",
       "android/metrics/android_profile_session_durations_service_factory.cc",
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index a25c22c..0728e8b 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2723,6 +2723,14 @@
      kOsAll,
      FEATURE_VALUE_TYPE(omnibox::kOmniboxGroupSuggestionsBySearchVsUrl)},
 
+    {"omnibox-preserve-default-match-against-async-update",
+     flag_descriptions::kOmniboxPreserveDefaultMatchAgainstAsyncUpdateName,
+     flag_descriptions::
+         kOmniboxPreserveDefaultMatchAgainstAsyncUpdateDescription,
+     kOsAll,
+     FEATURE_VALUE_TYPE(
+         omnibox::kOmniboxPreserveDefaultMatchAgainstAsyncUpdate)},
+
     {"omnibox-local-entity-suggestions",
      flag_descriptions::kOmniboxLocalEntitySuggestionsName,
      flag_descriptions::kOmniboxLocalEntitySuggestionsDescription, kOsDesktop,
diff --git a/chrome/browser/android/bottombar/overlay_panel_content.cc b/chrome/browser/android/bottombar/overlay_panel_content.cc
index eb30ffc..d7c3d06 100644
--- a/chrome/browser/android/bottombar/overlay_panel_content.cc
+++ b/chrome/browser/android/bottombar/overlay_panel_content.cc
@@ -17,7 +17,6 @@
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/browser/ui/android/view_android_helper.h"
-#include "chrome/common/chrome_render_frame.mojom.h"
 #include "components/embedder_support/android/delegate/web_contents_delegate_android.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/navigation_interception/intercept_navigation_delegate.h"
@@ -26,7 +25,6 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/browser_controls_state.h"
 #include "net/url_request/url_fetcher_impl.h"
-#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "ui/android/view_android.h"
 
 using base::android::JavaParamRef;
@@ -160,14 +158,7 @@
   if (are_controls_hidden)
     state = content::BROWSER_CONTROLS_STATE_HIDDEN;
 
-  chrome::mojom::ChromeRenderFrameAssociatedPtr renderer;
-  web_contents_->GetMainFrame()->GetRemoteAssociatedInterfaces()->GetInterface(
-      &renderer);
-
-  if (!renderer.is_bound())
-    return;
-
-  renderer->UpdateBrowserControlsState(
+  web_contents_->GetMainFrame()->UpdateBrowserControlsState(
       state, content::BROWSER_CONTROLS_STATE_BOTH, false);
 }
 
diff --git a/chrome/browser/android/media/media_capture_devices_dispatcher_android.cc b/chrome/browser/android/media/media_capture_devices_dispatcher_android.cc
new file mode 100644
index 0000000..4c8c116c
--- /dev/null
+++ b/chrome/browser/android/media/media_capture_devices_dispatcher_android.cc
@@ -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.
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
+#include "chrome/android/chrome_jni_headers/MediaCaptureDevicesDispatcherAndroid_jni.h"
+#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
+#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
+
+using base::android::JavaParamRef;
+
+jboolean JNI_MediaCaptureDevicesDispatcherAndroid_IsCapturingAudio(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& java_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(java_web_contents);
+  scoped_refptr<MediaStreamCaptureIndicator> indicator =
+      MediaCaptureDevicesDispatcher::GetInstance()
+          ->GetMediaStreamCaptureIndicator();
+  return indicator->IsCapturingAudio(web_contents);
+}
+
+jboolean JNI_MediaCaptureDevicesDispatcherAndroid_IsCapturingVideo(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& java_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(java_web_contents);
+  scoped_refptr<MediaStreamCaptureIndicator> indicator =
+      MediaCaptureDevicesDispatcher::GetInstance()
+          ->GetMediaStreamCaptureIndicator();
+  return indicator->IsCapturingVideo(web_contents);
+}
+
+jboolean JNI_MediaCaptureDevicesDispatcherAndroid_IsCapturingScreen(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& java_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(java_web_contents);
+  scoped_refptr<MediaStreamCaptureIndicator> indicator =
+      MediaCaptureDevicesDispatcher::GetInstance()
+          ->GetMediaStreamCaptureIndicator();
+  return indicator->IsCapturingDesktop(web_contents);
+}
+
+void JNI_MediaCaptureDevicesDispatcherAndroid_NotifyStopped(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& java_web_contents) {
+  content::WebContents* web_contents =
+      content::WebContents::FromJavaWebContents(java_web_contents);
+  scoped_refptr<MediaStreamCaptureIndicator> indicator =
+      MediaCaptureDevicesDispatcher::GetInstance()
+          ->GetMediaStreamCaptureIndicator();
+  indicator->NotifyStopped(web_contents);
+}
diff --git a/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc b/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc
index 220e03eb..aebca5c 100644
--- a/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc
+++ b/chrome/browser/android/oom_intervention/near_oom_monitor_unittest.cc
@@ -82,7 +82,7 @@
 
  protected:
   scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<MockNearOomMonitor> monitor_;
 };
 
diff --git a/chrome/browser/android/tab_browser_controls_state.cc b/chrome/browser/android/tab_browser_controls_state.cc
index d52e2695..7726c07 100644
--- a/chrome/browser/android/tab_browser_controls_state.cc
+++ b/chrome/browser/android/tab_browser_controls_state.cc
@@ -10,7 +10,6 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/browser_controls_state.h"
-#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 using base::android::AttachCurrentThread;
 using base::android::JavaParamRef;
@@ -42,20 +41,13 @@
 
   auto* web_contents = content::WebContents::FromJavaWebContents(jweb_contents);
   DCHECK(web_contents);
-  chrome::mojom::ChromeRenderFrameAssociatedPtr renderer;
-  web_contents->GetMainFrame()->GetRemoteAssociatedInterfaces()->GetInterface(
-      &renderer);
-  renderer->UpdateBrowserControlsState(constraints_state, current_state,
-                                       animate);
+  web_contents->GetMainFrame()->UpdateBrowserControlsState(
+      constraints_state, current_state, animate);
 
   if (web_contents->ShowingInterstitialPage()) {
-    chrome::mojom::ChromeRenderFrameAssociatedPtr interstitial_renderer;
     web_contents->GetInterstitialPage()
         ->GetMainFrame()
-        ->GetRemoteAssociatedInterfaces()
-        ->GetInterface(&interstitial_renderer);
-    interstitial_renderer->UpdateBrowserControlsState(constraints_state,
-                                                      current_state, animate);
+        ->UpdateBrowserControlsState(constraints_state, current_state, animate);
   }
 }
 
diff --git a/chrome/browser/android/tab_web_contents_delegate_android.cc b/chrome/browser/android/tab_web_contents_delegate_android.cc
index 1121b95..fd35f41 100644
--- a/chrome/browser/android/tab_web_contents_delegate_android.cc
+++ b/chrome/browser/android/tab_web_contents_delegate_android.cc
@@ -25,7 +25,6 @@
 #include "chrome/browser/infobars/infobar_service.h"
 #include "chrome/browser/media/protected_media_identifier_permission_context.h"
 #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
-#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
 #include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
 #include "chrome/browser/prerender/prerender_manager.h"
 #include "chrome/browser/prerender/prerender_manager_factory.h"
@@ -590,50 +589,6 @@
   infobar_service->RemoveInfoBar(hung_renderer_infobar);
 }
 
-jboolean JNI_TabWebContentsDelegateAndroid_IsCapturingAudio(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& java_web_contents) {
-  content::WebContents* web_contents =
-      content::WebContents::FromJavaWebContents(java_web_contents);
-  scoped_refptr<MediaStreamCaptureIndicator> indicator =
-      MediaCaptureDevicesDispatcher::GetInstance()
-          ->GetMediaStreamCaptureIndicator();
-  return indicator->IsCapturingAudio(web_contents);
-}
-
-jboolean JNI_TabWebContentsDelegateAndroid_IsCapturingVideo(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& java_web_contents) {
-  content::WebContents* web_contents =
-      content::WebContents::FromJavaWebContents(java_web_contents);
-  scoped_refptr<MediaStreamCaptureIndicator> indicator =
-      MediaCaptureDevicesDispatcher::GetInstance()
-          ->GetMediaStreamCaptureIndicator();
-  return indicator->IsCapturingVideo(web_contents);
-}
-
-jboolean JNI_TabWebContentsDelegateAndroid_IsCapturingScreen(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& java_web_contents) {
-  content::WebContents* web_contents =
-      content::WebContents::FromJavaWebContents(java_web_contents);
-  scoped_refptr<MediaStreamCaptureIndicator> indicator =
-      MediaCaptureDevicesDispatcher::GetInstance()
-          ->GetMediaStreamCaptureIndicator();
-  return indicator->IsCapturingDesktop(web_contents);
-}
-
-void JNI_TabWebContentsDelegateAndroid_NotifyStopped(
-    JNIEnv* env,
-    const JavaParamRef<jobject>& java_web_contents) {
-  content::WebContents* web_contents =
-      content::WebContents::FromJavaWebContents(java_web_contents);
-  scoped_refptr<MediaStreamCaptureIndicator> indicator =
-      MediaCaptureDevicesDispatcher::GetInstance()
-          ->GetMediaStreamCaptureIndicator();
-  indicator->NotifyStopped(web_contents);
-}
-
 void JNI_TabWebContentsDelegateAndroid_ShowFramebustBlockInfoBar(
     JNIEnv* env,
     const JavaParamRef<jobject>& java_web_contents,
diff --git a/chrome/browser/battery/battery_metrics_browsertest.cc b/chrome/browser/battery/battery_metrics_browsertest.cc
index deea6f5..f63c892 100644
--- a/chrome/browser/battery/battery_metrics_browsertest.cc
+++ b/chrome/browser/battery/battery_metrics_browsertest.cc
@@ -15,6 +15,7 @@
 #include "chrome/test/base/in_process_browser_test.h"
 #include "content/public/common/service_manager_connection.h"
 #include "content/public/test/browser_test_utils.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "services/device/public/mojom/battery_monitor.mojom.h"
 #include "services/device/public/mojom/battery_status.mojom.h"
@@ -49,12 +50,12 @@
 // Replaces the platform specific implementation of BatteryMonitor.
 class MockBatteryMonitor : public device::mojom::BatteryMonitor {
  public:
-  MockBatteryMonitor() : binding_(this) {}
+  MockBatteryMonitor() = default;
   ~MockBatteryMonitor() override = default;
 
-  void Bind(device::mojom::BatteryMonitorRequest request) {
-    DCHECK(!binding_.is_bound());
-    binding_.Bind(std::move(request));
+  void Bind(mojo::PendingReceiver<device::mojom::BatteryMonitor> receiver) {
+    DCHECK(!receiver_.is_bound());
+    receiver_.Bind(std::move(receiver));
   }
 
   void DidChange(const device::mojom::BatteryStatus& battery_status) {
@@ -65,13 +66,13 @@
       ReportStatus();
   }
 
-  void CloseBinding() { binding_.Close(); }
+  void CloseReceiver() { receiver_.reset(); }
 
  private:
   // mojom::BatteryMonitor methods:
   void QueryNextStatus(QueryNextStatusCallback callback) override {
     if (!callback_.is_null()) {
-      binding_.Close();
+      receiver_.reset();
       return;
     }
     callback_ = std::move(callback);
@@ -88,7 +89,7 @@
   QueryNextStatusCallback callback_;
   device::mojom::BatteryStatus status_;
   bool status_to_report_ = false;
-  mojo::Binding<device::mojom::BatteryMonitor> binding_;
+  mojo::Receiver<device::mojom::BatteryMonitor> receiver_{this};
 
   DISALLOW_COPY_AND_ASSIGN(MockBatteryMonitor);
 };
diff --git a/chrome/browser/cache_stats_recorder.cc b/chrome/browser/cache_stats_recorder.cc
index 7596baa..1106544 100644
--- a/chrome/browser/cache_stats_recorder.cc
+++ b/chrome/browser/cache_stats_recorder.cc
@@ -6,7 +6,7 @@
 
 #include "components/web_cache/browser/web_cache_manager.h"
 #include "content/public/browser/browser_thread.h"
-#include "mojo/public/cpp/bindings/strong_associated_binding.h"
+#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
 
 CacheStatsRecorder::CacheStatsRecorder(int render_process_id)
     : render_process_id_(render_process_id) {}
@@ -16,10 +16,11 @@
 // static
 void CacheStatsRecorder::Create(
     int render_process_id,
-    chrome::mojom::CacheStatsRecorderAssociatedRequest request) {
-  mojo::MakeStrongAssociatedBinding(
+    mojo::PendingAssociatedReceiver<chrome::mojom::CacheStatsRecorder>
+        receiver) {
+  mojo::MakeSelfOwnedAssociatedReceiver(
       std::make_unique<CacheStatsRecorder>(render_process_id),
-      std::move(request));
+      std::move(receiver));
 }
 
 void CacheStatsRecorder::RecordCacheStats(uint64_t capacity, uint64_t size) {
diff --git a/chrome/browser/cache_stats_recorder.h b/chrome/browser/cache_stats_recorder.h
index c4c352d..3d8c7b5 100644
--- a/chrome/browser/cache_stats_recorder.h
+++ b/chrome/browser/cache_stats_recorder.h
@@ -14,7 +14,8 @@
 
   static void Create(
       int render_process_id,
-      chrome::mojom::CacheStatsRecorderAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<chrome::mojom::CacheStatsRecorder>
+          receiver);
 
  private:
   // chrome::mojom::CacheStatsRecorder:
diff --git a/chrome/browser/chrome_browser_main.cc b/chrome/browser/chrome_browser_main.cc
index 1fa1f08..ad995a6 100644
--- a/chrome/browser/chrome_browser_main.cc
+++ b/chrome/browser/chrome_browser_main.cc
@@ -734,7 +734,7 @@
 
   // Record collected startup metrics.
   startup_metric_utils::RecordBrowserMainMessageLoopStart(
-      base::TimeTicks::Now(), is_first_run, g_browser_process->local_state());
+      base::TimeTicks::Now(), is_first_run);
 }
 
 void ChromeBrowserMainParts::SetupOriginTrialsCommandLine(
@@ -1629,9 +1629,8 @@
 #endif  // BUILDFLAG(ENABLE_RLZ) && !defined(OS_CHROMEOS)
 
   // Configure modules that need access to resources.
-  net::NetModule::SetResourceProvider(chrome_common_net::NetResourceProvider);
-  media::SetLocalizedStringProvider(
-      chrome_common_media::LocalizedStringProvider);
+  net::NetModule::SetResourceProvider(ChromeNetResourceProvider);
+  media::SetLocalizedStringProvider(ChromeMediaLocalizedStringProvider);
 
   // In unittest mode, this will do nothing.  In normal mode, this will create
   // the global IntranetRedirectDetector instance, which will promptly go to
diff --git a/chrome/browser/chromeos/crostini/crostini_manager.cc b/chrome/browser/chromeos/crostini/crostini_manager.cc
index 466ef1c..33734cc3 100644
--- a/chrome/browser/chromeos/crostini/crostini_manager.cc
+++ b/chrome/browser/chromeos/crostini/crostini_manager.cc
@@ -199,6 +199,9 @@
     if (completed_callback_) {
       LOG(ERROR) << "Destroying without having called the callback.";
     }
+    auto* mount_manager = chromeos::disks::DiskMountManager::GetInstance();
+    if (mount_manager)
+      mount_manager->RemoveObserver(this);
   }
 
   void ReportRestarterResult(CrostiniResult result) {
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
index f139f97c5..b183bcf7 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -401,6 +401,13 @@
   }
 }
 
+std::string GetPngDataAsString(scoped_refptr<base::RefCountedMemory> png_data) {
+  // Base64 encode the result so we can return it as a string.
+  std::string base64Png(png_data->front(),
+                        png_data->front() + png_data->size());
+  base::Base64Encode(base64Png, &base64Png);
+  return base64Png;
+}
 }  // namespace
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1396,17 +1403,60 @@
     std::unique_ptr<ui::ScreenshotGrabber> grabber,
     ui::ScreenshotResult screenshot_result,
     scoped_refptr<base::RefCountedMemory> png_data) {
-  if (screenshot_result == ui::ScreenshotResult::SUCCESS) {
-    // Base64 encode the result so we can return it as a string.
-    std::string base64Png(png_data->front(),
-                          png_data->front() + png_data->size());
-    base::Base64Encode(base64Png, &base64Png);
-    Respond(OneArgument(std::make_unique<base::Value>(std::move(base64Png))));
-  } else {
-    Respond(Error(base::StrCat(
+  if (screenshot_result != ui::ScreenshotResult::SUCCESS) {
+    return Respond(Error(base::StrCat(
         {"Error taking screenshot ",
          base::NumberToString(static_cast<int>(screenshot_result))})));
   }
+  Respond(
+      OneArgument(std::make_unique<base::Value>(GetPngDataAsString(png_data))));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// AutotestPrivateTakeScreenshotForDisplayFunction
+///////////////////////////////////////////////////////////////////////////////
+AutotestPrivateTakeScreenshotForDisplayFunction::
+    ~AutotestPrivateTakeScreenshotForDisplayFunction() = default;
+
+ExtensionFunction::ResponseAction
+AutotestPrivateTakeScreenshotForDisplayFunction::Run() {
+  std::unique_ptr<api::autotest_private::TakeScreenshotForDisplay::Params>
+      params(api::autotest_private::TakeScreenshotForDisplay::Params::Create(
+          *args_));
+  EXTENSION_FUNCTION_VALIDATE(params);
+  DVLOG(1) << "AutotestPrivateTakeScreenshotForDisplayFunction "
+           << params->display_id;
+  int64_t target_display_id;
+  base::StringToInt64(params->display_id, &target_display_id);
+  auto grabber = std::make_unique<ui::ScreenshotGrabber>();
+
+  for (auto* const window : ash::Shell::GetAllRootWindows()) {
+    const int64_t display_id =
+        display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();
+    if (display_id == target_display_id) {
+      grabber->TakeScreenshot(
+          window, window->bounds(),
+          base::BindOnce(
+              &AutotestPrivateTakeScreenshotForDisplayFunction::ScreenshotTaken,
+              this, base::Passed(&grabber)));
+      return RespondLater();
+    }
+  }
+  return RespondNow(Error(base::StrCat(
+      {"Error taking screenshot for display ", params->display_id})));
+}
+
+void AutotestPrivateTakeScreenshotForDisplayFunction::ScreenshotTaken(
+    std::unique_ptr<ui::ScreenshotGrabber> grabber,
+    ui::ScreenshotResult screenshot_result,
+    scoped_refptr<base::RefCountedMemory> png_data) {
+  if (screenshot_result != ui::ScreenshotResult::SUCCESS) {
+    return Respond(Error(base::StrCat(
+        {"Error taking screenshot ",
+         base::NumberToString(static_cast<int>(screenshot_result))})));
+  }
+  Respond(
+      OneArgument(std::make_unique<base::Value>(GetPngDataAsString(png_data))));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
index ef16213..6c26e4f 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.h
@@ -399,6 +399,21 @@
                        scoped_refptr<base::RefCountedMemory> png_data);
 };
 
+class AutotestPrivateTakeScreenshotForDisplayFunction
+    : public ExtensionFunction {
+ public:
+  DECLARE_EXTENSION_FUNCTION("autotestPrivate.takeScreenshotForDisplay",
+                             AUTOTESTPRIVATE_TAKESCREENSHOTFORDISPLAY)
+
+ private:
+  ~AutotestPrivateTakeScreenshotForDisplayFunction() override;
+  ResponseAction Run() override;
+
+  void ScreenshotTaken(std::unique_ptr<ui::ScreenshotGrabber> grabber,
+                       ui::ScreenshotResult screenshot_result,
+                       scoped_refptr<base::RefCountedMemory> png_data);
+};
+
 class AutotestPrivateGetPrinterListFunction
     : public ExtensionFunction,
       public chromeos::CupsPrintersManager::Observer {
diff --git a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h
index 504054d..13b14d2 100644
--- a/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h
+++ b/chrome/browser/chromeos/file_manager/fake_disk_mount_manager.h
@@ -113,7 +113,7 @@
                               const chromeos::disks::Disk* disk);
 
  private:
-  base::ObserverList<Observer>::Unchecked observers_;
+  base::ObserverList<Observer> observers_;
   base::queue<base::OnceClosure> pending_unmount_callbacks_;
 
   DiskMap disks_;
diff --git a/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc b/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
index 180256a6..563c791 100644
--- a/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
+++ b/chrome/browser/chromeos/file_manager/volume_manager_unittest.cc
@@ -225,6 +225,12 @@
           &user_, profile_.get());
     }
 
+    ~ProfileEnvironment() {
+      // In production, KeyedServices have Shutdown() called before destruction.
+      volume_manager_->Shutdown();
+      drive_integration_service_->Shutdown();
+    }
+
     Profile* profile() const { return profile_.get(); }
     VolumeManager* volume_manager() const { return volume_manager_.get(); }
 
diff --git a/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service_unittest.cc b/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service_unittest.cc
index 9816e59d..4a39fd9 100644
--- a/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service_unittest.cc
+++ b/chrome/browser/chromeos/wilco_dtc_supportd/wilco_dtc_supportd_web_request_service_unittest.cc
@@ -150,7 +150,7 @@
 
   std::unique_ptr<WilcoDtcSupportdWebRequestService> web_request_service_;
   network::TestURLLoaderFactory test_url_loader_factory_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 }  // namespace
diff --git a/chrome/browser/content_settings/mixed_content_settings_tab_helper.cc b/chrome/browser/content_settings/mixed_content_settings_tab_helper.cc
index 6a59e96..deea331 100644
--- a/chrome/browser/content_settings/mixed_content_settings_tab_helper.cc
+++ b/chrome/browser/content_settings/mixed_content_settings_tab_helper.cc
@@ -9,6 +9,7 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/site_instance.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 using content::BrowserThread;
@@ -48,7 +49,7 @@
   if (!is_running_insecure_content_allowed_)
     return;
 
-  chrome::mojom::ContentSettingsRendererAssociatedPtr renderer;
+  mojo::AssociatedRemote<chrome::mojom::ContentSettingsRenderer> renderer;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
   renderer->SetAllowRunningInsecureContent();
 }
diff --git a/chrome/browser/content_settings/sound_content_setting_observer.cc b/chrome/browser/content_settings/sound_content_setting_observer.cc
index c21fd5376..101c0f48 100644
--- a/chrome/browser/content_settings/sound_content_setting_observer.cc
+++ b/chrome/browser/content_settings/sound_content_setting_observer.cc
@@ -81,7 +81,7 @@
     return;
   }
 
-  blink::mojom::AutoplayConfigurationClientAssociatedPtr client;
+  mojo::AssociatedRemote<blink::mojom::AutoplayConfigurationClient> client;
   navigation_handle->GetRenderFrameHost()
       ->GetRemoteAssociatedInterfaces()
       ->GetInterface(&client);
diff --git a/chrome/browser/content_settings/tab_specific_content_settings.cc b/chrome/browser/content_settings/tab_specific_content_settings.cc
index 82466bb..f775ad2 100644
--- a/chrome/browser/content_settings/tab_specific_content_settings.cc
+++ b/chrome/browser/content_settings/tab_specific_content_settings.cc
@@ -47,6 +47,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_delegate.h"
 #include "content/public/common/content_constants.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/cookies/canonical_cookie.h"
 #include "storage/common/fileapi/file_system_types.h"
@@ -735,7 +736,8 @@
     content::RenderFrameHost* render_frame_host) {
   // We want to tell the renderer-side code to ignore content settings for this
   // page.
-  chrome::mojom::ContentSettingsRendererAssociatedPtr content_settings_renderer;
+  mojo::AssociatedRemote<chrome::mojom::ContentSettingsRenderer>
+      content_settings_renderer;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
       &content_settings_renderer);
   content_settings_renderer->SetAsInterstitial();
diff --git a/chrome/browser/data_reduction_proxy/data_reduction_proxy_settings_unittest_android.cc b/chrome/browser/data_reduction_proxy/data_reduction_proxy_settings_unittest_android.cc
index c050026..4503067 100644
--- a/chrome/browser/data_reduction_proxy/data_reduction_proxy_settings_unittest_android.cc
+++ b/chrome/browser/data_reduction_proxy/data_reduction_proxy_settings_unittest_android.cc
@@ -183,8 +183,8 @@
   }
 
  private:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   JNIEnv* env_;
   net::MockClientSocketFactory mock_socket_factory_;
   std::unique_ptr<data_reduction_proxy::DataReductionProxyTestContext>
diff --git a/chrome/browser/download/save_package_file_picker.cc b/chrome/browser/download/save_package_file_picker.cc
index de6c9a01..3ece211 100644
--- a/chrome/browser/download/save_package_file_picker.cc
+++ b/chrome/browser/download/save_package_file_picker.cc
@@ -197,7 +197,7 @@
       save_types_.push_back(content::SAVE_PAGE_TYPE_AS_ONLY_HTML);
     }
 
-    if (ShouldSaveAsMHTML()) {
+    if (can_save_as_complete_) {
       AddSingleFileFileTypeInfo(&file_type_info);
       save_types_.push_back(content::SAVE_PAGE_TYPE_AS_MHTML);
     }
diff --git a/chrome/browser/download/save_page_browsertest.cc b/chrome/browser/download/save_page_browsertest.cc
index 24bbe2aa..cb89784 100644
--- a/chrome/browser/download/save_page_browsertest.cc
+++ b/chrome/browser/download/save_page_browsertest.cc
@@ -60,12 +60,12 @@
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/download_test_observer.h"
 #include "content/public/test/no_renderer_crashes_assertion.h"
-#include "content/public/test/test_utils.h"
 #include "net/base/filename_util.h"
 #include "net/dns/mock_host_resolver.h"
 #include "net/test/embedded_test_server/embedded_test_server.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/shell_dialogs/fake_select_file_dialog.h"
 
 using content::BrowserContext;
 using content::BrowserThread;
@@ -76,6 +76,7 @@
 using download::DownloadItem;
 using testing::ContainsRegex;
 using testing::HasSubstr;
+using ui::FakeSelectFileDialog;
 
 namespace {
 
@@ -609,15 +610,15 @@
   // Save the page before completion.
   base::FilePath full_file_name, dir;
   GetDestinationPaths("b", &full_file_name, &dir);
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
+
+  base::RunLoop run_loop;
   content::SavePackageFinishedObserver observer(
       content::BrowserContext::GetDownloadManager(incognito->profile()),
-      loop_runner->QuitClosure());
+      run_loop.QuitClosure());
   ASSERT_TRUE(GetCurrentTab(incognito)->SavePage(
       full_file_name, dir, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML));
 
-  loop_runner->Run();
+  run_loop.Run();
   ASSERT_TRUE(VerifySavePackageExpectations(incognito, url));
 
   // We can't check more than this because SavePackage is racing with
@@ -642,15 +643,14 @@
   DownloadPersistedObserver persisted(browser()->profile(), base::Bind(
       &DownloadStoredProperly, url, full_file_name, 3,
       history::DownloadState::COMPLETE));
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
+  base::RunLoop run_loop;
   content::SavePackageFinishedObserver observer(
       content::BrowserContext::GetDownloadManager(browser()->profile()),
-      loop_runner->QuitClosure());
+      run_loop.QuitClosure());
   ASSERT_TRUE(GetCurrentTab(browser())->SavePage(
       full_file_name, dir, content::SAVE_PAGE_TYPE_AS_COMPLETE_HTML));
 
-  loop_runner->Run();
+  run_loop.Run();
   ASSERT_TRUE(VerifySavePackageExpectations(browser(), url));
   persisted.WaitForPersisted();
 
@@ -709,13 +709,12 @@
   ui_test_utils::NavigateToURL(browser(), url);
 
   SavePackageFilePicker::SetShouldPromptUser(false);
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
+  base::RunLoop run_loop;
   content::SavePackageFinishedObserver observer(
       content::BrowserContext::GetDownloadManager(browser()->profile()),
-      loop_runner->QuitClosure());
+      run_loop.QuitClosure());
   chrome::SavePage(browser());
-  loop_runner->Run();
+  run_loop.Run();
 
   EXPECT_TRUE(base::PathExists(full_file_name));
 
@@ -735,39 +734,50 @@
                                       security_state::NONE, 1);
 }
 
-class SavePageAsMHTMLBrowserTest : public SavePageBrowserTest {
- public:
-  SavePageAsMHTMLBrowserTest() {}
-  ~SavePageAsMHTMLBrowserTest() override;
-  void SetUpCommandLine(base::CommandLine* command_line) override {
-    command_line->AppendSwitch(switches::kSavePageAsMHTML);
-  }
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(SavePageAsMHTMLBrowserTest);
-};
-
-SavePageAsMHTMLBrowserTest::~SavePageAsMHTMLBrowserTest() {
-}
-
-IN_PROC_BROWSER_TEST_F(SavePageAsMHTMLBrowserTest, SavePageAsMHTML) {
+// Tests that a page can be saved as MHTML.
+IN_PROC_BROWSER_TEST_F(SavePageBrowserTest, SavePageAsMHTML) {
   static const int64_t kFileSizeMin = 2758;
   GURL url = NavigateToMockURL("b");
   base::FilePath download_dir = DownloadPrefs::FromDownloadManager(
       GetDownloadManager())->DownloadPath();
   base::FilePath full_file_name = download_dir.AppendASCII(std::string(
       "Test page for saving page feature.mhtml"));
-  SavePackageFilePicker::SetShouldPromptUser(false);
+
+  SavePackageFilePicker::SetShouldPromptUser(true);
   DownloadPersistedObserver persisted(browser()->profile(), base::Bind(
       &DownloadStoredProperly, url, full_file_name, -1,
       history::DownloadState::COMPLETE));
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
-  content::SavePackageFinishedObserver observer(
-      content::BrowserContext::GetDownloadManager(browser()->profile()),
-      loop_runner->QuitClosure());
-  chrome::SavePage(browser());
-  loop_runner->Run();
+
+  FakeSelectFileDialog::Factory* select_file_dialog_factory =
+      FakeSelectFileDialog::RegisterFactory();
+  // Save page and run until the fake select file dialog opens.
+  {
+    base::RunLoop run_loop;
+    select_file_dialog_factory->SetOpenCallback(run_loop.QuitClosure());
+    chrome::SavePage(browser());
+    run_loop.Run();
+  }
+
+// On ChromeOS, the default should be MHTML.
+#if defined(OS_CHROMEOS)
+  ASSERT_EQ("mhtml",
+            select_file_dialog_factory->GetLastDialog()->default_extension());
+#else
+  ASSERT_EQ("html",
+            select_file_dialog_factory->GetLastDialog()->default_extension());
+#endif
+
+  // Save the file as MHTML. Run until save completes.
+  ASSERT_TRUE(select_file_dialog_factory->GetLastDialog()->CallFileSelected(
+      full_file_name, "mhtml"));
+  {
+    base::RunLoop run_loop;
+    content::SavePackageFinishedObserver observer(
+        content::BrowserContext::GetDownloadManager(browser()->profile()),
+        run_loop.QuitClosure());
+    run_loop.Run();
+  }
+
   ASSERT_TRUE(VerifySavePackageExpectations(browser(), url));
   persisted.WaitForPersisted();
 
@@ -787,13 +797,12 @@
   SavePackageFilePicker::SetShouldPromptUser(false);
   GURL url("data:text/plain,foo");
   ui_test_utils::NavigateToURL(browser(), url);
-  scoped_refptr<content::MessageLoopRunner> loop_runner(
-      new content::MessageLoopRunner);
+  base::RunLoop run_loop;
   content::SavePackageFinishedObserver observer(
       content::BrowserContext::GetDownloadManager(browser()->profile()),
-      loop_runner->QuitClosure());
+      run_loop.QuitClosure());
   chrome::SavePage(browser());
-  loop_runner->Run();
+  run_loop.Run();
   base::FilePath download_dir = DownloadPrefs::FromDownloadManager(
       GetDownloadManager())->DownloadPath();
   base::FilePath filename = download_dir.AppendASCII("dataurl.txt");
diff --git a/chrome/browser/flag-metadata.json b/chrome/browser/flag-metadata.json
index eccd87ab..0d5b6c55 100644
--- a/chrome/browser/flag-metadata.json
+++ b/chrome/browser/flag-metadata.json
@@ -2604,6 +2604,11 @@
     "expiry_milestone": 80
   },
   {
+    "name": "omnibox-preserve-default-match-against-async-update",
+    "owners": [ "tommycli", "chrome-omnibox-team@google.com" ],
+    "expiry_milestone": 85
+  },
+  {
     "name": "omnibox-reverse-answers",
     "owners": [ "jdonnelly", "chrome-omnibox-team@google.com" ],
     "expiry_milestone": 76
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 699f417..217d4cd 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1431,6 +1431,15 @@
     "Google head non personalized search suggestions provided by a compact on "
     "device model";
 
+const char kOmniboxPreserveDefaultMatchAgainstAsyncUpdateName[] =
+    "Omnibox Preserve Default Match Against Async Update";
+const char kOmniboxPreserveDefaultMatchAgainstAsyncUpdateDescription[] =
+    "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 char kOmniboxUIShowPlaceholderWhenCaretShowingName[] =
     "Omnibox UI Show Placeholder When Caret Showing";
 const char kOmniboxUIShowPlaceholderWhenCaretShowingDescription[] =
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index f695908..46451aa 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -870,6 +870,9 @@
 extern const char kOmniboxOnDeviceHeadSuggestionsName[];
 extern const char kOmniboxOnDeviceHeadSuggestionsDescription[];
 
+extern const char kOmniboxPreserveDefaultMatchAgainstAsyncUpdateName[];
+extern const char kOmniboxPreserveDefaultMatchAgainstAsyncUpdateDescription[];
+
 extern const char kOmniboxUIShowPlaceholderWhenCaretShowingName[];
 extern const char kOmniboxUIShowPlaceholderWhenCaretShowingDescription[];
 
diff --git a/chrome/browser/media/media_engagement_contents_observer.cc b/chrome/browser/media/media_engagement_contents_observer.cc
index 6ca52f7..8346574e3 100644
--- a/chrome/browser/media/media_engagement_contents_observer.cc
+++ b/chrome/browser/media/media_engagement_contents_observer.cc
@@ -19,6 +19,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "media/base/media_switches.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "third_party/blink/public/mojom/autoplay/autoplay.mojom.h"
 
@@ -32,7 +33,7 @@
 
 void SendEngagementLevelToFrame(const url::Origin& origin,
                                 content::RenderFrameHost* render_frame_host) {
-  blink::mojom::AutoplayConfigurationClientAssociatedPtr client;
+  mojo::AssociatedRemote<blink::mojom::AutoplayConfigurationClient> client;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&client);
   client->AddAutoplayFlags(origin,
                            blink::mojom::kAutoplayFlagHighMediaEngagement);
diff --git a/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc b/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc
index 6eb89ed..2824ed8 100644
--- a/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc
+++ b/chrome/browser/metrics/desktop_session_duration/desktop_session_duration_tracker_unittest.cc
@@ -90,7 +90,7 @@
   MockDesktopSessionObserver observer_;
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   DISALLOW_COPY_AND_ASSIGN(DesktopSessionDurationTrackerTest);
 };
diff --git a/chrome/browser/metrics/startup_metrics_browsertest.cc b/chrome/browser/metrics/startup_metrics_browsertest.cc
index 42e0590..9f79cf2c 100644
--- a/chrome/browser/metrics/startup_metrics_browsertest.cc
+++ b/chrome/browser/metrics/startup_metrics_browsertest.cc
@@ -8,7 +8,6 @@
 #include "base/metrics/statistics_recorder.h"
 #include "base/run_loop.h"
 #include "build/build_config.h"
-#include "chrome/browser/browser_process.h"
 #include "chrome/test/base/in_process_browser_test.h"
 #include "components/startup_metric_utils/browser/startup_metric_utils.h"
 
@@ -49,8 +48,7 @@
 
   // This is usually done from ChromeBrowserMainParts::MainMessageLoopRun().
   startup_metric_utils::RecordBrowserMainMessageLoopStart(
-      base::TimeTicks::Now(), false /* is_first_run */,
-      g_browser_process->local_state());
+      base::TimeTicks::Now(), false /* is_first_run */);
 
   // Wait for all histograms to be recorded. The test will hang if an histogram
   // is not recorded.
diff --git a/chrome/browser/net/net_error_tab_helper.cc b/chrome/browser/net/net_error_tab_helper.cc
index f2dd356..2f45389e 100644
--- a/chrome/browser/net/net_error_tab_helper.cc
+++ b/chrome/browser/net/net_error_tab_helper.cc
@@ -22,6 +22,7 @@
 #include "content/public/browser/navigation_handle.h"
 #include "content/public/browser/render_frame_host.h"
 #include "ipc/ipc_message_macros.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "net/base/net_errors.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "url/gurl.h"
@@ -63,7 +64,7 @@
   if (render_frame_host->GetParent())
     return;
 
-  chrome::mojom::NetworkDiagnosticsClientAssociatedPtr client;
+  mojo::AssociatedRemote<chrome::mojom::NetworkDiagnosticsClient> client;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&client);
   client->SetCanShowNetworkDiagnosticsDialog(
       CanShowNetworkDiagnosticsDialog(web_contents()));
@@ -247,7 +248,7 @@
   DVLOG(1) << "Sending status " << DnsProbeStatusToString(dns_probe_status_);
   content::RenderFrameHost* rfh = web_contents()->GetMainFrame();
 
-  chrome::mojom::NetworkDiagnosticsClientAssociatedPtr client;
+  mojo::AssociatedRemote<chrome::mojom::NetworkDiagnosticsClient> client;
   rfh->GetRemoteAssociatedInterfaces()->GetInterface(&client);
   client->DNSProbeStatus(dns_probe_status_);
 
diff --git a/chrome/browser/notifications/scheduler/internal/icon_store.cc b/chrome/browser/notifications/scheduler/internal/icon_store.cc
index 0ef0aab..f804082 100644
--- a/chrome/browser/notifications/scheduler/internal/icon_store.cc
+++ b/chrome/browser/notifications/scheduler/internal/icon_store.cc
@@ -47,7 +47,7 @@
 
 IconProtoDbStore::~IconProtoDbStore() = default;
 
-void IconProtoDbStore::Init(InitCallback callback) {
+void IconProtoDbStore::InitAndLoadKeys(InitAndLoadKeysCallback callback) {
   db_->Init(base::BindOnce(&IconProtoDbStore::OnDbInitialized,
                            weak_ptr_factory_.GetWeakPtr(),
                            std::move(callback)));
@@ -97,10 +97,26 @@
 }
 
 void IconProtoDbStore::OnDbInitialized(
-    InitCallback callback,
+    InitAndLoadKeysCallback callback,
     leveldb_proto::Enums::InitStatus status) {
   bool success = (status == leveldb_proto::Enums::InitStatus::kOK);
-  std::move(callback).Run(success);
+  if (!success) {
+    std::move(callback).Run(success, nullptr /*LoadedIconKeys*/);
+    return;
+  }
+  db_->LoadKeys(base::BindOnce(&IconProtoDbStore::OnIconKeysLoaded,
+                               weak_ptr_factory_.GetWeakPtr(),
+                               std::move(callback)));
+}
+
+void IconProtoDbStore::OnIconKeysLoaded(InitAndLoadKeysCallback callback,
+                                        bool success,
+                                        LoadedIconKeys loaded_keys) {
+  if (!success) {
+    std::move(callback).Run(success, nullptr /*LoadedIconKeys*/);
+    return;
+  }
+  std::move(callback).Run(success, std::move(loaded_keys));
 }
 
 void IconProtoDbStore::OnIconEntriesLoaded(
diff --git a/chrome/browser/notifications/scheduler/internal/icon_store.h b/chrome/browser/notifications/scheduler/internal/icon_store.h
index e331976..2d8a8567 100644
--- a/chrome/browser/notifications/scheduler/internal/icon_store.h
+++ b/chrome/browser/notifications/scheduler/internal/icon_store.h
@@ -39,14 +39,16 @@
   using LoadedIconsMap = std::map<std::string /*icons_uuid*/, IconBundle>;
   using IconTypeUuidMap = std::map<IconType, std::string>;
   using IconTypeBundleMap = std::map<IconType, IconBundle>;
+  using LoadedIconKeys = std::unique_ptr<std::vector<std::string>>;
 
-  using InitCallback = base::OnceCallback<void(bool)>;
+  using InitAndLoadKeysCallback =
+      base::OnceCallback<void(bool, LoadedIconKeys)>;
   using LoadIconsCallback = base::OnceCallback<void(bool, LoadedIconsMap)>;
   using AddCallback = base::OnceCallback<void(IconTypeUuidMap, bool)>;
   using UpdateCallback = base::OnceCallback<void(bool)>;
 
-  // Initializes the storage.
-  virtual void Init(InitCallback callback) = 0;
+  // Initializes the storage, and load all keys.
+  virtual void InitAndLoadKeys(InitAndLoadKeysCallback callback) = 0;
 
   // Loads multiple icons.
   virtual void LoadIcons(const std::vector<std::string>& keys,
@@ -76,7 +78,7 @@
 
  private:
   // IconStore implementation.
-  void Init(InitCallback callback) override;
+  void InitAndLoadKeys(InitAndLoadKeysCallback callback) override;
   void LoadIcons(const std::vector<std::string>& keys,
                  LoadIconsCallback callback) override;
   void AddIcons(IconTypeBundleMap icons, AddCallback callback) override;
@@ -84,10 +86,15 @@
                    UpdateCallback callback) override;
 
   // Called when the proto database is initialized.
-  void OnDbInitialized(InitCallback callback,
+  void OnDbInitialized(InitAndLoadKeysCallback callback,
                        leveldb_proto::Enums::InitStatus status);
 
-  // Called when the icon is retrieved from the database.
+  // Called when the icon keys are retrieved from the database.
+  void OnIconKeysLoaded(InitAndLoadKeysCallback callback,
+                        bool success,
+                        LoadedIconKeys icon_keys);
+
+  // Called when the icons are retrieved from the database.
   void OnIconEntriesLoaded(
       LoadIconsCallback callback,
       bool success,
diff --git a/chrome/browser/notifications/scheduler/internal/icon_store_unittest.cc b/chrome/browser/notifications/scheduler/internal/icon_store_unittest.cc
index 0f166d26..43070dd 100644
--- a/chrome/browser/notifications/scheduler/internal/icon_store_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/icon_store_unittest.cc
@@ -57,7 +57,7 @@
   }
 
   void InitDb() {
-    store()->Init(base::DoNothing());
+    store()->InitAndLoadKeys(base::DoNothing());
     db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
   }
 
@@ -144,13 +144,21 @@
 };
 
 TEST_F(IconStoreTest, Init) {
-  store()->Init(base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
+  store()->InitAndLoadKeys(
+      base::BindOnce([](bool success, IconStore::LoadedIconKeys keys) {
+        EXPECT_TRUE(success);
+        EXPECT_NE(keys, nullptr);
+      }));
   db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
   base::RunLoop().RunUntilIdle();
 }
 
 TEST_F(IconStoreTest, InitFailed) {
-  store()->Init(base::BindOnce([](bool success) { EXPECT_FALSE(success); }));
+  store()->InitAndLoadKeys(base::BindOnce(
+      [](bool success, std::unique_ptr<std::vector<std::string>> keys) {
+        EXPECT_FALSE(success);
+        EXPECT_EQ(keys, nullptr);
+      }));
   db()->InitStatusCallback(leveldb_proto::Enums::InitStatus::kCorrupt);
   base::RunLoop().RunUntilIdle();
 }
diff --git a/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc b/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc
index f193f8e..1888e66 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc
+++ b/chrome/browser/notifications/scheduler/internal/notification_scheduler.cc
@@ -41,7 +41,6 @@
   using InitCallback = base::OnceCallback<void(bool)>;
   InitHelper()
       : context_(nullptr),
-        notification_manager_delegate_(nullptr),
         impression_tracker_delegate_(nullptr) {}
 
   ~InitHelper() = default;
@@ -51,14 +50,12 @@
   // object should be destroyed along with the |callback|.
   void Init(
       NotificationSchedulerContext* context,
-      ScheduledNotificationManager::Delegate* notification_manager_delegate,
       ImpressionHistoryTracker::Delegate* impression_tracker_delegate,
       InitCallback callback) {
     // TODO(xingliu): Initialize the databases in parallel, we currently
     // initialize one by one to work around a shared db issue. See
     // https://crbug.com/978680.
     context_ = context;
-    notification_manager_delegate_ = notification_manager_delegate;
     impression_tracker_delegate_ = impression_tracker_delegate;
     callback_ = std::move(callback);
 
@@ -76,7 +73,6 @@
     }
 
     context_->notification_manager()->Init(
-        notification_manager_delegate_,
         base::BindOnce(&InitHelper::OnNotificationManagerInitialized,
                        weak_ptr_factory_.GetWeakPtr()));
   }
@@ -86,7 +82,6 @@
   }
 
   NotificationSchedulerContext* context_;
-  ScheduledNotificationManager::Delegate* notification_manager_delegate_;
   ImpressionHistoryTracker::Delegate* impression_tracker_delegate_;
   InitCallback callback_;
 
@@ -96,7 +91,6 @@
 
 // Implementation of NotificationScheduler.
 class NotificationSchedulerImpl : public NotificationScheduler,
-                                  public ScheduledNotificationManager::Delegate,
                                   public ImpressionHistoryTracker::Delegate {
  public:
   NotificationSchedulerImpl(
@@ -112,7 +106,7 @@
     auto helper = std::make_unique<InitHelper>();
     auto* helper_ptr = helper.get();
     helper_ptr->Init(
-        context_.get(), this, this,
+        context_.get(), this,
         base::BindOnce(&NotificationSchedulerImpl::OnInitialized,
                        weak_ptr_factory_.GetWeakPtr(), std::move(helper),
                        std::move(init_callback)));
@@ -202,8 +196,9 @@
     ScheduleBackgroundTask();
   }
 
-  // ScheduledNotificationManager::Delegate implementation.
-  void DisplayNotification(std::unique_ptr<NotificationEntry> entry) override {
+  // TODO(xingliu): Tracks each display flow, and call finish callback and
+  // schedule background task after everything is done.
+  void BeforeDisplay(std::unique_ptr<NotificationEntry> entry) {
     if (!entry) {
       DLOG(ERROR) << "Notification entry is null";
       return;
@@ -276,7 +271,9 @@
         std::move(notifications), std::move(client_states), &results);
 
     for (const auto& guid : results) {
-      context_->notification_manager()->DisplayNotification(guid);
+      context_->notification_manager()->DisplayNotification(
+          guid, base::BindOnce(&NotificationSchedulerImpl::BeforeDisplay,
+                               weak_ptr_factory_.GetWeakPtr()));
     }
     stats::LogBackgroundTaskNotificationShown(results.size());
   }
diff --git a/chrome/browser/notifications/scheduler/internal/notification_scheduler_unittest.cc b/chrome/browser/notifications/scheduler/internal/notification_scheduler_unittest.cc
index 73eb675..a35c9185 100644
--- a/chrome/browser/notifications/scheduler/internal/notification_scheduler_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/notification_scheduler_unittest.cc
@@ -48,8 +48,7 @@
         client_(nullptr),
         task_coordinator_(nullptr),
         display_agent_(nullptr),
-        display_decider_(nullptr),
-        notification_manager_delegate_(nullptr) {}
+        display_decider_(nullptr) {}
   ~NotificationSchedulerTest() override = default;
 
   void SetUp() override {
@@ -92,11 +91,9 @@
           std::move(callback).Run(true);
         }));
 
-    EXPECT_CALL(*notification_manager(), Init(_, _))
+    EXPECT_CALL(*notification_manager(), Init(_))
         .WillOnce(
-            Invoke([&](ScheduledNotificationManager::Delegate* delegate,
-                       ScheduledNotificationManager::InitCallback callback) {
-              notification_manager_delegate_ = delegate;
+            Invoke([&](ScheduledNotificationManager::InitCallback callback) {
               std::move(callback).Run(true);
             }));
 
@@ -130,10 +127,6 @@
 
   test::MockDisplayDecider* display_decider() { return display_decider_; }
 
-  ScheduledNotificationManager::Delegate* notification_manager_delegate() {
-    return notification_manager_delegate_;
-  }
-
  private:
   base::test::TaskEnvironment task_environment_;
   NotificationSchedulerClientRegistrar* registrar_;
@@ -144,7 +137,6 @@
   test::MockDisplayAgent* display_agent_;
   test::MockDisplayDecider* display_decider_;
 
-  ScheduledNotificationManager::Delegate* notification_manager_delegate_;
 
   std::unique_ptr<NotificationScheduler> notification_scheduler_;
   DISALLOW_COPY_AND_ASSIGN(NotificationSchedulerTest);
@@ -164,7 +156,7 @@
         std::move(callback).Run(false);
       }));
 
-  EXPECT_CALL(*notification_manager(), Init(_, _)).Times(0);
+  EXPECT_CALL(*notification_manager(), Init(_)).Times(0);
 
   base::RunLoop run_loop;
   scheduler()->Init(
@@ -184,9 +176,8 @@
         std::move(callback).Run(true);
       }));
 
-  EXPECT_CALL(*notification_manager(), Init(_, _))
-      .WillOnce(Invoke([](ScheduledNotificationManager::Delegate* delegate,
-                          ScheduledNotificationManager::InitCallback callback) {
+  EXPECT_CALL(*notification_manager(), Init(_))
+      .WillOnce(Invoke([](ScheduledNotificationManager::InitCallback callback) {
         // Scheduled notification manager failed to load.
         std::move(callback).Run(false);
       }));
@@ -278,7 +269,7 @@
       .WillOnce(SetArgPointee<2>(result));
 
   EXPECT_CALL(*display_agent(), ShowNotification(_, _)).Times(0);
-  EXPECT_CALL(*notification_manager(), DisplayNotification(_)).Times(0);
+  EXPECT_CALL(*notification_manager(), DisplayNotification(_, _)).Times(0);
   EXPECT_CALL(*task_coordinator(), ScheduleBackgroundTask(_, _));
 
   scheduler()->OnStartTask(SchedulerTaskTime::kMorning, base::DoNothing());
@@ -310,10 +301,12 @@
   DisplayDecider::Results result({kGuid});
   EXPECT_CALL(*display_decider(), FindNotificationsToShow(_, _, _))
       .WillOnce(SetArgPointee<2>(result));
-  EXPECT_CALL(*notification_manager(), DisplayNotification(_))
-      .WillOnce(InvokeWithoutArgs([&]() {
-        notification_manager_delegate()->DisplayNotification(std::move(entry));
-      }));
+  EXPECT_CALL(*notification_manager(), DisplayNotification(_, _))
+      .WillOnce(
+          Invoke([&](const std::string& guid,
+                     ScheduledNotificationManager::DisplayCallback callback) {
+            std::move(callback).Run(std::move(entry));
+          }));
 
   EXPECT_CALL(*client(), BeforeShowNotification(_, _))
       .WillOnce(Invoke(
@@ -342,10 +335,12 @@
   DisplayDecider::Results result({kGuid});
   EXPECT_CALL(*display_decider(), FindNotificationsToShow(_, _, _))
       .WillOnce(SetArgPointee<2>(result));
-  EXPECT_CALL(*notification_manager(), DisplayNotification(_))
-      .WillOnce(InvokeWithoutArgs([&]() {
-        notification_manager_delegate()->DisplayNotification(std::move(entry));
-      }));
+  EXPECT_CALL(*notification_manager(), DisplayNotification(_, _))
+      .WillOnce(
+          Invoke([&](const std::string& guid,
+                     ScheduledNotificationManager::DisplayCallback callback) {
+            std::move(callback).Run(std::move(entry));
+          }));
 
   // The client drops the notification data before showing the notification.
   EXPECT_CALL(*client(), BeforeShowNotification(_, _))
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
index 9017818..c555e3e 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.cc
@@ -78,16 +78,12 @@
       : notification_store_(std::move(notification_store)),
         icon_store_(std::move(icon_store)),
         clients_(clients.begin(), clients.end()),
-        delegate_(nullptr),
         config_(config) {}
 
  private:
   // NotificationManager implementation.
-  void Init(Delegate* delegate, InitCallback callback) override {
-    DCHECK(!delegate_);
-    delegate_ = delegate;
-
-    icon_store_->Init(base::BindOnce(
+  void Init(InitCallback callback) override {
+    icon_store_->InitAndLoadKeys(base::BindOnce(
         &ScheduledNotificationManagerImpl::OnIconStoreInitialized,
         weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
   }
@@ -133,7 +129,8 @@
                        std::move(callback)));
   }
 
-  void DisplayNotification(const std::string& guid) override {
+  void DisplayNotification(const std::string& guid,
+                           DisplayCallback callback) override {
     NotificationEntry* entry = nullptr;
     for (auto it = notifications_.begin(); it != notifications_.end(); it++) {
       if (it->second.count(guid)) {
@@ -142,8 +139,10 @@
       }
     }
 
-    if (!entry)
+    if (!entry) {
+      std::move(callback).Run(nullptr);
       return;
+    }
 
     std::vector<std::string> keys;
     for (const auto& pair : entry->icons_uuid) {
@@ -152,8 +151,8 @@
     icon_store_->LoadIcons(
         std::move(keys),
         base::BindOnce(&ScheduledNotificationManagerImpl::OnIconsLoaded,
-                       weak_ptr_factory_.GetWeakPtr(), entry->type,
-                       entry->guid));
+                       weak_ptr_factory_.GetWeakPtr(), entry->type, entry->guid,
+                       std::move(callback)));
   }
 
   void GetAllNotifications(Notifications* notifications) const override {
@@ -198,10 +197,12 @@
     notifications_.erase(type);
   }
 
-  void OnIconStoreInitialized(InitCallback callback, bool success) {
-    // TODO(xingliu): Load icon store keys and count the number of records.
-    // Delete icons without notification entry associated.
-    stats::LogDbInit(stats::DatabaseType::kIconDb, success, 0 /*entry_count*/);
+  void OnIconStoreInitialized(InitCallback callback,
+                              bool success,
+                              IconStore::LoadedIconKeys loaded_keys) {
+    stats::LogDbInit(stats::DatabaseType::kIconDb, success,
+                     loaded_keys ? loaded_keys->size() : 0);
+
     if (!success) {
       std::move(callback).Run(false);
       return;
@@ -209,11 +210,13 @@
 
     notification_store_->InitAndLoad(base::BindOnce(
         &ScheduledNotificationManagerImpl::OnNotificationStoreInitialized,
-        weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
+        weak_ptr_factory_.GetWeakPtr(), std::move(callback),
+        std::move(loaded_keys)));
   }
 
   void OnNotificationStoreInitialized(
       InitCallback callback,
+      std::unique_ptr<std::vector<std::string>> loaded_icon_keys,
       bool success,
       CollectionStore<NotificationEntry>::Entries entries) {
     stats::LogDbInit(stats::DatabaseType::kNotificationDb, success,
@@ -225,9 +228,32 @@
     }
 
     FilterNotificationEntries(std::move(entries));
+    FilterIconEntries(std::move(loaded_icon_keys));
     std::move(callback).Run(true);
   }
 
+  void FilterIconEntries(
+      std::unique_ptr<std::vector<std::string>> uuids_from_icon_store) {
+    std::unordered_set<std::string> icons_uuid_from_entries;
+    for (const auto& client_pair : notifications_) {
+      for (const auto& notification : client_pair.second) {
+        for (const auto& icon : notification.second->icons_uuid) {
+          icons_uuid_from_entries.emplace(icon.second);
+        }
+      }
+    }
+    std::vector<std::string> icons_to_delete;
+    for (const auto& loaded_icon_key : *uuids_from_icon_store.get()) {
+      if (!base::Contains(icons_uuid_from_entries, loaded_icon_key)) {
+        icons_to_delete.emplace_back(loaded_icon_key);
+      }
+    }
+    icon_store_->DeleteIcons(
+        icons_to_delete,
+        base::BindOnce(&ScheduledNotificationManagerImpl::OnIconDeleted,
+                       weak_ptr_factory_.GetWeakPtr()));
+  }
+
   // Filters and loads notification into memory.
   void FilterNotificationEntries(
       CollectionStore<NotificationEntry>::Entries entries) {
@@ -324,13 +350,16 @@
 
   void OnIconsLoaded(SchedulerClientType client_type,
                      const std::string& guid,
+                     DisplayCallback display_callback,
                      bool success,
                      IconStore::LoadedIconsMap loaded_icons_map) {
     stats::LogDbOperation(stats::DatabaseType::kIconDb, success);
 
     // TODO(hesen): delete notification entry if icons failed to load.
-    if (!success)
+    if (!success) {
+      std::move(display_callback).Run(nullptr);
       return;
+    }
 
     // Delete icons from database.
     std::vector<std::string> icons_to_delete;
@@ -344,6 +373,7 @@
 
     // Can't find the entry.
     if (!FindNotificationEntry(client_type, guid)) {
+      std::move(display_callback).Run(nullptr);
       return;
     }
 
@@ -366,8 +396,7 @@
         base::BindOnce(&ScheduledNotificationManagerImpl::OnNotificationDeleted,
                        weak_ptr_factory_.GetWeakPtr()));
 
-    if (delegate_)
-      delegate_->DisplayNotification(std::move(entry));
+    std::move(display_callback).Run(std::move(entry));
   }
 
   NotificationEntry* FindNotificationEntry(SchedulerClientType type,
@@ -398,7 +427,6 @@
   NotificationStore notification_store_;
   std::unique_ptr<IconStore> icon_store_;
   const std::unordered_set<SchedulerClientType> clients_;
-  Delegate* delegate_;
   std::map<SchedulerClientType,
            std::map<std::string, std::unique_ptr<NotificationEntry>>>
       notifications_;
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
index c557269..9b62e6c 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager.h
@@ -30,20 +30,8 @@
       std::map<SchedulerClientType, std::vector<const NotificationEntry*>>;
   using InitCallback = base::OnceCallback<void(bool)>;
   using ScheduleCallback = base::OnceCallback<void(bool)>;
-
-  // Delegate that receives events from the manager.
-  class Delegate {
-   public:
-    // Displays a notification to the user.
-    virtual void DisplayNotification(
-        std::unique_ptr<NotificationEntry> notification_entry) = 0;
-
-    Delegate() = default;
-    virtual ~Delegate() = default;
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(Delegate);
-  };
+  using DisplayCallback =
+      base::OnceCallback<void(std::unique_ptr<NotificationEntry>)>;
 
   // Creates the instance.
   static std::unique_ptr<ScheduledNotificationManager> Create(
@@ -53,7 +41,7 @@
       const SchedulerConfig& config);
 
   // Initializes the notification store.
-  virtual void Init(Delegate* delegate, InitCallback callback) = 0;
+  virtual void Init(InitCallback callback) = 0;
 
   // Adds a new notification.
   virtual void ScheduleNotification(
@@ -61,8 +49,10 @@
       ScheduleCallback callback) = 0;
 
   // Displays a notification, the scheduled notification will be removed from
-  // storage, then Delegate::DisplayNotification() should be invoked.
-  virtual void DisplayNotification(const std::string& guid) = 0;
+  // storage, then the notification entry will be passed to |callback|. If
+  // failed to load the notification, nullptr will be passed to |callback|.
+  virtual void DisplayNotification(const std::string& guid,
+                                   DisplayCallback callback) = 0;
 
   // Gets all scheduled notifications. For each type, notifications are sorted
   // by creation timestamp.
diff --git a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
index 672b9cb1..213c86f 100644
--- a/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
+++ b/chrome/browser/notifications/scheduler/internal/scheduled_notification_manager_unittest.cc
@@ -29,6 +29,7 @@
 namespace {
 
 const char kGuid[] = "test_guid_1234";
+const char kNonExistentGuid[] = "guid_non_existent";
 const char kTitle[] = "test_title";
 const char kSmallIconUuid[] = "test_small_icon_uuid";
 const char kLargeIconUuid[] = "test_large_icon_uuid";
@@ -42,6 +43,28 @@
   return entry;
 }
 
+// Verifies notification entry is same as expected.
+void VerifyNotificationEntry(const NotificationEntry* entry,
+                             const NotificationEntry* expected) {
+  if (expected == nullptr) {
+    EXPECT_FALSE(entry);
+    return;
+  }
+
+  EXPECT_TRUE(entry);
+  EXPECT_EQ(*entry, *expected);
+  const auto& entry_icons = entry->notification_data.icons;
+  const auto& expected_icons = expected->notification_data.icons;
+  for (const auto& icon : entry_icons) {
+    auto icon_type = icon.first;
+    EXPECT_TRUE(base::Contains(expected_icons, icon_type));
+    EXPECT_EQ(entry_icons.at(icon_type).bitmap.width(),
+              expected_icons.at(icon_type).bitmap.width());
+    EXPECT_EQ(entry_icons.at(icon_type).bitmap.height(),
+              expected_icons.at(icon_type).bitmap.height());
+  }
+}
+
 IconStore::IconTypeBundleMap CreateIcons() {
   IconStore::IconTypeBundleMap result;
   SkBitmap large_icon;
@@ -53,15 +76,6 @@
   return result;
 }
 
-class MockDelegate : public ScheduledNotificationManager::Delegate {
- public:
-  MockDelegate() = default;
-  MOCK_METHOD1(DisplayNotification, void(std::unique_ptr<NotificationEntry>));
-
- private:
-  DISALLOW_COPY_AND_ASSIGN(MockDelegate);
-};
-
 class MockNotificationStore : public CollectionStore<NotificationEntry> {
  public:
   MockNotificationStore() {}
@@ -87,7 +101,7 @@
  public:
   MockIconStore() {}
 
-  MOCK_METHOD1(Init, void(IconStore::InitCallback));
+  MOCK_METHOD1(InitAndLoadKeys, void(IconStore::InitAndLoadKeysCallback));
   MOCK_METHOD2(LoadIcons,
                void(const std::vector<std::string>&,
                     IconStore::LoadIconsCallback));
@@ -108,7 +122,6 @@
   ~ScheduledNotificationManagerTest() override = default;
 
   void SetUp() override {
-    delegate_ = std::make_unique<MockDelegate>();
     auto notification_store = std::make_unique<MockNotificationStore>();
     auto icon_store = std::make_unique<MockIconStore>();
     notification_store_ = notification_store.get();
@@ -123,13 +136,16 @@
   ScheduledNotificationManager* manager() { return manager_.get(); }
   MockNotificationStore* notification_store() { return notification_store_; }
   MockIconStore* icon_store() { return icon_store_; }
-  MockDelegate* delegate() { return delegate_.get(); }
   const SchedulerConfig& config() const { return config_; }
 
   // Initializes the manager with predefined data in the store.
   void InitWithData(std::vector<NotificationEntry> data) {
     Entries entries;
+    auto icon_keys = std::make_unique<std::vector<std::string>>();
     for (auto it = data.begin(); it != data.end(); ++it) {
+      for (const auto& pair : it->icons_uuid) {
+        icon_keys->emplace_back(pair.second);
+      }
       auto entry_ptr = std::make_unique<NotificationEntry>(it->type, it->guid);
       *(entry_ptr.get()) = *it;
       entries.emplace_back(std::move(entry_ptr));
@@ -141,19 +157,21 @@
             Invoke([&entries](base::OnceCallback<void(bool, Entries)> cb) {
               std::move(cb).Run(true, std::move(entries));
             }));
-    EXPECT_CALL(*icon_store(), Init(_))
-        .WillOnce(Invoke([](base::OnceCallback<void(bool)> cb) {
-          std::move(cb).Run(true);
-        }));
+    EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
+        .WillOnce(Invoke(
+            [&icon_keys](
+                base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
+              std::move(cb).Run(true, std::move(icon_keys));
+            }));
+    EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).RetiresOnSaturation();
 
     base::RunLoop loop;
-    manager()->Init(delegate(),
-                    base::BindOnce(
-                        [](base::RepeatingClosure closure, bool success) {
-                          EXPECT_TRUE(success);
-                          std::move(closure).Run();
-                        },
-                        loop.QuitClosure()));
+    manager()->Init(base::BindOnce(
+        [](base::RepeatingClosure closure, bool success) {
+          EXPECT_TRUE(success);
+          std::move(closure).Run();
+        },
+        loop.QuitClosure()));
     loop.Run();
   }
 
@@ -173,9 +191,23 @@
     loop.Run();
   }
 
+  void DisplayNotification(const std::string& guid,
+                           const NotificationEntry* expected_entry) {
+    base::RunLoop loop;
+    auto display_callback = base::BindOnce(
+        [](base::RepeatingClosure quit_closure,
+           const NotificationEntry* expected_entry,
+           std::unique_ptr<NotificationEntry> entry) {
+          VerifyNotificationEntry(entry.get(), expected_entry);
+          quit_closure.Run();
+        },
+        loop.QuitClosure(), expected_entry);
+    manager()->DisplayNotification(guid, std::move(display_callback));
+    loop.Run();
+  }
+
  private:
   base::test::TaskEnvironment task_environment_;
-  std::unique_ptr<MockDelegate> delegate_;
   MockNotificationStore* notification_store_;
   MockIconStore* icon_store_;
   std::vector<SchedulerClientType> clients_;
@@ -193,20 +225,19 @@
         std::move(callback).Run(false, Entries());
       }));
 
-  EXPECT_CALL(*icon_store(), Init(_))
-      .WillOnce(Invoke([](base::OnceCallback<void(bool)> callback) {
-        std::move(callback).Run(true);
-      }));
-
+  EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
+      .WillOnce(Invoke(
+          [](base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
+            std::move(cb).Run(true, nullptr);
+          }));
   base::RunLoop loop;
-  manager()->Init(delegate(),
-                  base::BindOnce(
-                      [](base::RepeatingClosure closure, bool success) {
-                        // Expected to receive error.
-                        EXPECT_FALSE(success);
-                        std::move(closure).Run();
-                      },
-                      loop.QuitClosure()));
+  manager()->Init(base::BindOnce(
+      [](base::RepeatingClosure closure, bool success) {
+        // Expected to receive error.
+        EXPECT_FALSE(success);
+        std::move(closure).Run();
+      },
+      loop.QuitClosure()));
   loop.Run();
 }
 
@@ -218,20 +249,48 @@
             std::move(callback).Run(true, Entries());
           }));
 
-  EXPECT_CALL(*icon_store(), Init(_))
-      .WillOnce(Invoke([](base::OnceCallback<void(bool)> callback) {
-        std::move(callback).Run(false);
+  EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
+      .WillOnce(Invoke(
+          [](base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
+            std::move(cb).Run(false, nullptr);
+          }));
+  base::RunLoop loop;
+  manager()->Init(base::BindOnce(
+      [](base::RepeatingClosure closure, bool success) {
+        // Expected to receive error.
+        EXPECT_FALSE(success);
+        std::move(closure).Run();
+      },
+      loop.QuitClosure()));
+  loop.Run();
+}
+
+// Verify that residual expired icons are deleted when icon database is
+// initialized.
+TEST_F(ScheduledNotificationManagerTest, IconDbInitAndLoadKeys) {
+  EXPECT_CALL(*notification_store(), InitAndLoad(_))
+      .WillOnce(Invoke([](base::OnceCallback<void(bool, Entries)> callback) {
+        std::move(callback).Run(true, Entries());
       }));
 
+  auto icon_keys = std::make_unique<std::vector<std::string>>();
+  auto* icon_keys_ptr = icon_keys.get();
+  icon_keys->emplace_back(kSmallIconUuid);
+  icon_keys->emplace_back(kLargeIconUuid);
+  EXPECT_CALL(*icon_store(), InitAndLoadKeys(_))
+      .WillOnce(Invoke(
+          [&icon_keys](
+              base::OnceCallback<void(bool, IconStore::LoadedIconKeys)> cb) {
+            std::move(cb).Run(true, std::move(icon_keys));
+          }));
+  EXPECT_CALL(*icon_store(), DeleteIcons(*icon_keys_ptr, _));
   base::RunLoop loop;
-  manager()->Init(delegate(),
-                  base::BindOnce(
-                      [](base::RepeatingClosure closure, bool success) {
-                        // Expected to receive error.
-                        EXPECT_FALSE(success);
-                        std::move(closure).Run();
-                      },
-                      loop.QuitClosure()));
+  manager()->Init(base::BindOnce(
+      [](base::RepeatingClosure closure, bool success) {
+        EXPECT_TRUE(success);
+        std::move(closure).Run();
+      },
+      loop.QuitClosure()));
   loop.Run();
 }
 
@@ -317,47 +376,45 @@
   EXPECT_NE(entry->create_time, base::Time());
 }
 
-MATCHER_P(NotificationEntryIs, expected, "") {
-  if (*arg.get() != expected)
-    return false;
-  const auto& arg_icons = arg->notification_data.icons;
-  const auto& expected_icons = expected.notification_data.icons;
-  for (const auto& icon : arg_icons) {
-    auto icon_type = icon.first;
-    if (!base::Contains(expected_icons, icon_type))
-      return false;
-    if (arg_icons.at(icon_type).bitmap.width() !=
-            expected_icons.at(icon_type).bitmap.width() ||
-        arg_icons.at(icon_type).bitmap.height() !=
-            expected_icons.at(icon_type).bitmap.height())
-      return false;
-  }
-  return true;
-}
-
 // Test to display a notification.
 TEST_F(ScheduledNotificationManagerTest, DisplayNotification) {
   auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
   entry.guid = kGuid;
   InitWithData(std::vector<NotificationEntry>({entry}));
 
-  // Verify delegate and dependency call contract.
   EXPECT_CALL(*icon_store(), LoadIcons(_, _))
       .WillOnce(Invoke([](std::vector<std::string> keys,
                           IconStore::LoadIconsCallback callback) {
-        std::move(callback).Run(true, {});
+        std::move(callback).Run(true, IconStore::LoadedIconsMap());
       }));
   EXPECT_CALL(*notification_store(), Delete(kGuid, _));
   EXPECT_CALL(*icon_store(), DeleteIcons(_, _));
-  EXPECT_CALL(*delegate(), DisplayNotification(NotificationEntryIs(entry)));
-  manager()->DisplayNotification(kGuid);
 
-  // Verify in-memory data.
+  DisplayNotification(kGuid, &entry);
+
+  // Before display, the notification is removed from the store.
   ScheduledNotificationManager::Notifications notifications;
   manager()->GetAllNotifications(&notifications);
   EXPECT_TRUE(notifications.empty());
 }
 
+// Test to display non-existing notification.
+TEST_F(ScheduledNotificationManagerTest, DisplayNotificationWithoutEntry) {
+  auto entry = CreateNotificationEntry(SchedulerClientType::kTest1);
+  entry.guid = kGuid;
+  InitWithData(std::vector<NotificationEntry>({entry}));
+
+  EXPECT_CALL(*icon_store(), LoadIcons(_, _)).Times(0);
+  EXPECT_CALL(*notification_store(), Delete(_, _)).Times(0);
+  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).Times(0);
+
+  DisplayNotification(kNonExistentGuid, nullptr);
+
+  ScheduledNotificationManager::Notifications notifications;
+  manager()->GetAllNotifications(&notifications);
+  EXPECT_EQ(*notifications.at(SchedulerClientType::kTest1).back(), entry);
+}
+
 // Verify GetAllNotifications API, the notification should be sorted based on
 // creation timestamp.
 TEST_F(ScheduledNotificationManagerTest, GetAllNotifications) {
@@ -464,7 +521,7 @@
   auto entry4 = CreateNotificationEntry(SchedulerClientType::kTest3);
 
   EXPECT_CALL(*notification_store(), Delete(_, _)).Times(3);
-  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).Times(3);
+  EXPECT_CALL(*icon_store(), DeleteIcons(_, _)).Times(3).RetiresOnSaturation();
   InitWithData(
       std::vector<NotificationEntry>({entry0, entry1, entry2, entry3, entry4}));
   ScheduledNotificationManager::Notifications notifications;
@@ -608,7 +665,6 @@
   InitWithData(std::vector<NotificationEntry>({entry}));
 
   auto icons = CreateIcons();
-  // Verify delegate and dependency call contract.
   EXPECT_CALL(*icon_store(), LoadIcons(_, _))
       .WillOnce(Invoke([&icons](std::vector<std::string> keys,
                                 IconStore::LoadIconsCallback callback) {
@@ -622,9 +678,7 @@
 
   auto expected_entry(entry);
   expected_entry.notification_data.icons = icons;
-  EXPECT_CALL(*delegate(),
-              DisplayNotification(NotificationEntryIs(expected_entry)));
-  manager()->DisplayNotification(kGuid);
+  DisplayNotification(kGuid, &expected_entry);
   // Verify in-memory data.
   ScheduledNotificationManager::Notifications notifications;
   manager()->GetAllNotifications(&notifications);
@@ -639,13 +693,13 @@
   entry.icons_uuid[IconType::kSmallIcon] = kSmallIconUuid;
   InitWithData(std::vector<NotificationEntry>({entry}));
 
-  // Verify delegate and dependency call contract.
   EXPECT_CALL(*icon_store(), LoadIcons(_, _))
       .WillOnce(Invoke([](std::vector<std::string> keys,
                           IconStore::LoadIconsCallback callback) {
         std::move(callback).Run(false, {});
       }));
-  manager()->DisplayNotification(kGuid);
+
+  DisplayNotification(kGuid, nullptr /*expected_entry*/);
 
   // Verify in-memory data.
   ScheduledNotificationManager::Notifications notifications;
diff --git a/chrome/browser/notifications/scheduler/test/mock_scheduled_notification_manager.h b/chrome/browser/notifications/scheduler/test/mock_scheduled_notification_manager.h
index 5a38cbc..eaa0726 100644
--- a/chrome/browser/notifications/scheduler/test/mock_scheduled_notification_manager.h
+++ b/chrome/browser/notifications/scheduler/test/mock_scheduled_notification_manager.h
@@ -17,11 +17,11 @@
   MockScheduledNotificationManager();
   ~MockScheduledNotificationManager() override;
 
-  MOCK_METHOD2(Init, void(Delegate*, base::OnceCallback<void(bool)>));
+  MOCK_METHOD1(Init, void(base::OnceCallback<void(bool)>));
   MOCK_METHOD2(ScheduleNotification,
                void(std::unique_ptr<notifications::NotificationParams>,
                     ScheduleCallback));
-  MOCK_METHOD1(DisplayNotification, void(const std::string&));
+  MOCK_METHOD2(DisplayNotification, void(const std::string&, DisplayCallback));
   MOCK_CONST_METHOD1(GetAllNotifications, void(Notifications*));
   MOCK_CONST_METHOD2(GetNotifications,
                      void(SchedulerClientType,
diff --git a/chrome/browser/payments/has_enrolled_instrument_browsertest.cc b/chrome/browser/payments/has_enrolled_instrument_browsertest.cc
index 3a9479cf..64d95bf7 100644
--- a/chrome/browser/payments/has_enrolled_instrument_browsertest.cc
+++ b/chrome/browser/payments/has_enrolled_instrument_browsertest.cc
@@ -8,6 +8,7 @@
 #include "build/build_config.h"
 #include "chrome/browser/payments/personal_data_manager_test_util.h"
 #include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/payments/payment_request_test_controller.h"
 #include "components/autofill/core/browser/autofill_test_utils.h"
 #include "components/network_session_configurator/common/network_switches.h"
 #include "components/payments/core/features.h"
@@ -19,6 +20,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 
 #if defined(OS_ANDROID)
+#include "chrome/browser/android/chrome_feature_list.h"
 #include "chrome/test/base/android/android_browser_test.h"
 #else
 #include "chrome/test/base/in_process_browser_test.h"
@@ -27,6 +29,16 @@
 namespace payments {
 namespace {
 
+// TODO(https://crbug.com/994799): Unify error messages between desktop and
+// Android.
+const char kNotSupportedMessage[] =
+#if defined(OS_ANDROID)
+    "NotSupportedError: Payment method not supported. "
+#else
+    "NotSupportedError: The payment method \"basic-card\" is not supported. "
+#endif  // OS_ANDROID
+    "User does not have valid information on file.";
+
 autofill::CreditCard GetCardWithBillingAddress(
     const autofill::AutofillProfile& profile) {
   autofill::CreditCard card = autofill::test::GetCreditCard();
@@ -48,15 +60,36 @@
     ASSERT_TRUE(content::NavigateToURL(
         GetActiveWebContents(),
         https_server_.GetURL("/has_enrolled_instrument.html")));
+    test_controller_.SetUpOnMainThread();
     PlatformBrowserTest::SetUpOnMainThread();
   }
 
+  std::unique_ptr<base::test::ScopedFeatureList>
+  EnableStrictHasEnrolledAutofillInstrument() {
+    auto features = std::make_unique<base::test::ScopedFeatureList>();
+    features->InitWithFeatures(
+        /*enabled_features=*/{features::kStrictHasEnrolledAutofillInstrument},
+        /*disabled_features=*/{
+          features::kPaymentRequestSkipToGPay,
+#if defined(OS_ANDROID)
+              ::chrome::android::kNoCreditCardAbort,
+#endif  // OS_ANDROID
+        });
+    return features;
+  }
+
   content::WebContents* GetActiveWebContents() {
     return chrome_test_utils::GetActiveWebContents(this);
   }
 
+  const std::string& not_supported_message() const {
+    return not_supported_message_;
+  }
+
  private:
+  PaymentRequestTestController test_controller_;
   net::EmbeddedTestServer https_server_;
+  std::string not_supported_message_ = kNotSupportedMessage;
 
   DISALLOW_COPY_AND_ASSIGN(HasEnrolledInstrumentTest);
 };
@@ -71,17 +104,27 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
+                                     "hasEnrolledInstrument()"));
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, NoBillingAddress) {
@@ -97,17 +140,27 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
+                                     "hasEnrolledInstrument()"));
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest,
@@ -126,17 +179,27 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
+                                     "hasEnrolledInstrument()"));
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest,
@@ -156,17 +219,18 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
+                                    "hasEnrolledInstrument()"));
+    EXPECT_EQ(true,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(true, content::EvalJs(
+                        GetActiveWebContents(),
+                        "hasEnrolledInstrument({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, InvalidCardNumber) {
@@ -187,17 +251,27 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
+                                     "hasEnrolledInstrument()"));
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, ExpiredCard) {
@@ -217,17 +291,27 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
+                                     "hasEnrolledInstrument()"));
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 // TODO(https://crbug.com/994799): Unify autofill data validation and returned
@@ -254,26 +338,40 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  bool is_no_name_billing_address_valid =
+// TODO(https://crbug.com/994799): Unify autofill data requirements between
+// desktop and Android.
 #if defined(OS_ANDROID)
-      // Android requires the billing address to have a name.
-      false;
+    // Android requires the billing address to have a name.
+    bool is_no_name_billing_address_valid = false;
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
 #else
-      // Desktop does not require the billing address to have a name.
-      true;
-#endif
-  EXPECT_EQ(is_no_name_billing_address_valid,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  // Shipping address requires recipient name on all platforms.
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(is_no_name_billing_address_valid,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    // Desktop does not require the billing address to have a name.
+    bool is_no_name_billing_address_valid = true;
+#endif  // OS_ANDROID
+
+    EXPECT_EQ(
+        is_no_name_billing_address_valid,
+        content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+    EXPECT_EQ(
+        is_no_name_billing_address_valid,
+        content::EvalJs(GetActiveWebContents(),
+                        "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    // Shipping address requires recipient name on all platforms.
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest,
@@ -295,18 +393,28 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
+    EXPECT_EQ(false, content::EvalJs(GetActiveWebContents(),
+                                     "hasEnrolledInstrument()"));
 
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(false,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(), "show()"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestShipping:true})"));
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, NoEmailAddress) {
@@ -326,17 +434,23 @@
   EXPECT_EQ(true,
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
 
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
+
+    EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
+                                    "hasEnrolledInstrument()"));
+    EXPECT_EQ(true,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 IN_PROC_BROWSER_TEST_F(HasEnrolledInstrumentTest, InvalidEmailAddress) {
@@ -357,17 +471,22 @@
             content::EvalJs(GetActiveWebContents(),
                             "hasEnrolledInstrument({requestPayerEmail:true})"));
 
-  base::test::ScopedFeatureList features;
-  features.InitAndEnableFeature(features::kStrictHasEnrolledAutofillInstrument);
+  {
+    auto features = EnableStrictHasEnrolledAutofillInstrument();
 
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(), "hasEnrolledInstrument()"));
-  EXPECT_EQ(true,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestShipping:true})"));
-  EXPECT_EQ(false,
-            content::EvalJs(GetActiveWebContents(),
-                            "hasEnrolledInstrument({requestPayerEmail:true})"));
+    EXPECT_EQ(true, content::EvalJs(GetActiveWebContents(),
+                                    "hasEnrolledInstrument()"));
+    EXPECT_EQ(true,
+              content::EvalJs(GetActiveWebContents(),
+                              "hasEnrolledInstrument({requestShipping:true})"));
+    EXPECT_EQ(false, content::EvalJs(
+                         GetActiveWebContents(),
+                         "hasEnrolledInstrument({requestPayerEmail:true})"));
+
+    EXPECT_EQ(not_supported_message(),
+              content::EvalJs(GetActiveWebContents(),
+                              "show({requestPayerEmail:true})"));
+  }
 }
 
 }  // namespace
diff --git a/chrome/browser/payments/payment_handler_exploit_browsertest.cc b/chrome/browser/payments/payment_handler_exploit_browsertest.cc
index 2c06922..b34b483 100644
--- a/chrome/browser/payments/payment_handler_exploit_browsertest.cc
+++ b/chrome/browser/payments/payment_handler_exploit_browsertest.cc
@@ -5,6 +5,7 @@
 #include "base/macros.h"
 #include "build/build_config.h"
 #include "chrome/test/base/chrome_test_utils.h"
+#include "chrome/test/payments/payment_request_test_controller.h"
 #include "content/public/browser/render_process_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test_utils.h"
@@ -19,10 +20,7 @@
 #if defined(OS_ANDROID)
 #include "chrome/test/base/android/android_browser_test.h"
 #else
-#include "chrome/browser/payments/chrome_payment_request_delegate.h"
-#include "chrome/browser/payments/payment_request_factory.h"
 #include "chrome/test/base/in_process_browser_test.h"
-#include "components/payments/content/payment_request_web_contents_manager.h"
 #endif
 
 namespace payments {
@@ -49,36 +47,6 @@
   }
 
  private:
-#if !defined(OS_ANDROID)
-  // A delegate for PaymentRequest that mimics the browser window always being
-  // active, which is a requirement for launching PaymentRequest, but is false
-  // in MacOS browser tests.
-  class TestDelegate : public ChromePaymentRequestDelegate {
-   public:
-    explicit TestDelegate(content::WebContents* web_contents)
-        : ChromePaymentRequestDelegate(web_contents) {}
-    ~TestDelegate() override {}
-
-    // ChromePaymentRequestDelegate:
-    bool IsBrowserWindowActive() const override { return true; }
-
-   private:
-    DISALLOW_COPY_AND_ASSIGN(TestDelegate);
-  };
-
-  void CreatePaymentRequestForTest(
-      payments::mojom::PaymentRequestRequest request,
-      content::RenderFrameHost* render_frame_host) {
-    auto* web_contents =
-        content::WebContents::FromRenderFrameHost(render_frame_host);
-    PaymentRequestWebContentsManager::GetOrCreateForWebContents(web_contents)
-        ->CreatePaymentRequest(render_frame_host, web_contents,
-                               std::make_unique<TestDelegate>(web_contents),
-                               std::move(request),
-                               /*observer_for_testing=*/nullptr);
-  }
-#endif  // !OS_ANDROID
-
   // PlatformBrowserTest:
   void SetUpOnMainThread() override {
     https_server_.ServeFilesFromSourceDirectory(
@@ -86,14 +54,7 @@
     ASSERT_TRUE(https_server_.Start());
     ASSERT_TRUE(content::NavigateToURL(
         GetActiveWebContents(), GetTestServerUrl("/payment_handler.html")));
-
-#if !defined(OS_ANDROID)
-    // Use TestDelegate that helps with MacOS browser test environment:
-    payments::SetPaymentRequestFactoryForTesting(base::BindRepeating(
-        &PaymentHandlerExploitTest::CreatePaymentRequestForTest,
-        base::Unretained(this)));
-#endif  // !OS_ANDROID
-
+    test_controller_.SetUpOnMainThread();
     PlatformBrowserTest::SetUpOnMainThread();
   }
 
@@ -105,6 +66,7 @@
 
   GURL payment_handler_window_url_;
   net::EmbeddedTestServer https_server_;
+  PaymentRequestTestController test_controller_;
 
   DISALLOW_COPY_AND_ASSIGN(PaymentHandlerExploitTest);
 };
diff --git a/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc b/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc
index b8bf9c6..000b8525 100644
--- a/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc
+++ b/chrome/browser/payments/payment_request_can_make_payment_browsertest.cc
@@ -50,7 +50,9 @@
     ABORT_CALLED,
   };
 
-  PaymentRequestCanMakePaymentTestBase() : payment_request_controller_(this) {}
+  PaymentRequestCanMakePaymentTestBase() {
+    payment_request_controller_.SetObserver(this);
+  }
 
   void SetUpCommandLine(base::CommandLine* command_line) override {
     // HTTPS server only serves a valid cert for localhost, so this is needed to
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index f592c97..2bdbd99 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -126,7 +126,6 @@
 #include "components/search_engines/template_url_prepopulate_data.h"
 #include "components/sessions/core/session_id_generator.h"
 #include "components/signin/public/identity_manager/identity_manager.h"
-#include "components/startup_metric_utils/browser/startup_metric_utils.h"
 #include "components/subresource_filter/content/browser/ruleset_service.h"
 #include "components/sync/base/sync_prefs.h"
 #include "components/sync_device_info/device_info_prefs.h"
@@ -489,6 +488,9 @@
     "extension_updates.insecure_extension_updates_enabled";
 
 const char kLastStartupTimestamp[] = "startup_metric.last_startup_timestamp";
+const char kLastStartupVersion[] = "startup_metric.last_startup_version";
+const char kSameVersionStartupCount[] =
+    "startup_metric.same_version_startup_count";
 
 // Deprecated 8/2019
 const char kHintLoadedCounts[] = "optimization_guide.hint_loaded_counts";
@@ -603,7 +605,6 @@
   secure_origin_whitelist::RegisterPrefs(registry);
   sessions::SessionIdGenerator::RegisterPrefs(registry);
   SSLConfigServiceManager::RegisterPrefs(registry);
-  startup_metric_utils::RegisterPrefs(registry);
   subresource_filter::IndexedRulesetVersion::RegisterPrefs(registry);
   SystemNetworkContextManager::RegisterPrefs(registry);
   update_client::RegisterPrefs(registry);
@@ -727,6 +728,8 @@
   registry->RegisterBooleanPref(kNtpActivateHideShortcutsFieldTrial, false);
 #endif  // !defined(OS_ANDROID)
   registry->RegisterInt64Pref(kLastStartupTimestamp, 0);
+  registry->RegisterStringPref(kLastStartupVersion, std::string());
+  registry->RegisterIntegerPref(kSameVersionStartupCount, 0);
 
 #if defined(TOOLKIT_VIEWS)
   RegisterBrowserViewLocalPrefs(registry);
@@ -1049,6 +1052,8 @@
 
   // Added 8/2019.
   local_state->ClearPref(kLastStartupTimestamp);
+  local_state->ClearPref(kLastStartupVersion);
+  local_state->ClearPref(kSameVersionStartupCount);
 }
 
 // This method should be periodically pruned of year+ old migrations.
diff --git a/chrome/browser/resources/BUILD.gn b/chrome/browser/resources/BUILD.gn
index d1b6a8b..3fab0dd 100644
--- a/chrome/browser/resources/BUILD.gn
+++ b/chrome/browser/resources/BUILD.gn
@@ -224,6 +224,17 @@
   grit("welcome_resources") {
     source = "welcome/welcome_resources.grd"
 
+    # The .grd contains references to generated files.
+    source_is_generated = true
+
+    deps = [
+      "//chrome/browser/resources/welcome:polymer3_elements",
+    ]
+    grit_flags = [
+      "-E",
+      "root_gen_dir=" + rebase_path(root_gen_dir, root_build_dir),
+    ]
+
     defines = chrome_grit_defines
     outputs = [
       "grit/welcome_resources.h",
diff --git a/chrome/browser/resources/chromeos/login/security_token_pin.html b/chrome/browser/resources/chromeos/login/security_token_pin.html
index 21ca735..d1f2b8f 100644
--- a/chrome/browser/resources/chromeos/login/security_token_pin.html
+++ b/chrome/browser/resources/chromeos/login/security_token_pin.html
@@ -61,6 +61,8 @@
         <div id="pinKeyboardContainer">
           <pin-keyboard id="pinKeyboard"
               has-error="[[hasError_(parameters, userEdited_)]]"
+              aria-label="[[getAriaLabel_(locale, parameters, errorLabelId_,
+                                          userEdited_)]]"
               on-pin-change="onPinChange_">
             <div id="errorContainer" problem
                 hidden="[[!hasError_(parameters, userEdited_)]]">
diff --git a/chrome/browser/resources/chromeos/login/security_token_pin.js b/chrome/browser/resources/chromeos/login/security_token_pin.js
index 3d9cd29..d3cf118 100644
--- a/chrome/browser/resources/chromeos/login/security_token_pin.js
+++ b/chrome/browser/resources/chromeos/login/security_token_pin.js
@@ -161,4 +161,28 @@
   isAttemptsLeftVisible_: function(parameters) {
     return parameters && parameters.attemptsLeft != -1;
   },
+
+  /**
+   * Returns the aria label to be used for the PIN input field.
+   * @param {string} locale
+   * @param {OobeTypes.SecurityTokenPinDialogParameters} parameters
+   * @param {string} errorLabelId
+   * @param {boolean} userEdited
+   * @return {string}
+   * @private
+   */
+  getAriaLabel_: function(locale, parameters, errorLabelId, userEdited) {
+    var pieces = [];
+    if (this.isErrorLabelVisible_(errorLabelId, userEdited)) {
+      pieces.push(this.i18n(errorLabelId));
+      pieces.push(this.i18n('securityTokenPinDialogTryAgain'));
+    }
+    if (this.isAttemptsLeftVisible_(parameters)) {
+      pieces.push(this.i18n(
+          'securityTokenPinDialogAttemptsLeft', parameters.attemptsLeft));
+    }
+    // Note: The language direction is not taken into account here, since the
+    // order of pieces follows the reading order.
+    return pieces.join(' ');
+  },
 });
diff --git a/chrome/browser/resources/local_ntp/customize.js b/chrome/browser/resources/local_ntp/customize.js
index 3e0beb3f..ce243db 100644
--- a/chrome/browser/resources/local_ntp/customize.js
+++ b/chrome/browser/resources/local_ntp/customize.js
@@ -170,6 +170,7 @@
   COLLECTION_TILE: 'bg-sel-tile',  // Preview tile for background customization
   COLLECTION_TILE_BG: 'bg-sel-tile-bg',
   COLLECTION_TITLE: 'bg-sel-tile-title',  // Title of a background image
+  HIDDEN_SELECTED: 'hidden-selected',
   IMAGE_DIALOG: 'is-img-sel',
   ON_IMAGE_MENU: 'on-img-menu',
   OPTION: 'bg-option',
@@ -1032,6 +1033,8 @@
 /**
  * Handles hide shortcuts toggle. Apply/remove styling for the toggle and
  * enable/disable the done button.
+ * Note: If the toggle is enabled, the options for shortcut type will appear
+ * "disabled".
  * @param {boolean} areHidden True if the shortcuts are hidden, i.e. the toggle
  *     is on.
  */
@@ -1040,6 +1043,8 @@
   $(customize.IDS.SHORTCUTS_HIDE)
       .classList.toggle(customize.CLASSES.SELECTED, areHidden);
   $(customize.IDS.SHORTCUTS_HIDE_TOGGLE).checked = areHidden;
+  $(customize.IDS.SHORTCUTS_MENU)
+      .classList.toggle(customize.CLASSES.HIDDEN_SELECTED, areHidden);
 
   customize.selectedOptions.shortcutsAreHidden = areHidden;
 };
@@ -2027,6 +2032,8 @@
           customize.LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_CUSTOM_LINKS_CLICKED);
     }
     customize.richerPicker_selectShortcutType(clOption);
+    // Selecting a shortcut type will turn off hidden shortcuts.
+    customize.richerPicker_toggleShortcutHide(false);
   };
   clOption.onkeydown = function(event) {
     if (customize.arrowKeys.includes(event.keyCode)) {
@@ -2053,6 +2060,8 @@
           customize.LOG_TYPE.NTP_CUSTOMIZE_SHORTCUT_MOST_VISITED_CLICKED);
     }
     customize.richerPicker_selectShortcutType(mvOption);
+    // Selecting a shortcut type will turn off hidden shortcuts.
+    customize.richerPicker_toggleShortcutHide(false);
   };
   mvOption.onkeydown = function(event) {
     if (customize.arrowKeys.includes(event.keyCode)) {
diff --git a/chrome/browser/resources/local_ntp/local_ntp.css b/chrome/browser/resources/local_ntp/local_ntp.css
index fb40888..30c91911 100644
--- a/chrome/browser/resources/local_ntp/local_ntp.css
+++ b/chrome/browser/resources/local_ntp/local_ntp.css
@@ -1368,6 +1368,10 @@
   width: 268px;
 }
 
+#shortcuts-menu.hidden-selected .sh-option {
+  color: rgb(var(--GG500-rgb));
+}
+
 .sh-option-image {
   border: 1px solid rgb(var(--GG300-rgb));
   border-radius: 4px;
@@ -1384,13 +1388,13 @@
   }
 }
 
-.selected .sh-option-image {
+#shortcuts-menu:not(.hidden-selected) .selected .sh-option-image {
   background-color: rgb(var(--GB050-rgb));
   border-color: rgb(var(--GB600-rgb));
 }
 
 @media (prefers-color-scheme: dark) {
-  .selected .sh-option-image {
+  #shortcuts-menu:not(.hidden-selected) .selected .sh-option-image {
     background-color: rgba(var(--GB200-rgb), .1);
     border-color: rgb(var(--GB300-rgb));
   }
@@ -1420,13 +1424,13 @@
   right: unset;
 }
 
-.selected .sh-option-mini {
+#shortcuts-menu:not(.hidden-selected) .selected .sh-option-mini {
   box-shadow: 0 1px 3px 0 rgba(var(--GG800-rgb), .3),
       0 4px 8px 3px rgba(var(--GG800-rgb), .15);
 }
 
 @media (prefers-color-scheme: dark) {
-  .selected .sh-option-mini {
+  #shortcuts-menu:not(.hidden-selected) .selected .sh-option-mini {
     border-color: transparent;
     box-shadow: var(--dark-mode-shadow);
   }
@@ -1436,12 +1440,12 @@
   background-color: rgb(var(--GG500-rgb));
 }
 
-.selected .sh-option-mini .mini-shortcuts {
+#shortcuts-menu:not(.hidden-selected) .selected .mini-shortcuts {
   background-color: rgb(var(--GB600-rgb));
 }
 
 @media (prefers-color-scheme: dark) {
-  .selected .sh-option-mini .mini-shortcuts {
+  #shortcuts-menu:not(.hidden-selected) .selected .mini-shortcuts {
     background-color: rgb(var(--GB300-rgb));
   }
 }
@@ -1475,12 +1479,12 @@
   }
 }
 
-.selected .sh-option-icon {
+#shortcuts-menu:not(.hidden-selected) .selected .sh-option-icon {
   background-color: rgba(var(--GB600-rgb), .24);
 }
 
 @media (prefers-color-scheme: dark) {
-  .selected .sh-option-icon {
+  #shortcuts-menu:not(.hidden-selected) .selected .sh-option-icon {
     background-color: rgb(var(--GB300-rgb));
   }
 }
@@ -1510,6 +1514,11 @@
   right: initial;
 }
 
+#shortcuts-menu.hidden-selected .sh-option-image
+    :-webkit-any(.selected-circle, .selected-check) {
+  display: none;
+}
+
 .sh-option-title {
   font-weight: bold;
   margin: 8px 0;
diff --git a/chrome/browser/resources/welcome/BUILD.gn b/chrome/browser/resources/welcome/BUILD.gn
index ad2a0c5..a01d9fc 100644
--- a/chrome/browser/resources/welcome/BUILD.gn
+++ b/chrome/browser/resources/welcome/BUILD.gn
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
 
 group("closure_compile") {
   deps = [
@@ -14,6 +15,11 @@
 }
 
 js_type_check("welcome_files") {
+  is_polymer3 = true
+  closure_flags = default_closure_args + [
+                    "js_module_root=../../chrome/browser/resources/welcome/",
+                    "js_module_root=./gen/chrome/browser/resources/welcome/",
+                  ]
   deps = [
     ":landing_view",
     ":signin_view",
@@ -22,57 +28,103 @@
 }
 
 js_library("landing_view") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/landing_view.js",
+  ]
   deps = [
     ":landing_view_proxy",
     ":navigation_behavior",
     ":welcome_browser_proxy",
-    "//ui/webui/resources/js:load_time_data",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:load_time_data.m",
   ]
+  extra_deps = [ ":landing_view_module" ]
 }
 
 js_library("landing_view_proxy") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
 
 js_library("signin_view") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/signin_view.js",
+  ]
   deps = [
     ":navigation_behavior",
     ":signin_view_proxy",
     ":welcome_browser_proxy",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
   ]
+  extra_deps = [ ":signin_view_module" ]
 }
 
 js_library("signin_view_proxy") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
 
 js_library("navigation_behavior") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:assert.m",
   ]
 }
 
 js_library("welcome_app") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/welcome_app.js",
+  ]
   deps = [
     ":navigation_behavior",
     ":welcome_browser_proxy",
     "./set_as_default/:nux_set_as_default_proxy",
     "./shared:bookmark_proxy",
     "./shared:nux_types",
-    "//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager",
-    "//ui/webui/resources/js:load_time_data",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/cr_elements/cr_view_manager:cr_view_manager.m",
+    "//ui/webui/resources/js:load_time_data.m",
   ]
+  extra_deps = [ ":welcome_app_module" ]
 }
 
 js_library("welcome_browser_proxy") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
+
+polymer_modulizer("welcome_app") {
+  js_file = "welcome_app.js"
+  html_file = "welcome_app.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("landing_view") {
+  js_file = "landing_view.js"
+  html_file = "landing_view.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("signin_view") {
+  js_file = "signin_view.js"
+  html_file = "signin_view.html"
+  html_type = "v3-ready"
+}
+
+group("polymer3_elements") {
+  deps = [
+    ":landing_view_module",
+    ":signin_view_module",
+    ":welcome_app_module",
+    "./google_apps:nux_google_apps_module",
+    "./ntp_background:nux_ntp_background_module",
+    "./set_as_default:nux_set_as_default_module",
+    "./shared:polymer3_elements",
+  ]
+}
diff --git a/chrome/browser/resources/welcome/google_apps/BUILD.gn b/chrome/browser/resources/welcome/google_apps/BUILD.gn
index 6566c5e..8434c2f 100644
--- a/chrome/browser/resources/welcome/google_apps/BUILD.gn
+++ b/chrome/browser/resources/welcome/google_apps/BUILD.gn
@@ -3,8 +3,14 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
 
 js_type_check("closure_compile") {
+  closure_flags = default_closure_args + [
+                    "js_module_root=../../chrome/browser/resources/welcome/",
+                    "js_module_root=gen/chrome/browser/resources/welcome/",
+                  ]
+  is_polymer3 = true
   deps = [
     ":google_app_proxy",
     ":google_apps_metrics_proxy",
@@ -13,6 +19,9 @@
 }
 
 js_library("nux_google_apps") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/google_apps/nux_google_apps.js",
+  ]
   deps = [
     ":google_app_proxy",
     ":google_apps_metrics_proxy",
@@ -20,16 +29,18 @@
     "../shared:bookmark_proxy",
     "../shared:nux_types",
     "../shared:step_indicator",
-    "//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted",
-    "//ui/webui/resources/js:cr",
-    "//ui/webui/resources/js:i18n_behavior",
-    "//ui/webui/resources/js:util",
+    "//third_party/polymer/v3_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:util.m",
   ]
+  extra_deps = [ ":nux_google_apps_module" ]
 }
 
 js_library("google_app_proxy") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
@@ -39,3 +50,9 @@
     "../shared:module_metrics_proxy",
   ]
 }
+
+polymer_modulizer("nux_google_apps") {
+  js_file = "nux_google_apps.js"
+  html_file = "nux_google_apps.html"
+  html_type = "v3-ready"
+}
diff --git a/chrome/browser/resources/welcome/google_apps/google_app_proxy.html b/chrome/browser/resources/welcome/google_apps/google_app_proxy.html
deleted file mode 100644
index 3c50ce8..0000000
--- a/chrome/browser/resources/welcome/google_apps/google_app_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="google_app_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/google_apps/google_app_proxy.js b/chrome/browser/resources/welcome/google_apps/google_app_proxy.js
index b4edc67..63d890d8 100644
--- a/chrome/browser/resources/welcome/google_apps/google_app_proxy.js
+++ b/chrome/browser/resources/welcome/google_apps/google_app_proxy.js
@@ -2,69 +2,65 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {BookmarkListItem} from '../shared/nux_types.js';
+
+/**
+ * NuxGoogleAppsSelections enum.
+ * These values are persisted to logs and should not be renumbered or
+ * re-used.
+ * See tools/metrics/histograms/enums.xml.
+ * @enum {number}
+ */
+const NuxGoogleAppsSelections = {
+  GMAIL_DEPRECATED: 0,
+  YOU_TUBE: 1,
+  MAPS: 2,
+  TRANSLATE: 3,
+  NEWS: 4,
+  CHROME_WEB_STORE: 5,
+};
+
+/** @interface */
+export class GoogleAppProxy {
   /**
-   * NuxGoogleAppsSelections enum.
-   * These values are persisted to logs and should not be renumbered or
-   * re-used.
-   * See tools/metrics/histograms/enums.xml.
-   * @enum {number}
+   * Google app IDs are local to the list of Google apps, so their icon must
+   * be cached by the handler that provided the IDs.
+   * @param {number} appId
    */
-  const NuxGoogleAppsSelections = {
-    GMAIL_DEPRECATED: 0,
-    YOU_TUBE: 1,
-    MAPS: 2,
-    TRANSLATE: 3,
-    NEWS: 4,
-    CHROME_WEB_STORE: 5,
-  };
+  cacheBookmarkIcon(appId) {}
 
-  /** @interface */
-  class GoogleAppProxy {
-    /**
-     * Google app IDs are local to the list of Google apps, so their icon must
-     * be cached by the handler that provided the IDs.
-     * @param {number} appId
-     */
-    cacheBookmarkIcon(appId) {}
+  /**
+   * Returns a promise for an array of Google apps.
+   * @return {!Promise<!Array<!BookmarkListItem>>}
+   */
+  getAppList() {}
 
-    /**
-     * Returns a promise for an array of Google apps.
-     * @return {!Promise<!Array<!welcome.BookmarkListItem>>}
-     */
-    getAppList() {}
+  /**
+   * @param {number} providerId This should match one of the histogram enum
+   *     value for NuxGoogleAppsSelections.
+   */
+  recordProviderSelected(providerId) {}
+}
 
-    /**
-     * @param {number} providerId This should match one of the histogram enum
-     *     value for NuxGoogleAppsSelections.
-     */
-    recordProviderSelected(providerId) {}
+/** @implements {GoogleAppProxy} */
+export class GoogleAppProxyImpl {
+  /** @override */
+  cacheBookmarkIcon(appId) {
+    chrome.send('cacheGoogleAppIcon', [appId]);
   }
 
-  /** @implements {welcome.GoogleAppProxy} */
-  class GoogleAppProxyImpl {
-    /** @override */
-    cacheBookmarkIcon(appId) {
-      chrome.send('cacheGoogleAppIcon', [appId]);
-    }
-
-    /** @override */
-    getAppList() {
-      return cr.sendWithPromise('getGoogleAppsList');
-    }
-
-    /** @override */
-    recordProviderSelected(providerId) {
-      chrome.metricsPrivate.recordEnumerationValue(
-          'FirstRun.NewUserExperience.GoogleAppsSelection', providerId,
-          Object.keys(NuxGoogleAppsSelections).length);
-    }
+  /** @override */
+  getAppList() {
+    return sendWithPromise('getGoogleAppsList');
   }
 
-  cr.addSingletonGetter(GoogleAppProxyImpl);
+  /** @override */
+  recordProviderSelected(providerId) {
+    chrome.metricsPrivate.recordEnumerationValue(
+        'FirstRun.NewUserExperience.GoogleAppsSelection', providerId,
+        Object.keys(NuxGoogleAppsSelections).length);
+  }
+}
 
-  return {
-    GoogleAppProxy: GoogleAppProxy,
-    GoogleAppProxyImpl: GoogleAppProxyImpl,
-  };
-});
+addSingletonGetter(GoogleAppProxyImpl);
diff --git a/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.html b/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.html
deleted file mode 100644
index 222483c..0000000
--- a/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="../shared/module_metrics_proxy.html">
-<script src="google_apps_metrics_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.js b/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.js
index 77886462..529d4d63 100644
--- a/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.js
+++ b/chrome/browser/resources/welcome/google_apps/google_apps_metrics_proxy.js
@@ -2,18 +2,15 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
-  class GoogleAppsMetricsProxyImpl extends welcome.ModuleMetricsProxyImpl {
-    constructor() {
-      super(
-          'FirstRun.NewUserExperience.GoogleAppsInteraction',
-          welcome.NuxGoogleAppsInteractions);
-    }
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+import {ModuleMetricsProxyImpl, NuxGoogleAppsInteractions} from '../shared/module_metrics_proxy.js';
+
+export class GoogleAppsMetricsProxyImpl extends ModuleMetricsProxyImpl {
+  constructor() {
+    super(
+        'FirstRun.NewUserExperience.GoogleAppsInteraction',
+        NuxGoogleAppsInteractions);
   }
+}
 
-  cr.addSingletonGetter(GoogleAppsMetricsProxyImpl);
-
-  return {
-    GoogleAppsMetricsProxyImpl: GoogleAppsMetricsProxyImpl,
-  };
-});
+addSingletonGetter(GoogleAppsMetricsProxyImpl);
diff --git a/chrome/browser/resources/welcome/google_apps/nux_google_apps.html b/chrome/browser/resources/welcome/google_apps/nux_google_apps.html
index fd03465..c5f78a59 100644
--- a/chrome/browser/resources/welcome/google_apps/nux_google_apps.html
+++ b/chrome/browser/resources/welcome/google_apps/nux_google_apps.html
@@ -1,214 +1,190 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="animations chooser-shared-css">
+  .apps-ask {
+    text-align: center;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../navigation_behavior.html">
-<link rel="import" href="../shared/animations_css.html">
-<link rel="import" href="../shared/bookmark_proxy.html">
-<link rel="import" href="../shared/chooser_shared_css.html">
-<link rel="import" href="../shared/i18n_setup.html">
-<link rel="import" href="../shared/step_indicator.html">
-<link rel="import" href="google_app_proxy.html">
-<link rel="import" href="google_apps_metrics_proxy.html">
+  .chrome-logo {
+    content: url(../images/module_icons/google_light.svg);
+    height: 38px;
+    margin: auto;
+    margin-bottom: 16px;
+    width: 42px;
+  }
 
-<dom-module id="nux-google-apps">
-  <template>
-    <style include="animations chooser-shared-css">
-      .apps-ask {
-        text-align: center;
-      }
+  @media (prefers-color-scheme: dark) {
+    .chrome-logo {
+      content: url(../images/module_icons/google_dark.svg);
+    }
+  }
 
-      .chrome-logo {
-        content: url(../images/module_icons/google_light.svg);
-        height: 38px;
-        margin: auto;
-        margin-bottom: 16px;
-        width: 42px;
-      }
+  h1 {
+    color: var(--cr-primary-text-color);
+    font-size: 1.5rem;
+    font-weight: 500;
+    margin: 0;
+    margin-bottom: 48px;
+    outline: none;
+  }
 
-      @media (prefers-color-scheme: dark) {
-        .chrome-logo {
-          content: url(../images/module_icons/google_dark.svg);
-        }
-      }
+  #appChooser {
+    display: block;
+    white-space: nowrap;
+  }
 
-      h1 {
-        color: var(--cr-primary-text-color);
-        font-size: 1.5rem;
-        font-weight: 500;
-        margin: 0;
-        margin-bottom: 48px;
-        outline: none;
-      }
+  .button-bar {
+    margin-top: 4rem;
+  }
 
-      #appChooser {
-        display: block;
-        white-space: nowrap;
-      }
+  .option {
+    -webkit-appearance: none;
+    align-items: center;
+    border-radius: 8px;
+    box-sizing: border-box;
+    display: inline-flex;
+    font-family: inherit;
+    height: 7.5rem;
+    justify-content: center;
+    outline: 0;
+    position: relative;
+    transition-duration: 500ms;
+    transition-property: box-shadow;
+    vertical-align: bottom;
+    width: 6.25rem;
+  }
 
-      .button-bar {
-        margin-top: 4rem;
-      }
+  .option:not(:first-of-type) {
+    margin-inline-start: 1.5rem;
+  }
 
-      .option {
-        -webkit-appearance: none;
-        align-items: center;
-        border-radius: 8px;
-        box-sizing: border-box;
-        display: inline-flex;
-        font-family: inherit;
-        height: 7.5rem;
-        justify-content: center;
-        outline: 0;
-        position: relative;
-        transition-duration: 500ms;
-        transition-property: box-shadow;
-        vertical-align: bottom;
-        width: 6.25rem;
-      }
+  .option[active] {
+    border: 1px solid var(--cr-checked-color);
+    color: var(--cr-checked-color);
+    font-weight: 500;
+  }
 
-      .option:not(:first-of-type) {
-        margin-inline-start: 1.5rem;
-      }
+  .option.keyboard-focused:focus {
+    outline: var(--navi-keyboard-focus-color) solid 3px;
+  }
 
-      .option[active] {
-        border: 1px solid var(--cr-checked-color);
-        color: var(--cr-checked-color);
-        font-weight: 500;
-      }
+  .option-name {
+    flex-grow: 0;
+    line-height: 1.25rem;
+    text-align: center;
+    white-space: normal;
+  }
 
-      .option.keyboard-focused:focus {
-        outline: var(--navi-keyboard-focus-color) solid 3px;
-      }
+  .option-icon {
+    background-position: center;
+    background-repeat: no-repeat;
+    background-size: contain;
+    height: 2rem;
+    margin: auto;
+    width: 2rem;
+  }
 
-      .option-name {
-        flex-grow: 0;
-        line-height: 1.25rem;
-        text-align: center;
-        white-space: normal;
-      }
+  .option-icon-shadow {
+    background-color: var(--navi-option-icon-shadow-color);
+    border-radius: 50%;
+    display: flex;
+    height: 3rem;
+    margin-bottom: .25rem;
+    width: 3rem;
+  }
 
-      .option-icon {
-        background-position: center;
-        background-repeat: no-repeat;
-        background-size: contain;
-        height: 2rem;
-        margin: auto;
-        width: 2rem;
-      }
+  .option iron-icon {
+    --iron-icon-fill-color: var(--cr-card-background-color);
+    background: var(--navi-check-icon-color);
+    border-radius: 50%;
+    display: none;
+    height: .75rem;
+    margin: 0;
+    position: absolute;
+    right: .375rem;
+    top: .375rem;
+    width: .75rem;
+  }
 
-      .option-icon-shadow {
-        background-color: var(--navi-option-icon-shadow-color);
-        border-radius: 50%;
-        display: flex;
-        height: 3rem;
-        margin-bottom: .25rem;
-        width: 3rem;
-      }
+  :host-context([dir=rtl]) .option iron-icon {
+    left: .375rem;
+    right: unset;
+  }
 
-      .option iron-icon {
-        --iron-icon-fill-color: var(--cr-card-background-color);
-        background: var(--navi-check-icon-color);
-        border-radius: 50%;
-        display: none;
-        height: .75rem;
-        margin: 0;
-        position: absolute;
-        right: .375rem;
-        top: .375rem;
-        width: .75rem;
-      }
+  .option.keyboard-focused:focus iron-icon[icon='cr:check'],
+  .option:hover iron-icon[icon='cr:check'],
+  .option[active] iron-icon[icon='cr:check'] {
+    display: block;
+  }
 
-      :host-context([dir=rtl]) .option iron-icon {
-        left: .375rem;
-        right: unset;
-      }
+  .option[active] iron-icon[icon='cr:check'] {
+    background: var(--cr-checked-color);
+  }
 
-      .option.keyboard-focused:focus iron-icon[icon='cr:check'],
-      .option:hover iron-icon[icon='cr:check'],
-      .option[active] iron-icon[icon='cr:check'] {
-        display: block;
-      }
+  /* App Icons */
+  .gmail {
+    content: -webkit-image-set(
+        url(chrome://theme/IDS_WELCOME_GMAIL@1x) 1x,
+        url(chrome://theme/IDS_WELCOME_GMAIL@2x) 2x);
+  }
 
-      .option[active] iron-icon[icon='cr:check'] {
-        background: var(--cr-checked-color);
-      }
+  .youtube {
+    content: -webkit-image-set(
+        url(chrome://theme/IDS_WELCOME_YOUTUBE@1x) 1x,
+        url(chrome://theme/IDS_WELCOME_YOUTUBE@2x) 2x);
+  }
 
-      /* App Icons */
-      .gmail {
-        content: -webkit-image-set(
-            url(chrome://theme/IDS_WELCOME_GMAIL@1x) 1x,
-            url(chrome://theme/IDS_WELCOME_GMAIL@2x) 2x);
-      }
+  .maps {
+    content: -webkit-image-set(
+        url(chrome://theme/IDS_WELCOME_MAPS@1x) 1x,
+        url(chrome://theme/IDS_WELCOME_MAPS@2x) 2x);
+  }
 
-      .youtube {
-        content: -webkit-image-set(
-            url(chrome://theme/IDS_WELCOME_YOUTUBE@1x) 1x,
-            url(chrome://theme/IDS_WELCOME_YOUTUBE@2x) 2x);
-      }
+  .translate {
+    content: -webkit-image-set(
+        url(chrome://theme/IDS_WELCOME_TRANSLATE@1x) 1x,
+        url(chrome://theme/IDS_WELCOME_TRANSLATE@2x) 2x);
+  }
 
-      .maps {
-        content: -webkit-image-set(
-            url(chrome://theme/IDS_WELCOME_MAPS@1x) 1x,
-            url(chrome://theme/IDS_WELCOME_MAPS@2x) 2x);
-      }
+  .news {
+    content: -webkit-image-set(
+        url(chrome://theme/IDS_WELCOME_NEWS@1x) 1x,
+        url(chrome://theme/IDS_WELCOME_NEWS@2x) 2x);
+  }
 
-      .translate {
-        content: -webkit-image-set(
-            url(chrome://theme/IDS_WELCOME_TRANSLATE@1x) 1x,
-            url(chrome://theme/IDS_WELCOME_TRANSLATE@2x) 2x);
-      }
-
-      .news {
-        content: -webkit-image-set(
-            url(chrome://theme/IDS_WELCOME_NEWS@1x) 1x,
-            url(chrome://theme/IDS_WELCOME_NEWS@2x) 2x);
-      }
-
-      .search {
-        content: -webkit-image-set(
-            url(chrome://theme/IDS_WELCOME_SEARCH@1x) 1x,
-            url(chrome://theme/IDS_WELCOME_SEARCH@2x) 2x);
-      }
-    </style>
-    <div class="apps-ask">
-      <div class="chrome-logo" alt=""></div>
-      <h1 tabindex="-1">$i18n{googleAppsDescription}</h1>
-      <div id="appChooser">
-        <div class="slide-in">
-          <template is="dom-repeat" items="[[appList_]]">
-            <button active$="[[item.selected]]"
-                aria-pressed$="[[getAriaPressed_(item.selected)]]"
-                on-click="onAppClick_" on-pointerdown="onAppPointerDown_"
-                on-keyup="onAppKeyUp_" class="option">
-              <div class="option-icon-shadow">
-                <div class$="[[item.icon]] option-icon"></div>
-              </div>
-              <div class="option-name">[[item.name]]</div>
-              <iron-icon icon="cr:check"></iron-icon>
-            </button>
-          </template>
-        </div>
-
-        <div class="button-bar">
-          <cr-button id="noThanksButton" on-click="onNoThanksClicked_">
-            $i18n{skip}
-          </cr-button>
-          <step-indicator model="[[indicatorModel]]"></step-indicator>
-          <cr-button class="action-button" disabled$="[[!hasAppsSelected_]]"
-              on-click="onGetStartedClicked_">
-            $i18n{next}
-            <iron-icon icon="cr:chevron-right"></iron-icon>
-          </cr-button>
-        </div>
-      </div>
+  .search {
+    content: -webkit-image-set(
+        url(chrome://theme/IDS_WELCOME_SEARCH@1x) 1x,
+        url(chrome://theme/IDS_WELCOME_SEARCH@2x) 2x);
+  }
+</style>
+<div class="apps-ask">
+  <div class="chrome-logo" alt=""></div>
+  <h1 tabindex="-1">$i18n{googleAppsDescription}</h1>
+  <div id="appChooser">
+    <div class="slide-in">
+      <template is="dom-repeat" items="[[appList_]]">
+        <button active$="[[item.selected]]"
+            aria-pressed$="[[getAriaPressed_(item.selected)]]"
+            on-click="onAppClick_" on-pointerdown="onAppPointerDown_"
+            on-keyup="onAppKeyUp_" class="option">
+          <div class="option-icon-shadow">
+            <div class$="[[item.icon]] option-icon"></div>
+          </div>
+          <div class="option-name">[[item.name]]</div>
+          <iron-icon icon="cr:check"></iron-icon>
+        </button>
+      </template>
     </div>
-  </template>
-  <script src="nux_google_apps.js"></script>
-</dom-module>
+
+    <div class="button-bar">
+      <cr-button id="noThanksButton" on-click="onNoThanksClicked_">
+        $i18n{skip}
+      </cr-button>
+      <step-indicator model="[[indicatorModel]]"></step-indicator>
+      <cr-button class="action-button" disabled$="[[!hasAppsSelected_]]"
+          on-click="onGetStartedClicked_">
+        $i18n{next}
+        <iron-icon icon="cr:chevron-right"></iron-icon>
+      </cr-button>
+    </div>
+  </div>
+</div>
diff --git a/chrome/browser/resources/welcome/google_apps/nux_google_apps.js b/chrome/browser/resources/welcome/google_apps/nux_google_apps.js
index 8452574c..fd25a48 100644
--- a/chrome/browser/resources/welcome/google_apps/nux_google_apps.js
+++ b/chrome/browser/resources/welcome/google_apps/nux_google_apps.js
@@ -2,7 +2,29 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('welcome');
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/icons.m.js';
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import 'chrome://resources/js/cr.m.js';
+import 'chrome://resources/js/util.m.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../shared/animations_css.js';
+import '../shared/chooser_shared_css.js';
+import '../shared/step_indicator.js';
+import '../strings.m.js';
+
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {isRTL} from 'chrome://resources/js/util.m.js';
+import {IronA11yAnnouncer} from 'chrome://resources/polymer/v3_0/iron-a11y-announcer/iron-a11y-announcer.js';
+import {afterNextRender, html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from '../navigation_behavior.js';
+import {BookmarkBarManager, BookmarkProxy, BookmarkProxyImpl} from '../shared/bookmark_proxy.js';
+import {ModuleMetricsManager} from '../shared/module_metrics_proxy.js';
+import {BookmarkListItem, stepIndicatorModel} from '../shared/nux_types.js';
+
+import {GoogleAppProxy, GoogleAppProxyImpl} from './google_app_proxy.js';
+import {GoogleAppsMetricsProxyImpl} from './google_apps_metrics_proxy.js';
 
 /**
  * @typedef {{
@@ -14,29 +36,31 @@
  *   selected: boolean,
  * }}
  */
-welcome.AppItem;
+let AppItem;
 
 /**
  * @typedef {{
- *   item: !welcome.AppItem,
+ *   item: !AppItem,
  *   set: function(string, boolean):void
  * }}
  */
-welcome.AppItemModel;
+let AppItemModel;
 
 const KEYBOARD_FOCUSED = 'keyboard-focused';
 
 Polymer({
   is: 'nux-google-apps',
 
-  behaviors: [welcome.NavigationBehavior, I18nBehavior],
+  _template: html`{__html_template__}`,
+
+  behaviors: [NavigationBehavior, I18nBehavior],
 
   properties: {
-    /** @type {welcome.stepIndicatorModel} */
+    /** @type {stepIndicatorModel} */
     indicatorModel: Object,
 
     /**
-     * @type {!Array<!welcome.AppItem>}
+     * @type {!Array<!AppItem>}
      * @private
      */
     appList_: Array,
@@ -48,19 +72,19 @@
     },
   },
 
-  /** @private {welcome.GoogleAppProxy} */
+  /** @private {GoogleAppProxy} */
   appProxy_: null,
 
-  /** @private {?welcome.ModuleMetricsManager} */
+  /** @private {?ModuleMetricsManager} */
   metricsManager_: null,
 
-  /** @private */
+  /** @private {boolean} */
   finalized_: false,
 
-  /** @private {welcome.BookmarkProxy} */
+  /** @private {BookmarkProxy} */
   bookmarkProxy_: null,
 
-  /** @private {welcome.BookmarkBarManager} */
+  /** @private {BookmarkBarManager} */
   bookmarkBarManager_: null,
 
   /** @private {boolean} */
@@ -68,18 +92,16 @@
 
   /** @override */
   ready: function() {
-    this.appProxy_ = welcome.GoogleAppProxyImpl.getInstance();
-    this.metricsManager_ = new welcome.ModuleMetricsManager(
-        welcome.GoogleAppsMetricsProxyImpl.getInstance());
-    this.bookmarkProxy_ = welcome.BookmarkProxyImpl.getInstance();
-    this.bookmarkBarManager_ = welcome.BookmarkBarManager.getInstance();
+    this.appProxy_ = GoogleAppProxyImpl.getInstance();
+    this.metricsManager_ = new ModuleMetricsManager(
+        GoogleAppsMetricsProxyImpl.getInstance());
+    this.bookmarkProxy_ = BookmarkProxyImpl.getInstance();
+    this.bookmarkBarManager_ = BookmarkBarManager.getInstance();
   },
 
   /** @override */
   attached: function() {
-    Polymer.RenderStatus.afterNextRender(this, () => {
-      Polymer.IronA11yAnnouncer.requestAvailability();
-    });
+    afterNextRender(this, () => IronA11yAnnouncer.requestAvailability());
   },
 
   onRouteEnter: function() {
@@ -164,7 +186,7 @@
 
   /**
    * Handle toggling the apps selected.
-   * @param {!{model: !welcome.AppItemModel}} e
+   * @param {!{model: !AppItemModel}} e
    * @private
    */
   onAppClick_: function(e) {
@@ -214,14 +236,14 @@
       }
     });
     this.metricsManager_.recordGetStarted();
-    welcome.navigateToNextStep();
+    navigateToNextStep();
   },
 
   /** @private */
   onNoThanksClicked_: function() {
     this.cleanUp_();
     this.metricsManager_.recordNoThanks();
-    welcome.navigateToNextStep();
+    navigateToNextStep();
   },
 
   /**
@@ -235,7 +257,7 @@
       this.appList_.forEach(app => this.updateBookmark_(app));
     } else {
       this.appProxy_.getAppList().then(list => {
-        this.appList_ = /** @type(!Array<!welcome.AppItem>) */ (list);
+        this.appList_ = /** @type(!Array<!AppItem>) */ (list);
         this.appList_.forEach((app, index) => {
           // Default select first few items.
           app.selected = index < 3;
@@ -248,7 +270,7 @@
   },
 
   /**
-   * @param {!welcome.AppItem} item
+   * @param {!AppItem} item
    * @private
    */
   updateBookmark_: function(item) {
diff --git a/chrome/browser/resources/welcome/landing_view.html b/chrome/browser/resources/welcome/landing_view.html
index 2ae537d..3446ebd 100644
--- a/chrome/browser/resources/welcome/landing_view.html
+++ b/chrome/browser/resources/welcome/landing_view.html
@@ -1,43 +1,26 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="animations action-link-style splash-pages-shared-css">
+  onboarding-background {
+    --animation-delay: 275ms;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="landing_view_proxy.html">
-<link rel="import" href="navigation_behavior.html">
-<link rel="import" href="shared/action_link_style_css.html">
-<link rel="import" href="shared/i18n_setup.html">
-<link rel="import" href="shared/onboarding_background.html">
-<link rel="import" href="shared/splash_pages_shared_css.html">
-<link rel="import" href="welcome_browser_proxy.html">
+  h1 {
+    outline: none;
+  }
 
-<dom-module id="landing-view">
-  <template>
-    <style include="animations action-link-style splash-pages-shared-css">
-      onboarding-background {
-        --animation-delay: 275ms;
-      }
-
-      h1 {
-        outline: none;
-      }
-
-      .action-button,
-      .action-link {
-        --animation-delay: 150ms;
-      }
-    </style>
-    <div id="container">
-      <onboarding-background class="fade-in"></onboarding-background>
-      <h2 class="fade-in">$i18n{landingDescription}</h2>
-      <h1 class="fade-in" tabindex="-1">$i18n{landingTitle}</h1>
-      <cr-button class="action-button fade-in" on-click="onNewUserClick_">
-        $i18n{landingNewUser}
-      </cr-button>
-      <button class="action-link fade-in" on-click="onExistingUserClick_">
-        <span hidden$="[[!signinAllowed_]]">$i18n{landingExistingUser}</span>
-        <span hidden$="[[signinAllowed_]]">$i18n{skip}</span>
-      </button>
-    </div>
-  </template>
-  <script src="landing_view.js"></script>
-</dom-module>
+  .action-button,
+  .action-link {
+    --animation-delay: 150ms;
+  }
+</style>
+<div id="container">
+  <onboarding-background class="fade-in"></onboarding-background>
+  <h2 class="fade-in">$i18n{landingDescription}</h2>
+  <h1 class="fade-in" tabindex="-1">$i18n{landingTitle}</h1>
+  <cr-button class="action-button fade-in" on-click="onNewUserClick_">
+    $i18n{landingNewUser}
+  </cr-button>
+  <button class="action-link fade-in" on-click="onExistingUserClick_">
+    <span hidden$="[[!signinAllowed_]]">$i18n{landingExistingUser}</span>
+    <span hidden$="[[signinAllowed_]]">$i18n{skip}</span>
+  </button>
+</div>
diff --git a/chrome/browser/resources/welcome/landing_view.js b/chrome/browser/resources/welcome/landing_view.js
index 94f6d4ad..785bb5e 100644
--- a/chrome/browser/resources/welcome/landing_view.js
+++ b/chrome/browser/resources/welcome/landing_view.js
@@ -2,10 +2,26 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
+import './shared/action_link_style_css.js';
+import './shared/onboarding_background.js';
+import './shared/splash_pages_shared_css.js';
+import '../strings.m.js';
+
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {LandingViewProxy, LandingViewProxyImpl} from './landing_view_proxy.js';
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from './navigation_behavior.js';
+import {WelcomeBrowserProxyImpl} from './welcome_browser_proxy.js';
+
 Polymer({
   is: 'landing-view',
 
-  behaviors: [welcome.NavigationBehavior],
+  _template: html`{__html_template__}`,
+
+  behaviors: [NavigationBehavior],
 
   properties: {
     /** @private */
@@ -15,7 +31,7 @@
     }
   },
 
-  /** @private {?welcome.LandingViewProxy} */
+  /** @private {?LandingViewProxy} */
   landingViewProxy_: null,
 
   /** @private {boolean} */
@@ -23,7 +39,7 @@
 
   /** @override */
   ready() {
-    this.landingViewProxy_ = welcome.LandingViewProxyImpl.getInstance();
+    this.landingViewProxy_ = LandingViewProxyImpl.getInstance();
   },
 
   onRouteEnter: function() {
@@ -45,10 +61,10 @@
     this.finalized_ = true;
     this.landingViewProxy_.recordExistingUser();
     if (this.signinAllowed_) {
-      welcome.WelcomeBrowserProxyImpl.getInstance().handleActivateSignIn(
+      WelcomeBrowserProxyImpl.getInstance().handleActivateSignIn(
         'chrome://welcome/returning-user');
     } else {
-      welcome.navigateTo(welcome.Routes.RETURNING_USER, 1);
+      navigateTo(Routes.RETURNING_USER, 1);
     }
   },
 
@@ -56,6 +72,6 @@
   onNewUserClick_: function() {
     this.finalized_ = true;
     this.landingViewProxy_.recordNewUser();
-    welcome.navigateTo(welcome.Routes.NEW_USER, 1);
+    navigateTo(Routes.NEW_USER, 1);
   }
 });
diff --git a/chrome/browser/resources/welcome/landing_view_proxy.html b/chrome/browser/resources/welcome/landing_view_proxy.html
deleted file mode 100644
index 84eb5be..0000000
--- a/chrome/browser/resources/welcome/landing_view_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="landing_view_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/landing_view_proxy.js b/chrome/browser/resources/welcome/landing_view_proxy.js
index 1ccfbec..99e9819 100644
--- a/chrome/browser/resources/welcome/landing_view_proxy.js
+++ b/chrome/browser/resources/welcome/landing_view_proxy.js
@@ -2,74 +2,69 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
-  const NUX_LANDING_PAGE_INTERACTION_METRIC_NAME =
-      'FirstRun.NewUserExperience.LandingPageInteraction';
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
+const NUX_LANDING_PAGE_INTERACTION_METRIC_NAME =
+    'FirstRun.NewUserExperience.LandingPageInteraction';
+
+/**
+ * NuxLandingPageInteractions enum.
+ * These values are persisted to logs and should not be renumbered or re-used.
+ * See tools/metrics/histograms/enums.xml.
+ * @enum {number}
+ */
+const NuxLandingPageInteractions = {
+  PageShown: 0,
+  NavigatedAway: 1,
+  NewUser: 2,
+  ExistingUser: 3,
+};
+
+const NUX_LANDING_PAGE_INTERACTIONS_COUNT =
+    Object.keys(NuxLandingPageInteractions).length;
+
+/** @interface */
+export class LandingViewProxy {
+  recordPageShown() {}
+
+  recordNavigatedAway() {}
+
+  recordNewUser() {}
+
+  recordExistingUser() {}
+}
+
+/** @implements {LandingViewProxy} */
+export class LandingViewProxyImpl {
+  /** @override */
+  recordPageShown() {
+    this.recordInteraction_(NuxLandingPageInteractions.PageShown);
+  }
+
+  /** @override */
+  recordNavigatedAway() {
+    this.recordInteraction_(NuxLandingPageInteractions.NavigatedAway);
+  }
+
+  /** @override */
+  recordNewUser() {
+    this.recordInteraction_(NuxLandingPageInteractions.NewUser);
+  }
+
+  /** @override */
+  recordExistingUser() {
+    this.recordInteraction_(NuxLandingPageInteractions.ExistingUser);
+  }
 
   /**
-   * NuxLandingPageInteractions enum.
-   * These values are persisted to logs and should not be renumbered or re-used.
-   * See tools/metrics/histograms/enums.xml.
-   * @enum {number}
+   * @param {number} interaction
+   * @private
    */
-  const NuxLandingPageInteractions = {
-    PageShown: 0,
-    NavigatedAway: 1,
-    NewUser: 2,
-    ExistingUser: 3,
-  };
-
-  const NUX_LANDING_PAGE_INTERACTIONS_COUNT =
-      Object.keys(NuxLandingPageInteractions).length;
-
-  /** @interface */
-  class LandingViewProxy {
-    recordPageShown() {}
-
-    recordNavigatedAway() {}
-
-    recordNewUser() {}
-
-    recordExistingUser() {}
+  recordInteraction_(interaction) {
+    chrome.metricsPrivate.recordEnumerationValue(
+        NUX_LANDING_PAGE_INTERACTION_METRIC_NAME, interaction,
+        NUX_LANDING_PAGE_INTERACTIONS_COUNT);
   }
+}
 
-  /** @implements {welcome.LandingViewProxy} */
-  class LandingViewProxyImpl {
-    /** @override */
-    recordPageShown() {
-      this.recordInteraction_(NuxLandingPageInteractions.PageShown);
-    }
-
-    /** @override */
-    recordNavigatedAway() {
-      this.recordInteraction_(NuxLandingPageInteractions.NavigatedAway);
-    }
-
-    /** @override */
-    recordNewUser() {
-      this.recordInteraction_(NuxLandingPageInteractions.NewUser);
-    }
-
-    /** @override */
-    recordExistingUser() {
-      this.recordInteraction_(NuxLandingPageInteractions.ExistingUser);
-    }
-
-    /**
-     * @param {number} interaction
-     * @private
-     */
-    recordInteraction_(interaction) {
-      chrome.metricsPrivate.recordEnumerationValue(
-          NUX_LANDING_PAGE_INTERACTION_METRIC_NAME, interaction,
-          NUX_LANDING_PAGE_INTERACTIONS_COUNT);
-    }
-  }
-
-  cr.addSingletonGetter(LandingViewProxyImpl);
-
-  return {
-    LandingViewProxy: LandingViewProxy,
-    LandingViewProxyImpl: LandingViewProxyImpl,
-  };
-});
+addSingletonGetter(LandingViewProxyImpl);
diff --git a/chrome/browser/resources/welcome/navigation_behavior.html b/chrome/browser/resources/welcome/navigation_behavior.html
deleted file mode 100644
index 1488b4d..0000000
--- a/chrome/browser/resources/welcome/navigation_behavior.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
-
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-
-<script src="navigation_behavior.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/navigation_behavior.js b/chrome/browser/resources/welcome/navigation_behavior.js
index 5e10ba64..c2c94452 100644
--- a/chrome/browser/resources/welcome/navigation_behavior.js
+++ b/chrome/browser/resources/welcome/navigation_behavior.js
@@ -2,6 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {afterNextRender} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 /**
  * @fileoverview The NavigationBehavior is in charge of manipulating and
  *     watching window.history.state changes. The page is using the history
@@ -12,183 +15,172 @@
  *     or popping history state without actually changing the path.
  */
 
-cr.define('welcome', function() {
-  'use strict';
+/**
+ * Valid route pathnames.
+ * @enum {string}
+ */
+export const Routes = {
+  LANDING: 'landing',
+  NEW_USER: 'new-user',
+  RETURNING_USER: 'returning-user',
+};
 
-  /**
-   * Valid route pathnames.
-   * @enum {string}
-   */
-  const Routes = {
-    LANDING: 'landing',
-    NEW_USER: 'new-user',
-    RETURNING_USER: 'returning-user',
-  };
+/**
+ * Regular expression that captures the leading slash, the content and the
+ * trailing slash in three different groups.
+ * @const {!RegExp}
+ */
+const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
+const path = location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2');
 
-  /**
-   * Regular expression that captures the leading slash, the content and the
-   * trailing slash in three different groups.
-   * @const {!RegExp}
-   */
-  const CANONICAL_PATH_REGEX = /(^\/)([\/-\w]+)(\/$)/;
-  const path = location.pathname.replace(CANONICAL_PATH_REGEX, '$1$2');
+// Sets up history state based on the url path, unless it's already set (e.g.
+// when user uses browser-back button to get back on chrome://welcome/...).
+if (!history.state || !history.state.route || !history.state.step) {
+  switch (path) {
+    case `/${Routes.NEW_USER}`:
+      history.replaceState({route: Routes.NEW_USER, step: 1}, '', path);
+      break;
+    case `/${Routes.RETURNING_USER}`:
+      history.replaceState({route: Routes.RETURNING_USER, step: 1}, '', path);
+      break;
+    default:
+      history.replaceState(
+          {route: Routes.LANDING, step: Routes.LANDING}, '', '/');
+  }
+}
 
-  // Sets up history state based on the url path, unless it's already set (e.g.
-  // when user uses browser-back button to get back on chrome://welcome/...).
-  if (!history.state || !history.state.route || !history.state.step) {
-    switch (path) {
-      case `/${Routes.NEW_USER}`:
-        history.replaceState({route: Routes.NEW_USER, step: 1}, '', path);
-        break;
-      case `/${Routes.RETURNING_USER}`:
-        history.replaceState({route: Routes.RETURNING_USER, step: 1}, '', path);
-        break;
-      default:
-        history.replaceState(
-            {route: Routes.LANDING, step: Routes.LANDING}, '', '/');
-    }
+/** @type {!Set<!PolymerElement>} */
+const routeObservers = new Set();
+
+/** @type {?PolymerElement} */
+let currentRouteElement;
+
+// Notifies all the elements that extended NavigationBehavior.
+function notifyObservers() {
+  if (currentRouteElement) {
+    (/** @type {{onRouteExit: Function}} */ (currentRouteElement))
+        .onRouteExit();
+    currentRouteElement = null;
   }
 
-  /** @type {!Set<!PolymerElement>} */
-  const routeObservers = new Set();
+  const route = /** @type {!Routes} */ (history.state.route);
+  const step = history.state.step;
+  routeObservers.forEach(observer => {
+    (/** @type {{onRouteChange: Function}} */ (observer))
+        .onRouteChange(route, step);
 
-  /** @type {?PolymerElement} */
-  let currentRouteElement;
-
-  // Notifies all the elements that extended NavigationBehavior.
-  function notifyObservers() {
-    if (currentRouteElement) {
-      (/** @type {{onRouteExit: Function}} */ (currentRouteElement))
-          .onRouteExit();
-      currentRouteElement = null;
-    }
-
-    const route = /** @type {!welcome.Routes} */ (history.state.route);
-    const step = history.state.step;
-    routeObservers.forEach(observer => {
-      (/** @type {{onRouteChange: Function}} */ (observer))
-          .onRouteChange(route, step);
-
-      // Modules are only attached to DOM if they're for the current route, so
-      // as long as the id of an element matches up to the current step, it
-      // means that element is for the current route.
-      if (observer.id == `step-${step}`) {
-        currentRouteElement = observer;
-      }
-    });
-
-    // If currentRouteElement is not null, it means there was a new route.
-    if (currentRouteElement) {
-      (/** @type {{onRouteEnter: Function}} */ (currentRouteElement))
-          .onRouteEnter();
-      (/** @type {{updateFocusForA11y: Function}} */ (currentRouteElement))
-          .updateFocusForA11y();
-    }
-  }
-
-  // Notifies all elements when browser history is popped.
-  window.addEventListener('popstate', notifyObservers);
-
-  // Notify the active element before unload.
-  window.addEventListener('beforeunload', () => {
-    if (currentRouteElement) {
-      (/** @type {{onRouteUnload: Function}} */ (currentRouteElement))
-          .onRouteUnload();
+    // Modules are only attached to DOM if they're for the current route, so
+    // as long as the id of an element matches up to the current step, it
+    // means that element is for the current route.
+    if (observer.id == `step-${step}`) {
+      currentRouteElement = observer;
     }
   });
 
-  function navigateToNextStep() {
-    history.pushState(
-        {
-          route: history.state.route,
-          step: history.state.step + 1,
-        },
-        '', `/${history.state.route}`);
-    notifyObservers();
+  // If currentRouteElement is not null, it means there was a new route.
+  if (currentRouteElement) {
+    (/** @type {{onRouteEnter: Function}} */ (currentRouteElement))
+        .onRouteEnter();
+    (/** @type {{updateFocusForA11y: Function}} */ (currentRouteElement))
+        .updateFocusForA11y();
   }
+}
+
+// Notifies all elements when browser history is popped.
+window.addEventListener('popstate', notifyObservers);
+
+// Notify the active element before unload.
+window.addEventListener('beforeunload', () => {
+  if (currentRouteElement) {
+    (/** @type {{onRouteUnload: Function}} */ (currentRouteElement))
+        .onRouteUnload();
+  }
+});
+
+export function navigateToNextStep() {
+  history.pushState(
+      {
+        route: history.state.route,
+        step: history.state.step + 1,
+      },
+      '', `/${history.state.route}`);
+  notifyObservers();
+}
+
+/**
+ * @param {!Routes} route
+ * @param {number} step
+ */
+export function navigateTo(route, step) {
+  assert([
+    Routes.LANDING,
+    Routes.NEW_USER,
+    Routes.RETURNING_USER,
+  ].includes(route));
+
+  history.pushState(
+      {
+        route: route,
+        step: step,
+      },
+      '', '/' + (route === Routes.LANDING ? '' : route));
+  notifyObservers();
+}
+
+/**
+ * Elements can override onRoute(Change|Enter|Exit) to handle route changes.
+ * Order of hooks being called:
+ *   1) onRouteExit() on the old route
+ *   2) onRouteChange() on all subscribed routes
+ *   3) onRouteEnter() on the new route
+ *
+ * @polymerBehavior
+ */
+export const NavigationBehavior = {
+  /** @override */
+  attached: function() {
+    assert(!routeObservers.has(this));
+    routeObservers.add(this);
+
+    const route = /** @type {!Routes} */ (history.state.route);
+    const step = history.state.step;
+
+    // history state was set when page loaded, so when the element first
+    // attaches, call the route-change handler to initialize first.
+    this.onRouteChange(route, step);
+
+    // Modules are only attached to DOM if they're for the current route, so
+    // as long as the id of an element matches up to the current step, it
+    // means that element is for the current route.
+    if (this.id == `step-${step}`) {
+      currentRouteElement = this;
+      this.onRouteEnter();
+      this.updateFocusForA11y();
+    }
+  },
+
+  /** Called to update focus when progressing through the modules. */
+  updateFocusForA11y: function() {
+    const header = this.$$('h1');
+    if (header) {
+      afterNextRender(this, () => header.focus());
+    }
+  },
+
+  /** @override */
+  detached: function() {
+    assert(routeObservers.delete(this));
+  },
 
   /**
-   * @param {!welcome.Routes} route
+   * @param {!Routes} route
    * @param {number} step
    */
-  function navigateTo(route, step) {
-    assert([
-      Routes.LANDING,
-      Routes.NEW_USER,
-      Routes.RETURNING_USER,
-    ].includes(route));
+  onRouteChange: function(route, step) {},
 
-    history.pushState(
-        {
-          route: route,
-          step: step,
-        },
-        '', '/' + (route === Routes.LANDING ? '' : route));
-    notifyObservers();
-  }
+  onRouteEnter: function() {},
 
-  /**
-   * Elements can override onRoute(Change|Enter|Exit) to handle route changes.
-   * Order of hooks being called:
-   *   1) onRouteExit() on the old route
-   *   2) onRouteChange() on all subscribed routes
-   *   3) onRouteEnter() on the new route
-   *
-   * @polymerBehavior
-   */
-  const NavigationBehavior = {
-    /** @override */
-    attached: function() {
-      assert(!routeObservers.has(this));
-      routeObservers.add(this);
+  onRouteExit: function() {},
 
-      const route = /** @type {!welcome.Routes} */ (history.state.route);
-      const step = history.state.step;
-
-      // history state was set when page loaded, so when the element first
-      // attaches, call the route-change handler to initialize first.
-      this.onRouteChange(route, step);
-
-      // Modules are only attached to DOM if they're for the current route, so
-      // as long as the id of an element matches up to the current step, it
-      // means that element is for the current route.
-      if (this.id == `step-${step}`) {
-        currentRouteElement = this;
-        this.onRouteEnter();
-        this.updateFocusForA11y();
-      }
-    },
-
-    /** Called to update focus when progressing through the modules. */
-    updateFocusForA11y: function() {
-      const header = this.$$('h1');
-      if (header) {
-        Polymer.RenderStatus.afterNextRender(this, () => header.focus());
-      }
-    },
-
-    /** @override */
-    detached: function() {
-      assert(routeObservers.delete(this));
-    },
-
-    /**
-     * @param {!welcome.Routes} route
-     * @param {number} step
-     */
-    onRouteChange: function(route, step) {},
-
-    onRouteEnter: function() {},
-
-    onRouteExit: function() {},
-
-    onRouteUnload: function() {},
-  };
-
-  return {
-    NavigationBehavior: NavigationBehavior,
-    navigateTo: navigateTo,
-    navigateToNextStep: navigateToNextStep,
-    Routes: Routes,
-  };
-});
+  onRouteUnload: function() {},
+};
diff --git a/chrome/browser/resources/welcome/ntp_background/BUILD.gn b/chrome/browser/resources/welcome/ntp_background/BUILD.gn
index 32399f99..0500642d 100644
--- a/chrome/browser/resources/welcome/ntp_background/BUILD.gn
+++ b/chrome/browser/resources/welcome/ntp_background/BUILD.gn
@@ -3,8 +3,13 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
-
+import("//tools/polymer/polymer.gni")
 js_type_check("closure_compile") {
+  is_polymer3 = true
+  closure_flags = default_closure_args + [
+                    "js_module_root=../../chrome/browser/resources/welcome/",
+                    "js_module_root=gen/chrome/browser/resources/welcome/",
+                  ]
   deps = [
     ":ntp_background_metrics_proxy",
     ":ntp_background_proxy",
@@ -12,21 +17,10 @@
   ]
 }
 
-js_library("nux_ntp_background") {
-  deps = [
-    ":ntp_background_metrics_proxy",
-    ":ntp_background_proxy",
-    "../:navigation_behavior",
-    "../shared:nux_types",
-    "//ui/webui/resources/js:cr",
-    "//ui/webui/resources/js:i18n_behavior",
-    "//ui/webui/resources/js:util",
-  ]
-}
-
 js_library("ntp_background_proxy") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    ":ntp_background_metrics_proxy",
+    "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [ "$externs_path/chrome_send.js" ]
 }
@@ -34,5 +28,29 @@
 js_library("ntp_background_metrics_proxy") {
   deps = [
     "../shared:module_metrics_proxy",
+    "//ui/webui/resources/js:cr.m",
   ]
 }
+
+polymer_modulizer("nux_ntp_background") {
+  js_file = "nux_ntp_background.js"
+  html_file = "nux_ntp_background.html"
+  html_type = "v3-ready"
+}
+
+js_library("nux_ntp_background") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.js",
+  ]
+  deps = [
+    ":ntp_background_metrics_proxy",
+    ":ntp_background_proxy",
+    "../:navigation_behavior",
+    "../shared:nux_types",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:cr.m",
+    "//ui/webui/resources/js:i18n_behavior.m",
+    "//ui/webui/resources/js:util.m",
+  ]
+  extra_deps = [ ":nux_ntp_background_module" ]
+}
diff --git a/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.html b/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.html
deleted file mode 100644
index a5bbfe09..0000000
--- a/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="../shared/module_metrics_proxy.html">
-<script src="ntp_background_metrics_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.js b/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.js
index dcdb0ff..114e561 100644
--- a/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.js
+++ b/chrome/browser/resources/welcome/ntp_background/ntp_background_metrics_proxy.js
@@ -2,22 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
-  class NtpBackgroundMetricsProxyImpl extends welcome.ModuleMetricsProxyImpl {
-    constructor() {
-      super(
-          'FirstRun.NewUserExperience.NtpBackgroundInteraction',
-          welcome.NuxNtpBackgroundInteractions);
-    }
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+import {ModuleMetricsProxyImpl, NuxNtpBackgroundInteractions} from '../shared/module_metrics_proxy.js';
 
-    getInteractions() {
-      return this.interactions_;
-    }
+export class NtpBackgroundMetricsProxyImpl extends ModuleMetricsProxyImpl {
+  constructor() {
+    super(
+        'FirstRun.NewUserExperience.NtpBackgroundInteraction',
+        NuxNtpBackgroundInteractions);
   }
 
-  cr.addSingletonGetter(NtpBackgroundMetricsProxyImpl);
+  getInteractions() {
+    return this.interactions_;
+  }
+}
 
-  return {
-    NtpBackgroundMetricsProxyImpl: NtpBackgroundMetricsProxyImpl,
-  };
-});
+addSingletonGetter(NtpBackgroundMetricsProxyImpl);
diff --git a/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.html b/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.html
deleted file mode 100644
index a15c5167..0000000
--- a/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="ntp_background_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.js b/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.js
index 17b8ee6..6f86f16 100644
--- a/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.js
+++ b/chrome/browser/resources/welcome/ntp_background/ntp_background_proxy.js
@@ -2,100 +2,96 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {NtpBackgroundMetricsProxyImpl} from './ntp_background_metrics_proxy.js';
+
+/**
+ * @typedef {{
+ *   id: number,
+ *   imageUrl: string,
+ *   thumbnailClass: string,
+ *   title: string,
+ * }}
+ */
+export let NtpBackgroundData;
+
+/** @interface */
+export class NtpBackgroundProxy {
+  /** @return {!Promise} */
+  clearBackground() {}
+
+  /** @return {!Promise<!Array<!NtpBackgroundData>>} */
+  getBackgrounds() {}
+
   /**
-   * @typedef {{
-   *   id: number,
-   *   imageUrl: string,
-   *   thumbnailClass: string,
-   *   title: string,
-   * }}
+   * @param {string} url
+   * @return {!Promise<void>}
    */
-  let NtpBackgroundData;
+  preloadImage(url) {}
 
-  /** @interface */
-  class NtpBackgroundProxy {
-    clearBackground() {}
+  recordBackgroundImageFailedToLoad() {}
 
-    /** @return {!Promise<!Array<!welcome.NtpBackgroundData>>} */
-    getBackgrounds() {}
+  /** @param {number} loadTime */
+  recordBackgroundImageLoadTime(loadTime) {}
 
-    /**
-     * @param {string} url
-     * @return {!Promise<void>}
-     */
-    preloadImage(url) {}
+  recordBackgroundImageNeverLoaded() {}
 
-    recordBackgroundImageFailedToLoad() {}
+  /** @param {number} id */
+  setBackground(id) {}
+}
 
-    /** @param {number} loadTime */
-    recordBackgroundImageLoadTime(loadTime) {}
-
-    recordBackgroundImageNeverLoaded() {}
-
-    /** @param {number} id */
-    setBackground(id) {}
+/** @implements {NtpBackgroundProxy} */
+export class NtpBackgroundProxyImpl {
+  /** @override */
+  clearBackground() {
+    return sendWithPromise('clearBackground');
   }
 
-  /** @implements {welcome.NtpBackgroundProxy} */
-  class NtpBackgroundProxyImpl {
-    /** @override */
-    clearBackground() {
-      return cr.sendWithPromise('clearBackground');
-    }
-
-    /** @override */
-    getBackgrounds() {
-      return cr.sendWithPromise('getBackgrounds');
-    }
-
-    /** @override */
-    preloadImage(url) {
-      return new Promise((resolve, reject) => {
-        const preloadedImage = new Image();
-        preloadedImage.onerror = reject;
-        preloadedImage.onload = resolve;
-        preloadedImage.src = url;
-      });
-    }
-
-    /** @override */
-    recordBackgroundImageFailedToLoad() {
-      const ntpInteractions =
-          welcome.NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
-      chrome.metricsPrivate.recordEnumerationValue(
-          'FirstRun.NewUserExperience.NtpBackgroundInteraction',
-          ntpInteractions.BackgroundImageFailedToLoad,
-          Object.keys(ntpInteractions).length);
-    }
-
-    /** @override */
-    recordBackgroundImageLoadTime(loadTime) {
-      chrome.metricsPrivate.recordTime(
-          'FirstRun.NewUserExperience.NtpBackgroundLoadTime', loadTime);
-    }
-
-    /** @override */
-    recordBackgroundImageNeverLoaded() {
-      const ntpInteractions =
-          welcome.NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
-      chrome.metricsPrivate.recordEnumerationValue(
-          'FirstRun.NewUserExperience.NtpBackgroundInteraction',
-          ntpInteractions.BackgroundImageNeverLoaded,
-          Object.keys(ntpInteractions).length);
-    }
-
-    /** @override */
-    setBackground(id) {
-      chrome.send('setBackground', [id]);
-    }
+  /** @override */
+  getBackgrounds() {
+    return sendWithPromise('getBackgrounds');
   }
 
-  cr.addSingletonGetter(NtpBackgroundProxyImpl);
+  /** @override */
+  preloadImage(url) {
+    return new Promise((resolve, reject) => {
+      const preloadedImage = new Image();
+      preloadedImage.onerror = reject;
+      preloadedImage.onload = resolve;
+      preloadedImage.src = url;
+    });
+  }
 
-  return {
-    NtpBackgroundData: NtpBackgroundData,
-    NtpBackgroundProxy: NtpBackgroundProxy,
-    NtpBackgroundProxyImpl: NtpBackgroundProxyImpl,
-  };
-});
+  /** @override */
+  recordBackgroundImageFailedToLoad() {
+    const ntpInteractions =
+        NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
+    chrome.metricsPrivate.recordEnumerationValue(
+        'FirstRun.NewUserExperience.NtpBackgroundInteraction',
+        ntpInteractions.BackgroundImageFailedToLoad,
+        Object.keys(ntpInteractions).length);
+  }
+
+  /** @override */
+  recordBackgroundImageLoadTime(loadTime) {
+    chrome.metricsPrivate.recordTime(
+        'FirstRun.NewUserExperience.NtpBackgroundLoadTime', loadTime);
+  }
+
+  /** @override */
+  recordBackgroundImageNeverLoaded() {
+    const ntpInteractions =
+        NtpBackgroundMetricsProxyImpl.getInstance().getInteractions();
+    chrome.metricsPrivate.recordEnumerationValue(
+        'FirstRun.NewUserExperience.NtpBackgroundInteraction',
+        ntpInteractions.BackgroundImageNeverLoaded,
+        Object.keys(ntpInteractions).length);
+  }
+
+  /** @override */
+  setBackground(id) {
+    chrome.send('setBackground', [id]);
+  }
+}
+
+addSingletonGetter(NtpBackgroundProxyImpl);
diff --git a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html
index c6d03b9a..f5b6ac42 100644
--- a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html
+++ b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.html
@@ -1,213 +1,190 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="animations chooser-shared-css">
+  :host {
+    text-align: center;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr.html">
-<link rel="import" href="chrome://resources/html/i18n_behavior.html">
-<link rel="import" href="chrome://resources/html/util.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-<link rel="import" href="../navigation_behavior.html">
-<link rel="import" href="../shared/animations_css.html">
-<link rel="import" href="../shared/chooser_shared_css.html">
-<link rel="import" href="../shared/i18n_setup.html">
-<link rel="import" href="../shared/step_indicator.html">
-<link rel="import" href="ntp_background_metrics_proxy.html">
-<link rel="import" href="ntp_background_proxy.html">
+  #backgroundPreview {
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: cover;
+    bottom: 0;
+    left: 0;
+    opacity: 0;
+    position: fixed;
+    right: 0;
+    top: 0;
+    transition: background 300ms, opacity 400ms;
+  }
 
-<dom-module id="nux-ntp-background">
-  <template>
-    <style include="animations chooser-shared-css">
-      :host {
-        text-align: center;
-      }
+  #backgroundPreview.active {
+    opacity: 1;
+  }
 
-      #backgroundPreview {
-        background-position: center center;
-        background-repeat: no-repeat;
-        background-size: cover;
-        bottom: 0;
-        left: 0;
-        opacity: 0;
-        position: fixed;
-        right: 0;
-        top: 0;
-        transition: background 300ms, opacity 400ms;
-      }
+  #backgroundPreview::before {
+    /* Copied from browser/resources/local_ntp/custom_backgrounds.js */
+    background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .3));
+    /* Pseudo element needs some content (even an empty string) to be
+     * displayed. */
+    content: '';
+    display: block;
+    height: 100%;
+    width: 100%;
+  }
 
-      #backgroundPreview.active {
-        opacity: 1;
-      }
+  .content {
+    /* Put a non-static position on the content so that it can have a
+     * higher stacking level than its previous sibling,
+     * the #backgroundPreview element. */
+    position: relative;
+  }
 
-      #backgroundPreview::before {
-        /* Copied from browser/resources/local_ntp/custom_backgrounds.js */
-        background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, .3));
-        /* Pseudo element needs some content (even an empty string) to be
-         * displayed. */
-        content: '';
-        display: block;
-        height: 100%;
-        width: 100%;
-      }
+  .ntp-background-logo {
+    content: url(../images/module_icons/wallpaper_light.svg);
+    height: 39px;
+    margin: auto;
+    margin-bottom: 16px;
+    width: 44px;
+  }
 
-      .content {
-        /* Put a non-static position on the content so that it can have a
-         * higher stacking level than its previous sibling,
-         * the #backgroundPreview element. */
-        position: relative;
-      }
+  @media (prefers-color-scheme: dark) {
+    .ntp-background-logo {
+      content: url(../images/module_icons/wallpaper_dark.svg);
+    }
+  }
 
-      .ntp-background-logo {
-        content: url(../images/module_icons/wallpaper_light.svg);
-        height: 39px;
-        margin: auto;
-        margin-bottom: 16px;
-        width: 44px;
-      }
+  h1 {
+    color: var(--cr-primary-text-color);
+    font-size: 1.5rem;
+    font-weight: 500;
+    margin: 0;
+    margin-bottom: 46px;
+    outline: none;
+    transition: color 400ms;
+  }
 
-      @media (prefers-color-scheme: dark) {
-        .ntp-background-logo {
-          content: url(../images/module_icons/wallpaper_dark.svg);
-        }
-      }
+  #backgroundPreview.active + .content h1 {
+    color: white;
+  }
 
-      h1 {
-        color: var(--cr-primary-text-color);
-        font-size: 1.5rem;
-        font-weight: 500;
-        margin: 0;
-        margin-bottom: 46px;
-        outline: none;
-        transition: color 400ms;
-      }
+  .ntp-backgrounds-grid {
+    display: grid;
+    grid-gap: 32px;
+    grid-template-columns: repeat(3, 176px);
+    grid-template-rows: repeat(2, 176px);
+    width: 592px;
+  }
 
-      #backgroundPreview.active + .content h1 {
-        color: white;
-      }
+  .option {
+    align-items: stretch;
+    border-radius: 4px;
+    display: flex;
+    height: 100%;
+    overflow: hidden;
+    padding: 0;
+    text-align: start;
+    transition: border-color 400ms, box-shadow 500ms;
+    width: 100%;
+  }
 
-      .ntp-backgrounds-grid {
-        display: grid;
-        grid-gap: 32px;
-        grid-template-columns: repeat(3, 176px);
-        grid-template-rows: repeat(2, 176px);
-        width: 592px;
-      }
+  #backgroundPreview.active + .content .option {
+    border-color: var(--google-grey-refresh-700);
+  }
 
-      .option {
-        align-items: stretch;
-        border-radius: 4px;
-        display: flex;
-        height: 100%;
-        overflow: hidden;
-        padding: 0;
-        text-align: start;
-        transition: border-color 400ms, box-shadow 500ms;
-        width: 100%;
-      }
+  /* Remove outline when button is focused using the mouse. */
+  .option:focus:not(.keyboard-focused) {
+    outline: none;
+  }
 
-      #backgroundPreview.active + .content .option {
-        border-color: var(--google-grey-refresh-700);
-      }
+  .ntp-background-thumbnail {
+    background-color: var(--cr-card-background-color);
+    background-position: center center;
+    background-repeat: no-repeat;
+    background-size: cover;
+    flex: 1;
+  }
 
-      /* Remove outline when button is focused using the mouse. */
-      .option:focus:not(.keyboard-focused) {
-        outline: none;
-      }
+  .option-name {
+    border-top: var(--cr-separator-line);
+    color: var(--navi-wallpaper-text-color);
+    height: 3rem;
+    line-height: 3rem;
+    overflow: hidden;
+    padding: 0 .75rem;
+    text-overflow: ellipsis;
+  }
 
-      .ntp-background-thumbnail {
-        background-color: var(--cr-card-background-color);
-        background-position: center center;
-        background-repeat: no-repeat;
-        background-size: cover;
-        flex: 1;
-      }
+  .option[active] .option-name {
+    background: var(--cr-checked-color);
+    color: var(--cr-card-background-color);
+  }
 
-      .option-name {
-        border-top: var(--cr-separator-line);
-        color: var(--navi-wallpaper-text-color);
-        height: 3rem;
-        line-height: 3rem;
-        overflow: hidden;
-        padding: 0 .75rem;
-        text-overflow: ellipsis;
-      }
+  .button-bar {
+    margin-top: 56px;
+  }
 
-      .option[active] .option-name {
-        background: var(--cr-checked-color);
-        color: var(--cr-card-background-color);
-      }
+  #skipButton {
+    background-color: var(--cr-card-background-color)
+  }
 
-      .button-bar {
-        margin-top: 56px;
-      }
+  #skipButton:hover {
+    background-image:
+        linear-gradient(var(--hover-bg-color), var(--hover-bg-color));
+  }
 
-      #skipButton {
-        background-color: var(--cr-card-background-color)
-      }
+  /* Wallpaper Thumbnails */
+  .art {
+    background-image: url(../images/ntp_thumbnails/art.jpg);
+  }
 
-      #skipButton:hover {
-        background-image:
-            linear-gradient(var(--hover-bg-color), var(--hover-bg-color));
-      }
+  .cityscape {
+    background-image: url(../images/ntp_thumbnails/cityscape.jpg);
+  }
 
-      /* Wallpaper Thumbnails */
-      .art {
-        background-image: url(../images/ntp_thumbnails/art.jpg);
-      }
+  .earth {
+    background-image: url(../images/ntp_thumbnails/earth.jpg);
+  }
 
-      .cityscape {
-        background-image: url(../images/ntp_thumbnails/cityscape.jpg);
-      }
+  .geometric-shapes {
+    background-image: url(../images/ntp_thumbnails/geometric_shapes.jpg);
+  }
 
-      .earth {
-        background-image: url(../images/ntp_thumbnails/earth.jpg);
-      }
+  .landscape {
+    background-image: url(../images/ntp_thumbnails/landscape.jpg);
+  }
+</style>
+<div
+    id="backgroundPreview"
+    on-transitionend="onBackgroundPreviewTransitionEnd_">
+</div>
 
-      .geometric-shapes {
-        background-image: url(../images/ntp_thumbnails/geometric_shapes.jpg);
-      }
+<div class="content">
+  <div class="ntp-background-logo"></div>
+  <h1 tabindex="-1">$i18n{ntpBackgroundDescription}</h1>
 
-      .landscape {
-        background-image: url(../images/ntp_thumbnails/landscape.jpg);
-      }
-    </style>
-    <div
-        id="backgroundPreview"
-        on-transitionend="onBackgroundPreviewTransitionEnd_">
-    </div>
+  <div class="ntp-backgrounds-grid slide-in">
+    <template is="dom-repeat" items="[[backgrounds_]]">
+      <button
+          active$="[[isSelectedBackground_(item, selectedBackground_)]]"
+          class="option"
+          on-click="onBackgroundClick_"
+          on-keyup="onBackgroundKeyUp_"
+          on-pointerdown="onBackgroundPointerDown_">
+        <div
+            class$="ntp-background-thumbnail [[item.thumbnailClass]]">
+        </div>
+        <div class="option-name">[[item.title]]</div>
+      </button>
+    </template>
+  </div>
 
-    <div class="content">
-      <div class="ntp-background-logo"></div>
-      <h1 tabindex="-1">$i18n{ntpBackgroundDescription}</h1>
-
-      <div class="ntp-backgrounds-grid slide-in">
-        <template is="dom-repeat" items="[[backgrounds_]]">
-          <button
-              active$="[[isSelectedBackground_(item, selectedBackground_)]]"
-              class="option"
-              on-click="onBackgroundClick_"
-              on-keyup="onBackgroundKeyUp_"
-              on-pointerdown="onBackgroundPointerDown_">
-            <div
-                class$="ntp-background-thumbnail [[item.thumbnailClass]]">
-            </div>
-            <div class="option-name">[[item.title]]</div>
-          </button>
-        </template>
-      </div>
-
-      <div class="button-bar">
-        <cr-button id="skipButton" on-click="onSkipClicked_">
-          $i18n{skip}
-        </cr-button>
-        <step-indicator model="[[indicatorModel]]"></step-indicator>
-        <cr-button class="action-button" on-click="onNextClicked_">
-          $i18n{next}
-          <iron-icon icon="cr:chevron-right"></iron-icon>
-        </cr-button>
-      </div>
-    </div>
-  </template>
-
-  <script src="nux_ntp_background.js"></script>
-</dom-module>
+  <div class="button-bar">
+    <cr-button id="skipButton" on-click="onSkipClicked_">
+      $i18n{skip}
+    </cr-button>
+    <step-indicator model="[[indicatorModel]]"></step-indicator>
+    <cr-button class="action-button" on-click="onNextClicked_">
+      $i18n{next}
+      <iron-icon icon="cr:chevron-right"></iron-icon>
+    </cr-button>
+  </div>
+</div>
diff --git a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.js b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.js
index 1af8a41..b255441 100644
--- a/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.js
+++ b/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.js
@@ -2,28 +2,51 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/icons.m.js';
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import 'chrome://resources/js/cr.m.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
+import '../shared/animations_css.js';
+import '../shared/chooser_shared_css.js';
+import '../shared/step_indicator.js';
+import '../strings.m.js';
+
+import {I18nBehavior} from 'chrome://resources/js/i18n_behavior.m.js';
+import {isRTL} from 'chrome://resources/js/util.m.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from '../navigation_behavior.js';
+import {ModuleMetricsManager} from '../shared/module_metrics_proxy.js';
+import {stepIndicatorModel} from '../shared/nux_types.js';
+
+import {NtpBackgroundMetricsProxyImpl} from './ntp_background_metrics_proxy.js';
+import {NtpBackgroundData, NtpBackgroundProxy, NtpBackgroundProxyImpl} from './ntp_background_proxy.js';
+
 const KEYBOARD_FOCUSED_CLASS = 'keyboard-focused';
 
 Polymer({
   is: 'nux-ntp-background',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [
     I18nBehavior,
-    welcome.NavigationBehavior,
+    NavigationBehavior,
   ],
 
   properties: {
-    /** @type {welcome.stepIndicatorModel} */
+    /** @type {stepIndicatorModel} */
     indicatorModel: Object,
 
-    /** @private {?welcome.NtpBackgroundData} */
+    /** @private {?NtpBackgroundData} */
     selectedBackground_: {
       observer: 'onSelectedBackgroundChange_',
       type: Object,
     },
   },
 
-  /** @private {?Array<!welcome.NtpBackgroundData>} */
+  /** @private {?Array<!NtpBackgroundData>} */
   backgrounds_: null,
 
   /** @private */
@@ -32,17 +55,17 @@
   /** @private {boolean} */
   imageIsLoading_: false,
 
-  /** @private {?welcome.ModuleMetricsManager} */
+  /** @private {?ModuleMetricsManager} */
   metricsManager_: null,
 
-  /** @private {?welcome.NtpBackgroundProxy} */
+  /** @private {?NtpBackgroundProxy} */
   ntpBackgroundProxy_: null,
 
   /** @override */
   ready: function() {
-    this.ntpBackgroundProxy_ = welcome.NtpBackgroundProxyImpl.getInstance();
-    this.metricsManager_ = new welcome.ModuleMetricsManager(
-        welcome.NtpBackgroundMetricsProxyImpl.getInstance());
+    this.ntpBackgroundProxy_ = NtpBackgroundProxyImpl.getInstance();
+    this.metricsManager_ =
+        new ModuleMetricsManager(NtpBackgroundMetricsProxyImpl.getInstance());
   },
 
   onRouteEnter: function() {
@@ -98,7 +121,7 @@
   },
 
   /**
-   * @param {!welcome.NtpBackgroundData} background
+   * @param {!NtpBackgroundData} background
    * @private
    */
   isSelectedBackground_: function(background) {
@@ -144,7 +167,7 @@
   },
 
   /**
-   * @param {!{model: !{item: !welcome.NtpBackgroundData}}} e
+   * @param {!{model: !{item: !NtpBackgroundData}}} e
    * @private
    */
   onBackgroundClick_: function(e) {
@@ -217,14 +240,14 @@
       this.ntpBackgroundProxy_.clearBackground();
     }
     this.metricsManager_.recordGetStarted();
-    welcome.navigateToNextStep();
+    navigateToNextStep();
   },
 
   /** @private */
   onSkipClicked_: function() {
     this.finalized_ = true;
     this.metricsManager_.recordNoThanks();
-    welcome.navigateToNextStep();
+    navigateToNextStep();
 
     if (this.hasValidSelectedBackground_()) {
       this.fire('iron-announce', {text: this.i18n('ntpBackgroundReset')});
diff --git a/chrome/browser/resources/welcome/set_as_default/BUILD.gn b/chrome/browser/resources/welcome/set_as_default/BUILD.gn
index e633bc8..d4fa01a 100644
--- a/chrome/browser/resources/welcome/set_as_default/BUILD.gn
+++ b/chrome/browser/resources/welcome/set_as_default/BUILD.gn
@@ -3,30 +3,43 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
 
 js_type_check("closure_compile") {
+  is_polymer3 = true
+  closure_flags = default_closure_args + [
+                    "js_module_root=../../chrome/browser/resources/welcome/",
+                    "js_module_root=gen/chrome/browser/resources/welcome/",
+                  ]
   deps = [
     ":nux_set_as_default",
   ]
 }
 
 js_library("nux_set_as_default") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.js",
+  ]
   deps = [
     ":nux_set_as_default_proxy",
     "../:navigation_behavior",
     "../shared:nux_types",
-    "//ui/webui/resources/js:load_time_data",
-    "//ui/webui/resources/js:web_ui_listener_behavior",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+    "//ui/webui/resources/js:load_time_data.m",
+    "//ui/webui/resources/js:web_ui_listener_behavior.m",
   ]
+  extra_deps = [ ":nux_set_as_default_module" ]
 }
 
 js_library("nux_set_as_default_proxy") {
   deps = [
-    "../shared:nux_types",
-    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:cr.m",
   ]
-  externs_list = [
-    "$externs_path/chrome_send.js",
-    "$externs_path/metrics_private.js",
-  ]
+  externs_list = [ "$externs_path/metrics_private.js" ]
+}
+
+polymer_modulizer("nux_set_as_default") {
+  js_file = "nux_set_as_default.js"
+  html_file = "nux_set_as_default.html"
+  html_type = "v3-ready"
 }
diff --git a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.html b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.html
index 53688f8..c7f2b065 100644
--- a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.html
+++ b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.html
@@ -1,104 +1,84 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="animations">
+  .container {
+    text-align: center;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/web_ui_listener_behavior.html">
-<link rel="import" href="../navigation_behavior.html">
-<link rel="import" href="../shared/animations_css.html">
-<link rel="import" href="../shared/i18n_setup.html">
-<link rel="import" href="../shared/step_indicator.html">
-<link rel="import" href="nux_set_as_default_proxy.html">
-<if expr="is_win">
-<link rel="import" href="chrome://resources/cr_elements/icons.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/iron-icon/iron-icon.html">
-</if>
+  .logo {
+    content: url(../images/module_icons/set_default_light.svg);
+    display: inline-block;
+    height: 38px;
+    margin-bottom: 16px;
+    width: 42px;
+  }
 
-<dom-module id="nux-set-as-default">
-  <template>
-    <style include="animations">
-      .container {
-        text-align: center;
-      }
+  @media (prefers-color-scheme: dark) {
+    .logo {
+      content: url(../images/module_icons/set_default_dark.svg);
+    }
+  }
 
-      .logo {
-        content: url(../images/module_icons/set_default_light.svg);
-        display: inline-block;
-        height: 38px;
-        margin-bottom: 16px;
-        width: 42px;
-      }
+  .illustration {
+    content: url(../images/set_default_light.svg);
+    margin: auto;
+    width: 454px;
+  }
 
-      @media (prefers-color-scheme: dark) {
-        .logo {
-          content: url(../images/module_icons/set_default_dark.svg);
-        }
-      }
+  @media (prefers-color-scheme: dark) {
+    .illustration {
+      content: url(../images/set_default_dark.svg);
+    }
+  }
 
-      .illustration {
-        content: url(../images/set_default_light.svg);
-        margin: auto;
-        width: 454px;
-      }
+  h1 {
+    color: var(--cr-primary-text-color);
+    font-size: 1.5rem;
+    font-weight: 500;
+    line-height: 2.5rem;
+    margin: 0;
+    outline: none;
+  }
 
-      @media (prefers-color-scheme: dark) {
-        .illustration {
-          content: url(../images/set_default_dark.svg);
-        }
-      }
+  h2 {
+    color: var(--cr-secondary-text-color);
+    font-size: 1.25rem;
+    font-weight: unset;
+    line-height: 1.875rem;
+    margin: auto;
+    margin-bottom: 48px;
+    margin-top: 16px;
+    max-width: 400px;
+  }
 
-      h1 {
-        color: var(--cr-primary-text-color);
-        font-size: 1.5rem;
-        font-weight: 500;
-        line-height: 2.5rem;
-        margin: 0;
-        outline: none;
-      }
-
-      h2 {
-        color: var(--cr-secondary-text-color);
-        font-size: 1.25rem;
-        font-weight: unset;
-        line-height: 1.875rem;
-        margin: auto;
-        margin-bottom: 48px;
-        margin-top: 16px;
-        max-width: 400px;
-      }
-
-      .button-bar {
-        display: flex;
-        justify-content: space-between;
-        margin-top: 64px;
-      }
+  .button-bar {
+    display: flex;
+    justify-content: space-between;
+    margin-top: 64px;
+  }
 
 <if expr="is_win">
-      iron-icon[icon='cr:open-in-new'] {
-        height: 20px;
-        margin-inline-start: 6px;
-        margin-inline-end: -10px;
-        width: 20px;
-      }
+  iron-icon[icon='cr:open-in-new'] {
+    height: 20px;
+    margin-inline-start: 6px;
+    margin-inline-end: -10px;
+    width: 20px;
+  }
 </if>
-    </style>
-    <div class="container">
-      <div class="logo"></div>
-      <h1 tabindex="-1">$i18n{setDefaultHeader}</h1>
-      <h2>$i18n{setDefaultSubHeader}</h2>
-      <div class="illustration slide-in" aria-hidden="true"></div>
-      <div class="button-bar">
-        <cr-button id="decline-button" on-click="onDeclineClick_">
-          $i18n{skip}
-        </cr-button>
-        <step-indicator model="[[indicatorModel]]"></step-indicator>
-        <cr-button class="action-button" on-click="onSetDefaultClick_">
-          $i18n{setDefaultConfirm}
+</style>
+<div class="container">
+  <div class="logo"></div>
+  <h1 tabindex="-1">$i18n{setDefaultHeader}</h1>
+  <h2>$i18n{setDefaultSubHeader}</h2>
+  <div class="illustration slide-in" aria-hidden="true"></div>
+  <div class="button-bar">
+    <cr-button id="decline-button" on-click="onDeclineClick_">
+      $i18n{skip}
+    </cr-button>
+    <step-indicator model="[[indicatorModel]]"></step-indicator>
+    <cr-button class="action-button" on-click="onSetDefaultClick_">
+      $i18n{setDefaultConfirm}
 <if expr="is_win">
-          <iron-icon icon="cr:open-in-new" hidden="[[!isWin10]]"></iron-icon>
+      <iron-icon icon="cr:open-in-new" hidden="[[!isWin10]]"></iron-icon>
 </if>
-        </cr-button>
-      </div>
-    </div>
-  </template>
-  <script src="nux_set_as_default.js"></script>
-</dom-module>
+    </cr-button>
+  </div>
+</div>
diff --git a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.js b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.js
index 37e69a2d..c2be7a3 100644
--- a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.js
+++ b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.js
@@ -2,16 +2,37 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+// <if expr="is_win">
+import 'chrome://resources/cr_elements/icons.m.js';
+import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js';
+// </if>
+import '../shared/animations_css.js';
+import '../shared/step_indicator.js';
+import '../strings.m.js';
+
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from '../navigation_behavior.js';
+import {DefaultBrowserInfo, stepIndicatorModel} from '../shared/nux_types.js';
+
+import {NuxSetAsDefaultProxy, NuxSetAsDefaultProxyImpl} from './nux_set_as_default_proxy.js';
+
 Polymer({
   is: 'nux-set-as-default',
 
+  _template: html`{__html_template__}`,
+
   behaviors: [
     WebUIListenerBehavior,
-    welcome.NavigationBehavior,
+    NavigationBehavior,
   ],
 
   properties: {
-    /** @type {welcome.stepIndicatorModel} */
+    /** @type {stepIndicatorModel} */
     indicatorModel: Object,
 
     // <if expr="is_win">
@@ -22,15 +43,18 @@
     // </if>
   },
 
-  /** @private {welcome.NuxSetAsDefaultProxy} */
+  /** @private {NuxSetAsDefaultProxy} */
   browserProxy_: null,
 
   /** @private {boolean} */
   finalized_: false,
 
+  /** @private {!Function} */
+  navigateToNextStep_: navigateToNextStep,
+
   /** @override */
   ready: function() {
-    this.browserProxy_ = welcome.NuxSetAsDefaultProxyImpl.getInstance();
+    this.browserProxy_ = NuxSetAsDefaultProxyImpl.getInstance();
 
     this.addWebUIListener(
         'browser-default-state-changed',
@@ -80,7 +104,7 @@
 
   /**
    * Automatically navigate to the next onboarding step once default changed.
-   * @param {!welcome.DefaultBrowserInfo} status
+   * @param {!DefaultBrowserInfo} status
    * @private
    */
   onDefaultBrowserChange_: function(status) {
@@ -105,7 +129,6 @@
   /** @private */
   finished_: function() {
     this.finalized_ = true;
-
-    welcome.navigateToNextStep();
+    this.navigateToNextStep_();
   },
 });
diff --git a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.html b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.html
deleted file mode 100644
index b1dcbde2..0000000
--- a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="nux_set_as_default_proxy.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.js b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.js
index e7668ff..33266da 100644
--- a/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.js
+++ b/chrome/browser/resources/welcome/set_as_default/nux_set_as_default_proxy.js
@@ -2,100 +2,95 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
-  const NUX_SET_AS_DEFAULT_INTERACTION_METRIC_NAME =
-      'FirstRun.NewUserExperience.SetAsDefaultInteraction';
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+import {DefaultBrowserInfo} from '../shared/nux_types.js';
+
+const NUX_SET_AS_DEFAULT_INTERACTION_METRIC_NAME =
+    'FirstRun.NewUserExperience.SetAsDefaultInteraction';
+
+/**
+ * NuxSetAsDefaultInteractions enum.
+ * These values are persisted to logs and should not be renumbered or re-used.
+ * See tools/metrics/histograms/enums.xml.
+ * @enum {number}
+ */
+const NuxSetAsDefaultInteractions = {
+  PageShown: 0,
+  NavigatedAway: 1,
+  Skip: 2,
+  ClickSetDefault: 3,
+  SuccessfullySetDefault: 4,
+  NavigatedAwayThroughBrowserHistory: 5,
+};
+
+const NUX_SET_AS_DEFAULT_INTERACTIONS_COUNT =
+    Object.keys(NuxSetAsDefaultInteractions).length;
+
+/** @interface */
+export class NuxSetAsDefaultProxy {
+  /** @return {!Promise<!DefaultBrowserInfo>} */
+  requestDefaultBrowserState() {}
+  setAsDefault() {}
+  recordPageShown() {}
+  recordNavigatedAway() {}
+  recordNavigatedAwayThroughBrowserHistory() {}
+  recordSkip() {}
+  recordBeginSetDefault() {}
+  recordSuccessfullySetDefault() {}
+}
+
+/** @implements {NuxSetAsDefaultProxy} */
+export class NuxSetAsDefaultProxyImpl {
+  /** @override */
+  requestDefaultBrowserState() {
+    return sendWithPromise('requestDefaultBrowserState');
+  }
+
+  /** @override */
+  setAsDefault() {
+    chrome.send('setAsDefaultBrowser');
+  }
+
+  /** @override */
+  recordPageShown() {
+    this.recordInteraction_(NuxSetAsDefaultInteractions.PageShown);
+  }
+
+  /** @override */
+  recordNavigatedAway() {
+    this.recordInteraction_(NuxSetAsDefaultInteractions.NavigatedAway);
+  }
+
+  /** @override */
+  recordNavigatedAwayThroughBrowserHistory() {
+    this.recordInteraction_(
+        NuxSetAsDefaultInteractions.NavigatedAwayThroughBrowserHistory);
+  }
+
+  /** @override */
+  recordSkip() {
+    this.recordInteraction_(NuxSetAsDefaultInteractions.Skip);
+  }
+
+  /** @override */
+  recordBeginSetDefault() {
+    this.recordInteraction_(NuxSetAsDefaultInteractions.ClickSetDefault);
+  }
+
+  /** @override */
+  recordSuccessfullySetDefault() {
+    this.recordInteraction_(NuxSetAsDefaultInteractions.SuccessfullySetDefault);
+  }
 
   /**
-   * NuxSetAsDefaultInteractions enum.
-   * These values are persisted to logs and should not be renumbered or re-used.
-   * See tools/metrics/histograms/enums.xml.
-   * @enum {number}
+   * @param {number} interaction
+   * @private
    */
-  const NuxSetAsDefaultInteractions = {
-    PageShown: 0,
-    NavigatedAway: 1,
-    Skip: 2,
-    ClickSetDefault: 3,
-    SuccessfullySetDefault: 4,
-    NavigatedAwayThroughBrowserHistory: 5,
-  };
-
-  const NUX_SET_AS_DEFAULT_INTERACTIONS_COUNT =
-      Object.keys(NuxSetAsDefaultInteractions).length;
-
-  /** @interface */
-  class NuxSetAsDefaultProxy {
-    /** @return {!Promise<!welcome.DefaultBrowserInfo>} */
-    requestDefaultBrowserState() {}
-    setAsDefault() {}
-    recordPageShown() {}
-    recordNavigatedAway() {}
-    recordNavigatedAwayThroughBrowserHistory() {}
-    recordSkip() {}
-    recordBeginSetDefault() {}
-    recordSuccessfullySetDefault() {}
+  recordInteraction_(interaction) {
+    chrome.metricsPrivate.recordEnumerationValue(
+        NUX_SET_AS_DEFAULT_INTERACTION_METRIC_NAME, interaction,
+        NUX_SET_AS_DEFAULT_INTERACTIONS_COUNT);
   }
+}
 
-  /** @implements {welcome.NuxSetAsDefaultProxy} */
-  class NuxSetAsDefaultProxyImpl {
-    /** @override */
-    requestDefaultBrowserState() {
-      return cr.sendWithPromise('requestDefaultBrowserState');
-    }
-
-    /** @override */
-    setAsDefault() {
-      chrome.send('setAsDefaultBrowser');
-    }
-
-    /** @override */
-    recordPageShown() {
-      this.recordInteraction_(NuxSetAsDefaultInteractions.PageShown);
-    }
-
-    /** @override */
-    recordNavigatedAway() {
-      this.recordInteraction_(NuxSetAsDefaultInteractions.NavigatedAway);
-    }
-
-    /** @override */
-    recordNavigatedAwayThroughBrowserHistory() {
-      this.recordInteraction_(
-          NuxSetAsDefaultInteractions.NavigatedAwayThroughBrowserHistory);
-    }
-
-    /** @override */
-    recordSkip() {
-      this.recordInteraction_(NuxSetAsDefaultInteractions.Skip);
-    }
-
-    /** @override */
-    recordBeginSetDefault() {
-      this.recordInteraction_(NuxSetAsDefaultInteractions.ClickSetDefault);
-    }
-
-    /** @override */
-    recordSuccessfullySetDefault() {
-      this.recordInteraction_(
-          NuxSetAsDefaultInteractions.SuccessfullySetDefault);
-    }
-
-    /**
-     * @param {number} interaction
-     * @private
-     */
-    recordInteraction_(interaction) {
-      chrome.metricsPrivate.recordEnumerationValue(
-          NUX_SET_AS_DEFAULT_INTERACTION_METRIC_NAME, interaction,
-          NUX_SET_AS_DEFAULT_INTERACTIONS_COUNT);
-    }
-  }
-
-  cr.addSingletonGetter(NuxSetAsDefaultProxyImpl);
-
-  return {
-    NuxSetAsDefaultProxy: NuxSetAsDefaultProxy,
-    NuxSetAsDefaultProxyImpl: NuxSetAsDefaultProxyImpl,
-  };
-});
+addSingletonGetter(NuxSetAsDefaultProxyImpl);
diff --git a/chrome/browser/resources/welcome/shared/BUILD.gn b/chrome/browser/resources/welcome/shared/BUILD.gn
index 9a06f41..14b604fb 100644
--- a/chrome/browser/resources/welcome/shared/BUILD.gn
+++ b/chrome/browser/resources/welcome/shared/BUILD.gn
@@ -3,8 +3,14 @@
 # found in the LICENSE file.
 
 import("//third_party/closure_compiler/compile_js.gni")
+import("//tools/polymer/polymer.gni")
 
 js_type_check("closure_compile") {
+  is_polymer3 = true
+  closure_flags = default_closure_args + [
+                    "js_module_root=../../chrome/browser/resources/welcome/",
+                    "js_module_root=gen/chrome/browser/resources/welcome/",
+                  ]
   deps = [
     ":bookmark_proxy",
     ":module_metrics_proxy",
@@ -15,7 +21,7 @@
 
 js_library("bookmark_proxy") {
   deps = [
-    "//ui/webui/resources/js:cr",
+    "//ui/webui/resources/js:cr.m",
   ]
   externs_list = [
     "$externs_path/chrome_extensions.js",
@@ -24,17 +30,73 @@
 }
 
 js_library("module_metrics_proxy") {
-  deps = [
-    "//ui/webui/resources/js:cr",
-  ]
   externs_list = [ "$externs_path/metrics_private.js" ]
 }
 
 js_library("nux_types") {
-  deps = [
-    "//ui/webui/resources/js:cr",
-  ]
 }
 
 js_library("step_indicator") {
+  sources = [
+    "$root_gen_dir/chrome/browser/resources/welcome/shared/step_indicator.js",
+  ]
+  deps = [
+    ":nux_types",
+    "//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
+  ]
+  extra_deps = [ ":step_indicator_module" ]
+}
+
+polymer_modulizer("animations_css") {
+  js_file = "animations_css.js"
+  html_file = "animations_css.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("chooser_shared_css") {
+  js_file = "chooser_shared_css.js"
+  html_file = "chooser_shared_css.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("navi_colors_css") {
+  js_file = "navi_colors_css.js"
+  html_file = "navi_colors_css.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("action_link_style_css") {
+  js_file = "action_link_style_css.js"
+  html_file = "action_link_style_css.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("splash_pages_shared_css") {
+  js_file = "splash_pages_shared_css.js"
+  html_file = "splash_pages_shared_css.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("onboarding_background") {
+  js_file = "onboarding_background.js"
+  html_file = "onboarding_background.html"
+  html_type = "v3-ready"
+}
+
+polymer_modulizer("step_indicator") {
+  js_file = "step_indicator.js"
+  html_file = "step_indicator.html"
+  html_type = "v3-ready"
+}
+
+group("polymer3_elements") {
+  deps = [
+    ":action_link_style_css_module",
+    ":animations_css_module",
+    ":chooser_shared_css_module",
+    ":navi_colors_css_module",
+    ":onboarding_background_module",
+    ":splash_pages_shared_css_module",
+    ":step_indicator_module",
+  ]
 }
diff --git a/chrome/browser/resources/welcome/shared/action_link_style.js b/chrome/browser/resources/welcome/shared/action_link_style.js
deleted file mode 100644
index b4e52c4b..0000000
--- a/chrome/browser/resources/welcome/shared/action_link_style.js
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-/**
- * @fileoverview Initiates focus-outline-manager for this document so that
- * action-link style can take advantage of it.
- */
-cr.ui.FocusOutlineManager.forDocument(document);
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/shared/action_link_style_css.html b/chrome/browser/resources/welcome/shared/action_link_style_css.html
index 96d00fb..90f5c2c 100644
--- a/chrome/browser/resources/welcome/shared/action_link_style_css.html
+++ b/chrome/browser/resources/welcome/shared/action_link_style_css.html
@@ -1,34 +1,24 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<template>
+  <style>
+    button.action-link {
+      -webkit-appearance: none;
+      background: none;
+      border: none;
+      color: var(--cr-link-color);
+      cursor: pointer;
+      display: inline-block;
+      font-family: inherit;
+      text-decoration: none;
+    }
 
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/html/cr/ui/focus_outline_manager.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
+    button.action-link[disabled] {
+      color: var(--paper-grey-600);
+      cursor: default;
+      opacity: 0.65;
+    }
 
-<dom-module id="action-link-style">
-  <template>
-    <style>
-      button.action-link {
-        -webkit-appearance: none;
-        background: none;
-        border: none;
-        color: var(--cr-link-color);
-        cursor: pointer;
-        display: inline-block;
-        font-family: inherit;
-        text-decoration: none;
-      }
-
-      button.action-link[disabled] {
-        color: var(--paper-grey-600);
-        cursor: default;
-        opacity: 0.65;
-      }
-
-      :host-context(html:not(.focus-outline-visible)) button.action-link {
-        outline: none;
-      }
-    </style>
-  </template>
-</dom-module>
-
-<script src="action_link_style.js"></script>
+    :host-context(html:not(.focus-outline-visible)) button.action-link {
+      outline: none;
+    }
+  </style>
+</template>
diff --git a/chrome/browser/resources/welcome/shared/action_link_style_css.js b/chrome/browser/resources/welcome/shared/action_link_style_css.js
new file mode 100644
index 0000000..c6259334
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/action_link_style_css.js
@@ -0,0 +1,17 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {FocusOutlineManager} from 'chrome://resources/js/cr/ui/focus_outline_manager.m.js';
+
+const styleElement = document.createElement('dom-module');
+styleElement.innerHTML = `{__html_template__}`;
+styleElement.register('action-link-style');
+
+// Initiate focus-outline-manager for this document so that action-link style
+// can take advantage of it.
+FocusOutlineManager.forDocument(document);
diff --git a/chrome/browser/resources/welcome/shared/animations_css.html b/chrome/browser/resources/welcome/shared/animations_css.html
index c79bd54..268c030 100644
--- a/chrome/browser/resources/welcome/shared/animations_css.html
+++ b/chrome/browser/resources/welcome/shared/animations_css.html
@@ -1,39 +1,35 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<template>
+  <style>
+    @keyframes fade-in {
+      0% { opacity: 0; }
+      100% { opacity: 1; }
+    }
 
-<dom-module id="animations">
-  <template>
-    <style>
-      @keyframes fade-in {
-        0% { opacity: 0; }
-        100% { opacity: 1; }
-      }
+    @keyframes slide-in {
+      0% { transform: translateX(var(--slide-in-length, 40px)); }
+      100% { transform: translateX(0); }
+    }
 
-      @keyframes slide-in {
-        0% { transform: translateX(var(--slide-in-length, 40px)); }
-        100% { transform: translateX(0); }
-      }
+    .fade-in {
+      animation-delay: var(--animation-delay, 0);
+      animation-duration: 200ms;
+      animation-fill-mode: forwards;
+      animation-name: fade-in;
+      animation-timing-function: ease-in;
+      opacity: 0;
+    }
 
-      .fade-in {
-        animation-delay: var(--animation-delay, 0);
-        animation-duration: 200ms;
-        animation-fill-mode: forwards;
-        animation-name: fade-in;
-        animation-timing-function: ease-in;
-        opacity: 0;
-      }
+    .slide-in {
+      animation-delay: var(--animation-delay, 0);
+      animation-duration: 200ms;
+      animation-fill-mode: forwards;
+      animation-name: slide-in;
+      animation-timing-function: ease;
+      transform: translateX(30px);
+    }
 
-      .slide-in {
-        animation-delay: var(--animation-delay, 0);
-        animation-duration: 200ms;
-        animation-fill-mode: forwards;
-        animation-name: slide-in;
-        animation-timing-function: ease;
-        transform: translateX(30px);
-      }
-
-      :host-context(html[dir='rtl']) .slide-in {
-        --slide-in-length: -40px;
-      }
-    </style>
-  </template>
-</dom-module>
+    :host-context(html[dir='rtl']) .slide-in {
+      --slide-in-length: -40px;
+    }
+  </style>
+</template>
diff --git a/chrome/browser/resources/welcome/shared/animations_css.js b/chrome/browser/resources/welcome/shared/animations_css.js
new file mode 100644
index 0000000..5cdea0a
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/animations_css.js
@@ -0,0 +1,9 @@
+// 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.
+
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const styleElement = document.createElement('dom-module');
+styleElement.innerHTML = `{__html_template__}`;
+styleElement.register('animations');
diff --git a/chrome/browser/resources/welcome/shared/bookmark_proxy.html b/chrome/browser/resources/welcome/shared/bookmark_proxy.html
deleted file mode 100644
index 0336d1f..0000000
--- a/chrome/browser/resources/welcome/shared/bookmark_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="bookmark_proxy.js"></script>
\ No newline at end of file
diff --git a/chrome/browser/resources/welcome/shared/bookmark_proxy.js b/chrome/browser/resources/welcome/shared/bookmark_proxy.js
index 42bf8060..c905084 100644
--- a/chrome/browser/resources/welcome/shared/bookmark_proxy.js
+++ b/chrome/browser/resources/welcome/shared/bookmark_proxy.js
@@ -2,91 +2,85 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
+import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
+
+/**
+ * @typedef {{
+ *    parentId: string,
+ *    title: string,
+ *    url: string,
+ * }}
+ */
+let bookmarkData;
+
+/** @interface */
+export class BookmarkProxy {
   /**
-   * @typedef {{
-   *    parentId: string,
-   *    title: string,
-   *    url: string,
-   * }}
+   * @param {!bookmarkData} data
+   * @param {!Function} callback
    */
-  let bookmarkData;
+  addBookmark(data, callback) {}
 
-  /** @interface */
-  class BookmarkProxy {
-    /**
-     * @param {!bookmarkData} data
-     * @param {!Function} callback
-     */
-    addBookmark(data, callback) {}
+  /** @param {string} id ID provided by callback when bookmark was added. */
+  removeBookmark(id) {}
 
-    /** @param {string} id ID provided by callback when bookmark was added. */
-    removeBookmark(id) {}
+  /** @param {boolean} show */
+  toggleBookmarkBar(show) {}
 
-    /** @param {boolean} show */
-    toggleBookmarkBar(show) {}
+  /** @return {!Promise<boolean>} */
+  isBookmarkBarShown() {}
+}
 
-    /** @return {!Promise<boolean>} */
-    isBookmarkBarShown() {}
+/** @implements {BookmarkProxy} */
+export class BookmarkProxyImpl {
+  /** @override */
+  addBookmark(data, callback) {
+    chrome.bookmarks.create(data, callback);
   }
 
-  /** @implements {welcome.BookmarkProxy} */
-  class BookmarkProxyImpl {
-    /** @override */
-    addBookmark(data, callback) {
-      chrome.bookmarks.create(data, callback);
-    }
-
-    /** @override */
-    removeBookmark(id) {
-      chrome.bookmarks.remove(id);
-    }
-
-    /** @override */
-    toggleBookmarkBar(show) {
-      chrome.send('toggleBookmarkBar', [show]);
-    }
-
-    /** @override */
-    isBookmarkBarShown() {
-      return cr.sendWithPromise('isBookmarkBarShown');
-    }
+  /** @override */
+  removeBookmark(id) {
+    chrome.bookmarks.remove(id);
   }
 
-  cr.addSingletonGetter(BookmarkProxyImpl);
-
-  // Wrapper for bookmark proxy to keep some additional states.
-  class BookmarkBarManager {
-    constructor() {
-      /** @private {welcome.BookmarkProxy} */
-      this.proxy_ = BookmarkProxyImpl.getInstance();
-
-      /** @private {boolean} */
-      this.isBarShown_ = false;
-
-      /** @type {!Promise} */
-      this.initialized = this.proxy_.isBookmarkBarShown().then(shown => {
-        this.isBarShown_ = shown;
-      });
-    }
-
-    /** @return {boolean} */
-    getShown() {
-      return this.isBarShown_;
-    }
-
-    /** @param {boolean} show */
-    setShown(show) {
-      this.isBarShown_ = show;
-      this.proxy_.toggleBookmarkBar(show);
-    }
+  /** @override */
+  toggleBookmarkBar(show) {
+    chrome.send('toggleBookmarkBar', [show]);
   }
 
-  cr.addSingletonGetter(BookmarkBarManager);
+  /** @override */
+  isBookmarkBarShown() {
+    return sendWithPromise('isBookmarkBarShown');
+  }
+}
 
-  return {
-    BookmarkProxy: BookmarkProxy,
-    BookmarkProxyImpl: BookmarkProxyImpl,
-    BookmarkBarManager: BookmarkBarManager,
-  };
-});
+addSingletonGetter(BookmarkProxyImpl);
+
+// Wrapper for bookmark proxy to keep some additional states.
+export class BookmarkBarManager {
+  constructor() {
+    /** @private {BookmarkProxy} */
+    this.proxy_ = BookmarkProxyImpl.getInstance();
+
+    /** @private {boolean} */
+    this.isBarShown_ = false;
+
+    /** @type {!Promise} */
+    this.initialized = this.proxy_.isBookmarkBarShown().then(shown => {
+      this.isBarShown_ = shown;
+    });
+  }
+
+  /** @return {boolean} */
+  getShown() {
+    return this.isBarShown_;
+  }
+
+  /** @param {boolean} show */
+  setShown(show) {
+    this.isBarShown_ = show;
+    this.proxy_.toggleBookmarkBar(show);
+  }
+}
+
+addSingletonGetter(BookmarkBarManager);
diff --git a/chrome/browser/resources/welcome/shared/chooser_shared_css.html b/chrome/browser/resources/welcome/shared/chooser_shared_css.html
index 52b75a13..d734672 100644
--- a/chrome/browser/resources/welcome/shared/chooser_shared_css.html
+++ b/chrome/browser/resources/welcome/shared/chooser_shared_css.html
@@ -1,43 +1,35 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<template>
+  <style include="navi-colors-css">
+    .option {
+      background: var(--cr-card-background-color);
+      border: 1px solid var(--navi-border-color);
+      color: var(--cr-primary-text-color);
+      cursor: pointer;
+      flex-direction: column;
+    }
 
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-<link rel="import" href="navi_colors_css.html">
+    .option:hover {
+      box-shadow: var(--navi-option-box-shadow);
+    }
 
-<dom-module id="chooser-shared-css">
-  <template>
-    <style include="navi-colors-css">
-      .option {
-        background: var(--cr-card-background-color);
-        border: 1px solid var(--navi-border-color);
-        color: var(--cr-primary-text-color);
-        cursor: pointer;
-        flex-direction: column;
-      }
+    .option-name {
+      font-size: .875rem;
+    }
 
-      .option:hover {
-        box-shadow: var(--navi-option-box-shadow);
-      }
+    .button-bar {
+      display: flex;
+      justify-content: space-between;
+    }
 
-      .option-name {
-        font-size: .875rem;
-      }
+    :host-context([dir=rtl]) iron-icon[icon='cr:chevron-right'] {
+      transform: scaleX(-1);
+    }
 
-      .button-bar {
-        display: flex;
-        justify-content: space-between;
-      }
-
-      :host-context([dir=rtl]) iron-icon[icon='cr:chevron-right'] {
-        transform: scaleX(-1);
-      }
-
-      iron-icon[icon='cr:chevron-right'] {
-        height: 1.25rem;
-        margin-inline-end: -.625rem;
-        margin-inline-start: .375rem;
-        width: 1.25rem;
-      }
-    </style>
-  </template>
-</dom-module>
+    iron-icon[icon='cr:chevron-right'] {
+      height: 1.25rem;
+      margin-inline-end: -.625rem;
+      margin-inline-start: .375rem;
+      width: 1.25rem;
+    }
+  </style>
+</template>
diff --git a/chrome/browser/resources/welcome/shared/chooser_shared_css.js b/chrome/browser/resources/welcome/shared/chooser_shared_css.js
new file mode 100644
index 0000000..6f2044b
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/chooser_shared_css.js
@@ -0,0 +1,13 @@
+// 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.
+
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import 'chrome://resources/cr_elements/md_select_css.m.js';
+import './navi_colors_css.js';
+
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const styleElement = document.createElement('dom-module');
+styleElement.innerHTML = `{__html_template__}`;
+styleElement.register('chooser-shared-css');
diff --git a/chrome/browser/resources/welcome/shared/i18n_setup.html b/chrome/browser/resources/welcome/shared/i18n_setup.html
deleted file mode 100644
index c505b2c13..0000000
--- a/chrome/browser/resources/welcome/shared/i18n_setup.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<script src="chrome://resources/js/load_time_data.js"></script>
-<script src="../strings.js"></script>
diff --git a/chrome/browser/resources/welcome/shared/module_metrics_proxy.html b/chrome/browser/resources/welcome/shared/module_metrics_proxy.html
deleted file mode 100644
index 5c10f794..0000000
--- a/chrome/browser/resources/welcome/shared/module_metrics_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="module_metrics_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/shared/module_metrics_proxy.js b/chrome/browser/resources/welcome/shared/module_metrics_proxy.js
index d6a437b..75819f2 100644
--- a/chrome/browser/resources/welcome/shared/module_metrics_proxy.js
+++ b/chrome/browser/resources/welcome/shared/module_metrics_proxy.js
@@ -2,241 +2,228 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
+/**
+ * NuxNtpBackgroundInteractions enum.
+ * These values are persisted to logs and should not be renumbered or
+ * re-used.
+ * See tools/metrics/histograms/enums.xml.
+ * @enum {number}
+ */
+export const NuxNtpBackgroundInteractions = {
+  PageShown: 0,
+  DidNothingAndNavigatedAway: 1,
+  DidNothingAndChoseSkip: 2,
+  DidNothingAndChoseNext: 3,
+  ChoseAnOptionAndNavigatedAway: 4,
+  ChoseAnOptionAndChoseSkip: 5,
+  ChoseAnOptionAndChoseNext: 6,
+  NavigatedAwayThroughBrowserHistory: 7,
+  BackgroundImageFailedToLoad: 8,
+  BackgroundImageNeverLoaded: 9,
+};
+
+/**
+ * NuxGoogleAppsInteractions enum.
+ * These values are persisted to logs and should not be renumbered or
+ * re-used.
+ * See tools/metrics/histograms/enums.xml.
+ * @enum {number}
+ */
+export const NuxGoogleAppsInteractions = {
+  PageShown: 0,
+  NotUsed_DEPRECATED: 1,
+  GetStarted_DEPRECATED: 2,
+  DidNothingAndNavigatedAway: 3,
+  DidNothingAndChoseSkip: 4,
+  ChoseAnOptionAndNavigatedAway: 5,
+  ChoseAnOptionAndChoseSkip: 6,
+  ChoseAnOptionAndChoseNext: 7,
+  ClickedDisabledNextButtonAndNavigatedAway: 8,
+  ClickedDisabledNextButtonAndChoseSkip: 9,
+  ClickedDisabledNextButtonAndChoseNext: 10,
+  DidNothingAndChoseNext: 11,
+  NavigatedAwayThroughBrowserHistory: 12,
+};
+
+/** @interface */
+class ModuleMetricsProxy {
+  recordPageShown() {}
+
+  recordDidNothingAndNavigatedAway() {}
+
+  recordDidNothingAndChoseSkip() {}
+
+  recordDidNothingAndChoseNext() {}
+
+  recordChoseAnOptionAndNavigatedAway() {}
+
+  recordChoseAnOptionAndChoseSkip() {}
+
+  recordChoseAnOptionAndChoseNext() {}
+
+  recordClickedDisabledNextButtonAndNavigatedAway() {}
+
+  recordClickedDisabledNextButtonAndChoseSkip() {}
+
+  recordClickedDisabledNextButtonAndChoseNext() {}
+
+  recordNavigatedAwayThroughBrowserHistory() {}
+}
+
+/** @implements {ModuleMetricsProxy} */
+export class ModuleMetricsProxyImpl {
   /**
-   * NuxNtpBackgroundInteractions enum.
-   * These values are persisted to logs and should not be renumbered or
-   * re-used.
-   * See tools/metrics/histograms/enums.xml.
-   * @enum {number}
+   * @param {string} histogramName The histogram that will record the module
+   *      navigation metrics.
    */
-  const NuxNtpBackgroundInteractions = {
-    PageShown: 0,
-    DidNothingAndNavigatedAway: 1,
-    DidNothingAndChoseSkip: 2,
-    DidNothingAndChoseNext: 3,
-    ChoseAnOptionAndNavigatedAway: 4,
-    ChoseAnOptionAndChoseSkip: 5,
-    ChoseAnOptionAndChoseNext: 6,
-    NavigatedAwayThroughBrowserHistory: 7,
-    BackgroundImageFailedToLoad: 8,
-    BackgroundImageNeverLoaded: 9,
-  };
-
-  /**
-   * NuxGoogleAppsInteractions enum.
-   * These values are persisted to logs and should not be renumbered or
-   * re-used.
-   * See tools/metrics/histograms/enums.xml.
-   * @enum {number}
-   */
-  const NuxGoogleAppsInteractions = {
-    PageShown: 0,
-    NotUsed_DEPRECATED: 1,
-    GetStarted_DEPRECATED: 2,
-    DidNothingAndNavigatedAway: 3,
-    DidNothingAndChoseSkip: 4,
-    ChoseAnOptionAndNavigatedAway: 5,
-    ChoseAnOptionAndChoseSkip: 6,
-    ChoseAnOptionAndChoseNext: 7,
-    ClickedDisabledNextButtonAndNavigatedAway: 8,
-    ClickedDisabledNextButtonAndChoseSkip: 9,
-    ClickedDisabledNextButtonAndChoseNext: 10,
-    DidNothingAndChoseNext: 11,
-    NavigatedAwayThroughBrowserHistory: 12,
-  };
-
-  /** @interface */
-  class ModuleMetricsProxy {
-    recordPageShown() {}
-
-    recordDidNothingAndNavigatedAway() {}
-
-    recordDidNothingAndChoseSkip() {}
-
-    recordDidNothingAndChoseNext() {}
-
-    recordChoseAnOptionAndNavigatedAway() {}
-
-    recordChoseAnOptionAndChoseSkip() {}
-
-    recordChoseAnOptionAndChoseNext() {}
-
-    recordClickedDisabledNextButtonAndNavigatedAway() {}
-
-    recordClickedDisabledNextButtonAndChoseSkip() {}
-
-    recordClickedDisabledNextButtonAndChoseNext() {}
-
-    recordNavigatedAwayThroughBrowserHistory() {}
+  constructor(histogramName, interactions) {
+    /** @private {string} */
+    this.interactionMetric_ = histogramName;
+    this.interactions_ = interactions;
   }
 
-  /** @implements {welcome.ModuleMetricsProxy} */
-  class ModuleMetricsProxyImpl {
-    /**
-     * @param {string} histogramName The histogram that will record the module
-     *      navigation metrics.
-     */
-    constructor(histogramName, interactions) {
-      /** @private {string} */
-      this.interactionMetric_ = histogramName;
-      this.interactions_ = interactions;
-    }
+  /** @override */
+  recordPageShown() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_, this.interactions_.PageShown,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordPageShown() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_, this.interactions_.PageShown,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordDidNothingAndNavigatedAway() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_, this.interactions_.DidNothingAndNavigatedAway,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordDidNothingAndNavigatedAway() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_,
-          this.interactions_.DidNothingAndNavigatedAway,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordDidNothingAndChoseSkip() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_, this.interactions_.DidNothingAndChoseSkip,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordDidNothingAndChoseSkip() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_, this.interactions_.DidNothingAndChoseSkip,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordDidNothingAndChoseNext() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_, this.interactions_.DidNothingAndChoseNext,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordDidNothingAndChoseNext() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_, this.interactions_.DidNothingAndChoseNext,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordChoseAnOptionAndNavigatedAway() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_,
+        this.interactions_.ChoseAnOptionAndNavigatedAway,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordChoseAnOptionAndNavigatedAway() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_,
-          this.interactions_.ChoseAnOptionAndNavigatedAway,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordChoseAnOptionAndChoseSkip() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_, this.interactions_.ChoseAnOptionAndChoseSkip,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordChoseAnOptionAndChoseSkip() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_, this.interactions_.ChoseAnOptionAndChoseSkip,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordChoseAnOptionAndChoseNext() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_, this.interactions_.ChoseAnOptionAndChoseNext,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordChoseAnOptionAndChoseNext() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_, this.interactions_.ChoseAnOptionAndChoseNext,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordClickedDisabledNextButtonAndNavigatedAway() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_,
+        this.interactions_.ClickedDisabledNextButtonAndNavigatedAway,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordClickedDisabledNextButtonAndNavigatedAway() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_,
-          this.interactions_.ClickedDisabledNextButtonAndNavigatedAway,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordClickedDisabledNextButtonAndChoseSkip() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_,
+        this.interactions_.ClickedDisabledNextButtonAndChoseSkip,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordClickedDisabledNextButtonAndChoseSkip() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_,
-          this.interactions_.ClickedDisabledNextButtonAndChoseSkip,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordClickedDisabledNextButtonAndChoseNext() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_,
+        this.interactions_.ClickedDisabledNextButtonAndChoseNext,
+        Object.keys(this.interactions_).length);
+  }
 
-    /** @override */
-    recordClickedDisabledNextButtonAndChoseNext() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_,
-          this.interactions_.ClickedDisabledNextButtonAndChoseNext,
-          Object.keys(this.interactions_).length);
-    }
+  /** @override */
+  recordNavigatedAwayThroughBrowserHistory() {
+    chrome.metricsPrivate.recordEnumerationValue(
+        this.interactionMetric_,
+        this.interactions_.NavigatedAwayThroughBrowserHistory,
+        Object.keys(this.interactions_).length);
+  }
+}
 
-    /** @override */
-    recordNavigatedAwayThroughBrowserHistory() {
-      chrome.metricsPrivate.recordEnumerationValue(
-          this.interactionMetric_,
-          this.interactions_.NavigatedAwayThroughBrowserHistory,
-          Object.keys(this.interactions_).length);
+export class ModuleMetricsManager {
+  /** @param {ModuleMetricsProxy} metricsProxy */
+  constructor(metricsProxy) {
+    this.metricsProxy_ = metricsProxy;
+
+    this.options_ = {
+      didNothing: {
+        andNavigatedAway: metricsProxy.recordDidNothingAndNavigatedAway,
+        andChoseSkip: metricsProxy.recordDidNothingAndChoseSkip,
+        andChoseNext: metricsProxy.recordDidNothingAndChoseNext,
+      },
+      choseAnOption: {
+        andNavigatedAway: metricsProxy.recordChoseAnOptionAndNavigatedAway,
+        andChoseSkip: metricsProxy.recordChoseAnOptionAndChoseSkip,
+        andChoseNext: metricsProxy.recordChoseAnOptionAndChoseNext,
+      },
+      clickedDisabledNextButton: {
+        andNavigatedAway:
+            metricsProxy.recordClickedDisabledNextButtonAndNavigatedAway,
+        andChoseSkip: metricsProxy.recordClickedDisabledNextButtonAndChoseSkip,
+        andChoseNext: metricsProxy.recordClickedDisabledNextButtonAndChoseNext,
+      },
+    };
+
+    this.firstPart = this.options_.didNothing;
+  }
+
+  recordPageInitialized() {
+    this.metricsProxy_.recordPageShown();
+    this.firstPart = this.options_.didNothing;
+  }
+
+  recordClickedOption() {
+    // Only overwrite this.firstPart if it's not overwritten already
+    if (this.firstPart == this.options_.didNothing) {
+      this.firstPart = this.options_.choseAnOption;
     }
   }
 
-  class ModuleMetricsManager {
-    /** @param {welcome.ModuleMetricsProxy} metricsProxy */
-    constructor(metricsProxy) {
-      this.metricsProxy_ = metricsProxy;
-
-      this.options_ = {
-        didNothing: {
-          andNavigatedAway: metricsProxy.recordDidNothingAndNavigatedAway,
-          andChoseSkip: metricsProxy.recordDidNothingAndChoseSkip,
-          andChoseNext: metricsProxy.recordDidNothingAndChoseNext,
-        },
-        choseAnOption: {
-          andNavigatedAway: metricsProxy.recordChoseAnOptionAndNavigatedAway,
-          andChoseSkip: metricsProxy.recordChoseAnOptionAndChoseSkip,
-          andChoseNext: metricsProxy.recordChoseAnOptionAndChoseNext,
-        },
-        clickedDisabledNextButton: {
-          andNavigatedAway:
-              metricsProxy.recordClickedDisabledNextButtonAndNavigatedAway,
-          andChoseSkip:
-              metricsProxy.recordClickedDisabledNextButtonAndChoseSkip,
-          andChoseNext:
-              metricsProxy.recordClickedDisabledNextButtonAndChoseNext,
-        },
-      };
-
-      this.firstPart = this.options_.didNothing;
-    }
-
-    recordPageInitialized() {
-      this.metricsProxy_.recordPageShown();
-      this.firstPart = this.options_.didNothing;
-    }
-
-    recordClickedOption() {
-      // Only overwrite this.firstPart if it's not overwritten already
-      if (this.firstPart == this.options_.didNothing) {
-        this.firstPart = this.options_.choseAnOption;
-      }
-    }
-
-    recordClickedDisabledButton() {
-      // Only overwrite this.firstPart if it's not overwritten already
-      if (this.firstPart == this.options_.didNothing) {
-        this.firstPart = this.options_.clickedDisabledNextButton;
-      }
-    }
-
-    recordNoThanks() {
-      this.firstPart.andChoseSkip.call(this.metricsProxy_);
-    }
-
-    recordGetStarted() {
-      this.firstPart.andChoseNext.call(this.metricsProxy_);
-    }
-
-    recordNavigatedAway() {
-      this.firstPart.andNavigatedAway.call(this.metricsProxy_);
-    }
-
-    recordBrowserBackOrForward() {
-      this.metricsProxy_.recordNavigatedAwayThroughBrowserHistory();
+  recordClickedDisabledButton() {
+    // Only overwrite this.firstPart if it's not overwritten already
+    if (this.firstPart == this.options_.didNothing) {
+      this.firstPart = this.options_.clickedDisabledNextButton;
     }
   }
 
-  return {
-    ModuleMetricsManager: ModuleMetricsManager,
-    ModuleMetricsProxyImpl: ModuleMetricsProxyImpl,
-    ModuleMetricsProxy: ModuleMetricsProxy,
-    NuxGoogleAppsInteractions: NuxGoogleAppsInteractions,
-    NuxNtpBackgroundInteractions: NuxNtpBackgroundInteractions,
-  };
-});
+  recordNoThanks() {
+    this.firstPart.andChoseSkip.call(this.metricsProxy_);
+  }
+
+  recordGetStarted() {
+    this.firstPart.andChoseNext.call(this.metricsProxy_);
+  }
+
+  recordNavigatedAway() {
+    this.firstPart.andNavigatedAway.call(this.metricsProxy_);
+  }
+
+  recordBrowserBackOrForward() {
+    this.metricsProxy_.recordNavigatedAwayThroughBrowserHistory();
+  }
+}
diff --git a/chrome/browser/resources/welcome/shared/navi_colors_css.html b/chrome/browser/resources/welcome/shared/navi_colors_css.html
index 99846f3..403ee035 100644
--- a/chrome/browser/resources/welcome/shared/navi_colors_css.html
+++ b/chrome/browser/resources/welcome/shared/navi_colors_css.html
@@ -1,51 +1,44 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<template>
+  <style>
+    :host {
+      --navi-border-color: var(--google-grey-refresh-300);
+      --navi-check-icon-color: lightgrey;
+      --navi-keyboard-focus-color: rgba(var(--google-blue-600-rgb), .4);
+      --navi-option-box-shadow:
+          0 1px 2px 0 rgba(var(--google-grey-800-rgb), .3),
+          0 3px 6px 2px rgba(var(--google-grey-800-rgb), .15);
+      --navi-option-icon-shadow-color: var(--google-grey-refresh-100);
+      --navi-shape-blue-color: rgb(26, 115, 232);  /* #1A73E8 */
+      --navi-shape-green-color: rgb(49, 167, 83); /* #31A753 */
+      --navi-shape-grey-color: rgb(241, 243, 244); /* #F1F3F4 */
+      --navi-shape-red-color: rgb(233, 66, 53); /* #E94235 */
+      --navi-shape-yellow-dots-color: rgb(253, 214, 99); /* #FDD663 */
+      --navi-shape-yellow-semicircle-color: rgb(250, 207, 76); /* #FACF4C */
+      --navi-step-indicator-active-color:
+          rgba(var(--google-blue-600-rgb), .5);
+      --navi-step-indicator-color: var(--google-grey-200);
+      --navi-wallpaper-text-color: var(--google-grey-refresh-700);
+    }
 
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="chrome://resources/cr_elements/md_select_css.html">
-
-<dom-module id="navi-colors-css">
-  <template>
-    <style>
+    @media (prefers-color-scheme: dark) {
       :host {
-        --navi-border-color: var(--google-grey-refresh-300);
-        --navi-check-icon-color: lightgrey;
-        --navi-keyboard-focus-color: rgba(var(--google-blue-600-rgb), .4);
-        --navi-option-box-shadow:
-            0 1px 2px 0 rgba(var(--google-grey-800-rgb), .3),
-            0 3px 6px 2px rgba(var(--google-grey-800-rgb), .15);
-        --navi-option-icon-shadow-color: var(--google-grey-refresh-100);
-        --navi-shape-blue-color: rgb(26, 115, 232);  /* #1A73E8 */
-        --navi-shape-green-color: rgb(49, 167, 83); /* #31A753 */
-        --navi-shape-grey-color: rgb(241, 243, 244); /* #F1F3F4 */
-        --navi-shape-red-color: rgb(233, 66, 53); /* #E94235 */
-        --navi-shape-yellow-dots-color: rgb(253, 214, 99); /* #FDD663 */
-        --navi-shape-yellow-semicircle-color: rgb(250, 207, 76); /* #FACF4C */
-        --navi-step-indicator-active-color:
-            rgba(var(--google-blue-600-rgb), .5);
-        --navi-step-indicator-color: var(--google-grey-200);
-        --navi-wallpaper-text-color: var(--google-grey-refresh-700);
+        --navi-border-color: var(--google-grey-refresh-700);
+        --navi-check-icon-color: var(--google-grey-refresh-700);
+        --navi-keyboard-focus-color:
+            rgba(var(--google-blue-refresh-300-rgb), .5);
+        --navi-option-box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .3),
+                                  0 3px 6px 2px rgba(0, 0, 0, .15);
+        --navi-option-icon-shadow-color: var(--google-grey-refresh-700);
+        --navi-shape-blue-color: rgb(138, 180, 248); /* #8AB4F8 */
+        --navi-shape-green-color: rgb(129, 201, 149); /* #81C995 */
+        --navi-shape-grey-color: rgb(154, 160, 166); /* #9AA0A6 */
+        --navi-shape-red-color: rgb(238, 103, 92); /* #EE675C */
+        /* --navi-shape-yellow-dots-color is same color in dark mode */
+        --navi-shape-yellow-semicircle-color: rgb(253, 214, 99); /* #FDD663 */
+        --navi-step-indicator-active-color: var(--google-blue-refresh-300);
+        --navi-step-indicator-color: var(--google-grey-refresh-500);
+        --navi-wallpaper-text-color: var(--google-grey-200);
       }
-
-      @media (prefers-color-scheme: dark) {
-        :host {
-          --navi-border-color: var(--google-grey-refresh-700);
-          --navi-check-icon-color: var(--google-grey-refresh-700);
-          --navi-keyboard-focus-color:
-              rgba(var(--google-blue-refresh-300-rgb), .5);
-          --navi-option-box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .3),
-                                    0 3px 6px 2px rgba(0, 0, 0, .15);
-          --navi-option-icon-shadow-color: var(--google-grey-refresh-700);
-          --navi-shape-blue-color: rgb(138, 180, 248); /* #8AB4F8 */
-          --navi-shape-green-color: rgb(129, 201, 149); /* #81C995 */
-          --navi-shape-grey-color: rgb(154, 160, 166); /* #9AA0A6 */
-          --navi-shape-red-color: rgb(238, 103, 92); /* #EE675C */
-          /* --navi-shape-yellow-dots-color is same color in dark mode */
-          --navi-shape-yellow-semicircle-color: rgb(253, 214, 99); /* #FDD663 */
-          --navi-step-indicator-active-color: var(--google-blue-refresh-300);
-          --navi-step-indicator-color: var(--google-grey-refresh-500);
-          --navi-wallpaper-text-color: var(--google-grey-200);
-        }
-      }
-    </style>
-  </template>
-</dom-module>
+    }
+  </style>
+</template>
diff --git a/chrome/browser/resources/welcome/shared/navi_colors_css.js b/chrome/browser/resources/welcome/shared/navi_colors_css.js
new file mode 100644
index 0000000..f803f2d
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/navi_colors_css.js
@@ -0,0 +1,12 @@
+// 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.
+
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import 'chrome://resources/cr_elements/md_select_css.m.js';
+
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const styleElement = document.createElement('dom-module');
+styleElement.innerHTML = `{__html_template__}`;
+styleElement.register('navi-colors-css');
diff --git a/chrome/browser/resources/welcome/shared/nux_types.js b/chrome/browser/resources/welcome/shared/nux_types.js
index d8fd86d..3c8be49 100644
--- a/chrome/browser/resources/welcome/shared/nux_types.js
+++ b/chrome/browser/resources/welcome/shared/nux_types.js
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.exportPath('welcome');
-
 /**
  * @typedef {{
  *   id: number,
@@ -12,7 +10,7 @@
  *   url: string,
  * }}
  */
-welcome.BookmarkListItem;
+export let BookmarkListItem;
 
 /**
  * @typedef {{
@@ -20,7 +18,7 @@
  *   active: number,
  * }}
  */
-welcome.stepIndicatorModel;
+export let stepIndicatorModel;
 
 /**
  * TODO(hcarmona): somehow reuse from
@@ -32,4 +30,4 @@
  *   isUnknownError: boolean,
  * }};
  */
-welcome.DefaultBrowserInfo;
+export let DefaultBrowserInfo;
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.html b/chrome/browser/resources/welcome/shared/onboarding_background.html
index 643c8ef..e51c6d9 100644
--- a/chrome/browser/resources/welcome/shared/onboarding_background.html
+++ b/chrome/browser/resources/welcome/shared/onboarding_background.html
@@ -1,187 +1,180 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style>
+  @keyframes blue-circle-anim-x {
+    50% {
+      animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+      transform: translateX(44px);
+    }
+  }
 
-<dom-module id="onboarding-background">
-  <template>
-    <style>
-      @keyframes blue-circle-anim-x {
-        50% {
-          animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-          transform: translateX(44px);
-        }
-      }
+  @keyframes blue-circle-anim-y {
+    50% {
+      animation-timing-function: cubic-bezier(0.55, 0, 0.2, 1);
+      transform: translateY(17px);
+    }
+  }
 
-      @keyframes blue-circle-anim-y {
-        50% {
-          animation-timing-function: cubic-bezier(0.55, 0, 0.2, 1);
-          transform: translateY(17px);
-        }
-      }
+  @keyframes green-rectangle-anim {
+    100% {
+      transform: rotate(360deg);
+    }
+  }
 
-      @keyframes green-rectangle-anim {
-        100% {
-          transform: rotate(360deg);
-        }
-      }
+  @keyframes red-triangle-anim {
+    50% {
+      animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+      transform: translateY(25px) rotate(-53deg);
+    }
+  }
 
-      @keyframes red-triangle-anim {
-        50% {
-          animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-          transform: translateY(25px) rotate(-53deg);
-        }
-      }
+  @keyframes yellow-semicircle-anim {
+    40% {
+      animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+      transform: translateY(40px) rotate(-1deg);
+    }
+  }
 
-      @keyframes yellow-semicircle-anim {
-        40% {
-          animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-          transform: translateY(40px) rotate(-1deg);
-        }
-      }
+  @keyframes grey-rounded-rectangle-anim {
+    65% {
+      animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+      transform: translateY(-48px) rotate(-75deg);
+    }
+  }
 
-      @keyframes grey-rounded-rectangle-anim {
-        65% {
-          animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-          transform: translateY(-48px) rotate(-75deg);
-        }
-      }
+  :host {
+    bottom: 0;
+    left: 0;
+    margin: auto;
+    overflow: hidden;
+    position: absolute;
+    right: 0;
+    top: 0;
+    z-index: -1;
+  }
 
-      :host {
-        bottom: 0;
-        left: 0;
-        margin: auto;
-        overflow: hidden;
-        position: absolute;
-        right: 0;
-        top: 0;
-        z-index: -1;
-      }
+  /* The container is necessary in order for :host to hide overflowing SVGs
+     correctly without disturbing their positions. */
+  #container {
+    height: 100%;
+    left: 50%;
+    min-height: 700px;
+    min-width: 1024px;
+    position: absolute;
+    top: 50%;
+    transform: translate(-50%, -50%);
+    width: 100%;
+  }
 
-      /* The container is necessary in order for :host to hide overflowing SVGs
-         correctly without disturbing their positions. */
-      #container {
-        height: 100%;
-        left: 50%;
-        min-height: 700px;
-        min-width: 1024px;
-        position: absolute;
-        top: 50%;
-        transform: translate(-50%, -50%);
-        width: 100%;
-      }
+  img,
+  span {
+    position: absolute;
+  }
 
-      img,
-      span {
-        position: absolute;
-      }
+  #blue-circle-container {
+    animation: blue-circle-anim-x 9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+    left: calc(13% - 50px);  /* Relative to #yellow-dots. */
+    top: calc(18% - 26px);  /* Relative to #yellow-dots. */
+  }
 
-      #blue-circle-container {
-        animation: blue-circle-anim-x 9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
-        left: calc(13% - 50px);  /* Relative to #yellow-dots. */
-        top: calc(18% - 26px);  /* Relative to #yellow-dots. */
-      }
+  #blue-circle-container::after {
+    -webkit-mask: url(../images/background_svgs/blue_circle.svg) no-repeat
+        top left;
+    animation: blue-circle-anim-y 9s cubic-bezier(0.25, 0, 0.2, 1) infinite;
+    background-color: var(--navi-shape-blue-color);
+    content: ' '; /* Content needs to be non-empty */
+    height: 43px;
+    position: absolute;
+    width: 43px;
+  }
 
-      #blue-circle-container::after {
-        -webkit-mask: url(../images/background_svgs/blue_circle.svg) no-repeat
-            top left;
-        animation: blue-circle-anim-y 9s cubic-bezier(0.25, 0, 0.2, 1) infinite;
-        background-color: var(--navi-shape-blue-color);
-        content: ' '; /* Content needs to be non-empty */
-        height: 43px;
-        position: absolute;
-        width: 43px;
-      }
+  #yellow-dots {
+    -webkit-mask: url(../images/background_svgs/yellow_dots.svg) no-repeat
+        top left;
+    background-color: var(--navi-shape-yellow-dots-color);
+    content: ' '; /* Content needs to be non-empty */
+    height: 57px;
+    left: 13%;
+    top: 18%;
+    width: 76px;
+  }
 
-      #yellow-dots {
-        -webkit-mask: url(../images/background_svgs/yellow_dots.svg) no-repeat
-            top left;
-        background-color: var(--navi-shape-yellow-dots-color);
-        content: ' '; /* Content needs to be non-empty */
-        height: 57px;
-        left: 13%;
-        top: 18%;
-        width: 76px;
-      }
+  #grey-rounded-rectangle {
+    -webkit-mask: url(../images/background_svgs/grey_rounded_rectangle.svg)
+        no-repeat top left;
+    animation: grey-rounded-rectangle-anim 10s cubic-bezier(0.4, 0, 0.2, 1)
+        infinite;
+    background-color: var(--navi-shape-grey-color);
+    content: ' '; /* Content needs to be non-empty */
+    height: 132px;
+    left: -42px;
+    top: 45%;
+    width: 132px;
+  }
 
-      #grey-rounded-rectangle {
-        -webkit-mask: url(../images/background_svgs/grey_rounded_rectangle.svg)
-            no-repeat top left;
-        animation: grey-rounded-rectangle-anim 10s cubic-bezier(0.4, 0, 0.2, 1)
-            infinite;
-        background-color: var(--navi-shape-grey-color);
-        content: ' '; /* Content needs to be non-empty */
-        height: 132px;
-        left: -42px;
-        top: 45%;
-        width: 132px;
-      }
+  #red-triangle {
+    -webkit-mask: url(../images/background_svgs/red_triangle.svg) no-repeat
+        bottom left;
+    animation: red-triangle-anim 9.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+    background-color: var(--navi-shape-red-color);
+    bottom: 15%;
+    content: ' '; /* Content needs to be non-empty */
+    height: 74px;
+    left: 12%;
+    width: 65px;
+  }
 
-      #red-triangle {
-        -webkit-mask: url(../images/background_svgs/red_triangle.svg) no-repeat
-            bottom left;
-        animation: red-triangle-anim 9.6s cubic-bezier(0.4, 0, 0.2, 1) infinite;
-        background-color: var(--navi-shape-red-color);
-        bottom: 15%;
-        content: ' '; /* Content needs to be non-empty */
-        height: 74px;
-        left: 12%;
-        width: 65px;
-      }
+  #yellow-semicircle {
+    -webkit-mask: url(../images/background_svgs/yellow_semicircle.svg)
+        no-repeat top right;
+    animation: yellow-semicircle-anim 10s cubic-bezier(0.4, 0, 0.2, 1)
+        infinite;
+    background-color: var(--navi-shape-yellow-semicircle-color);
+    content: ' '; /* Content needs to be non-empty */
+    height: 171px;
+    right: 28.5%;
+    top: -50px;
+    transform: rotate(-7deg);
+    width: 211px;
+  }
 
-      #yellow-semicircle {
-        -webkit-mask: url(../images/background_svgs/yellow_semicircle.svg)
-            no-repeat top right;
-        animation: yellow-semicircle-anim 10s cubic-bezier(0.4, 0, 0.2, 1)
-            infinite;
-        background-color: var(--navi-shape-yellow-semicircle-color);
-        content: ' '; /* Content needs to be non-empty */
-        height: 171px;
-        right: 28.5%;
-        top: -50px;
-        transform: rotate(-7deg);
-        width: 211px;
-      }
+  #green-rectangle {
+    -webkit-mask: url(../images/background_svgs/green_rectangle.svg)
+        no-repeat bottom right;
+    animation: green-rectangle-anim 40s infinite linear;
+    background-color: var(--navi-shape-green-color);
+    bottom: 8%;
+    content: ' '; /* Content needs to be non-empty */
+    height: 371px;
+    right: -255px;
+    width: 371px;
+  }
 
-      #green-rectangle {
-        -webkit-mask: url(../images/background_svgs/green_rectangle.svg)
-            no-repeat bottom right;
-        animation: green-rectangle-anim 40s infinite linear;
-        background-color: var(--navi-shape-green-color);
-        bottom: 8%;
-        content: ' '; /* Content needs to be non-empty */
-        height: 371px;
-        right: -255px;
-        width: 371px;
-      }
+  #grey-oval {
+    -webkit-mask: url(../images/background_svgs/grey_oval.svg) no-repeat
+        bottom right;
+    background-color: var(--navi-shape-grey-color);
+    bottom: calc(8% + 24px);  /* Relative to green-rectangle. */
+    content: ' '; /* Content needs to be non-empty */
+    height: 100px;
+    mix-blend-mode: multiply;
+    right: 48px;
+    width: 100px;
+  }
 
-      #grey-oval {
-        -webkit-mask: url(../images/background_svgs/grey_oval.svg) no-repeat
-            bottom right;
-        background-color: var(--navi-shape-grey-color);
-        bottom: calc(8% + 24px);  /* Relative to green-rectangle. */
-        content: ' '; /* Content needs to be non-empty */
-        height: 100px;
-        mix-blend-mode: multiply;
-        right: 48px;
-        width: 100px;
-      }
-
-      @media (prefers-color-scheme: dark) {
-        #grey-oval {
-          mix-blend-mode: screen;
-        }
-      }
-    </style>
-    <div id="container">
-      <!-- Using span as container for an :after element that actually contains
-           the blue-circle svg, because the animation needs to curve so x and y
-           needs to be animated separately. -->
-      <span id="blue-circle-container"></span>
-      <span id="green-rectangle"></span>
-      <span id="grey-oval"></span>
-      <span id="grey-rounded-rectangle"></span>
-      <span id="red-triangle"></span>
-      <span id="yellow-dots"></span>
-      <span id="yellow-semicircle"></span>
-    </div>
-  </template>
-  <script src="onboarding_background.js"></script>
-</dom-module>
+  @media (prefers-color-scheme: dark) {
+    #grey-oval {
+      mix-blend-mode: screen;
+    }
+  }
+</style>
+<div id="container">
+  <!-- Using span as container for an :after element that actually contains
+       the blue-circle svg, because the animation needs to curve so x and y
+       needs to be animated separately. -->
+  <span id="blue-circle-container"></span>
+  <span id="green-rectangle"></span>
+  <span id="grey-oval"></span>
+  <span id="grey-rounded-rectangle"></span>
+  <span id="red-triangle"></span>
+  <span id="yellow-dots"></span>
+  <span id="yellow-semicircle"></span>
+</div>
diff --git a/chrome/browser/resources/welcome/shared/onboarding_background.js b/chrome/browser/resources/welcome/shared/onboarding_background.js
index 12358fa..98f2999 100644
--- a/chrome/browser/resources/welcome/shared/onboarding_background.js
+++ b/chrome/browser/resources/welcome/shared/onboarding_background.js
@@ -6,6 +6,11 @@
  * @fileoverview This element contains a set of SVGs that together acts as an
  * animated and responsive background for any page that contains it.
  */
+
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
 Polymer({
   is: 'onboarding-background',
-});
\ No newline at end of file
+
+  _template: html`{__html_template__}`,
+});
diff --git a/chrome/browser/resources/welcome/shared/splash_pages_shared_css.html b/chrome/browser/resources/welcome/shared/splash_pages_shared_css.html
index 3d662b4..8d2c1b7 100644
--- a/chrome/browser/resources/welcome/shared/splash_pages_shared_css.html
+++ b/chrome/browser/resources/welcome/shared/splash_pages_shared_css.html
@@ -1,55 +1,48 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<template>
+  <style include="navi-colors-css">
+    #container {
+      align-items: center;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      margin: auto;
+      min-height: 100%;
+      min-width: 800px;
+      position: relative;
+    }
 
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="navi_colors_css.html">
+    h1 {
+      color: var(--cr-primary-text-color);
+      font-size: 4rem;
+      margin-bottom: 40px;
+      margin-top: 16px;
+      text-align: center;
+    }
 
-<dom-module id="splash-pages-shared-css">
-  <template>
-    <style include="navi-colors-css">
-      #container {
-        align-items: center;
-        display: flex;
-        flex-direction: column;
-        justify-content: center;
-        margin: auto;
-        min-height: 100%;
-        min-width: 800px;
-        position: relative;
-      }
+    h2 {
+      color: var(--cr-secondary-text-color);
+      font-size: 1.5rem;
+      font-weight: 500;
+      line-height: 2.25rem;
+      margin: 0;
+      opacity: 0.8;
+      text-align: center;
+    }
 
-      h1 {
-        color: var(--cr-primary-text-color);
-        font-size: 4rem;
-        margin-bottom: 40px;
-        margin-top: 16px;
-        text-align: center;
-      }
+    cr-button {
+      font-size: 1rem;
+      height: 3rem;
+      padding-bottom: 12px;
+      padding-top: 12px;
+      text-align: center;
+      white-space: nowrap;
+      width: 256px;
+    }
 
-      h2 {
-        color: var(--cr-secondary-text-color);
-        font-size: 1.5rem;
-        font-weight: 500;
-        line-height: 2.25rem;
-        margin: 0;
-        opacity: 0.8;
-        text-align: center;
-      }
-
-      cr-button {
-        font-size: 1rem;
-        height: 3rem;
-        padding-bottom: 12px;
-        padding-top: 12px;
-        text-align: center;
-        white-space: nowrap;
-        width: 256px;
-      }
-
-      .action-link {
-        font-size: 1rem;
-        font-weight: 500;
-        margin-top: 24px;
-      }
-    </style>
-  </template>
-</dom-module>
+    .action-link {
+      font-size: 1rem;
+      font-weight: 500;
+      margin-top: 24px;
+    }
+  </style>
+</template>
diff --git a/chrome/browser/resources/welcome/shared/splash_pages_shared_css.js b/chrome/browser/resources/welcome/shared/splash_pages_shared_css.js
new file mode 100644
index 0000000..896d7adc6
--- /dev/null
+++ b/chrome/browser/resources/welcome/shared/splash_pages_shared_css.js
@@ -0,0 +1,12 @@
+// 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.
+
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import './navi_colors_css.js';
+
+import 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+const styleElement = document.createElement('dom-module');
+styleElement.innerHTML = `{__html_template__}`;
+styleElement.register('splash-pages-shared-css');
diff --git a/chrome/browser/resources/welcome/shared/step_indicator.html b/chrome/browser/resources/welcome/shared/step_indicator.html
index 4123eb5..4caec81 100644
--- a/chrome/browser/resources/welcome/shared/step_indicator.html
+++ b/chrome/browser/resources/welcome/shared/step_indicator.html
@@ -1,32 +1,22 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="navi-colors-css">
+  :host {
+    align-items: center;
+    display: flex;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/shared_vars_css.html">
-<link rel="import" href="navi_colors_css.html">
+  span {
+    background: var(--navi-step-indicator-color);
+    border-radius: 50%;
+    display: inline-block;
+    height: 8px;
+    margin: 0 4px;
+    width: 8px;
+  }
 
-<dom-module id="step-indicator">
-  <template>
-    <style include="navi-colors-css">
-      :host {
-        align-items: center;
-        display: flex;
-      }
-
-      span {
-        background: var(--navi-step-indicator-color);
-        border-radius: 50%;
-        display: inline-block;
-        height: 8px;
-        margin: 0 4px;
-        width: 8px;
-      }
-
-      span.active {
-        background: var(--navi-step-indicator-active-color);
-      }
-    </style>
-    <template is="dom-repeat" items="[[dots_]]">
-      <span class$="[[getActiveClass_(index, model.active)]]"></span>
-    </template>
-  </template>
-  <script src="step_indicator.js"></script>
-</dom-module>
+  span.active {
+    background: var(--navi-step-indicator-active-color);
+  }
+</style>
+<template is="dom-repeat" items="[[dots_]]">
+  <span class$="[[getActiveClass_(index, model.active)]]"></span>
+</template>
diff --git a/chrome/browser/resources/welcome/shared/step_indicator.js b/chrome/browser/resources/welcome/shared/step_indicator.js
index 4af45dc8..8d4b45c 100644
--- a/chrome/browser/resources/welcome/shared/step_indicator.js
+++ b/chrome/browser/resources/welcome/shared/step_indicator.js
@@ -6,11 +6,20 @@
  * @fileoverview This element contains a set of SVGs that together acts as an
  * animated and responsive background for any page that contains it.
  */
+import 'chrome://resources/cr_elements/shared_vars_css.m.js';
+import './navi_colors_css.js';
+
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {stepIndicatorModel} from './nux_types.js';
+
 Polymer({
   is: 'step-indicator',
 
+  _template: html`{__html_template__}`,
+
   properties: {
-    /** @type {welcome.stepIndicatorModel} */
+    /** @type {stepIndicatorModel} */
     model: Object,
 
     /** @private */
diff --git a/chrome/browser/resources/welcome/signin_view.html b/chrome/browser/resources/welcome/signin_view.html
index d5fc11e6..61fa2673 100644
--- a/chrome/browser/resources/welcome/signin_view.html
+++ b/chrome/browser/resources/welcome/signin_view.html
@@ -1,38 +1,20 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="animations action-link-style splash-pages-shared-css">
+  onboarding-background {
+    --animation-delay: 150ms;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_button/cr_button.html">
-<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
-<link rel="import" href="navigation_behavior.html">
-<link rel="import" href="shared/action_link_style_css.html">
-<link rel="import" href="shared/animations_css.html">
-<link rel="import" href="shared/i18n_setup.html">
-<link rel="import" href="shared/onboarding_background.html">
-<link rel="import" href="shared/splash_pages_shared_css.html">
-<link rel="import" href="signin_view_proxy.html">
-<link rel="import" href="welcome_browser_proxy.html">
-
-<dom-module id="signin-view">
-  <template>
-    <style include="animations action-link-style splash-pages-shared-css">
-      onboarding-background {
-        --animation-delay: 150ms;
-      }
-
-      h1 {
-        outline: none;
-      }
-    </style>
-    <div id="container">
-      <onboarding-background class="fade-in"></onboarding-background>
-      <h2>$i18n{signInSubHeader}</h2>
-      <h1 tabindex="-1">$i18n{signInHeader}</h1>
-      <cr-button class="action-button" on-click="onSignInClick_">
-        $i18n{signIn}
-      </cr-button>
-      <button class="action-link" on-click="onNoThanksClick_">
-        $i18n{noThanks}
-      </button>
-    </div>
-  </template>
-  <script src="signin_view.js"></script>
-</dom-module>
+  h1 {
+    outline: none;
+  }
+</style>
+<div id="container">
+  <onboarding-background class="fade-in"></onboarding-background>
+  <h2>$i18n{signInSubHeader}</h2>
+  <h1 tabindex="-1">$i18n{signInHeader}</h1>
+  <cr-button class="action-button" on-click="onSignInClick_">
+    $i18n{signIn}
+  </cr-button>
+  <button class="action-link" on-click="onNoThanksClick_">
+    $i18n{noThanks}
+  </button>
+</div>
diff --git a/chrome/browser/resources/welcome/signin_view.js b/chrome/browser/resources/welcome/signin_view.js
index 15096824..b0c8bef 100644
--- a/chrome/browser/resources/welcome/signin_view.js
+++ b/chrome/browser/resources/welcome/signin_view.js
@@ -2,24 +2,40 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
+import 'chrome://resources/polymer/v3_0/paper-styles/color.js';
+import './shared/action_link_style_css.js';
+import './shared/animations_css.js';
+import './shared/onboarding_background.js';
+import './shared/splash_pages_shared_css.js';
+import '../strings.m.js';
+
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from './navigation_behavior.js';
+import {SigninViewProxy, SigninViewProxyImpl} from './signin_view_proxy.js';
+import {WelcomeBrowserProxy, WelcomeBrowserProxyImpl} from './welcome_browser_proxy.js';
+
 Polymer({
   is: 'signin-view',
 
-  behaviors: [welcome.NavigationBehavior],
+  _template: html`{__html_template__}`,
+
+  behaviors: [NavigationBehavior],
 
   /** @private {boolean} */
   finalized_: false,
 
-  /** @private {?welcome.WelcomeBrowserProxy} */
+  /** @private {?WelcomeBrowserProxy} */
   welcomeBrowserProxy_: null,
 
-  /** @private {?welcome.SigninViewProxy} */
+  /** @private {?SigninViewProxy} */
   signinViewProxy_: null,
 
   /** @override */
   ready: function() {
-    this.welcomeBrowserProxy_ = welcome.WelcomeBrowserProxyImpl.getInstance();
-    this.signinViewProxy_ = welcome.SigninViewProxyImpl.getInstance();
+    this.welcomeBrowserProxy_ = WelcomeBrowserProxyImpl.getInstance();
+    this.signinViewProxy_ = SigninViewProxyImpl.getInstance();
   },
 
   onRouteEnter: function() {
diff --git a/chrome/browser/resources/welcome/signin_view_proxy.html b/chrome/browser/resources/welcome/signin_view_proxy.html
deleted file mode 100644
index 5d26af4..0000000
--- a/chrome/browser/resources/welcome/signin_view_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="signin_view_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/signin_view_proxy.js b/chrome/browser/resources/welcome/signin_view_proxy.js
index 0e731b9..43266ea4 100644
--- a/chrome/browser/resources/welcome/signin_view_proxy.js
+++ b/chrome/browser/resources/welcome/signin_view_proxy.js
@@ -2,79 +2,74 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome', function() {
-  const NUX_SIGNIN_VIEW_INTERACTION_METRIC_NAME =
-      'FirstRun.NewUserExperience.SignInInterstitialInteraction';
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
+const NUX_SIGNIN_VIEW_INTERACTION_METRIC_NAME =
+    'FirstRun.NewUserExperience.SignInInterstitialInteraction';
+
+/**
+ * NuxSignInInterstitialInteractions enum.
+ * These values are persisted to logs and should not be renumbered or re-used.
+ * See tools/metrics/histograms/enums.xml.
+ * @enum {number}
+ */
+const NuxSignInInterstitialInteractions = {
+  PageShown: 0,
+  NavigatedAway: 1,
+  Skip: 2,
+  SignIn: 3,
+  NavigatedAwayThroughBrowserHistory: 4,
+};
+
+const NUX_SIGNIN_VIEW_INTERACTIONS_COUNT =
+    Object.keys(NuxSignInInterstitialInteractions).length;
+
+/** @interface */
+export class SigninViewProxy {
+  recordPageShown() {}
+  recordNavigatedAway() {}
+  recordNavigatedAwayThroughBrowserHistory() {}
+  recordSkip() {}
+  recordSignIn() {}
+}
+
+/** @implements {SigninViewProxy} */
+export class SigninViewProxyImpl {
+  /** @override */
+  recordPageShown() {
+    this.recordInteraction_(NuxSignInInterstitialInteractions.PageShown);
+  }
+
+  /** @override */
+  recordNavigatedAway() {
+    this.recordInteraction_(NuxSignInInterstitialInteractions.NavigatedAway);
+  }
+
+  /** @override */
+  recordNavigatedAwayThroughBrowserHistory() {
+    this.recordInteraction_(
+        NuxSignInInterstitialInteractions.NavigatedAwayThroughBrowserHistory);
+  }
+
+  /** @override */
+  recordSkip() {
+    this.recordInteraction_(NuxSignInInterstitialInteractions.Skip);
+  }
+
+  /** @override */
+  recordSignIn() {
+    this.recordInteraction_(NuxSignInInterstitialInteractions.SignIn);
+  }
 
   /**
-   * NuxSignInInterstitialInteractions enum.
-   * These values are persisted to logs and should not be renumbered or re-used.
-   * See tools/metrics/histograms/enums.xml.
-   * @enum {number}
+   * @param {number} interaction
+   * @private
    */
-  const NuxSignInInterstitialInteractions = {
-    PageShown: 0,
-    NavigatedAway: 1,
-    Skip: 2,
-    SignIn: 3,
-    NavigatedAwayThroughBrowserHistory: 4,
-  };
-
-  const NUX_SIGNIN_VIEW_INTERACTIONS_COUNT =
-      Object.keys(NuxSignInInterstitialInteractions).length;
-
-  /** @interface */
-  class SigninViewProxy {
-    recordPageShown() {}
-    recordNavigatedAway() {}
-    recordNavigatedAwayThroughBrowserHistory() {}
-    recordSkip() {}
-    recordSignIn() {}
+  recordInteraction_(interaction) {
+    chrome.metricsPrivate.recordEnumerationValue(
+        NUX_SIGNIN_VIEW_INTERACTION_METRIC_NAME, interaction,
+        NUX_SIGNIN_VIEW_INTERACTIONS_COUNT);
   }
+}
 
-  /** @implements {welcome.SigninViewProxy} */
-  class SigninViewProxyImpl {
-    /** @override */
-    recordPageShown() {
-      this.recordInteraction_(NuxSignInInterstitialInteractions.PageShown);
-    }
-
-    /** @override */
-    recordNavigatedAway() {
-      this.recordInteraction_(NuxSignInInterstitialInteractions.NavigatedAway);
-    }
-
-    /** @override */
-    recordNavigatedAwayThroughBrowserHistory() {
-      this.recordInteraction_(
-          NuxSignInInterstitialInteractions.NavigatedAwayThroughBrowserHistory);
-    }
-
-    /** @override */
-    recordSkip() {
-      this.recordInteraction_(NuxSignInInterstitialInteractions.Skip);
-    }
-
-    /** @override */
-    recordSignIn() {
-      this.recordInteraction_(NuxSignInInterstitialInteractions.SignIn);
-    }
-
-    /**
-     * @param {number} interaction
-     * @private
-     */
-    recordInteraction_(interaction) {
-      chrome.metricsPrivate.recordEnumerationValue(
-          NUX_SIGNIN_VIEW_INTERACTION_METRIC_NAME, interaction,
-          NUX_SIGNIN_VIEW_INTERACTIONS_COUNT);
-    }
-  }
-
-  cr.addSingletonGetter(SigninViewProxyImpl);
-
-  return {
-    SigninViewProxy: SigninViewProxy,
-    SigninViewProxyImpl: SigninViewProxyImpl,
-  };
-});
+addSingletonGetter(SigninViewProxyImpl);
diff --git a/chrome/browser/resources/welcome/welcome.html b/chrome/browser/resources/welcome/welcome.html
index e2a37c5..88145e7 100644
--- a/chrome/browser/resources/welcome/welcome.html
+++ b/chrome/browser/resources/welcome/welcome.html
@@ -3,7 +3,7 @@
   <head>
     <meta charset="utf-8">
     <title>$i18n{headerText}</title>
-    <link rel="import" href="welcome_app.html">
+    <script type="module" src="welcome_app.js"></script>
     <link rel="stylesheet" href="chrome://resources/css/md_colors.css">
     <link rel="stylesheet" href="chrome://resources/css/text_defaults_md.css">
   </head>
@@ -16,7 +16,7 @@
       }
     </style>
     <welcome-app></welcome-app>
-    <script src="/welcome.js"></script>
+    <script type="module" src="welcome.js"></script>
     <link rel="stylesheet" href="chrome://welcome/welcome.css">
   </body>
 </html>
diff --git a/chrome/browser/resources/welcome/welcome.js b/chrome/browser/resources/welcome/welcome.js
index 2e78a647..198e365 100644
--- a/chrome/browser/resources/welcome/welcome.js
+++ b/chrome/browser/resources/welcome/welcome.js
@@ -7,9 +7,10 @@
  * it's included more than once, which can happen when an include is misspelled.
  */
 
-cr.exportPath('welcome');
+import {assert} from 'chrome://resources/js/assert.m.js';
+
 assert(
-    !welcome.defaultResourceLoaded,
+    !window.defaultResourceLoaded,
     'welcome.js run twice. You probably have an invalid import.');
 /** Global defined when the main welcome script runs. */
-welcome.defaultResourceLoaded = true;
+window.defaultResourceLoaded = true;
diff --git a/chrome/browser/resources/welcome/welcome_app.html b/chrome/browser/resources/welcome/welcome_app.html
index a520eaa..21e0477 100644
--- a/chrome/browser/resources/welcome/welcome_app.html
+++ b/chrome/browser/resources/welcome/welcome_app.html
@@ -1,50 +1,29 @@
-<link rel="import" href="chrome://resources/html/polymer.html">
+<style include="cr-hidden-style">
+  #viewManager {
+    display: flex;
+    font-size: 100%;
+    margin: 0;
+    min-height: 100vh;
+  }
 
-<link rel="import" href="chrome://resources/cr_elements/cr_toast/cr_toast.html">
-<link rel="import" href="chrome://resources/cr_elements/cr_view_manager/cr_view_manager.html">
-<link rel="import" href="chrome://resources/cr_elements/hidden_style_css.html">
-<link rel="import" href="chrome://resources/html/assert.html">
-<link rel="import" href="google_apps/nux_google_apps.html">
-<link rel="import" href="landing_view.html">
-<link rel="import" href="navigation_behavior.html">
-<link rel="import" href="ntp_background/nux_ntp_background.html">
-<link rel="import" href="set_as_default/nux_set_as_default.html">
-<link rel="import" href="set_as_default/nux_set_as_default_proxy.html">
-<link rel="import" href="shared/bookmark_proxy.html">
-<link rel="import" href="shared/i18n_setup.html">
-<link rel="import" href="signin_view.html">
+  #viewManager :-webkit-any(nux-google-apps, nux-ntp-background,
+      nux-set-as-default) {
+    /* Override cr-view-manager's default styling for view. */
+    bottom: initial;
+    left: initial;
+    margin: auto;
+    position: unset;
+    right: initial;
+    top: initial;
+  }
 
-<dom-module id="welcome-app">
-  <template>
-    <style include="cr-hidden-style">
-      #viewManager {
-        display: flex;
-        font-size: 100%;
-        margin: 0;
-        min-height: 100vh;
-      }
-
-      #viewManager :-webkit-any(nux-google-apps, nux-ntp-background,
-          nux-set-as-default) {
-        /* Override cr-view-manager's default styling for view. */
-        bottom: initial;
-        left: initial;
-        margin: auto;
-        position: unset;
-        right: initial;
-        top: initial;
-      }
-
-      cr-toast {
-        min-width: initial;
-      }
-    </style>
-    <cr-view-manager id="viewManager" hidden="[[!modulesInitialized_]]">
-      <landing-view id="step-landing" slot="view" class="active"></landing-view>
-    </cr-view-manager>
-    <cr-toast duration="3000">
-      <div>$i18n{defaultBrowserChanged}</div>
-    </cr-toast>
-  </template>
-  <script src="welcome_app.js"></script>
-</dom-module>
+  cr-toast {
+    min-width: initial;
+  }
+</style>
+<cr-view-manager id="viewManager" hidden="[[!modulesInitialized_]]">
+  <landing-view id="step-landing" slot="view" class="active"></landing-view>
+</cr-view-manager>
+<cr-toast duration="3000">
+  <div>$i18n{defaultBrowserChanged}</div>
+</cr-toast>
diff --git a/chrome/browser/resources/welcome/welcome_app.js b/chrome/browser/resources/welcome/welcome_app.js
index 9830c921..02912917 100644
--- a/chrome/browser/resources/welcome/welcome_app.js
+++ b/chrome/browser/resources/welcome/welcome_app.js
@@ -2,6 +2,25 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'chrome://resources/cr_elements/cr_toast/cr_toast.m.js';
+import 'chrome://resources/cr_elements/cr_view_manager/cr_view_manager.m.js';
+import 'chrome://resources/cr_elements/hidden_style_css.m.js';
+import './google_apps/nux_google_apps.js';
+import './landing_view.js';
+import './ntp_background/nux_ntp_background.js';
+import './set_as_default/nux_set_as_default.js';
+import './signin_view.js';
+import '../strings.m.js';
+
+import {assert} from 'chrome://resources/js/assert.m.js';
+import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
+import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from './navigation_behavior.js';
+import {NuxSetAsDefaultProxyImpl} from './set_as_default/nux_set_as_default_proxy.js';
+import {BookmarkBarManager} from './shared/bookmark_proxy.js';
+import {WelcomeBrowserProxyImpl} from './welcome_browser_proxy.js';
+
 /**
  * The strings contained in the arrays should be valid DOM-element tag names.
  * @typedef {{
@@ -31,9 +50,11 @@
 Polymer({
   is: 'welcome-app',
 
-  behaviors: [welcome.NavigationBehavior],
+  _template: html`{__html_template__}`,
 
-  /** @private {?welcome.Routes} */
+  behaviors: [NavigationBehavior],
+
+  /** @private {?Routes} */
   currentRoute_: null,
 
   /** @private {NuxOnboardingModules} */
@@ -65,7 +86,7 @@
   },
 
   /**
-   * @param {welcome.Routes} route
+   * @param {Routes} route
    * @param {number} step
    * @private
    */
@@ -74,7 +95,7 @@
       // If the specified step doesn't exist, that means there are no more
       // steps. In that case, replace this page with NTP.
       if (!this.$$(`#step-${step}`)) {
-        welcome.WelcomeBrowserProxyImpl.getInstance().goToNewTabPage(
+        WelcomeBrowserProxyImpl.getInstance().goToNewTabPage(
             /* replace */ true);
       } else {  // Otherwise, go to the chosen step of that route.
         // At this point, views are ready to be shown.
@@ -94,7 +115,7 @@
     this.currentRoute_ = route;
   },
 
-  /** @param {welcome.Routes} route */
+  /** @param {Routes} route */
   initializeModules: function(route) {
     // Remove all views except landing.
     this.$.viewManager
@@ -102,7 +123,7 @@
         .forEach(element => element.remove());
 
     // If it is on landing route, end here.
-    if (route == welcome.Routes.LANDING) {
+    if (route == Routes.LANDING) {
       return Promise.resolve();
     }
 
@@ -111,7 +132,7 @@
 
     /** @type {!Promise} */
     const defaultBrowserPromise =
-        welcome.NuxSetAsDefaultProxyImpl.getInstance()
+        NuxSetAsDefaultProxyImpl.getInstance()
             .requestDefaultBrowserState()
             .then((status) => {
               if (status.isDefault || !status.canBeDefault) {
@@ -128,7 +149,7 @@
     return Promise
         .all([
           defaultBrowserPromise,
-          welcome.BookmarkBarManager.getInstance().initialized,
+          BookmarkBarManager.getInstance().initialized,
         ])
         .then(([canSetDefault]) => {
           modules = modules.filter(module => {
diff --git a/chrome/browser/resources/welcome/welcome_browser_proxy.html b/chrome/browser/resources/welcome/welcome_browser_proxy.html
deleted file mode 100644
index 24f3320..0000000
--- a/chrome/browser/resources/welcome/welcome_browser_proxy.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<link rel="import" href="chrome://resources/html/cr.html">
-<script src="welcome_browser_proxy.js"></script>
diff --git a/chrome/browser/resources/welcome/welcome_browser_proxy.js b/chrome/browser/resources/welcome/welcome_browser_proxy.js
index f549f22..bf8146f 100644
--- a/chrome/browser/resources/welcome/welcome_browser_proxy.js
+++ b/chrome/browser/resources/welcome/welcome_browser_proxy.js
@@ -2,57 +2,52 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {addSingletonGetter} from 'chrome://resources/js/cr.m.js';
+
 /**
  * @fileoverview A helper object used by the welcome page to interact with
  * the browser.
  */
 
-cr.define('welcome', function() {
-  /** @interface */
-  class WelcomeBrowserProxy {
-    /** @param {?string} redirectUrl the URL to go to, after signing in. */
-    handleActivateSignIn(redirectUrl) {}
+/** @interface */
+export class WelcomeBrowserProxy {
+  /** @param {?string} redirectUrl the URL to go to, after signing in. */
+  handleActivateSignIn(redirectUrl) {}
 
-    handleUserDecline() {}
+  handleUserDecline() {}
 
-    /** @param {boolean=} replace */
-    goToNewTabPage(replace) {}
+  /** @param {boolean=} replace */
+  goToNewTabPage(replace) {}
 
-    /** @param {string} url */
-    goToURL(url) {}
+  /** @param {string} url */
+  goToURL(url) {}
+}
+
+/** @implements {WelcomeBrowserProxy} */
+export class WelcomeBrowserProxyImpl {
+  /** @override */
+  handleActivateSignIn(redirectUrl) {
+    chrome.send('handleActivateSignIn', redirectUrl ? [redirectUrl] : []);
   }
 
-  /** @implements {welcome.WelcomeBrowserProxy} */
-  class WelcomeBrowserProxyImpl {
-    /** @override */
-    handleActivateSignIn(redirectUrl) {
-      chrome.send('handleActivateSignIn', redirectUrl ? [redirectUrl] : []);
-    }
+  /** @override */
+  handleUserDecline() {
+    chrome.send('handleUserDecline');
+  }
 
-    /** @override */
-    handleUserDecline() {
-      chrome.send('handleUserDecline');
-    }
-
-    /** @override */
-    goToNewTabPage(replace) {
-      if (replace) {
-        window.location.replace('chrome://newtab');
-      } else {
-        window.location.assign('chrome://newtab');
-      }
-    }
-
-    /** @override */
-    goToURL(url) {
-      window.location.assign(url);
+  /** @override */
+  goToNewTabPage(replace) {
+    if (replace) {
+      window.location.replace('chrome://newtab');
+    } else {
+      window.location.assign('chrome://newtab');
     }
   }
 
-  cr.addSingletonGetter(WelcomeBrowserProxyImpl);
+  /** @override */
+  goToURL(url) {
+    window.location.assign(url);
+  }
+}
 
-  return {
-    WelcomeBrowserProxy: WelcomeBrowserProxy,
-    WelcomeBrowserProxyImpl: WelcomeBrowserProxyImpl,
-  };
-});
+addSingletonGetter(WelcomeBrowserProxyImpl);
diff --git a/chrome/browser/resources/welcome/welcome_resources.grd b/chrome/browser/resources/welcome/welcome_resources.grd
index cc67d33..5a9e022c 100644
--- a/chrome/browser/resources/welcome/welcome_resources.grd
+++ b/chrome/browser/resources/welcome/welcome_resources.grd
@@ -42,132 +42,79 @@
                file="images/background_svgs/yellow_semicircle.svg"
                compress="gzip"
                type="BINDATA" />
+
+      <!-- Generated Polymer 3 elements -->
+      <include name="IDR_WELCOME_APP_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/welcome_app.js"
+               use_base_dir="false" type="BINDATA" compress="gzip" preprocess="true"/>
+      <include name="IDR_GOOGLE_APPS_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/google_apps/nux_google_apps.js"
+               use_base_dir="false" type="BINDATA" compress="gzip" preprocess="true"/>
+      <include name="IDR_SET_AS_DEFAULT_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/set_as_default/nux_set_as_default.js"
+               use_base_dir="false" type="BINDATA" compress="gzip" preprocess="true"/>
+      <include name="IDR_WELCOME_LANDING_VIEW_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/landing_view.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SIGNIN_VIEW_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/signin_view.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_NTP_BACKGROUND_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/ntp_background/nux_ntp_background.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SHARED_STEP_INDICATOR_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/step_indicator.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SHARED_ONBOARDING_BACKGROUND_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/onboarding_background.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+
+
+      <!-- Generated style files -->
+      <include name="IDR_WELCOME_SHARED_ANIMATIONS_CSS_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/animations_css.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SHARED_CHOOSER_SHARED_CSS_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/chooser_shared_css.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SHARED_NAVI_COLORS_CSS_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/navi_colors_css.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SHARED_ACTION_LINK_STYLE_CSS_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/action_link_style_css.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
+      <include name="IDR_WELCOME_SHARED_SPLASH_PAGES_SHARED_CSS_JS"
+               file="${root_gen_dir}/chrome/browser/resources/welcome/shared/splash_pages_shared_css.js"
+               use_base_dir="false" type="BINDATA" compress="gzip"/>
     </includes>
     <structures>
-      <structure name="IDR_WELCOME_LANDING_VIEW_HTML"
-                 file="landing_view.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_WELCOME_LANDING_VIEW_JS"
-                 file="landing_view.js"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_WELCOME_LANDING_VIEW_PROXY_HTML"
-                 file="landing_view_proxy.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
       <structure name="IDR_WELCOME_LANDING_VIEW_PROXY_JS"
                  file="landing_view_proxy.js"
                  type="chrome_html"
                  compress="gzip"
                  preprocess="true"/>
-      <structure name="IDR_WELCOME_NAVIGATION_BEHAVIOR_HTML"
-                 file="navigation_behavior.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
       <structure name="IDR_WELCOME_NAVIGATION_BEHAVIOR_JS"
                  file="navigation_behavior.js"
                  type="chrome_html"
                  compress="gzip"
                  preprocess="true"/>
-      <structure name="IDR_WELCOME_SHARED_ACTION_LINK_STYLE_JS"
-                 file="shared/action_link_style.js"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_ACTION_LINK_STYLE_CSS_HTML"
-                 file="shared/action_link_style_css.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_ANIMATIONS_CSS"
-                 file="shared/animations_css.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_BOOKMARK_PROXY_HTML"
-                 file="shared/bookmark_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
       <structure name="IDR_WELCOME_SHARED_BOOKMARK_PROXY_JS"
                  file="shared/bookmark_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_CHOOSER_SHARED_CSS"
-                 file="shared/chooser_shared_css.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_I18N_SETUP_HTML"
-                 file="shared/i18n_setup.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_MODULE_METRICS_PROXY_HTML"
-                 file="shared/module_metrics_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
       <structure name="IDR_WELCOME_SHARED_MODULE_METRICS_PROXY_JS"
                  file="shared/module_metrics_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_NAVI_COLORS_CSS"
-                 file="shared/navi_colors_css.html"
+      <structure name="IDR_WELCOME_SHARED_NUX_TYPES_JS"
+                 file="shared/nux_types.js"
                  compress="gzip"
                  type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_ONBOARDING_BACKGROUND_HTML"
-                 file="shared/onboarding_background.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_ONBOARDING_BACKGROUND_JS"
-                 file="shared/onboarding_background.js"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_STEP_INDICATOR_HTML"
-                 file="shared/step_indicator.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_STEP_INDICATOR_JS"
-                 file="shared/step_indicator.js"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SHARED_SPLASH_PAGES_SHARED_CSS"
-                 file="shared/splash_pages_shared_css.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_WELCOME_SIGNIN_VIEW_HTML"
-                 file="signin_view.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_WELCOME_SIGNIN_VIEW_JS"
-                 file="signin_view.js"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_WELCOME_SIGNIN_VIEW_PROXY_HTML"
-                 file="signin_view_proxy.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
       <structure name="IDR_WELCOME_SIGNIN_VIEW_PROXY_JS"
                  file="signin_view_proxy.js"
                  type="chrome_html"
                  compress="gzip"
                  preprocess="true"/>
-      <structure name="IDR_WELCOME_APP_HTML"
-                 file="welcome_app.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_WELCOME_APP_JS"
-                 file="welcome_app.js"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_WELCOME_BROWSER_PROXY_HTML"
-                 file="welcome_browser_proxy.html"
-                 compress="gzip"
-                 type="chrome_html"/>
       <structure name="IDR_WELCOME_BROWSER_PROXY_JS"
                  file="welcome_browser_proxy.js"
                  compress="gzip"
@@ -189,70 +136,26 @@
                  preprocess="true"/>
 
        <!-- Google apps-->
-      <structure name="IDR_GOOGLE_APPS_HTML"
-                 file="google_apps/nux_google_apps.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_GOOGLE_APPS_JS"
-                 file="google_apps/nux_google_apps.js"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_GOOGLE_APP_PROXY_HTML"
-                 file="google_apps/google_app_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
       <structure name="IDR_GOOGLE_APP_PROXY_JS"
                  file="google_apps/google_app_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
-      <structure name="IDR_SET_AS_DEFAULT_HTML"
-                 file="set_as_default/nux_set_as_default.html"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_SET_AS_DEFAULT_JS"
-                 file="set_as_default/nux_set_as_default.js"
-                 type="chrome_html"
-                 compress="gzip"
-                 preprocess="true"/>
-      <structure name="IDR_SET_AS_DEFAULT_PROXY_HTML"
-                 file="set_as_default/nux_set_as_default_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_SET_AS_DEFAULT_PROXY_JS"
-                 file="set_as_default/nux_set_as_default_proxy.js"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_GOOGLE_APPS_METRICS_PROXY_HTML"
-                 file="google_apps/google_apps_metrics_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
       <structure name="IDR_GOOGLE_APPS_METRICS_PROXY_JS"
                  file="google_apps/google_apps_metrics_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
 
+      <!-- Set as default-->
+      <structure name="IDR_SET_AS_DEFAULT_PROXY_JS"
+                 file="set_as_default/nux_set_as_default_proxy.js"
+                 compress="gzip"
+                 type="chrome_html" />
+
       <!-- NTP background-->
-      <structure name="IDR_NTP_BACKGROUND_HTML"
-                 file="ntp_background/nux_ntp_background.html"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_NTP_BACKGROUND_JS"
-                 file="ntp_background/nux_ntp_background.js"
-                 compress="gzip"
-                 type="chrome_html" />
-      <structure name="IDR_NTP_BACKGROUND_PROXY_HTML"
-                 file="ntp_background/ntp_background_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
       <structure name="IDR_NTP_BACKGROUND_PROXY_JS"
                  file="ntp_background/ntp_background_proxy.js"
                  compress="gzip"
                  type="chrome_html" />
-      <structure name="IDR_NTP_BACKGROUND_METRICS_PROXY_HTML"
-                 file="ntp_background/ntp_background_metrics_proxy.html"
-                 compress="gzip"
-                 type="chrome_html" />
       <structure name="IDR_NTP_BACKGROUND_METRICS_PROXY_JS"
                  file="ntp_background/ntp_background_metrics_proxy.js"
                  compress="gzip"
diff --git a/chrome/browser/supervised_user/supervised_user_features.cc b/chrome/browser/supervised_user/supervised_user_features.cc
index 3fe29316..38cf091 100644
--- a/chrome/browser/supervised_user/supervised_user_features.cc
+++ b/chrome/browser/supervised_user/supervised_user_features.cc
@@ -6,6 +6,9 @@
 
 namespace supervised_users {
 
+const base::Feature kSupervisedUserIframeFilter{
+    "SupervisedUserIframeFilter", base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kSupervisedUserInitiatedExtensionInstall{
     "SupervisedUserInitiatedExtensionInstall",
     base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/supervised_user/supervised_user_features.h b/chrome/browser/supervised_user/supervised_user_features.h
index 5bf4b908..0c7ee10 100644
--- a/chrome/browser/supervised_user/supervised_user_features.h
+++ b/chrome/browser/supervised_user/supervised_user_features.h
@@ -9,6 +9,8 @@
 
 namespace supervised_users {
 
+extern const base::Feature kSupervisedUserIframeFilter;
+
 extern const base::Feature kSupervisedUserInitiatedExtensionInstall;
 
 }
diff --git a/chrome/browser/supervised_user/supervised_user_service.cc b/chrome/browser/supervised_user/supervised_user_service.cc
index 40550a8e..bc1302b5 100644
--- a/chrome/browser/supervised_user/supervised_user_service.cc
+++ b/chrome/browser/supervised_user/supervised_user_service.cc
@@ -316,6 +316,11 @@
                                     base::UTF8ToUTF16(GetCustodianName()));
 }
 
+bool SupervisedUserService::IsSupervisedUserIframeFilterEnabled() const {
+  return base::FeatureList::IsEnabled(
+      supervised_users::kSupervisedUserIframeFilter);
+}
+
 #if !defined(OS_ANDROID)
 void SupervisedUserService::InitSync(const std::string& refresh_token) {
   IdentityManagerFactory::GetForProfile(profile_)
diff --git a/chrome/browser/supervised_user/supervised_user_service.h b/chrome/browser/supervised_user/supervised_user_service.h
index 72dc4d2..2af6b7e 100644
--- a/chrome/browser/supervised_user/supervised_user_service.h
+++ b/chrome/browser/supervised_user/supervised_user_service.h
@@ -157,6 +157,8 @@
   // custodian.
   base::string16 GetExtensionsLockedMessage() const;
 
+  bool IsSupervisedUserIframeFilterEnabled() const;
+
 #if !defined(OS_ANDROID)
   // Initializes this profile for syncing, using the provided |refresh_token| to
   // mint access tokens for Sync.
diff --git a/chrome/browser/ui/android/context_menu_helper.cc b/chrome/browser/ui/android/context_menu_helper.cc
index 5cc6340..7d9a035 100644
--- a/chrome/browser/ui/android/context_menu_helper.cc
+++ b/chrome/browser/ui/android/context_menu_helper.cc
@@ -72,7 +72,8 @@
 };
 
 void OnRetrieveImageForShare(
-    chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame,
+    mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
+        chrome_render_frame,
     const base::android::JavaRef<jobject>& jcallback,
     const std::vector<uint8_t>& thumbnail_data,
     const gfx::Size& original_size) {
@@ -80,7 +81,8 @@
 }
 
 void OnRetrieveImageForContextMenu(
-    chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame,
+    mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
+        chrome_render_frame,
     const base::android::JavaRef<jobject>& jcallback,
     const std::vector<uint8_t>& thumbnail_data,
     const gfx::Size& original_size) {
@@ -225,7 +227,7 @@
   if (!render_frame_host)
     return;
 
-  chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame;
+  mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
       &chrome_render_frame);
   // Bind the InterfacePtr into the callback so that it's kept alive
diff --git a/chrome/browser/ui/android/context_menu_helper.h b/chrome/browser/ui/android/context_menu_helper.h
index d810624..645d33b 100644
--- a/chrome/browser/ui/android/context_menu_helper.h
+++ b/chrome/browser/ui/android/context_menu_helper.h
@@ -15,6 +15,7 @@
 #include "chrome/common/chrome_render_frame.mojom.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "content/public/common/context_menu_params.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 
 namespace content {
 struct ContextMenuParams;
@@ -26,7 +27,8 @@
     : public content::WebContentsUserData<ContextMenuHelper> {
  protected:
   using ImageRetrieveCallback = base::Callback<void(
-      chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame_ptr,
+      mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
+          chrome_render_frame_ptr,
       const base::android::JavaRef<jobject>& jcallback,
       const std::vector<uint8_t>& thumbnail_data,
       const gfx::Size& max_dimen_px)>;
diff --git a/chrome/browser/ui/app_list/search/search_controller.cc b/chrome/browser/ui/app_list/search/search_controller.cc
index 6f9e17e..3da1f4d5 100644
--- a/chrome/browser/ui/app_list/search/search_controller.cc
+++ b/chrome/browser/ui/app_list/search/search_controller.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/app_list/search/chrome_search_result.h"
 #include "chrome/browser/ui/app_list/search/search_provider.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/app_list_launch_recorder.h"
+#include "chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/search_result_ranker.h"
 #include "third_party/metrics_proto/chrome_os_app_list_launch_event.pb.h"
@@ -173,8 +174,17 @@
     const base::string16& trimmed_query,
     const ash::SearchResultIdWithPositionIndices& results,
     int launched_index) {
-  if (trimmed_query.empty())
+  if (trimmed_query.empty()) {
     mixer_->GetNonAppSearchResultRanker()->ZeroStateResultsDisplayed(results);
+
+    // Extract result types for logging.
+    std::vector<RankingItemType> result_types;
+    for (const auto& result : results) {
+      result_types.push_back(
+          RankingItemTypeFromSearchResult(*FindSearchResult(result.id)));
+    }
+    LogZeroStateResultsListMetrics(result_types, launched_index);
+  }
 }
 
 ChromeSearchResult* SearchController::GetResultByTitleForTest(
diff --git a/chrome/browser/ui/app_list/search/search_controller.h b/chrome/browser/ui/app_list/search/search_controller.h
index 9abca1f..d55c09b 100644
--- a/chrome/browser/ui/app_list/search/search_controller.h
+++ b/chrome/browser/ui/app_list/search/search_controller.h
@@ -75,6 +75,7 @@
 
   // Called when items in the results list have been on screen for some amount
   // of time, or the user clicked a search result.
+  // TODO(959679): Rename this function to better reflect its nature.
   void OnSearchResultsDisplayed(
       const base::string16& trimmed_query,
       const ash::SearchResultIdWithPositionIndices& results,
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.cc b/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.cc
index fefbd18..1696934 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.cc
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.cc
@@ -6,6 +6,7 @@
 
 #include <cmath>
 
+#include "base/containers/flat_set.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/histogram_macros.h"
 #include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
@@ -87,4 +88,28 @@
       floor(score * 100), 100);
 }
 
+void LogZeroStateResultsListMetrics(
+    const std::vector<RankingItemType>& result_types,
+    int launched_index) {
+  // Log position of clicked items.
+  if (launched_index >= 0) {
+    UMA_HISTOGRAM_COUNTS_100(
+        "Apps.AppList.ZeroStateResultsList.LaunchedItemPosition",
+        launched_index);
+  }
+
+  // Log the number of types shown in the impression set.
+  base::flat_set<RankingItemType> type_set(result_types);
+  UMA_HISTOGRAM_COUNTS_100(
+      "Apps.AppList.ZeroStateResultsList.NumImpressionTypes", type_set.size());
+
+  // Log CTR metrics. Note that all clicks are captured and indicated by a
+  // non-negative launch index, while an index of -1 indicates that results were
+  // impressed on screen for some amount of time.
+  UMA_HISTOGRAM_BOOLEAN("Apps.AppList.ZeroStateResultsList.Clicked",
+                        launched_index >= 0);
+
+  // TODO(999912): Add UMA metrics for file-specific CTR.
+}
+
 }  // namespace app_list
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h b/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h
index ff607f3..f7f8393 100644
--- a/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h
@@ -96,6 +96,12 @@
 
 void LogZeroStateReceivedScore(const std::string& suffix, float score);
 
+// Logs zero state UI-related metrics. These comprise of the clicked position,
+// number of types per impression set, and CTR metrics.
+void LogZeroStateResultsListMetrics(
+    const std::vector<RankingItemType>& result_types,
+    int launched_index);
+
 }  // namespace app_list
 
 #endif  // CHROME_BROWSER_UI_APP_LIST_SEARCH_SEARCH_RESULT_RANKER_HISTOGRAM_UTIL_H_
diff --git a/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util_unittest.cc b/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util_unittest.cc
new file mode 100644
index 0000000..8092efd
--- /dev/null
+++ b/chrome/browser/ui/app_list/search/search_result_ranker/histogram_util_unittest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/ui/app_list/search/search_result_ranker/histogram_util.h"
+
+#include "base/containers/flat_set.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "chrome/browser/ui/app_list/search/search_result_ranker/ranking_item_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace app_list {
+
+class HistogramUtilTest : public testing::Test {
+ public:
+  HistogramUtilTest() {}
+  ~HistogramUtilTest() override {}
+
+  const base::HistogramTester histogram_tester_;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(HistogramUtilTest);
+};
+
+TEST_F(HistogramUtilTest, TestLaunchedItemPosition) {
+  std::vector<RankingItemType> result_types = {
+      RankingItemType::kOmniboxGeneric, RankingItemType::kZeroStateFile,
+      RankingItemType::kZeroStateFile, RankingItemType::kZeroStateFile};
+
+  // Don't log if there is no click.
+  LogZeroStateResultsListMetrics(result_types, -1);
+  histogram_tester_.ExpectTotalCount(
+      "Apps.AppList.ZeroStateResultsList.LaunchedItemPosition", 0);
+
+  // Log some actual clicks.
+  LogZeroStateResultsListMetrics(result_types, 3);
+  histogram_tester_.ExpectBucketCount(
+      "Apps.AppList.ZeroStateResultsList.LaunchedItemPosition", 3, 1);
+
+  LogZeroStateResultsListMetrics(result_types, 2);
+  histogram_tester_.ExpectBucketCount(
+      "Apps.AppList.ZeroStateResultsList.LaunchedItemPosition", 2, 1);
+}
+
+TEST_F(HistogramUtilTest, TestNumImpressionTypes) {
+  // No results.
+  std::vector<RankingItemType> result_types_1;
+  LogZeroStateResultsListMetrics(result_types_1, 0);
+  histogram_tester_.ExpectBucketCount(
+      "Apps.AppList.ZeroStateResultsList.NumImpressionTypes", 0, 1);
+
+  // Several types of results.
+  std::vector<RankingItemType> result_types_2 = {
+      RankingItemType::kOmniboxGeneric, RankingItemType::kZeroStateFile,
+      RankingItemType::kDriveQuickAccess};
+  LogZeroStateResultsListMetrics(result_types_2, 0);
+  histogram_tester_.ExpectBucketCount(
+      "Apps.AppList.ZeroStateResultsList.NumImpressionTypes", 3, 1);
+
+  // Some types doubled up.
+  std::vector<RankingItemType> result_types_3 = {
+      RankingItemType::kOmniboxGeneric, RankingItemType::kZeroStateFile,
+      RankingItemType::kZeroStateFile, RankingItemType::kZeroStateFile};
+  LogZeroStateResultsListMetrics(result_types_3, 0);
+  histogram_tester_.ExpectBucketCount(
+      "Apps.AppList.ZeroStateResultsList.NumImpressionTypes", 2, 1);
+}
+
+}  // namespace app_list
diff --git a/chrome/browser/ui/blocked_content/popup_blocker_tab_helper.cc b/chrome/browser/ui/blocked_content/popup_blocker_tab_helper.cc
index dcc5740..f913fdf 100644
--- a/chrome/browser/ui/blocked_content/popup_blocker_tab_helper.cc
+++ b/chrome/browser/ui/blocked_content/popup_blocker_tab_helper.cc
@@ -25,6 +25,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 #if defined(OS_ANDROID)
@@ -136,7 +137,7 @@
       content::RenderFrameHost* host =
           popup->params.navigated_or_inserted_contents->GetMainFrame();
       DCHECK(host);
-      chrome::mojom::ChromeRenderFrameAssociatedPtr client;
+      mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> client;
       host->GetRemoteAssociatedInterfaces()->GetInterface(&client);
       client->SetWindowFeatures(popup->window_features.Clone());
     }
diff --git a/chrome/browser/ui/navigation_correction_tab_observer.cc b/chrome/browser/ui/navigation_correction_tab_observer.cc
index 9720d6c..945d1b5 100644
--- a/chrome/browser/ui/navigation_correction_tab_observer.cc
+++ b/chrome/browser/ui/navigation_correction_tab_observer.cc
@@ -18,6 +18,7 @@
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/web_contents.h"
 #include "google_apis/google_api_keys.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 
 using content::RenderFrameHost;
@@ -79,7 +80,7 @@
 void NavigationCorrectionTabObserver::UpdateNavigationCorrectionInfo(
     RenderFrameHost* render_frame_host) {
   GURL google_base_url(UIThreadSearchTermsData().GoogleBaseURLValue());
-  chrome::mojom::NavigationCorrectorAssociatedPtr client;
+  mojo::AssociatedRemote<chrome::mojom::NavigationCorrector> client;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&client);
   client->SetNavigationCorrectionInfo(
       GetNavigationCorrectionURL(),
diff --git a/chrome/browser/ui/omnibox/clipboard_utils_unittest.cc b/chrome/browser/ui/omnibox/clipboard_utils_unittest.cc
index 5d5e779..4524c0e 100644
--- a/chrome/browser/ui/omnibox/clipboard_utils_unittest.cc
+++ b/chrome/browser/ui/omnibox/clipboard_utils_unittest.cc
@@ -24,7 +24,8 @@
 class ClipboardUtilsTest : public PlatformTest {
  public:
   ClipboardUtilsTest()
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
+      : task_environment_(
+            base::test::SingleThreadTaskEnvironment::MainThreadType::UI) {}
 
   void SetUp() override {
     PlatformTest::SetUp();
@@ -38,7 +39,7 @@
 
  private:
   // Windows requires a message loop for clipboard access.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 TEST_F(ClipboardUtilsTest, GetClipboardText) {
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper.cc b/chrome/browser/ui/tab_contents/core_tab_helper.cc
index 118ee62..0a9622c 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper.cc
+++ b/chrome/browser/ui/tab_contents/core_tab_helper.cc
@@ -86,7 +86,7 @@
 void CoreTabHelper::SearchByImageInNewTab(
     content::RenderFrameHost* render_frame_host,
     const GURL& src_url) {
-  chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame;
+  mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame> chrome_render_frame;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
       &chrome_render_frame);
   // Bind the InterfacePtr into the callback so that it's kept alive until
@@ -233,7 +233,8 @@
 // Handles the image thumbnail for the context node, composes a image search
 // request based on the received thumbnail and opens the request in a new tab.
 void CoreTabHelper::DoSearchByImageInNewTab(
-    chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame,
+    mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
+        chrome_render_frame,
     const GURL& src_url,
     const std::vector<uint8_t>& thumbnail_data,
     const gfx::Size& original_size) {
diff --git a/chrome/browser/ui/tab_contents/core_tab_helper.h b/chrome/browser/ui/tab_contents/core_tab_helper.h
index 4ee6f9b..2035e878 100644
--- a/chrome/browser/ui/tab_contents/core_tab_helper.h
+++ b/chrome/browser/ui/tab_contents/core_tab_helper.h
@@ -57,7 +57,8 @@
   void NavigationEntriesDeleted() override;
 
   void DoSearchByImageInNewTab(
-      chrome::mojom::ChromeRenderFrameAssociatedPtr chrome_render_frame,
+      mojo::AssociatedRemote<chrome::mojom::ChromeRenderFrame>
+          chrome_render_frame,
       const GURL& src_url,
       const std::vector<uint8_t>& thumbnail_data,
       const gfx::Size& original_size);
diff --git a/chrome/browser/ui/toolbar/app_menu_icon_controller.cc b/chrome/browser/ui/toolbar/app_menu_icon_controller.cc
index 7dd9cd9..f6d6209 100644
--- a/chrome/browser/ui/toolbar/app_menu_icon_controller.cc
+++ b/chrome/browser/ui/toolbar/app_menu_icon_controller.cc
@@ -172,6 +172,12 @@
                                         promo_highlight_color));
 }
 
+SkColor AppMenuIconController::GetIconColor(
+    base::Optional<SkColor> promo_highlight_color) const {
+  return GetIconColorForSeverity(delegate_, GetTypeAndSeverity().severity,
+                                 promo_highlight_color);
+}
+
 void AppMenuIconController::OnGlobalErrorsChanged() {
   UpdateDelegate();
 }
diff --git a/chrome/browser/ui/toolbar/app_menu_icon_controller.h b/chrome/browser/ui/toolbar/app_menu_icon_controller.h
index 1af2ff55..07d974bb 100644
--- a/chrome/browser/ui/toolbar/app_menu_icon_controller.h
+++ b/chrome/browser/ui/toolbar/app_menu_icon_controller.h
@@ -89,6 +89,10 @@
       bool touch_ui,
       base::Optional<SkColor> promo_highlight_color = base::nullopt) const;
 
+  // Gets the color to be used for the app menu's icon. |promo_highlight_color|,
+  // if provided, overrides the basic color when the icon's Severity is NONE.
+  SkColor GetIconColor(base::Optional<SkColor> promo_highlight_color) const;
+
  private:
   // GlobalErrorObserver:
   void OnGlobalErrorsChanged() override;
diff --git a/chrome/browser/ui/ui_features.cc b/chrome/browser/ui/ui_features.cc
index 2c708e1..b9d4902 100644
--- a/chrome/browser/ui/ui_features.cc
+++ b/chrome/browser/ui/ui_features.cc
@@ -24,6 +24,11 @@
 const base::Feature kExtensionsToolbarMenu{"ExtensionsToolbarMenu",
                                            base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enables showing text next to the 3-dot menu when an update is available.
+// See https://crbug.com/1001731
+const base::Feature kUseTextForUpdateButton{"UseTextForUpdateButton",
+                                            base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Enables updated tabstrip animations, required for a scrollable tabstrip.
 // https://crbug.com/958173
 const base::Feature kNewTabstripAnimation{"NewTabstripAnimation",
diff --git a/chrome/browser/ui/ui_features.h b/chrome/browser/ui/ui_features.h
index 983df09..9b67005 100644
--- a/chrome/browser/ui/ui_features.h
+++ b/chrome/browser/ui/ui_features.h
@@ -25,6 +25,8 @@
 
 extern const base::Feature kExtensionsToolbarMenu;
 
+extern const base::Feature kUseTextForUpdateButton;
+
 extern const base::Feature kNewTabstripAnimation;
 
 extern const base::Feature kProfileMenuRevamp;
diff --git a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
index 24caa2c..f72a0a5 100644
--- a/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
+++ b/chrome/browser/ui/views/frame/top_controls_slide_controller_chromeos.cc
@@ -11,7 +11,6 @@
 #include "chrome/browser/ssl/security_state_tab_helper.h"
 #include "chrome/browser/ui/views/frame/browser_view.h"
 #include "chrome/browser/ui/views/frame/top_container_view.h"
-#include "chrome/common/chrome_render_frame.mojom.h"
 #include "chrome/common/url_constants.h"
 #include "content/public/browser/focused_node_details.h"
 #include "content/public/browser/navigation_controller.h"
@@ -27,7 +26,6 @@
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/browser_controls_state.h"
 #include "extensions/common/constants.h"
-#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
 #include "ui/aura/window.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/views/controls/native/native_view_host.h"
@@ -113,19 +111,13 @@
   if (!main_frame)
     return;
 
-  chrome::mojom::ChromeRenderFrameAssociatedPtr renderer;
-  main_frame->GetRemoteAssociatedInterfaces()->GetInterface(&renderer);
-
-  if (!renderer)
-    return;
-
   const content::BrowserControlsState constraints_state =
       GetBrowserControlsStateConstraints(web_contents);
 
   const content::BrowserControlsState current_state =
       content::BROWSER_CONTROLS_STATE_SHOWN;
-  renderer->UpdateBrowserControlsState(constraints_state, current_state,
-                                       animate);
+  main_frame->UpdateBrowserControlsState(constraints_state, current_state,
+                                         animate);
 }
 
 // Triggers a visual properties synchrnoization event on |contents|' main
diff --git a/chrome/browser/ui/views/frame/web_contents_close_handler_unittest.cc b/chrome/browser/ui/views/frame/web_contents_close_handler_unittest.cc
index b713aa3..812bddf5 100644
--- a/chrome/browser/ui/views/frame/web_contents_close_handler_unittest.cc
+++ b/chrome/browser/ui/views/frame/web_contents_close_handler_unittest.cc
@@ -13,7 +13,8 @@
     : public WebContentsCloseHandlerDelegate {
  public:
   MockWebContentsCloseHandlerDelegate()
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI),
+      : task_environment_(
+            base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
         got_clone_(false),
         got_destroy_(false) {}
   ~MockWebContentsCloseHandlerDelegate() override {}
@@ -33,7 +34,7 @@
   void DestroyClonedLayer() override { got_destroy_ = true; }
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   bool got_clone_;
   bool got_destroy_;
 
diff --git a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
index 1a7006d..d17f268 100644
--- a/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
+++ b/chrome/browser/ui/views/toolbar/browser_app_menu_button.cc
@@ -25,6 +25,7 @@
 #include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
 #include "chrome/grit/chromium_strings.h"
+#include "chrome/grit/generated_resources.h"
 #include "components/feature_engagement/public/feature_constants.h"
 #include "ui/base/l10n/l10n_util.h"
 #include "ui/base/material_design/material_design_controller.h"
@@ -35,6 +36,7 @@
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/color_palette.h"
 #include "ui/gfx/color_utils.h"
+#include "ui/gfx/paint_vector_icon.h"
 #include "ui/views/animation/animation_delegate_views.h"
 #include "ui/views/animation/ink_drop.h"
 #include "ui/views/animation/ink_drop_highlight.h"
@@ -147,7 +149,7 @@
 BrowserAppMenuButton::BrowserAppMenuButton(ToolbarView* toolbar_view)
     : AppMenuButton(toolbar_view), toolbar_view_(toolbar_view) {
   SetInkDropMode(InkDropMode::ON);
-  SetHorizontalAlignment(gfx::ALIGN_CENTER);
+  SetHorizontalAlignment(gfx::ALIGN_RIGHT);
 
   set_ink_drop_visible_opacity(kToolbarInkDropVisibleOpacity);
 
@@ -163,14 +165,47 @@
   type_and_severity_ = type_and_severity;
 
   int message_id;
+  base::string16 text;
   if (type_and_severity.severity == AppMenuIconController::Severity::NONE) {
     message_id = IDS_APPMENU_TOOLTIP;
   } else if (type_and_severity.type ==
              AppMenuIconController::IconType::UPGRADE_NOTIFICATION) {
     message_id = IDS_APPMENU_TOOLTIP_UPDATE_AVAILABLE;
+    text = l10n_util::GetStringUTF16(IDS_APP_MENU_BUTTON_UPDATE);
   } else {
     message_id = IDS_APPMENU_TOOLTIP_ALERT;
+    text = l10n_util::GetStringUTF16(IDS_APP_MENU_BUTTON_ERROR);
   }
+
+  base::Optional<SkColor> color;
+  switch (type_and_severity.severity) {
+    case AppMenuIconController::Severity::NONE:
+      break;
+    case AppMenuIconController::Severity::LOW:
+      color = AdjustHighlightColorForContrast(
+          GetThemeProvider(), gfx::kGoogleGreen300, gfx::kGoogleGreen600,
+          gfx::kGoogleGreen050, gfx::kGoogleGreen900);
+
+      break;
+    case AppMenuIconController::Severity::MEDIUM:
+      color = AdjustHighlightColorForContrast(
+          GetThemeProvider(), gfx::kGoogleYellow300, gfx::kGoogleYellow600,
+          gfx::kGoogleYellow050, gfx::kGoogleYellow900);
+
+      break;
+    case AppMenuIconController::Severity::HIGH:
+      color = AdjustHighlightColorForContrast(
+          GetThemeProvider(), gfx::kGoogleRed300, gfx::kGoogleRed600,
+          gfx::kGoogleRed050, gfx::kGoogleRed900);
+
+      break;
+  }
+
+  if (base::FeatureList::IsEnabled(features::kUseTextForUpdateButton)) {
+    SetHighlightColor(color);
+    SetText(text);
+  }
+
   SetTooltipText(l10n_util::GetStringUTF16(message_id));
   UpdateIcon();
 }
@@ -237,6 +272,16 @@
 }
 
 void BrowserAppMenuButton::UpdateIcon() {
+  if (base::FeatureList::IsEnabled(features::kUseTextForUpdateButton)) {
+    SetImage(
+        views::Button::STATE_NORMAL,
+        gfx::CreateVectorIcon(
+            ui::MaterialDesignController::touch_ui() ? kBrowserToolsTouchIcon
+                                                     : kBrowserToolsIcon,
+            toolbar_view_->app_menu_icon_controller()->GetIconColor(
+                GetPromoHighlightColor())));
+    return;
+  }
   SetImage(
       views::Button::STATE_NORMAL,
       toolbar_view_->app_menu_icon_controller()->GetIconImage(
diff --git a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
index e04d1de2..04f5347b 100644
--- a/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
+++ b/chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.cc
@@ -4,7 +4,6 @@
 
 #include "chrome/browser/ui/views/toolbar/toolbar_ink_drop_util.h"
 
-#include "base/i18n/rtl.h"
 #include "chrome/browser/themes/theme_properties.h"
 #include "chrome/browser/ui/layout_constants.h"
 #include "chrome/browser/ui/views/chrome_layout_provider.h"
@@ -27,19 +26,13 @@
 
 gfx::Insets GetToolbarInkDropInsets(const views::View* host_view,
                                     const gfx::Insets& margin_insets) {
-  // TODO(pbos): Inkdrop masks and layers should be flipped with RTL. Fix this
-  // and remove RTL handling from here.
-  gfx::Insets inkdrop_insets =
-      base::i18n::IsRTL()
-          ? gfx::Insets(margin_insets.top(), margin_insets.right(),
-                        margin_insets.bottom(), margin_insets.left())
-          : margin_insets;
-
   // Inset the inkdrop insets so that the end result matches the target inkdrop
   // dimensions.
   const gfx::Size host_size = host_view->size();
   const int inkdrop_dimensions = GetLayoutConstant(LOCATION_BAR_HEIGHT);
-  inkdrop_insets += gfx::Insets((host_size.height() - inkdrop_dimensions) / 2);
+  gfx::Insets inkdrop_insets =
+      margin_insets +
+      gfx::Insets((host_size.height() - inkdrop_dimensions) / 2);
 
   return inkdrop_insets;
 }
diff --git a/chrome/browser/ui/webui/sandbox_internals_ui.cc b/chrome/browser/ui/webui/sandbox_internals_ui.cc
index e09f9d4..18b4832 100644
--- a/chrome/browser/ui/webui/sandbox_internals_ui.cc
+++ b/chrome/browser/ui/webui/sandbox_internals_ui.cc
@@ -86,7 +86,7 @@
 void SandboxInternalsUI::RenderFrameCreated(
     content::RenderFrameHost* render_frame_host) {
 #if defined(OS_ANDROID)
-  chrome::mojom::SandboxStatusExtensionAssociatedPtr sandbox_status;
+  mojo::AssociatedRemote<chrome::mojom::SandboxStatusExtension> sandbox_status;
   render_frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
       &sandbox_status);
   sandbox_status->AddSandboxStatusExtension();
diff --git a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
index 34dcba8a..46f670b 100644
--- a/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
+++ b/chrome/browser/ui/webui/settings/chromeos/device_storage_handler.cc
@@ -102,6 +102,7 @@
 }
 
 StorageHandler::~StorageHandler() {
+  DiskMountManager::GetInstance()->RemoveObserver(this);
   arc::ArcServiceManager::Get()
       ->arc_bridge_service()
       ->storage_manager()
diff --git a/chrome/browser/ui/webui/test_data_source.cc b/chrome/browser/ui/webui/test_data_source.cc
index 3c2b5ac..2b447e8 100644
--- a/chrome/browser/ui/webui/test_data_source.cc
+++ b/chrome/browser/ui/webui/test_data_source.cc
@@ -48,7 +48,9 @@
     return "text/html";
   }
   // The test data source currently only serves HTML and JS.
-  CHECK(base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII));
+  CHECK(base::EndsWith(path, ".js", base::CompareCase::INSENSITIVE_ASCII))
+      << "Tried to read file with unexpected type from test data source: "
+      << path;
   return "application/javascript";
 }
 
diff --git a/chrome/browser/ui/webui/welcome/welcome_ui.cc b/chrome/browser/ui/webui/welcome/welcome_ui.cc
index d0b69ca..0dba778 100644
--- a/chrome/browser/ui/webui/welcome/welcome_ui.cc
+++ b/chrome/browser/ui/webui/welcome/welcome_ui.cc
@@ -132,9 +132,16 @@
   AddStrings(html_source);
 
   // Add all welcome resources.
+  std::string generated_path =
+      "@out_folder@/gen/chrome/browser/resources/welcome/";
+
   for (size_t i = 0; i < kWelcomeResourcesSize; ++i) {
-    html_source->AddResourcePath(kWelcomeResources[i].name,
-                                 kWelcomeResources[i].value);
+    std::string path = kWelcomeResources[i].name;
+    if (path.rfind(generated_path, 0) == 0) {
+      path = path.substr(generated_path.length());
+    }
+
+    html_source->AddResourcePath(path, kWelcomeResources[i].value);
   }
 
 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
@@ -202,6 +209,7 @@
       base::BindRepeating(&HandleRequestCallback,
                           weak_ptr_factory_.GetWeakPtr()));
   html_source->UseStringsJs();
+  html_source->EnableReplaceI18nInJS();
 
   content::WebUIDataSource::Add(profile, html_source);
 }
diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn
index 82682dd..9d6af9c 100644
--- a/chrome/common/BUILD.gn
+++ b/chrome/common/BUILD.gn
@@ -107,7 +107,6 @@
     "attrition_experiments.h",
     "auto_start_linux.cc",
     "auto_start_linux.h",
-    "browser_controls_state_param_traits.h",
     "child_process_logging.h",
     "child_process_logging_win.cc",
     "chrome_content_client.cc",
diff --git a/chrome/common/browser_controls_state.typemap b/chrome/common/browser_controls_state.typemap
deleted file mode 100644
index 328a450..0000000
--- a/chrome/common/browser_controls_state.typemap
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright 2017 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-mojom = "//chrome/common/chrome_render_frame.mojom"
-public_headers = [ "//content/public/common/browser_controls_state.h" ]
-traits_headers = [ "//chrome/common/browser_controls_state_param_traits.h" ]
-public_deps = [
-  "//ipc",
-]
-
-type_mappings =
-    [ "chrome.mojom.BrowserControlsState=::content::BrowserControlsState" ]
diff --git a/chrome/common/browser_controls_state_param_traits.h b/chrome/common/browser_controls_state_param_traits.h
deleted file mode 100644
index 07aec319..0000000
--- a/chrome/common/browser_controls_state_param_traits.h
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2017 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/public/common/browser_controls_state.h"
-#include "ipc/ipc_message_macros.h"
-
-IPC_ENUM_TRAITS_MAX_VALUE(content::BrowserControlsState,
-                          content::BROWSER_CONTROLS_STATE_LAST)
diff --git a/chrome/common/chrome_render_frame.mojom b/chrome/common/chrome_render_frame.mojom
index b26a8a3..f1039f59 100644
--- a/chrome/common/chrome_render_frame.mojom
+++ b/chrome/common/chrome_render_frame.mojom
@@ -14,9 +14,6 @@
 };
 
 [Native]
-enum BrowserControlsState;
-
-[Native]
 struct WebApplicationInfo;
 
 // Messages sent from chrome to the render frame.
@@ -50,11 +47,4 @@
 
   // Requests the web application info from the renderer.
   GetWebApplicationInfo() => (WebApplicationInfo web_application_info);
-
-  // Notifies the renderer whether hiding/showing the browser controls is
-  // enabled, what the current state should be, and whether or not to
-  // animate to the proper state.
-  UpdateBrowserControlsState(BrowserControlsState constraints,
-                             BrowserControlsState current,
-                             bool animate);
 };
diff --git a/chrome/common/extensions/api/autotest_private.idl b/chrome/common/extensions/api/autotest_private.idl
index 93e7125..b7bdecc 100644
--- a/chrome/common/extensions/api/autotest_private.idl
+++ b/chrome/common/extensions/api/autotest_private.idl
@@ -441,6 +441,12 @@
     // Takes a screenshot and returns the data in base64 encoded PNG format.
     static void takeScreenshot(TakeScreenshotCallback callback);
 
+    // Tasks a screenshot for a display.
+    // |display_id|: the display id of the display.
+    // |callback|: called when the operation has completed.
+    static void takeScreenshotForDisplay(DOMString display_id,
+                                         TakeScreenshotCallback callback);
+
     // Makes a basic request to ML Service, triggering 1. ML Service
     // daemon startup, and 2. the initial D-Bus -> Mojo IPC bootstrap.
     // |callback|: Called when the operation has completed.
diff --git a/chrome/common/media/media_resource_provider.cc b/chrome/common/media/media_resource_provider.cc
index a86ff80..6bea66ca 100644
--- a/chrome/common/media/media_resource_provider.cc
+++ b/chrome/common/media/media_resource_provider.cc
@@ -8,7 +8,7 @@
 #include "chrome/grit/generated_resources.h"
 #include "ui/base/l10n/l10n_util.h"
 
-namespace chrome_common_media {
+namespace {
 
 int MediaMessageIdToGrdId(media::MessageId message_id) {
   switch (message_id) {
@@ -24,8 +24,8 @@
   }
 }
 
-base::string16 LocalizedStringProvider(media::MessageId message_id) {
+}  // namespace
+
+base::string16 ChromeMediaLocalizedStringProvider(media::MessageId message_id) {
   return l10n_util::GetStringUTF16(MediaMessageIdToGrdId(message_id));
 }
-
-}  // namespace chrome_common_media
diff --git a/chrome/common/media/media_resource_provider.h b/chrome/common/media/media_resource_provider.h
index 4f4ad9f..46f5a47d 100644
--- a/chrome/common/media/media_resource_provider.h
+++ b/chrome/common/media/media_resource_provider.h
@@ -8,11 +8,8 @@
 #include "base/strings/string16.h"
 #include "media/base/localized_strings.h"
 
-namespace chrome_common_media {
-
 // This is called indirectly by the media layer to access resources.
-base::string16 LocalizedStringProvider(media::MessageId media_message_id);
-
-}  // namespace chrome_common_media
+base::string16 ChromeMediaLocalizedStringProvider(
+    media::MessageId media_message_id);
 
 #endif  // CHROME_COMMON_MEDIA_MEDIA_RESOURCE_PROVIDER_H_
diff --git a/chrome/common/net/net_resource_provider.cc b/chrome/common/net/net_resource_provider.cc
index f9cbfd4..366c894d2 100644
--- a/chrome/common/net/net_resource_provider.cc
+++ b/chrome/common/net/net_resource_provider.cc
@@ -55,9 +55,7 @@
 
 }  // namespace
 
-namespace chrome_common_net {
-
-base::StringPiece NetResourceProvider(int key) {
+base::StringPiece ChromeNetResourceProvider(int key) {
   static base::NoDestructor<LazyDirectoryListerCacher> lazy_dir_lister;
 
   if (IDR_DIR_HEADER_HTML == key)
@@ -65,5 +63,3 @@
 
   return ui::ResourceBundle::GetSharedInstance().GetRawDataResource(key);
 }
-
-}  // namespace chrome_common_net
diff --git a/chrome/common/net/net_resource_provider.h b/chrome/common/net/net_resource_provider.h
index 80a1602..de752f1 100644
--- a/chrome/common/net/net_resource_provider.h
+++ b/chrome/common/net/net_resource_provider.h
@@ -7,11 +7,7 @@
 
 #include "base/strings/string_piece.h"
 
-namespace chrome_common_net {
-
 // This is called indirectly by the network layer to access resources.
-base::StringPiece NetResourceProvider(int key);
-
-}  // namespace chrome_common_net
+base::StringPiece ChromeNetResourceProvider(int key);
 
 #endif  // CHROME_COMMON_NET_NET_RESOURCE_PROVIDER_H_
diff --git a/chrome/common/render_messages.h b/chrome/common/render_messages.h
index 7da5adb..97bb5f3e 100644
--- a/chrome/common/render_messages.h
+++ b/chrome/common/render_messages.h
@@ -13,7 +13,6 @@
 #include "base/strings/string16.h"
 #include "base/time/time.h"
 #include "build/build_config.h"
-#include "chrome/common/browser_controls_state_param_traits.h"
 #include "chrome/common/buildflags.h"
 #include "chrome/common/web_application_info_provider_param_traits.h"
 #include "components/content_settings/core/common/content_settings.h"
diff --git a/chrome/renderer/chrome_render_frame_observer.cc b/chrome/renderer/chrome_render_frame_observer.cc
index f2eea9c..fa001df3 100644
--- a/chrome/renderer/chrome_render_frame_observer.cc
+++ b/chrome/renderer/chrome_render_frame_observer.cc
@@ -344,7 +344,7 @@
 
   GURL osdd_url = frame->GetDocument().OpenSearchDescriptionURL();
   if (!osdd_url.is_empty()) {
-    chrome::mojom::OpenSearchDescriptionDocumentHandlerAssociatedPtr
+    mojo::AssociatedRemote<chrome::mojom::OpenSearchDescriptionDocumentHandler>
         osdd_handler;
     render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
         &osdd_handler);
@@ -369,7 +369,8 @@
 
   // Connect to Mojo service on browser to notify it of the page's archive
   // properties.
-  offline_pages::mojom::MhtmlPageNotifierAssociatedPtr mhtml_notifier;
+  mojo::AssociatedRemote<offline_pages::mojom::MhtmlPageNotifier>
+      mhtml_notifier;
   render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
       &mhtml_notifier);
   DCHECK(mhtml_notifier);
@@ -502,8 +503,9 @@
 }
 
 void ChromeRenderFrameObserver::OnRenderFrameObserverRequest(
-    chrome::mojom::ChromeRenderFrameAssociatedRequest request) {
-  bindings_.AddBinding(this, std::move(request));
+    mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame>
+        receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 void ChromeRenderFrameObserver::SetWindowFeatures(
@@ -511,13 +513,3 @@
   render_frame()->GetRenderView()->GetWebView()->SetWindowFeatures(
       content::ConvertMojoWindowFeaturesToWebWindowFeatures(*window_features));
 }
-
-void ChromeRenderFrameObserver::UpdateBrowserControlsState(
-    content::BrowserControlsState constraints,
-    content::BrowserControlsState current,
-    bool animate) {
-#if defined(OS_ANDROID) || defined(OS_CHROMEOS)
-  render_frame()->GetRenderView()->UpdateBrowserControlsState(constraints,
-                                                              current, animate);
-#endif
-}
diff --git a/chrome/renderer/chrome_render_frame_observer.h b/chrome/renderer/chrome_render_frame_observer.h
index 7f4d9b27..98ea5b6 100644
--- a/chrome/renderer/chrome_render_frame_observer.h
+++ b/chrome/renderer/chrome_render_frame_observer.h
@@ -11,8 +11,8 @@
 #include "chrome/common/chrome_render_frame.mojom.h"
 #include "chrome/common/prerender_types.h"
 #include "content/public/renderer/render_frame_observer.h"
-#include "mojo/public/cpp/bindings/associated_binding_set.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 
@@ -89,12 +89,10 @@
   void RequestReloadImageForContextNode() override;
   void SetClientSidePhishingDetection(bool enable_phishing_detection) override;
   void GetWebApplicationInfo(GetWebApplicationInfoCallback callback) override;
-  void UpdateBrowserControlsState(content::BrowserControlsState constraints,
-                                  content::BrowserControlsState current,
-                                  bool animate) override;
 
   void OnRenderFrameObserverRequest(
-      chrome::mojom::ChromeRenderFrameAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<chrome::mojom::ChromeRenderFrame>
+          receiver);
 
   // Captures page information using the top (main) frame of a frame tree.
   // Currently, this page information is just the text content of the all
@@ -118,7 +116,7 @@
   std::vector<base::string16> webui_javascript_;
 #endif
 
-  mojo::AssociatedBindingSet<chrome::mojom::ChromeRenderFrame> bindings_;
+  mojo::AssociatedReceiverSet<chrome::mojom::ChromeRenderFrame> receivers_;
 
   service_manager::BinderRegistry registry_;
   blink::AssociatedInterfaceRegistry associated_interfaces_;
diff --git a/chrome/renderer/chrome_render_thread_observer.cc b/chrome/renderer/chrome_render_thread_observer.cc
index d570bcc..47189d7 100644
--- a/chrome/renderer/chrome_render_thread_observer.cc
+++ b/chrome/renderer/chrome_render_thread_observer.cc
@@ -198,9 +198,8 @@
   thread->SetResourceDispatcherDelegate(resource_delegate_.get());
 
   // Configure modules that need access to resources.
-  net::NetModule::SetResourceProvider(chrome_common_net::NetResourceProvider);
-  media::SetLocalizedStringProvider(
-      chrome_common_media::LocalizedStringProvider);
+  net::NetModule::SetResourceProvider(ChromeNetResourceProvider);
+  media::SetLocalizedStringProvider(ChromeMediaLocalizedStringProvider);
 
   // chrome-native: is a scheme used for placeholder navigations that allow
   // UIs to be drawn with platform native widgets instead of HTML.  These pages
@@ -275,8 +274,9 @@
 }
 
 void ChromeRenderThreadObserver::OnRendererConfigurationAssociatedRequest(
-    chrome::mojom::RendererConfigurationAssociatedRequest request) {
-  renderer_configuration_bindings_.AddBinding(this, std::move(request));
+    mojo::PendingAssociatedReceiver<chrome::mojom::RendererConfiguration>
+        receiver) {
+  renderer_configuration_receivers_.Add(this, std::move(receiver));
 }
 
 const RendererContentSettingRules*
diff --git a/chrome/renderer/chrome_render_thread_observer.h b/chrome/renderer/chrome_render_thread_observer.h
index be58214a..b49fc17 100644
--- a/chrome/renderer/chrome_render_thread_observer.h
+++ b/chrome/renderer/chrome_render_thread_observer.h
@@ -13,10 +13,12 @@
 #include "chrome/common/renderer_configuration.mojom.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "content/public/renderer/render_thread_observer.h"
-#include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 
 #if defined(OS_CHROMEOS)
 #include "chrome/renderer/chromeos_delayed_callback_group.h"
+#include "mojo/public/cpp/bindings/binding.h"
 #endif  // defined(OS_CHROMEOS)
 
 namespace content {
@@ -114,7 +116,8 @@
                           const std::string& group_name) override;
 
   void OnRendererConfigurationAssociatedRequest(
-      chrome::mojom::RendererConfigurationAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<chrome::mojom::RendererConfiguration>
+          receiver);
 
   static bool is_incognito_process_;
   std::unique_ptr<content::ResourceDispatcherDelegate> resource_delegate_;
@@ -122,8 +125,8 @@
 
   std::unique_ptr<visitedlink::VisitedLinkSlave> visited_link_slave_;
 
-  mojo::AssociatedBindingSet<chrome::mojom::RendererConfiguration>
-      renderer_configuration_bindings_;
+  mojo::AssociatedReceiverSet<chrome::mojom::RendererConfiguration>
+      renderer_configuration_receivers_;
 
 #if defined(OS_CHROMEOS)
   // Only set if the Chrome OS merge session was running when the renderer
diff --git a/chrome/renderer/content_settings_observer.cc b/chrome/renderer/content_settings_observer.cc
index 67f7a209..87b2be2 100644
--- a/chrome/renderer/content_settings_observer.cc
+++ b/chrome/renderer/content_settings_observer.cc
@@ -247,8 +247,9 @@
 }
 
 void ContentSettingsObserver::OnContentSettingsRendererRequest(
-    chrome::mojom::ContentSettingsRendererAssociatedRequest request) {
-  bindings_.AddBinding(this, std::move(request));
+    mojo::PendingAssociatedReceiver<chrome::mojom::ContentSettingsRenderer>
+        receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 bool ContentSettingsObserver::AllowDatabase() {
@@ -531,7 +532,7 @@
   UMA_HISTOGRAM_COUNTS_100("ClientHints.UpdateSize", update_count);
 
   // Notify the embedder.
-  client_hints::mojom::ClientHintsAssociatedPtr host_observer;
+  mojo::AssociatedRemote<client_hints::mojom::ClientHints> host_observer;
   render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&host_observer);
   host_observer->PersistClientHints(primary_origin, std::move(client_hints),
                                     duration);
diff --git a/chrome/renderer/content_settings_observer.h b/chrome/renderer/content_settings_observer.h
index 5939455c..e55f9c20 100644
--- a/chrome/renderer/content_settings_observer.h
+++ b/chrome/renderer/content_settings_observer.h
@@ -19,7 +19,9 @@
 #include "content/public/renderer/render_frame_observer.h"
 #include "content/public/renderer/render_frame_observer_tracker.h"
 #include "extensions/buildflags/buildflags.h"
-#include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/associated_remote.h"
 #include "services/service_manager/public/cpp/binder_registry.h"
 #include "third_party/blink/public/platform/web_content_settings_client.h"
 #include "url/gurl.h"
@@ -128,7 +130,8 @@
   void SetAsInterstitial() override;
 
   void OnContentSettingsRendererRequest(
-      chrome::mojom::ContentSettingsRendererAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<chrome::mojom::ContentSettingsRenderer>
+          receiver);
 
   // Message handlers.
   void OnLoadBlockedPlugins(const std::string& identifier);
@@ -190,7 +193,8 @@
   // If true, IsWhitelistedForContentSettings will always return true.
   const bool should_whitelist_;
 
-  mojo::AssociatedBindingSet<chrome::mojom::ContentSettingsRenderer> bindings_;
+  mojo::AssociatedReceiverSet<chrome::mojom::ContentSettingsRenderer>
+      receivers_;
 
   DISALLOW_COPY_AND_ASSIGN(ContentSettingsObserver);
 };
diff --git a/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc b/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc
index 796b206..714404e7 100644
--- a/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc
+++ b/chrome/renderer/plugins/non_loadable_plugin_placeholder.cc
@@ -56,7 +56,7 @@
   plugins::PluginPlaceholder* plugin =
       new plugins::PluginPlaceholder(render_frame, params, html_data);
 
-  chrome::mojom::PluginHostAssociatedPtr plugin_host;
+  mojo::AssociatedRemote<chrome::mojom::PluginHost> plugin_host;
   render_frame->GetRemoteAssociatedInterfaces()->GetInterface(&plugin_host);
   plugin_host->CouldNotLoadPlugin(file_path);
 
diff --git a/chrome/renderer/prerender/prerender_dispatcher.cc b/chrome/renderer/prerender/prerender_dispatcher.cc
index a259c66..edf31ee8 100644
--- a/chrome/renderer/prerender/prerender_dispatcher.cc
+++ b/chrome/renderer/prerender/prerender_dispatcher.cc
@@ -128,8 +128,9 @@
 }
 
 void PrerenderDispatcher::OnPrerenderDispatcherRequest(
-    chrome::mojom::PrerenderDispatcherAssociatedRequest request) {
-  bindings_.AddBinding(this, std::move(request));
+    mojo::PendingAssociatedReceiver<chrome::mojom::PrerenderDispatcher>
+        receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 void PrerenderDispatcher::RegisterMojoInterfaces(
diff --git a/chrome/renderer/prerender/prerender_dispatcher.h b/chrome/renderer/prerender/prerender_dispatcher.h
index b741683..ecbcadc6 100644
--- a/chrome/renderer/prerender/prerender_dispatcher.h
+++ b/chrome/renderer/prerender/prerender_dispatcher.h
@@ -14,7 +14,8 @@
 #include "base/time/time.h"
 #include "chrome/common/prerender.mojom.h"
 #include "content/public/renderer/render_thread_observer.h"
-#include "mojo/public/cpp/bindings/associated_binding_set.h"
+#include "mojo/public/cpp/bindings/associated_receiver_set.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
 #include "third_party/blink/public/platform/web_prerender.h"
 #include "third_party/blink/public/platform/web_prerendering_support.h"
@@ -51,7 +52,8 @@
   void PrerenderStop(int prerender_id) override;
 
   void OnPrerenderDispatcherRequest(
-      chrome::mojom::PrerenderDispatcherAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<chrome::mojom::PrerenderDispatcher>
+          receiver);
 
   // From RenderThreadObserver:
   void RegisterMojoInterfaces(
@@ -77,7 +79,7 @@
   base::TimeTicks process_start_time_;
   base::TimeTicks prefetch_parsed_time_;
 
-  mojo::AssociatedBindingSet<chrome::mojom::PrerenderDispatcher> bindings_;
+  mojo::AssociatedReceiverSet<chrome::mojom::PrerenderDispatcher> receivers_;
 };
 
 }  // namespace prerender
diff --git a/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc b/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
index d3a60a0..c1b3830 100644
--- a/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
+++ b/chrome/renderer/safe_browsing/phishing_term_feature_extractor_unittest.cc
@@ -131,7 +131,7 @@
     active_run_loop_->QuitWhenIdle();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<base::RunLoop> active_run_loop_;
   MockFeatureExtractorClock clock_;
   std::unique_ptr<PhishingTermFeatureExtractor> extractor_;
diff --git a/chrome/renderer/sandbox_status_extension_android.cc b/chrome/renderer/sandbox_status_extension_android.cc
index ea7b0cac..8076cd91 100644
--- a/chrome/renderer/sandbox_status_extension_android.cc
+++ b/chrome/renderer/sandbox_status_extension_android.cc
@@ -26,7 +26,7 @@
 #include "v8/include/v8.h"
 
 SandboxStatusExtension::SandboxStatusExtension(content::RenderFrame* frame)
-    : content::RenderFrameObserver(frame), binding_(this) {
+    : content::RenderFrameObserver(frame) {
   // Don't do anything else for subframes.
   if (!frame->IsMainFrame())
     return;
@@ -57,8 +57,9 @@
 }
 
 void SandboxStatusExtension::OnSandboxStatusExtensionRequest(
-    chrome::mojom::SandboxStatusExtensionAssociatedRequest request) {
-  binding_.Bind(std::move(request));
+    mojo::PendingAssociatedReceiver<chrome::mojom::SandboxStatusExtension>
+        receiver) {
+  receiver_.Bind(std::move(receiver));
 }
 
 void SandboxStatusExtension::Install() {
diff --git a/chrome/renderer/sandbox_status_extension_android.h b/chrome/renderer/sandbox_status_extension_android.h
index 0ea0434..0b352ec 100644
--- a/chrome/renderer/sandbox_status_extension_android.h
+++ b/chrome/renderer/sandbox_status_extension_android.h
@@ -12,7 +12,8 @@
 #include "base/values.h"
 #include "chrome/common/sandbox_status_extension_android.mojom.h"
 #include "content/public/renderer/render_frame_observer.h"
-#include "mojo/public/cpp/bindings/associated_binding.h"
+#include "mojo/public/cpp/bindings/associated_receiver.h"
+#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
 #include "v8/include/v8.h"
 
 namespace gin {
@@ -45,7 +46,8 @@
   void AddSandboxStatusExtension() override;
 
   void OnSandboxStatusExtensionRequest(
-      chrome::mojom::SandboxStatusExtensionAssociatedRequest request);
+      mojo::PendingAssociatedReceiver<chrome::mojom::SandboxStatusExtension>
+          receiver);
 
   // Installs the JavaScript function into the scripting context, if
   // should_install_ is true.
@@ -67,7 +69,8 @@
   // Set to true by AddSandboxStatusExtension().
   bool should_install_ = false;
 
-  mojo::AssociatedBinding<chrome::mojom::SandboxStatusExtension> binding_;
+  mojo::AssociatedReceiver<chrome::mojom::SandboxStatusExtension> receiver_{
+      this};
 
   DISALLOW_COPY_AND_ASSIGN(SandboxStatusExtension);
 };
diff --git a/chrome/services/app_service/app_service_impl_unittest.cc b/chrome/services/app_service/app_service_impl_unittest.cc
index 1de4cc1..40010fb4 100644
--- a/chrome/services/app_service/app_service_impl_unittest.cc
+++ b/chrome/services/app_service/app_service_impl_unittest.cc
@@ -119,7 +119,7 @@
 
 class AppServiceImplTest : public testing::Test {
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 TEST_F(AppServiceImplTest, PubSub) {
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index e0ec8df2..fc6cbca 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -719,6 +719,7 @@
       "//ui/compositor:test_support",
       "//ui/native_theme:test_support",
       "//ui/resources",
+      "//ui/shell_dialogs:test_support",
       "//ui/web_dialogs:test_support",
       "//v8",
     ]
@@ -5102,6 +5103,7 @@
       "../browser/ui/app_list/search/search_result_ranker/app_list_launch_metrics_provider_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/app_search_result_ranker_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/frecency_store_unittest.cc",
+      "../browser/ui/app_list/search/search_result_ranker/histogram_util_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/ml_app_rank_provider_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/ranking_item_util_unittest.cc",
       "../browser/ui/app_list/search/search_result_ranker/recurrence_predictor_unittest.cc",
diff --git a/chrome/test/chromedriver/test/run_py_tests.py b/chrome/test/chromedriver/test/run_py_tests.py
index def91fa..6cee4fa 100755
--- a/chrome/test/chromedriver/test/run_py_tests.py
+++ b/chrome/test/chromedriver/test/run_py_tests.py
@@ -454,6 +454,36 @@
     self.assertTrue(eager_time < 9)
     thread.join()
 
+  def testDoesntWaitWhenPageLoadStrategyIsNone(self):
+    class HandleRequest(object):
+      def __init__(self):
+        self.sent_hello = threading.Event()
+
+      def slowPage(self, request):
+        self.sent_hello.wait(2)
+        return {}, """
+        <html>
+        <body>hello</body>
+        </html>"""
+
+    handler = HandleRequest()
+    self._http_server.SetCallbackForPath('/slow', handler.slowPage)
+
+    driver = self.CreateDriver(page_load_strategy='none')
+    self.assertEquals('none', driver.capabilities['pageLoadStrategy'])
+
+    driver.Load(self._http_server.GetUrl() + '/chromedriver/empty.html')
+    start = time.time()
+    driver.Load(self._http_server.GetUrl() + '/slow')
+    self.assertTrue(time.time() - start < 2)
+    handler.sent_hello.set()
+    self.WaitForCondition(lambda: 'hello' in driver.GetPageSource())
+    self.assertTrue('hello' in driver.GetPageSource())
+
+  def testUnsupportedPageLoadStrategyRaisesException(self):
+    self.assertRaises(chromedriver.InvalidArgument,
+                      self.CreateDriver, page_load_strategy="unsupported")
+
 
 class ChromeDriverTest(ChromeDriverBaseTestWithWebServer):
   """End to end tests for ChromeDriver."""
@@ -3502,36 +3532,6 @@
     div.SingleTap()
     self.assertEquals(1, len(driver.FindElements('tag name', 'br')))
 
-  def testDoesntWaitWhenPageLoadStrategyIsNone(self):
-    class HandleRequest(object):
-      def __init__(self):
-        self.sent_hello = threading.Event()
-
-      def slowPage(self, request):
-        self.sent_hello.wait(2)
-        return {}, """
-        <html>
-        <body>hello</body>
-        </html>"""
-
-    handler = HandleRequest()
-    self._http_server.SetCallbackForPath('/slow', handler.slowPage)
-
-    driver = self.CreateDriver(page_load_strategy='none')
-    self.assertEquals('none', driver.capabilities['pageLoadStrategy'])
-
-    driver.Load(self._http_server.GetUrl() + '/chromedriver/empty.html')
-    start = time.time()
-    driver.Load(self._http_server.GetUrl() + '/slow')
-    self.assertTrue(time.time() - start < 2)
-    handler.sent_hello.set()
-    self.WaitForCondition(lambda: 'hello' in driver.GetPageSource())
-    self.assertTrue('hello' in driver.GetPageSource())
-
-  def testUnsupportedPageLoadStrategyRaisesException(self):
-    self.assertRaises(chromedriver.InvalidArgument,
-                      self.CreateDriver, page_load_strategy="unsupported")
-
   def testNetworkConnectionDisabledByDefault(self):
     driver = self.CreateDriver()
     self.assertFalse(driver.capabilities['networkConnectionEnabled'])
diff --git a/chrome/test/data/local_ntp/customize_menu_browsertest.js b/chrome/test/data/local_ntp/customize_menu_browsertest.js
index 8ca3492..663d3bd46 100644
--- a/chrome/test/data/local_ntp/customize_menu_browsertest.js
+++ b/chrome/test/data/local_ntp/customize_menu_browsertest.js
@@ -533,6 +533,11 @@
   const hiddenToggle = $(test.customizeMenu.IDS.SHORTCUTS_HIDE_TOGGLE);
   hiddenToggle.dispatchEvent(enter);
   assertShortcutOptionsSelected(false, true, true);
+
+  // Select the custom links option. The custom links option should be selected
+  // and the hide shortcuts toggle disabled.
+  $(test.customizeMenu.IDS.SHORTCUTS_OPTION_CUSTOM_LINKS).click();
+  assertShortcutOptionsSelected(true, false, false);
 };
 
 /**
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index e8876e394..16bcbae 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -404,13 +404,7 @@
   },
 
   "ExternalPrintServers": {
-    "os": ["chromeos"],
-    "test_policy": {
-      "ExternalPrintServers": {
-        "url": "https://example.com/policyfile",
-        "hash": "deadbeefdeadbeefdeadbeef"
-      }
-    }
+    "note": "This policy will be added in R-79"
   },
 
   "NativePrinters": {
diff --git a/chrome/test/data/webui/BUILD.gn b/chrome/test/data/webui/BUILD.gn
index a017711..5e34b4f6 100644
--- a/chrome/test/data/webui/BUILD.gn
+++ b/chrome/test/data/webui/BUILD.gn
@@ -156,6 +156,7 @@
   }
   deps = [
     ":modulize",
+    "//build:branding_buildflags",
     "//chrome/browser/ui",
     "//skia",
   ]
diff --git a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
index f7b93fd..6763186 100644
--- a/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/chromeos/os_settings_browsertest.js
@@ -11,6 +11,7 @@
 GEN_INCLUDE(['//chrome/test/data/webui/polymer_browser_test_base.js']);
 
 GEN('#include "ash/public/cpp/ash_features.h"');
+GEN('#include "build/branding_buildflags.h"');
 GEN('#include "chrome/common/chrome_features.h"');
 GEN('#include "chromeos/constants/chromeos_features.h"');
 
@@ -71,7 +72,7 @@
   mocha.run();
 });
 
-GEN('#if defined(GOOGLE_CHROME_BUILD)');
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 TEST_F('OSSettingsAboutPageTest', 'AboutPage_OfficialBuild', () => {
   settings_about_page.registerOfficialBuildTests();
   mocha.run();
diff --git a/chrome/test/data/webui/settings/cr_settings_browsertest.js b/chrome/test/data/webui/settings/cr_settings_browsertest.js
index 0240a59..a652b35 100644
--- a/chrome/test/data/webui/settings/cr_settings_browsertest.js
+++ b/chrome/test/data/webui/settings/cr_settings_browsertest.js
@@ -12,6 +12,7 @@
 GEN('#include "chromeos/constants/chromeos_switches.h"');
 GEN('#endif  // defined(OS_CHROMEOS)');
 
+GEN('#include "build/branding_buildflags.h"');
 GEN('#include "chrome/common/chrome_features.h"');
 GEN('#include "chromeos/constants/chromeos_features.h"');
 GEN('#include "components/autofill/core/common/autofill_features.h"');
@@ -239,7 +240,7 @@
   mocha.run();
 });
 
-GEN('#if defined(GOOGLE_CHROME_BUILD)');
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 TEST_F('CrSettingsAboutPageTest', 'AboutPage_OfficialBuild', function() {
   settings_about_page.registerOfficialBuildTests();
   mocha.run();
@@ -888,7 +889,7 @@
   mocha.run();
 });
 
-GEN('#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)');
+GEN('#if defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 
 /**
  * @constructor
@@ -938,7 +939,7 @@
   mocha.run();
 });
 
-GEN('#endif  // defined(OS_WIN) and defined(GOOGLE_CHROME_BUILD)');
+GEN('#endif  // defined(OS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 
 /**
  * @constructor
@@ -1132,7 +1133,7 @@
   mocha.grep('PersonalizationOptionsTests_AllBuilds').run();
 });
 
-GEN('#if defined(GOOGLE_CHROME_BUILD)');
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 TEST_F('CrSettingsPersonalizationOptionsTest', 'OfficialBuild', function() {
   mocha.grep('PersonalizationOptionsTests_OfficialBuild').run();
 });
@@ -1845,7 +1846,7 @@
   mocha.grep(assert(languages_page_tests.TestNames.Spellcheck)).run();
 });
 
-GEN('#if defined(GOOGLE_CHROME_BUILD)');
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 TEST_F('CrSettingsLanguagesPageTest', 'SpellcheckOfficialBuild', function() {
   mocha.grep(assert(languages_page_tests.TestNames.SpellcheckOfficialBuild))
       .run();
@@ -2071,7 +2072,7 @@
   mocha.run();
 });
 
-GEN('#if defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS)');
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && !defined(OS_CHROMEOS)');
 
 function CrSettingsMetricsReportingTest() {}
 
@@ -2093,7 +2094,7 @@
   mocha.run();
 });
 
-GEN('#endif  // defined(GOOGLE_CHROME_BUILD) && !defined(OS_CHROMEOS)');
+GEN('#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING) && !defined(OS_CHROMEOS)');
 
 GEN('#if defined(OS_CHROMEOS)');
 
diff --git a/chrome/test/data/webui/signin/signin_browsertest.js b/chrome/test/data/webui/signin/signin_browsertest.js
index 2502d413..3b7999b 100644
--- a/chrome/test/data/webui/signin/signin_browsertest.js
+++ b/chrome/test/data/webui/signin/signin_browsertest.js
@@ -7,6 +7,7 @@
 // Polymer BrowserTest fixture.
 GEN_INCLUDE(['//chrome/test/data/webui/polymer_browser_test_base.js']);
 GEN('#include "base/command_line.h"');
+GEN('#include "build/branding_buildflags.h"');
 GEN('#include "chrome/test/data/webui/signin_browsertest.h"');
 
 /**
@@ -46,7 +47,7 @@
 
 // TODO(https://crbug.com/862573): Re-enable when no longer failing when
 // is_chrome_branded is true.
-GEN('#if defined(GOOGLE_CHROME_BUILD)');
+GEN('#if BUILDFLAG(GOOGLE_CHROME_BRANDING)');
 GEN('#define MAYBE_DialogWithDice DISABLED_DialogWithDice');
 GEN('#else');
 GEN('#define MAYBE_DialogWithDice');
diff --git a/chrome/test/data/webui/welcome/a11y_tests.js b/chrome/test/data/webui/welcome/a11y_tests.js
index 6a611f44..da69f12 100644
--- a/chrome/test/data/webui/welcome/a11y_tests.js
+++ b/chrome/test/data/webui/welcome/a11y_tests.js
@@ -19,6 +19,14 @@
   get featureList() {
     return {enabled: ['welcome::kForceEnabled']};
   }
+
+  /** @override */
+  get extraLibraries() {
+    return [
+      ...super.extraLibraries,
+      '//ui/webui/resources/js/util.js',
+    ];
+  }
 };
 
 AccessibilityTest.define('WelcomeA11y', {
diff --git a/chrome/test/data/webui/welcome/app_chooser_test.js b/chrome/test/data/webui/welcome/app_chooser_test.js
index 0bdfb63..c82fb79 100644
--- a/chrome/test/data/webui/welcome/app_chooser_test.js
+++ b/chrome/test/data/webui/welcome/app_chooser_test.js
@@ -2,170 +2,187 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome_app_chooser', function() {
-  suite('AppChooserTest', function() {
-    const apps = [
-      {
-        id: 0,
-        name: 'First',
-        icon: 'first',
-        url: 'http://first.example.com',
-      },
-      {
-        id: 1,
-        name: 'Second',
-        icon: 'second',
-        url: 'http://second.example.com',
-      },
-      {
-        id: 2,
-        name: 'Third',
-        icon: 'third',
-        url: 'http://third.example.com',
-      },
-      {
-        id: 3,
-        name: 'Fourth',
-        icon: 'fourth',
-        url: 'http://fourth.example.com',
-      },
-      {
-        id: 4,
-        name: 'Fifth',
-        icon: 'fifth',
-        url: 'http://fifth.example.com',
-      },
-    ];
+import 'chrome://welcome/google_apps/nux_google_apps.js';
 
-    /** @type {welcome.NuxAppProxy} */
-    let testAppBrowserProxy;
+import {TestBookmarkProxy} from 'chrome://test/welcome/test_bookmark_proxy.js';
+import {TestGoogleAppProxy} from 'chrome://test/welcome/test_google_app_proxy.js';
+import {TestMetricsProxy} from 'chrome://test/welcome/test_metrics_proxy.js';
+import {GoogleAppProxyImpl} from 'chrome://welcome/google_apps/google_app_proxy.js';
+import {GoogleAppsMetricsProxyImpl} from 'chrome://welcome/google_apps/google_apps_metrics_proxy.js';
+import {BookmarkBarManager, BookmarkProxyImpl} from 'chrome://welcome/shared/bookmark_proxy.js';
 
-    /** @type {welcome.ModuleMetricsProxy} */
-    let testAppMetricsProxy;
+suite('AppChooserTest', function() {
+  const apps = [
+    {
+      id: 0,
+      name: 'First',
+      icon: 'first',
+      url: 'http://first.example.com',
+    },
+    {
+      id: 1,
+      name: 'Second',
+      icon: 'second',
+      url: 'http://second.example.com',
+    },
+    {
+      id: 2,
+      name: 'Third',
+      icon: 'third',
+      url: 'http://third.example.com',
+    },
+    {
+      id: 3,
+      name: 'Fourth',
+      icon: 'fourth',
+      url: 'http://fourth.example.com',
+    },
+    {
+      id: 4,
+      name: 'Fifth',
+      icon: 'fifth',
+      url: 'http://fifth.example.com',
+    },
+  ];
 
-    /** @type {welcome.BookmarkProxy} */
-    let testBookmarkBrowserProxy;
+  /** @type {NuxAppProxy} */
+  let testAppBrowserProxy;
 
-    /** @type {AppChooserElement} */
-    let testElement;
+  /** @type {ModuleMetricsProxy} */
+  let testAppMetricsProxy;
 
-    setup(async function() {
-      testAppBrowserProxy = new TestGoogleAppProxy();
-      testAppMetricsProxy = new TestMetricsProxy();
-      testBookmarkBrowserProxy = new TestBookmarkProxy();
+  /** @type {BookmarkProxy} */
+  let testBookmarkBrowserProxy;
 
-      welcome.GoogleAppProxyImpl.instance_ = testAppBrowserProxy;
-      welcome.GoogleAppsMetricsProxyImpl.instance_ = testAppMetricsProxy;
-      welcome.BookmarkProxyImpl.instance_ = testBookmarkBrowserProxy;
-      welcome.BookmarkBarManager.instance_ = new welcome.BookmarkBarManager();
+  /** @type {AppChooserElement} */
+  let testElement;
 
-      testAppBrowserProxy.setAppList(apps);
+  setup(async function() {
+    testAppBrowserProxy = new TestGoogleAppProxy();
+    testAppMetricsProxy = new TestMetricsProxy();
+    testBookmarkBrowserProxy = new TestBookmarkProxy();
 
-      PolymerTest.clearBody();
-      testElement = document.createElement('nux-google-apps');
-      document.body.appendChild(testElement);
-      // Simulate nux-app's onRouteEnter call.
-      testElement.onRouteEnter();
-      await testAppMetricsProxy.whenCalled('recordPageShown');
-      await testAppBrowserProxy.whenCalled('getAppList');
-    });
+    GoogleAppProxyImpl.instance_ = testAppBrowserProxy;
+    GoogleAppsMetricsProxyImpl.instance_ = testAppMetricsProxy;
+    BookmarkProxyImpl.instance_ = testBookmarkBrowserProxy;
+    BookmarkBarManager.instance_ = new BookmarkBarManager();
 
-    teardown(function() {
-      testElement.remove();
-    });
+    testAppBrowserProxy.setAppList(apps);
 
-    function getSelected() {
-      return Array.from(
-          testElement.shadowRoot.querySelectorAll('.option[active]'));
-    }
+    PolymerTest.clearBody();
 
-    test('test app chooser options', async function() {
-      const options =
-          Array.from(testElement.shadowRoot.querySelectorAll('.option'));
-      assertEquals(5, options.length);
+    // Add <base> so that images in nux-google-apps will be loaded from the
+    // correct data source.
+    const base = document.createElement('base');
+    base.href = 'chrome://welcome/google_apps/';
+    document.head.appendChild(base);
+    testElement = document.createElement('nux-google-apps');
+    document.body.appendChild(testElement);
+    // Remove <base> so that routing happens from a base of chrome://test, to
+    // prevent a security error.
+    document.head.removeChild(base);
 
-      // First three options are selected and action button should be enabled.
-      assertDeepEquals(options.slice(0, 3), getSelected());
-      assertFalse(testElement.$$('.action-button').disabled);
+    // Simulate nux-app's onRouteEnter call.
+    testElement.onRouteEnter();
+    await testAppMetricsProxy.whenCalled('recordPageShown');
+    await testAppBrowserProxy.whenCalled('getAppList');
+  });
 
-      // Click the first option to deselect it.
-      testBookmarkBrowserProxy.reset();
-      options[0].click();
+  teardown(function() {
+    testElement.remove();
+  });
 
-      assertEquals(
-          1, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
-      assertDeepEquals(options.slice(1, 3), getSelected());
-      assertFalse(testElement.$$('.action-button').disabled);
+  function getSelected() {
+    return Array.from(
+        testElement.shadowRoot.querySelectorAll('.option[active]'));
+  }
 
-      // Click fourth option to select it.
-      testBookmarkBrowserProxy.reset();
-      options[3].click();
+  test('test app chooser options', async function() {
+    const options =
+        Array.from(testElement.shadowRoot.querySelectorAll('.option'));
+    assertEquals(5, options.length);
 
-      assertDeepEquals(
-          {
-            title: apps[3].name,
-            url: apps[3].url,
-            parentId: '1',
-          },
-          await testBookmarkBrowserProxy.whenCalled('addBookmark'));
+    // First three options are selected and action button should be enabled.
+    assertDeepEquals(options.slice(0, 3), getSelected());
+    assertFalse(testElement.$$('.action-button').disabled);
 
-      assertDeepEquals(options.slice(1, 4), getSelected());
-      assertFalse(testElement.$$('.action-button').disabled);
+    // Click the first option to deselect it.
+    testBookmarkBrowserProxy.reset();
+    options[0].click();
 
-      // Click fourth option again to deselect it.
-      testBookmarkBrowserProxy.reset();
-      options[3].click();
+    assertEquals(
+        1, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
+    assertDeepEquals(options.slice(1, 3), getSelected());
+    assertFalse(testElement.$$('.action-button').disabled);
 
-      assertEquals(
-          4, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
-      assertDeepEquals(options.slice(1, 3), getSelected());
-      assertFalse(testElement.$$('.action-button').disabled);
+    // Click fourth option to select it.
+    testBookmarkBrowserProxy.reset();
+    options[3].click();
 
-      // Click second option to deselect it.
-      testBookmarkBrowserProxy.reset();
-      options[1].click();
+    assertDeepEquals(
+        {
+          title: apps[3].name,
+          url: apps[3].url,
+          parentId: '1',
+        },
+        await testBookmarkBrowserProxy.whenCalled('addBookmark'));
 
-      assertEquals(
-          2, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
-      assertDeepEquals(options.slice(2, 3), getSelected());
-      assertFalse(testElement.$$('.action-button').disabled);
+    assertDeepEquals(options.slice(1, 4), getSelected());
+    assertFalse(testElement.$$('.action-button').disabled);
 
-      // Click third option to deselect all options.
-      testBookmarkBrowserProxy.reset();
-      options[2].click();
+    // Click fourth option again to deselect it.
+    testBookmarkBrowserProxy.reset();
+    options[3].click();
 
-      assertEquals(
-          3, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
-      assertEquals(0, getSelected().length);
-      assertTrue(testElement.$$('.action-button').disabled);
-    });
+    assertEquals(
+        4, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
+    assertDeepEquals(options.slice(1, 3), getSelected());
+    assertFalse(testElement.$$('.action-button').disabled);
 
-    test('test app chooser skip button', async function() {
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-      testElement.wasBookmarkBarShownOnInit_ = true;
+    // Click second option to deselect it.
+    testBookmarkBrowserProxy.reset();
+    options[1].click();
 
-      // First option should be selected and action button should be enabled.
-      testElement.$.noThanksButton.click();
-      assertEquals(
-          1, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
-      assertEquals(
-          true, await testBookmarkBrowserProxy.whenCalled('toggleBookmarkBar'));
-      await testAppMetricsProxy.whenCalled('recordDidNothingAndChoseSkip');
-    });
+    assertEquals(
+        2, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
+    assertDeepEquals(options.slice(2, 3), getSelected());
+    assertFalse(testElement.$$('.action-button').disabled);
 
-    test('test app chooser next button', async function() {
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-      testElement.wasBookmarkBarShownOnInit_ = true;
+    // Click third option to deselect all options.
+    testBookmarkBrowserProxy.reset();
+    options[2].click();
 
-      // First option should be selected and action button should be enabled.
-      testElement.$$('.action-button').click();
+    assertEquals(
+        3, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
+    assertEquals(0, getSelected().length);
+    assertTrue(testElement.$$('.action-button').disabled);
+  });
 
-      await testAppMetricsProxy.whenCalled('recordDidNothingAndChoseNext');
+  test('test app chooser skip button', async function() {
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    testElement.wasBookmarkBarShownOnInit_ = true;
 
-      // Test framework only records first result, but should be called 3 times.
-      assertEquals(
-          0, await testAppBrowserProxy.whenCalled('recordProviderSelected'));
-      assertEquals(3, testAppBrowserProxy.providerSelectedCount);
-    });
+    // First option should be selected and action button should be enabled.
+    testElement.$.noThanksButton.click();
+    assertEquals(
+        1, await testBookmarkBrowserProxy.whenCalled('removeBookmark'));
+    assertEquals(
+        true, await testBookmarkBrowserProxy.whenCalled('toggleBookmarkBar'));
+    await testAppMetricsProxy.whenCalled('recordDidNothingAndChoseSkip');
+  });
+
+  test('test app chooser next button', async function() {
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    testElement.wasBookmarkBarShownOnInit_ = true;
+
+    // First option should be selected and action button should be enabled.
+    testElement.$$('.action-button').click();
+
+    await testAppMetricsProxy.whenCalled('recordDidNothingAndChoseNext');
+
+    // Test framework only records first result, but should be called 3 times.
+    assertEquals(
+        0, await testAppBrowserProxy.whenCalled('recordProviderSelected'));
+    assertEquals(3, testAppBrowserProxy.providerSelectedCount);
   });
 });
diff --git a/chrome/test/data/webui/welcome/module_metrics_test.js b/chrome/test/data/webui/welcome/module_metrics_test.js
index 41b4c51..3d049e5 100644
--- a/chrome/test/data/webui/welcome/module_metrics_test.js
+++ b/chrome/test/data/webui/welcome/module_metrics_test.js
@@ -2,84 +2,85 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome_module_metrics', function() {
-  suite('ModuleMetricsTest', function() {
-    /** @type {welcome.ModuleMetricsProxy} */
-    let testMetricsProxy;
+import {TestMetricsProxy} from 'chrome://test/welcome/test_metrics_proxy.js';
+import {ModuleMetricsManager} from 'chrome://welcome/shared/module_metrics_proxy.js';
 
-    /** @type {welcome.ModuleMetricsManager} */
-    let testMetricsManager;
+suite('ModuleMetricsTest', function() {
+  /** @type {ModuleMetricsProxy} */
+  let testMetricsProxy;
 
-    setup(function() {
-      testMetricsProxy = new TestMetricsProxy();
-      testMetricsManager = new welcome.ModuleMetricsManager(testMetricsProxy);
+  /** @type {ModuleMetricsManager} */
+  let testMetricsManager;
 
-      testMetricsManager.recordPageInitialized();
+  setup(function() {
+    testMetricsProxy = new TestMetricsProxy();
+    testMetricsManager = new ModuleMetricsManager(testMetricsProxy);
 
-      return testMetricsProxy.whenCalled('recordPageShown');
-    });
+    testMetricsManager.recordPageInitialized();
 
-    test('do nothing, click skip', function() {
-      testMetricsManager.recordNoThanks();
-      return testMetricsProxy.whenCalled('recordDidNothingAndChoseSkip');
-    });
+    return testMetricsProxy.whenCalled('recordPageShown');
+  });
 
-    test('do nothing, click next', function() {
-      testMetricsManager.recordGetStarted();
-      return testMetricsProxy.whenCalled('recordDidNothingAndChoseNext');
-    });
+  test('do nothing, click skip', function() {
+    testMetricsManager.recordNoThanks();
+    return testMetricsProxy.whenCalled('recordDidNothingAndChoseSkip');
+  });
 
-    test('do nothing, navigate away', function() {
-      testMetricsManager.recordNavigatedAway();
-      return testMetricsProxy.whenCalled('recordDidNothingAndNavigatedAway');
-    });
+  test('do nothing, click next', function() {
+    testMetricsManager.recordGetStarted();
+    return testMetricsProxy.whenCalled('recordDidNothingAndChoseNext');
+  });
 
-    test('choose option, click skip', function() {
-      testMetricsManager.recordClickedOption();
-      testMetricsManager.recordNoThanks();
-      return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseSkip');
-    });
+  test('do nothing, navigate away', function() {
+    testMetricsManager.recordNavigatedAway();
+    return testMetricsProxy.whenCalled('recordDidNothingAndNavigatedAway');
+  });
 
-    test('choose option, click next', function() {
-      testMetricsManager.recordClickedOption();
-      testMetricsManager.recordGetStarted();
-      return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseNext');
-    });
+  test('choose option, click skip', function() {
+    testMetricsManager.recordClickedOption();
+    testMetricsManager.recordNoThanks();
+    return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseSkip');
+  });
 
-    test('choose option, navigate away', function() {
-      testMetricsManager.recordClickedOption();
-      testMetricsManager.recordNavigatedAway();
-      return testMetricsProxy.whenCalled('recordChoseAnOptionAndNavigatedAway');
-    });
+  test('choose option, click next', function() {
+    testMetricsManager.recordClickedOption();
+    testMetricsManager.recordGetStarted();
+    return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseNext');
+  });
 
-    test('click disabled next, click skip', function() {
-      testMetricsManager.recordClickedDisabledButton();
-      testMetricsManager.recordNoThanks();
-      return testMetricsProxy.whenCalled(
-          'recordClickedDisabledNextButtonAndChoseSkip');
-    });
+  test('choose option, navigate away', function() {
+    testMetricsManager.recordClickedOption();
+    testMetricsManager.recordNavigatedAway();
+    return testMetricsProxy.whenCalled('recordChoseAnOptionAndNavigatedAway');
+  });
 
-    test('click disabled next, click next', function() {
-      testMetricsManager.recordClickedDisabledButton();
-      // 'Next' should become enabled only after clicking another option.
-      testMetricsManager.recordClickedOption();
-      testMetricsManager.recordGetStarted();
-      return testMetricsProxy.whenCalled(
-          'recordClickedDisabledNextButtonAndChoseNext');
-    });
+  test('click disabled next, click skip', function() {
+    testMetricsManager.recordClickedDisabledButton();
+    testMetricsManager.recordNoThanks();
+    return testMetricsProxy.whenCalled(
+        'recordClickedDisabledNextButtonAndChoseSkip');
+  });
 
-    test('click disabled next, navigate away', function() {
-      testMetricsManager.recordClickedDisabledButton();
-      testMetricsManager.recordNavigatedAway();
-      return testMetricsProxy.whenCalled(
-          'recordClickedDisabledNextButtonAndNavigatedAway');
-    });
+  test('click disabled next, click next', function() {
+    testMetricsManager.recordClickedDisabledButton();
+    // 'Next' should become enabled only after clicking another option.
+    testMetricsManager.recordClickedOption();
+    testMetricsManager.recordGetStarted();
+    return testMetricsProxy.whenCalled(
+        'recordClickedDisabledNextButtonAndChoseNext');
+  });
 
-    test('choose option, click disabled next, click next', function() {
-      testMetricsManager.recordClickedOption();
-      testMetricsManager.recordClickedDisabledButton();
-      testMetricsManager.recordGetStarted();
-      return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseNext');
-    });
+  test('click disabled next, navigate away', function() {
+    testMetricsManager.recordClickedDisabledButton();
+    testMetricsManager.recordNavigatedAway();
+    return testMetricsProxy.whenCalled(
+        'recordClickedDisabledNextButtonAndNavigatedAway');
+  });
+
+  test('choose option, click disabled next, click next', function() {
+    testMetricsManager.recordClickedOption();
+    testMetricsManager.recordClickedDisabledButton();
+    testMetricsManager.recordGetStarted();
+    return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseNext');
   });
 });
diff --git a/chrome/test/data/webui/welcome/navigation_behavior_test.js b/chrome/test/data/webui/welcome/navigation_behavior_test.js
index bf68e33..a909bed 100644
--- a/chrome/test/data/webui/welcome/navigation_behavior_test.js
+++ b/chrome/test/data/webui/welcome/navigation_behavior_test.js
@@ -2,165 +2,164 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome_navigation_behavior_test', function() {
-  suite('NavigationBehaviorTest', function() {
-    let elements = [];
-    let callOrders = [];
+import {Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
+import {eventToPromise} from 'chrome://test/test_util.m.js';
+import {navigateTo, navigateToNextStep, NavigationBehavior, Routes} from 'chrome://welcome/navigation_behavior.js';
 
-    suiteSetup(function() {
-      Polymer({
-        is: 'test-element',
+suite('NavigationBehaviorTest', function() {
+  let elements = [];
+  let callOrders = [];
 
-        behaviors: [welcome.NavigationBehavior],
+  suiteSetup(function() {
+    Polymer({
+      is: 'test-element',
 
-        ready: function() {
-          this.reset();
-        },
+      behaviors: [NavigationBehavior],
 
-        onRouteEnter: function() {
-          this.enterCalled = true;
-          callOrders.push('enter');
-        },
+      ready: function() {
+        this.reset();
+      },
 
-        onRouteChange: function() {
-          this.changeCalled = true;
-          callOrders.push('change');
-        },
+      onRouteEnter: function() {
+        this.enterCalled = true;
+        callOrders.push('enter');
+      },
 
-        onRouteExit: function() {
-          this.exitCalled = true;
-          callOrders.push('exit');
-        },
+      onRouteChange: function() {
+        this.changeCalled = true;
+        callOrders.push('change');
+      },
 
-        reset: function() {
-          this.enterCalled = false;
-          this.changeCalled = false;
-          this.exitCalled = false;
-        }
-      });
-    });
+      onRouteExit: function() {
+        this.exitCalled = true;
+        callOrders.push('exit');
+      },
 
-    setup(function() {
-      PolymerTest.clearBody();
-      // Creates 3 elements with IDs step-(0~2).
-      for (let i = 0; i < 3; i++) {
-        elements.push(document.createElement('test-element'));
-        elements[i].id = `step-${i}`;
+      reset: function() {
+        this.enterCalled = false;
+        this.changeCalled = false;
+        this.exitCalled = false;
       }
     });
+  });
 
-    teardown(function() {
-      callOrders = [];
-      elements = [];
-    });
-
-    function appendAll() {
-      elements.forEach(elem => document.body.appendChild(elem));
+  setup(function() {
+    PolymerTest.clearBody();
+    // Creates 3 elements with IDs step-(0~2).
+    for (let i = 0; i < 3; i++) {
+      elements.push(document.createElement('test-element'));
+      elements[i].id = `step-${i}`;
     }
+  });
 
-    function resetAll() {
-      elements.forEach(elem => elem.reset());
-      callOrders = [];
-    }
+  teardown(function() {
+    callOrders = [];
+    elements = [];
+  });
 
-    // exit should be called first, enter last, and all change calls in between.
-    function assertCallOrders() {
-      assertEquals(callOrders[0], 'exit');
-      assertEquals(callOrders[callOrders.length - 1], 'enter');
-      callOrders.slice(1, callOrders.length - 1).forEach(called => {
-        assertEquals(called, 'change');
-      });
-    }
+  function appendAll() {
+    elements.forEach(elem => document.body.appendChild(elem));
+  }
 
-    test('correct hooks fire when elements are attached', function() {
-      // Setup the "current route" state before things are appended.
-      welcome.navigateTo(
-          /* doesn't matter which route */ welcome.Routes.NEW_USER, 1);
-      appendAll();
+  function resetAll() {
+    elements.forEach(elem => elem.reset());
+    callOrders = [];
+  }
 
-      assertFalse(elements[0].enterCalled);
-      assertTrue(elements[0].changeCalled);
-      assertFalse(elements[0].exitCalled);
-
-      assertTrue(elements[1].enterCalled);
-      assertTrue(elements[1].changeCalled);
-      assertFalse(elements[1].exitCalled);
-
-      assertFalse(elements[2].enterCalled);
-      assertTrue(elements[2].changeCalled);
-      assertFalse(elements[2].exitCalled);
+  // exit should be called first, enter last, and all change calls in between.
+  function assertCallOrders() {
+    assertEquals(callOrders[0], 'exit');
+    assertEquals(callOrders[callOrders.length - 1], 'enter');
+    callOrders.slice(1, callOrders.length - 1).forEach(called => {
+      assertEquals(called, 'change');
     });
+  }
 
-    test('hooks fire in expected order when elements are attached', function() {
-      // Pretend we're on step-1
-      welcome.navigateTo(
-          /* doesn't matter which route */ welcome.Routes.NEW_USER, 1);
-      appendAll();
-      resetAll();
+  test('correct hooks fire when elements are attached', function() {
+    // Setup the "current route" state before things are appended.
+    navigateTo(/* doesn't matter which route */ Routes.NEW_USER, 1);
+    appendAll();
 
-      // move on from step-1 to step 2.
-      welcome.navigateToNextStep();
+    assertFalse(elements[0].enterCalled);
+    assertTrue(elements[0].changeCalled);
+    assertFalse(elements[0].exitCalled);
 
-      assertFalse(elements[0].enterCalled);
-      assertTrue(elements[0].changeCalled);
-      assertFalse(elements[0].exitCalled);
+    assertTrue(elements[1].enterCalled);
+    assertTrue(elements[1].changeCalled);
+    assertFalse(elements[1].exitCalled);
 
-      assertFalse(elements[1].enterCalled);
-      assertTrue(elements[1].changeCalled);
-      assertTrue(elements[1].exitCalled);
+    assertFalse(elements[2].enterCalled);
+    assertTrue(elements[2].changeCalled);
+    assertFalse(elements[2].exitCalled);
+  });
 
-      assertTrue(elements[2].enterCalled);
-      assertTrue(elements[2].changeCalled);
-      assertFalse(elements[2].exitCalled);
-      assertCallOrders();
-    });
+  test('hooks fire in expected order when elements are attached', function() {
+    // Pretend we're on step-1
+    navigateTo(/* doesn't matter which route */ Routes.NEW_USER, 1);
+    appendAll();
+    resetAll();
 
-    test('popstate works as expected', async function() {
-      // Pretend we're on step-1
-      welcome.navigateTo(
-          /* doesn't matter which route */ welcome.Routes.NEW_USER, 1);
-      appendAll();
-      // move on from step-1 to step 2.
-      welcome.navigateToNextStep();
-      resetAll();
+    // move on from step-1 to step 2.
+    navigateToNextStep();
 
-      // back from step-2 to step 1.
-      window.history.back();
+    assertFalse(elements[0].enterCalled);
+    assertTrue(elements[0].changeCalled);
+    assertFalse(elements[0].exitCalled);
 
-      await test_util.eventToPromise('popstate', window);
+    assertFalse(elements[1].enterCalled);
+    assertTrue(elements[1].changeCalled);
+    assertTrue(elements[1].exitCalled);
 
-      assertFalse(elements[0].enterCalled);
-      assertTrue(elements[0].changeCalled);
-      assertFalse(elements[0].exitCalled);
+    assertTrue(elements[2].enterCalled);
+    assertTrue(elements[2].changeCalled);
+    assertFalse(elements[2].exitCalled);
+    assertCallOrders();
+  });
 
-      assertTrue(elements[1].enterCalled);
-      assertTrue(elements[1].changeCalled);
-      assertFalse(elements[1].exitCalled);
+  test('popstate works as expected', async function() {
+    // Pretend we're on step-1
+    navigateTo(/* doesn't matter which route */ Routes.NEW_USER, 1);
+    appendAll();
+    // move on from step-1 to step 2.
+    navigateToNextStep();
+    resetAll();
 
-      assertFalse(elements[2].enterCalled);
-      assertTrue(elements[2].changeCalled);
-      assertTrue(elements[2].exitCalled);
+    // back from step-2 to step 1.
+    window.history.back();
 
-      assertCallOrders();
+    await eventToPromise('popstate', window);
 
-      resetAll();
-      // move on from step-1 to step 2 again.
-      window.history.forward();
+    assertFalse(elements[0].enterCalled);
+    assertTrue(elements[0].changeCalled);
+    assertFalse(elements[0].exitCalled);
 
-      await test_util.eventToPromise('popstate', window);
+    assertTrue(elements[1].enterCalled);
+    assertTrue(elements[1].changeCalled);
+    assertFalse(elements[1].exitCalled);
 
-      assertFalse(elements[0].enterCalled);
-      assertTrue(elements[0].changeCalled);
-      assertFalse(elements[0].exitCalled);
+    assertFalse(elements[2].enterCalled);
+    assertTrue(elements[2].changeCalled);
+    assertTrue(elements[2].exitCalled);
 
-      assertFalse(elements[1].enterCalled);
-      assertTrue(elements[1].changeCalled);
-      assertTrue(elements[1].exitCalled);
+    assertCallOrders();
 
-      assertTrue(elements[2].enterCalled);
-      assertTrue(elements[2].changeCalled);
-      assertFalse(elements[2].exitCalled);
-      assertCallOrders();
-    });
+    resetAll();
+    // move on from step-1 to step 2 again.
+    window.history.forward();
+
+    await eventToPromise('popstate', window);
+
+    assertFalse(elements[0].enterCalled);
+    assertTrue(elements[0].changeCalled);
+    assertFalse(elements[0].exitCalled);
+
+    assertFalse(elements[1].enterCalled);
+    assertTrue(elements[1].changeCalled);
+    assertTrue(elements[1].exitCalled);
+
+    assertTrue(elements[2].enterCalled);
+    assertTrue(elements[2].changeCalled);
+    assertFalse(elements[2].exitCalled);
+    assertCallOrders();
   });
 });
diff --git a/chrome/test/data/webui/welcome/nux_ntp_background_test.js b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
index ec33882..beda88e 100644
--- a/chrome/test/data/webui/welcome/nux_ntp_background_test.js
+++ b/chrome/test/data/webui/welcome/nux_ntp_background_test.js
@@ -2,176 +2,185 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('ntp_background_test', function() {
-  suite('NuxNtpBackgroundTest', function() {
-    /** @type {!Array<!welcome.NtpBackgroundData} */
-    let backgrounds = [
-      {
-        id: 0,
-        title: 'Art',
-        /* Image URLs are set to actual static images to prevent requesting
-         * an external image. */
-        imageUrl: '../images/ntp_thumbnails/art.jpg',
-        thumbnailClass: 'art',
-      },
-      {
-        id: 1,
-        title: 'Cityscape',
-        imageUrl: '../images/ntp_thumbnails/cityscape.jpg',
-        thumbnailClass: 'cityscape',
-      },
-    ];
+import 'chrome://welcome/ntp_background/nux_ntp_background.js';
 
-    /** @type {NuxNtpBackgroundElement} */
-    let testElement;
+import {TestMetricsProxy} from 'chrome://test/welcome/test_metrics_proxy.js';
+import {TestNtpBackgroundProxy} from 'chrome://test/welcome/test_ntp_background_proxy.js';
+import {NtpBackgroundMetricsProxyImpl} from 'chrome://welcome/ntp_background/ntp_background_metrics_proxy.js';
+import {NtpBackgroundProxyImpl} from 'chrome://welcome/ntp_background/ntp_background_proxy.js';
 
-    /** @type {welcome.ModuleMetricsProxy} */
-    let testMetricsProxy;
+suite('NuxNtpBackgroundTest', function() {
+  /** @type {!Array<!NtpBackgroundData} */
+  let backgrounds = [
+    {
+      id: 0,
+      title: 'Art',
+      /* Image URLs are set to actual static images to prevent requesting
+       * an external image. */
+      imageUrl: 'chrome://welcome/images/ntp_thumbnails/art.jpg',
+      thumbnailClass: 'art',
+    },
+    {
+      id: 1,
+      title: 'Cityscape',
+      imageUrl: 'chrome://welcome/images/ntp_thumbnails/cityscape.jpg',
+      thumbnailClass: 'cityscape',
+    },
+  ];
 
-    /** @type {welcome.NtpBackgroundProxy} */
-    let testNtpBackgroundProxy;
+  /** @type {NuxNtpBackgroundElement} */
+  let testElement;
 
-    setup(function() {
-      loadTimeData.overrideValues({
-        ntpBackgroundDefault: 'Default',
-      });
+  /** @type {ModuleMetricsProxy} */
+  let testMetricsProxy;
 
-      testMetricsProxy = new TestMetricsProxy();
-      welcome.NtpBackgroundMetricsProxyImpl.instance_ = testMetricsProxy;
-      testNtpBackgroundProxy = new TestNtpBackgroundProxy();
-      welcome.NtpBackgroundProxyImpl.instance_ = testNtpBackgroundProxy;
-      testNtpBackgroundProxy.setBackgroundsList(backgrounds);
+  /** @type {NtpBackgroundProxy} */
+  let testNtpBackgroundProxy;
 
-      PolymerTest.clearBody();
-      testElement = document.createElement('nux-ntp-background');
-      document.body.appendChild(testElement);
-
-      testElement.onRouteEnter();
-      return Promise.all([
-        testMetricsProxy.whenCalled('recordPageShown'),
-        testNtpBackgroundProxy.whenCalled('getBackgrounds'),
-      ]);
+  setup(function() {
+    loadTimeData.overrideValues({
+      ntpBackgroundDefault: 'Default',
     });
 
-    teardown(function() {
-      testElement.remove();
-    });
+    testMetricsProxy = new TestMetricsProxy();
+    NtpBackgroundMetricsProxyImpl.instance_ = testMetricsProxy;
+    testNtpBackgroundProxy = new TestNtpBackgroundProxy();
+    NtpBackgroundProxyImpl.instance_ = testNtpBackgroundProxy;
+    testNtpBackgroundProxy.setBackgroundsList(backgrounds);
 
-    test('test displaying default and custom background', function() {
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-      assertEquals(3, options.length);
+    PolymerTest.clearBody();
+    // Add <base> so that images in nux-google-apps will be loaded from the
+    // correct data source.
+    const base = document.createElement('base');
+    base.href = 'chrome://welcome/google_apps/';
+    document.head.appendChild(base);
+    testElement = document.createElement('nux-ntp-background');
+    document.body.appendChild(testElement);
+    // Remove <base> so that routing happens from a base of chrome://test, to
+    // prevent a security error.
+    document.head.removeChild(base);
 
-      // the first option should be the 'Default' option
+    testElement.onRouteEnter();
+    return Promise.all([
+      testMetricsProxy.whenCalled('recordPageShown'),
+      testNtpBackgroundProxy.whenCalled('getBackgrounds'),
+    ]);
+  });
+
+  teardown(function() {
+    testElement.remove();
+  });
+
+  test('test displaying default and custom background', function() {
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    assertEquals(3, options.length);
+
+    // the first option should be the 'Default' option
+    assertEquals(options[0].querySelector('.option-name').innerText, 'Default');
+
+    for (let i = 0; i < backgrounds.length; i++) {
       assertEquals(
-          options[0].querySelector('.option-name').innerText, 'Default');
+          options[i + 1].querySelector('.option-name').innerText,
+          backgrounds[i].title);
+    }
+  });
 
-      for (let i = 0; i < backgrounds.length; i++) {
-        assertEquals(
-            options[i + 1].querySelector('.option-name').innerText,
-            backgrounds[i].title);
-      }
-    });
+  test('test previewing a background and going back to default', function() {
+    const options = testElement.shadowRoot.querySelectorAll('.option');
 
-    test('test previewing a background and going back to default', function() {
-      const options = testElement.shadowRoot.querySelectorAll('.option');
+    options[1].click();
+    return testNtpBackgroundProxy.whenCalled('preloadImage').then(() => {
+      assertEquals(
+          testElement.$.backgroundPreview.style.backgroundImage,
+          `url("${backgrounds[0].imageUrl}")`);
+      assertTrue(testElement.$.backgroundPreview.classList.contains('active'));
 
-      options[1].click();
-      return testNtpBackgroundProxy.whenCalled('preloadImage').then(() => {
-        assertEquals(
-            testElement.$.backgroundPreview.style.backgroundImage,
-            `url("${backgrounds[0].imageUrl}")`);
-        assertTrue(
-            testElement.$.backgroundPreview.classList.contains('active'));
-
-        // go back to the default option, and pretend all CSS transitions
-        // have completed
-        options[0].click();
-        testElement.$.backgroundPreview.dispatchEvent(
-            new Event('transitionend'));
-        assertEquals(testElement.$.backgroundPreview.style.backgroundImage, '');
-        assertFalse(
-            testElement.$.backgroundPreview.classList.contains('active'));
-      });
-    });
-
-    test('test activating a background', function() {
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-
-      options[1].click();
-      assertFalse(options[0].hasAttribute('active'));
-      assertTrue(options[1].hasAttribute('active'));
-      assertFalse(options[2].hasAttribute('active'));
-    });
-
-    test('test setting the background when hitting next', function() {
-      // select the first non-default option and hit 'Next'
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-      options[1].click();
-      testElement.$$('.action-button').click();
-      return Promise
-          .all([
-            testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseNext'),
-            testNtpBackgroundProxy.whenCalled('setBackground'),
-          ])
-          .then((responses) => {
-            assertEquals(backgrounds[0].id, responses[1]);
-          });
-    });
-
-    test('test metrics for selecting an option and skipping', function() {
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-      options[1].click();
-      testElement.$.skipButton.click();
-      return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseSkip');
-    });
-
-    test(
-        'test metrics for when there is an error previewing the background',
-        function() {
-          testNtpBackgroundProxy.setPreloadImageSuccess(false);
-          const options = testElement.shadowRoot.querySelectorAll('.option');
-          options[1].click();
-          return testNtpBackgroundProxy.whenCalled(
-              'recordBackgroundImageFailedToLoad');
-        });
-
-    test(
-        `test metrics aren't sent when previewing the background is a success`,
-        function() {
-          testNtpBackgroundProxy.setPreloadImageSuccess(true);
-          const options = testElement.shadowRoot.querySelectorAll('.option');
-          options[1].click();
-          return testNtpBackgroundProxy.whenCalled('preloadImage').then(() => {
-            assertEquals(
-                0,
-                testNtpBackgroundProxy.getCallCount(
-                    'recordBackgroundImageFailedToLoad'));
-          });
-        });
-
-    test('test metrics for load times of background images', function() {
-      testNtpBackgroundProxy.setPreloadImageSuccess(true);
-      const options = testElement.shadowRoot.querySelectorAll('.option');
-      options[1].click();
-      return testNtpBackgroundProxy.whenCalled('recordBackgroundImageLoadTime');
-    });
-
-    test('test metrics for doing nothing and navigating away', function() {
-      testElement.onRouteUnload();
-      return testMetricsProxy.whenCalled('recordDidNothingAndNavigatedAway');
-    });
-
-    test('test metrics for skipping', function() {
-      testElement.$.skipButton.click();
-      return testMetricsProxy.whenCalled('recordDidNothingAndChoseSkip');
-    });
-
-    test('test clearing the background when default is selected', function() {
-      // select the default option and hit 'Next'
-      const options = testElement.shadowRoot.querySelectorAll('.option');
+      // go back to the default option, and pretend all CSS transitions
+      // have completed
       options[0].click();
-      testElement.$$('.action-button').click();
-      return testNtpBackgroundProxy.whenCalled('clearBackground');
+      testElement.$.backgroundPreview.dispatchEvent(new Event('transitionend'));
+      assertEquals(testElement.$.backgroundPreview.style.backgroundImage, '');
+      assertFalse(testElement.$.backgroundPreview.classList.contains('active'));
     });
   });
+
+  test('test activating a background', function() {
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+
+    options[1].click();
+    assertFalse(options[0].hasAttribute('active'));
+    assertTrue(options[1].hasAttribute('active'));
+    assertFalse(options[2].hasAttribute('active'));
+  });
+
+  test('test setting the background when hitting next', function() {
+    // select the first non-default option and hit 'Next'
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    options[1].click();
+    testElement.$$('.action-button').click();
+    return Promise
+        .all([
+          testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseNext'),
+          testNtpBackgroundProxy.whenCalled('setBackground'),
+        ])
+        .then((responses) => {
+          assertEquals(backgrounds[0].id, responses[1]);
+        });
+  });
+
+  test('test metrics for selecting an option and skipping', function() {
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    options[1].click();
+    testElement.$.skipButton.click();
+    return testMetricsProxy.whenCalled('recordChoseAnOptionAndChoseSkip');
+  });
+
+  test(
+      'test metrics for when there is an error previewing the background',
+      function() {
+        testNtpBackgroundProxy.setPreloadImageSuccess(false);
+        const options = testElement.shadowRoot.querySelectorAll('.option');
+        options[1].click();
+        return testNtpBackgroundProxy.whenCalled(
+            'recordBackgroundImageFailedToLoad');
+      });
+
+  test(
+      `test metrics aren't sent when previewing the background is a success`,
+      function() {
+        testNtpBackgroundProxy.setPreloadImageSuccess(true);
+        const options = testElement.shadowRoot.querySelectorAll('.option');
+        options[1].click();
+        return testNtpBackgroundProxy.whenCalled('preloadImage').then(() => {
+          assertEquals(
+              0,
+              testNtpBackgroundProxy.getCallCount(
+                  'recordBackgroundImageFailedToLoad'));
+        });
+      });
+
+  test('test metrics for load times of background images', function() {
+    testNtpBackgroundProxy.setPreloadImageSuccess(true);
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    options[1].click();
+    return testNtpBackgroundProxy.whenCalled('recordBackgroundImageLoadTime');
+  });
+
+  test('test metrics for doing nothing and navigating away', function() {
+    testElement.onRouteUnload();
+    return testMetricsProxy.whenCalled('recordDidNothingAndNavigatedAway');
+  });
+
+  test('test metrics for skipping', function() {
+    testElement.$.skipButton.click();
+    return testMetricsProxy.whenCalled('recordDidNothingAndChoseSkip');
+  });
+
+  test('test clearing the background when default is selected', function() {
+    // select the default option and hit 'Next'
+    const options = testElement.shadowRoot.querySelectorAll('.option');
+    options[0].click();
+    testElement.$$('.action-button').click();
+    return testNtpBackgroundProxy.whenCalled('clearBackground');
+  });
 });
diff --git a/chrome/test/data/webui/welcome/nux_set_as_default_test.js b/chrome/test/data/webui/welcome/nux_set_as_default_test.js
index 58b720db..59de895 100644
--- a/chrome/test/data/webui/welcome/nux_set_as_default_test.js
+++ b/chrome/test/data/webui/welcome/nux_set_as_default_test.js
@@ -2,77 +2,87 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('set_as_default_test', function() {
-  suite('SetAsDefaultTest', function() {
-    /** @type {NuxSetAsDefaultElement} */
-    let testElement;
+import 'chrome://welcome/set_as_default/nux_set_as_default.js';
 
-    /** @type {welcome.NuxSetAsDefaultProxy} */
-    let testSetAsDefaultProxy;
+import {webUIListenerCallback} from 'chrome://resources/js/cr.m.js';
+import {eventToPromise} from 'chrome://test/test_util.m.js';
+import {TestNuxSetAsDefaultProxy} from 'chrome://test/welcome/test_nux_set_as_default_proxy.js';
+import {NuxSetAsDefaultProxyImpl} from 'chrome://welcome/set_as_default/nux_set_as_default_proxy.js';
 
-    /** @type {!Promise} */
-    let navigatedPromise;
+suite('SetAsDefaultTest', function() {
+  /** @type {NuxSetAsDefaultElement} */
+  let testElement;
 
-    setup(function() {
-      testSetAsDefaultProxy = new TestNuxSetAsDefaultProxy();
-      welcome.NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
+  /** @type {NuxSetAsDefaultProxy} */
+  let testSetAsDefaultProxy;
 
-      navigatedPromise = new Promise(resolve => {
-        // Spy on navigational function to make sure it's called.
-        welcome.navigateToNextStep = () => resolve();
+  /** @type {!Promise} */
+  let navigatedPromise;
+
+  setup(function() {
+    testSetAsDefaultProxy = new TestNuxSetAsDefaultProxy();
+    NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
+
+    PolymerTest.clearBody();
+    const base = document.createElement('base');
+    base.href = 'chrome://welcome/set_as_default/';
+    document.head.appendChild(base);
+    testElement = document.createElement('nux-set-as-default');
+    document.body.appendChild(testElement);
+    let navigateToNextStep;
+    navigatedPromise = new Promise(resolve => {
+      // Spy on navigational function to make sure it's called.
+      navigateToNextStep = () => resolve();
+    });
+    testElement.navigateToNextStep_ = navigateToNextStep;
+    document.head.removeChild(base);
+  });
+
+  teardown(function() {
+    testElement.remove();
+  });
+
+  test('skip', function() {
+    testElement.$['decline-button'].click();
+    return testSetAsDefaultProxy.whenCalled('recordSkip');
+  });
+
+  test(
+      'click set-default button and finishes setting default',
+      async function() {
+        testElement.$$('.action-button').click();
+
+        await Promise.all([
+          testSetAsDefaultProxy.whenCalled('recordBeginSetDefault'),
+          testSetAsDefaultProxy.whenCalled('setAsDefault'),
+        ]);
+
+        const notifyPromise =
+            eventToPromise('default-browser-change', testElement);
+
+        webUIListenerCallback(
+            'browser-default-state-changed', {isDefault: true});
+
+        return Promise.all([
+          notifyPromise,
+          testSetAsDefaultProxy.whenCalled('recordSuccessfullySetDefault'),
+          navigatedPromise
+        ]);
       });
 
-      PolymerTest.clearBody();
-      testElement = document.createElement('nux-set-as-default');
-      document.body.appendChild(testElement);
-    });
+  test('click set-default button but gives up and skip', async function() {
+    testElement.$$('.action-button').click();
 
-    teardown(function() {
-      testElement.remove();
-    });
+    await Promise.all([
+      testSetAsDefaultProxy.whenCalled('recordBeginSetDefault'),
+      testSetAsDefaultProxy.whenCalled('setAsDefault'),
+    ]);
 
-    test('skip', function() {
-      testElement.$['decline-button'].click();
-      return testSetAsDefaultProxy.whenCalled('recordSkip');
-    });
+    testElement.$['decline-button'].click();
 
-    test(
-        'click set-default button and finishes setting default',
-        async function() {
-          testElement.$$('.action-button').click();
-
-          await Promise.all([
-            testSetAsDefaultProxy.whenCalled('recordBeginSetDefault'),
-            testSetAsDefaultProxy.whenCalled('setAsDefault'),
-          ]);
-
-          const notifyPromise =
-              test_util.eventToPromise('default-browser-change', testElement);
-
-          cr.webUIListenerCallback(
-              'browser-default-state-changed', {isDefault: true});
-
-          return Promise.all([
-            notifyPromise,
-            testSetAsDefaultProxy.whenCalled('recordSuccessfullySetDefault'),
-            navigatedPromise
-          ]);
-        });
-
-    test('click set-default button but gives up and skip', async function() {
-      testElement.$$('.action-button').click();
-
-      await Promise.all([
-        testSetAsDefaultProxy.whenCalled('recordBeginSetDefault'),
-        testSetAsDefaultProxy.whenCalled('setAsDefault'),
-      ]);
-
-      testElement.$['decline-button'].click();
-
-      return Promise.all([
-        testSetAsDefaultProxy.whenCalled('recordSkip'),
-        navigatedPromise,
-      ]);
-    });
+    return Promise.all([
+      testSetAsDefaultProxy.whenCalled('recordSkip'),
+      navigatedPromise,
+    ]);
   });
 });
diff --git a/chrome/test/data/webui/welcome/signin_view_test.js b/chrome/test/data/webui/welcome/signin_view_test.js
index c5ce927a..a3e06639 100644
--- a/chrome/test/data/webui/welcome/signin_view_test.js
+++ b/chrome/test/data/webui/welcome/signin_view_test.js
@@ -2,42 +2,58 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('signin_view_test', function() {
-  suite('SigninViewTest', function() {
+import 'chrome://welcome/signin_view.js';
 
-    /** @type {SigninViewElement} */
-    let testElement;
+import {TestSigninViewProxy} from 'chrome://test/welcome/test_signin_view_proxy.js';
+import {TestWelcomeBrowserProxy} from 'chrome://test/welcome/test_welcome_browser_proxy.js';
+import {SigninViewProxyImpl} from 'chrome://welcome/signin_view_proxy.js';
+import {WelcomeBrowserProxyImpl} from 'chrome://welcome/welcome_browser_proxy.js';
 
-    /** @type {welcome.WelcomeBrowserProxy} */
-    let testWelcomeBrowserProxy;
+suite('SigninViewTest', function() {
+  /** @type {SigninViewElement} */
+  let testElement;
 
-    setup(function() {
-      testWelcomeBrowserProxy = new TestWelcomeBrowserProxy();
-      welcome.WelcomeBrowserProxyImpl.instance_ = testWelcomeBrowserProxy;
+  /** @type {WelcomeBrowserProxy} */
+  let testWelcomeBrowserProxy;
 
-      PolymerTest.clearBody();
-      testElement = document.createElement('signin-view');
-      document.body.appendChild(testElement);
-    });
+  setup(function() {
+    testWelcomeBrowserProxy = new TestWelcomeBrowserProxy();
+    WelcomeBrowserProxyImpl.instance_ = testWelcomeBrowserProxy;
 
-    teardown(function() {
-      testElement.remove();
-    });
+    // Not used in test, but setting to test proxy anyway, in order to prevent
+    // calls to backend.
+    SigninViewProxyImpl.instance_ = new TestSigninViewProxy();
 
-    test('sign-in button', function() {
-      const signinButton = testElement.$$('cr-button');
-      assertTrue(!!signinButton);
+    PolymerTest.clearBody();
+    // Add <base> so that images in nux-google-apps will be loaded from the
+    // correct data source.
+    const base = document.createElement('base');
+    base.href = 'chrome://welcome/google_apps/';
+    document.head.appendChild(base);
+    testElement = document.createElement('signin-view');
+    document.body.appendChild(testElement);
+    // Remove <base> so that routing happens from a base of chrome://test, to
+    // prevent a security error.
+    document.head.removeChild(base);
+  });
 
-      signinButton.click();
-      return testWelcomeBrowserProxy.whenCalled('handleActivateSignIn')
-          .then(redirectUrl => assertEquals(null, redirectUrl));
-    });
+  teardown(function() {
+    testElement.remove();
+  });
 
-    test('no-thanks button', function() {
-      const noThanksButton = testElement.$$('button');
-      assertTrue(!!noThanksButton);
-      noThanksButton.click();
-      return testWelcomeBrowserProxy.whenCalled('handleUserDecline');
-    });
+  test('sign-in button', function() {
+    const signinButton = testElement.$$('cr-button');
+    assertTrue(!!signinButton);
+
+    signinButton.click();
+    return testWelcomeBrowserProxy.whenCalled('handleActivateSignIn')
+        .then(redirectUrl => assertEquals(null, redirectUrl));
+  });
+
+  test('no-thanks button', function() {
+    const noThanksButton = testElement.$$('button');
+    assertTrue(!!noThanksButton);
+    noThanksButton.click();
+    return testWelcomeBrowserProxy.whenCalled('handleUserDecline');
   });
 });
diff --git a/chrome/test/data/webui/welcome/test_bookmark_proxy.js b/chrome/test/data/webui/welcome/test_bookmark_proxy.js
index 911722f..f939a7b 100644
--- a/chrome/test/data/webui/welcome/test_bookmark_proxy.js
+++ b/chrome/test/data/webui/welcome/test_bookmark_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {welcome.BookmarkProxy} */
-class TestBookmarkProxy extends TestBrowserProxy {
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {BookmarkProxy} */
+export class TestBookmarkProxy extends TestBrowserProxy {
   constructor() {
     super([
       'addBookmark',
diff --git a/chrome/test/data/webui/welcome/test_google_app_proxy.js b/chrome/test/data/webui/welcome/test_google_app_proxy.js
index c352083..f104523 100644
--- a/chrome/test/data/webui/welcome/test_google_app_proxy.js
+++ b/chrome/test/data/webui/welcome/test_google_app_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {welcome.GoogleAppProxy} */
-class TestGoogleAppProxy extends TestBrowserProxy {
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {GoogleAppProxy} */
+export class TestGoogleAppProxy extends TestBrowserProxy {
   constructor() {
     super([
       'cacheBookmarkIcon',
@@ -13,7 +15,7 @@
 
     this.providerSelectedCount = 0;
 
-    /** @private {!Array<!welcome.BookmarkListItem>} */
+    /** @private {!Array<!BookmarkListItem>} */
     this.appList_ = [];
   }
 
@@ -34,7 +36,7 @@
     this.providerSelectedCount++;
   }
 
-  /** @param {!Array<!welcome.BookmarkListItem>} appList */
+  /** @param {!Array<!BookmarkListItem>} appList */
   setAppList(appList) {
     this.appList_ = appList;
   }
diff --git a/chrome/test/data/webui/welcome/test_landing_view_proxy.js b/chrome/test/data/webui/welcome/test_landing_view_proxy.js
new file mode 100644
index 0000000..f1394a3c
--- /dev/null
+++ b/chrome/test/data/webui/welcome/test_landing_view_proxy.js
@@ -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.
+
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {LandingViewProxy} */
+export class TestLandingViewProxy extends TestBrowserProxy {
+  constructor() {
+    super([
+      'recordPageShown',
+      'recordNavigatedAway',
+      'recordNewUser',
+      'recordExistingUser',
+    ]);
+  }
+
+  /** @override */
+  recordPageShown() {
+    this.methodCalled('recordPageShown');
+  }
+
+  /** @override */
+  recordNavigatedAway() {
+    this.methodCalled('recordNavigatedAway');
+  }
+
+  /** @override */
+  recordNewUser() {
+    this.methodCalled('recordNewUser');
+  }
+
+  /** @override */
+  recordExistingUser() {
+    this.methodCalled('recordExistingUser');
+  }
+}
diff --git a/chrome/test/data/webui/welcome/test_metrics_proxy.js b/chrome/test/data/webui/welcome/test_metrics_proxy.js
index 87ec0156..74b7d71b 100644
--- a/chrome/test/data/webui/welcome/test_metrics_proxy.js
+++ b/chrome/test/data/webui/welcome/test_metrics_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {welcome.ModuleMetricsProxy} */
-class TestMetricsProxy extends TestBrowserProxy {
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {ModuleMetricsProxy} */
+export class TestMetricsProxy extends TestBrowserProxy {
   constructor() {
     super([
       'recordChoseAnOptionAndChoseNext',
diff --git a/chrome/test/data/webui/welcome/test_ntp_background_proxy.js b/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
index 223b6f4..b25c8222 100644
--- a/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
+++ b/chrome/test/data/webui/welcome/test_ntp_background_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
 /** @implements {NtpBackgroundProxy} */
-class TestNtpBackgroundProxy extends TestBrowserProxy {
+export class TestNtpBackgroundProxy extends TestBrowserProxy {
   constructor() {
     super([
       'clearBackground',
@@ -14,7 +16,7 @@
       'setBackground',
     ]);
 
-    /** @private {!Array<!welcome.NtpBackgroundData} */
+    /** @private {!Array<!NtpBackgroundData} */
     this.backgroundsList_ = [];
 
     /** @private {boolean} */
@@ -58,7 +60,7 @@
     this.preloadImageSuccess_ = success;
   }
 
-  /** @param {!Array<!welcome.NtpBackgroundData>} backgroundsList */
+  /** @param {!Array<!NtpBackgroundData>} backgroundsList */
   setBackgroundsList(backgroundsList) {
     this.backgroundsList_ = backgroundsList;
   }
diff --git a/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js b/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js
index c6ab9262..f68abe7d 100644
--- a/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js
+++ b/chrome/test/data/webui/welcome/test_nux_set_as_default_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {welcome.NuxSetAsDefaultProxy} */
-class TestNuxSetAsDefaultProxy extends TestBrowserProxy {
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {NuxSetAsDefaultProxy} */
+export class TestNuxSetAsDefaultProxy extends TestBrowserProxy {
   constructor() {
     super([
       'requestDefaultBrowserState',
@@ -30,7 +32,7 @@
     this.methodCalled('setAsDefault');
   }
 
-  /** @param {!welcome.DefaultBrowserInfo} status */
+  /** @param {!DefaultBrowserInfo} status */
   setDefaultStatus(status) {
     this.defaultStatus_ = status;
   }
diff --git a/chrome/test/data/webui/welcome/test_signin_view_proxy.js b/chrome/test/data/webui/welcome/test_signin_view_proxy.js
new file mode 100644
index 0000000..e099c72
--- /dev/null
+++ b/chrome/test/data/webui/welcome/test_signin_view_proxy.js
@@ -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.
+
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {SigninViewProxy} */
+export class TestSigninViewProxy extends TestBrowserProxy {
+  constructor() {
+    super([
+      'recordPageShown',
+      'recordNavigatedAway',
+      'recordNavigatedAwayThroughBrowserHistory',
+      'recordSkip',
+      'recordSignIn',
+    ]);
+  }
+
+  /** @override */
+  recordPageShown() {
+    this.methodCalled('recordPageShown');
+  }
+
+  /** @override */
+  recordNavigatedAway() {
+    this.methodCalled('recordNavigatedAway');
+  }
+
+  /** @override */
+  recordNavigatedAwayThroughBrowserHistory() {
+    this.methodCalled('recordNavigatedAwayThroughBrowserHistory');
+  }
+
+  /** @override */
+  recordSkip() {
+    this.methodCalled('recordSkip');
+  }
+
+  /** @override */
+  recordSignIn() {
+    this.methodCalled('recordSignIn');
+  }
+}
diff --git a/chrome/test/data/webui/welcome/test_welcome_browser_proxy.js b/chrome/test/data/webui/welcome/test_welcome_browser_proxy.js
index e151927..0fff458 100644
--- a/chrome/test/data/webui/welcome/test_welcome_browser_proxy.js
+++ b/chrome/test/data/webui/welcome/test_welcome_browser_proxy.js
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/** @implements {welcome.WelcomeBrowserProxy} */
-class TestWelcomeBrowserProxy extends TestBrowserProxy {
+import {TestBrowserProxy} from 'chrome://test/test_browser_proxy.m.js';
+
+/** @implements {WelcomeBrowserProxy} */
+export class TestWelcomeBrowserProxy extends TestBrowserProxy {
   constructor() {
     super([
       'handleActivateSignIn',
diff --git a/chrome/test/data/webui/welcome/welcome_app_test.js b/chrome/test/data/webui/welcome/welcome_app_test.js
index 060cc2aa..00f51d8 100644
--- a/chrome/test/data/webui/welcome/welcome_app_test.js
+++ b/chrome/test/data/webui/welcome/welcome_app_test.js
@@ -2,199 +2,225 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-cr.define('welcome_app_test', function() {
-  suite('WelcomeAppTest', function() {
+import 'chrome://welcome/welcome_app.js';
 
-    /** @type {WelcomeAppElement} */
-    let testElement;
+import {waitBeforeNextRender} from 'chrome://test/test_util.m.js';
+import {TestBookmarkProxy} from 'chrome://test/welcome/test_bookmark_proxy.js';
+import {TestLandingViewProxy} from 'chrome://test/welcome/test_landing_view_proxy.js';
+import {TestMetricsProxy} from 'chrome://test/welcome/test_metrics_proxy.js';
+import {TestNuxSetAsDefaultProxy} from 'chrome://test/welcome/test_nux_set_as_default_proxy.js';
+import {TestWelcomeBrowserProxy} from 'chrome://test/welcome/test_welcome_browser_proxy.js';
+import {LandingViewProxyImpl} from 'chrome://welcome/landing_view_proxy.js';
+import {navigateTo, Routes} from 'chrome://welcome/navigation_behavior.js';
+import {NuxSetAsDefaultProxyImpl} from 'chrome://welcome/set_as_default/nux_set_as_default_proxy.js';
+import {BookmarkProxyImpl} from 'chrome://welcome/shared/bookmark_proxy.js';
+import {ModuleMetricsProxyImpl} from 'chrome://welcome/shared/module_metrics_proxy.js';
+import {WelcomeBrowserProxyImpl} from 'chrome://welcome/welcome_browser_proxy.js';
 
-    /** @type {welcome.WelcomeBrowserProxy} */
-    let testWelcomeBrowserProxy;
+suite('WelcomeWelcomeAppTest', function() {
+  /** @type {WelcomeAppElement} */
+  let testElement;
 
-    /** @type {welcome.NuxSetAsDefaultProxy} */
-    let testSetAsDefaultProxy;
+  /** @type {WelcomeBrowserProxy} */
+  let testWelcomeBrowserProxy;
 
-    function resetTestElement() {
-      PolymerTest.clearBody();
-      welcome.navigateTo(welcome.Routes.LANDING, 'landing');
-      testElement = document.createElement('welcome-app');
-      document.body.appendChild(testElement);
+  /** @type {NuxSetAsDefaultProxy} */
+  let testSetAsDefaultProxy;
+
+  let base;
+
+  function resetTestElement() {
+    PolymerTest.clearBody();
+    navigateToForTest(Routes.LANDING, 'landing');
+    testElement = document.createElement('welcome-app');
+    document.body.appendChild(testElement);
+  }
+
+  function navigateToForTest(route, name) {
+    if (base) {
+      document.head.removeChild(base);
+    }
+    navigateTo(route, name);
+    base = document.createElement('base');
+    base.href = 'chrome://welcome/';
+    document.head.appendChild(base);
+  }
+
+  function simulateCanSetDefault() {
+    testSetAsDefaultProxy.setDefaultStatus({
+      isDefault: false,
+      canBeDefault: true,
+      isDisabledByPolicy: false,
+      isUnknownError: false,
+    });
+
+    resetTestElement();
+  }
+
+  function simulateCannotSetDefault() {
+    testSetAsDefaultProxy.setDefaultStatus({
+      isDefault: true,
+      canBeDefault: true,
+      isDisabledByPolicy: false,
+      isUnknownError: false,
+    });
+
+    resetTestElement();
+  }
+
+  setup(function() {
+    testWelcomeBrowserProxy = new TestWelcomeBrowserProxy();
+    WelcomeBrowserProxyImpl.instance_ = testWelcomeBrowserProxy;
+
+    testSetAsDefaultProxy = new TestNuxSetAsDefaultProxy();
+    NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
+
+    // Not used in test, but setting to test proxy anyway, in order to prevent
+    // calls to backend.
+    BookmarkProxyImpl.instance_ = new TestBookmarkProxy();
+    LandingViewProxyImpl.instance_ = new TestLandingViewProxy();
+    ModuleMetricsProxyImpl.instance_ = new TestMetricsProxy();
+
+    return resetTestElement();
+  });
+
+  teardown(function() {
+    testElement.remove();
+  });
+
+  test('shows landing page by default', function() {
+    assertEquals(
+        testElement.shadowRoot.querySelectorAll('[slot=view]').length, 1);
+    assertTrue(!!testElement.$$('landing-view'));
+    assertTrue(testElement.$$('landing-view').classList.contains('active'));
+  });
+
+  test('new user route (can set default)', function() {
+    simulateCanSetDefault();
+    navigateToForTest(Routes.NEW_USER, 1);
+    return waitBeforeNextRender(testElement).then(() => {
+      const views = testElement.shadowRoot.querySelectorAll('[slot=view]');
+      assertEquals(views.length, 5);
+      ['LANDING-VIEW',
+       'NUX-GOOGLE-APPS',
+       'NUX-NTP-BACKGROUND',
+       'NUX-SET-AS-DEFAULT',
+       'SIGNIN-VIEW',
+      ].forEach((expectedView, ix) => {
+        assertEquals(expectedView, views[ix].tagName);
+      });
+    });
+  });
+
+  test('new user route (cannot set default)', function() {
+    simulateCannotSetDefault();
+    navigateToForTest(Routes.NEW_USER, 1);
+    return waitBeforeNextRender(testElement).then(() => {
+      const views = testElement.shadowRoot.querySelectorAll('[slot=view]');
+      assertEquals(views.length, 4);
+      ['LANDING-VIEW',
+       'NUX-GOOGLE-APPS',
+       'NUX-NTP-BACKGROUND',
+       'SIGNIN-VIEW',
+      ].forEach((expectedView, ix) => {
+        assertEquals(expectedView, views[ix].tagName);
+      });
+    });
+  });
+
+  test('returning user route (can set default)', function() {
+    simulateCanSetDefault();
+    navigateToForTest(Routes.RETURNING_USER, 1);
+    return waitBeforeNextRender(testElement).then(() => {
+      const views = testElement.shadowRoot.querySelectorAll('[slot=view]');
+      assertEquals(views.length, 2);
+      assertEquals(views[0].tagName, 'LANDING-VIEW');
+      assertEquals(views[1].tagName, 'NUX-SET-AS-DEFAULT');
+    });
+  });
+
+  test('returning user route (cannot set default)', function() {
+    simulateCannotSetDefault();
+    navigateToForTest(Routes.RETURNING_USER, 1);
+
+    // At this point, there should be no steps in the returning user, so
+    // welcome_app should try to go to NTP.
+    return testWelcomeBrowserProxy.whenCalled('goToNewTabPage');
+  });
+
+  test('default-status check resolves with correct value', function() {
+    /**
+     * @param {!DefaultBrowserInfo} status
+     * @param {boolean} expectedDefaultExists
+     * @return {!Promise}
+     */
+    function checkDefaultStatusResolveValue(status, expectedDefaultExists) {
+      testSetAsDefaultProxy.setDefaultStatus(status);
+      resetTestElement();
+
+      // Use the new-user route to test if nux-set-as-default module gets
+      // initialized.
+      navigateToForTest(Routes.NEW_USER, 1);
+      return waitBeforeNextRender(testElement).then(() => {
+        // Use the existence of the nux-set-as-default as indication of
+        // whether or not the promise is resolved with the expected result.
+        assertEquals(
+            expectedDefaultExists, !!testElement.$$('nux-set-as-default'));
+      });
     }
 
-    function simulateCanSetDefault() {
-      testSetAsDefaultProxy.setDefaultStatus({
-        isDefault: false,
-        canBeDefault: true,
-        isDisabledByPolicy: false,
-        isUnknownError: false,
-      });
-
-      resetTestElement();
-    }
-
-    function simulateCannotSetDefault() {
-      testSetAsDefaultProxy.setDefaultStatus({
-        isDefault: true,
-        canBeDefault: true,
-        isDisabledByPolicy: false,
-        isUnknownError: false,
-      });
-
-      resetTestElement();
-    }
-
-    setup(function() {
-      testWelcomeBrowserProxy = new TestWelcomeBrowserProxy();
-      welcome.WelcomeBrowserProxyImpl.instance_ = testWelcomeBrowserProxy;
-
-      testSetAsDefaultProxy = new TestNuxSetAsDefaultProxy();
-      welcome.NuxSetAsDefaultProxyImpl.instance_ = testSetAsDefaultProxy;
-
-      // Not used in test, but setting to test proxy anyway, in order to prevent
-      // calls to backend.
-      welcome.BookmarkProxyImpl.instance_ = new TestBookmarkProxy();
-
-      resetTestElement();
-    });
-
-    teardown(function() {
-      testElement.remove();
-    });
-
-    test('shows landing page by default', function() {
-      assertEquals(
-          testElement.shadowRoot.querySelectorAll('[slot=view]').length, 1);
-      assertTrue(!!testElement.$$('landing-view'));
-      assertTrue(testElement.$$('landing-view').classList.contains('active'));
-    });
-
-    test('new user route (can set default)', function() {
-      simulateCanSetDefault();
-      welcome.navigateTo(welcome.Routes.NEW_USER, 1);
-      return test_util.waitBeforeNextRender(testElement).then(() => {
-        const views = testElement.shadowRoot.querySelectorAll('[slot=view]');
-        assertEquals(views.length, 5);
-        ['LANDING-VIEW',
-         'NUX-GOOGLE-APPS',
-         'NUX-NTP-BACKGROUND',
-         'NUX-SET-AS-DEFAULT',
-         'SIGNIN-VIEW',
-        ].forEach((expectedView, ix) => {
-          assertEquals(expectedView, views[ix].tagName);
+    return checkDefaultStatusResolveValue(
+               {
+                 // Allowed to set as default, and not default yet.
+                 isDefault: false,
+                 canBeDefault: true,
+                 isDisabledByPolicy: false,
+                 isUnknownError: false,
+               },
+               true)
+        .then(() => {
+          return checkDefaultStatusResolveValue(
+              {
+                // Allowed to set as default, but already default.
+                isDefault: true,
+                canBeDefault: true,
+                isDisabledByPolicy: false,
+                isUnknownError: false,
+              },
+              false);
+        })
+        .then(() => {
+          return checkDefaultStatusResolveValue(
+              {
+                // Not default yet, but this chrome install cannot be default.
+                isDefault: false,
+                canBeDefault: false,
+                isDisabledByPolicy: false,
+                isUnknownError: false,
+              },
+              false);
+        })
+        .then(() => {
+          return checkDefaultStatusResolveValue(
+              {
+                // Not default yet, but setting default is disabled by policy.
+                isDefault: false,
+                canBeDefault: true,
+                isDisabledByPolicy: true,
+                isUnknownError: false,
+              },
+              false);
+        })
+        .then(() => {
+          return checkDefaultStatusResolveValue(
+              {
+                // Not default yet, but there is some unknown error.
+                isDefault: false,
+                canBeDefault: true,
+                isDisabledByPolicy: false,
+                isUnknownError: true,
+              },
+              false);
         });
-      });
-    });
-
-    test('new user route (cannot set default)', function() {
-      simulateCannotSetDefault();
-      welcome.navigateTo(welcome.Routes.NEW_USER, 1);
-      return test_util.waitBeforeNextRender(testElement).then(() => {
-        const views = testElement.shadowRoot.querySelectorAll('[slot=view]');
-        assertEquals(views.length, 4);
-        ['LANDING-VIEW',
-         'NUX-GOOGLE-APPS',
-         'NUX-NTP-BACKGROUND',
-         'SIGNIN-VIEW',
-        ].forEach((expectedView, ix) => {
-          assertEquals(expectedView, views[ix].tagName);
-        });
-      });
-    });
-
-    test('returning user route (can set default)', function() {
-      simulateCanSetDefault();
-      welcome.navigateTo(welcome.Routes.RETURNING_USER, 1);
-      return test_util.waitBeforeNextRender(testElement).then(() => {
-        const views = testElement.shadowRoot.querySelectorAll('[slot=view]');
-        assertEquals(views.length, 2);
-        assertEquals(views[0].tagName, 'LANDING-VIEW');
-        assertEquals(views[1].tagName, 'NUX-SET-AS-DEFAULT');
-      });
-    });
-
-    test('returning user route (cannot set default)', function() {
-      simulateCannotSetDefault();
-      welcome.navigateTo(welcome.Routes.RETURNING_USER, 1);
-
-      // At this point, there should be no steps in the returning user, so
-      // welcome_app should try to go to NTP.
-      return testWelcomeBrowserProxy.whenCalled('goToNewTabPage');
-    });
-
-    test('default-status check resolves with correct value', function() {
-      /**
-       * @param {!welcome.DefaultBrowserInfo} status
-       * @param {boolean} expectedDefaultExists
-       * @return {!Promise}
-       */
-      function checkDefaultStatusResolveValue(status, expectedDefaultExists) {
-        testSetAsDefaultProxy.setDefaultStatus(status);
-        resetTestElement();
-
-        // Use the new-user route to test if nux-set-as-default module gets
-        // initialized.
-        welcome.navigateTo(welcome.Routes.NEW_USER, 1);
-        return test_util.waitBeforeNextRender(testElement).then(() => {
-          // Use the existence of the nux-set-as-default as indication of
-          // whether or not the promise is resolved with the expected result.
-          assertEquals(
-              expectedDefaultExists, !!testElement.$$('nux-set-as-default'));
-        });
-      }
-
-      return checkDefaultStatusResolveValue(
-                 {
-                   // Allowed to set as default, and not default yet.
-                   isDefault: false,
-                   canBeDefault: true,
-                   isDisabledByPolicy: false,
-                   isUnknownError: false,
-                 },
-                 true)
-          .then(() => {
-            return checkDefaultStatusResolveValue(
-                {
-                  // Allowed to set as default, but already default.
-                  isDefault: true,
-                  canBeDefault: true,
-                  isDisabledByPolicy: false,
-                  isUnknownError: false,
-                },
-                false);
-          })
-          .then(() => {
-            return checkDefaultStatusResolveValue(
-                {
-                  // Not default yet, but this chrome install cannot be default.
-                  isDefault: false,
-                  canBeDefault: false,
-                  isDisabledByPolicy: false,
-                  isUnknownError: false,
-                },
-                false);
-          })
-          .then(() => {
-            return checkDefaultStatusResolveValue(
-                {
-                  // Not default yet, but setting default is disabled by policy.
-                  isDefault: false,
-                  canBeDefault: true,
-                  isDisabledByPolicy: true,
-                  isUnknownError: false,
-                },
-                false);
-          })
-          .then(() => {
-            return checkDefaultStatusResolveValue(
-                {
-                  // Not default yet, but there is some unknown error.
-                  isDefault: false,
-                  canBeDefault: true,
-                  isDisabledByPolicy: false,
-                  isUnknownError: true,
-                },
-                false);
-          });
-    });
   });
 });
diff --git a/chrome/test/data/webui/welcome/welcome_browsertest.js b/chrome/test/data/webui/welcome/welcome_browsertest.js
index 393c381..9dd25b9 100644
--- a/chrome/test/data/webui/welcome/welcome_browsertest.js
+++ b/chrome/test/data/webui/welcome/welcome_browsertest.js
@@ -15,14 +15,20 @@
     throw 'this is abstract and should be overridden by subclasses';
   }
 
+  /** @override */
   get extraLibraries() {
     return [
-      ...super.extraLibraries,
-      '../test_browser_proxy.js',
+      '//third_party/mocha/mocha.js',
+      '//chrome/test/data/webui/mocha_adapter.js',
     ];
   }
 
   /** @override */
+  get webuiHost() {
+    return 'welcome';
+  }
+
+  /** @override */
   get featureList() {
     return {enabled: ['welcome::kForceEnabled']};
   }
@@ -32,17 +38,7 @@
 var WelcomeAppChooserTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/google_apps/nux_google_apps.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      'app_chooser_test.js',
-      'test_google_app_proxy.js',
-      'test_metrics_proxy.js',
-      'test_bookmark_proxy.js',
-    ]);
+    return 'chrome://test?module=welcome/app_chooser_test.js';
   }
 };
 
@@ -54,18 +50,7 @@
 var WelcomeWelcomeAppTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/welcome_app.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      '../test_util.js',
-      'welcome_app_test.js',
-      'test_bookmark_proxy.js',
-      'test_welcome_browser_proxy.js',
-      'test_nux_set_as_default_proxy.js',
-    ]);
+    return 'chrome://test?module=welcome/welcome_app_test.js';
   }
 };
 
@@ -77,15 +62,7 @@
 var WelcomeSigninViewTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/signin_view.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      'signin_view_test.js',
-      'test_welcome_browser_proxy.js',
-    ]);
+    return 'chrome://test?module=welcome/signin_view_test.js';
   }
 };
 
@@ -97,15 +74,7 @@
 var WelcomeNavigationBehaviorTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/navigation_behavior.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      '../test_util.js',
-      'navigation_behavior_test.js',
-    ]);
+    return 'chrome://test?module=welcome/navigation_behavior_test.js';
   }
 };
 
@@ -117,15 +86,7 @@
 var WelcomeModuleMetricsTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/shared/module_metrics_proxy.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      'module_metrics_test.js',
-      'test_metrics_proxy.js',
-    ]);
+    return 'chrome://test?module=welcome/module_metrics_test.js';
   }
 };
 
@@ -137,16 +98,7 @@
 var WelcomeSetAsDefaultTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/set_as_default/nux_set_as_default.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      '../test_util.js',
-      'nux_set_as_default_test.js',
-      'test_nux_set_as_default_proxy.js',
-    ]);
+    return 'chrome://test?module=welcome/nux_set_as_default_test.js';
   }
 };
 
@@ -158,16 +110,7 @@
 var WelcomeNtpBackgroundTest = class extends WelcomeBrowserTest {
   /** @override */
   get browsePreload() {
-    return 'chrome://welcome/ntp_background/nux_ntp_background.html';
-  }
-
-  /** @override */
-  get extraLibraries() {
-    return super.extraLibraries.concat([
-      'nux_ntp_background_test.js',
-      'test_metrics_proxy.js',
-      'test_ntp_background_proxy.js',
-    ]);
+    return 'chrome://test?module=welcome/nux_ntp_background_test.js';
   }
 };
 
diff --git a/chrome/test/payments/payment_request_test_controller.h b/chrome/test/payments/payment_request_test_controller.h
index 03a650c6d..d1756970 100644
--- a/chrome/test/payments/payment_request_test_controller.h
+++ b/chrome/test/payments/payment_request_test_controller.h
@@ -37,12 +37,14 @@
 // cross-platform way for testing both Android and desktop.
 class PaymentRequestTestController {
  public:
-  explicit PaymentRequestTestController(PaymentRequestTestObserver* observer);
+  PaymentRequestTestController();
   ~PaymentRequestTestController();
 
   // To be called from an override of BrowserTestBase::SetUpOnMainThread().
   void SetUpOnMainThread();
 
+  void SetObserver(PaymentRequestTestObserver* observer);
+
   // Sets values that will change the behaviour of PaymentRequests created in
   // the future.
   void SetIncognito(bool is_incognito);
@@ -59,7 +61,7 @@
   void OnConnectionTerminated();
   void OnAbortCalled();
 
-  PaymentRequestTestObserver* const observer_;
+  PaymentRequestTestObserver* observer_ = nullptr;
 
   bool is_incognito_ = false;
   bool valid_ssl_ = true;
diff --git a/chrome/test/payments/payment_request_test_controller_android.cc b/chrome/test/payments/payment_request_test_controller_android.cc
index 6493ee72..c96acca 100644
--- a/chrome/test/payments/payment_request_test_controller_android.cc
+++ b/chrome/test/payments/payment_request_test_controller_android.cc
@@ -10,9 +10,7 @@
 
 namespace payments {
 
-PaymentRequestTestController::PaymentRequestTestController(
-    PaymentRequestTestObserver* observer)
-    : observer_(observer) {}
+PaymentRequestTestController::PaymentRequestTestController() {}
 
 PaymentRequestTestController::~PaymentRequestTestController() = default;
 
@@ -46,6 +44,11 @@
       /*skip_ui_for_basic_card=*/false);
 }
 
+void PaymentRequestTestController::SetObserver(
+    PaymentRequestTestObserver* observer) {
+  observer_ = observer;
+}
+
 void PaymentRequestTestController::SetIncognito(bool is_incognito) {
   is_incognito_ = is_incognito;
   SetUseDelegateOnPaymentRequestForTesting(
@@ -72,31 +75,38 @@
 }
 
 void PaymentRequestTestController::OnCanMakePaymentCalled() {
-  observer_->OnCanMakePaymentCalled();
+  if (observer_)
+    observer_->OnCanMakePaymentCalled();
 }
 
 void PaymentRequestTestController::OnCanMakePaymentReturned() {
-  observer_->OnCanMakePaymentReturned();
+  if (observer_)
+    observer_->OnCanMakePaymentReturned();
 }
 
 void PaymentRequestTestController::OnHasEnrolledInstrumentCalled() {
-  observer_->OnHasEnrolledInstrumentCalled();
+  if (observer_)
+    observer_->OnHasEnrolledInstrumentCalled();
 }
 
 void PaymentRequestTestController::OnHasEnrolledInstrumentReturned() {
-  observer_->OnHasEnrolledInstrumentReturned();
+  if (observer_)
+    observer_->OnHasEnrolledInstrumentReturned();
 }
 
 void PaymentRequestTestController::OnNotSupportedError() {
-  observer_->OnNotSupportedError();
+  if (observer_)
+    observer_->OnNotSupportedError();
 }
 
 void PaymentRequestTestController::OnConnectionTerminated() {
-  observer_->OnConnectionTerminated();
+  if (observer_)
+    observer_->OnConnectionTerminated();
 }
 
 void PaymentRequestTestController::OnAbortCalled() {
-  observer_->OnAbortCalled();
+  if (observer_)
+    observer_->OnAbortCalled();
 }
 
 }  // namespace payments
diff --git a/chrome/test/payments/payment_request_test_controller_desktop.cc b/chrome/test/payments/payment_request_test_controller_desktop.cc
index b33fea5..860fe59 100644
--- a/chrome/test/payments/payment_request_test_controller_desktop.cc
+++ b/chrome/test/payments/payment_request_test_controller_desktop.cc
@@ -31,6 +31,7 @@
     return valid_ssl_ ? "" : "Invalid SSL certificate";
   }
   PrefService* GetPrefService() override { return prefs_; }
+  bool IsBrowserWindowActive() const override { return true; }
 
  private:
   const bool is_incognito_;
@@ -66,10 +67,8 @@
   PaymentRequestTestController* const controller_;
 };
 
-PaymentRequestTestController::PaymentRequestTestController(
-    PaymentRequestTestObserver* observer)
-    : observer_(observer),
-      prefs_(std::make_unique<sync_preferences::TestingPrefServiceSyncable>()),
+PaymentRequestTestController::PaymentRequestTestController()
+    : prefs_(std::make_unique<sync_preferences::TestingPrefServiceSyncable>()),
       observer_converter_(std::make_unique<ObserverConverter>(this)) {}
 
 PaymentRequestTestController::~PaymentRequestTestController() = default;
@@ -82,6 +81,11 @@
   UpdateDelegateFactory();
 }
 
+void PaymentRequestTestController::SetObserver(
+    PaymentRequestTestObserver* observer) {
+  observer_ = observer;
+}
+
 void PaymentRequestTestController::SetIncognito(bool is_incognito) {
   is_incognito_ = is_incognito;
   UpdateDelegateFactory();
@@ -122,31 +126,38 @@
 }
 
 void PaymentRequestTestController::OnCanMakePaymentCalled() {
-  observer_->OnCanMakePaymentCalled();
+  if (observer_)
+    observer_->OnCanMakePaymentCalled();
 }
 
 void PaymentRequestTestController::OnCanMakePaymentReturned() {
-  observer_->OnCanMakePaymentReturned();
+  if (observer_)
+    observer_->OnCanMakePaymentReturned();
 }
 
 void PaymentRequestTestController::OnHasEnrolledInstrumentCalled() {
-  observer_->OnHasEnrolledInstrumentCalled();
+  if (observer_)
+    observer_->OnHasEnrolledInstrumentCalled();
 }
 
 void PaymentRequestTestController::OnHasEnrolledInstrumentReturned() {
-  observer_->OnHasEnrolledInstrumentReturned();
+  if (observer_)
+    observer_->OnHasEnrolledInstrumentReturned();
 }
 
 void PaymentRequestTestController::OnNotSupportedError() {
-  observer_->OnNotSupportedError();
+  if (observer_)
+    observer_->OnNotSupportedError();
 }
 
 void PaymentRequestTestController::OnConnectionTerminated() {
-  observer_->OnConnectionTerminated();
+  if (observer_)
+    observer_->OnConnectionTerminated();
 }
 
 void PaymentRequestTestController::OnAbortCalled() {
-  observer_->OnAbortCalled();
+  if (observer_)
+    observer_->OnAbortCalled();
 }
 
 }  // namespace payments
diff --git a/chrome/typemaps.gni b/chrome/typemaps.gni
index 24fe700..dc66375 100644
--- a/chrome/typemaps.gni
+++ b/chrome/typemaps.gni
@@ -3,7 +3,6 @@
 # found in the LICENSE file.
 
 typemaps = [
-  "//chrome/common/browser_controls_state.typemap",
   "//chrome/common/mac/app_shim.typemap",
   "//chrome/common/search.typemap",
   "//chrome/common/web_application_info_provider.typemap",
diff --git a/chromecast/media/base/media_resource_tracker_unittest.cc b/chromecast/media/base/media_resource_tracker_unittest.cc
index 506b777..68292bf8 100644
--- a/chromecast/media/base/media_resource_tracker_unittest.cc
+++ b/chromecast/media/base/media_resource_tracker_unittest.cc
@@ -72,7 +72,7 @@
     base::RunLoop().RunUntilIdle();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   TestMediaResourceTracker* resource_tracker_;
   std::unique_ptr<MediaResourceTrackerTestMocks> test_mocks_;
 
diff --git a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
index 20cb3c6..f0e7741 100644
--- a/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
+++ b/chromecast/media/cma/backend/android/java/src/org/chromium/chromecast/cma/backend/android/AudioSinkAudioTrackImpl.java
@@ -132,7 +132,7 @@
     private static final long MAX_TIME_IGNORING_TSTAMPS_NSECS = SEC_IN_NSEC;
 
     // Additional padding for minimum buffer time, determined experimentally.
-    private static final long MIN_BUFFERED_TIME_PADDING_US = ANDROID_AUDIO_PERIOD_SIZE_US;
+    private static final long MIN_BUFFERED_TIME_PADDING_US = 120000;
 
     // Max retries for AudioTrackBuilder
     private static final int MAX_RETRIES_FOR_AUDIO_TRACKS = 1;
diff --git a/chromecast/media/cma/backend/fuchsia/mixer_output_stream_fuchsia_unittest.cc b/chromecast/media/cma/backend/fuchsia/mixer_output_stream_fuchsia_unittest.cc
index f1d5e46..0eb4df1a 100644
--- a/chromecast/media/cma/backend/fuchsia/mixer_output_stream_fuchsia_unittest.cc
+++ b/chromecast/media/cma/backend/fuchsia/mixer_output_stream_fuchsia_unittest.cc
@@ -18,9 +18,8 @@
 
 class MixerOutputStreamFuchsiaTest : public ::testing::Test {
  protected:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   MixerOutputStreamFuchsia output_;
 };
 
diff --git a/chromecast/media/cma/backend/multizone_backend_unittest.cc b/chromecast/media/cma/backend/multizone_backend_unittest.cc
index 220bc4d..53fc090a 100644
--- a/chromecast/media/cma/backend/multizone_backend_unittest.cc
+++ b/chromecast/media/cma/backend/multizone_backend_unittest.cc
@@ -152,7 +152,7 @@
   void OnEndOfStream();
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::vector<std::unique_ptr<BufferFeeder>> effects_feeders_;
   std::unique_ptr<BufferFeeder> audio_feeder_;
 
diff --git a/chromeos/attestation/attestation_flow_unittest.cc b/chromeos/attestation/attestation_flow_unittest.cc
index 7bc59cf..96310ae6 100644
--- a/chromeos/attestation/attestation_flow_unittest.cc
+++ b/chromeos/attestation/attestation_flow_unittest.cc
@@ -66,7 +66,7 @@
     run_loop_->Run();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::RunLoop* run_loop_;
 };
 
diff --git a/chromeos/audio/cras_audio_handler_unittest.cc b/chromeos/audio/cras_audio_handler_unittest.cc
index ea55ff2..29cec43 100644
--- a/chromeos/audio/cras_audio_handler_unittest.cc
+++ b/chromeos/audio/cras_audio_handler_unittest.cc
@@ -308,7 +308,8 @@
 class CrasAudioHandlerTest : public testing::TestWithParam<int> {
  public:
   CrasAudioHandlerTest()
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
+      : task_environment_(
+            base::test::SingleThreadTaskEnvironment::MainThreadType::UI) {}
   ~CrasAudioHandlerTest() override = default;
 
   void SetUp() override {
@@ -483,7 +484,7 @@
     return FakeCrasAudioClient::Get();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::SystemMonitor system_monitor_;
   SystemMonitorObserver system_monitor_observer_;
   CrasAudioHandler* cras_audio_handler_ = nullptr;         // Not owned.
diff --git a/chromeos/dbus/cros_disks_client.cc b/chromeos/dbus/cros_disks_client.cc
index bbee539..60729ad 100644
--- a/chromeos/dbus/cros_disks_client.cc
+++ b/chromeos/dbus/cros_disks_client.cc
@@ -525,7 +525,7 @@
 
   dbus::ObjectProxy* proxy_;
 
-  base::ObserverList<Observer>::Unchecked observer_list_;
+  base::ObserverList<Observer> observer_list_;
 
   std::unordered_map<std::string, base::TimeTicks> format_start_time_;
 
diff --git a/chromeos/dbus/cros_disks_client.h b/chromeos/dbus/cros_disks_client.h
index 24ac348f..5167175 100644
--- a/chromeos/dbus/cros_disks_client.h
+++ b/chromeos/dbus/cros_disks_client.h
@@ -14,6 +14,7 @@
 #include "base/callback_forward.h"
 #include "base/component_export.h"
 #include "base/macros.h"
+#include "base/observer_list_types.h"
 #include "chromeos/dbus/dbus_client.h"
 #include "chromeos/dbus/dbus_method_call_status.h"
 
@@ -298,7 +299,7 @@
   // The argument is the unmount error code.
   typedef base::OnceCallback<void(MountError error_code)> UnmountCallback;
 
-  class Observer {
+  class Observer : public base::CheckedObserver {
    public:
     // Called when a mount event signal is received.
     virtual void OnMountEvent(MountEventType event_type,
@@ -314,9 +315,6 @@
     // Called when a RenameCompleted signal is received.
     virtual void OnRenameCompleted(RenameError error_code,
                                    const std::string& device_path) = 0;
-
-   protected:
-    virtual ~Observer() = default;
   };
 
   ~CrosDisksClient() override;
diff --git a/chromeos/dbus/fake_cros_disks_client.h b/chromeos/dbus/fake_cros_disks_client.h
index 5ff4220..d737306 100644
--- a/chromeos/dbus/fake_cros_disks_client.h
+++ b/chromeos/dbus/fake_cros_disks_client.h
@@ -164,7 +164,7 @@
                 VoidDBusMethodCallback callback,
                 MountError mount_error);
 
-  base::ObserverList<Observer>::Unchecked observer_list_;
+  base::ObserverList<Observer> observer_list_;
   int unmount_call_count_ = 0;
   std::string last_unmount_device_path_;
   UnmountOptions last_unmount_options_ = UNMOUNT_OPTIONS_NONE;
diff --git a/chromeos/dbus/shill/modem_messaging_client_unittest.cc b/chromeos/dbus/shill/modem_messaging_client_unittest.cc
index 38890d7..c636661 100644
--- a/chromeos/dbus/shill/modem_messaging_client_unittest.cc
+++ b/chromeos/dbus/shill/modem_messaging_client_unittest.cc
@@ -120,7 +120,7 @@
  protected:
   ModemMessagingClient* client_ = nullptr;  // Unowned convenience pointer.
   // A message loop to emulate asynchronous behavior.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   // The mock bus.
   scoped_refptr<dbus::MockBus> mock_bus_;
   // The mock object proxy.
diff --git a/chromeos/dbus/shill/shill_client_unittest_base.h b/chromeos/dbus/shill/shill_client_unittest_base.h
index 0fe1c67..f6f813c 100644
--- a/chromeos/dbus/shill/shill_client_unittest_base.h
+++ b/chromeos/dbus/shill/shill_client_unittest_base.h
@@ -173,7 +173,7 @@
       const base::DictionaryValue& result);
 
   // A message loop to emulate asynchronous behavior.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   // The mock bus.
   scoped_refptr<dbus::MockBus> mock_bus_;
 
diff --git a/chromeos/disks/disk_mount_manager.cc b/chromeos/disks/disk_mount_manager.cc
index 6c6aa5fbb..9cc169ed6 100644
--- a/chromeos/disks/disk_mount_manager.cc
+++ b/chromeos/disks/disk_mount_manager.cc
@@ -16,6 +16,7 @@
 #include "base/barrier_closure.h"
 #include "base/bind.h"
 #include "base/bind_helpers.h"
+#include "base/logging.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/weak_ptr.h"
@@ -830,7 +831,7 @@
   }
 
   // Mount event change observers.
-  base::ObserverList<DiskMountManager::Observer>::Unchecked observers_;
+  base::ObserverList<DiskMountManager::Observer> observers_;
 
   CrosDisksClient* cros_disks_client_;
 
@@ -856,6 +857,10 @@
 
 }  // namespace
 
+DiskMountManager::Observer::~Observer() {
+  DCHECK(!IsInObserverList());
+}
+
 bool DiskMountManager::AddDiskForTest(std::unique_ptr<Disk> disk) {
   return false;
 }
diff --git a/chromeos/disks/disk_mount_manager.h b/chromeos/disks/disk_mount_manager.h
index 44dbde4..8be70ef 100644
--- a/chromeos/disks/disk_mount_manager.h
+++ b/chromeos/disks/disk_mount_manager.h
@@ -13,6 +13,7 @@
 #include "base/callback_forward.h"
 #include "base/component_export.h"
 #include "base/files/file_path.h"
+#include "base/observer_list_types.h"
 #include "chromeos/dbus/cros_disks_client.h"
 
 namespace chromeos {
@@ -107,10 +108,8 @@
       EnsureMountInfoRefreshedCallback;
 
   // Implement this interface to be notified about disk/mount related events.
-  class Observer {
+  class Observer : public base::CheckedObserver {
    public:
-    virtual ~Observer() {}
-
     // Called when auto-mountable disk mount status is changed.
     virtual void OnAutoMountableDiskEvent(DiskEvent event, const Disk& disk) {}
     // Called when fixed storage disk status is changed.
@@ -130,6 +129,9 @@
     virtual void OnRenameEvent(RenameEvent event,
                                RenameError error_code,
                                const std::string& device_path) {}
+
+   protected:
+    ~Observer() override;
   };
 
   virtual ~DiskMountManager() {}
diff --git a/chromeos/disks/mock_disk_mount_manager.h b/chromeos/disks/mock_disk_mount_manager.h
index a9f33b8..dbb327a 100644
--- a/chromeos/disks/mock_disk_mount_manager.h
+++ b/chromeos/disks/mock_disk_mount_manager.h
@@ -117,7 +117,7 @@
   void NotifyDiskChanged(DiskEvent event, const Disk* disk);
 
   // The list of observers.
-  base::ObserverList<DiskMountManager::Observer>::Unchecked observers_;
+  base::ObserverList<DiskMountManager::Observer> observers_;
 
   // The list of disks found.
   DiskMountManager::DiskMap disks_;
diff --git a/chromeos/printing/ppd_line_reader.cc b/chromeos/printing/ppd_line_reader.cc
index 8125237..b4c87c9 100644
--- a/chromeos/printing/ppd_line_reader.cc
+++ b/chromeos/printing/ppd_line_reader.cc
@@ -108,9 +108,7 @@
 
   // Skip input until we hit a newline (which is discarded).  If
   // we encounter eof before a newline, false is returned.
-  // TODO(thakis): Remove NOINLINE once the msan fix for
-  // https://crbug.com/998966 is rolled in.
-  bool SkipToNextLine() NOINLINE {
+  bool SkipToNextLine() {
     while (true) {
       char c = NextChar();
       if (Eof()) {
diff --git a/chromeos/process_proxy/process_output_watcher_unittest.cc b/chromeos/process_proxy/process_output_watcher_unittest.cc
index e169ffd..9bde562 100644
--- a/chromeos/process_proxy/process_output_watcher_unittest.cc
+++ b/chromeos/process_proxy/process_output_watcher_unittest.cc
@@ -193,7 +193,7 @@
 
  private:
   base::Closure test_case_done_callback_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<base::Thread> output_watch_thread_;
   bool output_watch_thread_started_;
   bool failed_;
diff --git a/chromeos/tpm/tpm_token_info_getter_unittest.cc b/chromeos/tpm/tpm_token_info_getter_unittest.cc
index 503b7310..cd1c8ba 100644
--- a/chromeos/tpm/tpm_token_info_getter_unittest.cc
+++ b/chromeos/tpm/tpm_token_info_getter_unittest.cc
@@ -226,7 +226,7 @@
   std::vector<int64_t> delays_;
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   DISALLOW_COPY_AND_ASSIGN(SystemTPMTokenInfoGetterTest);
 };
@@ -252,7 +252,7 @@
   std::vector<int64_t> delays_;
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   DISALLOW_COPY_AND_ASSIGN(UserTPMTokenInfoGetterTest);
 };
diff --git a/components/blacklist/opt_out_blacklist/opt_out_blacklist_unittest.cc b/components/blacklist/opt_out_blacklist/opt_out_blacklist_unittest.cc
index 085c225d2..82c34530 100644
--- a/components/blacklist/opt_out_blacklist/opt_out_blacklist_unittest.cc
+++ b/components/blacklist/opt_out_blacklist/opt_out_blacklist_unittest.cc
@@ -271,7 +271,7 @@
   }
 
  protected:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   // Observer to |black_list_|.
   TestOptOutBlacklistDelegate blacklist_delegate_;
diff --git a/components/blacklist/opt_out_blacklist/sql/opt_out_store_sql_unittest.cc b/components/blacklist/opt_out_blacklist/sql/opt_out_store_sql_unittest.cc
index 4e056a5..053b516c 100644
--- a/components/blacklist/opt_out_blacklist/sql/opt_out_store_sql_unittest.cc
+++ b/components/blacklist/opt_out_blacklist/sql/opt_out_store_sql_unittest.cc
@@ -93,7 +93,7 @@
   void TearDown() override { DestroyStore(); }
 
  protected:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   // The backing SQL store.
   std::unique_ptr<OptOutStoreSQL> store_;
diff --git a/components/cronet/url_request_context_config.cc b/components/cronet/url_request_context_config.cc
index 690fccc..b92ed20 100644
--- a/components/cronet/url_request_context_config.cc
+++ b/components/cronet/url_request_context_config.cc
@@ -152,6 +152,8 @@
 
 const char kSSLKeyLogFile[] = "ssl_key_log_file";
 
+const char kGoAwayOnPathDegrading[] = "go_away_on_path_degrading";
+
 // "goaway_sessions_on_ip_change" is default on for iOS unless overrided via
 // experimental options explicitly.
 #if defined(OS_IOS)
@@ -421,6 +423,13 @@
             goaway_sessions_on_ip_change;
       }
 
+      bool go_away_on_path_degrading = false;
+      if (quic_args->GetBoolean(kGoAwayOnPathDegrading,
+                                &go_away_on_path_degrading)) {
+        session_params->quic_params.go_away_on_path_degrading =
+            go_away_on_path_degrading;
+      }
+
       bool quic_allow_server_migration = false;
       if (quic_args->GetBoolean(kQuicAllowServerMigration,
                                 &quic_allow_server_migration)) {
diff --git a/components/cronet/url_request_context_config_unittest.cc b/components/cronet/url_request_context_config_unittest.cc
index a924e4d..17b1cfb 100644
--- a/components/cronet/url_request_context_config_unittest.cc
+++ b/components/cronet/url_request_context_config_unittest.cc
@@ -208,6 +208,7 @@
   EXPECT_FALSE(params->quic_params.migrate_idle_sessions);
   EXPECT_FALSE(params->quic_params.retry_on_alternate_network_before_handshake);
   EXPECT_FALSE(params->quic_params.race_stale_dns_on_connection);
+  EXPECT_FALSE(params->quic_params.go_away_on_path_degrading);
 
   // Check race_cert_verification.
   EXPECT_TRUE(params->quic_params.race_cert_verification);
@@ -760,6 +761,57 @@
   EXPECT_TRUE(params->quic_params.race_stale_dns_on_connection);
 }
 
+TEST(URLRequestContextConfigTest, SetQuicGoawayOnPathDegrading) {
+  base::test::TaskEnvironment task_environment_(
+      base::test::TaskEnvironment::MainThreadType::IO);
+
+  URLRequestContextConfig config(
+      // Enable QUIC.
+      true,
+      // QUIC User Agent ID.
+      "Default QUIC User Agent ID",
+      // Enable SPDY.
+      true,
+      // Enable Brotli.
+      false,
+      // Type of http cache.
+      URLRequestContextConfig::HttpCacheType::DISK,
+      // Max size of http cache in bytes.
+      1024000,
+      // Disable caching for HTTP responses. Other information may be stored in
+      // the cache.
+      false,
+      // Storage path for http cache and cookie storage.
+      "/data/data/org.chromium.net/app_cronet_test/test_storage",
+      // Accept-Language request header field.
+      "foreign-language",
+      // User-Agent request header field.
+      "fake agent",
+      // JSON encoded experimental options.
+      "{\"QUIC\":{\"go_away_on_path_degrading\":true}}",
+      // MockCertVerifier to use for testing purposes.
+      std::unique_ptr<net::CertVerifier>(),
+      // Enable network quality estimator.
+      false,
+      // Enable Public Key Pinning bypass for local trust anchors.
+      true,
+      // Optional network thread priority.
+      base::Optional<double>());
+
+  net::URLRequestContextBuilder builder;
+  net::NetLog net_log;
+  config.ConfigureURLRequestContextBuilder(&builder, &net_log);
+  // Set a ProxyConfigService to avoid DCHECK failure when building.
+  builder.set_proxy_config_service(
+      std::make_unique<net::ProxyConfigServiceFixed>(
+          net::ProxyConfigWithAnnotation::CreateDirect()));
+  std::unique_ptr<net::URLRequestContext> context(builder.Build());
+  const net::HttpNetworkSession::Params* params =
+      context->GetNetworkSessionParams();
+
+  EXPECT_TRUE(params->quic_params.go_away_on_path_degrading);
+}
+
 TEST(URLRequestContextConfigTest, SetQuicHostWhitelist) {
   base::test::TaskEnvironment task_environment_(
       base::test::TaskEnvironment::MainThreadType::IO);
diff --git a/components/download/internal/background_service/scheduler/device_status_listener_unittest.cc b/components/download/internal/background_service/scheduler/device_status_listener_unittest.cc
index d6ad0b4..f814936c 100644
--- a/components/download/internal/background_service/scheduler/device_status_listener_unittest.cc
+++ b/components/download/internal/background_service/scheduler/device_status_listener_unittest.cc
@@ -146,7 +146,7 @@
   MockObserver mock_observer_;
 
   // Needed for network change notifier and power monitor.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::PowerMonitorTestSource* power_source_;
   TestBatteryStatusListener* test_battery_listener_;
 };
diff --git a/components/download/internal/common/download_item_impl.cc b/components/download/internal/common/download_item_impl.cc
index 0486da5..f24ef71 100644
--- a/components/download/internal/common/download_item_impl.cc
+++ b/components/download/internal/common/download_item_impl.cc
@@ -423,9 +423,9 @@
       delegate_(delegate),
       destination_info_(path, path, 0, false, std::string(), base::Time()),
       is_updating_observers_(false) {
-  job_ = DownloadJobFactory::CreateJob(this, std::move(cancel_request_callback),
-                                       DownloadCreateInfo(), true, nullptr,
-                                       nullptr);
+  job_ = DownloadJobFactory::CreateJob(
+      this, std::move(cancel_request_callback), DownloadCreateInfo(), true,
+      URLLoaderFactoryProvider::GetNullPtr(), nullptr);
   delegate_->Attach();
   Init(true /* actively downloading */, TYPE_SAVE_PAGE_AS);
 }
@@ -1417,7 +1417,8 @@
     std::unique_ptr<DownloadFile> file,
     DownloadJob::CancelRequestCallback cancel_request_callback,
     const DownloadCreateInfo& new_create_info,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider) {
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider) {
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!download_file_);
   DVLOG(20) << __func__ << "() this=" << DebugString(true);
@@ -1426,7 +1427,7 @@
   download_file_ = std::move(file);
   job_ = DownloadJobFactory::CreateJob(
       this, std::move(cancel_request_callback), new_create_info, false,
-      url_loader_factory_provider,
+      std::move(url_loader_factory_provider),
       delegate_ ? delegate_->GetServiceManagerConnector() : nullptr);
   if (job_->IsParallelizable()) {
     RecordParallelizableDownloadCount(START_COUNT, IsParallelDownloadEnabled());
diff --git a/components/download/internal/common/download_item_impl_unittest.cc b/components/download/internal/common/download_item_impl_unittest.cc
index c6e03b3..7bb61475 100644
--- a/components/download/internal/common/download_item_impl_unittest.cc
+++ b/components/download/internal/common/download_item_impl_unittest.cc
@@ -290,7 +290,7 @@
     }
 
     item->Start(std::move(download_file), base::DoNothing(), *create_info_,
-                nullptr);
+                URLLoaderFactoryProvider::GetNullPtr());
     task_environment_.RunUntilIdle();
 
     // So that we don't have a function writing to a stack variable
@@ -576,7 +576,7 @@
   EXPECT_CALL(*mock_download_file, Initialize(_, _, _, _));
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(_, _));
   item->Start(std::move(download_file), base::DoNothing(), *create_info(),
-              nullptr);
+              URLLoaderFactoryProvider::GetNullPtr());
 
   item->Pause();
   ASSERT_TRUE(observer.CheckAndResetDownloadUpdated());
@@ -822,7 +822,7 @@
     // Copied key parts of DoIntermediateRename & CallDownloadItemStart
     // to allow for holding onto the request handle.
     item->Start(std::move(mock_download_file), base::DoNothing(),
-                *create_info(), nullptr);
+                *create_info(), URLLoaderFactoryProvider::GetNullPtr());
     task_environment_.RunUntilIdle();
 
     base::FilePath target_path(kDummyTargetPath);
@@ -1154,7 +1154,7 @@
   EXPECT_CALL(*mock_download_file, Initialize(_, _, _, _));
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _));
   item->Start(std::move(download_file), base::DoNothing(), *create_info(),
-              nullptr);
+              URLLoaderFactoryProvider::GetNullPtr());
   task_environment_.RunUntilIdle();
 
   CleanupItem(item, mock_download_file, DownloadItem::IN_PROGRESS);
@@ -1180,7 +1180,7 @@
   item->Start(
       std::move(file),
       base::Bind(&DownloadItemTest::CancelRequest, base::Unretained(this)),
-      *create_info(), nullptr);
+      *create_info(), URLLoaderFactoryProvider::GetNullPtr());
   task_environment_.RunUntilIdle();
 
   download_target_callback.Run(base::FilePath(kDummyTargetPath),
@@ -1217,7 +1217,7 @@
   EXPECT_CALL(*mock_delegate(), DetermineDownloadTarget(item, _))
       .WillOnce(SaveArg<1>(&download_target_callback));
   item->Start(std::move(null_download_file), base::DoNothing(), *create_info(),
-              nullptr);
+              URLLoaderFactoryProvider::GetNullPtr());
   EXPECT_EQ(DownloadItem::IN_PROGRESS, item->GetState());
   task_environment_.RunUntilIdle();
 
@@ -2313,7 +2313,7 @@
   item_->Start(
       std::move(file_),
       base::Bind(&DownloadItemTest::CancelRequest, base::Unretained(this)),
-      *create_info(), nullptr);
+      *create_info(), URLLoaderFactoryProvider::GetNullPtr());
   task_environment_.RunUntilIdle();
 
   base::WeakPtr<DownloadDestinationObserver> destination_observer =
@@ -2362,7 +2362,7 @@
   item_->Start(
       std::move(file_),
       base::Bind(&DownloadItemTest::CancelRequest, base::Unretained(this)),
-      *create_info(), nullptr);
+      *create_info(), URLLoaderFactoryProvider::GetNullPtr());
   task_environment_.RunUntilIdle();
 
   base::WeakPtr<DownloadDestinationObserver> destination_observer =
@@ -2425,7 +2425,8 @@
   EXPECT_CALL(*file_, Initialize(_, _, _, _))
       .WillOnce(SaveArg<0>(&initialize_callback));
 
-  item_->Start(std::move(file_), base::DoNothing(), *create_info(), nullptr);
+  item_->Start(std::move(file_), base::DoNothing(), *create_info(),
+               URLLoaderFactoryProvider::GetNullPtr());
   task_environment_.RunUntilIdle();
 
   base::WeakPtr<DownloadDestinationObserver> destination_observer =
diff --git a/components/download/internal/common/download_job_factory.cc b/components/download/internal/common/download_job_factory.cc
index 8747b53..cd6274c 100644
--- a/components/download/internal/common/download_job_factory.cc
+++ b/components/download/internal/common/download_job_factory.cc
@@ -14,7 +14,6 @@
 #include "components/download/public/common/download_features.h"
 #include "components/download/public/common/download_item.h"
 #include "components/download/public/common/download_stats.h"
-#include "components/download/public/common/url_loader_factory_provider.h"
 #include "net/url_request/url_request_context_getter.h"
 
 namespace download {
@@ -164,7 +163,8 @@
     DownloadJob::CancelRequestCallback cancel_request_callback,
     const DownloadCreateInfo& create_info,
     bool is_save_package_download,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider,
     service_manager::Connector* connector) {
   if (is_save_package_download) {
     return std::make_unique<SavePackageDownloadJob>(
@@ -176,7 +176,7 @@
   if (IsParallelDownloadEnabled() && is_parallelizable) {
     return std::make_unique<ParallelDownloadJob>(
         download_item, std::move(cancel_request_callback), create_info,
-        url_loader_factory_provider, connector);
+        std::move(url_loader_factory_provider), connector);
   }
 
   // An ordinary download job.
diff --git a/components/download/internal/common/download_worker.cc b/components/download/internal/common/download_worker.cc
index baed3b2..b1e9792 100644
--- a/components/download/internal/common/download_worker.cc
+++ b/components/download/internal/common/download_worker.cc
@@ -49,7 +49,7 @@
 void CreateUrlDownloadHandler(
     std::unique_ptr<DownloadUrlParameters> params,
     base::WeakPtr<UrlDownloadHandler::Delegate> delegate,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider* url_loader_factory_provider,
     const URLSecurityPolicy& url_security_policy,
     std::unique_ptr<service_manager::Connector> connector,
     const scoped_refptr<base::SingleThreadTaskRunner>& task_runner) {
@@ -83,15 +83,17 @@
 
 void DownloadWorker::SendRequest(
     std::unique_ptr<DownloadUrlParameters> params,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider* url_loader_factory_provider,
     service_manager::Connector* connector) {
   GetIOTaskRunner()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&CreateUrlDownloadHandler, std::move(params),
-                     weak_factory_.GetWeakPtr(), url_loader_factory_provider,
-                     base::BindRepeating(&IsURLSafe),
-                     connector ? connector->Clone() : nullptr,
-                     base::ThreadTaskRunnerHandle::Get()));
+      FROM_HERE, base::BindOnce(&CreateUrlDownloadHandler, std::move(params),
+                                weak_factory_.GetWeakPtr(),
+                                // This is safe because URLLoaderFactoryProvider
+                                // deleter is called on the same task sequence.
+                                base::Unretained(url_loader_factory_provider),
+                                base::BindRepeating(&IsURLSafe),
+                                connector ? connector->Clone() : nullptr,
+                                base::ThreadTaskRunnerHandle::Get()));
 }
 
 void DownloadWorker::Pause() {
@@ -110,7 +112,8 @@
 void DownloadWorker::OnUrlDownloadStarted(
     std::unique_ptr<DownloadCreateInfo> create_info,
     std::unique_ptr<InputStream> input_stream,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider,
     UrlDownloadHandler* downloader,
     const DownloadUrlParameters::OnStartedCallback& callback) {
   // |callback| is not used in subsequent requests.
diff --git a/components/download/internal/common/download_worker.h b/components/download/internal/common/download_worker.h
index cd4b819..d560f0d 100644
--- a/components/download/internal/common/download_worker.h
+++ b/components/download/internal/common/download_worker.h
@@ -47,10 +47,9 @@
   int64_t length() const { return length_; }
 
   // Send network request to ask for a download.
-  void SendRequest(
-      std::unique_ptr<DownloadUrlParameters> params,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
-      service_manager::Connector* connector);
+  void SendRequest(std::unique_ptr<DownloadUrlParameters> params,
+                   URLLoaderFactoryProvider* url_loader_factory_provider,
+                   service_manager::Connector* connector);
 
   // Download operations.
   void Pause();
@@ -62,7 +61,8 @@
   void OnUrlDownloadStarted(
       std::unique_ptr<DownloadCreateInfo> create_info,
       std::unique_ptr<InputStream> input_stream,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+      URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+          url_loader_factory_provider,
       UrlDownloadHandler* downloader,
       const DownloadUrlParameters::OnStartedCallback& callback) override;
   void OnUrlDownloadStopped(UrlDownloadHandler* downloader) override;
diff --git a/components/download/internal/common/in_progress_download_manager.cc b/components/download/internal/common/in_progress_download_manager.cc
index a24a873..694651a 100644
--- a/components/download/internal/common/in_progress_download_manager.cc
+++ b/components/download/internal/common/in_progress_download_manager.cc
@@ -219,11 +219,12 @@
 void InProgressDownloadManager::OnUrlDownloadStarted(
     std::unique_ptr<DownloadCreateInfo> download_create_info,
     std::unique_ptr<InputStream> input_stream,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider,
     UrlDownloadHandler* downloader,
     const DownloadUrlParameters::OnStartedCallback& callback) {
   StartDownload(std::move(download_create_info), std::move(input_stream),
-                url_loader_factory_provider,
+                std::move(url_loader_factory_provider),
                 base::BindOnce(&InProgressDownloadManager::CancelUrlDownload,
                                weak_factory_.GetWeakPtr(), downloader),
                 callback);
@@ -437,7 +438,8 @@
 void InProgressDownloadManager::StartDownload(
     std::unique_ptr<DownloadCreateInfo> info,
     std::unique_ptr<InputStream> stream,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider,
     DownloadJob::CancelRequestCallback cancel_request_callback,
     const DownloadUrlParameters::OnStartedCallback& on_started) {
   DCHECK(info);
@@ -477,7 +479,7 @@
         std::move(info), on_started,
         base::BindOnce(&InProgressDownloadManager::StartDownloadWithItem,
                        weak_factory_.GetWeakPtr(), std::move(stream),
-                       url_loader_factory_provider,
+                       std::move(url_loader_factory_provider),
                        std::move(cancel_request_callback)));
   } else {
     std::string guid = info->guid;
@@ -488,7 +490,7 @@
       in_progress_downloads_.push_back(std::move(download));
     }
     StartDownloadWithItem(
-        std::move(stream), url_loader_factory_provider,
+        std::move(stream), std::move(url_loader_factory_provider),
         std::move(cancel_request_callback), std::move(info),
         static_cast<DownloadItemImpl*>(GetDownloadByGuid(guid)), false);
   }
@@ -496,7 +498,8 @@
 
 void InProgressDownloadManager::StartDownloadWithItem(
     std::unique_ptr<InputStream> stream,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider,
     DownloadJob::CancelRequestCallback cancel_request_callback,
     std::unique_ptr<DownloadCreateInfo> info,
     DownloadItemImpl* download,
@@ -540,7 +543,7 @@
   // resumption attempt.
 
   download->Start(std::move(download_file), std::move(cancel_request_callback),
-                  *info, url_loader_factory_provider);
+                  *info, std::move(url_loader_factory_provider));
 
   if (download_start_observer_)
     download_start_observer_->OnDownloadStarted(download);
diff --git a/components/download/internal/common/parallel_download_job.cc b/components/download/internal/common/parallel_download_job.cc
index b1a19868..a2e1e42 100644
--- a/components/download/internal/common/parallel_download_job.cc
+++ b/components/download/internal/common/parallel_download_job.cc
@@ -23,7 +23,8 @@
     DownloadItem* download_item,
     CancelRequestCallback cancel_request_callback,
     const DownloadCreateInfo& create_info,
-    base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+    URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+        url_loader_factory_provider,
     service_manager::Connector* connector)
     : DownloadJobImpl(download_item, std::move(cancel_request_callback), true),
       initial_request_offset_(create_info.offset),
@@ -32,7 +33,7 @@
       requests_sent_(false),
       is_canceled_(false),
       range_support_(create_info.accept_range),
-      url_loader_factory_provider_(url_loader_factory_provider),
+      url_loader_factory_provider_(std::move(url_loader_factory_provider)),
       connector_(connector) {}
 
 ParallelDownloadJob::~ParallelDownloadJob() = default;
@@ -293,8 +294,8 @@
   download_params->set_follow_cross_origin_redirects(false);
 
   // Send the request.
-  worker->SendRequest(std::move(download_params), url_loader_factory_provider_,
-                      connector_);
+  worker->SendRequest(std::move(download_params),
+                      url_loader_factory_provider_.get(), connector_);
   DCHECK(workers_.find(offset) == workers_.end());
   workers_[offset] = std::move(worker);
 }
diff --git a/components/download/internal/common/parallel_download_job.h b/components/download/internal/common/parallel_download_job.h
index ca48feed..e502805 100644
--- a/components/download/internal/common/parallel_download_job.h
+++ b/components/download/internal/common/parallel_download_job.h
@@ -34,12 +34,12 @@
  public:
   // TODO(qinmin): Remove |url_request_context_getter| once network service is
   // enabled.
-  ParallelDownloadJob(
-      DownloadItem* download_item,
-      CancelRequestCallback cancel_request_callback,
-      const DownloadCreateInfo& create_info,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
-      service_manager::Connector* connector);
+  ParallelDownloadJob(DownloadItem* download_item,
+                      CancelRequestCallback cancel_request_callback,
+                      const DownloadCreateInfo& create_info,
+                      URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+                          url_loader_factory_provider,
+                      service_manager::Connector* connector);
   ~ParallelDownloadJob() override;
 
   // DownloadJobImpl implementation.
@@ -120,7 +120,8 @@
 
   // URLLoaderFactoryProvider to retrieve the URLLoaderFactory and issue
   // parallel requests.
-  base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider_;
+  URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+      url_loader_factory_provider_;
 
   // Connector used for establishing the connection to the ServiceManager.
   service_manager::Connector* connector_;
diff --git a/components/download/internal/common/parallel_download_job_unittest.cc b/components/download/internal/common/parallel_download_job_unittest.cc
index ede78f1..7909c2e 100644
--- a/components/download/internal/common/parallel_download_job_unittest.cc
+++ b/components/download/internal/common/parallel_download_job_unittest.cc
@@ -61,7 +61,7 @@
       : ParallelDownloadJob(download_item,
                             std::move(cancel_request_callback),
                             create_info,
-                            nullptr,
+                            URLLoaderFactoryProvider::GetNullPtr(),
                             nullptr),
         request_count_(request_count),
         min_slice_size_(min_slice_size),
diff --git a/components/download/internal/common/resource_downloader.cc b/components/download/internal/common/resource_downloader.cc
index 9cc04c9..cf8829e 100644
--- a/components/download/internal/common/resource_downloader.cc
+++ b/components/download/internal/common/resource_downloader.cc
@@ -8,6 +8,7 @@
 
 #include "base/bind.h"
 #include "components/download/public/common/stream_handle_input_stream.h"
+#include "components/download/public/common/url_loader_factory_provider.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "services/device/public/mojom/constants.mojom.h"
 #include "services/device/public/mojom/wake_lock_provider.mojom.h"
@@ -236,7 +237,10 @@
           &UrlDownloadHandler::Delegate::OnUrlDownloadStarted, delegate_,
           std::move(download_create_info),
           std::make_unique<StreamHandleInputStream>(std::move(stream_handle)),
-          weak_ptr_factory_.GetWeakPtr(), this, callback_));
+          URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr(
+              new URLLoaderFactoryProvider(url_loader_factory_),
+              base::OnTaskRunnerDeleter(base::ThreadTaskRunnerHandle::Get())),
+          this, callback_));
 }
 
 void ResourceDownloader::OnReceiveRedirect() {
@@ -263,11 +267,6 @@
       FROM_HERE, base::BindOnce(upload_callback_, bytes_uploaded));
 }
 
-scoped_refptr<network::SharedURLLoaderFactory>
-ResourceDownloader::GetURLLoaderFactory() {
-  return url_loader_factory_;
-}
-
 void ResourceDownloader::CancelRequest() {
   Destroy();
 }
diff --git a/components/download/internal/common/resource_downloader.h b/components/download/internal/common/resource_downloader.h
index 175f5719..fe7cb243a 100644
--- a/components/download/internal/common/resource_downloader.h
+++ b/components/download/internal/common/resource_downloader.h
@@ -10,7 +10,6 @@
 #include "components/download/public/common/download_response_handler.h"
 #include "components/download/public/common/download_utils.h"
 #include "components/download/public/common/url_download_handler.h"
-#include "components/download/public/common/url_loader_factory_provider.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "net/cert/cert_status_flags.h"
@@ -26,8 +25,7 @@
 // Class for handing the download of a url. Lives on IO thread.
 class COMPONENTS_DOWNLOAD_EXPORT ResourceDownloader
     : public UrlDownloadHandler,
-      public DownloadResponseHandler::Delegate,
-      public URLLoaderFactoryProvider {
+      public DownloadResponseHandler::Delegate {
  public:
   // Called to start a download, must be called on IO thread.
   static std::unique_ptr<ResourceDownloader> BeginDownload(
@@ -90,9 +88,6 @@
   bool CanRequestURL(const GURL& url) override;
   void OnUploadProgress(uint64_t bytes_uploaded) override;
 
-  // URLLoaderFactoryProvider implementation.
-  scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory() override;
-
  private:
   // Helper method to start the network request.
   void Start(
diff --git a/components/download/internal/common/url_loader_factory_provider.cc b/components/download/internal/common/url_loader_factory_provider.cc
index 5277716a..d4c2627 100644
--- a/components/download/internal/common/url_loader_factory_provider.cc
+++ b/components/download/internal/common/url_loader_factory_provider.cc
@@ -4,15 +4,26 @@
 
 #include "components/download/public/common/url_loader_factory_provider.h"
 
+#include "components/download/public/common/download_task_runner.h"
+
 namespace download {
 
-URLLoaderFactoryProvider::URLLoaderFactoryProvider() = default;
+URLLoaderFactoryProvider::URLLoaderFactoryProvider(
+    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
+    : url_loader_factory_(std::move(url_loader_factory)) {}
 
 URLLoaderFactoryProvider::~URLLoaderFactoryProvider() = default;
 
 scoped_refptr<network::SharedURLLoaderFactory>
 URLLoaderFactoryProvider::GetURLLoaderFactory() {
-  return nullptr;
+  return url_loader_factory_;
+}
+
+// static
+URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+URLLoaderFactoryProvider::GetNullPtr() {
+  return URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr(
+      nullptr, base::OnTaskRunnerDeleter(nullptr));
 }
 
 }  // namespace download
diff --git a/components/download/public/common/download_item_impl.h b/components/download/public/common/download_item_impl.h
index c81bcc08..9df91842 100644
--- a/components/download/public/common/download_item_impl.h
+++ b/components/download/public/common/download_item_impl.h
@@ -309,11 +309,11 @@
   // parameters. It may be different from the DownloadCreateInfo used to create
   // the DownloadItem if Start() is being called in response for a
   // download resumption request.
-  virtual void Start(
-      std::unique_ptr<DownloadFile> download_file,
-      DownloadJob::CancelRequestCallback cancel_request_callback,
-      const DownloadCreateInfo& new_create_info,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider);
+  virtual void Start(std::unique_ptr<DownloadFile> download_file,
+                     DownloadJob::CancelRequestCallback cancel_request_callback,
+                     const DownloadCreateInfo& new_create_info,
+                     URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+                         url_loader_factory_provider);
 
   // Needed because of intertwining with DownloadManagerImpl -------------------
 
diff --git a/components/download/public/common/download_job_factory.h b/components/download/public/common/download_job_factory.h
index 9a0457fd..0d51cdaf 100644
--- a/components/download/public/common/download_job_factory.h
+++ b/components/download/public/common/download_job_factory.h
@@ -12,6 +12,7 @@
 #include "components/download/public/common/download_create_info.h"
 #include "components/download/public/common/download_export.h"
 #include "components/download/public/common/download_job.h"
+#include "components/download/public/common/url_loader_factory_provider.h"
 
 namespace service_manager {
 class Connector;
@@ -19,7 +20,6 @@
 
 namespace download {
 class DownloadItem;
-class URLLoaderFactoryProvider;
 
 // Factory class to create different kinds of DownloadJob.
 class COMPONENTS_DOWNLOAD_EXPORT DownloadJobFactory {
@@ -29,7 +29,8 @@
       DownloadJob::CancelRequestCallback cancel_request_callback,
       const DownloadCreateInfo& create_info,
       bool is_save_package_download,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+      URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+          url_loader_factory_provider,
       service_manager::Connector* connector);
 
  private:
diff --git a/components/download/public/common/in_progress_download_manager.h b/components/download/public/common/in_progress_download_manager.h
index 2dc066c..25d2058 100644
--- a/components/download/public/common/in_progress_download_manager.h
+++ b/components/download/public/common/in_progress_download_manager.h
@@ -127,7 +127,8 @@
   void StartDownload(
       std::unique_ptr<DownloadCreateInfo> info,
       std::unique_ptr<InputStream> stream,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+      URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+          url_loader_factory_provider,
       DownloadJob::CancelRequestCallback cancel_request_callback,
       const DownloadUrlParameters::OnStartedCallback& on_started);
 
@@ -211,7 +212,8 @@
   void OnUrlDownloadStarted(
       std::unique_ptr<DownloadCreateInfo> download_create_info,
       std::unique_ptr<InputStream> input_stream,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+      URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+          url_loader_factory_provider,
       UrlDownloadHandler* downloader,
       const DownloadUrlParameters::OnStartedCallback& callback) override;
   void OnUrlDownloadStopped(UrlDownloadHandler* downloader) override;
@@ -230,7 +232,8 @@
   // Start a DownloadItemImpl.
   void StartDownloadWithItem(
       std::unique_ptr<InputStream> stream,
-      base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+      URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+          url_loader_factory_provider,
       DownloadJob::CancelRequestCallback cancel_request_callback,
       std::unique_ptr<DownloadCreateInfo> info,
       DownloadItemImpl* download,
diff --git a/components/download/public/common/mock_download_item_impl.h b/components/download/public/common/mock_download_item_impl.h
index ac239342..8da0c0f 100644
--- a/components/download/public/common/mock_download_item_impl.h
+++ b/components/download/public/common/mock_download_item_impl.h
@@ -53,7 +53,7 @@
   void Start(std::unique_ptr<DownloadFile> download_file,
              DownloadJob::CancelRequestCallback cancel_request_callback,
              const DownloadCreateInfo& create_info,
-             base::WeakPtr<URLLoaderFactoryProvider>
+             URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
                  url_loader_factory_provider) override {
     MockStart(download_file.get());
   }
diff --git a/components/download/public/common/url_download_handler.h b/components/download/public/common/url_download_handler.h
index 5e19283..c2d5149 100644
--- a/components/download/public/common/url_download_handler.h
+++ b/components/download/public/common/url_download_handler.h
@@ -25,7 +25,8 @@
     virtual void OnUrlDownloadStarted(
         std::unique_ptr<DownloadCreateInfo> download_create_info,
         std::unique_ptr<InputStream> input_stream,
-        base::WeakPtr<URLLoaderFactoryProvider> url_loader_factory_provider,
+        URLLoaderFactoryProvider::URLLoaderFactoryProviderPtr
+            url_loader_factory_provider,
         UrlDownloadHandler* downloader,
         const DownloadUrlParameters::OnStartedCallback& callback) = 0;
 
diff --git a/components/download/public/common/url_loader_factory_provider.h b/components/download/public/common/url_loader_factory_provider.h
index 356091b6..ae04d8f4 100644
--- a/components/download/public/common/url_loader_factory_provider.h
+++ b/components/download/public/common/url_loader_factory_provider.h
@@ -5,22 +5,33 @@
 #ifndef COMPONENTS_DOWNLOAD_PUBLIC_COMMON_URL_LOADER_FACTORY_PROVIDER_H_
 #define COMPONENTS_DOWNLOAD_PUBLIC_COMMON_URL_LOADER_FACTORY_PROVIDER_H_
 
+#include "base/sequenced_task_runner.h"
 #include "components/download/public/common/download_export.h"
 #include "services/network/public/cpp/shared_url_loader_factory.h"
 
 namespace download {
 
-// Interface for providing a SharedURLLoaderFactory on IO thread that can be
-// used to create parallel download requests.
+// Class for wrapping a SharedURLLoaderFactory that can be passed across thread
+// so that it can be used later on IO thread to retrieve the factory to create
+// parallel download requests.
 class COMPONENTS_DOWNLOAD_EXPORT URLLoaderFactoryProvider {
  public:
-  URLLoaderFactoryProvider();
+  using URLLoaderFactoryProviderPtr =
+      std::unique_ptr<URLLoaderFactoryProvider, base::OnTaskRunnerDeleter>;
+
+  explicit URLLoaderFactoryProvider(
+      scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory);
   virtual ~URLLoaderFactoryProvider();
 
   // Called on the io thread to get the URL loader.
-  virtual scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory();
+  scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory();
+
+  // Helper method to get an null ptr.
+  static URLLoaderFactoryProviderPtr GetNullPtr();
 
  private:
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+
   DISALLOW_COPY_AND_ASSIGN(URLLoaderFactoryProvider);
 };
 
diff --git a/components/drive/drive_uploader_unittest.cc b/components/drive/drive_uploader_unittest.cc
index ce7a2fc..48cbba7 100644
--- a/components/drive/drive_uploader_unittest.cc
+++ b/components/drive/drive_uploader_unittest.cc
@@ -420,7 +420,7 @@
   void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
 
  protected:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::ScopedTempDir temp_dir_;
 };
 
diff --git a/components/drive/local_file_reader_unittest.cc b/components/drive/local_file_reader_unittest.cc
index b5d258f..e3028811 100644
--- a/components/drive/local_file_reader_unittest.cc
+++ b/components/drive/local_file_reader_unittest.cc
@@ -56,7 +56,7 @@
         std::make_unique<LocalFileReader>(worker_thread_->task_runner().get());
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::ScopedTempDir temp_dir_;
   std::unique_ptr<base::Thread> worker_thread_;
   std::unique_ptr<LocalFileReader> file_reader_;
diff --git a/components/drive/service/fake_drive_service_unittest.cc b/components/drive/service/fake_drive_service_unittest.cc
index 1011925..acfaae7 100644
--- a/components/drive/service/fake_drive_service_unittest.cc
+++ b/components/drive/service/fake_drive_service_unittest.cc
@@ -227,7 +227,7 @@
     return about_resource->largest_change_id();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   FakeDriveService fake_service_;
 };
 
diff --git a/components/ntp_tiles/most_visited_sites_unittest.cc b/components/ntp_tiles/most_visited_sites_unittest.cc
index 2c318f5..f5659ea5 100644
--- a/components/ntp_tiles/most_visited_sites_unittest.cc
+++ b/components/ntp_tiles/most_visited_sites_unittest.cc
@@ -559,7 +559,7 @@
       suggestions_service_callbacks_;
   TopSitesCallbackList top_sites_callbacks_;
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   data_decoder::TestingJsonParser::ScopedFactoryOverride factory_override_;
   sync_preferences::TestingPrefServiceSyncable pref_service_;
   PopularSitesFactoryForTest popular_sites_factory_;
diff --git a/components/ntp_tiles/popular_sites_impl_unittest.cc b/components/ntp_tiles/popular_sites_impl_unittest.cc
index ce0748b5..b6f1aa02 100644
--- a/components/ntp_tiles/popular_sites_impl_unittest.cc
+++ b/components/ntp_tiles/popular_sites_impl_unittest.cc
@@ -212,8 +212,8 @@
   const TestPopularSite kYouTube;
   const TestPopularSite kChromium;
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::UI};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::UI};
   data_decoder::TestingJsonParser::ScopedFactoryOverride factory_override_;
   std::unique_ptr<sync_preferences::TestingPrefServiceSyncable> prefs_;
   network::TestURLLoaderFactory test_url_loader_factory_;
diff --git a/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc b/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc
index 83dd964..7b0df36 100644
--- a/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc
+++ b/components/password_manager/core/browser/credential_manager_password_form_manager_unittest.cc
@@ -97,7 +97,7 @@
   }
 
   // Necessary for callbacks, and for TestAutofillDriver.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   StubPasswordManagerClient client_;
   MockDelegate delegate_;
diff --git a/components/password_manager/core/browser/form_saver_impl_unittest.cc b/components/password_manager/core/browser/form_saver_impl_unittest.cc
index 5188fbba..bde146b 100644
--- a/components/password_manager/core/browser/form_saver_impl_unittest.cc
+++ b/components/password_manager/core/browser/form_saver_impl_unittest.cc
@@ -77,7 +77,7 @@
 
  protected:
   // For the MockPasswordStore.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   scoped_refptr<StrictMock<MockPasswordStore>> mock_store_;
   FormSaverImpl form_saver_;
 
diff --git a/components/password_manager/core/browser/hsts_query_unittest.cc b/components/password_manager/core/browser/hsts_query_unittest.cc
index 3bcbbda..2833a64 100644
--- a/components/password_manager/core/browser/hsts_query_unittest.cc
+++ b/components/password_manager/core/browser/hsts_query_unittest.cc
@@ -71,7 +71,7 @@
 
  private:
   // Used by request_context_.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   scoped_refptr<net::TestURLRequestContextGetter> request_context_;
   network::mojom::NetworkContextPtr network_context_pipe_;
   std::unique_ptr<network::NetworkContext> network_context_;
diff --git a/components/password_manager/core/browser/password_autofill_manager_unittest.cc b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
index b3a76599..15f27f81 100644
--- a/components/password_manager/core/browser/password_autofill_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_autofill_manager_unittest.cc
@@ -194,7 +194,7 @@
 
   // The TestAutofillDriver uses a SequencedWorkerPool which expects the
   // existence of a MessageLoop.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 TEST_F(PasswordAutofillManagerTest, FillSuggestion) {
diff --git a/components/password_manager/core/browser/password_manager_unittest.cc b/components/password_manager/core/browser/password_manager_unittest.cc
index e098b9a..78222648 100644
--- a/components/password_manager/core/browser/password_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_manager_unittest.cc
@@ -488,7 +488,7 @@
   }
 
   const GURL test_url_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   scoped_refptr<MockPasswordStore> store_;
   testing::NiceMock<MockPasswordManagerClient> client_;
   MockPasswordManagerDriver driver_;
diff --git a/components/password_manager/core/browser/password_reuse_detection_manager_unittest.cc b/components/password_manager/core/browser/password_reuse_detection_manager_unittest.cc
index fdd5b86f..7ab66b78 100644
--- a/components/password_manager/core/browser/password_reuse_detection_manager_unittest.cc
+++ b/components/password_manager/core/browser/password_reuse_detection_manager_unittest.cc
@@ -52,7 +52,7 @@
  protected:
   // It's needed for an initialisation of thread runners that are used in
   // MockPasswordStore.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   MockPasswordManagerClient client_;
   scoped_refptr<MockPasswordStore> store_;
 
diff --git a/components/password_manager/core/browser/password_store_default_unittest.cc b/components/password_manager/core/browser/password_store_default_unittest.cc
index 040adb4e..65952c4f 100644
--- a/components/password_manager/core/browser/password_store_default_unittest.cc
+++ b/components/password_manager/core/browser/password_store_default_unittest.cc
@@ -97,15 +97,14 @@
 
   base::FilePath test_login_db_file_path() const;
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::TaskEnvironment task_environment_{base::test::TaskEnvironment::MainThreadType::UI};
   base::ScopedTempDir temp_dir_;
   scoped_refptr<PasswordStoreDefault> store_;
 
   DISALLOW_COPY_AND_ASSIGN(PasswordStoreDefaultTestDelegate);
 };
 
-PasswordStoreDefaultTestDelegate::PasswordStoreDefaultTestDelegate()
-    : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {
+PasswordStoreDefaultTestDelegate::PasswordStoreDefaultTestDelegate() {
   OSCryptMocker::SetUp();
   SetupTempDir();
   store_ = CreateInitializedStore(std::make_unique<LoginDatabase>(
@@ -113,8 +112,7 @@
 }
 
 PasswordStoreDefaultTestDelegate::PasswordStoreDefaultTestDelegate(
-    std::unique_ptr<LoginDatabase> database)
-    : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {
+    std::unique_ptr<LoginDatabase> database) {
   OSCryptMocker::SetUp();
   SetupTempDir();
   store_ = CreateInitializedStore(std::move(database));
diff --git a/components/password_manager/core/browser/password_store_unittest.cc b/components/password_manager/core/browser/password_store_unittest.cc
index 55b1a61..ada99916 100644
--- a/components/password_manager/core/browser/password_store_unittest.cc
+++ b/components/password_manager/core/browser/password_store_unittest.cc
@@ -108,8 +108,7 @@
 
 class PasswordStoreTest : public testing::Test {
  protected:
-  PasswordStoreTest()
-      : task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}
+  PasswordStoreTest() = default;
 
   void SetUp() override {
     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
@@ -135,7 +134,7 @@
 
  private:
   base::ScopedTempDir temp_dir_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::TaskEnvironment task_environment_{base::test::TaskEnvironment::MainThreadType::UI};
 
   DISALLOW_COPY_AND_ASSIGN(PasswordStoreTest);
 };
diff --git a/components/payments/content/payment_request.cc b/components/payments/content/payment_request.cc
index 331be864..72bab9b 100644
--- a/components/payments/content/payment_request.cc
+++ b/components/payments/content/payment_request.cc
@@ -264,6 +264,8 @@
   }
 
   display_handle_->Show(this);
+
+  state_->set_is_show_user_gesture(is_show_user_gesture_);
   state_->AreRequestedMethodsSupported(
       base::BindOnce(&PaymentRequest::AreRequestedMethodsSupportedCallback,
                      weak_ptr_factory_.GetWeakPtr()));
diff --git a/components/payments/content/payment_request.h b/components/payments/content/payment_request.h
index c59f187..aaae0a6 100644
--- a/components/payments/content/payment_request.h
+++ b/components/payments/content/payment_request.h
@@ -125,6 +125,7 @@
   content::WebContents* web_contents() { return web_contents_; }
 
   bool skipped_payment_request_ui() { return skipped_payment_request_ui_; }
+  bool is_show_user_gesture() const { return is_show_user_gesture_; }
 
   PaymentRequestSpec* spec() { return spec_.get(); }
   PaymentRequestState* state() { return state_.get(); }
diff --git a/components/payments/content/payment_request_state.cc b/components/payments/content/payment_request_state.cc
index c12071d..9843380b 100644
--- a/components/payments/content/payment_request_state.cc
+++ b/components/payments/content/payment_request_state.cc
@@ -24,6 +24,7 @@
 #include "components/payments/content/service_worker_payment_instrument.h"
 #include "components/payments/core/autofill_card_validation.h"
 #include "components/payments/core/autofill_payment_instrument.h"
+#include "components/payments/core/error_strings.h"
 #include "components/payments/core/features.h"
 #include "components/payments/core/payment_instrument.h"
 #include "components/payments/core/payment_request_data_util.h"
@@ -31,6 +32,33 @@
 #include "content/public/common/content_features.h"
 
 namespace payments {
+namespace {
+
+// Checks whether any of the |instruments| return true in
+// IsValidForCanMakePayment().
+bool GetHasEnrolledInstrument(
+    const std::vector<std::unique_ptr<PaymentInstrument>>& instruments) {
+  return std::any_of(instruments.begin(), instruments.end(),
+                     [](const auto& instrument) {
+                       return instrument->IsValidForCanMakePayment();
+                     });
+}
+
+// Invokes the |callback| with |status|.
+void CallStatusCallback(PaymentRequestState::StatusCallback callback,
+                        bool status) {
+  std::move(callback).Run(status);
+}
+
+// Posts the |callback| to be invoked with |status| asynchronously.
+void PostStatusCallback(PaymentRequestState::StatusCallback callback,
+                        bool status) {
+  base::ThreadTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce(&CallStatusCallback, std::move(callback), status));
+}
+
+}  // namespace
 
 PaymentRequestState::PaymentRequestState(
     content::WebContents* web_contents,
@@ -45,7 +73,6 @@
         sw_identity_observer,
     JourneyLogger* journey_logger)
     : is_ready_to_pay_(false),
-      get_all_instruments_finished_(true),
       is_waiting_for_merchant_validation_(false),
       app_locale_(app_locale),
       spec_(spec),
@@ -67,7 +94,6 @@
   DCHECK(sw_identity_observer_);
   if (base::FeatureList::IsEnabled(::features::kServiceWorkerPaymentApps)) {
     DCHECK(web_contents);
-    get_all_instruments_finished_ = false;
     bool may_crawl_for_installable_payment_apps =
         PaymentsExperimentalFeatures::IsEnabled(
             features::kAlwaysAllowJustInTimePaymentApp) ||
@@ -87,6 +113,8 @@
   } else {
     PopulateProfileCache();
     SetDefaultProfileSelections();
+    get_all_instruments_finished_ = true;
+    has_enrolled_instrument_ = GetHasEnrolledInstrument(available_instruments_);
   }
   spec_->AddObserver(this);
 }
@@ -136,6 +164,8 @@
 void PaymentRequestState::OnSWPaymentInstrumentValidated(
     ServiceWorkerPaymentInstrument* instrument,
     bool result) {
+  has_non_autofill_instrument_ |= result;
+
   // Remove service worker payment instruments failed on validation.
   if (!result) {
     for (size_t i = 0; i < available_instruments_.size(); i++) {
@@ -157,18 +187,18 @@
   SetDefaultProfileSelections();
 
   get_all_instruments_finished_ = true;
+  has_enrolled_instrument_ = GetHasEnrolledInstrument(available_instruments_);
   are_requested_methods_supported_ |= !available_instruments_.empty();
   NotifyOnGetAllPaymentInstrumentsFinished();
   NotifyInitialized();
 
   // Fulfill the pending CanMakePayment call.
-  if (can_make_payment_callback_) {
-    CheckCanMakePayment(std::move(can_make_payment_callback_));
-  }
+  if (can_make_payment_callback_)
+    std::move(can_make_payment_callback_).Run(are_requested_methods_supported_);
 
   // Fulfill the pending HasEnrolledInstrument call.
   if (has_enrolled_instrument_callback_)
-    CheckHasEnrolledInstrument(std::move(has_enrolled_instrument_callback_));
+    std::move(has_enrolled_instrument_callback_).Run(has_enrolled_instrument_);
 
   // Fulfill the pending AreRequestedMethodsSupported call.
   if (are_requested_methods_supported_callback_)
@@ -226,15 +256,7 @@
     return;
   }
 
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&PaymentRequestState::CheckCanMakePayment,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void PaymentRequestState::CheckCanMakePayment(StatusCallback callback) {
-  DCHECK(get_all_instruments_finished_);
-  std::move(callback).Run(are_requested_methods_supported_);
+  PostStatusCallback(std::move(callback), are_requested_methods_supported_);
 }
 
 void PaymentRequestState::HasEnrolledInstrument(StatusCallback callback) {
@@ -244,22 +266,7 @@
     return;
   }
 
-  base::ThreadTaskRunnerHandle::Get()->PostTask(
-      FROM_HERE,
-      base::BindOnce(&PaymentRequestState::CheckHasEnrolledInstrument,
-                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
-}
-
-void PaymentRequestState::CheckHasEnrolledInstrument(StatusCallback callback) {
-  DCHECK(get_all_instruments_finished_);
-  bool has_enrolled_instrument_value = false;
-  for (const auto& instrument : available_instruments_) {
-    if (instrument->IsValidForCanMakePayment()) {
-      has_enrolled_instrument_value = true;
-      break;
-    }
-  }
-  std::move(callback).Run(has_enrolled_instrument_value);
+  PostStatusCallback(std::move(callback), has_enrolled_instrument_);
 }
 
 void PaymentRequestState::AreRequestedMethodsSupported(
@@ -279,8 +286,19 @@
     MethodsSupportedCallback callback) {
   DCHECK(get_all_instruments_finished_);
 
-  std::move(callback).Run(are_requested_methods_supported_,
-                          get_all_payment_apps_error_);
+  // Don't modify the value of |are_requested_methods_supported_|, because it's
+  // used for canMakePayment().
+  bool supported = are_requested_methods_supported_;
+  if (supported && is_show_user_gesture_ &&
+      base::Contains(spec_->payment_method_identifiers_set(), "basic-card") &&
+      !has_non_autofill_instrument_ && !has_enrolled_instrument_ &&
+      PaymentsExperimentalFeatures::IsEnabled(
+          features::kStrictHasEnrolledAutofillInstrument)) {
+    supported = false;
+    get_all_payment_apps_error_ = errors::kStrictBasicCardShowReject;
+  }
+
+  std::move(callback).Run(supported, get_all_payment_apps_error_);
 }
 
 std::string PaymentRequestState::GetAuthenticatedEmail() const {
diff --git a/components/payments/content/payment_request_state.h b/components/payments/content/payment_request_state.h
index e176d35f..2fd3518 100644
--- a/components/payments/content/payment_request_state.h
+++ b/components/payments/content/payment_request_state.h
@@ -266,6 +266,10 @@
 
   base::WeakPtr<PaymentRequestState> AsWeakPtr();
 
+  void set_is_show_user_gesture(bool is_show_user_gesture) {
+    is_show_user_gesture_ = is_show_user_gesture;
+  }
+
  private:
   // Fetches the Autofill Profiles for this user from the PersonalDataManager,
   // and stores copies of them, owned by this PaymentRequestState, in
@@ -310,15 +314,6 @@
       bool result);
   void FinishedGetAllSWPaymentInstruments();
 
-  // Checks whether support for the specified payment methods exists and call
-  // the |callback| to return the result.
-  void CheckCanMakePayment(StatusCallback callback);
-
-  // Checks whether the user has at least one instrument that satisfies the
-  // specified supported payment methods and call the |callback| to return the
-  // result.
-  void CheckHasEnrolledInstrument(StatusCallback callback);
-
   // Checks if the payment methods that the merchant website have
   // requested are supported and call the |callback| to return the result.
   void CheckRequestedMethodsSupported(MethodsSupportedCallback callback);
@@ -339,7 +334,16 @@
   // not affected by payment instruments.
   bool is_requested_autofill_data_available_ = true;
 
-  bool get_all_instruments_finished_;
+  // Whether getting all available instruments is finished.
+  bool get_all_instruments_finished_ = false;
+
+  // The value returned by hasEnrolledInstrument(). Can be used only after
+  // |get_all_instruments_finished_| is true.
+  bool has_enrolled_instrument_ = false;
+
+  // Whether there's at least one instrument that is not autofill. Can be used
+  // only after |get_all_instruments_finished_| is true.
+  bool has_non_autofill_instrument_ = false;
 
   // Whether the data is currently being validated by the merchant.
   bool is_waiting_for_merchant_validation_;
@@ -389,6 +393,9 @@
 
   base::ObserverList<Observer>::Unchecked observers_;
 
+  // Whether PaymentRequest.show() was invoked with a user gesture.
+  bool is_show_user_gesture_ = false;
+
   base::WeakPtrFactory<PaymentRequestState> weak_ptr_factory_{this};
 
   DISALLOW_COPY_AND_ASSIGN(PaymentRequestState);
diff --git a/components/payments/core/can_make_payment_query_unittest.cc b/components/payments/core/can_make_payment_query_unittest.cc
index 4b00986..87a72b90 100644
--- a/components/payments/core/can_make_payment_query_unittest.cc
+++ b/components/payments/core/can_make_payment_query_unittest.cc
@@ -16,7 +16,7 @@
   CanMakePaymentQuery guard_;
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 // An HTTPS website is not allowed to query all of the networks of the cards in
diff --git a/components/payments/core/error_strings.cc b/components/payments/core/error_strings.cc
index 70b9b5d..a411d7ad 100644
--- a/components/payments/core/error_strings.cc
+++ b/components/payments/core/error_strings.cc
@@ -22,6 +22,7 @@
 const char kNotInASecureOrigin[] = "Not in a secure origin.";
 const char kProhibitedOrigin[] = "Only localhost, file://, and cryptographic scheme origins allowed.";
 const char kProhibitedOriginOrInvalidSslExplanation[] = "No UI will be shown. CanMakePayment and hasEnrolledInstrument will always return false. Show will be rejected with NotSupportedError.";
+const char kStrictBasicCardShowReject[] = "User does not have valid information on file.";
 const char kTotalRequired[] = "Total required.";
 const char kUserCancelled[] = "User closed the Payment Request UI.";
 
diff --git a/components/payments/core/error_strings.h b/components/payments/core/error_strings.h
index 33293dd3..ced992e 100644
--- a/components/payments/core/error_strings.h
+++ b/components/payments/core/error_strings.h
@@ -51,6 +51,10 @@
 // or kInvalidSslCertificate error.
 extern const char kProhibitedOriginOrInvalidSslExplanation[];
 
+// Used when rejecting show() with NotSupportedError, because the user did not
+// have all valid autofill data.
+extern const char kStrictBasicCardShowReject[];
+
 // Used when "total": {"label": "Total", "amount": {"currency": "USD", "value":
 // "0.01"}} is required, bot not provided.
 extern const char kTotalRequired[];
diff --git a/components/pdf/renderer/pdf_accessibility_tree.cc b/components/pdf/renderer/pdf_accessibility_tree.cc
index c4e1474..ea4c049 100644
--- a/components/pdf/renderer/pdf_accessibility_tree.cc
+++ b/components/pdf/renderer/pdf_accessibility_tree.cc
@@ -151,6 +151,14 @@
   *static_text_node = nullptr;
 }
 
+void ConnectPreviousAndNextOnLine(ui::AXNodeData* previous_on_line_node,
+                                  ui::AXNodeData* next_on_line_node) {
+  previous_on_line_node->AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
+                                         next_on_line_node->id);
+  next_on_line_node->AddIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId,
+                                     previous_on_line_node->id);
+}
+
 bool BreakParagraph(
     const std::vector<PP_PrivateAccessibilityTextRunInfo>& text_runs,
     uint32_t text_run_index,
@@ -429,11 +437,8 @@
           inline_text_box_node->relative_bounds.bounds);
 
       if (previous_on_line_node) {
-        previous_on_line_node->AddIntAttribute(
-            ax::mojom::IntAttribute::kNextOnLineId, inline_text_box_node->id);
-        inline_text_box_node->AddIntAttribute(
-            ax::mojom::IntAttribute::kPreviousOnLineId,
-            previous_on_line_node->id);
+        ConnectPreviousAndNextOnLine(previous_on_line_node,
+                                     inline_text_box_node);
       } else {
         line_helper.StartNewLine(text_run_index);
       }
@@ -451,23 +456,15 @@
     }
 
     if (text_run_index == text_runs.size() - 1) {
-      if (static_text_node) {
-        static_text_node->AddStringAttribute(ax::mojom::StringAttribute::kName,
-                                             static_text);
-      }
+      FinishStaticNode(&static_text_node, &static_text);
       break;
     }
 
     if (!previous_on_line_node) {
       if (BreakParagraph(text_runs, text_run_index,
                          paragraph_spacing_threshold)) {
-        if (static_text_node) {
-          static_text_node->AddStringAttribute(
-              ax::mojom::StringAttribute::kName, static_text);
-        }
+        FinishStaticNode(&static_text_node, &static_text);
         para_node = nullptr;
-        static_text_node = nullptr;
-        static_text.clear();
       }
     }
   }
@@ -774,12 +771,8 @@
     *char_index += text_run.len;
 
     if (*previous_on_line_node) {
-      (*previous_on_line_node)
-          ->AddIntAttribute(ax::mojom::IntAttribute::kNextOnLineId,
-                            inline_text_box_node->id);
-      inline_text_box_node->AddIntAttribute(
-          ax::mojom::IntAttribute::kPreviousOnLineId,
-          (*previous_on_line_node)->id);
+      ConnectPreviousAndNextOnLine(*previous_on_line_node,
+                                   inline_text_box_node);
     } else {
       line_helper.StartNewLine(text_run_index);
     }
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 0ab6eb13..bae7c15 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -17070,7 +17070,8 @@
         },
       },
       'id': 572,
-      'supported_on': ['chrome_os:78-'],
+      'supported_on': ['chrome_os:79-'],
+      'future': True,
       'features': {
         'dynamic_refresh': True,
         'per_profile': True,
diff --git a/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc b/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
index b4bc739..cc7c9710 100644
--- a/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
+++ b/components/proxy_config/pref_proxy_config_tracker_impl_unittest.cc
@@ -104,7 +104,7 @@
     proxy_config_service_.reset();
   }
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<TestingPrefServiceSimple> pref_service_;
   TestProxyConfigService* delegate_service_; // weak
   std::unique_ptr<net::ProxyConfigService> proxy_config_service_;
diff --git a/components/startup_metric_utils/browser/BUILD.gn b/components/startup_metric_utils/browser/BUILD.gn
index eb19975..c975663 100644
--- a/components/startup_metric_utils/browser/BUILD.gn
+++ b/components/startup_metric_utils/browser/BUILD.gn
@@ -17,8 +17,6 @@
 
 static_library("lib") {
   sources = [
-    "pref_names.cc",
-    "pref_names.h",
     "startup_metric_utils.cc",
     "startup_metric_utils.h",
   ]
diff --git a/components/startup_metric_utils/browser/DEPS b/components/startup_metric_utils/browser/DEPS
index 29510d99..dba3d91d 100644
--- a/components/startup_metric_utils/browser/DEPS
+++ b/components/startup_metric_utils/browser/DEPS
@@ -1,6 +1,5 @@
 include_rules = [
   "+components/metrics",
-  "+components/prefs",
   "+components/version_info",
   "+content/public/browser",
   "+mojo/public/cpp/bindings",
diff --git a/components/startup_metric_utils/browser/pref_names.cc b/components/startup_metric_utils/browser/pref_names.cc
deleted file mode 100644
index 906f4fd..0000000
--- a/components/startup_metric_utils/browser/pref_names.cc
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "components/startup_metric_utils/browser/pref_names.h"
-
-namespace startup_metric_utils {
-namespace prefs {
-
-// Version of the product in the startup preceding this one as reported by
-// version_info.h.
-const char kLastStartupVersion[] = "startup_metric.last_startup_version";
-
-// Number of startups previously seen with kLastStartupVersion.
-const char kSameVersionStartupCount[] =
-    "startup_metric.same_version_startup_count";
-
-}  // namespace prefs
-}  // namespace startup_metric_utils
diff --git a/components/startup_metric_utils/browser/pref_names.h b/components/startup_metric_utils/browser/pref_names.h
deleted file mode 100644
index cc804161..0000000
--- a/components/startup_metric_utils/browser/pref_names.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2016 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef COMPONENTS_STARTUP_METRIC_UTILS_BROWSER_PREF_NAMES_H_
-#define COMPONENTS_STARTUP_METRIC_UTILS_BROWSER_PREF_NAMES_H_
-
-namespace startup_metric_utils {
-namespace prefs {
-
-// Alphabetical list of preference names specific to the startup_metric_utils
-// component. Keep alphabetized, and document each in the .cc file.
-
-extern const char kLastStartupVersion[];
-extern const char kSameVersionStartupCount[];
-
-}  // namespace prefs
-}  // namespace startup_metric_utils
-
-#endif  // COMPONENTS_STARTUP_METRIC_UTILS_BROWSER_PREF_NAMES_H_
diff --git a/components/startup_metric_utils/browser/startup_metric_utils.cc b/components/startup_metric_utils/browser/startup_metric_utils.cc
index c1bc9d5..f4541e5 100644
--- a/components/startup_metric_utils/browser/startup_metric_utils.cc
+++ b/components/startup_metric_utils/browser/startup_metric_utils.cc
@@ -21,9 +21,6 @@
 #include "base/threading/platform_thread.h"
 #include "base/trace_event/trace_event.h"
 #include "build/build_config.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
-#include "components/startup_metric_utils/browser/pref_names.h"
 #include "components/version_info/version_info.h"
 
 #if defined(OS_WIN)
@@ -32,17 +29,8 @@
 #include "base/win/win_util.h"
 #endif
 
-// Data from deprecated UMA histograms:
-//
-// Startup.TimeSinceLastStartup.[Cold/Warm]Startup, August 2019, Windows-only:
-//   Time elapsed since the last startup that went up to the main message loop
-//   start. This is recorded just before the main message loop starts.
-//
-//                      Cold startup   Warm startup
-//   25th percentile    3.5 hours      4 minutes
-//   50th percentile    14.5 hours     21 minutes
-//   75th percentile    27 hours       90 minutes
-//   95th percentile    13 days        17 hours
+// Data from deprecated UMA histograms available at
+// https://docs.google.com/document/d/18uYnVwLly7C_ckGsDbqdNs-AgAAt3AmUmn7wYLkyBN0/edit?usp=sharing
 
 namespace startup_metric_utils {
 
@@ -87,9 +75,6 @@
 
 StartupTemperature g_startup_temperature = UNDETERMINED_STARTUP_TEMPERATURE;
 
-constexpr int kUndeterminedStartupsWithCurrentVersion = 0;
-int g_startups_with_current_version = kUndeterminedStartupsWithCurrentVersion;
-
 #if defined(OS_WIN)
 
 // These values are taken from the Startup.BrowserMessageLoopStartHardFaultCount
@@ -235,33 +220,6 @@
     }                                                                         \
   } while (0)
 
-// Records |value_expr| to the histogram with name |basename| suffixed with the
-// number of startups with the current version in addition to all histograms
-// recorded by UMA_HISTOGRAM_WITH_TEMPERATURE.
-// A metric logged using this macro must have affected-histogram entries in the
-// definition of the StartupTemperature and SameVersionStartupCounts suffixes in
-// histograms.xml.
-// This macro must only be used in code that runs after |g_startup_temperature|
-// and |g_startups_with_current_version| have been initialized.
-#define UMA_HISTOGRAM_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(type, basename, \
-                                                              value_expr)     \
-  do {                                                                        \
-    const auto value_same_version_count = value_expr;                         \
-    /* Record to the base histogram and to a histogram suffixed with the      \
-       startup temperature. */                                                \
-    UMA_HISTOGRAM_WITH_TEMPERATURE(type, basename, value_same_version_count); \
-    /* Record to a histogram suffixed with the number of startups for the     \
-       current version. Since the number of startups for the current version  \
-       is set once per process, using a histogram macro which expects a       \
-       constant histogram name across invocations is fine. */                 \
-    const auto same_version_startup_count_suffix =                            \
-        GetSameVersionStartupCountSuffix();                                   \
-    if (!same_version_startup_count_suffix.empty()) {                         \
-      type(basename + same_version_startup_count_suffix,                      \
-           value_same_version_count);                                         \
-    }                                                                         \
-  } while (0)
-
 #define UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(type, basename, begin_ticks, \
                                                  end_ticks)                   \
   do {                                                                        \
@@ -274,40 +232,6 @@
         g_startup_temperature);                                               \
   } while (0)
 
-#define UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(      \
-    type, basename, begin_ticks, end_ticks)                                   \
-  do {                                                                        \
-    UMA_HISTOGRAM_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(                    \
-        type, basename, end_ticks - begin_ticks);                             \
-    TRACE_EVENT_ASYNC_BEGIN_WITH_TIMESTAMP2(                                  \
-        "startup", basename, 0, begin_ticks, "Temperature",                   \
-        g_startup_temperature, "Startups with current version",               \
-        g_startups_with_current_version);                                     \
-    TRACE_EVENT_ASYNC_END_WITH_TIMESTAMP2(                                    \
-        "startup", basename, 0, end_ticks, "Temperature",                     \
-        g_startup_temperature, "Startups with current version",               \
-        g_startups_with_current_version);                                     \
-  } while (0)
-
-std::string GetSameVersionStartupCountSuffix() {
-  // TODO(fdoray): Remove this once crbug.com/580207 is fixed.
-  if (g_startups_with_current_version ==
-      kUndeterminedStartupsWithCurrentVersion) {
-    return std::string();
-  }
-
-  // The suffix is |g_startups_with_current_version| up to
-  // |kMaxSameVersionCountRecorded|. Higher counts are grouped in the ".Over"
-  // suffix. Make sure to reflect changes to |kMaxSameVersionCountRecorded| in
-  // the "SameVersionStartupCounts" histogram suffix.
-  constexpr int kMaxSameVersionCountRecorded = 9;
-  DCHECK_GE(g_startups_with_current_version, 1);
-  if (g_startups_with_current_version > kMaxSameVersionCountRecorded)
-    return ".Over";
-  return std::string(".") +
-         base::NumberToString(g_startups_with_current_version);
-}
-
 // Returns the system uptime on process launch.
 base::TimeDelta GetSystemUptimeOnProcessLaunch() {
   // Process launch time is not available on Android.
@@ -327,9 +251,9 @@
   if (system_uptime_on_process_launch.is_zero())
     return;
 
-  UMA_HISTOGRAM_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
-      UMA_HISTOGRAM_LONG_TIMES_100, "Startup.SystemUptime",
-      GetSystemUptimeOnProcessLaunch());
+  UMA_HISTOGRAM_WITH_TEMPERATURE(UMA_HISTOGRAM_LONG_TIMES_100,
+                                 "Startup.SystemUptime",
+                                 GetSystemUptimeOnProcessLaunch());
 }
 
 // On Windows, records the number of hard-faults that have occurred in the
@@ -343,26 +267,12 @@
   if (!GetHardFaultCountForCurrentProcess(&hard_fault_count))
     return;
 
-  const std::string same_version_startup_count_suffix(
-      GetSameVersionStartupCountSuffix());
-
   // Hard fault counts are expected to be in the thousands range,
   // corresponding to faulting in ~10s of MBs of code ~10s of KBs at a time.
   // (Observed to vary from 1000 to 10000 on various test machines and
   // platforms.)
-  const char kHardFaultCountHistogram[] =
-      "Startup.BrowserMessageLoopStartHardFaultCount";
-  UMA_HISTOGRAM_CUSTOM_COUNTS(kHardFaultCountHistogram, hard_fault_count, 1,
-                              40000, 50);
-  // Also record the hard fault count histogram suffixed by the number of
-  // startups this specific version has been through.
-  // Factory properties copied from UMA_HISTOGRAM_CUSTOM_COUNTS macro.
-  if (!same_version_startup_count_suffix.empty()) {
-    base::Histogram::FactoryGet(
-        kHardFaultCountHistogram + same_version_startup_count_suffix, 1, 40000,
-        50, base::HistogramBase::kUmaTargetedHistogramFlag)
-        ->Add(hard_fault_count);
-  }
+  UMA_HISTOGRAM_CUSTOM_COUNTS("Startup.BrowserMessageLoopStartHardFaultCount",
+                              hard_fault_count, 1, 40000, 50);
 
   // Determine the startup type based on the number of observed hard faults.
   DCHECK_EQ(UNDETERMINED_STARTUP_TEMPERATURE, g_startup_temperature);
@@ -375,18 +285,8 @@
   }
 
   // Record the startup 'temperature'.
-  const char kStartupTemperatureHistogram[] = "Startup.Temperature";
-  UMA_HISTOGRAM_ENUMERATION(kStartupTemperatureHistogram, g_startup_temperature,
+  UMA_HISTOGRAM_ENUMERATION("Startup.Temperature", g_startup_temperature,
                             STARTUP_TEMPERATURE_COUNT);
-  // As well as its suffixed twin.
-  // Factory properties copied from UMA_HISTOGRAM_ENUMERATION macro.
-  if (!same_version_startup_count_suffix.empty()) {
-    base::LinearHistogram::FactoryGet(
-        kStartupTemperatureHistogram + same_version_startup_count_suffix, 1,
-        STARTUP_TEMPERATURE_COUNT, STARTUP_TEMPERATURE_COUNT + 1,
-        base::HistogramBase::kUmaTargetedHistogramFlag)
-        ->Add(g_startup_temperature);
-  }
 #endif  // defined(OS_WIN)
 }
 
@@ -435,7 +335,7 @@
 void RecordRendererMainEntryHistogram() {
   if (!g_browser_main_entry_point_ticks.is_null() &&
       !g_renderer_main_entry_point_ticks.is_null()) {
-    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
         UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserMainToRendererMain",
         g_browser_main_entry_point_ticks, g_renderer_main_entry_point_ticks);
   }
@@ -450,32 +350,6 @@
                                       g_browser_main_entry_point_ticks);
 }
 
-// Logs the Startup.SameVersionStartupCount histogram. Relies on |pref_service|
-// to know information about the previous startups and store information for
-// future ones. Stores the logged value in |g_startups_with_current_version|.
-void RecordSameVersionStartupCount(PrefService* pref_service) {
-  DCHECK(pref_service);
-  DCHECK_EQ(kUndeterminedStartupsWithCurrentVersion,
-            g_startups_with_current_version);
-
-  const std::string current_version = version_info::GetVersionNumber();
-
-  if (current_version == pref_service->GetString(prefs::kLastStartupVersion)) {
-    g_startups_with_current_version =
-        pref_service->GetInteger(prefs::kSameVersionStartupCount);
-    ++g_startups_with_current_version;
-    pref_service->SetInteger(prefs::kSameVersionStartupCount,
-                             g_startups_with_current_version);
-  } else {
-    g_startups_with_current_version = 1;
-    pref_service->SetString(prefs::kLastStartupVersion, current_version);
-    pref_service->SetInteger(prefs::kSameVersionStartupCount, 1);
-  }
-
-  UMA_HISTOGRAM_COUNTS_100("Startup.SameVersionStartupCount",
-                           g_startups_with_current_version);
-}
-
 bool ShouldLogStartupHistogram() {
   return !WasMainWindowStartupInterrupted() &&
          !g_process_creation_ticks.is_null();
@@ -483,12 +357,6 @@
 
 }  // namespace
 
-void RegisterPrefs(PrefRegistrySimple* registry) {
-  DCHECK(registry);
-  registry->RegisterStringPref(prefs::kLastStartupVersion, std::string());
-  registry->RegisterIntegerPref(prefs::kSameVersionStartupCount, 0);
-}
-
 bool WasMainWindowStartupInterrupted() {
   return g_main_window_startup_interrupted;
 }
@@ -526,21 +394,17 @@
 }
 
 void RecordBrowserMainMessageLoopStart(base::TimeTicks ticks,
-                                       bool is_first_run,
-                                       PrefService* pref_service) {
-  DCHECK(pref_service);
+                                       bool is_first_run) {
   RecordMessageLoopStartTicks(ticks);
 
-  // Keep RecordSameVersionStartupCount() and RecordHardFaultHistogram()
-  // near the top of this method (as much as possible) as many other
-  // histograms depend on it setting |g_startup_temperature| and
-  // |g_startups_with_current_version|.
-  RecordSameVersionStartupCount(pref_service);
+  // Keep RecordHardFaultHistogram() near the top of this method (as much as
+  // possible) as many other histograms depend on it setting
+  // |g_startup_temperature|.
   RecordHardFaultHistogram();
 
   // Record timing of the browser message-loop start time.
   if (!is_first_run && !g_process_creation_ticks.is_null()) {
-    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
         UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserMessageLoopStartTime",
         g_process_creation_ticks, ticks);
   }
@@ -553,7 +417,7 @@
         "Startup.BrowserMessageLoopStartTimeFromMainEntry.FirstRun2",
         g_browser_main_entry_point_ticks, ticks);
   } else {
-    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
         UMA_HISTOGRAM_LONG_TIMES,
         "Startup.BrowserMessageLoopStartTimeFromMainEntry3",
         g_browser_main_entry_point_ticks, ticks);
@@ -565,13 +429,13 @@
   // Record values stored prior to startup temperature evaluation.
   if (ShouldLogStartupHistogram()) {
     if (!g_browser_open_tabs_duration.is_max()) {
-      UMA_HISTOGRAM_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
-          UMA_HISTOGRAM_LONG_TIMES_100, "Startup.BrowserOpenTabs",
-          g_browser_open_tabs_duration);
+      UMA_HISTOGRAM_WITH_TEMPERATURE(UMA_HISTOGRAM_LONG_TIMES_100,
+                                     "Startup.BrowserOpenTabs",
+                                     g_browser_open_tabs_duration);
     }
 
     if (!g_browser_window_display_ticks.is_null()) {
-      UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+      UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
           UMA_HISTOGRAM_LONG_TIMES, "Startup.BrowserWindowDisplay",
           g_process_creation_ticks, g_browser_window_display_ticks);
     }
@@ -582,18 +446,18 @@
   if (!g_process_creation_ticks.is_null() &&
       !g_browser_exe_main_entry_point_ticks.is_null()) {
     // Process create to chrome.exe:main().
-    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
         UMA_HISTOGRAM_LONG_TIMES, "Startup.LoadTime.ProcessCreateToExeMain2",
         g_process_creation_ticks, g_browser_exe_main_entry_point_ticks);
 
     // chrome.exe:main() to chrome.dll:main().
-    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+    UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
         UMA_HISTOGRAM_LONG_TIMES, "Startup.LoadTime.ExeMainToDllMain2",
         g_browser_exe_main_entry_point_ticks, g_browser_main_entry_point_ticks);
 
     // Process create to chrome.dll:main(). Reported as a histogram only as
     // the other two events above are sufficient for tracing purposes.
-    UMA_HISTOGRAM_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+    UMA_HISTOGRAM_WITH_TEMPERATURE(
         UMA_HISTOGRAM_LONG_TIMES, "Startup.LoadTime.ProcessCreateToDllMain2",
         g_browser_main_entry_point_ticks - g_process_creation_ticks);
   }
@@ -638,7 +502,7 @@
   if (!ShouldLogStartupHistogram())
     return;
 
-  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
       UMA_HISTOGRAM_LONG_TIMES_100, "Startup.FirstWebContents.MainFrameLoad2",
       g_process_creation_ticks, ticks);
 }
@@ -658,7 +522,7 @@
   if (!ShouldLogStartupHistogram())
     return;
 
-  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
       UMA_HISTOGRAM_LONG_TIMES_100, "Startup.FirstWebContents.NonEmptyPaint2",
       g_process_creation_ticks, now);
   UMA_HISTOGRAM_WITH_TEMPERATURE(
@@ -681,7 +545,7 @@
   if (!ShouldLogStartupHistogram())
     return;
 
-  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
       UMA_HISTOGRAM_LONG_TIMES_100,
       "Startup.FirstWebContents.MainNavigationStart", g_process_creation_ticks,
       ticks);
@@ -710,7 +574,7 @@
   if (!ShouldLogStartupHistogram())
     return;
 
-  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE_AND_SAME_VERSION_COUNT(
+  UMA_HISTOGRAM_AND_TRACE_WITH_TEMPERATURE(
       UMA_HISTOGRAM_LONG_TIMES_100,
       "Startup.FirstWebContents.MainNavigationFinished",
       g_process_creation_ticks, ticks);
diff --git a/components/startup_metric_utils/browser/startup_metric_utils.h b/components/startup_metric_utils/browser/startup_metric_utils.h
index 962d3cad..2f3841b 100644
--- a/components/startup_metric_utils/browser/startup_metric_utils.h
+++ b/components/startup_metric_utils/browser/startup_metric_utils.h
@@ -7,9 +7,6 @@
 
 #include "base/time/time.h"
 
-class PrefRegistrySimple;
-class PrefService;
-
 // Utility functions to support metric collection for browser startup. Timings
 // should use TimeTicks whenever possible. OS-provided timings are still
 // received as Time out of cross-platform support necessity but are converted to
@@ -27,9 +24,6 @@
   MULTI_TABS,
 };
 
-// Registers startup related prefs in |registry|.
-void RegisterPrefs(PrefRegistrySimple* registry);
-
 // Returns true when browser UI was not launched normally: some other UI was
 // shown first or browser was launched in background mode.
 bool WasMainWindowStartupInterrupted();
@@ -60,11 +54,9 @@
 void RecordExeMainEntryPointTicks(base::TimeTicks ticks);
 
 // Call this with the time recorded just before the message loop is started.
-// |is_first_run| - is the current launch part of a first run. |pref_service| is
-// used to store state for stats that span multiple startups.
+// |is_first_run| - is the current launch part of a first run.
 void RecordBrowserMainMessageLoopStart(base::TimeTicks ticks,
-                                       bool is_first_run,
-                                       PrefService* pref_service);
+                                       bool is_first_run);
 
 // Call this with the time when the first browser window became visible.
 void RecordBrowserWindowDisplay(base::TimeTicks ticks);
diff --git a/components/storage_monitor/storage_monitor_unittest.cc b/components/storage_monitor/storage_monitor_unittest.cc
index 4fe8492b..14f449a 100644
--- a/components/storage_monitor/storage_monitor_unittest.cc
+++ b/components/storage_monitor/storage_monitor_unittest.cc
@@ -24,7 +24,7 @@
 namespace storage_monitor {
 
 TEST(StorageMonitorTest, TestInitialize) {
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
   TestStorageMonitor::Destroy();
   TestStorageMonitor monitor;
   EXPECT_FALSE(monitor.init_called());
@@ -39,7 +39,7 @@
 
 TEST(StorageMonitorTest, DeviceAttachDetachNotifications) {
   TestStorageMonitor::Destroy();
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
   const std::string kDeviceId1 = "dcim:UUID:FFF0-0001";
   const std::string kDeviceId2 = "dcim:UUID:FFF0-0002";
   MockRemovableStorageObserver observer1;
@@ -82,7 +82,7 @@
 
 TEST(StorageMonitorTest, GetAllAvailableStoragesEmpty) {
   TestStorageMonitor::Destroy();
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
   TestStorageMonitor monitor;
   std::vector<StorageInfo> devices = monitor.GetAllAvailableStorages();
   EXPECT_EQ(0U, devices.size());
@@ -90,7 +90,7 @@
 
 TEST(StorageMonitorTest, GetAllAvailableStorageAttachDetach) {
   TestStorageMonitor::Destroy();
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
   TestStorageMonitor monitor;
   const std::string kDeviceId1 = "dcim:UUID:FFF0-0042";
   const base::FilePath kDevicePath1(FILE_PATH_LITERAL("/testfoo"));
diff --git a/components/test/data/payments/has_enrolled_instrument.js b/components/test/data/payments/has_enrolled_instrument.js
index 5dfc165..a32e33ff 100644
--- a/components/test/data/payments/has_enrolled_instrument.js
+++ b/components/test/data/payments/has_enrolled_instrument.js
@@ -5,6 +5,18 @@
  */
 
 /**
+ * Builds a PaymentRequest for 'basic-card' with the given options.
+ * @param {PaymentOptions} options - The payment options to use.
+ * @return {PaymentRequest} The new PaymentRequest object.
+ */
+function buildPaymentRequest(options) {
+  return new PaymentRequest(
+      [{supportedMethods: 'basic-card'}],
+      {total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}}},
+      options);
+}
+
+/**
  * Checks the hasEnrolledInstrument() value for 'basic-card' with the given
  * options.
  * @param {PaymentOptions} options - The payment options to use.
@@ -13,14 +25,21 @@
  */
 async function hasEnrolledInstrument(options) { // eslint-disable-line no-unused-vars,max-len
   try {
-    const result =
-        await new PaymentRequest(
-            [{supportedMethods: 'basic-card'}], {
-              total: {label: 'Total', amount: {currency: 'USD', value: '0.01'}},
-            },
-            options)
-            .hasEnrolledInstrument();
-    return result;
+    return await buildPaymentRequest(options).hasEnrolledInstrument();
+  } catch (e) {
+    return e.toString();
+  }
+}
+
+/**
+ * Runs the show() method for 'basic-card' with the given options.
+ * @param {PaymentOptions} options - The payment options to use.
+ * @return {Promise<string>} The error message string, if any.
+ */
+async function show(options) { // eslint-disable-line no-unused-vars
+  try {
+    await buildPaymentRequest(options).show();
+    return '';
   } catch (e) {
     return e.toString();
   }
diff --git a/components/tracing/test/trace_event_perftest.cc b/components/tracing/test/trace_event_perftest.cc
index 3aae9d7..206e1c2 100644
--- a/components/tracing/test/trace_event_perftest.cc
+++ b/components/tracing/test/trace_event_perftest.cc
@@ -90,7 +90,7 @@
   }
 
  private:
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
 };
 
 TEST_F(TraceEventPerfTest, Submit_10000_TRACE_EVENT0) {
diff --git a/components/viz/service/display_embedder/image_context_impl.cc b/components/viz/service/display_embedder/image_context_impl.cc
index 629121b0..e98cae3 100644
--- a/components/viz/service/display_embedder/image_context_impl.cc
+++ b/components/viz/service/display_embedder/image_context_impl.cc
@@ -71,60 +71,10 @@
     std::vector<GrBackendSemaphore>* begin_semaphores,
     std::vector<GrBackendSemaphore>* end_semaphores) {
   // Prepare for accessing shared image.
-  if (mailbox_holder().mailbox.IsSharedImage()) {
-    // Skip the context if it has been processed.
-    if (representation_scoped_read_access_) {
-      DCHECK(!owned_promise_image_texture_);
-      DCHECK(promise_image_texture_);
-      return;
-    }
-
-    // promise_image_texture_ is not null here, it means we are using a fallback
-    // image.
-    if (promise_image_texture_) {
-      DCHECK(owned_promise_image_texture_);
-      return;
-    }
-
-    if (!representation_) {
-      auto representation = representation_factory->ProduceSkia(
-          mailbox_holder().mailbox, context_state);
-      if (!representation) {
-        DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
-                       "mailbox not found in SharedImageManager.";
-        CreateFallbackImage(context_state);
-        return;
-      }
-
-      if (!(representation->usage() & gpu::SHARED_IMAGE_USAGE_DISPLAY)) {
-        DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
-                       "was not created with display usage.";
-        CreateFallbackImage(context_state);
-        return;
-      }
-
-      if (representation->size() != size()) {
-        DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
-                       "size does not match TransferableResource size.";
-        CreateFallbackImage(context_state);
-        return;
-      }
-
-      representation_ = std::move(representation);
-    }
-
-    representation_scoped_read_access_.emplace(
-        representation_.get(), begin_semaphores, end_semaphores);
-    if (!representation_scoped_read_access_->success()) {
-      representation_scoped_read_access_.reset();
-      representation_ = nullptr;
-      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
-                     "begin read access failed..";
-      CreateFallbackImage(context_state);
-      return;
-    }
-    promise_image_texture_ =
-        representation_scoped_read_access_->promise_image_texture();
+  if (mailbox_holder().mailbox.IsSharedImage() &&
+      BeginAccessIfNecessaryForSharedImage(context_state,
+                                           representation_factory,
+                                           begin_semaphores, end_semaphores)) {
     return;
   }
 
@@ -171,6 +121,63 @@
   set_promise_image_texture(SkPromiseImageTexture::Make(backend_texture));
 }
 
+bool ImageContextImpl::BeginAccessIfNecessaryForSharedImage(
+    gpu::SharedContextState* context_state,
+    gpu::SharedImageRepresentationFactory* representation_factory,
+    std::vector<GrBackendSemaphore>* begin_semaphores,
+    std::vector<GrBackendSemaphore>* end_semaphores) {
+  // Skip the context if it has been processed.
+  if (representation_scoped_read_access_) {
+    DCHECK(!owned_promise_image_texture_);
+    DCHECK(promise_image_texture_);
+    return true;
+  }
+
+  // promise_image_texture_ is not null here, it means we are using a fallback
+  // image.
+  if (promise_image_texture_) {
+    DCHECK(owned_promise_image_texture_);
+    return true;
+  }
+
+  if (!representation_) {
+    auto representation = representation_factory->ProduceSkia(
+        mailbox_holder().mailbox, context_state);
+    if (!representation) {
+      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
+                     "mailbox not found in SharedImageManager.";
+      return false;
+    }
+
+    if (!(representation->usage() & gpu::SHARED_IMAGE_USAGE_DISPLAY)) {
+      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
+                     "was not created with display usage.";
+      return false;
+    }
+
+    if (representation->size() != size()) {
+      DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
+                     "size does not match TransferableResource size.";
+      return false;
+    }
+
+    representation_ = std::move(representation);
+  }
+
+  representation_scoped_read_access_.emplace(representation_.get(),
+                                             begin_semaphores, end_semaphores);
+  if (!representation_scoped_read_access_->success()) {
+    representation_scoped_read_access_.reset();
+    representation_ = nullptr;
+    DLOG(ERROR) << "Failed to fulfill the promise texture - SharedImage "
+                   "begin read access failed..";
+    return false;
+  }
+  promise_image_texture_ =
+      representation_scoped_read_access_->promise_image_texture();
+  return true;
+}
+
 bool ImageContextImpl::BindOrCopyTextureIfNecessary(
     gpu::TextureBase* texture_base,
     gfx::Size* size) {
diff --git a/components/viz/service/display_embedder/image_context_impl.h b/components/viz/service/display_embedder/image_context_impl.h
index e56c198..6fd6059 100644
--- a/components/viz/service/display_embedder/image_context_impl.h
+++ b/components/viz/service/display_embedder/image_context_impl.h
@@ -79,6 +79,11 @@
 
  private:
   void CreateFallbackImage(gpu::SharedContextState* context_state);
+  bool BeginAccessIfNecessaryForSharedImage(
+      gpu::SharedContextState* context_state,
+      gpu::SharedImageRepresentationFactory* representation_factory,
+      std::vector<GrBackendSemaphore>* begin_semaphores,
+      std::vector<GrBackendSemaphore>* end_semaphores);
 
   // Returns true if |texture_base| is a gles2::Texture and all necessary
   // operations completed successfully. In this case, |*size| is the size of
diff --git a/content/browser/accessibility/browser_accessibility_android.cc b/content/browser/accessibility/browser_accessibility_android.cc
index b828f57..5d799ab 100644
--- a/content/browser/accessibility/browser_accessibility_android.cc
+++ b/content/browser/accessibility/browser_accessibility_android.cc
@@ -290,7 +290,19 @@
 }
 
 bool BrowserAccessibilityAndroid::IsEnabled() const {
-  return GetData().GetRestriction() != ax::mojom::Restriction::kDisabled;
+  switch (GetData().GetRestriction()) {
+    case ax::mojom::Restriction::kNone:
+      return true;
+    case ax::mojom::Restriction::kReadOnly:
+    case ax::mojom::Restriction::kDisabled:
+      // On Android, both Disabled and ReadOnly are treated the same.
+      // For both of them, we set AccessibilityNodeInfo.IsEnabled to false
+      // and we don't expose certain actions like SET_VALUE and PASTE.
+      return false;
+  }
+
+  NOTREACHED();
+  return true;
 }
 
 bool BrowserAccessibilityAndroid::IsExpanded() const {
diff --git a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
index b02e676..c103229 100644
--- a/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
+++ b/content/browser/background_fetch/background_fetch_data_manager_unittest.cc
@@ -872,7 +872,7 @@
     blob->size = blob_handle->size();
     storage::BlobImpl::Create(
         std::make_unique<storage::BlobDataHandle>(*blob_handle),
-        MakeRequest(&blob->blob));
+        blob->blob.InitWithNewPipeAndPassReceiver());
     return blob;
   }
 
diff --git a/content/browser/background_fetch/storage/mark_request_complete_task.cc b/content/browser/background_fetch/storage/mark_request_complete_task.cc
index 147722b..87cf3c73 100644
--- a/content/browser/background_fetch/storage/mark_request_complete_task.cc
+++ b/content/browser/background_fetch/storage/mark_request_complete_task.cc
@@ -45,7 +45,7 @@
   blob->size = response_blob_handle->size();
 
   storage::BlobImpl::Create(std::move(response_blob_handle),
-                            MakeRequest(&blob->blob));
+                            blob->blob.InitWithNewPipeAndPassReceiver());
   return blob;
 }
 
diff --git a/content/browser/battery_monitor_browsertest.cc b/content/browser/battery_monitor_browsertest.cc
index 60d32b2..f21ab47 100644
--- a/content/browser/battery_monitor_browsertest.cc
+++ b/content/browser/battery_monitor_browsertest.cc
@@ -13,7 +13,7 @@
 #include "content/public/test/test_navigation_observer.h"
 #include "content/public/test/test_utils.h"
 #include "content/shell/browser/shell.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "services/device/public/mojom/battery_monitor.mojom.h"
 #include "services/device/public/mojom/battery_status.mojom.h"
 #include "services/device/public/mojom/constants.mojom.h"
@@ -25,12 +25,12 @@
 
 class MockBatteryMonitor : public device::mojom::BatteryMonitor {
  public:
-  MockBatteryMonitor() : binding_(this) {}
+  MockBatteryMonitor() = default;
   ~MockBatteryMonitor() override = default;
 
-  void Bind(device::mojom::BatteryMonitorRequest request) {
-    DCHECK(!binding_.is_bound());
-    binding_.Bind(std::move(request));
+  void Bind(mojo::PendingReceiver<device::mojom::BatteryMonitor> receiver) {
+    DCHECK(!receiver_.is_bound());
+    receiver_.Bind(std::move(receiver));
   }
 
   void DidChange(const device::mojom::BatteryStatus& battery_status) {
@@ -46,7 +46,7 @@
   void QueryNextStatus(QueryNextStatusCallback callback) override {
     if (!callback_.is_null()) {
       DVLOG(1) << "Overlapped call to QueryNextStatus!";
-      binding_.Close();
+      receiver_.reset();
       return;
     }
     callback_ = std::move(callback);
@@ -63,7 +63,7 @@
   QueryNextStatusCallback callback_;
   device::mojom::BatteryStatus status_;
   bool status_to_report_ = false;
-  mojo::Binding<device::mojom::BatteryMonitor> binding_;
+  mojo::Receiver<device::mojom::BatteryMonitor> receiver_{this};
 
   DISALLOW_COPY_AND_ASSIGN(MockBatteryMonitor);
 };
diff --git a/content/browser/cache_storage/cache_storage_cache_entry_handler.cc b/content/browser/cache_storage/cache_storage_cache_entry_handler.cc
index abc075a..ae750404 100644
--- a/content/browser/cache_storage/cache_storage_cache_entry_handler.cc
+++ b/content/browser/cache_storage/cache_storage_cache_entry_handler.cc
@@ -397,13 +397,14 @@
   if (BrowserThread::CurrentlyOn(BrowserThread::IO)) {
     FinalizeBlobOnIOThread(blob_context_, std::move(blob_entry),
                            disk_cache_index, side_data_disk_cache_index,
-                           blob->uuid, MakeRequest(&blob->blob));
+                           blob->uuid,
+                           blob->blob.InitWithNewPipeAndPassReceiver());
   } else {
     base::PostTask(FROM_HERE, {BrowserThread::IO},
                    base::BindOnce(&FinalizeBlobOnIOThread, blob_context_,
                                   std::move(blob_entry), disk_cache_index,
                                   side_data_disk_cache_index, blob->uuid,
-                                  MakeRequest(&blob->blob)));
+                                  blob->blob.InitWithNewPipeAndPassReceiver()));
   }
 
   return blob;
diff --git a/content/browser/cache_storage/cache_storage_cache_unittest.cc b/content/browser/cache_storage/cache_storage_cache_unittest.cc
index e1681d55..daa3a53 100644
--- a/content/browser/cache_storage/cache_storage_cache_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_cache_unittest.cc
@@ -39,7 +39,7 @@
 #include "content/public/test/test_browser_context.h"
 #include "content/public/test/test_utils.h"
 #include "crypto/symmetric_key.h"
-#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/system/data_pipe.h"
 #include "mojo/public/cpp/system/data_pipe_drainer.h"
 #include "net/base/test_completion_callback.h"
@@ -501,7 +501,7 @@
     blob->uuid = blob_handle_->uuid();
     blob->size = expected_blob_data_.size();
     // Use cloned blob pointer for all responses with blob body.
-    blob_ptr_->Clone(mojo::MakeRequest(&blob->blob));
+    blob_ptr_->Clone(blob->blob.InitWithNewPipeAndPassReceiver());
 
     blink::mojom::FetchAPIResponsePtr response = CreateNoBodyResponse();
     response->url_list = {kBodyUrl};
@@ -545,7 +545,7 @@
     response->side_data_blob->size = side_data_blob_handle->size();
     storage::BlobImpl::Create(
         std::make_unique<storage::BlobDataHandle>(*side_data_blob_handle),
-        MakeRequest(&response->side_data_blob->blob));
+        response->side_data_blob->blob.InitWithNewPipeAndPassReceiver());
   }
 
   blink::mojom::FetchAPIRequestPtr CopyFetchRequest(
@@ -2232,8 +2232,9 @@
   auto blob = blink::mojom::SerializedBlob::New();
   blob->uuid = "mock blob";
   blob->size = 100;
-  mojo::MakeStrongBinding(std::make_unique<SlowBlob>(run_loop.QuitClosure()),
-                          MakeRequest(&blob->blob));
+  mojo::MakeSelfOwnedReceiver(
+      std::make_unique<SlowBlob>(run_loop.QuitClosure()),
+      blob->blob.InitWithNewPipeAndPassReceiver());
   blink::mojom::FetchAPIResponsePtr response = CreateNoBodyResponse();
   response->url_list = {kBodyUrl};
   response->blob = std::move(blob);
diff --git a/content/browser/cache_storage/cache_storage_manager_unittest.cc b/content/browser/cache_storage/cache_storage_manager_unittest.cc
index 150fe4c..caf0120 100644
--- a/content/browser/cache_storage/cache_storage_manager_unittest.cc
+++ b/content/browser/cache_storage/cache_storage_manager_unittest.cc
@@ -2341,10 +2341,9 @@
   // Provide a fake blob implementation that delays completion.  This will
   // allow us to pause the writing operation so we can drop the external
   // reference.
-  auto blob_request = mojo::MakeRequest(&blob->blob);
   base::RunLoop blob_loop;
-  DelayedBlob delayed_blob(std::move(blob_request), body_data,
-                           blob_loop.QuitClosure());
+  DelayedBlob delayed_blob(blob->blob.InitWithNewPipeAndPassReceiver(),
+                           body_data, blob_loop.QuitClosure());
 
   // Begin the operation to write the blob into the cache.
   base::RunLoop cache_loop;
diff --git a/content/browser/device_sensors/device_sensor_browsertest.cc b/content/browser/device_sensors/device_sensor_browsertest.cc
index f3d12aa..e6a90d18 100644
--- a/content/browser/device_sensors/device_sensor_browsertest.cc
+++ b/content/browser/device_sensors/device_sensor_browsertest.cc
@@ -104,8 +104,9 @@
   std::unique_ptr<net::EmbeddedTestServer> https_embedded_test_server_;
 
  private:
-  void BindSensorProvider(device::mojom::SensorProviderRequest request) {
-    sensor_provider_->Bind(std::move(request));
+  void BindSensorProvider(
+      mojo::PendingReceiver<device::mojom::SensorProvider> receiver) {
+    sensor_provider_->Bind(std::move(receiver));
   }
 };
 
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index c4ff843..ad1d447 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -57,6 +57,8 @@
 const char kMethod[] = "method";
 const char kResumeMethod[] = "Runtime.runIfWaitingForDebugger";
 const char kSessionId[] = "sessionId";
+
+// Clients match against this error message verbatim (http://crbug.com/1001678).
 const char kTargetClosedMessage[] = "Inspected target navigated or closed";
 }  // namespace
 
diff --git a/content/browser/download/download_manager_impl.cc b/content/browser/download/download_manager_impl.cc
index 2ac05f67..286e70f 100644
--- a/content/browser/download/download_manager_impl.cc
+++ b/content/browser/download/download_manager_impl.cc
@@ -631,9 +631,10 @@
     const download::DownloadUrlParameters::OnStartedCallback& on_started) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
   DCHECK(info);
-  in_progress_manager_->StartDownload(std::move(info), std::move(stream),
-                                      nullptr /* url_loader_factory_provider */,
-                                      base::DoNothing(), on_started);
+  in_progress_manager_->StartDownload(
+      std::move(info), std::move(stream),
+      download::URLLoaderFactoryProvider::GetNullPtr(), base::DoNothing(),
+      on_started);
 }
 
 void DownloadManagerImpl::CheckForHistoryFilesRemoval() {
diff --git a/content/browser/download/download_manager_impl_unittest.cc b/content/browser/download/download_manager_impl_unittest.cc
index b32bf929..5cf0f7e 100644
--- a/content/browser/download/download_manager_impl_unittest.cc
+++ b/content/browser/download/download_manager_impl_unittest.cc
@@ -466,7 +466,7 @@
     // we call Start on it immediately, so we need to set that expectation
     // in the factory.
     item.Start(std::unique_ptr<download::DownloadFile>(), base::DoNothing(),
-               info, nullptr);
+               info, download::URLLoaderFactoryProvider::GetNullPtr());
     DCHECK(id < download_urls_.size());
     EXPECT_CALL(item, GetURL()).WillRepeatedly(ReturnRef(download_urls_[id]));
 
diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc
index 54be9df8..79ed532 100644
--- a/content/browser/frame_host/render_frame_host_impl.cc
+++ b/content/browser/frame_host/render_frame_host_impl.cc
@@ -3418,6 +3418,14 @@
   Send(new AccessibilityMsg_EventBundle_ACK(routing_id_, ack_token));
 }
 
+void RenderFrameHostImpl::UpdateBrowserControlsState(
+    BrowserControlsState constraints,
+    BrowserControlsState current,
+    bool animate) {
+  if (frame_)
+    frame_->UpdateBrowserControlsState(constraints, current, animate);
+}
+
 void RenderFrameHostImpl::SendAccessibilityEventsToManager(
     const AXEventNotificationDetails& details) {
   if (browser_accessibility_manager_ &&
diff --git a/content/browser/frame_host/render_frame_host_impl.h b/content/browser/frame_host/render_frame_host_impl.h
index 709eb475..a24beb7 100644
--- a/content/browser/frame_host/render_frame_host_impl.h
+++ b/content/browser/frame_host/render_frame_host_impl.h
@@ -301,6 +301,9 @@
   void UpdateSubresourceLoaderFactories() override;
   blink::FrameOwnerElementType GetFrameOwnerElementType() override;
   bool HasTransientUserActivation() override;
+  void UpdateBrowserControlsState(BrowserControlsState constraints,
+                                  BrowserControlsState current,
+                                  bool animate) override;
 
   void SendAccessibilityEventsToManager(
       const AXEventNotificationDetails& details);
diff --git a/content/browser/generic_sensor/generic_sensor_browsertest.cc b/content/browser/generic_sensor/generic_sensor_browsertest.cc
index 43c72da1..efc3c13 100644
--- a/content/browser/generic_sensor/generic_sensor_browsertest.cc
+++ b/content/browser/generic_sensor/generic_sensor_browsertest.cc
@@ -51,7 +51,7 @@
     service_manager::ServiceBinding::OverrideInterfaceBinderForTesting(
         device::mojom::kServiceName,
         base::BindRepeating(
-            &GenericSensorBrowserTest::BindSensorProviderRequest,
+            &GenericSensorBrowserTest::BindSensorProviderReceiver,
             base::Unretained(this)));
   }
 
@@ -78,7 +78,8 @@
     command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
   }
 
-  void BindSensorProviderRequest(device::mojom::SensorProviderRequest request) {
+  void BindSensorProviderReceiver(
+      mojo::PendingReceiver<device::mojom::SensorProvider> receiver) {
     if (!sensor_provider_available_)
       return;
 
@@ -87,7 +88,7 @@
       fake_sensor_provider_->SetAmbientLightSensorData(50);
     }
 
-    fake_sensor_provider_->Bind(std::move(request));
+    fake_sensor_provider_->Bind(std::move(receiver));
   }
 
   void set_sensor_provider_available(bool sensor_provider_available) {
diff --git a/content/browser/indexed_db/indexed_db_callbacks.cc b/content/browser/indexed_db/indexed_db_callbacks.cc
index 792d1b44..1492380 100644
--- a/content/browser/indexed_db/indexed_db_callbacks.cc
+++ b/content/browser/indexed_db/indexed_db_callbacks.cc
@@ -130,7 +130,7 @@
     uuid_ = base::GenerateGUID();
   }
   (*blob_or_file_info)->uuid = uuid_;
-  receiver_ = mojo::MakeRequest(&(*blob_or_file_info)->blob);
+  receiver_ = (*blob_or_file_info)->blob.InitWithNewPipeAndPassReceiver();
 }
 IndexedDBCallbacks::IndexedDBValueBlob::IndexedDBValueBlob(
     IndexedDBValueBlob&& other) = default;
diff --git a/content/browser/media/session/media_session_service_impl.cc b/content/browser/media/session/media_session_service_impl.cc
index f4d4c5f..eb12ef1 100644
--- a/content/browser/media/session/media_session_service_impl.cc
+++ b/content/browser/media/session/media_session_service_impl.cc
@@ -32,10 +32,10 @@
 // static
 void MediaSessionServiceImpl::Create(
     RenderFrameHost* render_frame_host,
-    blink::mojom::MediaSessionServiceRequest request) {
+    mojo::PendingReceiver<blink::mojom::MediaSessionService> receiver) {
   MediaSessionServiceImpl* impl =
       new MediaSessionServiceImpl(render_frame_host);
-  impl->Bind(std::move(request));
+  impl->Bind(std::move(receiver));
 }
 
 RenderFrameHost* MediaSessionServiceImpl::GetRenderFrameHost() {
@@ -137,9 +137,9 @@
 }
 
 void MediaSessionServiceImpl::Bind(
-    blink::mojom::MediaSessionServiceRequest request) {
+    mojo::PendingReceiver<blink::mojom::MediaSessionService> receiver) {
   receiver_.reset(new mojo::Receiver<blink::mojom::MediaSessionService>(
-      this, std::move(request)));
+      this, std::move(receiver)));
 }
 
 }  // namespace content
diff --git a/content/browser/media/session/media_session_service_impl.h b/content/browser/media/session/media_session_service_impl.h
index d88dd5e..5ecb5344 100644
--- a/content/browser/media/session/media_session_service_impl.h
+++ b/content/browser/media/session/media_session_service_impl.h
@@ -6,6 +6,9 @@
 #define CONTENT_BROWSER_MEDIA_SESSION_MEDIA_SESSION_SERVICE_IMPL_H_
 
 #include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/mediasession/media_session.mojom.h"
 
@@ -22,8 +25,9 @@
  public:
   ~MediaSessionServiceImpl() override;
 
-  static void Create(RenderFrameHost* render_frame_host,
-                     blink::mojom::MediaSessionServiceRequest request);
+  static void Create(
+      RenderFrameHost* render_frame_host,
+      mojo::PendingReceiver<blink::mojom::MediaSessionService> receiver);
   const mojo::Remote<blink::mojom::MediaSessionClient>& GetClient() {
     return client_;
   }
@@ -63,7 +67,7 @@
  private:
   MediaSessionImpl* GetMediaSession();
 
-  void Bind(blink::mojom::MediaSessionServiceRequest request);
+  void Bind(mojo::PendingReceiver<blink::mojom::MediaSessionService> receiver);
 
   void ClearActions();
 
diff --git a/content/browser/power_monitor_browsertest.cc b/content/browser/power_monitor_browsertest.cc
index 667d4d9c..fd671ca9 100644
--- a/content/browser/power_monitor_browsertest.cc
+++ b/content/browser/power_monitor_browsertest.cc
@@ -106,8 +106,9 @@
         device::mojom::PowerMonitor>(device::mojom::kServiceName);
   }
 
-  void BindPowerMonitor(const service_manager::BindSourceInfo& source_info,
-                        device::mojom::PowerMonitorRequest request) {
+  void BindPowerMonitor(
+      const service_manager::BindSourceInfo& source_info,
+      mojo::PendingReceiver<device::mojom::PowerMonitor> receiver) {
     if (source_info.identity.name() == mojom::kRendererServiceName) {
       // We can receive binding requests for the spare RenderProcessHost - this
       // might happen before the test has provided the
@@ -140,7 +141,7 @@
         std::move(gpu_bound_closure_).Run();
     }
 
-    power_monitor_message_broadcaster_.Bind(std::move(request));
+    power_monitor_message_broadcaster_.Bind(std::move(receiver));
   }
 
  protected:
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
index d3c26f79..1a3b5b44 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host.cc
@@ -16,6 +16,7 @@
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/render_frame_host.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
@@ -25,16 +26,16 @@
 
 namespace {
 
-void BindMediaStreamDeviceObserverRequest(
+void BindMediaStreamDeviceObserverReceiver(
     int render_process_id,
     int render_frame_id,
-    blink::mojom::MediaStreamDeviceObserverRequest request) {
+    mojo::PendingReceiver<blink::mojom::MediaStreamDeviceObserver> receiver) {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   RenderFrameHost* render_frame_host =
       RenderFrameHost::FromID(render_process_id, render_frame_id);
   if (render_frame_host)
-    render_frame_host->GetRemoteInterfaces()->GetInterface(std::move(request));
+    render_frame_host->GetRemoteInterfaces()->GetInterface(std::move(receiver));
 }
 
 }  // namespace
@@ -89,23 +90,22 @@
                                                   new_device);
 }
 
-const blink::mojom::MediaStreamDeviceObserverPtr&
+const mojo::Remote<blink::mojom::MediaStreamDeviceObserver>&
 MediaStreamDispatcherHost::GetMediaStreamDeviceObserver() {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
 
   if (media_stream_device_observer_)
     return media_stream_device_observer_;
 
-  blink::mojom::MediaStreamDeviceObserverPtr observer;
-  auto dispatcher_request = mojo::MakeRequest(&observer);
-  observer.set_connection_error_handler(base::BindOnce(
+  auto dispatcher_receiver =
+      media_stream_device_observer_.BindNewPipeAndPassReceiver();
+  media_stream_device_observer_.set_disconnect_handler(base::BindOnce(
       &MediaStreamDispatcherHost::OnMediaStreamDeviceObserverConnectionError,
       weak_factory_.GetWeakPtr()));
   base::PostTask(
       FROM_HERE, {BrowserThread::UI},
-      base::BindOnce(&BindMediaStreamDeviceObserverRequest, render_process_id_,
-                     render_frame_id_, std::move(dispatcher_request)));
-  media_stream_device_observer_ = std::move(observer);
+      base::BindOnce(&BindMediaStreamDeviceObserverReceiver, render_process_id_,
+                     render_frame_id_, std::move(dispatcher_receiver)));
   return media_stream_device_observer_;
 }
 
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host.h b/content/browser/renderer_host/media/media_stream_dispatcher_host.h
index fbd601c2e..7b6617b2 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host.h
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host.h
@@ -13,6 +13,8 @@
 #include "content/browser/media/media_devices_util.h"
 #include "content/common/content_export.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/common/mediastream/media_stream_controls.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 
@@ -41,14 +43,14 @@
     salt_and_origin_callback_ = std::move(callback);
   }
   void SetMediaStreamDeviceObserverForTesting(
-      blink::mojom::MediaStreamDeviceObserverPtr observer) {
-    media_stream_device_observer_ = std::move(observer);
+      mojo::PendingRemote<blink::mojom::MediaStreamDeviceObserver> observer) {
+    media_stream_device_observer_.Bind(std::move(observer));
   }
 
  private:
   friend class MockMediaStreamDispatcherHost;
 
-  const blink::mojom::MediaStreamDeviceObserverPtr&
+  const mojo::Remote<blink::mojom::MediaStreamDeviceObserver>&
   GetMediaStreamDeviceObserver();
   void OnMediaStreamDeviceObserverConnectionError();
   void CancelAllRequests();
@@ -100,7 +102,8 @@
   const int render_frame_id_;
   const int requester_id_;
   MediaStreamManager* media_stream_manager_;
-  blink::mojom::MediaStreamDeviceObserverPtr media_stream_device_observer_;
+  mojo::Remote<blink::mojom::MediaStreamDeviceObserver>
+      media_stream_device_observer_;
   MediaDeviceSaltAndOriginCallback salt_and_origin_callback_;
 
   base::WeakPtrFactory<MediaStreamDispatcherHost> weak_factory_{this};
diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
index fab4eeb..5d4c6b4f 100644
--- a/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
+++ b/content/browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc
@@ -35,7 +35,7 @@
 #include "media/audio/mock_audio_manager.h"
 #include "media/audio/test_audio_thread.h"
 #include "media/base/media_switches.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
@@ -85,8 +85,7 @@
                                 int render_frame_id,
                                 MediaStreamManager* manager)
       : MediaStreamDispatcherHost(render_process_id, render_frame_id, manager),
-        task_runner_(base::ThreadTaskRunnerHandle::Get()),
-        binding_(this) {}
+        task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
   ~MockMediaStreamDispatcherHost() override {}
 
   // A list of mock methods.
@@ -145,10 +144,9 @@
                        const blink::MediaStreamDevice& old_device,
                        const blink::MediaStreamDevice& new_device) override {}
 
-  blink::mojom::MediaStreamDeviceObserverPtr CreateInterfacePtrAndBind() {
-    blink::mojom::MediaStreamDeviceObserverPtr observer;
-    binding_.Bind(mojo::MakeRequest(&observer));
-    return observer;
+  mojo::PendingRemote<blink::mojom::MediaStreamDeviceObserver>
+  BindNewPipeAndPassRemote() {
+    return receiver_.BindNewPipeAndPassRemote();
   }
 
   std::string label_;
@@ -220,7 +218,7 @@
 
   const scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
   base::queue<base::Closure> quit_closures_;
-  mojo::Binding<blink::mojom::MediaStreamDeviceObserver> binding_;
+  mojo::Receiver<blink::mojom::MediaStreamDeviceObserver> receiver_{this};
 };
 
 class MockMediaStreamUIProxy : public FakeMediaStreamUIProxy {
@@ -265,7 +263,7 @@
         base::BindRepeating(&MediaStreamDispatcherHostTest::GetSaltAndOrigin,
                             base::Unretained(this)));
     host_->SetMediaStreamDeviceObserverForTesting(
-        host_->CreateInterfacePtrAndBind());
+        host_->BindNewPipeAndPassRemote());
 
 #if defined(OS_CHROMEOS)
     chromeos::CrasAudioClient::InitializeFake();
@@ -590,7 +588,7 @@
       base::BindRepeating(&MediaStreamDispatcherHostTest::GetSaltAndOrigin,
                           base::Unretained(this)));
   host_->SetMediaStreamDeviceObserverForTesting(
-      host_->CreateInterfacePtrAndBind());
+      host_->BindNewPipeAndPassRemote());
 
   GenerateStreamAndWaitForResult(kPageRequestId + 1, controls);
 
diff --git a/content/browser/renderer_host/media/media_stream_track_metrics_host.cc b/content/browser/renderer_host/media/media_stream_track_metrics_host.cc
index 800a5f8..88c4a16 100644
--- a/content/browser/renderer_host/media/media_stream_track_metrics_host.cc
+++ b/content/browser/renderer_host/media/media_stream_track_metrics_host.cc
@@ -28,9 +28,9 @@
   tracks_.clear();
 }
 
-void MediaStreamTrackMetricsHost::BindRequest(
-    blink::mojom::MediaStreamTrackMetricsHostRequest request) {
-  bindings_.AddBinding(this, std::move(request));
+void MediaStreamTrackMetricsHost::BindReceiver(
+    mojo::PendingReceiver<blink::mojom::MediaStreamTrackMetricsHost> receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 void MediaStreamTrackMetricsHost::AddTrack(uint64_t id,
diff --git a/content/browser/renderer_host/media/media_stream_track_metrics_host.h b/content/browser/renderer_host/media/media_stream_track_metrics_host.h
index 4e1d4a05..32b4268 100644
--- a/content/browser/renderer_host/media/media_stream_track_metrics_host.h
+++ b/content/browser/renderer_host/media/media_stream_track_metrics_host.h
@@ -11,7 +11,8 @@
 #include <string>
 
 #include "base/time/time.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 
 namespace content {
@@ -34,7 +35,9 @@
   explicit MediaStreamTrackMetricsHost();
 
   ~MediaStreamTrackMetricsHost() override;
-  void BindRequest(blink::mojom::MediaStreamTrackMetricsHostRequest request);
+  void BindReceiver(
+      mojo::PendingReceiver<blink::mojom::MediaStreamTrackMetricsHost>
+          receiver);
 
  private:
   void AddTrack(uint64_t id, bool is_audio, bool is_remote) override;
@@ -57,7 +60,7 @@
   typedef std::map<uint64_t, TrackInfo> TrackMap;
   TrackMap tracks_;
 
-  mojo::BindingSet<blink::mojom::MediaStreamTrackMetricsHost> bindings_;
+  mojo::ReceiverSet<blink::mojom::MediaStreamTrackMetricsHost> receivers_;
 };
 
 }  // namespace content
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
index 111796d..ac4362e 100644
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -4534,11 +4534,11 @@
 }
 
 void RenderProcessHostImpl::CreateMediaStreamTrackMetricsHost(
-    blink::mojom::MediaStreamTrackMetricsHostRequest request) {
+    mojo::PendingReceiver<blink::mojom::MediaStreamTrackMetricsHost> receiver) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   if (!media_stream_track_metrics_host_)
     media_stream_track_metrics_host_.reset(new MediaStreamTrackMetricsHost());
-  media_stream_track_metrics_host_->BindRequest(std::move(request));
+  media_stream_track_metrics_host_->BindReceiver(std::move(receiver));
 }
 
 #if BUILDFLAG(ENABLE_MDNS)
diff --git a/content/browser/renderer_host/render_process_host_impl.h b/content/browser/renderer_host/render_process_host_impl.h
index e8d094ec..6575b28 100644
--- a/content/browser/renderer_host/render_process_host_impl.h
+++ b/content/browser/renderer_host/render_process_host_impl.h
@@ -651,7 +651,8 @@
       SiteInstanceImpl* site_instance);
 
   void CreateMediaStreamTrackMetricsHost(
-      blink::mojom::MediaStreamTrackMetricsHostRequest request);
+      mojo::PendingReceiver<blink::mojom::MediaStreamTrackMetricsHost>
+          receiver);
 
 #if BUILDFLAG(ENABLE_MDNS)
   void CreateMdnsResponder(network::mojom::MdnsResponderRequest request);
diff --git a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
index 96d2506..c64d936 100644
--- a/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
+++ b/content/browser/service_worker/service_worker_navigation_loader_unittest.cc
@@ -566,9 +566,8 @@
   auto blob = blink::mojom::SerializedBlob::New();
   blob->uuid = blob_handle->uuid();
   blob->size = blob_handle->size();
-  mojo::PendingReceiver<blink::mojom::Blob> receiver =
-      mojo::MakeRequest(&blob->blob);
-  storage::BlobImpl::Create(std::move(blob_handle), std::move(receiver));
+  storage::BlobImpl::Create(std::move(blob_handle),
+                            blob->blob.InitWithNewPipeAndPassReceiver());
   service_worker_->RespondWithBlob(std::move(blob));
 
   // Perform the request.
@@ -607,9 +606,8 @@
                                   storage::BlobStatus::ERR_OUT_OF_MEMORY);
   auto blob = blink::mojom::SerializedBlob::New();
   blob->uuid = kBrokenUUID;
-  mojo::PendingReceiver<blink::mojom::Blob> receiver =
-      mojo::MakeRequest(&blob->blob);
-  storage::BlobImpl::Create(std::move(blob_handle), std::move(receiver));
+  storage::BlobImpl::Create(std::move(blob_handle),
+                            blob->blob.InitWithNewPipeAndPassReceiver());
   service_worker_->RespondWithBlob(std::move(blob));
 
   // Perform the request.
diff --git a/content/browser/vibration_browsertest.cc b/content/browser/vibration_browsertest.cc
index df3bba70..4fc38b9 100644
--- a/content/browser/vibration_browsertest.cc
+++ b/content/browser/vibration_browsertest.cc
@@ -14,7 +14,7 @@
 #include "content/public/test/content_browser_test.h"
 #include "content/public/test/content_browser_test_utils.h"
 #include "content/shell/browser/shell.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "services/device/public/mojom/constants.mojom.h"
 #include "services/device/public/mojom/vibration_manager.mojom.h"
 #include "services/service_manager/public/cpp/service_binding.h"
@@ -26,7 +26,7 @@
 class VibrationTest : public ContentBrowserTest,
                       public device::mojom::VibrationManager {
  public:
-  VibrationTest() : binding_(this) {
+  VibrationTest() {
     // Because Device Service also runs in this process(browser process), here
     // we can directly set our binder to intercept interface requests against
     // it.
@@ -41,8 +41,9 @@
         device::mojom::VibrationManager>(device::mojom::kServiceName);
   }
 
-  void BindVibrationManager(device::mojom::VibrationManagerRequest request) {
-    binding_.Bind(std::move(request));
+  void BindVibrationManager(
+      mojo::PendingReceiver<device::mojom::VibrationManager> receiver) {
+    receiver_.Bind(std::move(receiver));
   }
 
  protected:
@@ -70,7 +71,7 @@
 
   int64_t vibrate_milliseconds_ = -1;
   base::Closure vibrate_done_;
-  mojo::Binding<device::mojom::VibrationManager> binding_;
+  mojo::Receiver<device::mojom::VibrationManager> receiver_{this};
 
   DISALLOW_COPY_AND_ASSIGN(VibrationTest);
 };
diff --git a/content/child/webthemeengine_impl_default.cc b/content/child/webthemeengine_impl_default.cc
index 35bb754..59e6012 100644
--- a/content/child/webthemeengine_impl_default.cc
+++ b/content/child/webthemeengine_impl_default.cc
@@ -132,6 +132,8 @@
   switch (part) {
     case WebThemeEngine::kPartScrollbarHorizontalTrack:
     case WebThemeEngine::kPartScrollbarVerticalTrack:
+      native_theme_extra_params->scrollbar_track.is_upper =
+          extra_params->scrollbar_track.is_back;
       native_theme_extra_params->scrollbar_track.track_x =
           extra_params->scrollbar_track.track_x;
       native_theme_extra_params->scrollbar_track.track_y =
diff --git a/content/common/background_fetch/background_fetch_types.cc b/content/common/background_fetch/background_fetch_types.cc
index f5726e16a..ff307ad 100644
--- a/content/common/background_fetch/background_fetch_types.cc
+++ b/content/common/background_fetch/background_fetch_types.cc
@@ -4,16 +4,18 @@
 
 #include "content/common/background_fetch/background_fetch_types.h"
 
+#include "mojo/public/cpp/bindings/remote.h"
+
 namespace {
 
 blink::mojom::SerializedBlobPtr CloneSerializedBlob(
     const blink::mojom::SerializedBlobPtr& blob) {
   if (blob.is_null())
     return nullptr;
-  blink::mojom::BlobPtr blob_ptr(std::move(blob->blob));
-  blob_ptr->Clone(mojo::MakeRequest(&blob->blob));
-  return blink::mojom::SerializedBlob::New(
-      blob->uuid, blob->content_type, blob->size, blob_ptr.PassInterface());
+  mojo::Remote<blink::mojom::Blob> blob_remote(std::move(blob->blob));
+  blob_remote->Clone(blob->blob.InitWithNewPipeAndPassReceiver());
+  return blink::mojom::SerializedBlob::New(blob->uuid, blob->content_type,
+                                           blob->size, blob_remote.Unbind());
 }
 
 }  // namespace
diff --git a/content/common/content_param_traits.cc b/content/common/content_param_traits.cc
index 7a5c824a..c561ea6 100644
--- a/content/common/content_param_traits.cc
+++ b/content/common/content_param_traits.cc
@@ -223,7 +223,7 @@
     WriteParam(m, p->uuid);
     WriteParam(m, p->content_type);
     WriteParam(m, p->size);
-    WriteParam(m, p->blob.PassHandle().release());
+    WriteParam(m, p->blob.PassPipe().release());
   }
 
   static bool Read(const base::Pickle* m,
diff --git a/content/common/frame.mojom b/content/common/frame.mojom
index 79660beb..a99a8f3d 100644
--- a/content/common/frame.mojom
+++ b/content/common/frame.mojom
@@ -9,6 +9,7 @@
 import "content/common/native_types.mojom";
 import "content/common/navigation_client.mojom";
 import "content/common/navigation_params.mojom";
+import "content/public/common/browser_controls_state.mojom";
 import "content/public/common/resource_type.mojom";
 import "content/public/common/resource_load_info.mojom";
 import "content/public/common/transferrable_url_loader.mojom";
@@ -71,6 +72,13 @@
   ExtractSmartClipData(gfx.mojom.Rect rect)
       => (mojo_base.mojom.String16 text, mojo_base.mojom.String16 html,
               gfx.mojom.Rect clip_rect);
+
+  // Notifies the renderer whether hiding/showing the browser controls is
+  // enabled, what the current state should be, and whether or not to
+  // animate to the proper state.
+  UpdateBrowserControlsState(BrowserControlsState constraints,
+                             BrowserControlsState current,
+                             bool animate);
 };
 
 // Implemented by the frame provider and currently must be associated with the
diff --git a/content/public/browser/render_frame_host.h b/content/public/browser/render_frame_host.h
index 6811d5c..97d4c1b 100644
--- a/content/public/browser/render_frame_host.h
+++ b/content/public/browser/render_frame_host.h
@@ -15,6 +15,7 @@
 #include "build/build_config.h"
 #include "content/common/content_export.h"
 #include "content/public/browser/page_visibility_state.h"
+#include "content/public/common/browser_controls_state.h"
 #include "content/public/common/isolated_world_ids.h"
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_sender.h"
@@ -416,6 +417,13 @@
   // FrameTreeNode associated with this RenderFrameHost.
   virtual bool HasTransientUserActivation() = 0;
 
+  // Notifies the renderer whether hiding/showing the browser controls is
+  // enabled, what the current state should be, and whether or not to animate to
+  // the proper state.
+  virtual void UpdateBrowserControlsState(BrowserControlsState constraints,
+                                          BrowserControlsState current,
+                                          bool animate) = 0;
+
  private:
   // This interface should only be implemented inside content.
   friend class RenderFrameHostImpl;
diff --git a/content/public/common/BUILD.gn b/content/public/common/BUILD.gn
index 7f789e1..a1b2f7e3 100644
--- a/content/public/common/BUILD.gn
+++ b/content/public/common/BUILD.gn
@@ -344,6 +344,7 @@
   ]
 
   sources = [
+    "browser_controls_state.mojom",
     "drop_data.mojom",
     "fullscreen_video_element.mojom",
     "resource_load_info.mojom",
diff --git a/content/public/common/browser_controls_state.mojom b/content/public/common/browser_controls_state.mojom
new file mode 100644
index 0000000..3ec41b84
--- /dev/null
+++ b/content/public/common/browser_controls_state.mojom
@@ -0,0 +1,8 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+module content.mojom;
+
+[Native]
+enum BrowserControlsState;
diff --git a/content/public/common/browser_controls_state.typemap b/content/public/common/browser_controls_state.typemap
new file mode 100644
index 0000000..7f185e7
--- /dev/null
+++ b/content/public/common/browser_controls_state.typemap
@@ -0,0 +1,9 @@
+# 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.
+
+mojom = "//content/public/common/browser_controls_state.mojom"
+public_headers = [ "//content/public/common/browser_controls_state.h" ]
+traits_headers = [ "//content/public/common/common_param_traits_macros.h" ]
+type_mappings =
+    [ "content.mojom.BrowserControlsState=::content::BrowserControlsState" ]
diff --git a/content/public/common/common_param_traits_macros.h b/content/public/common/common_param_traits_macros.h
index dd1363a6..4249ab9 100644
--- a/content/public/common/common_param_traits_macros.h
+++ b/content/public/common/common_param_traits_macros.h
@@ -10,6 +10,7 @@
 
 #include "build/build_config.h"
 #include "cc/input/touch_action.h"
+#include "content/public/common/browser_controls_state.h"
 #include "content/public/common/drop_data.h"
 #include "content/public/common/referrer.h"
 #include "content/public/common/web_preferences.h"
@@ -42,6 +43,9 @@
 #undef IPC_MESSAGE_EXPORT
 #define IPC_MESSAGE_EXPORT CONTENT_EXPORT
 
+IPC_ENUM_TRAITS_MAX_VALUE(content::BrowserControlsState,
+                          content::BROWSER_CONTROLS_STATE_LAST)
+
 IPC_ENUM_TRAITS_VALIDATE(ui::PageTransition,
                          ((value &
                            ui::PageTransition::PAGE_TRANSITION_CORE_MASK) <=
diff --git a/content/public/common/typemaps.gni b/content/public/common/typemaps.gni
index 7b4138b..0f203696 100644
--- a/content/public/common/typemaps.gni
+++ b/content/public/common/typemaps.gni
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 typemaps = [
+  "//content/public/common/browser_controls_state.typemap",
   "//content/public/common/drop_data.typemap",
   "//content/public/common/resource_type.typemap",
   "//content/public/common/webplugininfo.typemap",
diff --git a/content/public/renderer/render_view.h b/content/public/renderer/render_view.h
index b454d8a..3403d7e 100644
--- a/content/public/renderer/render_view.h
+++ b/content/public/renderer/render_view.h
@@ -12,7 +12,6 @@
 #include "base/strings/string16.h"
 #include "build/build_config.h"
 #include "content/common/content_export.h"
-#include "content/public/common/browser_controls_state.h"
 #include "ipc/ipc_sender.h"
 #include "ui/gfx/geometry/rect_f.h"
 #include "ui/gfx/native_widget_types.h"
@@ -98,10 +97,6 @@
   // Returns |renderer_preferences_.accept_languages| value.
   virtual const std::string& GetAcceptLanguages() = 0;
 
-  virtual void UpdateBrowserControlsState(BrowserControlsState constraints,
-                                          BrowserControlsState current,
-                                          bool animate) = 0;
-
   // Converts the |rect| from Viewport coordinates to Window coordinates.
   // See blink::WebWidgetClient::convertViewportToWindow for more details.
   virtual void ConvertViewportToWindowViaWidget(blink::WebRect* rect) = 0;
diff --git a/content/public/test/test_renderer_host.cc b/content/public/test/test_renderer_host.cc
index f8d2c8dd..0c15f2a 100644
--- a/content/public/test/test_renderer_host.cc
+++ b/content/public/test/test_renderer_host.cc
@@ -111,12 +111,14 @@
       rvh_factory_(new TestRenderViewHostFactory(rph_factory_.get())),
       rfh_factory_(new TestRenderFrameHostFactory()),
       rwhi_factory_(new TestRenderWidgetHostFactory()) {
-  // A MessageLoop is needed for Mojo bindings to graphics services. Some
-  // tests have their own, so this only creates one when none exists. This
-  // means tests must ensure any MessageLoop they make is created before
-  // the RenderViewHostTestEnabler.
-  if (!base::MessageLoopCurrent::Get())
-    task_environment_ = std::make_unique<base::test::TaskEnvironment>();
+  // A TaskEnvironment is needed on the main thread for Mojo bindings to
+  // graphics services. Some tests have their own, so this only creates one
+  // (single-threaded) when none exists. This means tests must ensure any
+  // TaskEnvironment they make is created before the RenderViewHostTestEnabler.
+  if (!base::MessageLoopCurrent::Get()) {
+    task_environment_ =
+        std::make_unique<base::test::SingleThreadTaskEnvironment>();
+  }
 #if !defined(OS_ANDROID)
   ImageTransportFactory::SetFactory(
       std::make_unique<TestImageTransportFactory>());
diff --git a/content/public/test/test_renderer_host.h b/content/public/test/test_renderer_host.h
index d7b5ad0c..d019a30 100644
--- a/content/public/test/test_renderer_host.h
+++ b/content/public/test/test_renderer_host.h
@@ -154,7 +154,7 @@
 #if defined(OS_ANDROID)
   std::unique_ptr<display::Screen> screen_;
 #endif
-  std::unique_ptr<base::test::TaskEnvironment> task_environment_;
+  std::unique_ptr<base::test::SingleThreadTaskEnvironment> task_environment_;
   std::unique_ptr<MockRenderProcessHostFactory> rph_factory_;
   std::unique_ptr<TestRenderViewHostFactory> rvh_factory_;
   std::unique_ptr<TestRenderFrameHostFactory> rfh_factory_;
diff --git a/content/renderer/loader/web_url_loader_impl.cc b/content/renderer/loader/web_url_loader_impl.cc
index 5b47e50..f2178f52 100644
--- a/content/renderer/loader/web_url_loader_impl.cc
+++ b/content/renderer/loader/web_url_loader_impl.cc
@@ -1214,7 +1214,7 @@
         WebString::FromLatin1(sync_load_response.downloaded_blob->uuid),
         WebString::FromLatin1(sync_load_response.downloaded_blob->content_type),
         sync_load_response.downloaded_blob->size,
-        sync_load_response.downloaded_blob->blob.PassHandle());
+        sync_load_response.downloaded_blob->blob.PassPipe());
   }
 
   data.Assign(sync_load_response.data.data(), sync_load_response.data.size());
diff --git a/content/renderer/media/webrtc/media_stream_track_metrics.cc b/content/renderer/media/webrtc/media_stream_track_metrics.cc
index f2451e9..e2b4c01 100644
--- a/content/renderer/media/webrtc/media_stream_track_metrics.cc
+++ b/content/renderer/media/webrtc/media_stream_track_metrics.cc
@@ -261,11 +261,11 @@
       direction);
 }
 
-blink::mojom::MediaStreamTrackMetricsHostPtr&
+mojo::Remote<blink::mojom::MediaStreamTrackMetricsHost>&
 MediaStreamTrackMetrics::GetMediaStreamTrackMetricsHost() {
   if (!track_metrics_host_) {
     ChildThreadImpl::current()->BindHostReceiver(
-        mojo::MakeRequest(&track_metrics_host_));
+        track_metrics_host_.BindNewPipeAndPassReceiver());
   }
   return track_metrics_host_;
 }
diff --git a/content/renderer/media/webrtc/media_stream_track_metrics.h b/content/renderer/media/webrtc/media_stream_track_metrics.h
index 0125c07..5b2657c 100644
--- a/content/renderer/media/webrtc/media_stream_track_metrics.h
+++ b/content/renderer/media/webrtc/media_stream_track_metrics.h
@@ -12,6 +12,7 @@
 
 #include "base/sequence_checker.h"
 #include "content/common/content_export.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
 #include "third_party/webrtc/api/peer_connection_interface.h"
 
@@ -86,10 +87,10 @@
   // track object and the PeerConnection it is attached to both exist.
   uint64_t MakeUniqueId(const std::string& track_id, Direction direction);
 
-  blink::mojom::MediaStreamTrackMetricsHostPtr&
+  mojo::Remote<blink::mojom::MediaStreamTrackMetricsHost>&
   GetMediaStreamTrackMetricsHost();
 
-  blink::mojom::MediaStreamTrackMetricsHostPtr track_metrics_host_;
+  mojo::Remote<blink::mojom::MediaStreamTrackMetricsHost> track_metrics_host_;
 
   typedef std::vector<std::unique_ptr<MediaStreamTrackMetricsObserver>>
       ObserverVector;
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
index 03d173d..e3d4ffc 100644
--- a/content/renderer/render_frame_impl.cc
+++ b/content/renderer/render_frame_impl.cc
@@ -2826,6 +2826,13 @@
   frame_->SetLifecycleState(state);
 }
 
+void RenderFrameImpl::UpdateBrowserControlsState(
+    BrowserControlsState constraints,
+    BrowserControlsState current,
+    bool animate) {
+  render_view_->UpdateBrowserControlsState(constraints, current, animate);
+}
+
 void RenderFrameImpl::VisibilityChanged(
     blink::mojom::FrameVisibility visibility) {
   GetFrameHost()->VisibilityChanged(visibility);
@@ -7701,8 +7708,9 @@
 #endif
 }
 
-void RenderFrameImpl::BindWidget(mojom::WidgetRequest request) {
-  GetLocalRootRenderWidget()->SetWidgetBinding(std::move(request));
+void RenderFrameImpl::BindWidget(
+    mojo::PendingReceiver<mojom::Widget> receiver) {
+  GetLocalRootRenderWidget()->SetWidgetReceiver(std::move(receiver));
 }
 
 blink::WebComputedAXTree* RenderFrameImpl::GetOrCreateWebComputedAXTree() {
diff --git a/content/renderer/render_frame_impl.h b/content/renderer/render_frame_impl.h
index 0dc583d..6b95cb5e 100644
--- a/content/renderer/render_frame_impl.h
+++ b/content/renderer/render_frame_impl.h
@@ -41,6 +41,7 @@
 #include "content/common/renderer.mojom.h"
 #include "content/common/unique_name_helper.h"
 #include "content/common/widget.mojom.h"
+#include "content/public/common/browser_controls_state.h"
 #include "content/public/common/fullscreen_video_element.mojom.h"
 #include "content/public/common/javascript_dialog_type.h"
 #include "content/public/common/previews_state.h"
@@ -64,6 +65,7 @@
 #include "mojo/public/cpp/bindings/associated_receiver.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "mojo/public/cpp/bindings/binding_set.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/system/data_pipe.h"
@@ -524,6 +526,9 @@
   void ResumeBlockedRequests() override;
   void CancelBlockedRequests() override;
   void SetLifecycleState(blink::mojom::FrameLifecycleState state) override;
+  void UpdateBrowserControlsState(BrowserControlsState constraints,
+                                  BrowserControlsState current,
+                                  bool animate) override;
 
 #if defined(OS_ANDROID)
   void ExtractSmartClipData(
@@ -1334,7 +1339,7 @@
 
   void SendUpdateFaviconURL();
 
-  void BindWidget(mojom::WidgetRequest request);
+  void BindWidget(mojo::PendingReceiver<mojom::Widget> receiver);
 
   void ShowDeferredContextMenu(const ContextMenuParams& params);
 
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index 2da8e83..36012ef 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -1691,6 +1691,27 @@
   history_navigation_virtual_time_pauser_.UnpauseVirtualTime();
 }
 
+void RenderViewImpl::UpdateBrowserControlsState(
+    BrowserControlsState constraints,
+    BrowserControlsState current,
+    bool animate) {
+  TRACE_EVENT2("renderer", "RenderViewImpl::UpdateBrowserControlsState",
+               "Constraint", static_cast<int>(constraints), "Current",
+               static_cast<int>(current));
+  TRACE_EVENT_INSTANT1("renderer", "is_animated", TRACE_EVENT_SCOPE_THREAD,
+                       "animated", animate);
+
+  if (GetWidget() && GetWidget()->layer_tree_view()) {
+    GetWidget()
+        ->layer_tree_view()
+        ->layer_tree_host()
+        ->UpdateBrowserControlsState(ContentToCc(constraints),
+                                     ContentToCc(current), animate);
+  }
+
+  top_controls_constraints_ = constraints;
+}
+
 void RenderViewImpl::RegisterRendererPreferenceWatcher(
     mojo::PendingRemote<blink::mojom::RendererPreferenceWatcher> watcher) {
   renderer_preference_watchers_.Add(std::move(watcher));
@@ -1737,27 +1758,6 @@
   return renderer_preferences_.accept_languages;
 }
 
-void RenderViewImpl::UpdateBrowserControlsState(
-    BrowserControlsState constraints,
-    BrowserControlsState current,
-    bool animate) {
-  TRACE_EVENT2("renderer", "RenderViewImpl::UpdateBrowserControlsState",
-               "Constraint", static_cast<int>(constraints), "Current",
-               static_cast<int>(current));
-  TRACE_EVENT_INSTANT1("renderer", "is_animated", TRACE_EVENT_SCOPE_THREAD,
-                       "animated", animate);
-
-  if (GetWidget() && GetWidget()->layer_tree_view()) {
-    GetWidget()
-        ->layer_tree_view()
-        ->layer_tree_host()
-        ->UpdateBrowserControlsState(ContentToCc(constraints),
-                                     ContentToCc(current), animate);
-  }
-
-  top_controls_constraints_ = constraints;
-}
-
 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
 
 void RenderViewImpl::didScrollWithKeyboard(const blink::WebSize& delta) {
diff --git a/content/renderer/render_view_impl.h b/content/renderer/render_view_impl.h
index 3905c06..7562387 100644
--- a/content/renderer/render_view_impl.h
+++ b/content/renderer/render_view_impl.h
@@ -208,6 +208,10 @@
   void NavigateBackForwardSoon(int offset, bool has_user_gesture);
   void DidCommitProvisionalHistoryLoad();
 
+  void UpdateBrowserControlsState(BrowserControlsState constraints,
+                                  BrowserControlsState current,
+                                  bool animate);
+
   // Registers a watcher to observe changes in the
   // blink::mojom::RendererPreferences.
   void RegisterRendererPreferenceWatcher(
@@ -275,9 +279,6 @@
                                      const std::string& value) override;
   void ClearEditCommands() override;
   const std::string& GetAcceptLanguages() override;
-  void UpdateBrowserControlsState(BrowserControlsState constraints,
-                                  BrowserControlsState current,
-                                  bool animate) override;
 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
   virtual void didScrollWithKeyboard(const blink::WebSize& delta);
 #endif
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 4777600..746a567f 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -423,14 +423,14 @@
     bool is_undead,
     bool never_visible) {
   if (g_create_render_widget_for_frame) {
-    return g_create_render_widget_for_frame(widget_routing_id, compositor_deps,
-                                            screen_info, display_mode,
-                                            is_undead, never_visible, nullptr);
+    return g_create_render_widget_for_frame(
+        widget_routing_id, compositor_deps, screen_info, display_mode,
+        is_undead, never_visible, mojo::NullReceiver());
   }
 
   return std::make_unique<RenderWidget>(
       widget_routing_id, compositor_deps, screen_info, display_mode, is_undead,
-      /*hidden=*/true, never_visible, nullptr);
+      /*hidden=*/true, never_visible, mojo::NullReceiver());
 }
 
 RenderWidget* RenderWidget::CreateForPopup(
@@ -440,10 +440,10 @@
     blink::WebDisplayMode display_mode,
     bool hidden,
     bool never_visible,
-    mojom::WidgetRequest widget_request) {
+    mojo::PendingReceiver<mojom::Widget> widget_receiver) {
   return new RenderWidget(widget_routing_id, compositor_deps, screen_info,
                           display_mode, /*is_undead=*/false, hidden,
-                          never_visible, std::move(widget_request));
+                          never_visible, std::move(widget_receiver));
 }
 
 RenderWidget::RenderWidget(int32_t widget_routing_id,
@@ -453,7 +453,7 @@
                            bool is_undead,
                            bool hidden,
                            bool never_visible,
-                           mojom::WidgetRequest widget_request)
+                           mojo::PendingReceiver<mojom::Widget> widget_receiver)
     : routing_id_(widget_routing_id),
       compositor_deps_(compositor_deps),
       is_hidden_(hidden),
@@ -463,7 +463,7 @@
       next_previous_flags_(kInvalidNextPreviousFlagsValue),
       screen_info_(screen_info),
       frame_swap_message_queue_(new FrameSwapMessageQueue(routing_id_)),
-      widget_binding_(this, std::move(widget_request)) {
+      widget_receiver_(this, std::move(widget_receiver)) {
   DCHECK_NE(routing_id_, MSG_ROUTING_NONE);
   DCHECK(RenderThread::IsMainThread());
 
@@ -3714,11 +3714,12 @@
                                               std::move(host));
 }
 
-void RenderWidget::SetWidgetBinding(mojom::WidgetRequest request) {
-  // Close the old binding if there was one.
+void RenderWidget::SetWidgetReceiver(
+    mojo::PendingReceiver<mojom::Widget> recevier) {
+  // Close the old receiver if there was one.
   // A RenderWidgetHost should not need more than one channel.
-  widget_binding_.Close();
-  widget_binding_.Bind(std::move(request));
+  widget_receiver_.reset();
+  widget_receiver_.Bind(std::move(recevier));
 }
 
 void RenderWidget::SetMouseCapture(bool capture) {
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 372bf0c..053214e 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -49,7 +49,8 @@
 #include "ipc/ipc_listener.h"
 #include "ipc/ipc_message.h"
 #include "ipc/ipc_sender.h"
-#include "mojo/public/cpp/bindings/binding.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
 #include "ppapi/buildflags/buildflags.h"
 #include "services/network/public/mojom/referrer_policy.mojom.h"
 #include "third_party/blink/public/common/frame/occlusion_state.h"
@@ -164,7 +165,7 @@
                bool is_undead,
                bool hidden,
                bool never_visible,
-               mojom::WidgetRequest widget_request);
+               mojo::PendingReceiver<mojom::Widget> widget_receiver);
 
   ~RenderWidget() override;
 
@@ -181,14 +182,14 @@
 
   // Convenience type for creation method taken by InstallCreateForFrameHook().
   // The method signature matches the RenderWidget constructor.
-  using CreateRenderWidgetFunction =
-      std::unique_ptr<RenderWidget> (*)(int32_t,
-                                        CompositorDependencies*,
-                                        const ScreenInfo&,
-                                        blink::WebDisplayMode display_mode,
-                                        bool is_undead,
-                                        bool never_visible,
-                                        mojom::WidgetRequest widget_request);
+  using CreateRenderWidgetFunction = std::unique_ptr<RenderWidget> (*)(
+      int32_t,
+      CompositorDependencies*,
+      const ScreenInfo&,
+      blink::WebDisplayMode display_mode,
+      bool is_undead,
+      bool never_visible,
+      mojo::PendingReceiver<mojom::Widget> widget_receiver);
   // Overrides the implementation of CreateForFrame() function below. Used by
   // web tests to return a partial fake of RenderWidget.
   static void InstallCreateForFrameHook(
@@ -210,13 +211,14 @@
   // A RenderWidget popup is owned by the browser process. The object will be
   // destroyed by the WidgetMsg_Close message. The object can request its own
   // destruction via ClosePopupWidgetSoon().
-  static RenderWidget* CreateForPopup(int32_t widget_routing_id,
-                                      CompositorDependencies* compositor_deps,
-                                      const ScreenInfo& screen_info,
-                                      blink::WebDisplayMode display_mode,
-                                      bool hidden,
-                                      bool never_visible,
-                                      mojom::WidgetRequest widget_request);
+  static RenderWidget* CreateForPopup(
+      int32_t widget_routing_id,
+      CompositorDependencies* compositor_deps,
+      const ScreenInfo& screen_info,
+      blink::WebDisplayMode display_mode,
+      bool hidden,
+      bool never_visible,
+      mojo::PendingReceiver<mojom::Widget> widget_receiver);
 
   // Initialize a new RenderWidget for a popup. The |show_callback| is called
   // when RenderWidget::Show() happens. This method increments the reference
@@ -668,7 +670,7 @@
   // composition info (when in monitor mode).
   void OnRequestCompositionUpdates(bool immediate_request,
                                    bool monitor_updates);
-  void SetWidgetBinding(mojom::WidgetRequest request);
+  void SetWidgetReceiver(mojo::PendingReceiver<mojom::Widget> receiver);
 
   void SetMouseCapture(bool capture);
 
@@ -1181,7 +1183,7 @@
 
   scoped_refptr<MainThreadEventQueue> input_event_queue_;
 
-  mojo::Binding<mojom::Widget> widget_binding_;
+  mojo::Receiver<mojom::Widget> widget_receiver_;
 
   gfx::Rect compositor_visible_rect_;
 
diff --git a/content/renderer/render_widget_fullscreen_pepper.cc b/content/renderer/render_widget_fullscreen_pepper.cc
index 35e8718..5db5b666 100644
--- a/content/renderer/render_widget_fullscreen_pepper.cc
+++ b/content/renderer/render_widget_fullscreen_pepper.cc
@@ -278,12 +278,12 @@
     PepperPluginInstanceImpl* plugin,
     const blink::WebURL& local_main_frame_url,
     const ScreenInfo& screen_info,
-    mojom::WidgetRequest widget_request) {
+    mojo::PendingReceiver<mojom::Widget> widget_receiver) {
   DCHECK_NE(MSG_ROUTING_NONE, routing_id);
   DCHECK(show_callback);
   RenderWidgetFullscreenPepper* widget =
       new RenderWidgetFullscreenPepper(routing_id, compositor_deps, plugin,
-                                       screen_info, std::move(widget_request));
+                                       screen_info, std::move(widget_receiver));
   widget->Init(std::move(show_callback),
                new PepperWidget(widget, local_main_frame_url));
   return widget;
@@ -294,7 +294,7 @@
     CompositorDependencies* compositor_deps,
     PepperPluginInstanceImpl* plugin,
     const ScreenInfo& screen_info,
-    mojom::WidgetRequest widget_request)
+    mojo::PendingReceiver<mojom::Widget> widget_receiver)
     : RenderWidget(routing_id,
                    compositor_deps,
                    screen_info,
@@ -302,7 +302,7 @@
                    false,
                    false,
                    false,
-                   std::move(widget_request)),
+                   std::move(widget_receiver)),
       plugin_(plugin),
       layer_(nullptr),
       mouse_lock_dispatcher_(new FullscreenMouseLockDispatcher(this)) {
diff --git a/content/renderer/render_widget_fullscreen_pepper.h b/content/renderer/render_widget_fullscreen_pepper.h
index 00b6d03..0c1dc33 100644
--- a/content/renderer/render_widget_fullscreen_pepper.h
+++ b/content/renderer/render_widget_fullscreen_pepper.h
@@ -13,6 +13,7 @@
 #include "content/renderer/mouse_lock_dispatcher.h"
 #include "content/renderer/pepper/fullscreen_container.h"
 #include "content/renderer/render_widget.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/web/web_widget.h"
 #include "url/gurl.h"
 
@@ -39,7 +40,7 @@
       PepperPluginInstanceImpl* plugin,
       const blink::WebURL& local_main_frame_url,
       const ScreenInfo& screen_info,
-      mojom::WidgetRequest widget_request);
+      mojo::PendingReceiver<mojom::Widget> widget_receiver);
 
   // pepper::FullscreenContainer API.
   void ScrollRect(int dx, int dy, const blink::WebRect& rect) override;
@@ -58,11 +59,12 @@
   }
 
  protected:
-  RenderWidgetFullscreenPepper(int32_t routing_id,
-                               CompositorDependencies* compositor_deps,
-                               PepperPluginInstanceImpl* plugin,
-                               const ScreenInfo& screen_info,
-                               mojom::WidgetRequest widget_request);
+  RenderWidgetFullscreenPepper(
+      int32_t routing_id,
+      CompositorDependencies* compositor_deps,
+      PepperPluginInstanceImpl* plugin,
+      const ScreenInfo& screen_info,
+      mojo::PendingReceiver<mojom::Widget> widget_receiver);
   ~RenderWidgetFullscreenPepper() override;
 
   // RenderWidget API.
diff --git a/content/renderer/render_widget_unittest.cc b/content/renderer/render_widget_unittest.cc
index b05042c..8acd0ad 100644
--- a/content/renderer/render_widget_unittest.cc
+++ b/content/renderer/render_widget_unittest.cc
@@ -187,7 +187,7 @@
                      false,
                      false,
                      false,
-                     nullptr),
+                     mojo::NullReceiver()),
         always_overscroll_(false) {
     InitForPopup(base::NullCallback(), &mock_page_popup_);
 
@@ -456,7 +456,7 @@
                      false,
                      false,
                      false,
-                     nullptr) {
+                     mojo::NullReceiver()) {
     InitForPopup(RenderWidget::ShowCallback(), &stub_page_popup_);
   }
   ~PopupRenderWidget() override { DCHECK(shutdown_); }
diff --git a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
index 7867910..0d01a71 100644
--- a/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
+++ b/content/renderer/service_worker/service_worker_subresource_loader_unittest.cc
@@ -25,6 +25,7 @@
 #include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "mojo/public/cpp/bindings/remote.h"
+#include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "mojo/public/cpp/bindings/strong_binding.h"
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "net/http/http_util.h"
@@ -167,9 +168,9 @@
     blob_body_ = blink::mojom::SerializedBlob::New();
     blob_body_->uuid = "dummy-blob-uuid";
     blob_body_->size = body.size();
-    mojo::MakeStrongBinding(
+    mojo::MakeSelfOwnedReceiver(
         std::make_unique<FakeBlob>(std::move(metadata), std::move(body)),
-        mojo::MakeRequest(&blob_body_->blob));
+        blob_body_->blob.InitWithNewPipeAndPassReceiver());
   }
 
   // Tells this controller to respond to fetch events with a 206 partial
@@ -278,8 +279,9 @@
         auto blob = blink::mojom::SerializedBlob::New();
         blob->uuid = "dummy-blob-uuid";
         blob->size = size;
-        mojo::MakeStrongBinding(std::make_unique<FakeBlob>(base::nullopt, body),
-                                mojo::MakeRequest(&blob->blob));
+        mojo::MakeSelfOwnedReceiver(
+            std::make_unique<FakeBlob>(base::nullopt, body),
+            blob->blob.InitWithNewPipeAndPassReceiver());
 
         // Respond with a 206 response.
         auto response = OkResponse(std::move(blob), response_source_);
diff --git a/content/shell/browser/web_test/blink_test_controller.cc b/content/shell/browser/web_test/blink_test_controller.cc
index 0edc28d..400d959 100644
--- a/content/shell/browser/web_test/blink_test_controller.cc
+++ b/content/shell/browser/web_test/blink_test_controller.cc
@@ -994,6 +994,9 @@
     } else {
       did_send_initial_test_configuration_ = true;
       GetWebTestControlPtr(frame)->SetTestConfiguration(std::move(params));
+      // Tests should always start with the browser controls hidden.
+      frame->UpdateBrowserControlsState(BROWSER_CONTROLS_STATE_BOTH,
+                                        BROWSER_CONTROLS_STATE_HIDDEN, false);
     }
   }
 
diff --git a/content/shell/renderer/web_test/blink_test_runner.cc b/content/shell/renderer/web_test/blink_test_runner.cc
index fe016ca..2a7515a 100644
--- a/content/shell/renderer/web_test/blink_test_runner.cc
+++ b/content/shell/renderer/web_test/blink_test_runner.cc
@@ -800,10 +800,6 @@
                         WebSize(local_params->initial_size.width(),
                                 local_params->initial_size.height()));
 
-  // Tests should always start with the browser controls hidden.
-  render_view()->UpdateBrowserControlsState(
-      BROWSER_CONTROLS_STATE_BOTH, BROWSER_CONTROLS_STATE_HIDDEN, false);
-
   WebTestRenderThreadObserver::GetInstance()
       ->test_interfaces()
       ->TestRunner()
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index b0971ea3..2d750e1 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -482,7 +482,9 @@
       "../public/test/fake_pepper_plugin_instance.cc",
       "../public/test/fake_pepper_plugin_instance.h",
       "../public/test/ppapi_test_utils.cc",
+      "../public/test/ppapi_test_utils.h",
       "ppapi_unittest.cc",
+      "ppapi_unittest.h",
     ]
     deps += [
       "//content/ppapi_plugin",
diff --git a/content/test/data/accessibility/aria/aria-readonly-expected-android.txt b/content/test/data/accessibility/aria/aria-readonly-expected-android.txt
index 3f266344..51bc21a 100644
--- a/content/test/data/accessibility/aria/aria-readonly-expected-android.txt
+++ b/content/test/data/accessibility/aria/aria-readonly-expected-android.txt
@@ -2,22 +2,22 @@
 ++android.view.View
 ++++android.widget.EditText clickable editable_text focusable has_non_empty_value name='Readonly-false input' input_type=1 text_change_added_count=20
 ++android.view.View
-++++android.widget.EditText clickable editable_text focusable has_non_empty_value name='Readonly-true input' input_type=1 text_change_added_count=19
+++++android.widget.EditText disabled editable_text focusable has_non_empty_value name='Readonly-true input' input_type=1 text_change_added_count=19
 ++android.view.View clickable focusable name='Readonly-false plain div'
 ++android.view.View clickable focusable name='Readonly-true plain div'
 ++android.widget.EditText clickable editable_text focusable multiline hint='Readonly-false contenteditable div'
 ++android.widget.EditText clickable editable_text focusable multiline hint='Readonly-true contenteditable div'
 ++android.widget.EditText clickable editable_text focusable hint='Readonly-false role unimplemented textbox'
-++android.widget.EditText clickable editable_text focusable hint='Readonly-true role unimplemented textbox'
+++android.widget.EditText disabled editable_text focusable hint='Readonly-true role unimplemented textbox'
 ++android.widget.EditText clickable editable_text focusable hint='Readonly-false contenteditable textbox'
-++android.widget.EditText clickable editable_text focusable hint='Readonly-true contenteditable textbox'
-++android.widget.CheckBox role_description='checkbox' checkable clickable name='Readonly checkbox'
-++android.widget.EditText clickable editable_text focusable hint='Readonly combobox' input_type=1
-++android.widget.ListView role_description='list box' clickable collection name='Readonly listbox'
-++android.view.View role_description='radio group' name='Readonly radiogroup'
-++android.widget.SeekBar role_description='slider' range name='Readonly slider' item_index=50 item_count=100 range_max=100 range_current_value=50
-++android.widget.EditText role_description='spin button' name='Readonly spinbutton'
-++android.view.MenuItem role_description='checkbox' checkable clickable name='Readonly menuitemcheckbox'
-++android.view.MenuItem role_description='radio button' checkable clickable name='Readonly menuitemradio'
-++android.widget.EditText role_description='search text field' clickable editable_text hint='Readonly searchbox'
-++android.widget.CheckBox role_description='switch' checkable clickable name='Readonly switch'
+++android.widget.EditText disabled editable_text focusable hint='Readonly-true contenteditable textbox'
+++android.widget.CheckBox role_description='checkbox' checkable disabled name='Readonly checkbox'
+++android.widget.EditText disabled editable_text focusable hint='Readonly combobox' input_type=1
+++android.widget.ListView role_description='list box' collection disabled name='Readonly listbox'
+++android.view.View role_description='radio group' disabled name='Readonly radiogroup'
+++android.widget.SeekBar role_description='slider' disabled range name='Readonly slider' item_index=50 item_count=100 range_max=100 range_current_value=50
+++android.widget.EditText role_description='spin button' disabled name='Readonly spinbutton'
+++android.view.MenuItem role_description='checkbox' checkable disabled name='Readonly menuitemcheckbox'
+++android.view.MenuItem role_description='radio button' checkable disabled name='Readonly menuitemradio'
+++android.widget.EditText role_description='search text field' disabled editable_text hint='Readonly searchbox'
+++android.widget.CheckBox role_description='switch' checkable disabled name='Readonly switch'
diff --git a/content/test/web_test_support.cc b/content/test/web_test_support.cc
index 95de392..ccbd88b 100644
--- a/content/test/web_test_support.cc
+++ b/content/test/web_test_support.cc
@@ -36,6 +36,7 @@
 #include "content/shell/test_runner/web_test_interfaces.h"
 #include "content/shell/test_runner/web_view_test_proxy.h"
 #include "content/shell/test_runner/web_widget_test_proxy.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_float_rect.h"
 #include "third_party/blink/public/platform/web_input_event.h"
@@ -96,10 +97,10 @@
     blink::WebDisplayMode display_mode,
     bool swapped_out,
     bool never_visible,
-    mojom::WidgetRequest widget_request) {
+    mojo::PendingReceiver<mojom::Widget> widget_receiver) {
   return std::make_unique<test_runner::WebWidgetTestProxy>(
       routing_id, compositor_deps, screen_info, display_mode, swapped_out,
-      /*hidden=*/true, never_visible, std::move(widget_request));
+      /*hidden=*/true, never_visible, std::move(widget_receiver));
 }
 
 RenderFrameImpl* CreateWebFrameTestProxy(RenderFrameImpl::CreateParams params) {
diff --git a/device/gamepad/gamepad_service_unittest.cc b/device/gamepad/gamepad_service_unittest.cc
index e2d313c..75234dd5 100644
--- a/device/gamepad/gamepad_service_unittest.cc
+++ b/device/gamepad/gamepad_service_unittest.cc
@@ -81,7 +81,7 @@
   void SetUp() override;
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   device::MockGamepadDataFetcher* fetcher_;
   GamepadService* service_;
   std::unique_ptr<ConnectionListener> connection_listener_;
diff --git a/device/gamepad/gamepad_test_helpers.h b/device/gamepad/gamepad_test_helpers.h
index 1ea3643d..c41ecab2 100644
--- a/device/gamepad/gamepad_test_helpers.h
+++ b/device/gamepad/gamepad_test_helpers.h
@@ -60,7 +60,7 @@
 
  private:
   // This must be constructed before the system monitor.
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 
   DISALLOW_COPY_AND_ASSIGN(GamepadTestHelper);
 };
diff --git a/docs/contributing.md b/docs/contributing.md
index 32239ca..6bb13a6 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -282,6 +282,39 @@
 commit][direct-commit] a change, bypassing the commit queue. This should only
 be used in emergencies because it will bypass all the safety nets.
 
+## Code guidelines
+
+In addition to the adhering to the [styleguide][cr-styleguide], the following
+general rules of thumb can be helpful in navigating how to structure changes:
+
+- **Code in the Chromium project should be in service of code in the Chromium
+  project.** This is important so developers can understand the constraints
+  informing a design decision. Those constraints should be apparent from the
+  scope of code within the boundary of the project and its various
+  repositories.
+
+- **Code should only be moved to a central location (e.g., //base) when
+  multiple consumers would benefit.** We should resist the temptation to
+  build overly generic common libraries as that can lead to code bloat and
+  unnecessary complexity in common code.
+
+- **The code likely wasn't designed for everything we are trying to do with it
+  now.** Take time to refactor existing code to make sure the new feature or
+  subcomponent you are developing fits properly within the system. Technical
+  debt is easy to accumulate and is everyone's responsibility to avoid.
+
+- **Common code is everyone's responsibility.** Large files that are at the
+  cross-roads of many subsystems, where integration happens, can be some of the
+  most fragile in the system. As a companion to the previous point, be
+  cognizant of how you may be adding more complexity to the commons as you
+  venture to complete your task.
+
+- **Changes should include corresponding tests.** Automated testing is at the
+  heart of how we move forward as a project. All changes should include
+  corresponding tests so we can ensure that there is good coverage for code and
+  that future changes will be less likely to regress functionality. Protect
+  your code with tests!
+
 ## Tips
 
 ### Review etiquette
diff --git a/extensions/browser/api/serial/serial_apitest.cc b/extensions/browser/api/serial/serial_apitest.cc
index 1990b235..d69a12f6 100644
--- a/extensions/browser/api/serial/serial_apitest.cc
+++ b/extensions/browser/api/serial/serial_apitest.cc
@@ -356,11 +356,12 @@
   void FailEnumeratorRequest() { fail_enumerator_request_ = true; }
 
  protected:
-  void BindSerialPortManager(device::mojom::SerialPortManagerRequest request) {
+  void BindSerialPortManager(
+      mojo::PendingReceiver<device::mojom::SerialPortManager> receiver) {
     if (fail_enumerator_request_)
       return;
 
-    port_manager_->Bind(std::move(request));
+    port_manager_->Bind(std::move(receiver));
   }
 
   bool fail_enumerator_request_ = false;
diff --git a/extensions/browser/extension_function_histogram_value.h b/extensions/browser/extension_function_histogram_value.h
index 1ca160f5..9f5849b 100644
--- a/extensions/browser/extension_function_histogram_value.h
+++ b/extensions/browser/extension_function_histogram_value.h
@@ -1443,6 +1443,7 @@
   LOGINSTATE_GETSESSIONSTATE = 1380,
   AUTOTESTPRIVATE_GETARCSTARTTIME = 1381,
   AUTOTESTPRIVATE_SETOVERVIEWMODESTATE = 1382,
+  AUTOTESTPRIVATE_TAKESCREENSHOTFORDISPLAY = 1383,
   // Last entry: Add new entries above, then run:
   // python tools/metrics/histograms/update_extension_histograms.py
   ENUM_BOUNDARY
diff --git a/extensions/renderer/extension_throttle_simulation_unittest.cc b/extensions/renderer/extension_throttle_simulation_unittest.cc
index d35af48..35d25e3 100644
--- a/extensions/renderer/extension_throttle_simulation_unittest.cc
+++ b/extensions/renderer/extension_throttle_simulation_unittest.cc
@@ -507,8 +507,8 @@
 }
 
 TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
   Server unprotected_server(30, 1.0);
   RequesterResults unprotected_attacker_results;
   RequesterResults unprotected_client_results;
@@ -588,8 +588,8 @@
 }
 
 TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
   struct Stats {
     // Expected interval that we expect the ratio of downtime when anti-DDoS
     // is enabled and downtime when anti-DDoS is not enabled to fall within.
diff --git a/extensions/renderer/script_context_set_unittest.cc b/extensions/renderer/script_context_set_unittest.cc
index bbe83d9..0a46b00 100644
--- a/extensions/renderer/script_context_set_unittest.cc
+++ b/extensions/renderer/script_context_set_unittest.cc
@@ -21,7 +21,7 @@
 namespace extensions {
 
 TEST(ScriptContextSetTest, Lifecycle) {
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
   ScopedWebFrame web_frame;
   // Used by ScriptContextSet::Register().
   TestExtensionsRendererClient extensions_renderer_client;
diff --git a/fuchsia/base/agent_impl_unittests.cc b/fuchsia/base/agent_impl_unittests.cc
index c11dfdc..12317fe 100644
--- a/fuchsia/base/agent_impl_unittests.cc
+++ b/fuchsia/base/agent_impl_unittests.cc
@@ -100,9 +100,8 @@
     return nullptr;
   }
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   sys::OutgoingDirectory services_;
   std::unique_ptr<base::fuchsia::ServiceDirectoryClient> services_client_;
 
diff --git a/fuchsia/engine/context_provider_impl_unittest.cc b/fuchsia/engine/context_provider_impl_unittest.cc
index 6f30b41..2e8c026 100644
--- a/fuchsia/engine/context_provider_impl_unittest.cc
+++ b/fuchsia/engine/context_provider_impl_unittest.cc
@@ -46,9 +46,8 @@
 constexpr char kTitle[] = "Palindrome";
 
 MULTIPROCESS_TEST_MAIN(SpawnContextServer) {
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
 
   base::FilePath data_dir;
   CHECK(base::PathService::Get(base::DIR_APP_DATA, &data_dir));
@@ -179,9 +178,8 @@
   }
 
  protected:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   std::unique_ptr<ContextProviderImpl> provider_;
   fuchsia::web::ContextProviderPtr provider_ptr_;
   fidl::BindingSet<fuchsia::web::ContextProvider> bindings_;
diff --git a/fuchsia/engine/web_engine_debug_integration_test.cc b/fuchsia/engine/web_engine_debug_integration_test.cc
index 2168aebe..3b66a46 100644
--- a/fuchsia/engine/web_engine_debug_integration_test.cc
+++ b/fuchsia/engine/web_engine_debug_integration_test.cc
@@ -96,9 +96,8 @@
               fuchsia::web::ContextError::REMOTE_DEBUGGING_PORT_NOT_OPENED);
   }
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
 
   TestDebugListener dev_tools_listener_;
   fidl::Binding<fuchsia::web::DevToolsListener> dev_tools_listener_binding_;
diff --git a/fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc b/fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc
index a9d5772d..61d52a5a 100644
--- a/fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc
+++ b/fuchsia/mojom/fidl_interface_request_mojom_traits_unittest.cc
@@ -15,9 +15,8 @@
 using base::fuchsia::testfidl::TestInterfacePtr;
 
 TEST(InterfaceRequestStructTraitsTest, Serialization) {
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
   TestInterfacePtr test_ptr;
   fidl::InterfaceRequest<TestInterface> input_request = test_ptr.NewRequest();
   fidl::InterfaceRequest<TestInterface> output_request;
diff --git a/fuchsia/runners/cast/cast_runner_integration_test.cc b/fuchsia/runners/cast/cast_runner_integration_test.cc
index 88a63ea..37c9676 100644
--- a/fuchsia/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia/runners/cast/cast_runner_integration_test.cc
@@ -250,9 +250,8 @@
   }
 
   const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   net::EmbeddedTestServer test_server_;
 
   // Returns fake Cast application information to the CastRunner.
diff --git a/fuchsia/runners/cast/main.cc b/fuchsia/runners/cast/main.cc
index 65c55dd..ecafa69 100644
--- a/fuchsia/runners/cast/main.cc
+++ b/fuchsia/runners/cast/main.cc
@@ -16,9 +16,7 @@
 
   constexpr fuchsia::web::ContextFeatureFlags kCastRunnerFeatures =
       fuchsia::web::ContextFeatureFlags::NETWORK |
-      fuchsia::web::ContextFeatureFlags::AUDIO |
-      fuchsia::web::ContextFeatureFlags::VULKAN |
-      fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER;
+      fuchsia::web::ContextFeatureFlags::AUDIO;
 
   CastRunner runner(
       base::fuchsia::ComponentContextForCurrentProcess()->outgoing().get(),
diff --git a/fuchsia/runners/web/main.cc b/fuchsia/runners/web/main.cc
index ed0d1f7..0afb19f 100644
--- a/fuchsia/runners/web/main.cc
+++ b/fuchsia/runners/web/main.cc
@@ -16,9 +16,7 @@
 
   constexpr fuchsia::web::ContextFeatureFlags kWebRunnerFeatures =
       fuchsia::web::ContextFeatureFlags::NETWORK |
-      fuchsia::web::ContextFeatureFlags::AUDIO |
-      fuchsia::web::ContextFeatureFlags::VULKAN |
-      fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER;
+      fuchsia::web::ContextFeatureFlags::AUDIO;
 
   WebContentRunner runner(
       base::fuchsia::ComponentContextForCurrentProcess()->outgoing().get(),
diff --git a/fuchsia/runners/web/web_runner_smoke_test.cc b/fuchsia/runners/web/web_runner_smoke_test.cc
index dadc81f..81220a1 100644
--- a/fuchsia/runners/web/web_runner_smoke_test.cc
+++ b/fuchsia/runners/web/web_runner_smoke_test.cc
@@ -87,9 +87,8 @@
   bool test_html_requested_ = false;
   bool test_image_requested_ = false;
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
 
   sys::OutgoingDirectory outgoing_directory_;
   std::unique_ptr<base::fuchsia::ServiceProviderImpl> service_provider_;
diff --git a/gpu/angle_deqp_tests_main.cc b/gpu/angle_deqp_tests_main.cc
index 9122030..7567b6a 100644
--- a/gpu/angle_deqp_tests_main.cc
+++ b/gpu/angle_deqp_tests_main.cc
@@ -34,6 +34,10 @@
   base::CommandLine::Init(argc, argv);
   angle::InitTestHarness(&argc, argv);
   base::TestSuite test_suite(argc, argv);
+
+  // The process priority is lowered by the constructor of tcu::ANGLEPlatform().
+  test_suite.DisableCheckForProcessPriority();
+
   int rt = base::LaunchUnitTestsSerially(
       argc, argv, base::BindOnce(&RunHelper, base::Unretained(&test_suite)));
   return rt;
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
index 9634c13..f01e498 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d.cc
@@ -13,12 +13,19 @@
 #include "gpu/command_buffer/service/shared_image_representation.h"
 #include "gpu/command_buffer/service/texture_manager.h"
 #include "ui/gfx/buffer_format_util.h"
+#include "ui/gl/buildflags.h"
 #include "ui/gl/direct_composition_surface_win.h"
 #include "ui/gl/gl_angle_util_win.h"
 #include "ui/gl/gl_bindings.h"
 #include "ui/gl/gl_image_d3d.h"
 #include "ui/gl/trace_util.h"
 
+// Usage of BUILDFLAG(USE_DAWN) needs to be after the include for
+// ui/gl/buildflags.h
+#if BUILDFLAG(USE_DAWN)
+#include <dawn_native/D3D12Backend.h>
+#endif  // BUILDFLAG(USE_DAWN)
+
 namespace gpu {
 
 namespace {
@@ -70,6 +77,38 @@
   return true;
 }
 
+base::Optional<DXGI_FORMAT> VizFormatToDXGIFormat(
+    viz::ResourceFormat viz_resource_format) {
+  switch (viz_resource_format) {
+    case viz::RGBA_F16:
+      return DXGI_FORMAT_R16G16B16A16_FLOAT;
+    case viz::BGRA_8888:
+      return DXGI_FORMAT_B8G8R8A8_UNORM;
+    case viz::RGBA_8888:
+      return DXGI_FORMAT_R8G8B8A8_UNORM;
+    default:
+      NOTREACHED();
+      return {};
+  }
+}
+
+#if BUILDFLAG(USE_DAWN)
+base::Optional<DawnTextureFormat> VizResourceFormatToDawnTextureFormat(
+    viz::ResourceFormat viz_resource_format) {
+  switch (viz_resource_format) {
+    case viz::RGBA_F16:
+      return DAWN_TEXTURE_FORMAT_RGBA16_FLOAT;
+    case viz::BGRA_8888:
+      return DAWN_TEXTURE_FORMAT_BGRA8_UNORM;
+    case viz::RGBA_8888:
+      return DAWN_TEXTURE_FORMAT_RGBA8_UNORM;
+    default:
+      NOTREACHED();
+      return {};
+  }
+}
+#endif  // BUILDFLAG(USE_DAWN)
+
 }  // anonymous namespace
 
 // Representation of a SharedImageBackingD3D as a GL Texture.
@@ -113,6 +152,42 @@
   scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
 };
 
+// Representation of a SharedImageBackingD3D as a Dawn Texture
+#if BUILDFLAG(USE_DAWN)
+class SharedImageRepresentationDawnD3D : public SharedImageRepresentationDawn {
+ public:
+  SharedImageRepresentationDawnD3D(SharedImageManager* manager,
+                                   SharedImageBacking* backing,
+                                   MemoryTypeTracker* tracker,
+                                   DawnDevice device)
+      : SharedImageRepresentationDawn(manager, backing, tracker),
+        device_(device),
+        dawn_procs_(dawn_native::GetProcs()) {
+    DCHECK(device_);
+
+    // Keep a reference to the device so that it stays valid (it might become
+    // lost in which case operations will be noops).
+    dawn_procs_.deviceReference(device_);
+  }
+
+  ~SharedImageRepresentationDawnD3D() override {
+    EndAccess();
+    dawn_procs_.deviceRelease(device_);
+  }
+
+  DawnTexture BeginAccess(DawnTextureUsage usage) override;
+  void EndAccess() override;
+
+ private:
+  DawnDevice device_;
+  DawnTexture texture_ = nullptr;
+
+  // TODO(cwallez@chromium.org): Load procs only once when the factory is
+  // created and pass a pointer to them around?
+  DawnProcTable dawn_procs_;
+};
+#endif  // BUILDFLAG(USE_DAWN)
+
 // Implementation of SharedImageBacking that holds buffer (front buffer/back
 // buffer of swap chain) texture (as gles2::Texture/gles2::TexturePassthrough)
 // and a reference to created swap chain.
@@ -128,7 +203,9 @@
       gles2::Texture* texture,
       scoped_refptr<gles2::TexturePassthrough> texture_passthrough,
       scoped_refptr<gl::GLImageD3D> image,
-      size_t buffer_index)
+      size_t buffer_index,
+      Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+      base::win::ScopedHandle shared_handle)
       : SharedImageBacking(mailbox,
                            format,
                            size,
@@ -141,8 +218,10 @@
         texture_(texture),
         texture_passthrough_(std::move(texture_passthrough)),
         image_(std::move(image)),
-        buffer_index_(buffer_index) {
-    DCHECK(swap_chain_);
+        buffer_index_(buffer_index),
+        d3d11_texture_(std::move(d3d11_texture)),
+        shared_handle_(std::move(shared_handle)) {
+    DCHECK(d3d11_texture_);
     DCHECK((texture_ && !texture_passthrough_) ||
            (!texture_ && texture_passthrough_));
   }
@@ -171,6 +250,18 @@
     return true;
   }
 
+  std::unique_ptr<SharedImageRepresentationDawn> ProduceDawn(
+      SharedImageManager* manager,
+      MemoryTypeTracker* tracker,
+      DawnDevice device) override {
+#if BUILDFLAG(USE_DAWN)
+    return std::make_unique<SharedImageRepresentationDawnD3D>(manager, this,
+                                                              tracker, device);
+#else
+    return nullptr;
+#endif  // BUILDFLAG(USE_DAWN)
+  }
+
   void Destroy() override {
     if (texture_) {
       texture_->RemoveLightweightRef(have_context());
@@ -181,6 +272,8 @@
       texture_passthrough_ = nullptr;
     }
     swap_chain_ = nullptr;
+    d3d11_texture_.Reset();
+    shared_handle_.Close();
   }
 
   void OnMemoryDump(const std::string& dump_name,
@@ -203,6 +296,8 @@
     image_->OnMemoryDump(pmd, client_tracing_id, dump_name);
   }
 
+  HANDLE GetSharedHandle() const { return shared_handle_.Get(); }
+
   bool PresentSwapChain() override {
     TRACE_EVENT0("gpu", "SharedImageBackingD3D::PresentSwapChain");
     if (buffer_index_ != 0) {
@@ -266,13 +361,76 @@
   scoped_refptr<gles2::TexturePassthrough> texture_passthrough_;
   scoped_refptr<gl::GLImageD3D> image_;
   const size_t buffer_index_;
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture_;
+  base::win::ScopedHandle shared_handle_;
+
   DISALLOW_COPY_AND_ASSIGN(SharedImageBackingD3D);
 };
 
+#if BUILDFLAG(USE_DAWN)
+DawnTexture SharedImageRepresentationDawnD3D::BeginAccess(
+    DawnTextureUsage usage) {
+  SharedImageBackingD3D* d3d_image_backing =
+      static_cast<SharedImageBackingD3D*>(backing());
+
+  const HANDLE shared_handle = d3d_image_backing->GetSharedHandle();
+  const viz::ResourceFormat viz_resource_format = d3d_image_backing->format();
+  const base::Optional<DawnTextureFormat> dawn_texture_format =
+      VizResourceFormatToDawnTextureFormat(viz_resource_format);
+  if (!dawn_texture_format.has_value()) {
+    DLOG(ERROR) << "Unsupported viz format found: " << viz_resource_format;
+    return nullptr;
+  }
+
+  DawnTextureDescriptor desc;
+  desc.nextInChain = nullptr;
+  desc.format = dawn_texture_format.value();
+  desc.usage = usage;
+  desc.dimension = DAWN_TEXTURE_DIMENSION_2D;
+  desc.size = {size().width(), size().height(), 1};
+  desc.arrayLayerCount = 1;
+  desc.mipLevelCount = 1;
+  desc.sampleCount = 1;
+
+  texture_ =
+      dawn_native::d3d12::WrapSharedHandle(device_, &desc, shared_handle);
+  if (texture_) {
+    // Keep a reference to the texture so that it stays valid (its content
+    // might be destroyed).
+    dawn_procs_.textureReference(texture_);
+
+    // Assume that the user of this representation will write to the texture
+    // so set the cleared flag so that other representations don't overwrite
+    // the result.
+    // TODO(cwallez@chromium.org): This is incorrect and allows reading
+    // uninitialized data. When !IsCleared we should tell dawn_native to
+    // consider the texture lazy-cleared.
+    SetCleared();
+  }
+
+  return texture_;
+}
+
+void SharedImageRepresentationDawnD3D::EndAccess() {
+  if (!texture_) {
+    return;
+  }
+
+  // TODO(cwallez@chromium.org): query dawn_native to know if the texture was
+  // cleared and set IsCleared appropriately.
+
+  // All further operations on the textures are errors (they would be racy
+  // with other backings).
+  dawn_procs_.textureDestroy(texture_);
+
+  dawn_procs_.textureRelease(texture_);
+  texture_ = nullptr;
+}
+#endif  // BUILDFLAG(USE_DAWN)
+
 SharedImageBackingFactoryD3D::SharedImageBackingFactoryD3D(bool use_passthrough)
     : use_passthrough_(use_passthrough),
       d3d11_device_(gl::QueryD3D11DeviceObjectFromANGLE()) {
-  DCHECK(d3d11_device_);
 }
 
 SharedImageBackingFactoryD3D::~SharedImageBackingFactoryD3D() = default;
@@ -304,8 +462,10 @@
     const gfx::Size& size,
     const gfx::ColorSpace& color_space,
     uint32_t usage,
-    const Microsoft::WRL::ComPtr<IDXGISwapChain1>& swap_chain,
-    size_t buffer_index) {
+    Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
+    size_t buffer_index,
+    Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+    base::win::ScopedHandle shared_handle) {
   gl::GLApi* const api = gl::g_current_gl_context;
   ScopedRestoreTexture2D scoped_restore(api);
 
@@ -318,21 +478,25 @@
   api->glTexParameteriFn(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   api->glTexParameteriFn(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 
-  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
-  HRESULT hr =
-      swap_chain->GetBuffer(buffer_index, IID_PPV_ARGS(&d3d11_texture));
-  if (FAILED(hr)) {
-    DLOG(ERROR) << "GetBuffer failed with error " << std::hex << hr;
-    return nullptr;
+  if (swap_chain) {
+    DCHECK(!d3d11_texture);
+    DCHECK(!shared_handle.IsValid());
+    const HRESULT hr =
+        swap_chain->GetBuffer(buffer_index, IID_PPV_ARGS(&d3d11_texture));
+    if (FAILED(hr)) {
+      DLOG(ERROR) << "GetBuffer failed with error " << std::hex;
+      return nullptr;
+    }
+  } else {
+    DCHECK(d3d11_texture);
   }
-  DCHECK(d3d11_texture);
 
-  gfx::BufferFormat buffer_format = format == viz::RGBA_F16
-                                        ? gfx::BufferFormat::RGBA_F16
-                                        : gfx::BufferFormat::BGRA_8888;
+  const gfx::BufferFormat buffer_format = format == viz::RGBA_F16
+                                              ? gfx::BufferFormat::RGBA_F16
+                                              : gfx::BufferFormat::BGRA_8888;
 
-  auto image = base::MakeRefCounted<gl::GLImageD3D>(
-      size, buffer_format, std::move(d3d11_texture), swap_chain);
+  auto image = base::MakeRefCounted<gl::GLImageD3D>(size, buffer_format,
+                                                    d3d11_texture, swap_chain);
   if (!image->Initialize()) {
     DLOG(ERROR) << "GLImageD3D::Initialize failed";
     return nullptr;
@@ -355,10 +519,10 @@
     texture_passthrough->SetEstimatedSize(texture_memory_size);
   } else {
     // Image internal format could be different from |format| e.g. BGRA vs RGBA.
-    GLuint internal_format = image->GetInternalFormat();
-    GLenum gl_format =
+    const GLuint internal_format = image->GetInternalFormat();
+    const GLenum gl_format =
         gles2::TextureManager::ExtractFormatFromStorageFormat(internal_format);
-    GLenum gl_type =
+    const GLenum gl_type =
         gles2::TextureManager::ExtractTypeFromStorageFormat(internal_format);
 
     texture = new gles2::Texture(service_id);
@@ -378,7 +542,8 @@
 
   return std::make_unique<SharedImageBackingD3D>(
       mailbox, format, size, color_space, usage, std::move(swap_chain), texture,
-      std::move(texture_passthrough), std::move(image), buffer_index);
+      std::move(texture_passthrough), std::move(image), buffer_index,
+      std::move(d3d11_texture), std::move(shared_handle));
 }
 
 SharedImageBackingFactoryD3D::SwapChainBackings
@@ -389,6 +554,9 @@
     const gfx::Size& size,
     const gfx::ColorSpace& color_space,
     uint32_t usage) {
+  if (!SharedImageBackingFactoryD3D::IsSwapChainSupported())
+    return {nullptr, nullptr};
+
   switch (format) {
     case viz::RGBA_8888:
     case viz::BGRA_8888:
@@ -456,13 +624,15 @@
 
   auto back_buffer_backing =
       MakeBacking(back_buffer_mailbox, format, size, color_space, usage,
-                  swap_chain, 0 /* buffer_index */);
+                  swap_chain, 0 /* buffer_index */, nullptr /* d3d11_texture */,
+                  base::win::ScopedHandle());
   if (!back_buffer_backing)
     return {nullptr, nullptr};
 
   auto front_buffer_backing =
       MakeBacking(front_buffer_mailbox, format, size, color_space, usage,
-                  swap_chain, 1 /* buffer_index */);
+                  swap_chain, 1 /* buffer_index */, nullptr /* d3d11_texture */,
+                  base::win::ScopedHandle());
   if (!front_buffer_backing)
     return {nullptr, nullptr};
 
@@ -477,8 +647,65 @@
     const gfx::ColorSpace& color_space,
     uint32_t usage,
     bool is_thread_safe) {
-  NOTIMPLEMENTED();
-  return nullptr;
+  DCHECK(!is_thread_safe);
+
+  // Without D3D11, we cannot do shared images. This will happen if we're
+  // running with Vulkan, D3D9, GL or with the non-passthrough command decoder
+  // in tests.
+  if (!d3d11_device_) {
+    return nullptr;
+  }
+
+  const base::Optional<DXGI_FORMAT> dxgi_format = VizFormatToDXGIFormat(format);
+  if (!dxgi_format.has_value()) {
+    DLOG(ERROR) << "Unsupported viz format found: " << format;
+    return nullptr;
+  }
+
+  D3D11_TEXTURE2D_DESC desc;
+  desc.Width = size.width();
+  desc.Height = size.height();
+  desc.MipLevels = 1;
+  desc.ArraySize = 1;
+  desc.Format = dxgi_format.value();
+  desc.SampleDesc.Count = 1;
+  desc.SampleDesc.Quality = 0;
+  desc.Usage = D3D11_USAGE_DEFAULT;
+  desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
+  desc.CPUAccessFlags = 0;
+  desc.MiscFlags =
+      D3D11_RESOURCE_MISC_SHARED_NTHANDLE | D3D11_RESOURCE_MISC_SHARED;
+  Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture;
+  HRESULT hr = d3d11_device_->CreateTexture2D(&desc, nullptr, &d3d11_texture);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "CreateTexture2D failed with error " << std::hex << hr;
+    return nullptr;
+  }
+
+  Microsoft::WRL::ComPtr<IDXGIResource1> dxgi_resource;
+  hr = d3d11_texture.As(&dxgi_resource);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "QueryInterface for IDXGIResource failed with error "
+                << std::hex << hr;
+    return nullptr;
+  }
+
+  HANDLE shared_handle;
+  hr = dxgi_resource->CreateSharedHandle(
+      nullptr, DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, nullptr,
+      &shared_handle);
+  if (FAILED(hr)) {
+    DLOG(ERROR) << "Unable to create shared handle for DXGIResource "
+                << std::hex << hr;
+    return nullptr;
+  }
+
+  // Put the shared handle into an RAII object as quickly as possible to
+  // ensure we do not leak it.
+  base::win::ScopedHandle scoped_shared_handle(shared_handle);
+
+  return MakeBacking(mailbox, format, size, color_space, usage, nullptr, 0,
+                     std::move(d3d11_texture), std::move(scoped_shared_handle));
 }
 
 std::unique_ptr<SharedImageBacking>
@@ -511,7 +738,6 @@
 // this factory.
 bool SharedImageBackingFactoryD3D::CanImportGpuMemoryBuffer(
     gfx::GpuMemoryBufferType memory_buffer_type) {
-  NOTIMPLEMENTED();
   return false;
 }
 
diff --git a/gpu/command_buffer/service/shared_image_backing_factory_d3d.h b/gpu/command_buffer/service/shared_image_backing_factory_d3d.h
index a108e24..632b70a 100644
--- a/gpu/command_buffer/service/shared_image_backing_factory_d3d.h
+++ b/gpu/command_buffer/service/shared_image_backing_factory_d3d.h
@@ -88,8 +88,8 @@
       gfx::GpuMemoryBufferType memory_buffer_type) override;
 
  private:
-  // Wraps the swap chain buffer (front buffer/back buffer) into GLimage and
-  // creates a GL texture and stores it as gles2::Texture or as
+  // Wraps the optional swap chain buffer (front buffer/back buffer) and texture
+  // into GLimage and creates a GL texture and stores it as gles2::Texture or as
   // gles2::TexturePassthrough in the backing that is created.
   std::unique_ptr<SharedImageBacking> MakeBacking(
       const Mailbox& mailbox,
@@ -97,8 +97,10 @@
       const gfx::Size& size,
       const gfx::ColorSpace& color_space,
       uint32_t usage,
-      const Microsoft::WRL::ComPtr<IDXGISwapChain1>& swap_chain,
-      size_t buffer_index);
+      Microsoft::WRL::ComPtr<IDXGISwapChain1> swap_chain,
+      size_t buffer_index,
+      const Microsoft::WRL::ComPtr<ID3D11Texture2D> d3d11_texture,
+      base::win::ScopedHandle shared_handle);
 
   // Whether we're using the passthrough command decoder and should generate
   // passthrough textures.
diff --git a/gpu/command_buffer/service/shared_image_factory.cc b/gpu/command_buffer/service/shared_image_factory.cc
index fcdc0b2..3a29bec2 100644
--- a/gpu/command_buffer/service/shared_image_factory.cc
+++ b/gpu/command_buffer/service/shared_image_factory.cc
@@ -114,12 +114,10 @@
 
 #if defined(OS_WIN)
   // For Windows
-  if (SharedImageBackingFactoryD3D::IsSwapChainSupported()) {
-    bool use_passthrough = gpu_preferences.use_passthrough_cmd_decoder &&
-                           gles2::PassthroughCommandDecoderSupported();
-    d3d_backing_factory_ =
-        std::make_unique<SharedImageBackingFactoryD3D>(use_passthrough);
-  }
+  bool use_passthrough = gpu_preferences.use_passthrough_cmd_decoder &&
+                         gles2::PassthroughCommandDecoderSupported();
+  interop_backing_factory_ =
+      std::make_unique<SharedImageBackingFactoryD3D>(use_passthrough);
 #endif  // OS_WIN
 
 #if defined(OS_FUCHSIA)
@@ -243,10 +241,14 @@
                                          const gfx::Size& size,
                                          const gfx::ColorSpace& color_space,
                                          uint32_t usage) {
-  if (!d3d_backing_factory_)
+  if (!SharedImageBackingFactoryD3D::IsSwapChainSupported())
     return false;
+
+  SharedImageBackingFactoryD3D* d3d_backing_factory =
+      static_cast<SharedImageBackingFactoryD3D*>(
+          interop_backing_factory_.get());
   bool allow_legacy_mailbox = true;
-  auto backings = d3d_backing_factory_->CreateSwapChain(
+  auto backings = d3d_backing_factory->CreateSwapChain(
       front_buffer_mailbox, back_buffer_mailbox, format, size, color_space,
       usage);
   return RegisterBacking(std::move(backings.front_buffer),
@@ -255,7 +257,7 @@
 }
 
 bool SharedImageFactory::PresentSwapChain(const Mailbox& mailbox) {
-  if (!d3d_backing_factory_)
+  if (!SharedImageBackingFactoryD3D::IsSwapChainSupported())
     return false;
   auto it = shared_images_.find(mailbox);
   if (it == shared_images_.end()) {
diff --git a/gpu/command_buffer/service/shared_image_factory.h b/gpu/command_buffer/service/shared_image_factory.h
index c40e307..eea81d4 100644
--- a/gpu/command_buffer/service/shared_image_factory.h
+++ b/gpu/command_buffer/service/shared_image_factory.h
@@ -35,10 +35,6 @@
 struct GpuFeatureInfo;
 struct GpuPreferences;
 
-#if defined(OS_WIN)
-class SharedImageBackingFactoryD3D;
-#endif  // OS_WIN
-
 #if defined(OS_FUCHSIA)
 class SysmemBufferCollection;
 #endif  // OS_FUCHSIA
@@ -135,17 +131,13 @@
   // eventually.
   std::unique_ptr<SharedImageBackingFactoryGLTexture> gl_backing_factory_;
 
-  // Used for creating shared image which can be shared between gl and vulakn.
+  // Used for creating shared image which can be shared between GL, Vulkan and
+  // D3D12.
   std::unique_ptr<SharedImageBackingFactory> interop_backing_factory_;
 
   // Non-null if compositing with SkiaRenderer.
   std::unique_ptr<raster::WrappedSkImageFactory> wrapped_sk_image_factory_;
 
-#if defined(OS_WIN)
-  // Used for creating DXGI Swap Chain.
-  std::unique_ptr<SharedImageBackingFactoryD3D> d3d_backing_factory_;
-#endif  // OS_WIN
-
 #if defined(OS_FUCHSIA)
   viz::VulkanContextProvider* vulkan_context_provider_;
   base::flat_map<gfx::SysmemBufferCollectionId,
diff --git a/gpu/command_buffer/service/webgpu_decoder_impl.cc b/gpu/command_buffer/service/webgpu_decoder_impl.cc
index 9aefcad2d..27f097a 100644
--- a/gpu/command_buffer/service/webgpu_decoder_impl.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_impl.cc
@@ -15,6 +15,7 @@
 #include "base/logging.h"
 #include "base/macros.h"
 #include "base/trace_event/trace_event.h"
+#include "build/build_config.h"
 #include "gpu/command_buffer/common/mailbox.h"
 #include "gpu/command_buffer/common/webgpu_cmd_format.h"
 #include "gpu/command_buffer/common/webgpu_cmd_ids.h"
@@ -418,6 +419,25 @@
 }
 
 DawnDevice WebGPUDecoderImpl::CreateDefaultDevice() {
+#if defined(OS_WIN)
+  // On Windows 10, we pick D3D12 backend because the rest of Chromium renders
+  // with D3D11. By the same token, we pick the first adapter because ANGLE also
+  // picks the first adapter. Later, we'll need to centralize adapter picking
+  // such that Dawn and ANGLE are told which adapter to use by Chromium. If we
+  // decide to handle multiple adapters, code on the Chromium side will need to
+  // change to do appropriate cross adapter copying to make this happen, either
+  // manually or by using DirectComposition.
+  dawn_instance_->DiscoverDefaultAdapters();
+  const std::vector<dawn_native::Adapter> adapters =
+      dawn_instance_->GetAdapters();
+
+  for (dawn_native::Adapter adapter : adapters) {
+    if (adapter.GetBackendType() == dawn_native::BackendType::D3D12) {
+      return adapter.CreateDevice();
+    }
+  }
+  return nullptr;
+#else
   dawn_instance_->DiscoverDefaultAdapters();
   std::vector<dawn_native::Adapter> adapters = dawn_instance_->GetAdapters();
 
@@ -457,6 +477,7 @@
     return unknown_adapter.CreateDevice();
   }
   return nullptr;
+#endif
 }
 
 const char* WebGPUDecoderImpl::GetCommandName(unsigned int command_id) const {
diff --git a/gpu/command_buffer/service/webgpu_decoder_unittest.cc b/gpu/command_buffer/service/webgpu_decoder_unittest.cc
index 7eceeaf..3a20602 100644
--- a/gpu/command_buffer/service/webgpu_decoder_unittest.cc
+++ b/gpu/command_buffer/service/webgpu_decoder_unittest.cc
@@ -14,6 +14,9 @@
 #include "gpu/command_buffer/service/shared_image_manager.h"
 #include "gpu/command_buffer/service/test_helper.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gl/gl_context.h"
+#include "ui/gl/gl_surface.h"
+#include "ui/gl/init/gl_factory.h"
 
 using ::testing::_;
 using ::testing::Return;
@@ -27,6 +30,16 @@
   WebGPUDecoderTest() {}
 
   void SetUp() override {
+    // Shared image factories for some backends take a dependency on GL.
+    // Failure to create a test context with a surface and making it current
+    // will result in a "NoContext" context being current that asserts on all
+    // GL calls.
+    gl::init::InitializeGLNoExtensionsOneOff();
+    gl_surface_ = gl::init::CreateOffscreenGLSurface(gfx::Size(1, 1));
+    gl_context_ = gl::init::CreateGLContext(nullptr, gl_surface_.get(),
+                                            gl::GLContextAttribs());
+    gl_context_->MakeCurrent(gl_surface_.get());
+
     command_buffer_service_.reset(new FakeCommandBufferServiceBase());
     decoder_.reset(WebGPUDecoder::Create(nullptr, command_buffer_service_.get(),
                                          &shared_image_manager_, nullptr,
@@ -45,6 +58,10 @@
   void TearDown() override {
     factory_->DestroyAllSharedImages(true);
     factory_.reset();
+
+    gl_surface_.reset();
+    gl_context_.reset();
+    gl::init::ShutdownGL(false);
   }
 
   bool WebGPUSupported() const { return decoder_ != nullptr; }
@@ -75,7 +92,8 @@
   gles2::TraceOutputter outputter_;
   SharedImageManager shared_image_manager_;
   std::unique_ptr<SharedImageFactory> factory_;
-  scoped_refptr<gles2::ContextGroup> group_;
+  scoped_refptr<gl::GLSurface> gl_surface_;
+  scoped_refptr<gl::GLContext> gl_context_;
 };
 
 TEST_F(WebGPUDecoderTest, DawnCommands) {
diff --git a/gpu/command_buffer/tests/webgpu_test.cc b/gpu/command_buffer/tests/webgpu_test.cc
index 5808f85..0b00e0f 100644
--- a/gpu/command_buffer/tests/webgpu_test.cc
+++ b/gpu/command_buffer/tests/webgpu_test.cc
@@ -29,8 +29,9 @@
 }
 
 bool WebGPUTest::WebGPUSharedImageSupported() const {
-  // Currently WebGPUSharedImage is only implemented on Mac and Linux
-#if (defined(OS_MACOSX) || defined(OS_LINUX)) && BUILDFLAG(USE_DAWN)
+  // Currently WebGPUSharedImage is only implemented on Mac, Linux and Windows
+#if (defined(OS_MACOSX) || defined(OS_LINUX) || defined(OS_WIN)) && \
+    BUILDFLAG(USE_DAWN)
   return true;
 #else
   return false;
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index dc3136a1..0b7b955 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3034,7 +3034,7 @@
     },
     {
       "id": 285,
-      "cr_bugs": [914976],
+      "cr_bugs": [914976, 1000113],
       "description": "Context flush ordering doesn't seem to work on AMD",
       "vendor_id": "0x1002",
       "os": {
@@ -3042,6 +3042,15 @@
       },
       "features": [
         "use_virtualized_gl_contexts"
+      ],
+      "exceptions": [
+        {
+          "driver_vendor": "Mesa",
+          "driver_version": {
+            "op": ">=",
+            "value": "19.0"
+          }
+        }
       ]
     },
     {
diff --git a/infra/config/cr-buildbucket.cfg b/infra/config/cr-buildbucket.cfg
index 2a1077d3..c887b69 100644
--- a/infra/config/cr-buildbucket.cfg
+++ b/infra/config/cr-buildbucket.cfg
@@ -1343,7 +1343,7 @@
     }
 
     builders {
-      name: "android-oreo-arm64-rel"
+      name: "android-pie-arm64-rel"
       mixins: "android-ci"
       mixins: "linux-xenial"
       mixins: "builderless"
@@ -4057,7 +4057,7 @@
     }
     builders {
       mixins: "android-try"
-      name: "android-oreo-arm64-rel"
+      name: "android-pie-arm64-rel"
       mixins: "linux-xenial"
       mixins: "builderless"
     }
diff --git a/infra/config/luci-milo.cfg b/infra/config/luci-milo.cfg
index a024db1..823e615 100644
--- a/infra/config/luci-milo.cfg
+++ b/infra/config/luci-milo.cfg
@@ -1613,9 +1613,9 @@
     short_name: "san"
   }
   builders {
-    name: "buildbucket/luci.chromium.ci/android-oreo-arm64-rel"
+    name: "buildbucket/luci.chromium.ci/android-pie-arm64-rel"
     category: "on_cq|future"
-    short_name: "O"
+    short_name: "P"
   }
 }
 
@@ -4327,7 +4327,7 @@
     name: "buildbucket/luci.chromium.try/android-marshmallow-x86-fyi-rel"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android-oreo-arm64-rel"
+    name: "buildbucket/luci.chromium.try/android-pie-arm64-rel"
   }
   builders {
     name: "buildbucket/luci.chromium.try/android_optional_gpu_tests_rel"
@@ -4991,7 +4991,7 @@
     name: "buildbucket/luci.chromium.try/android-oreo-arm64-cts-networkservice-dbg"
   }
   builders {
-    name: "buildbucket/luci.chromium.try/android-oreo-arm64-rel"
+    name: "buildbucket/luci.chromium.try/android-pie-arm64-rel"
   }
   builders {
     name: "buildbucket/luci.chromium.try/android_archive_rel_ng"
diff --git a/infra/config/luci-scheduler.cfg b/infra/config/luci-scheduler.cfg
index 347f0fc..db876c7 100644
--- a/infra/config/luci-scheduler.cfg
+++ b/infra/config/luci-scheduler.cfg
@@ -326,7 +326,7 @@
   triggers: "android-kitkat-arm-rel"
   triggers: "android-marshmallow-arm64-rel"
   triggers: "android-mojo-webview-rel"
-  triggers: "android-oreo-arm64-rel"
+  triggers: "android-pie-arm64-rel"
   triggers: "android-archive-rel"
   triggers: "chromeos-amd64-generic-asan-rel"
   triggers: "chromeos-amd64-generic-cfi-thin-lto-rel"
@@ -916,12 +916,12 @@
 }
 
 job {
-  id: "android-oreo-arm64-rel"
+  id: "android-pie-arm64-rel"
   acl_sets: "default"
   buildbucket: {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.chromium.ci"
-    builder: "android-oreo-arm64-rel"
+    builder: "android-pie-arm64-rel"
   }
 }
 
diff --git a/ios/chrome/app/application_delegate/memory_warning_helper_unittest.mm b/ios/chrome/app/application_delegate/memory_warning_helper_unittest.mm
index fbc5d469..713c6f1 100644
--- a/ios/chrome/app/application_delegate/memory_warning_helper_unittest.mm
+++ b/ios/chrome/app/application_delegate/memory_warning_helper_unittest.mm
@@ -52,8 +52,7 @@
   void RunMessageLoop() { run_loop_.Run(); }
 
  private:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY};
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::RunLoop run_loop_;
   base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level_;
   std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
diff --git a/ios/chrome/browser/flags/BUILD.gn b/ios/chrome/browser/flags/BUILD.gn
index eeb04a8..ed3dd60 100644
--- a/ios/chrome/browser/flags/BUILD.gn
+++ b/ios/chrome/browser/flags/BUILD.gn
@@ -45,7 +45,6 @@
     "//ios/chrome/browser/find_in_page:feature_flags",
     "//ios/chrome/browser/passwords:feature_flags",
     "//ios/chrome/browser/reading_list:features",
-    "//ios/chrome/browser/signin:feature_flags",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/dialogs:feature_flags",
     "//ios/chrome/browser/ui/fullscreen:feature_flags",
diff --git a/ios/chrome/browser/flags/about_flags.mm b/ios/chrome/browser/flags/about_flags.mm
index ae4477b..ec8b59f 100644
--- a/ios/chrome/browser/flags/about_flags.mm
+++ b/ios/chrome/browser/flags/about_flags.mm
@@ -56,7 +56,6 @@
 #include "ios/chrome/browser/flags/ios_chrome_flag_descriptions.h"
 #include "ios/chrome/browser/passwords/password_manager_features.h"
 #include "ios/chrome/browser/reading_list/features.h"
-#include "ios/chrome/browser/signin/feature_flags.h"
 #include "ios/chrome/browser/system_flags.h"
 #import "ios/chrome/browser/ui/dialogs/dialog_features.h"
 #import "ios/chrome/browser/ui/fullscreen/fullscreen_features.h"
@@ -528,9 +527,6 @@
     {"lock-bottom-toolbar", flag_descriptions::kLockBottomToolbarName,
      flag_descriptions::kLockBottomToolbarDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(fullscreen::features::kLockBottomToolbar)},
-    {"identity-disc", flag_descriptions::kIdentityDiscName,
-     flag_descriptions::kIdentityDiscDescription, flags_ui::kOsIos,
-     FEATURE_VALUE_TYPE(kIdentityDisc)},
     {"toolbar-new-tab-button", flag_descriptions::kToolbarNewTabButtonName,
      flag_descriptions::kToolbarNewTabButtonDescription, flags_ui::kOsIos,
      FEATURE_VALUE_TYPE(kToolbarNewTabButton)},
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
index a81e44e6..292e5d3 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.cc
@@ -218,10 +218,6 @@
     "The different ways in which the web view's viewport is updated for scroll "
     "events.  The default option updates the web view's frame.";
 
-const char kIdentityDiscName[] = "Identity Disc";
-const char kIdentityDiscDescription[] =
-    "Enables Identity Disc, profile avatar icon button in toolbar.";
-
 const char kIgnoresViewportScaleLimitsName[] = "Ignore Viewport Scale Limits";
 const char kIgnoresViewportScaleLimitsDescription[] =
     "When enabled the page can always be scaled, regardless of author intent.";
diff --git a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
index 4c140659..11604e8d 100644
--- a/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
+++ b/ios/chrome/browser/flags/ios_chrome_flag_descriptions.h
@@ -177,11 +177,6 @@
 extern const char kFullscreenViewportAdjustmentExperimentName[];
 extern const char kFullscreenViewportAdjustmentExperimentDescription[];
 
-// Title and description for the flag to display current user identity on
-// New Tab Page.
-extern const char kIdentityDiscName[];
-extern const char kIdentityDiscDescription[];
-
 // Title and description for the flag to ignore viewport scale limits.
 extern const char kIgnoresViewportScaleLimitsName[];
 extern const char kIgnoresViewportScaleLimitsDescription[];
diff --git a/ios/chrome/browser/signin/feature_flags.h b/ios/chrome/browser/signin/feature_flags.h
index f25e3ff..dc82b280 100644
--- a/ios/chrome/browser/signin/feature_flags.h
+++ b/ios/chrome/browser/signin/feature_flags.h
@@ -10,10 +10,4 @@
 // Feature flag to enable NSURLSession for GAIAAuthFetcherIOS.
 extern const base::Feature kUseNSURLSessionForGaiaSigninRequests;
 
-// Feature flag to enable display of current user identity on New Tab Page.
-extern const base::Feature kIdentityDisc;
-
-// Whether Identity Disc feature is enabled.
-bool IsIdentityDiscFeatureEnabled();
-
 #endif  // IOS_CHROME_BROWSER_SIGNIN_FEATURE_FLAGS_H_
diff --git a/ios/chrome/browser/signin/feature_flags.mm b/ios/chrome/browser/signin/feature_flags.mm
index ae30e8fb..6374db8 100644
--- a/ios/chrome/browser/signin/feature_flags.mm
+++ b/ios/chrome/browser/signin/feature_flags.mm
@@ -13,10 +13,3 @@
 // See: http://crbug.com/939508.
 const base::Feature kUseNSURLSessionForGaiaSigninRequests{
     "UseNSURLSessionForGaiaSigninRequests", base::FEATURE_DISABLED_BY_DEFAULT};
-
-const base::Feature kIdentityDisc{"IdentityDisc",
-                                  base::FEATURE_ENABLED_BY_DEFAULT};
-
-bool IsIdentityDiscFeatureEnabled() {
-  return base::FeatureList::IsEnabled(kIdentityDisc);
-}
diff --git a/ios/chrome/browser/ui/content_suggestions/BUILD.gn b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
index 547fb50..942ae3b0 100644
--- a/ios/chrome/browser/ui/content_suggestions/BUILD.gn
+++ b/ios/chrome/browser/ui/content_suggestions/BUILD.gn
@@ -48,7 +48,6 @@
     "//ios/chrome/browser/reading_list",
     "//ios/chrome/browser/search_engines",
     "//ios/chrome/browser/signin",
-    "//ios/chrome/browser/signin:feature_flags",
     "//ios/chrome/browser/tabs",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/alert_coordinator",
@@ -120,7 +119,6 @@
     "resources:ntp_search_icon",
     "//base",
     "//components/strings",
-    "//ios/chrome/browser/signin:feature_flags",
     "//ios/chrome/browser/ui:feature_flags",
     "//ios/chrome/browser/ui/collection_view",
     "//ios/chrome/browser/ui/commands",
@@ -169,7 +167,6 @@
     "//base:i18n",
     "//components/strings",
     "//ios/chrome/app/strings",
-    "//ios/chrome/browser/signin:feature_flags",
     "//ios/chrome/browser/ui/commands",
     "//ios/chrome/browser/ui/content_suggestions/cells:cells_ui",
     "//ios/chrome/browser/ui/location_bar:constants",
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm
index da1bc59..9bd3ae44 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.mm
@@ -7,7 +7,6 @@
 #include "base/i18n/rtl.h"
 #include "base/logging.h"
 #include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/signin/feature_flags.h"
 #import "ios/chrome/browser/ui/content_suggestions/cells/content_suggestions_most_visited_cell.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
 #import "ios/chrome/browser/ui/location_bar/location_bar_constants.h"
@@ -53,10 +52,6 @@
 
 // Height for the doodle frame when Google is not the default search engine.
 const CGFloat kNonGoogleSearchDoodleHeight = 60;
-
-// Height for the header view on tablet when Google is not the default search
-// engine.
-const CGFloat kNonGoogleSearchHeaderHeightIPad = 10;
 }
 
 namespace content_suggestions {
@@ -105,13 +100,10 @@
     return headerHeight;
   }
   if (!logoIsShowing) {
-    if (IsIdentityDiscFeatureEnabled()) {
-      // Returns sufficient vertical space for the Identity Disc to be
-      // displayed.
-      return ntp_home::kIdentityAvatarDimension +
-             2 * ntp_home::kIdentityAvatarMargin;
-    }
-    return kNonGoogleSearchHeaderHeightIPad;
+    // Returns sufficient vertical space for the Identity Disc to be
+    // displayed.
+    return ntp_home::kIdentityAvatarDimension +
+           2 * ntp_home::kIdentityAvatarMargin;
   }
   if (!promoCanShow) {
     headerHeight += kTopSpacingMaterial;
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
index be312777..5c54f98 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view.mm
@@ -9,7 +9,6 @@
 #include "base/feature_list.h"
 #include "base/logging.h"
 #include "components/strings/grit/components_strings.h"
-#import "ios/chrome/browser/signin/feature_flags.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_utils.h"
 #import "ios/chrome/browser/ui/content_suggestions/ntp_home_constant.h"
 #import "ios/chrome/browser/ui/ntp/new_tab_page_header_constants.h"
@@ -397,7 +396,6 @@
   // identityDiscView may not be set if feature is not enabled.
   if (!self.identityDiscView)
     return;
-  DCHECK(IsIdentityDiscFeatureEnabled());
   if ((self.traitCollection.verticalSizeClass !=
        previousTraitCollection.verticalSizeClass) ||
       (self.traitCollection.horizontalSizeClass !=
diff --git a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
index c600302..4b78ef5 100644
--- a/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
+++ b/ios/chrome/browser/ui/content_suggestions/content_suggestions_header_view_controller.mm
@@ -11,7 +11,6 @@
 #include "base/metrics/user_metrics.h"
 #include "components/strings/grit/components_strings.h"
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
-#import "ios/chrome/browser/signin/feature_flags.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
 #import "ios/chrome/browser/ui/content_suggestions/content_suggestions_collection_synchronizing.h"
@@ -226,8 +225,7 @@
 
     // Identity disc needs to be added after the Google logo/doodle since it
     // needs to respond to user taps first.
-    if (IsIdentityDiscFeatureEnabled())
-      [self addIdentityDisc];
+    [self addIdentityDisc];
 
     // -headerForView is regularly called before self.headerView has been added
     // to the view hierarchy, so there's no simple way to get the correct
diff --git a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
index 0120308..34e840a4 100644
--- a/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
+++ b/ios/chrome/browser/ui/content_suggestions/ntp_home_mediator.mm
@@ -18,7 +18,6 @@
 #import "ios/chrome/browser/ntp/new_tab_page_tab_helper.h"
 #import "ios/chrome/browser/search_engines/search_engine_observer_bridge.h"
 #import "ios/chrome/browser/signin/authentication_service.h"
-#include "ios/chrome/browser/signin/feature_flags.h"
 #import "ios/chrome/browser/ui/alert_coordinator/alert_coordinator.h"
 #import "ios/chrome/browser/ui/commands/application_commands.h"
 #import "ios/chrome/browser/ui/commands/browser_commands.h"
@@ -600,10 +599,6 @@
 // Fetches and update user's avatar on NTP, or use default avatar if user is
 // not signed in.
 - (void)updateAccountImage {
-  // Early return here to avoid doing all that work to fetch an image that
-  // won't be used.
-  if (!IsIdentityDiscFeatureEnabled())
-    return;
   UIImage* image;
   // Fetches user's identity from Authentication Service.
   ChromeIdentity* identity = self.authService->GetAuthenticatedIdentity();
diff --git a/media/base/media_switches.cc b/media/base/media_switches.cc
index eefd6ec..98ad20ed 100644
--- a/media/base/media_switches.cc
+++ b/media/base/media_switches.cc
@@ -269,6 +269,10 @@
 const base::Feature kD3D11VideoDecoderIgnoreWorkarounds{
     "D3D11VideoDecoderIgnoreWorkarounds", base::FEATURE_DISABLED_BY_DEFAULT};
 
+// Enable D3D11VideoDecoder to decode VP9 profile 2 (10 bit) video.
+const base::Feature kD3D11VideoDecoderVP9Profile2{
+    "D3D11VideoDecoderEnablbleVP9Profile2", base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Falls back to other decoders after audio/video decode error happens. The
 // implementation may choose different strategies on when to fallback. See
 // DecoderStream for details. When disabled, playback will fail immediately
diff --git a/media/base/media_switches.h b/media/base/media_switches.h
index a5fd670..a0dac5ef 100644
--- a/media/base/media_switches.h
+++ b/media/base/media_switches.h
@@ -103,6 +103,7 @@
 MEDIA_EXPORT extern const base::Feature kD3D11PrintCodecOnCrash;
 MEDIA_EXPORT extern const base::Feature kD3D11VideoDecoder;
 MEDIA_EXPORT extern const base::Feature kD3D11VideoDecoderIgnoreWorkarounds;
+MEDIA_EXPORT extern const base::Feature kD3D11VideoDecoderVP9Profile2;
 MEDIA_EXPORT extern const base::Feature kExternalClearKeyForTesting;
 MEDIA_EXPORT extern const base::Feature kFFmpegDecodeOpaqueVP8;
 MEDIA_EXPORT extern const base::Feature kFailUrlProvisionFetcherForTesting;
diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc
index d508dba9..8f067cb 100644
--- a/media/blink/webmediaplayer_impl.cc
+++ b/media/blink/webmediaplayer_impl.cc
@@ -2008,7 +2008,11 @@
 
 void WebMediaPlayerImpl::OnProgress() {
   DVLOG(4) << __func__;
-  if (highest_ready_state_ < ReadyState::kReadyStateHaveMetadata) {
+
+  // See IsPrerollAttemptNeeded() for more details. We can't use that method
+  // here since it considers |preroll_attempt_start_time_| and for OnProgress()
+  // events we must make the attempt -- since there may not be another event.
+  if (highest_ready_state_ < ReadyState::kReadyStateHaveFutureData) {
     // Reset the preroll attempt clock.
     preroll_attempt_pending_ = true;
     preroll_attempt_start_time_ = base::TimeTicks();
diff --git a/media/blink/webmediaplayer_impl_unittest.cc b/media/blink/webmediaplayer_impl_unittest.cc
index a0f1494..f8e3b78 100644
--- a/media/blink/webmediaplayer_impl_unittest.cc
+++ b/media/blink/webmediaplayer_impl_unittest.cc
@@ -701,6 +701,8 @@
     base::RunLoop().RunUntilIdle();
   }
 
+  void OnProgress() { wmpi_->OnProgress(); }
+
   // "Media" thread. This is necessary because WMPI destruction waits on a
   // WaitableEvent.
   base::Thread media_thread_;
@@ -1925,6 +1927,22 @@
   EXPECT_CALL(*surface_layer_bridge_ptr_, ClearObserver());
 }
 
+TEST_F(WebMediaPlayerImplTest, OnProgressClearsStale) {
+  InitializeWebMediaPlayerImpl();
+  SetMetadata(true, true);
+
+  for (auto rs = blink::WebMediaPlayer::kReadyStateHaveNothing;
+       rs <= blink::WebMediaPlayer::kReadyStateHaveEnoughData;
+       rs = static_cast<blink::WebMediaPlayer::ReadyState>(
+           static_cast<int>(rs) + 1)) {
+    SetReadyState(rs);
+    delegate_.SetStaleForTesting(true);
+    OnProgress();
+    EXPECT_EQ(delegate_.IsStale(delegate_.player_id()),
+              rs >= blink::WebMediaPlayer::kReadyStateHaveFutureData);
+  }
+}
+
 class WebMediaPlayerImplBackgroundBehaviorTest
     : public WebMediaPlayerImplTest,
       public ::testing::WithParamInterface<
diff --git a/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.cc b/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.cc
index 492afa9..06f22cb 100644
--- a/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.cc
+++ b/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.cc
@@ -47,28 +47,32 @@
   } while(0);
 
 #define CREATE_FILE_IO \
-  ADD_TEST_STEP(ACTION_CREATE, kSuccess, NULL, 0)
+  ADD_TEST_STEP(ACTION_CREATE, kSuccess, nullptr, 0)
 
 #define OPEN_FILE \
-  ADD_TEST_STEP(ACTION_OPEN, kSuccess, NULL, 0)
+  ADD_TEST_STEP(ACTION_OPEN, kSuccess, nullptr, 0)
 
 #define EXPECT_FILE_OPENED(status) \
-  ADD_TEST_STEP(RESULT_OPEN, status, NULL, 0)
+  ADD_TEST_STEP(RESULT_OPEN, status, nullptr, 0)
 
 #define READ_FILE \
-  ADD_TEST_STEP(ACTION_READ, kSuccess, NULL, 0)
+  ADD_TEST_STEP(ACTION_READ, kSuccess, nullptr, 0)
 
 #define EXPECT_FILE_READ(status, data, data_size) \
   ADD_TEST_STEP(RESULT_READ, status, data, data_size)
 
+#define EXPECT_FILE_READ_EITHER(status, data, data_size, data2, data2_size) \
+  test_case->AddResultReadEither(cdm::FileIOClient::Status::status, (data), \
+                                 (data_size), (data2), (data2_size));
+
 #define WRITE_FILE(data, data_size) \
   ADD_TEST_STEP(ACTION_WRITE, kSuccess, data, data_size)
 
 #define EXPECT_FILE_WRITTEN(status) \
-  ADD_TEST_STEP(RESULT_WRITE, status, NULL, 0)
+  ADD_TEST_STEP(RESULT_WRITE, status, nullptr, 0)
 
 #define CLOSE_FILE \
-  ADD_TEST_STEP(ACTION_CLOSE, kSuccess, NULL, 0)
+  ADD_TEST_STEP(ACTION_CLOSE, kSuccess, nullptr, 0)
 
 // FileIOTestRunner implementation.
 
@@ -150,7 +154,7 @@
 
   START_TEST_CASE("ReadBeforeOpeningFile")
     READ_FILE
-    EXPECT_FILE_READ(kError, NULL, 0)
+    EXPECT_FILE_READ(kError, nullptr, 0)
   END_TEST_CASE
 
   START_TEST_CASE("WriteBeforeOpeningFile")
@@ -162,7 +166,7 @@
     OPEN_FILE
     READ_FILE
     EXPECT_FILE_OPENED(kSuccess)
-    EXPECT_FILE_READ(kError, NULL, 0)
+    EXPECT_FILE_READ(kError, nullptr, 0)
     // After file opened, we can still do normal operations.
     WRITE_FILE(kData, kDataSize)
     EXPECT_FILE_WRITTEN(kSuccess)
@@ -189,7 +193,7 @@
     EXPECT_FILE_WRITTEN(kSuccess)
     READ_FILE
     READ_FILE
-    EXPECT_FILE_READ(kInUse, NULL, 0)
+    EXPECT_FILE_READ(kInUse, nullptr, 0)
     EXPECT_FILE_READ(kSuccess, kData, kDataSize)
     // Read again.
     READ_FILE
@@ -201,7 +205,7 @@
     EXPECT_FILE_OPENED(kSuccess)
     WRITE_FILE(kData, kDataSize)
     READ_FILE
-    EXPECT_FILE_READ(kInUse, NULL, 0)
+    EXPECT_FILE_READ(kInUse, nullptr, 0)
     EXPECT_FILE_WRITTEN(kSuccess)
     // Read again.
     READ_FILE
@@ -214,7 +218,7 @@
     READ_FILE
     WRITE_FILE(kData, kDataSize)
     EXPECT_FILE_WRITTEN(kInUse)
-    EXPECT_FILE_READ(kSuccess, NULL, 0)
+    EXPECT_FILE_READ(kSuccess, nullptr, 0)
     // We can still do normal operations.
     WRITE_FILE(kData, kDataSize)
     EXPECT_FILE_WRITTEN(kSuccess)
@@ -238,7 +242,7 @@
     OPEN_FILE
     EXPECT_FILE_OPENED(kSuccess)
     READ_FILE
-    EXPECT_FILE_READ(kSuccess, NULL, 0)
+    EXPECT_FILE_READ(kSuccess, nullptr, 0)
   END_TEST_CASE
 
   START_TEST_CASE("WriteAndRead")
@@ -253,10 +257,10 @@
   START_TEST_CASE("WriteAndReadEmptyFile")
     OPEN_FILE
     EXPECT_FILE_OPENED(kSuccess)
-    WRITE_FILE(NULL, 0)
+    WRITE_FILE(nullptr, 0)
     EXPECT_FILE_WRITTEN(kSuccess)
     READ_FILE
-    EXPECT_FILE_READ(kSuccess, NULL, 0)
+    EXPECT_FILE_READ(kSuccess, nullptr, 0)
   END_TEST_CASE
 
   START_TEST_CASE("WriteAndReadLargeData")
@@ -275,10 +279,10 @@
     EXPECT_FILE_WRITTEN(kSuccess)
     READ_FILE
     EXPECT_FILE_READ(kSuccess, kData, kDataSize)
-    WRITE_FILE(NULL, 0)
+    WRITE_FILE(nullptr, 0)
     EXPECT_FILE_WRITTEN(kSuccess)
     READ_FILE
-    EXPECT_FILE_READ(kSuccess, NULL, 0)
+    EXPECT_FILE_READ(kSuccess, nullptr, 0)
   END_TEST_CASE
 
   START_TEST_CASE("OverwriteWithSmallerData")
@@ -353,7 +357,7 @@
     EXPECT_FILE_OPENED(kSuccess)
     // Read file which doesn't exist.
     READ_FILE
-    EXPECT_FILE_READ(kSuccess, NULL, 0)
+    EXPECT_FILE_READ(kSuccess, nullptr, 0)
     // Write kData to file.
     WRITE_FILE(kData, kDataSize)
     EXPECT_FILE_WRITTEN(kSuccess)
@@ -376,11 +380,11 @@
     READ_FILE
     EXPECT_FILE_READ(kSuccess, kData, kDataSize)
     // Overwrite file with zero bytes.
-    WRITE_FILE(NULL, 0)
+    WRITE_FILE(nullptr, 0)
     EXPECT_FILE_WRITTEN(kSuccess)
     // Read file.
     READ_FILE
-    EXPECT_FILE_READ(kSuccess, NULL, 0)
+    EXPECT_FILE_READ(kSuccess, nullptr, 0)
   END_TEST_CASE
 
   START_TEST_CASE("OpenAfterOpen")
@@ -482,7 +486,12 @@
       OPEN_FILE
       EXPECT_FILE_OPENED(kSuccess)
       READ_FILE
-      EXPECT_FILE_READ(kSuccess, kData, kDataSize)
+      // As Write() is async, it is possible that the second write above
+      // succeeds before the file is closed. So check that the contents
+      // is either data set.
+      EXPECT_FILE_READ_EITHER(kSuccess,
+                              kData, kDataSize,
+                              kBigData, kBigDataSize)
       CLOSE_FILE
     }
   END_TEST_CASE
@@ -530,6 +539,16 @@
   test_steps_.push_back(TestStep(type, status, data, data_size));
 }
 
+void FileIOTest::AddResultReadEither(Status status,
+                                     const uint8_t* data,
+                                     uint32_t data_size,
+                                     const uint8_t* data2,
+                                     uint32_t data2_size) {
+  DCHECK_NE(data_size, data2_size);
+  test_steps_.push_back(TestStep(FileIOTest::RESULT_READ, status, data,
+                                 data_size, data2, data2_size));
+}
+
 void FileIOTest::Run(const CompletionCB& completion_cb) {
   FILE_IO_DVLOG(3) << "Run " << test_name_;
   completion_cb_ = completion_cb;
@@ -538,7 +557,7 @@
 }
 
 void FileIOTest::OnOpenComplete(Status status) {
-  OnResult(TestStep(RESULT_OPEN, status, NULL, 0));
+  OnResult(TestStep(RESULT_OPEN, status));
 }
 
 void FileIOTest::OnReadComplete(Status status,
@@ -548,7 +567,7 @@
 }
 
 void FileIOTest::OnWriteComplete(Status status) {
-  OnResult(TestStep(RESULT_WRITE, status, NULL, 0));
+  OnResult(TestStep(RESULT_WRITE, status));
 }
 
 bool FileIOTest::IsResult(const TestStep& test_step) {
@@ -570,12 +589,19 @@
 
 bool FileIOTest::MatchesResult(const TestStep& a, const TestStep& b) {
   DCHECK(IsResult(a) && IsResult(b));
+  DCHECK(!b.data2);
+
   if (a.type != b.type || a.status != b.status)
     return false;
 
   if (a.type != RESULT_READ || a.status != cdm::FileIOClient::Status::kSuccess)
     return true;
 
+  // If |a| specifies a data2, compare it first. If the size matches, compare
+  // the contents.
+  if (a.data2 && b.data_size == a.data2_size)
+    return std::equal(a.data2, a.data2 + a.data2_size, b.data);
+
   return (a.data_size == b.data_size &&
           std::equal(a.data, a.data + a.data_size, b.data));
 }
@@ -590,7 +616,8 @@
     TestStep test_step = test_steps_.front();
     test_steps_.pop_front();
 
-    cdm::FileIO* file_io = file_io_stack_.empty() ? NULL : file_io_stack_.top();
+    cdm::FileIO* file_io =
+        file_io_stack_.empty() ? nullptr : file_io_stack_.top();
 
     switch (test_step.type) {
       case ACTION_CREATE:
diff --git a/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.h b/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.h
index 4eb4b3e..a07fcc1 100644
--- a/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.h
+++ b/media/cdm/library_cdm/clear_key_cdm/cdm_file_io_test.h
@@ -75,19 +75,34 @@
                    Status status,
                    const uint8_t* data,
                    uint32_t data_size);
+  // Adds a test step in this test that expects a successful read of either
+  // |data| or |data2|. |this| object doesn't take the ownership of |data| or
+  // |data2|, which should be valid throughout the lifetime of |this| object.
+  void AddResultReadEither(Status status,
+                           const uint8_t* data,
+                           uint32_t data_size,
+                           const uint8_t* data2,
+                           uint32_t data2_size);
 
   // Runs this test case and returns the test result through |completion_cb|.
   void Run(const CompletionCB& completion_cb);
 
  private:
   struct TestStep {
-    // |this| object doesn't take the ownership of |data|, which should be valid
-    // throughout the lifetime of |this| object.
+    // |this| object doesn't take the ownership of |data| or |data2|, which
+    // should be valid throughout the lifetime of |this| object.
     TestStep(StepType type,
              Status status,
-             const uint8_t* data,
-             uint32_t data_size)
-        : type(type), status(status), data(data), data_size(data_size) {}
+             const uint8_t* data = nullptr,
+             uint32_t data_size = 0,
+             const uint8_t* data2 = nullptr,
+             uint32_t data2_size = 0)
+        : type(type),
+          status(status),
+          data(data),
+          data_size(data_size),
+          data2(data2),
+          data2_size(data2_size) {}
 
     StepType type;
 
@@ -97,6 +112,10 @@
     // Data to write in ACTION_WRITE, or read data in RESULT_READ.
     const uint8_t* data;
     uint32_t data_size;
+
+    // Alternate read data in RESULT_READ, if |data2| != nullptr.
+    const uint8_t* data2;
+    uint32_t data2_size;
   };
 
   // Returns whether |test_step| is a RESULT_* step.
diff --git a/media/filters/BUILD.gn b/media/filters/BUILD.gn
index c66febd..6aeb553 100644
--- a/media/filters/BUILD.gn
+++ b/media/filters/BUILD.gn
@@ -207,6 +207,14 @@
     sources += [
       "fuchsia/fuchsia_video_decoder.cc",
       "fuchsia/fuchsia_video_decoder.h",
+      "fuchsia/stream_processor_helper.cc",
+      "fuchsia/stream_processor_helper.h",
+      "fuchsia/sysmem_buffer_pool.cc",
+      "fuchsia/sysmem_buffer_pool.h",
+      "fuchsia/sysmem_buffer_reader.cc",
+      "fuchsia/sysmem_buffer_reader.h",
+      "fuchsia/sysmem_buffer_writer.cc",
+      "fuchsia/sysmem_buffer_writer.h",
     ]
     deps += [
       "//gpu/command_buffer/client",
diff --git a/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc b/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc
index eb1dddf..8219207e 100644
--- a/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc
+++ b/media/filters/fuchsia/fuchsia_video_decoder_unittest.cc
@@ -241,9 +241,8 @@
   }
 
  protected:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::ThreadingMode::MAIN_THREAD_ONLY,
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
 
   TestSharedImageInterface shared_image_interface_;
   viz::TestContextSupport gpu_context_support_;
diff --git a/media/filters/fuchsia/stream_processor_helper.cc b/media/filters/fuchsia/stream_processor_helper.cc
new file mode 100644
index 0000000..06ff2a0
--- /dev/null
+++ b/media/filters/fuchsia/stream_processor_helper.cc
@@ -0,0 +1,348 @@
+// 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 "media/filters/fuchsia/stream_processor_helper.h"
+
+#include "base/bind.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "media/base/bind_to_current_loop.h"
+
+namespace media {
+
+StreamProcessorHelper::IoPacket::IoPacket(size_t index,
+                                          size_t offset,
+                                          size_t size,
+                                          base::TimeDelta timestamp,
+                                          fuchsia::media::FormatDetails format,
+                                          base::OnceClosure destroy_cb)
+    : index_(index),
+      offset_(offset),
+      size_(size),
+      timestamp_(timestamp),
+      format_(std::move(format)),
+      destroy_cb_(std::move(destroy_cb)) {}
+
+StreamProcessorHelper::IoPacket::~IoPacket() = default;
+
+// static
+std::unique_ptr<StreamProcessorHelper::IoPacket>
+StreamProcessorHelper::IoPacket::CreateInput(
+    size_t index,
+    size_t size,
+    base::TimeDelta timestamp,
+    fuchsia::media::FormatDetails format,
+    base::OnceClosure destroy_cb) {
+  return std::make_unique<IoPacket>(index, 0 /* offset */, size, timestamp,
+                                    std::move(format), std::move(destroy_cb));
+}
+
+// static
+std::unique_ptr<StreamProcessorHelper::IoPacket>
+StreamProcessorHelper::IoPacket::CreateOutput(size_t index,
+                                              size_t offset,
+                                              size_t size,
+                                              base::TimeDelta timestamp,
+                                              base::OnceClosure destroy_cb) {
+  return std::make_unique<IoPacket>(index, offset, size, timestamp,
+                                    fuchsia::media::FormatDetails(),
+                                    std::move(destroy_cb));
+}
+
+StreamProcessorHelper::StreamProcessorHelper(
+    fuchsia::media::StreamProcessorPtr processor,
+    Client* client)
+    : processor_(std::move(processor)), client_(client), weak_factory_(this) {
+  DCHECK(processor_);
+  DCHECK(client_);
+  weak_this_ = weak_factory_.GetWeakPtr();
+
+  processor_.set_error_handler(
+      [this](zx_status_t status) {
+        ZX_LOG(ERROR, status)
+            << "The fuchsia.media.StreamProcessor channel was terminated.";
+        OnError();
+      });
+
+  processor_.events().OnStreamFailed =
+      fit::bind_member(this, &StreamProcessorHelper::OnStreamFailed);
+  processor_.events().OnInputConstraints =
+      fit::bind_member(this, &StreamProcessorHelper::OnInputConstraints);
+  processor_.events().OnFreeInputPacket =
+      fit::bind_member(this, &StreamProcessorHelper::OnFreeInputPacket);
+  processor_.events().OnOutputConstraints =
+      fit::bind_member(this, &StreamProcessorHelper::OnOutputConstraints);
+  processor_.events().OnOutputFormat =
+      fit::bind_member(this, &StreamProcessorHelper::OnOutputFormat);
+  processor_.events().OnOutputPacket =
+      fit::bind_member(this, &StreamProcessorHelper::OnOutputPacket);
+  processor_.events().OnOutputEndOfStream =
+      fit::bind_member(this, &StreamProcessorHelper::OnOutputEndOfStream);
+
+  processor_->EnableOnStreamFailed();
+}
+
+StreamProcessorHelper::~StreamProcessorHelper() = default;
+
+void StreamProcessorHelper::Process(std::unique_ptr<IoPacket> input) {
+  DCHECK(input);
+  DCHECK(processor_);
+
+  fuchsia::media::Packet packet;
+  packet.mutable_header()->set_buffer_lifetime_ordinal(
+      input_buffer_lifetime_ordinal_);
+  packet.mutable_header()->set_packet_index(input->index());
+  packet.set_buffer_index(packet.header().packet_index());
+  packet.set_timestamp_ish(input->timestamp().InNanoseconds());
+  packet.set_stream_lifetime_ordinal(stream_lifetime_ordinal_);
+  packet.set_start_offset(input->offset());
+  packet.set_valid_length_bytes(input->size());
+
+  active_stream_ = true;
+
+  if (!input->format().IsEmpty()) {
+    processor_->QueueInputFormatDetails(stream_lifetime_ordinal_,
+                                        fidl::Clone(input->format()));
+  }
+
+  DCHECK(input_packets_.find(input->index()) == input_packets_.end());
+  input_packets_[input->index()] = std::move(input);
+  processor_->QueueInputPacket(std::move(packet));
+}
+
+void StreamProcessorHelper::ProcessEos() {
+  DCHECK(processor_);
+
+  active_stream_ = true;
+  processor_->QueueInputEndOfStream(stream_lifetime_ordinal_);
+  processor_->FlushEndOfStreamAndCloseStream(stream_lifetime_ordinal_);
+}
+
+void StreamProcessorHelper::Reset() {
+  if (active_stream_) {
+    return;
+  }
+
+  processor_->CloseCurrentStream(stream_lifetime_ordinal_,
+                                 /*release_input_buffers=*/false,
+                                 /*release_output_buffers=*/false);
+  stream_lifetime_ordinal_ += 2;
+  active_stream_ = false;
+  input_packets_.clear();
+}
+
+void StreamProcessorHelper::OnStreamFailed(uint64_t stream_lifetime_ordinal,
+                                           fuchsia::media::StreamError error) {
+  if (stream_lifetime_ordinal_ != stream_lifetime_ordinal) {
+    return;
+  }
+
+  if (error == fuchsia::media::StreamError::DECRYPTOR_NO_KEY) {
+    client_->OnNoKey();
+    return;
+  }
+
+  OnError();
+}
+
+void StreamProcessorHelper::OnInputConstraints(
+    fuchsia::media::StreamBufferConstraints constraints) {
+  // Buffer lifetime ordinal is an odd number incremented by 2 for each buffer
+  // generation as required by StreamProcessor.
+  input_buffer_lifetime_ordinal_ += 2;
+
+  // Default settings are used in CompleteInputBuffersAllocation to finish
+  // StreamProcessor input buffers setup.
+  if (!constraints.has_default_settings() ||
+      !constraints.default_settings().has_packet_count_for_server() ||
+      !constraints.default_settings().has_packet_count_for_client()) {
+    DLOG(ERROR)
+        << "Received OnInputConstraints() with missing required fields.";
+    OnError();
+    return;
+  }
+
+  DCHECK(input_packets_.empty());
+  input_buffer_constraints_ = std::move(constraints);
+
+  client_->AllocateInputBuffers(input_buffer_constraints_);
+}
+
+void StreamProcessorHelper::OnFreeInputPacket(
+    fuchsia::media::PacketHeader free_input_packet) {
+  if (!free_input_packet.has_buffer_lifetime_ordinal() ||
+      !free_input_packet.has_packet_index()) {
+    DLOG(ERROR) << "Received OnFreeInputPacket() with missing required fields.";
+    OnError();
+    return;
+  }
+
+  if (free_input_packet.buffer_lifetime_ordinal() !=
+      input_buffer_lifetime_ordinal_) {
+    return;
+  }
+
+  auto it = input_packets_.find(free_input_packet.packet_index());
+  if (it == input_packets_.end()) {
+    DLOG(WARNING) << "Received OnFreeInputPacket() with invalid packet index.";
+    return;
+  }
+
+  input_packets_.erase(it);
+}
+
+void StreamProcessorHelper::OnOutputConstraints(
+    fuchsia::media::StreamOutputConstraints output_constraints) {
+  if (!output_constraints.has_stream_lifetime_ordinal()) {
+    DLOG(ERROR)
+        << "Received OnOutputConstraints() with missing required fields.";
+    OnError();
+    return;
+  }
+
+  if (output_constraints.stream_lifetime_ordinal() !=
+      stream_lifetime_ordinal_) {
+    return;
+  }
+
+  if (!output_constraints.has_buffer_constraints_action_required() ||
+      !output_constraints.buffer_constraints_action_required()) {
+    return;
+  }
+
+  if (!output_constraints.has_buffer_constraints()) {
+    DLOG(ERROR) << "Received OnOutputConstraints() which requires buffer "
+                   "constraints action, but without buffer constraints.";
+    OnError();
+    return;
+  }
+
+  // StreamProcessor API expects odd buffer lifetime ordinal, which is
+  // incremented by 2 for each buffer generation.
+  output_buffer_lifetime_ordinal_ += 2;
+
+  output_buffer_constraints_ =
+      std::move(*output_constraints.mutable_buffer_constraints());
+
+  client_->AllocateOutputBuffers(output_buffer_constraints_);
+}
+
+void StreamProcessorHelper::OnOutputFormat(
+    fuchsia::media::StreamOutputFormat output_format) {
+  if (!output_format.has_stream_lifetime_ordinal() ||
+      !output_format.has_format_details()) {
+    DLOG(ERROR) << "Received OnOutputFormat() with missing required fields.";
+    OnError();
+    return;
+  }
+
+  if (output_format.stream_lifetime_ordinal() != stream_lifetime_ordinal_) {
+    return;
+  }
+
+  client_->OnOutputFormat(std::move(output_format));
+}
+
+void StreamProcessorHelper::OnOutputPacket(fuchsia::media::Packet output_packet,
+                                           bool error_detected_before,
+                                           bool error_detected_during) {
+  if (!output_packet.has_header() ||
+      !output_packet.header().has_buffer_lifetime_ordinal() ||
+      !output_packet.header().has_packet_index() ||
+      !output_packet.has_buffer_index()) {
+    DLOG(ERROR) << "Received OnOutputPacket() with missing required fields.";
+    OnError();
+    return;
+  }
+
+  if (output_packet.header().buffer_lifetime_ordinal() !=
+      output_buffer_lifetime_ordinal_) {
+    return;
+  }
+
+  auto buffer_index = output_packet.buffer_index();
+  auto packet_index = output_packet.header().packet_index();
+  base::TimeDelta timestamp;
+  if (output_packet.has_timestamp_ish()) {
+    timestamp = base::TimeDelta::FromNanoseconds(output_packet.timestamp_ish());
+  }
+
+  client_->OnOutputPacket(IoPacket::CreateOutput(
+      buffer_index, output_packet.start_offset(),
+      output_packet.valid_length_bytes(), timestamp,
+      BindToCurrentLoop(base::BindOnce(
+          &StreamProcessorHelper::OnRecycleOutputBuffer, weak_this_,
+          output_buffer_lifetime_ordinal_, packet_index))));
+}
+
+void StreamProcessorHelper::OnOutputEndOfStream(
+    uint64_t stream_lifetime_ordinal,
+    bool error_detected_before) {
+  if (stream_lifetime_ordinal != stream_lifetime_ordinal_) {
+    return;
+  }
+
+  stream_lifetime_ordinal_ += 2;
+  active_stream_ = false;
+
+  client_->OnProcessEos();
+}
+
+void StreamProcessorHelper::OnError() {
+  processor_.Unbind();
+  client_->OnError();
+}
+
+void StreamProcessorHelper::CompleteInputBuffersAllocation(
+    fuchsia::sysmem::BufferCollectionTokenPtr sysmem_token) {
+  DCHECK(!input_buffer_constraints_.IsEmpty());
+  fuchsia::media::StreamBufferPartialSettings settings;
+  settings.set_buffer_lifetime_ordinal(input_buffer_lifetime_ordinal_);
+  settings.set_buffer_constraints_version_ordinal(
+      input_buffer_constraints_.buffer_constraints_version_ordinal());
+  settings.set_single_buffer_mode(false);
+  settings.set_packet_count_for_server(
+      input_buffer_constraints_.default_settings().packet_count_for_server());
+  settings.set_packet_count_for_client(
+      input_buffer_constraints_.default_settings().packet_count_for_client());
+  settings.set_sysmem_token(std::move(sysmem_token));
+  processor_->SetInputBufferPartialSettings(std::move(settings));
+}
+
+void StreamProcessorHelper::CompleteOutputBuffersAllocation(
+    size_t max_used_output_buffers,
+    fuchsia::sysmem::BufferCollectionTokenPtr collection_token) {
+  DCHECK(!output_buffer_constraints_.IsEmpty());
+  DCHECK_LE(max_used_output_buffers,
+            output_buffer_constraints_.packet_count_for_client_max());
+
+  // Pass new output buffer settings to the stream processor.
+  fuchsia::media::StreamBufferPartialSettings settings;
+  settings.set_buffer_lifetime_ordinal(output_buffer_lifetime_ordinal_);
+  settings.set_buffer_constraints_version_ordinal(
+      output_buffer_constraints_.buffer_constraints_version_ordinal());
+  settings.set_packet_count_for_client(max_used_output_buffers);
+  settings.set_packet_count_for_server(
+      output_buffer_constraints_.packet_count_for_server_recommended());
+  settings.set_sysmem_token(std::move(collection_token));
+  processor_->SetOutputBufferPartialSettings(std::move(settings));
+  processor_->CompleteOutputBufferPartialSettings(
+      output_buffer_lifetime_ordinal_);
+}
+
+void StreamProcessorHelper::OnRecycleOutputBuffer(
+    uint64_t buffer_lifetime_ordinal,
+    uint32_t packet_index) {
+  if (!processor_)
+    return;
+
+  if (buffer_lifetime_ordinal != output_buffer_lifetime_ordinal_)
+    return;
+
+  fuchsia::media::PacketHeader header;
+  header.set_buffer_lifetime_ordinal(buffer_lifetime_ordinal);
+  header.set_packet_index(packet_index);
+  processor_->RecycleOutputPacket(std::move(header));
+}
+
+}  // namespace media
diff --git a/media/filters/fuchsia/stream_processor_helper.h b/media/filters/fuchsia/stream_processor_helper.h
new file mode 100644
index 0000000..39c60198
--- /dev/null
+++ b/media/filters/fuchsia/stream_processor_helper.h
@@ -0,0 +1,173 @@
+// 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 MEDIA_FILTERS_FUCHSIA_STREAM_PROCESSOR_HELPER_H_
+#define MEDIA_FILTERS_FUCHSIA_STREAM_PROCESSOR_HELPER_H_
+
+#include <fuchsia/media/cpp/fidl.h>
+#include <fuchsia/sysmem/cpp/fidl.h>
+
+#include "base/callback.h"
+#include "base/callback_helpers.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Helper class of fuchsia::media::StreamProcessor. It's responsible for:
+// 1. Data validation check.
+// 2. Stream/Buffer life time management.
+// 3. Configure StreamProcessor and input/output buffer settings.
+class MEDIA_EXPORT StreamProcessorHelper {
+ public:
+  class MEDIA_EXPORT IoPacket {
+   public:
+    static std::unique_ptr<IoPacket> CreateInput(
+        size_t index,
+        size_t size,
+        base::TimeDelta timestamp,
+        fuchsia::media::FormatDetails format,
+        base::OnceClosure destroy_cb);
+
+    static std::unique_ptr<IoPacket> CreateOutput(size_t index,
+                                                  size_t offset,
+                                                  size_t size,
+                                                  base::TimeDelta timestamp,
+                                                  base::OnceClosure destroy_cb);
+
+    IoPacket(size_t index,
+             size_t offset,
+             size_t size,
+             base::TimeDelta timestamp,
+             fuchsia::media::FormatDetails format,
+             base::OnceClosure destroy_cb);
+    ~IoPacket();
+
+    size_t index() const { return index_; }
+
+    size_t offset() const { return offset_; }
+
+    size_t size() const { return size_; }
+
+    base::TimeDelta timestamp() const { return timestamp_; }
+
+    const fuchsia::media::FormatDetails& format() const { return format_; }
+
+   private:
+    size_t index_;
+    size_t offset_;
+    size_t size_;
+    base::TimeDelta timestamp_;
+    fuchsia::media::FormatDetails format_;
+    base::ScopedClosureRunner destroy_cb_;
+  };
+
+  class Client {
+   public:
+    // Allocate input/output buffers with the given constraints. Client should
+    // call ProvideInput/OutputBufferCollectionToken to finish the buffer
+    // allocation flow.
+    virtual void AllocateInputBuffers(
+        const fuchsia::media::StreamBufferConstraints& stream_constraints) = 0;
+    virtual void AllocateOutputBuffers(
+        const fuchsia::media::StreamBufferConstraints& stream_constraints) = 0;
+
+    // Called when all the pushed packets are processed.
+    virtual void OnProcessEos() = 0;
+
+    // Called when output format is available.
+    virtual void OnOutputFormat(fuchsia::media::StreamOutputFormat format) = 0;
+
+    // Called when output packet is available. Deleting |packet| will notify
+    // StreamProcessor the output buffer is available to be re-used.
+    virtual void OnOutputPacket(std::unique_ptr<IoPacket> packet) = 0;
+
+    // Only available for decryption, which indicates currently the
+    // StreamProcessor doesn't have the content key to process.
+    virtual void OnNoKey() = 0;
+
+    // Called when any fatal errors happens.
+    virtual void OnError() = 0;
+
+   protected:
+    virtual ~Client() = default;
+  };
+
+  StreamProcessorHelper(fuchsia::media::StreamProcessorPtr processor,
+                        Client* client);
+  ~StreamProcessorHelper();
+
+  // Process one packet. |packet| is owned by this class until the buffer
+  // represented by |packet| is released.
+  void Process(std::unique_ptr<IoPacket> packet);
+
+  // Push End-Of-Stream to StreamProcessor. No more data should be sent to
+  // StreamProcessor without calling Reset.
+  void ProcessEos();
+
+  // Provide input/output BufferCollectionToken to finish StreamProcessor buffer
+  // setup flow.
+  void CompleteInputBuffersAllocation(
+      fuchsia::sysmem::BufferCollectionTokenPtr token);
+  void CompleteOutputBuffersAllocation(
+      size_t max_used_output_buffers,
+      fuchsia::sysmem::BufferCollectionTokenPtr token);
+
+  void Reset();
+
+ private:
+  // Event handlers for |processor_|.
+  void OnStreamFailed(uint64_t stream_lifetime_ordinal,
+                      fuchsia::media::StreamError error);
+  void OnInputConstraints(
+      fuchsia::media::StreamBufferConstraints input_constraints);
+  void OnFreeInputPacket(fuchsia::media::PacketHeader free_input_packet);
+  void OnOutputConstraints(
+      fuchsia::media::StreamOutputConstraints output_constraints);
+  void OnOutputFormat(fuchsia::media::StreamOutputFormat output_format);
+  void OnOutputPacket(fuchsia::media::Packet output_packet,
+                      bool error_detected_before,
+                      bool error_detected_during);
+  void OnOutputEndOfStream(uint64_t stream_lifetime_ordinal,
+                           bool error_detected_before);
+
+  void OnError();
+
+  void OnRecycleOutputBuffer(uint64_t buffer_lifetime_ordinal,
+                             uint32_t packet_index);
+
+  uint64_t stream_lifetime_ordinal_ = 1;
+
+  // Set to true if we've sent an input packet with the current
+  // stream_lifetime_ordinal_.
+  bool active_stream_ = false;
+
+  // Input buffers.
+  uint64_t input_buffer_lifetime_ordinal_ = 1;
+  fuchsia::media::StreamBufferConstraints input_buffer_constraints_;
+
+  // Map from packet index to corresponding input IoPacket. IoPacket should be
+  // owned by this class until StreamProcessor released the buffer.
+  base::flat_map<size_t, std::unique_ptr<IoPacket>> input_packets_;
+
+  // Output buffers.
+  uint64_t output_buffer_lifetime_ordinal_ = 1;
+  fuchsia::media::StreamBufferConstraints output_buffer_constraints_;
+
+  fuchsia::media::StreamProcessorPtr processor_;
+  Client* const client_;
+
+  base::WeakPtr<StreamProcessorHelper> weak_this_;
+  base::WeakPtrFactory<StreamProcessorHelper> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(StreamProcessorHelper);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FILTERS_FUCHSIA_STREAM_PROCESSOR_HELPER_H_
diff --git a/media/filters/fuchsia/sysmem_buffer_pool.cc b/media/filters/fuchsia/sysmem_buffer_pool.cc
new file mode 100644
index 0000000..e7b611fa
--- /dev/null
+++ b/media/filters/fuchsia/sysmem_buffer_pool.cc
@@ -0,0 +1,188 @@
+// 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 "media/filters/fuchsia/sysmem_buffer_pool.h"
+
+#include <zircon/rights.h>
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/fuchsia/default_context.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "media/filters/fuchsia/sysmem_buffer_reader.h"
+#include "media/filters/fuchsia/sysmem_buffer_writer.h"
+
+namespace media {
+namespace {
+
+template <typename T>
+using CreateAccessorCB = base::OnceCallback<void(std::unique_ptr<T>)>;
+
+template <typename T>
+void CreateAccessorInstance(
+    zx_status_t status,
+    fuchsia::sysmem::BufferCollectionInfo_2 buffer_collection_info,
+    CreateAccessorCB<T> create_cb) {
+  if (status != ZX_OK) {
+    ZX_LOG(ERROR, status) << "Fail to enable reader or writer";
+    std::move(create_cb).Run(nullptr);
+    return;
+  }
+
+  std::unique_ptr<T> accessor = T::Create(std::move(buffer_collection_info));
+  if (!accessor) {
+    std::move(create_cb).Run(nullptr);
+    return;
+  }
+
+  std::move(create_cb).Run(std::move(accessor));
+}
+
+template <typename T>
+void CreateAccessor(fuchsia::sysmem::BufferCollection* collection,
+                    CreateAccessorCB<T> create_cb) {
+  collection->WaitForBuffersAllocated(
+      [create_cb = std::move(create_cb)](zx_status_t status,
+                                         fuchsia::sysmem::BufferCollectionInfo_2
+                                             buffer_collection_info) mutable {
+        CreateAccessorInstance<T>(status, std::move(buffer_collection_info),
+                                  std::move(create_cb));
+      });
+}
+
+}  // namespace
+
+SysmemBufferPool::Creator::Creator(
+    fuchsia::sysmem::BufferCollectionPtr collection,
+    std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens)
+    : collection_(std::move(collection)),
+      shared_tokens_(std::move(shared_tokens)) {
+  collection_.set_error_handler(
+      [this](zx_status_t status) {
+        ZX_DLOG(ERROR, status)
+            << "Connection to BufferCollection was disconnected.";
+        collection_.Unbind();
+
+        if (create_cb_)
+          std::move(create_cb_).Run(nullptr);
+      });
+}
+
+SysmemBufferPool::Creator::~Creator() = default;
+
+void SysmemBufferPool::Creator::Create(
+    fuchsia::sysmem::BufferCollectionConstraints constraints,
+    CreateCB create_cb) {
+  DCHECK(!create_cb_);
+  create_cb_ = std::move(create_cb);
+  // BufferCollection needs to be synchronized to ensure that all token
+  // duplicate requests have been processed and sysmem knows about all clients
+  // that will be using this buffer collection.
+  collection_->Sync([this, constraints = std::move(constraints)]() mutable {
+    collection_->SetConstraints(true /* has constraints */,
+                                std::move(constraints));
+
+    DCHECK(create_cb_);
+    std::move(create_cb_)
+        .Run(std::make_unique<SysmemBufferPool>(std::move(collection_),
+                                                std::move(shared_tokens_)));
+  });
+}
+
+SysmemBufferPool::SysmemBufferPool(
+    fuchsia::sysmem::BufferCollectionPtr collection,
+    std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens)
+    : collection_(std::move(collection)),
+      shared_tokens_(std::move(shared_tokens)) {
+  collection_.set_error_handler(
+      [this](zx_status_t status) {
+        ZX_LOG(ERROR, status)
+            << "The fuchsia.sysmem.BufferCollection channel was disconnected.";
+        collection_.Unbind();
+        if (create_reader_cb_)
+          std::move(create_reader_cb_).Run(nullptr);
+        if (create_writer_cb_)
+          std::move(create_writer_cb_).Run(nullptr);
+      });
+}
+
+SysmemBufferPool::~SysmemBufferPool() = default;
+
+fuchsia::sysmem::BufferCollectionTokenPtr SysmemBufferPool::TakeToken() {
+  DCHECK(!shared_tokens_.empty());
+  auto token = std::move(shared_tokens_.back());
+  shared_tokens_.pop_back();
+  return token;
+}
+
+void SysmemBufferPool::CreateReader(CreateReaderCB create_cb) {
+  DCHECK(!create_reader_cb_);
+  create_reader_cb_ = std::move(create_cb);
+
+  CreateAccessor<SysmemBufferReader>(
+      collection_.get(), base::BindOnce(&SysmemBufferPool::OnReaderCreated,
+                                        base::Unretained(this)));
+}
+
+void SysmemBufferPool::OnReaderCreated(
+    std::unique_ptr<SysmemBufferReader> reader) {
+  DCHECK(create_reader_cb_);
+  std::move(create_reader_cb_).Run(std::move(reader));
+}
+
+void SysmemBufferPool::CreateWriter(CreateWriterCB create_cb) {
+  DCHECK(!create_writer_cb_);
+  create_writer_cb_ = std::move(create_cb);
+
+  CreateAccessor<SysmemBufferWriter>(
+      collection_.get(), base::BindOnce(&SysmemBufferPool::OnWriterCreated,
+                                        base::Unretained(this)));
+}
+
+void SysmemBufferPool::OnWriterCreated(
+    std::unique_ptr<SysmemBufferWriter> writer) {
+  DCHECK(create_writer_cb_);
+  std::move(create_writer_cb_).Run(std::move(writer));
+}
+
+BufferAllocator::BufferAllocator() {
+  allocator_ = base::fuchsia::ComponentContextForCurrentProcess()
+                   ->svc()
+                   ->Connect<fuchsia::sysmem::Allocator>();
+
+  allocator_.set_error_handler([](zx_status_t status) {
+    // Just log a warning. We will handle BufferCollection the failure when
+    // trying to create a new BufferCollection.
+    ZX_DLOG(WARNING, status)
+        << "The fuchsia.sysmem.Allocator channel was disconnected.";
+  });
+}
+
+BufferAllocator::~BufferAllocator() = default;
+
+std::unique_ptr<SysmemBufferPool::Creator>
+BufferAllocator::MakeBufferPoolCreator(size_t num_of_tokens) {
+  // Create a new sysmem buffer collection token for the allocated buffers.
+  fuchsia::sysmem::BufferCollectionTokenPtr collection_token;
+  allocator_->AllocateSharedCollection(collection_token.NewRequest());
+
+  // Create collection token for sharing with other components.
+  std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens;
+  for (size_t i = 0; i < num_of_tokens; i++) {
+    fuchsia::sysmem::BufferCollectionTokenPtr token;
+    collection_token->Duplicate(ZX_RIGHT_SAME_RIGHTS, token.NewRequest());
+    shared_tokens.push_back(std::move(token));
+  }
+
+  fuchsia::sysmem::BufferCollectionPtr buffer_collection;
+
+  // Convert the token to a BufferCollection connection.
+  allocator_->BindSharedCollection(std::move(collection_token),
+                                   buffer_collection.NewRequest());
+
+  return std::make_unique<SysmemBufferPool::Creator>(
+      std::move(buffer_collection), std::move(shared_tokens));
+}
+
+}  // namespace media
diff --git a/media/filters/fuchsia/sysmem_buffer_pool.h b/media/filters/fuchsia/sysmem_buffer_pool.h
new file mode 100644
index 0000000..bab4e863
--- /dev/null
+++ b/media/filters/fuchsia/sysmem_buffer_pool.h
@@ -0,0 +1,105 @@
+// 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 MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_POOL_H_
+#define MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_POOL_H_
+
+#include <fuchsia/media/cpp/fidl.h>
+#include <fuchsia/sysmem/cpp/fidl.h>
+#include <lib/sys/cpp/component_context.h>
+
+#include <list>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/span.h"
+#include "base/macros.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+class SysmemBufferReader;
+class SysmemBufferWriter;
+
+// Pool of buffers allocated by sysmem. It owns BufferCollection. It doesn't
+// provide any function read/write the buffers. Call should use
+// ReadableBufferPool/WritableBufferPool for read/write.
+class MEDIA_EXPORT SysmemBufferPool {
+ public:
+  using CreateReaderCB =
+      base::OnceCallback<void(std::unique_ptr<SysmemBufferReader>)>;
+  using CreateWriterCB =
+      base::OnceCallback<void(std::unique_ptr<SysmemBufferWriter>)>;
+
+  // Creates SysmemBufferPool asynchronously. It also owns the channel to
+  // fuchsia services.
+  class MEDIA_EXPORT Creator {
+   public:
+    using CreateCB =
+        base::OnceCallback<void(std::unique_ptr<SysmemBufferPool>)>;
+    Creator(
+        fuchsia::sysmem::BufferCollectionPtr collection,
+        std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens);
+    ~Creator();
+
+    void Create(fuchsia::sysmem::BufferCollectionConstraints constraints,
+                CreateCB build_cb);
+
+   private:
+    fuchsia::sysmem::BufferCollectionPtr collection_;
+    std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens_;
+    CreateCB create_cb_;
+
+    DISALLOW_COPY_AND_ASSIGN(Creator);
+  };
+
+  SysmemBufferPool(
+      fuchsia::sysmem::BufferCollectionPtr collection,
+      std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens);
+  ~SysmemBufferPool();
+
+  fuchsia::sysmem::BufferCollectionTokenPtr TakeToken();
+
+  // Create Reader/Writer to access raw memory. The returned Reader/Writer is
+  // owned by SysmemBufferPool and lives as long as SysmemBufferPool.
+  void CreateReader(CreateReaderCB create_cb);
+  void CreateWriter(CreateWriterCB create_cb);
+
+  // Returns if this object is still usable. Caller must check this before
+  // calling SysmemBufferReader/Writer APIs.
+  bool is_live() const { return collection_.is_bound(); }
+
+ private:
+  friend class BufferAllocator;
+
+  void OnReaderCreated(std::unique_ptr<SysmemBufferReader> reader);
+  void OnWriterCreated(std::unique_ptr<SysmemBufferWriter> reader);
+
+  fuchsia::sysmem::BufferCollectionPtr collection_;
+  std::vector<fuchsia::sysmem::BufferCollectionTokenPtr> shared_tokens_;
+
+  CreateReaderCB create_reader_cb_;
+  CreateWriterCB create_writer_cb_;
+
+  DISALLOW_COPY_AND_ASSIGN(SysmemBufferPool);
+};
+
+// Wrapper of sysmem Allocator.
+class MEDIA_EXPORT BufferAllocator {
+ public:
+  BufferAllocator();
+  ~BufferAllocator();
+
+  std::unique_ptr<SysmemBufferPool::Creator> MakeBufferPoolCreator(
+      size_t num_shared_token);
+
+ private:
+  fuchsia::sysmem::AllocatorPtr allocator_;
+
+  DISALLOW_COPY_AND_ASSIGN(BufferAllocator);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_POOL_H_
diff --git a/media/filters/fuchsia/sysmem_buffer_reader.cc b/media/filters/fuchsia/sysmem_buffer_reader.cc
new file mode 100644
index 0000000..25dc719
--- /dev/null
+++ b/media/filters/fuchsia/sysmem_buffer_reader.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 "media/filters/fuchsia/sysmem_buffer_reader.h"
+
+#include "base/fuchsia/fuchsia_logging.h"
+
+namespace media {
+
+SysmemBufferReader::SysmemBufferReader(
+    fuchsia::sysmem::BufferCollectionInfo_2 info)
+    : collection_info_(std::move(info)) {}
+
+SysmemBufferReader::~SysmemBufferReader() = default;
+
+bool SysmemBufferReader::Read(size_t index,
+                              size_t offset,
+                              base::span<uint8_t> data) {
+  DCHECK_LT(index, collection_info_.buffer_count);
+  const fuchsia::sysmem::BufferMemorySettings& settings =
+      collection_info_.settings.buffer_settings;
+  fuchsia::sysmem::VmoBuffer& buffer = collection_info_.buffers[index];
+  DCHECK_LE(buffer.vmo_usable_start + offset + data.size(),
+            settings.size_bytes);
+
+  size_t vmo_offset = buffer.vmo_usable_start + offset;
+
+  // Invalidate cache.
+  if (settings.coherency_domain == fuchsia::sysmem::CoherencyDomain::RAM) {
+    zx_status_t status = buffer.vmo.op_range(
+        ZX_VMO_OP_CACHE_CLEAN_INVALIDATE, vmo_offset, data.size(), nullptr, 0);
+    ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to invalidate cache";
+  }
+
+  zx_status_t status = buffer.vmo.read(data.data(), vmo_offset, data.size());
+  ZX_LOG_IF(ERROR, status != ZX_OK, status) << "Fail to read";
+  return status == ZX_OK;
+}
+
+// static
+std::unique_ptr<SysmemBufferReader> SysmemBufferReader::Create(
+    fuchsia::sysmem::BufferCollectionInfo_2 info) {
+  return std::make_unique<SysmemBufferReader>(std::move(info));
+}
+
+// static
+fuchsia::sysmem::BufferCollectionConstraints
+SysmemBufferReader::GetRecommendedConstraints(size_t max_used_output_frames) {
+  fuchsia::sysmem::BufferCollectionConstraints buffer_constraints;
+  buffer_constraints.usage.cpu = fuchsia::sysmem::cpuUsageRead;
+  buffer_constraints.min_buffer_count_for_camping = max_used_output_frames;
+  return buffer_constraints;
+}
+
+}  // namespace media
diff --git a/media/filters/fuchsia/sysmem_buffer_reader.h b/media/filters/fuchsia/sysmem_buffer_reader.h
new file mode 100644
index 0000000..8426055
--- /dev/null
+++ b/media/filters/fuchsia/sysmem_buffer_reader.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_READER_H_
+#define MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_READER_H_
+
+#include <fuchsia/media/cpp/fidl.h>
+#include <fuchsia/sysmem/cpp/fidl.h>
+
+#include <memory>
+
+#include "base/containers/span.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Helper class to read content from fuchsia::sysmem::BufferCollection.
+class MEDIA_EXPORT SysmemBufferReader {
+ public:
+  static fuchsia::sysmem::BufferCollectionConstraints GetRecommendedConstraints(
+      size_t max_used_output_frames);
+
+  static std::unique_ptr<SysmemBufferReader> Create(
+      fuchsia::sysmem::BufferCollectionInfo_2 info);
+
+  explicit SysmemBufferReader(fuchsia::sysmem::BufferCollectionInfo_2 info);
+  ~SysmemBufferReader();
+
+  // Read the buffer content at |index| into |data|, starting from |offset|.
+  bool Read(size_t index, size_t offset, base::span<uint8_t> data);
+
+ private:
+  fuchsia::sysmem::BufferCollectionInfo_2 collection_info_;
+
+  DISALLOW_COPY_AND_ASSIGN(SysmemBufferReader);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_READER_H_
diff --git a/media/filters/fuchsia/sysmem_buffer_writer.cc b/media/filters/fuchsia/sysmem_buffer_writer.cc
new file mode 100644
index 0000000..ea6e355
--- /dev/null
+++ b/media/filters/fuchsia/sysmem_buffer_writer.cc
@@ -0,0 +1,175 @@
+// 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 "media/filters/fuchsia/sysmem_buffer_writer.h"
+
+#include <zircon/rights.h>
+#include <algorithm>
+
+#include "base/bits.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/process/process_metrics.h"
+
+namespace media {
+
+class SysmemBufferWriter::Buffer {
+ public:
+  Buffer() = default;
+
+  ~Buffer() {
+    if (!base_address_) {
+      return;
+    }
+
+    size_t mapped_bytes =
+        base::bits::Align(offset_ + size_, base::GetPageSize());
+    zx_status_t status = zx::vmar::root_self()->unmap(
+        reinterpret_cast<uintptr_t>(base_address_), mapped_bytes);
+    ZX_DCHECK(status == ZX_OK, status) << "zx_vmar_unmap";
+  }
+
+  Buffer(Buffer&&) = default;
+  Buffer& operator=(Buffer&&) = default;
+
+  bool Initialize(zx::vmo vmo,
+                  size_t offset,
+                  size_t size,
+                  fuchsia::sysmem::CoherencyDomain coherency_domain) {
+    DCHECK(!base_address_);
+    DCHECK(vmo);
+
+    // zx_vmo_write() doesn't work for sysmem-allocated VMOs (see ZX-4854), so
+    // the VMOs have to be mapped.
+    size_t bytes_to_map = base::bits::Align(offset + size, base::GetPageSize());
+    uintptr_t addr;
+    zx_status_t status = zx::vmar::root_self()->map(
+        /*vmar_offset=*/0, vmo, /*vmo_offset=*/0, bytes_to_map,
+        ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, &addr);
+    if (status != ZX_OK) {
+      ZX_DLOG(ERROR, status) << "zx_vmar_map";
+      return false;
+    }
+
+    base_address_ = reinterpret_cast<uint8_t*>(addr);
+    offset_ = offset;
+    size_ = size;
+    coherency_domain_ = coherency_domain;
+
+    return true;
+  }
+
+  bool is_used() const { return is_used_; }
+  size_t size() const { return size_; }
+
+  // Copies as much data as possible from |data| to this input buffer.
+  size_t Write(base::span<const uint8_t> data) {
+    DCHECK(!is_used_);
+    is_used_ = true;
+
+    size_t bytes_to_fill = std::min(size_, data.size());
+    memcpy(base_address_ + offset_, data.data(), bytes_to_fill);
+
+    // Flush CPU cache if StreamProcessor reads from RAM.
+    if (coherency_domain_ == fuchsia::sysmem::CoherencyDomain::RAM) {
+      zx_status_t status = zx_cache_flush(base_address_ + offset_,
+                                          bytes_to_fill, ZX_CACHE_FLUSH_DATA);
+      ZX_DCHECK(status == ZX_OK, status) << "zx_cache_flush";
+    }
+
+    return bytes_to_fill;
+  }
+
+  void Release() { is_used_ = false; }
+
+ private:
+  uint8_t* base_address_ = nullptr;
+
+  // Buffer settings provided by sysmem.
+  size_t offset_ = 0;
+  size_t size_ = 0;
+  fuchsia::sysmem::CoherencyDomain coherency_domain_;
+
+  // Set to true when this buffer is being used by the codec.
+  bool is_used_ = false;
+};
+
+SysmemBufferWriter::SysmemBufferWriter(std::vector<Buffer> buffers)
+    : buffers_(std::move(buffers)) {}
+
+SysmemBufferWriter::~SysmemBufferWriter() = default;
+
+size_t SysmemBufferWriter::Write(size_t index, base::span<const uint8_t> data) {
+  DCHECK_LT(index, buffers_.size());
+  DCHECK(!buffers_[index].is_used());
+
+  return buffers_[index].Write(data);
+}
+
+base::Optional<size_t> SysmemBufferWriter::Acquire() {
+  auto it = std::find_if(
+      buffers_.begin(), buffers_.end(),
+      [](const SysmemBufferWriter::Buffer& buf) { return !buf.is_used(); });
+
+  if (it == buffers_.end())
+    return base::nullopt;
+
+  return it - buffers_.begin();
+}
+
+void SysmemBufferWriter::Release(size_t index) {
+  DCHECK_LT(index, buffers_.size());
+  buffers_[index].Release();
+}
+
+// static
+std::unique_ptr<SysmemBufferWriter> SysmemBufferWriter::Create(
+    fuchsia::sysmem::BufferCollectionInfo_2 info) {
+  std::vector<SysmemBufferWriter::Buffer> buffers;
+  buffers.resize(info.buffer_count);
+
+  fuchsia::sysmem::BufferMemorySettings& settings =
+      info.settings.buffer_settings;
+  for (size_t i = 0; i < info.buffer_count; ++i) {
+    fuchsia::sysmem::VmoBuffer& buffer = info.buffers[i];
+    if (!buffers[i].Initialize(std::move(buffer.vmo), buffer.vmo_usable_start,
+                               settings.size_bytes,
+                               settings.coherency_domain)) {
+      return nullptr;
+    }
+  }
+
+  return std::make_unique<SysmemBufferWriter>(std::move(buffers));
+}
+
+// static
+base::Optional<fuchsia::sysmem::BufferCollectionConstraints>
+SysmemBufferWriter::GetRecommendedConstraints(
+    const fuchsia::media::StreamBufferConstraints& stream_constraints) {
+  fuchsia::sysmem::BufferCollectionConstraints buffer_constraints;
+
+  if (!stream_constraints.has_default_settings() ||
+      !stream_constraints.default_settings().has_packet_count_for_client()) {
+    DLOG(ERROR)
+        << "Received StreamBufferConstaints with missing required fields.";
+    return base::nullopt;
+  }
+
+  // Currently we have to map buffers VMOs to write to them (see ZX-4854) and
+  // memory cannot be mapped as write-only (see ZX-4872), so request RW access
+  // even though we will never need to read from these buffers.
+  buffer_constraints.usage.cpu =
+      fuchsia::sysmem::cpuUsageRead | fuchsia::sysmem::cpuUsageWrite;
+
+  buffer_constraints.min_buffer_count_for_camping =
+      stream_constraints.default_settings().packet_count_for_client();
+  buffer_constraints.has_buffer_memory_constraints = true;
+  buffer_constraints.buffer_memory_constraints.min_size_bytes =
+      stream_constraints.has_per_packet_buffer_bytes_recommended();
+  buffer_constraints.buffer_memory_constraints.ram_domain_supported = true;
+  buffer_constraints.buffer_memory_constraints.cpu_domain_supported = true;
+
+  return buffer_constraints;
+}
+
+}  // namespace media
diff --git a/media/filters/fuchsia/sysmem_buffer_writer.h b/media/filters/fuchsia/sysmem_buffer_writer.h
new file mode 100644
index 0000000..9f9e1f3
--- /dev/null
+++ b/media/filters/fuchsia/sysmem_buffer_writer.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 MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_WRITER_H_
+#define MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_WRITER_H_
+
+#include <fuchsia/media/cpp/fidl.h>
+#include <fuchsia/sysmem/cpp/fidl.h>
+
+#include <memory>
+
+#include "base/containers/span.h"
+#include "base/optional.h"
+#include "media/base/media_export.h"
+
+namespace media {
+
+// Helper class to write content into fuchsia::sysmem::BufferCollection.
+class MEDIA_EXPORT SysmemBufferWriter {
+ public:
+  class Buffer;
+
+  static base::Optional<fuchsia::sysmem::BufferCollectionConstraints>
+  GetRecommendedConstraints(
+      const fuchsia::media::StreamBufferConstraints& stream_constraints);
+
+  static std::unique_ptr<SysmemBufferWriter> Create(
+      fuchsia::sysmem::BufferCollectionInfo_2 info);
+
+  explicit SysmemBufferWriter(std::vector<Buffer> buffers);
+  ~SysmemBufferWriter();
+
+  // Write the content of |data| into buffer at |index|. Return num of bytes
+  // written into the buffer. Write a used buffer will fail. It will mark the
+  // buffer as "used".
+  size_t Write(size_t index, base::span<const uint8_t> data);
+
+  // Acquire unused buffer for write. If |min_size| is provided, the returned
+  // buffer will have available size larger than |min_size|. This will NOT
+  // mark the buffer as "used".
+  base::Optional<size_t> Acquire();
+
+  // Notify the pool buffer at |index| is free to write new data.
+  void Release(size_t index);
+
+ private:
+  std::vector<Buffer> buffers_;
+
+  DISALLOW_COPY_AND_ASSIGN(SysmemBufferWriter);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FILTERS_FUCHSIA_SYSMEM_BUFFER_WRITER_H_
diff --git a/media/fuchsia/cdm/BUILD.gn b/media/fuchsia/cdm/BUILD.gn
index 04defe5..1f08b488 100644
--- a/media/fuchsia/cdm/BUILD.gn
+++ b/media/fuchsia/cdm/BUILD.gn
@@ -10,6 +10,10 @@
     "fuchsia_cdm.h",
     "fuchsia_cdm_factory.cc",
     "fuchsia_cdm_factory.h",
+    "fuchsia_decryptor.cc",
+    "fuchsia_decryptor.h",
+    "stream_processor_decryptor.cc",
+    "stream_processor_decryptor.h",
   ]
 
   deps = [
diff --git a/media/fuchsia/cdm/fuchsia_cdm.cc b/media/fuchsia/cdm/fuchsia_cdm.cc
index 5d46759..398c803 100644
--- a/media/fuchsia/cdm/fuchsia_cdm.cc
+++ b/media/fuchsia/cdm/fuchsia_cdm.cc
@@ -10,6 +10,7 @@
 #include "fuchsia/base/mem_buffer_util.h"
 #include "media/base/callback_registry.h"
 #include "media/base/cdm_promise.h"
+#include "media/fuchsia/cdm/fuchsia_decryptor.h"
 
 namespace media {
 
@@ -221,7 +222,9 @@
 
 FuchsiaCdm::FuchsiaCdm(fuchsia::media::drm::ContentDecryptionModulePtr cdm,
                        SessionCallbacks callbacks)
-    : cdm_(std::move(cdm)), session_callbacks_(std::move(callbacks)) {
+    : cdm_(std::move(cdm)),
+      session_callbacks_(std::move(callbacks)),
+      decryptor_(new FuchsiaDecryptor(cdm_.get())) {
   DCHECK(cdm_);
   cdm_.set_error_handler([](zx_status_t status) {
     // Error will be handled in CdmSession::OnSessionError.
@@ -396,8 +399,8 @@
 }
 
 Decryptor* FuchsiaCdm::GetDecryptor() {
-  NOTIMPLEMENTED();
-  return nullptr;
+  DCHECK(decryptor_);
+  return decryptor_.get();
 }
 
 int FuchsiaCdm::GetCdmId() const {
diff --git a/media/fuchsia/cdm/fuchsia_cdm.h b/media/fuchsia/cdm/fuchsia_cdm.h
index 93e2a7e..b12e527 100644
--- a/media/fuchsia/cdm/fuchsia_cdm.h
+++ b/media/fuchsia/cdm/fuchsia_cdm.h
@@ -15,6 +15,7 @@
 #include "media/base/content_decryption_module.h"
 
 namespace media {
+class FuchsiaDecryptor;
 
 class FuchsiaCdm : public ContentDecryptionModule, public CdmContext {
  public:
@@ -86,6 +87,8 @@
   fuchsia::media::drm::ContentDecryptionModulePtr cdm_;
   SessionCallbacks session_callbacks_;
 
+  std::unique_ptr<FuchsiaDecryptor> decryptor_;
+
   DISALLOW_COPY_AND_ASSIGN(FuchsiaCdm);
 };
 
diff --git a/media/fuchsia/cdm/fuchsia_decryptor.cc b/media/fuchsia/cdm/fuchsia_decryptor.cc
new file mode 100644
index 0000000..a84ea4a
--- /dev/null
+++ b/media/fuchsia/cdm/fuchsia_decryptor.cc
@@ -0,0 +1,90 @@
+// 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 "media/fuchsia/cdm/fuchsia_decryptor.h"
+
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/logging.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/video_frame.h"
+#include "media/fuchsia/cdm/stream_processor_decryptor.h"
+
+namespace media {
+
+FuchsiaDecryptor::FuchsiaDecryptor(
+    fuchsia::media::drm::ContentDecryptionModule* cdm)
+    : cdm_(cdm) {
+  DCHECK(cdm_);
+}
+
+FuchsiaDecryptor::~FuchsiaDecryptor() = default;
+
+void FuchsiaDecryptor::RegisterNewKeyCB(StreamType stream_type,
+                                        const NewKeyCB& key_added_cb) {
+  NOTIMPLEMENTED();
+}
+
+void FuchsiaDecryptor::Decrypt(StreamType stream_type,
+                               scoped_refptr<DecoderBuffer> encrypted,
+                               const DecryptCB& decrypt_cb) {
+  if (stream_type != StreamType::kAudio) {
+    NOTREACHED();
+    decrypt_cb.Run(Status::kError, nullptr);
+    return;
+  }
+
+  if (!audio_decryptor_)
+    audio_decryptor_ = StreamProcessorDecryptor::CreateAudioDecryptor(cdm_);
+
+  audio_decryptor_->Decrypt(std::move(encrypted), decrypt_cb);
+}
+
+void FuchsiaDecryptor::CancelDecrypt(StreamType stream_type) {
+  DCHECK_EQ(stream_type, StreamType::kAudio);
+  audio_decryptor_->CancelDecrypt();
+}
+
+void FuchsiaDecryptor::InitializeAudioDecoder(const AudioDecoderConfig& config,
+                                              const DecoderInitCB& init_cb) {
+  // Only supports decrypt for audio stream.
+  NOTREACHED();
+  init_cb.Run(false);
+}
+
+void FuchsiaDecryptor::InitializeVideoDecoder(const VideoDecoderConfig& config,
+                                              const DecoderInitCB& init_cb) {
+  NOTIMPLEMENTED();
+  init_cb.Run(false);
+}
+
+void FuchsiaDecryptor::DecryptAndDecodeAudio(
+    scoped_refptr<DecoderBuffer> encrypted,
+    const AudioDecodeCB& audio_decode_cb) {
+  // Only supports decrypt for audio stream.
+  NOTREACHED();
+  audio_decode_cb.Run(Status::kError, AudioFrames());
+}
+
+void FuchsiaDecryptor::DecryptAndDecodeVideo(
+    scoped_refptr<DecoderBuffer> encrypted,
+    const VideoDecodeCB& video_decode_cb) {
+  NOTIMPLEMENTED();
+  video_decode_cb.Run(Status::kError, nullptr);
+}
+
+void FuchsiaDecryptor::ResetDecoder(StreamType stream_type) {
+  DCHECK_EQ(stream_type, StreamType::kVideo);
+  NOTIMPLEMENTED();
+}
+
+void FuchsiaDecryptor::DeinitializeDecoder(StreamType stream_type) {
+  DCHECK_EQ(stream_type, StreamType::kVideo);
+  NOTIMPLEMENTED();
+}
+
+bool FuchsiaDecryptor::CanAlwaysDecrypt() {
+  return false;
+}
+
+}  // namespace media
diff --git a/media/fuchsia/cdm/fuchsia_decryptor.h b/media/fuchsia/cdm/fuchsia_decryptor.h
new file mode 100644
index 0000000..76ed365
--- /dev/null
+++ b/media/fuchsia/cdm/fuchsia_decryptor.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 MEDIA_FUCHSIA_CDM_FUCHSIA_DECRYPTOR_H_
+#define MEDIA_FUCHSIA_CDM_FUCHSIA_DECRYPTOR_H_
+
+#include <memory>
+
+#include "media/base/decryptor.h"
+
+namespace fuchsia {
+namespace media {
+namespace drm {
+class ContentDecryptionModule;
+}  // namespace drm
+}  // namespace media
+}  // namespace fuchsia
+
+namespace media {
+class StreamProcessorDecryptor;
+
+class FuchsiaDecryptor : public Decryptor {
+ public:
+  // Caller should make sure |cdm| lives longer than this class.
+  explicit FuchsiaDecryptor(fuchsia::media::drm::ContentDecryptionModule* cdm);
+  ~FuchsiaDecryptor() override;
+
+  // media::Decryptor implementation:
+  void RegisterNewKeyCB(StreamType stream_type,
+                        const NewKeyCB& key_added_cb) override;
+  void Decrypt(StreamType stream_type,
+               scoped_refptr<DecoderBuffer> encrypted,
+               const DecryptCB& decrypt_cb) override;
+  void CancelDecrypt(StreamType stream_type) override;
+  void InitializeAudioDecoder(const AudioDecoderConfig& config,
+                              const DecoderInitCB& init_cb) override;
+  void InitializeVideoDecoder(const VideoDecoderConfig& config,
+                              const DecoderInitCB& init_cb) override;
+  void DecryptAndDecodeAudio(scoped_refptr<DecoderBuffer> encrypted,
+                             const AudioDecodeCB& audio_decode_cb) override;
+  void DecryptAndDecodeVideo(scoped_refptr<DecoderBuffer> encrypted,
+                             const VideoDecodeCB& video_decode_cb) override;
+  void ResetDecoder(StreamType stream_type) override;
+  void DeinitializeDecoder(StreamType stream_type) override;
+  bool CanAlwaysDecrypt() override;
+
+ private:
+  fuchsia::media::drm::ContentDecryptionModule* const cdm_;
+
+  std::unique_ptr<StreamProcessorDecryptor> audio_decryptor_;
+
+  DISALLOW_COPY_AND_ASSIGN(FuchsiaDecryptor);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FUCHSIA_CDM_FUCHSIA_DECRYPTOR_H_
diff --git a/media/fuchsia/cdm/stream_processor_decryptor.cc b/media/fuchsia/cdm/stream_processor_decryptor.cc
new file mode 100644
index 0000000..1665687
--- /dev/null
+++ b/media/fuchsia/cdm/stream_processor_decryptor.cc
@@ -0,0 +1,351 @@
+// 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 "media/fuchsia/cdm/stream_processor_decryptor.h"
+
+#include <fuchsia/media/cpp/fidl.h>
+#include <fuchsia/media/drm/cpp/fidl.h>
+
+#include "base/bind.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/logging.h"
+#include "media/base/decoder_buffer.h"
+#include "media/base/decrypt_config.h"
+#include "media/base/encryption_pattern.h"
+#include "media/base/subsample_entry.h"
+#include "media/filters/fuchsia/sysmem_buffer_reader.h"
+#include "media/filters/fuchsia/sysmem_buffer_writer.h"
+
+namespace media {
+namespace {
+
+// Decryptor will copy decrypted data immediately once it's available. Client
+// just needs one buffer.
+const uint32_t kMaxUsedOutputFrames = 1;
+
+std::string GetEncryptionMode(EncryptionMode mode) {
+  switch (mode) {
+    case EncryptionMode::kCenc:
+      return fuchsia::media::drm::ENCRYPTION_MODE_CENC;
+    case EncryptionMode::kCbcs:
+      return fuchsia::media::drm::ENCRYPTION_MODE_CBCS;
+    default:
+      NOTREACHED() << "unknown encryption mode " << static_cast<int>(mode);
+      return "";
+  }
+}
+
+fuchsia::media::KeyId GetKeyId(const std::string& key_id) {
+  fuchsia::media::KeyId fuchsia_key_id;
+  DCHECK_EQ(key_id.size(), fuchsia_key_id.data.size());
+  std::copy(key_id.begin(), key_id.end(), fuchsia_key_id.data.begin());
+  return fuchsia_key_id;
+}
+
+std::vector<fuchsia::media::SubsampleEntry> GetSubsamples(
+    const std::vector<SubsampleEntry>& subsamples) {
+  std::vector<fuchsia::media::SubsampleEntry> fuchsia_subsamples(
+      subsamples.size());
+
+  for (size_t i = 0; i < subsamples.size(); i++) {
+    fuchsia_subsamples[i].clear_bytes = subsamples[i].clear_bytes;
+    fuchsia_subsamples[i].encrypted_bytes = subsamples[i].cypher_bytes;
+  }
+
+  return fuchsia_subsamples;
+}
+
+fuchsia::media::EncryptionPattern GetEncryptionPattern(
+    EncryptionPattern pattern) {
+  fuchsia::media::EncryptionPattern fuchsia_pattern;
+  fuchsia_pattern.clear_blocks = pattern.skip_byte_block();
+  fuchsia_pattern.encrypted_blocks = pattern.crypt_byte_block();
+  return fuchsia_pattern;
+}
+
+fuchsia::media::FormatDetails GetFormatDetails(const DecryptConfig* config) {
+  DCHECK(config);
+
+  fuchsia::media::EncryptedFormat encrypted_format;
+  encrypted_format.set_mode(GetEncryptionMode(config->encryption_mode()))
+      .set_key_id(GetKeyId(config->key_id()))
+      .set_init_vector(
+          std::vector<uint8_t>(config->iv().begin(), config->iv().end()))
+      .set_subsamples(GetSubsamples(config->subsamples()));
+  if (config->encryption_mode() == EncryptionMode::kCbcs) {
+    DCHECK(config->encryption_pattern().has_value());
+    encrypted_format.set_pattern(
+        GetEncryptionPattern(config->encryption_pattern().value()));
+  }
+
+  fuchsia::media::FormatDetails format;
+  format.set_format_details_version_ordinal(0);
+  format.mutable_domain()->crypto().set_encrypted(std::move(encrypted_format));
+  return format;
+}
+
+}  // namespace
+
+StreamProcessorDecryptor::StreamProcessorDecryptor(
+    fuchsia::media::StreamProcessorPtr processor)
+    : processor_(std::move(processor), this) {}
+
+StreamProcessorDecryptor::~StreamProcessorDecryptor() = default;
+
+void StreamProcessorDecryptor::Decrypt(scoped_refptr<DecoderBuffer> encrypted,
+                                       Decryptor::DecryptCB decrypt_cb) {
+  DCHECK(!encrypted->end_of_stream()) << "EOS frame is always clear.";
+  DCHECK(!decrypt_cb_);
+  DCHECK(!pending_encrypted_buffer_);
+
+  decrypt_cb_ = std::move(decrypt_cb);
+
+  // Input buffer writer is not available. Wait.
+  if (!input_writer_) {
+    pending_encrypted_buffer_ = std::move(encrypted);
+    return;
+  }
+
+  if (!input_pool_->is_live()) {
+    DLOG(ERROR) << "Input buffer pool is dead.";
+    std::move(decrypt_cb_).Run(Decryptor::kError, nullptr);
+    return;
+  }
+
+  // Decryptor can only process one buffer at a time, which means there
+  // should be always enough unused buffers.
+  base::Optional<size_t> buf_index = input_writer_->Acquire();
+
+  // No available input buffer. Just wait for the next available one.
+  if (!buf_index.has_value()) {
+    pending_encrypted_buffer_ = std::move(encrypted);
+    return;
+  }
+
+  size_t index = buf_index.value();
+
+  size_t bytes = input_writer_->Write(
+      index, base::make_span(encrypted->data(), encrypted->data_size()));
+  if (bytes < encrypted->data_size()) {
+    // The encrypted data size is too big. Decryptor should consider
+    // splitting the buffer and update the IV and subsample entries.
+    // TODO(yucliu): Handle large encrypted buffer correctly. For now, just
+    // reject the decryption.
+    DLOG(ERROR) << "Encrypted data size is too big.";
+    std::move(decrypt_cb_).Run(Decryptor::kError, nullptr);
+    return;
+  }
+
+  const DecryptConfig* decrypt_config = encrypted->decrypt_config();
+  DCHECK(decrypt_config);
+
+  auto input_packet = StreamProcessorHelper::IoPacket::CreateInput(
+      index, encrypted->data_size(), encrypted->timestamp(),
+      GetFormatDetails(decrypt_config),
+      base::BindOnce(&StreamProcessorDecryptor::OnInputPacketReleased,
+                     base::Unretained(this), index));
+
+  processor_.Process(std::move(input_packet));
+}
+
+void StreamProcessorDecryptor::CancelDecrypt() {
+  // Close current stream and drop all the cached decoder buffers.
+  // Keep input/output_pool_ to avoid buffer re-allocation.
+  processor_.Reset();
+  pending_encrypted_buffer_ = nullptr;
+
+  // Fire |decrypt_cb_| immediately as required by Decryptor::CancelDecrypt.
+  if (decrypt_cb_)
+    std::move(decrypt_cb_).Run(Decryptor::kSuccess, nullptr);
+}
+
+// StreamProcessorHelper::Client implementation:
+void StreamProcessorDecryptor::AllocateInputBuffers(
+    const fuchsia::media::StreamBufferConstraints& stream_constraints) {
+  base::Optional<fuchsia::sysmem::BufferCollectionConstraints>
+      buffer_constraints =
+          SysmemBufferWriter::GetRecommendedConstraints(stream_constraints);
+
+  if (!buffer_constraints.has_value()) {
+    OnError();
+    return;
+  }
+
+  input_pool_creator_ =
+      allocator_.MakeBufferPoolCreator(1 /* num_shared_token */);
+
+  input_pool_creator_->Create(
+      std::move(buffer_constraints).value(),
+      base::BindOnce(&StreamProcessorDecryptor::OnInputBufferPoolAvailable,
+                     base::Unretained(this)));
+}
+
+void StreamProcessorDecryptor::AllocateOutputBuffers(
+    const fuchsia::media::StreamBufferConstraints& stream_constraints) {
+  if (!stream_constraints.has_packet_count_for_client_max() ||
+      !stream_constraints.has_packet_count_for_client_min()) {
+    DLOG(ERROR) << "StreamBufferConstraints doesn't contain required fields.";
+    OnError();
+    return;
+  }
+
+  size_t max_used_output_buffers = std::min(
+      kMaxUsedOutputFrames, stream_constraints.packet_count_for_client_max());
+  max_used_output_buffers = std::max(
+      max_used_output_buffers,
+      static_cast<size_t>(stream_constraints.packet_count_for_client_min()));
+
+  output_pool_creator_ =
+      allocator_.MakeBufferPoolCreator(1 /* num_shared_token */);
+
+  output_pool_creator_->Create(
+      SysmemBufferReader::GetRecommendedConstraints(max_used_output_buffers),
+      base::BindOnce(&StreamProcessorDecryptor::OnOutputBufferPoolAvailable,
+                     base::Unretained(this), max_used_output_buffers));
+}
+
+void StreamProcessorDecryptor::OnProcessEos() {
+  // Decryptor never pushes EOS frame.
+  NOTREACHED();
+}
+
+void StreamProcessorDecryptor::OnOutputFormat(
+    fuchsia::media::StreamOutputFormat format) {}
+
+void StreamProcessorDecryptor::OnOutputPacket(
+    std::unique_ptr<StreamProcessorHelper::IoPacket> packet) {
+  DCHECK(output_reader_);
+  if (!output_pool_->is_live()) {
+    DLOG(ERROR) << "Output buffer pool is dead.";
+    return;
+  }
+
+  auto clear_buffer = base::MakeRefCounted<DecoderBuffer>(packet->size());
+  clear_buffer->set_timestamp(packet->timestamp());
+
+  bool read_success =
+      output_reader_->Read(packet->index(), packet->offset(),
+                           base::make_span(clear_buffer->writable_data(),
+                                           clear_buffer->data_size()));
+
+  if (!read_success) {
+    DLOG(ERROR) << "Fail to get decrypted result.";
+    std::move(decrypt_cb_).Run(Decryptor::kError, nullptr);
+    return;
+  }
+
+  std::move(decrypt_cb_).Run(Decryptor::kSuccess, std::move(clear_buffer));
+}
+
+void StreamProcessorDecryptor::OnNoKey() {
+  if (decrypt_cb_)
+    std::move(decrypt_cb_).Run(Decryptor::kNoKey, nullptr);
+}
+
+void StreamProcessorDecryptor::OnError() {
+  pending_encrypted_buffer_ = nullptr;
+  if (decrypt_cb_)
+    std::move(decrypt_cb_).Run(Decryptor::kError, nullptr);
+
+  processor_.Reset();
+}
+
+void StreamProcessorDecryptor::OnInputPacketReleased(size_t index) {
+  input_writer_->Release(index);
+
+  if (!pending_encrypted_buffer_)
+    return;
+
+  DCHECK(decrypt_cb_);
+
+  // If there're pending decryption request, handle it now since we have
+  // available input buffers.
+  Decrypt(std::move(pending_encrypted_buffer_), std::move(decrypt_cb_));
+}
+
+void StreamProcessorDecryptor::OnInputBufferPoolAvailable(
+    std::unique_ptr<SysmemBufferPool> pool) {
+  if (!pool) {
+    DLOG(ERROR) << "Fail to allocate input buffer.";
+    OnError();
+    return;
+  }
+
+  input_pool_ = std::move(pool);
+
+  // Provide token before enabling writer. Tokens must be provided to
+  // StreamProcessor before getting the allocated buffers.
+  processor_.CompleteInputBuffersAllocation(input_pool_->TakeToken());
+
+  input_pool_->CreateWriter(
+      base::BindOnce(&StreamProcessorDecryptor::OnInputBufferPoolWriter,
+                     base::Unretained(this)));
+}
+
+void StreamProcessorDecryptor::OnInputBufferPoolWriter(
+    std::unique_ptr<SysmemBufferWriter> writer) {
+  if (!writer) {
+    LOG(ERROR) << "Fail to enable input buffer writer";
+    OnError();
+    return;
+  }
+
+  DCHECK(!input_writer_);
+  input_writer_ = std::move(writer);
+
+  if (pending_encrypted_buffer_) {
+    DCHECK(decrypt_cb_);
+    Decrypt(std::move(pending_encrypted_buffer_), std::move(decrypt_cb_));
+  }
+}
+
+void StreamProcessorDecryptor::OnOutputBufferPoolAvailable(
+    size_t max_used_output_buffers,
+    std::unique_ptr<SysmemBufferPool> pool) {
+  if (!pool) {
+    LOG(ERROR) << "Fail to allocate output buffer.";
+    OnError();
+    return;
+  }
+
+  output_pool_ = std::move(pool);
+
+  // Provide token before enabling reader. Tokens must be provided to
+  // StreamProcessor before getting the allocated buffers.
+  processor_.CompleteOutputBuffersAllocation(max_used_output_buffers,
+                                             output_pool_->TakeToken());
+
+  output_pool_->CreateReader(
+      base::BindOnce(&StreamProcessorDecryptor::OnOutputBufferPoolReader,
+                     base::Unretained(this)));
+}
+
+void StreamProcessorDecryptor::OnOutputBufferPoolReader(
+    std::unique_ptr<SysmemBufferReader> reader) {
+  if (!reader) {
+    LOG(ERROR) << "Fail to enable output buffer reader.";
+    OnError();
+    return;
+  }
+
+  DCHECK(!output_reader_);
+  output_reader_ = std::move(reader);
+}
+
+std::unique_ptr<StreamProcessorDecryptor>
+StreamProcessorDecryptor::CreateAudioDecryptor(
+    fuchsia::media::drm::ContentDecryptionModule* cdm) {
+  DCHECK(cdm);
+
+  fuchsia::media::drm::DecryptorParams params;
+  params.set_require_secure_mode(false);
+  params.mutable_input_details()->set_format_details_version_ordinal(0);
+  fuchsia::media::StreamProcessorPtr stream_processor;
+  cdm->CreateDecryptor(std::move(params), stream_processor.NewRequest());
+
+  return std::make_unique<StreamProcessorDecryptor>(
+      std::move(stream_processor));
+}
+
+}  // namespace media
diff --git a/media/fuchsia/cdm/stream_processor_decryptor.h b/media/fuchsia/cdm/stream_processor_decryptor.h
new file mode 100644
index 0000000..7646822
--- /dev/null
+++ b/media/fuchsia/cdm/stream_processor_decryptor.h
@@ -0,0 +1,78 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef MEDIA_FUCHSIA_CDM_STREAM_PROCESSOR_DECRYPTOR_H_
+#define MEDIA_FUCHSIA_CDM_STREAM_PROCESSOR_DECRYPTOR_H_
+
+#include <fuchsia/media/drm/cpp/fidl.h>
+
+#include <memory>
+
+#include "media/base/decryptor.h"
+#include "media/filters/fuchsia/stream_processor_helper.h"
+#include "media/filters/fuchsia/sysmem_buffer_pool.h"
+
+namespace media {
+class SysmemBufferReader;
+class SysmemBufferWriter;
+
+// Class to handle decryption for one stream. All the APIs have the same
+// requirement as Decryptor.
+class StreamProcessorDecryptor : public StreamProcessorHelper::Client {
+ public:
+  static std::unique_ptr<StreamProcessorDecryptor> CreateAudioDecryptor(
+      fuchsia::media::drm::ContentDecryptionModule* cdm);
+
+  explicit StreamProcessorDecryptor(
+      fuchsia::media::StreamProcessorPtr processor);
+  ~StreamProcessorDecryptor() override;
+
+  void Decrypt(scoped_refptr<DecoderBuffer> encrypted,
+               Decryptor::DecryptCB decrypt_cb);
+  void CancelDecrypt();
+
+  // StreamProcessorHelper::Client implementation:
+  void AllocateInputBuffers(const fuchsia::media::StreamBufferConstraints&
+                                stream_constraints) override;
+  void AllocateOutputBuffers(const fuchsia::media::StreamBufferConstraints&
+                                 stream_constraints) override;
+  void OnProcessEos() override;
+  void OnOutputFormat(fuchsia::media::StreamOutputFormat format) override;
+  void OnOutputPacket(
+      std::unique_ptr<StreamProcessorHelper::IoPacket> packet) override;
+  void OnNoKey() override;
+  void OnError() override;
+
+ private:
+  void OnInputPacketReleased(size_t index);
+  void OnInputBufferPoolAvailable(std::unique_ptr<SysmemBufferPool> pool);
+  void OnInputBufferPoolWriter(std::unique_ptr<SysmemBufferWriter> writer);
+  void OnOutputBufferPoolAvailable(size_t max_used_output_buffers,
+                                   std::unique_ptr<SysmemBufferPool> pool);
+  void OnOutputBufferPoolReader(std::unique_ptr<SysmemBufferReader> reader);
+
+  StreamProcessorHelper processor_;
+  BufferAllocator allocator_;
+
+  // Pending buffers due to input buffer pool not available.
+  scoped_refptr<DecoderBuffer> pending_encrypted_buffer_;
+
+  Decryptor::DecryptCB decrypt_cb_;
+
+  std::unique_ptr<SysmemBufferPool::Creator> input_pool_creator_;
+  std::unique_ptr<SysmemBufferPool> input_pool_;
+  std::unique_ptr<SysmemBufferWriter> input_writer_;
+
+  std::unique_ptr<SysmemBufferPool::Creator> output_pool_creator_;
+  std::unique_ptr<SysmemBufferPool> output_pool_;
+  std::unique_ptr<SysmemBufferReader> output_reader_;
+
+  scoped_refptr<DecoderBuffer> clear_buffer_;
+
+  DISALLOW_COPY_AND_ASSIGN(StreamProcessorDecryptor);
+};
+
+}  // namespace media
+
+#endif  // MEDIA_FUCHSIA_CDM_STREAM_PROCESSOR_DECRYPTOR_H_
diff --git a/media/gpu/android/media_codec_video_decoder_unittest.cc b/media/gpu/android/media_codec_video_decoder_unittest.cc
index daf21059..0e7db6f 100644
--- a/media/gpu/android/media_codec_video_decoder_unittest.cc
+++ b/media/gpu/android/media_codec_video_decoder_unittest.cc
@@ -280,7 +280,7 @@
 
  protected:
   const VideoCodec codec_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::android::ScopedJavaGlobalRef<jobject> java_surface_;
   scoped_refptr<DecoderBuffer> fake_decoder_buffer_;
   std::unique_ptr<MockDeviceInfo> device_info_;
diff --git a/media/gpu/windows/d3d11_texture_selector.cc b/media/gpu/windows/d3d11_texture_selector.cc
index ac1c327..cb633c89 100644
--- a/media/gpu/windows/d3d11_texture_selector.cc
+++ b/media/gpu/windows/d3d11_texture_selector.cc
@@ -61,6 +61,8 @@
   } else if (config.profile() == VP9PROFILE_PROFILE2) {
     decoder_guid = D3D11_DECODER_PROFILE_VP9_VLD_10BIT_PROFILE2;
     input_dxgi_format = DXGI_FORMAT_P010;
+    output_dxgi_format = DXGI_FORMAT_R16_FLOAT;
+    needs_texture_copy = true;
   } else {
     // TODO(tmathmeyer) support other profiles in the future.
     return nullptr;
diff --git a/media/gpu/windows/d3d11_video_decoder.cc b/media/gpu/windows/d3d11_video_decoder.cc
index bdb776b..b0244af 100644
--- a/media/gpu/windows/d3d11_video_decoder.cc
+++ b/media/gpu/windows/d3d11_video_decoder.cc
@@ -855,6 +855,27 @@
         false));                              // require_encrypted
   }
 
+  if (base::FeatureList::IsEnabled(kD3D11VideoDecoderVP9Profile2)) {
+    if (max_vp9_profile2_resolutions.first.width()) {
+      // landscape
+      configs.push_back(SupportedVideoDecoderConfig(
+          VP9PROFILE_PROFILE2,                 // profile_min
+          VP9PROFILE_PROFILE2,                 // profile_max
+          min_resolution,                      // coded_size_min
+          max_vp9_profile2_resolutions.first,  // coded_size_max
+          allow_encrypted,                     // allow_encrypted
+          false));                             // require_encrypted
+      // portrait
+      configs.push_back(SupportedVideoDecoderConfig(
+          VP9PROFILE_PROFILE2,                  // profile_min
+          VP9PROFILE_PROFILE2,                  // profile_max
+          min_resolution,                       // coded_size_min
+          max_vp9_profile2_resolutions.second,  // coded_size_max
+          allow_encrypted,                      // allow_encrypted
+          false));                              // require_encrypted
+    }
+  }
+
   // TODO(liberato): Should we separate out h264, vp9, and encrypted?
   UMA_HISTOGRAM_ENUMERATION(uma_name, NotSupportedReason::kVideoIsSupported);
 
diff --git a/mojo/public/cpp/bindings/pending_remote.h b/mojo/public/cpp/bindings/pending_remote.h
index e2788ce..6d149b8 100644
--- a/mojo/public/cpp/bindings/pending_remote.h
+++ b/mojo/public/cpp/bindings/pending_remote.h
@@ -87,6 +87,7 @@
     state_.version = 0;
     return std::move(state_.pipe);
   }
+  const ScopedMessagePipeHandle& Pipe() const { return state_.pipe; }
 
   // The version of the interface this Remote is assuming when making method
   // calls. For the most common case of unversioned mojom interfaces, this is
diff --git a/net/dns/dns_transaction.cc b/net/dns/dns_transaction.cc
index 7a5ef871..af57285 100644
--- a/net/dns/dns_transaction.cc
+++ b/net/dns/dns_transaction.cc
@@ -1172,7 +1172,7 @@
     net_log_.AddEventReferencingSource(NetLogEventType::DNS_TRANSACTION_ATTEMPT,
                                        attempt->GetSocketNetLog().source());
 
-    int rv = attempt->Start(base::Bind(
+    int rv = attempt->Start(base::BindOnce(
         &DnsTransactionImpl::OnAttemptComplete, base::Unretained(this),
         attempt_number, true /* record_rtt */, base::TimeTicks::Now()));
     if (rv == ERR_IO_PENDING) {
diff --git a/net/dns/dns_transaction_unittest.cc b/net/dns/dns_transaction_unittest.cc
index af6a29b1..d21391b 100644
--- a/net/dns/dns_transaction_unittest.cc
+++ b/net/dns/dns_transaction_unittest.cc
@@ -302,8 +302,8 @@
     EXPECT_EQ(NULL, transaction_.get());
     transaction_ = factory->CreateTransaction(
         hostname_, qtype_,
-        base::Bind(&TransactionHelper::OnTransactionComplete,
-                   base::Unretained(this)),
+        base::BindOnce(&TransactionHelper::OnTransactionComplete,
+                       base::Unretained(this)),
         NetLogWithSource::Make(&net_log_, net::NetLogSourceType::NONE), secure_,
         factory->GetSecureDnsModeForTest(), &request_context_);
     transaction_->SetRequestPriority(DEFAULT_PRIORITY);
@@ -1962,8 +1962,8 @@
       GURL(GetURLFromTemplateWithoutParameters(
           config_.dns_over_https_servers[0].server_template)),
       CookieOptions::MakeAllInclusive(),
-      base::Bind(&CookieCallback::GetCookieListCallback,
-                 base::Unretained(&callback)));
+      base::BindOnce(&CookieCallback::GetCookieListCallback,
+                     base::Unretained(&callback)));
   callback.WaitUntilDone();
   EXPECT_EQ(0u, callback.cookie_list_size());
   callback.Reset();
@@ -1974,8 +1974,8 @@
       base::nullopt /* server_time */);
   helper1.request_context()->cookie_store()->SetCanonicalCookieAsync(
       std::move(cookie), cookie_url.scheme(), CookieOptions(),
-      base::Bind(&CookieCallback::SetCookieCallback,
-                 base::Unretained(&callback)));
+      base::BindOnce(&CookieCallback::SetCookieCallback,
+                     base::Unretained(&callback)));
   EXPECT_TRUE(helper1.RunUntilDone(transaction_factory_.get()));
 }
 
diff --git a/net/dns/mdns_client_impl.cc b/net/dns/mdns_client_impl.cc
index 9ac0592f..050d40c 100644
--- a/net/dns/mdns_client_impl.cc
+++ b/net/dns/mdns_client_impl.cc
@@ -82,8 +82,8 @@
 
     rv = socket_->RecvFrom(
         response_.io_buffer(), response_.io_buffer_size(), &recv_addr_,
-        base::Bind(&MDnsConnection::SocketHandler::OnDatagramReceived,
-                   base::Unretained(this)));
+        base::BindOnce(&MDnsConnection::SocketHandler::OnDatagramReceived,
+                       base::Unretained(this)));
   } while (rv > 0);
 
   if (rv != ERR_IO_PENDING)
@@ -106,11 +106,10 @@
     send_queue_.push(std::make_pair(buffer, size));
     return;
   }
-  int rv = socket_->SendTo(buffer.get(),
-                           size,
-                           multicast_addr_,
-                           base::Bind(&MDnsConnection::SocketHandler::SendDone,
-                                      base::Unretained(this)));
+  int rv =
+      socket_->SendTo(buffer.get(), size, multicast_addr_,
+                      base::BindOnce(&MDnsConnection::SocketHandler::SendDone,
+                                     base::Unretained(this)));
   if (rv == ERR_IO_PENDING) {
     send_in_progress_ = true;
   } else if (rv < OK) {
@@ -395,9 +394,10 @@
 
   // If |cleanup| is empty, then no cleanup necessary.
   if (cleanup != base::Time()) {
-    cleanup_timer_->Start(
-        FROM_HERE, std::max(base::TimeDelta(), cleanup - clock_->Now()),
-        base::Bind(&MDnsClientImpl::Core::DoCleanup, base::Unretained(this)));
+    cleanup_timer_->Start(FROM_HERE,
+                          std::max(base::TimeDelta(), cleanup - clock_->Now()),
+                          base::BindOnce(&MDnsClientImpl::Core::DoCleanup,
+                                         base::Unretained(this)));
   }
 }
 
diff --git a/net/quic/quic_chromium_client_session.cc b/net/quic/quic_chromium_client_session.cc
index fffc6bc..3bb97f7 100644
--- a/net/quic/quic_chromium_client_session.cc
+++ b/net/quic/quic_chromium_client_session.cc
@@ -782,6 +782,7 @@
   // Make sure connection migration and goaway on path degrading are not turned
   // on at the same time.
   DCHECK(!(migrate_session_early_v2_ && go_away_on_path_degrading_));
+  DCHECK(!(allow_port_migration_ && go_away_on_path_degrading_));
 
   quic::QuicSpdyClientSessionBase::set_max_allowed_push_id(max_allowed_push_id);
   default_network_ = default_network;
diff --git a/net/quic/quic_flags_list.h b/net/quic/quic_flags_list.h
index 2081dd6..bf42b94 100644
--- a/net/quic/quic_flags_list.h
+++ b/net/quic/quic_flags_list.h
@@ -216,19 +216,11 @@
           FLAGS_quic_reloadable_flag_quic_ignore_tlpr_if_no_pending_stream_data,
           true)
 
-// When true, QuicDispatcher will drop packets that have an initial destination
-// connection ID that is too short, instead of responding with a Version
-// Negotiation packet to reject it.
-QUIC_FLAG(
-    bool,
-    FLAGS_quic_reloadable_flag_quic_drop_invalid_small_initial_connection_id,
-    true)
-
 // When true, QUIC Version Negotiation packets will randomly include fake
 // versions.
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_version_negotiation_grease,
-          true)
+          false)
 
 // If true, use predictable version negotiation versions.
 QUIC_FLAG(bool, FLAGS_quic_disable_version_negotiation_grease_randomness, false)
@@ -311,7 +303,7 @@
 QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_negotiate_ack_delay_time, false)
 
 // When true, QuicDispatcher will always use QuicFramer::ParsePublicHeader
-QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_use_parse_public_header, false)
+QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_use_parse_public_header, true)
 
 // If true, QuicFramer::WriteClientVersionNegotiationProbePacket uses
 // length-prefixed connection IDs.
@@ -328,13 +320,13 @@
 QUIC_FLAG(
     bool,
     FLAGS_quic_reloadable_flag_quic_add_upper_limit_of_buffered_control_frames,
-    true)
+    false)
 
 // If true, static streams should never be closed before QuicSession
 // destruction.
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_active_streams_never_negative,
-          true)
+          false)
 
 // If true and FIFO connection option is received, write_blocked_streams uses
 // FIFO(stream with smallest ID has highest priority) write scheduler.
@@ -363,22 +355,22 @@
 // closed streams whose highest byte offset is not received yet.
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_aggressive_connection_aliveness,
-          true)
+          false)
 
 // If true, QuicStreamSequencer will not take in new data if the stream is
 // reset.
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_no_stream_data_after_reset,
-          true)
+          false)
 
 // When true, QuicDispatcher::MaybeDispatchPacket will use
 // packet_info.use_length_prefix instead of an incorrect local computation.
 QUIC_FLAG(bool,
           FLAGS_quic_reloadable_flag_quic_use_length_prefix_from_packet_info,
-          false)
+          true)
 
 // If true, enable IETF style probe timeout.
-QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_enable_pto, true)
+QUIC_FLAG(bool, FLAGS_quic_reloadable_flag_quic_enable_pto, false)
 
 // When true, QuicFramer will use QueueUndecryptablePacket on all QUIC versions.
 QUIC_FLAG(bool,
@@ -389,7 +381,7 @@
 // instead of XORing the bytes
 QUIC_FLAG(bool,
           FLAGS_quic_restart_flag_quic_use_hashed_stateless_reset_tokens,
-          false)
+          true)
 
 // This flag enables a temporary workaround which makes us reply to a specific
 // invalid packet that is sent by an Android UDP network conformance test.
diff --git a/net/quic/quic_stream_factory.cc b/net/quic/quic_stream_factory.cc
index 542ae43..adf37e73 100644
--- a/net/quic/quic_stream_factory.cc
+++ b/net/quic/quic_stream_factory.cc
@@ -1153,6 +1153,9 @@
   params_.retry_on_alternate_network_before_handshake = false;
   params_.migrate_idle_sessions = false;
 
+  DCHECK(!(migrate_sessions_early && params_.go_away_on_path_degrading));
+  DCHECK(!(allow_port_migration && params_.go_away_on_path_degrading));
+
   // TODO(zhongyi): deprecate |goaway_sessions_on_ip_change| if the experiment
   // is no longer needed.
   // goaway_sessions_on_ip_change and close_sessions_on_ip_change should never
diff --git a/net/quic/quic_test_packet_maker.cc b/net/quic/quic_test_packet_maker.cc
index 9008a009..ccbebdd4 100644
--- a/net/quic/quic_test_packet_maker.cc
+++ b/net/quic/quic_test_packet_maker.cc
@@ -484,12 +484,9 @@
   frames.push_back(quic::QuicFrame(&ack));
   DVLOG(1) << "Adding frame: " << frames.back();
 
-  quic::QuicConnectionCloseFrame close;
-  close.quic_error_code = quic_error;
-  close.error_details = MaybePrependErrorCode(quic_error_details, quic_error);
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    close.close_type = quic::IETF_QUIC_TRANSPORT_CONNECTION_CLOSE;
-  }
+  quic::QuicConnectionCloseFrame close(version_.transport_version, quic_error,
+                                       quic_error_details,
+                                       /*transport_close_frame_type=*/0);
 
   frames.push_back(quic::QuicFrame(&close));
   DVLOG(1) << "Adding frame: " << frames.back();
@@ -524,13 +521,8 @@
   frames.push_back(quic::QuicFrame(&ack));
   DVLOG(1) << "Adding frame: " << frames.back();
 
-  quic::QuicConnectionCloseFrame close;
-  close.quic_error_code = quic_error;
-  close.error_details = MaybePrependErrorCode(quic_error_details, quic_error);
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    close.close_type = quic::IETF_QUIC_TRANSPORT_CONNECTION_CLOSE;
-    close.transport_close_frame_type = frame_type;
-  }
+  quic::QuicConnectionCloseFrame close(version_.transport_version, quic_error,
+                                       quic_error_details, frame_type);
 
   frames.push_back(quic::QuicFrame(&close));
   DVLOG(1) << "Adding frame: " << frames.back();
@@ -546,12 +538,9 @@
     const std::string& quic_error_details) {
   InitializeHeader(num, include_version);
 
-  quic::QuicConnectionCloseFrame close;
-  close.quic_error_code = quic_error;
-  close.error_details = MaybePrependErrorCode(quic_error_details, quic_error);
-  if (version_.transport_version == quic::QUIC_VERSION_99) {
-    close.close_type = quic::IETF_QUIC_TRANSPORT_CONNECTION_CLOSE;
-  }
+  quic::QuicConnectionCloseFrame close(version_.transport_version, quic_error,
+                                       quic_error_details,
+                                       /*transport_close_frame_type=*/0);
 
   return MakePacket(header_, quic::QuicFrame(&close));
 }
diff --git a/pdf/pdfium/accessibility_unittest.cc b/pdf/pdfium/accessibility_unittest.cc
index b99fd577..3add565 100644
--- a/pdf/pdfium/accessibility_unittest.cc
+++ b/pdf/pdfium/accessibility_unittest.cc
@@ -189,4 +189,41 @@
   EXPECT_EQ(kExpectedPoint, client.GetScrollRequestPoints());
 }
 
+// This class is required to just override the NavigateTo
+// functionality for testing in a specific way. It will
+// keep the TestClient class clean for extension by others.
+class NavigationEnabledTestClient : public TestClient {
+ public:
+  NavigationEnabledTestClient() = default;
+  ~NavigationEnabledTestClient() override = default;
+
+  void NavigateTo(const std::string& url,
+                  WindowOpenDisposition disposition) override {
+    url_ = url;
+    disposition_ = disposition;
+  }
+
+  const std::string& url() const { return url_; }
+  WindowOpenDisposition disposition() const { return disposition_; }
+
+ private:
+  std::string url_;
+  WindowOpenDisposition disposition_;
+};
+
+TEST_F(AccessibilityTest, TestLinkDefaultActionHandling) {
+  NavigationEnabledTestClient client;
+  std::unique_ptr<PDFiumEngine> engine =
+      InitializeEngine(&client, FILE_PATH_LITERAL("weblinks.pdf"));
+  ASSERT_TRUE(engine);
+
+  PP_PdfAccessibilityActionData action_data;
+  action_data.action = PP_PdfAccessibilityAction::PP_PDF_DO_DEFAULT_ACTION;
+  action_data.page_index = 0;
+  action_data.link_index = 0;
+  engine->HandleAccessibilityAction(action_data);
+  EXPECT_EQ("http://yahoo.com", client.url());
+  EXPECT_EQ(WindowOpenDisposition::CURRENT_TAB, client.disposition());
+}
+
 }  // namespace chrome_pdf
diff --git a/pdf/pdfium/pdfium_engine.cc b/pdf/pdfium/pdfium_engine.cc
index 683dc2a..2019d74 100644
--- a/pdf/pdfium/pdfium_engine.cc
+++ b/pdf/pdfium/pdfium_engine.cc
@@ -1195,6 +1195,41 @@
   return true;
 }
 
+bool PDFiumEngine::NavigateToLinkDestination(
+    PDFiumPage::Area area,
+    const PDFiumPage::LinkTarget& target,
+    WindowOpenDisposition disposition) {
+  if (area == PDFiumPage::WEBLINK_AREA) {
+    client_->NavigateTo(target.url, disposition);
+    SetInFormTextArea(false);
+    return true;
+  }
+  if (area == PDFiumPage::DOCLINK_AREA) {
+    if (!PageIndexInBounds(target.page))
+      return true;
+
+    if (disposition == WindowOpenDisposition::CURRENT_TAB) {
+      pp::Rect page_rect(GetPageScreenRect(target.page));
+      int y = position_.y() + page_rect.y();
+      if (target.y_in_pixels)
+        y += target.y_in_pixels.value() * current_zoom_;
+
+      client_->ScrollToY(y, /*compensate_for_toolbar=*/true);
+    } else {
+      std::string parameters = base::StringPrintf("#page=%d", target.page + 1);
+      if (target.y_in_pixels) {
+        parameters += base::StringPrintf(
+            "&zoom=100,0,%d", static_cast<int>(target.y_in_pixels.value()));
+      }
+
+      client_->NavigateTo(parameters, disposition);
+    }
+    SetInFormTextArea(false);
+    return true;
+  }
+  return false;
+}
+
 bool PDFiumEngine::OnMouseUp(const pp::MouseInputEvent& event) {
   if (event.GetButton() != PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
       event.GetButton() != PP_INPUTEVENT_MOUSEBUTTON_MIDDLE) {
@@ -1227,36 +1262,8 @@
     WindowOpenDisposition disposition = ui::DispositionFromClick(
         middle_button, alt_key, ctrl_key, meta_key, shift_key);
 
-    if (area == PDFiumPage::WEBLINK_AREA) {
-      client_->NavigateTo(target.url, disposition);
-      SetInFormTextArea(false);
+    if (NavigateToLinkDestination(area, target, disposition))
       return true;
-    }
-    if (area == PDFiumPage::DOCLINK_AREA) {
-      if (!PageIndexInBounds(target.page))
-        return true;
-
-      if (disposition == WindowOpenDisposition::CURRENT_TAB) {
-        pp::Rect page_rect(GetPageScreenRect(target.page));
-        int y = position_.y() + page_rect.y();
-        if (target.y_in_pixels)
-          y += target.y_in_pixels.value() * current_zoom_;
-
-        client_->ScrollToY(y, /*compensate_for_toolbar=*/true);
-      } else {
-        std::string parameters =
-            base::StringPrintf("#page=%d", target.page + 1);
-        if (target.y_in_pixels) {
-          parameters += base::StringPrintf(
-              "&zoom=100,0,%d", static_cast<int>(target.y_in_pixels.value()));
-        }
-
-        client_->NavigateTo(parameters, disposition);
-      }
-      SetInFormTextArea(false);
-
-      return true;
-    }
   }
 
   if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE) {
@@ -1984,9 +1991,16 @@
       pp::Rect target_point_screen = GetScreenRect(target_rect);
       client_->ScrollBy(target_point_screen.point());
     } break;
-    // TODO(https://crbug.com/981448): Handle default action case.
-    case PP_PdfAccessibilityAction::PP_PDF_DO_DEFAULT_ACTION:
-      break;
+    case PP_PdfAccessibilityAction::PP_PDF_DO_DEFAULT_ACTION: {
+      if (PageIndexInBounds(action_data.page_index)) {
+        PDFiumPage::LinkTarget target;
+        PDFiumPage::Area area =
+            pages_[action_data.page_index]->GetLinkTargetAtIndex(
+                action_data.link_index, &target);
+        NavigateToLinkDestination(area, target,
+                                  WindowOpenDisposition::CURRENT_TAB);
+      }
+    } break;
     default:
       NOTREACHED();
       break;
diff --git a/pdf/pdfium/pdfium_engine.h b/pdf/pdfium/pdfium_engine.h
index 13fc41c5..d945f90f 100644
--- a/pdf/pdfium/pdfium_engine.h
+++ b/pdf/pdfium/pdfium_engine.h
@@ -516,6 +516,12 @@
   // Set if the document has any local edits.
   void SetEditMode(bool edit_mode);
 
+  // Navigates to a link destination depending on the type of destination.
+  // Returns false if |area| is not a link.
+  bool NavigateToLinkDestination(PDFiumPage::Area area,
+                                 const PDFiumPage::LinkTarget& target,
+                                 WindowOpenDisposition disposition);
+
   // IFSDK_PAUSE callbacks
   static FPDF_BOOL Pause_NeedToPauseNow(IFSDK_PAUSE* param);
 
diff --git a/pdf/pdfium/pdfium_page.cc b/pdf/pdfium/pdfium_page.cc
index 8cc124df..db070ad 100644
--- a/pdf/pdfium/pdfium_page.cc
+++ b/pdf/pdfium/pdfium_page.cc
@@ -432,6 +432,18 @@
   return GetFloatCharRectInPixels(page, text_page, char_index);
 }
 
+PDFiumPage::Area PDFiumPage::GetLinkTargetAtIndex(int link_index,
+                                                  LinkTarget* target) {
+  if (!available_ || link_index < 0)
+    return NONSELECTABLE_AREA;
+  CalculateLinks();
+  if (link_index >= static_cast<int>(links_.size()))
+    return NONSELECTABLE_AREA;
+  target->url = links_[link_index].url;
+  DCHECK(!target->url.empty());
+  return WEBLINK_AREA;
+}
+
 PDFiumPage::Area PDFiumPage::GetCharIndex(const pp::Point& point,
                                           PageOrientation orientation,
                                           int* char_index,
diff --git a/pdf/pdfium/pdfium_page.h b/pdf/pdfium/pdfium_page.h
index ae7695c..0696925 100644
--- a/pdf/pdfium/pdfium_page.h
+++ b/pdf/pdfium/pdfium_page.h
@@ -79,6 +79,11 @@
     base::Optional<float> y_in_pixels;
   };
 
+  // Given a |link_index|, returns the type of underlying area and the link
+  // target. |target| must be valid. Returns NONSELECTABLE_AREA if
+  // |link_index| is invalid.
+  Area GetLinkTargetAtIndex(int link_index, LinkTarget* target);
+
   // Returns the (x, y) position of a destination in page coordinates.
   base::Optional<gfx::PointF> GetPageXYTarget(FPDF_DEST destination);
 
diff --git a/remoting/host/installer/mac/do_signing.sh b/remoting/host/installer/mac/do_signing.sh
index 995c867..5dbbbe3 100755
--- a/remoting/host/installer/mac/do_signing.sh
+++ b/remoting/host/installer/mac/do_signing.sh
@@ -126,13 +126,16 @@
   fi
 
   echo Signing "${name}"
+
+  # It may be more natural to define a separate array just for the keychain
+  # args, but there is a bug in older versions of Bash:
+  # Expanding a zero-size array with "set -u" aborts with "unbound variable".
+  local args=(-vv --sign "${id}")
   if [[ -n "${keychain}" ]]; then
-    codesign -vv --sign "${id}" --options runtime \
-        --keychain "${keychain}" "${name}"
-  else
-    codesign -vv --sign "${id}" --options runtime \
-        "${name}"
+      args+=(--keychain "${keychain}")
   fi
+  args+=(--timestamp --options runtime "${name}")
+  codesign "${args[@]}"
   codesign -v "${name}"
 }
 
@@ -153,12 +156,12 @@
   local id="${3}"
 
   local package="${input_dir}/${PKG_DIR}/${PKG_FINAL}"
+  local args=(--sign "${id}" --timestamp)
   if [[ -n "${keychain}" ]]; then
-    productsign --sign "${id}" --keychain "${keychain}" \
-        "${package}" "${package}.signed"
-  else
-    productsign --sign "${id}" "${package}" "${package}.signed"
+      args+=(--keychain "${keychain}")
   fi
+  args+=("${package}" "${package}.signed")
+  productsign "${args[@]}"
   mv -f "${package}.signed" "${package}"
 }
 
@@ -327,6 +330,9 @@
     sign_installer "${input_dir}" "${keychain}" "${productsign_id}"
   fi
   build_dmg "${input_dir}" "${output_dir}"
+  if [[ "${do_sign_binaries}" == 1 ]]; then
+      sign "${output_dir}/${DMG_FILE_NAME}" "${keychain}" "${codesign_id}"
+  fi
 
   cleanup
 }
diff --git a/services/audio/public/cpp/fake_system_info.cc b/services/audio/public/cpp/fake_system_info.cc
index 17ee96b6..5e778bf 100644
--- a/services/audio/public/cpp/fake_system_info.cc
+++ b/services/audio/public/cpp/fake_system_info.cc
@@ -70,8 +70,8 @@
   std::move(callback).Run(base::nullopt, base::nullopt);
 }
 
-void FakeSystemInfo::Bind(mojom::SystemInfoRequest request) {
-  receivers_.Add(this, std::move(request));
+void FakeSystemInfo::Bind(mojo::PendingReceiver<mojom::SystemInfo> receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 }  // namespace audio
diff --git a/services/audio/public/cpp/fake_system_info.h b/services/audio/public/cpp/fake_system_info.h
index 3336d7b..e09be2e 100644
--- a/services/audio/public/cpp/fake_system_info.h
+++ b/services/audio/public/cpp/fake_system_info.h
@@ -47,7 +47,7 @@
                           GetInputDeviceInfoCallback callback) override;
 
  private:
-  void Bind(mojom::SystemInfoRequest request);
+  void Bind(mojo::PendingReceiver<mojom::SystemInfo> receiver);
 
   mojo::ReceiverSet<mojom::SystemInfo> receivers_;
   DISALLOW_COPY_AND_ASSIGN(FakeSystemInfo);
diff --git a/services/device/fingerprint/fingerprint_chromeos_unittest.cc b/services/device/fingerprint/fingerprint_chromeos_unittest.cc
index 6faa399a..a48c365 100644
--- a/services/device/fingerprint/fingerprint_chromeos_unittest.cc
+++ b/services/device/fingerprint/fingerprint_chromeos_unittest.cc
@@ -125,7 +125,7 @@
   int get_records_results() { return get_records_results_; }
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<FingerprintChromeOS> fingerprint_;
   int get_records_results_ = 0;
 
diff --git a/services/device/public/cpp/hid/fake_hid_manager.cc b/services/device/public/cpp/hid/fake_hid_manager.cc
index 6c12300..b765d3a3 100644
--- a/services/device/public/cpp/hid/fake_hid_manager.cc
+++ b/services/device/public/cpp/hid/fake_hid_manager.cc
@@ -94,8 +94,8 @@
 FakeHidManager::FakeHidManager() {}
 FakeHidManager::~FakeHidManager() = default;
 
-void FakeHidManager::Bind(mojom::HidManagerRequest request) {
-  bindings_.AddBinding(this, std::move(request));
+void FakeHidManager::Bind(mojo::PendingReceiver<mojom::HidManager> receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 // mojom::HidManager implementation:
diff --git a/services/device/public/cpp/hid/fake_hid_manager.h b/services/device/public/cpp/hid/fake_hid_manager.h
index 489f5402..5a296c0 100644
--- a/services/device/public/cpp/hid/fake_hid_manager.h
+++ b/services/device/public/cpp/hid/fake_hid_manager.h
@@ -9,9 +9,9 @@
 #include <string>
 #include <vector>
 
-#include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
 #include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 #include "services/device/public/mojom/hid.mojom.h"
 
 namespace device {
@@ -42,7 +42,7 @@
   FakeHidManager();
   ~FakeHidManager() override;
 
-  void Bind(mojom::HidManagerRequest request);
+  void Bind(mojo::PendingReceiver<mojom::HidManager> receiver);
 
   // mojom::HidManager implementation:
   void GetDevicesAndSetClient(mojom::HidManagerClientAssociatedPtrInfo client,
@@ -72,7 +72,7 @@
  private:
   std::map<std::string, mojom::HidDeviceInfoPtr> devices_;
   mojo::AssociatedInterfacePtrSet<mojom::HidManagerClient> clients_;
-  mojo::BindingSet<mojom::HidManager> bindings_;
+  mojo::ReceiverSet<mojom::HidManager> receivers_;
 };
 
 }  // namespace device
diff --git a/services/device/public/cpp/hid/fake_input_service_linux.cc b/services/device/public/cpp/hid/fake_input_service_linux.cc
index 310e2fd..a387eb2 100644
--- a/services/device/public/cpp/hid/fake_input_service_linux.cc
+++ b/services/device/public/cpp/hid/fake_input_service_linux.cc
@@ -32,8 +32,9 @@
   std::move(callback).Run(std::move(devices));
 }
 
-void FakeInputServiceLinux::Bind(mojom::InputDeviceManagerRequest request) {
-  bindings_.AddBinding(this, std::move(request));
+void FakeInputServiceLinux::Bind(
+    mojo::PendingReceiver<mojom::InputDeviceManager> receiver) {
+  receivers_.Add(this, std::move(receiver));
 }
 
 void FakeInputServiceLinux::AddDevice(mojom::InputDeviceInfoPtr info) {
diff --git a/services/device/public/cpp/hid/fake_input_service_linux.h b/services/device/public/cpp/hid/fake_input_service_linux.h
index 391cf7cf..71f200b6 100644
--- a/services/device/public/cpp/hid/fake_input_service_linux.h
+++ b/services/device/public/cpp/hid/fake_input_service_linux.h
@@ -8,8 +8,9 @@
 #include <map>
 #include <string>
 
-#include "mojo/public/cpp/bindings/binding_set.h"
 #include "mojo/public/cpp/bindings/interface_ptr_set.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 #include "services/device/public/mojom/input_service.mojom.h"
 
 namespace device {
@@ -27,14 +28,14 @@
       GetDevicesCallback callback) override;
   void GetDevices(GetDevicesCallback callback) override;
 
-  void Bind(mojom::InputDeviceManagerRequest request);
+  void Bind(mojo::PendingReceiver<mojom::InputDeviceManager> receiver);
   void AddDevice(mojom::InputDeviceInfoPtr info);
   void RemoveDevice(const std::string& id);
 
   DeviceMap devices_;
 
  private:
-  mojo::BindingSet<mojom::InputDeviceManager> bindings_;
+  mojo::ReceiverSet<mojom::InputDeviceManager> receivers_;
   mojo::AssociatedInterfacePtrSet<mojom::InputDeviceManagerClient> clients_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeInputServiceLinux);
diff --git a/services/device/public/cpp/test/scoped_geolocation_overrider.cc b/services/device/public/cpp/test/scoped_geolocation_overrider.cc
index cc720935..7ac3630 100644
--- a/services/device/public/cpp/test/scoped_geolocation_overrider.cc
+++ b/services/device/public/cpp/test/scoped_geolocation_overrider.cc
@@ -4,9 +4,14 @@
 
 #include <vector>
 
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
 #include "services/device/public/cpp/geolocation/geoposition.h"
 #include "services/device/public/cpp/test/scoped_geolocation_overrider.h"
 #include "services/device/public/mojom/constants.mojom.h"
+#include "services/device/public/mojom/geolocation.mojom.h"
+#include "services/device/public/mojom/geolocation_context.mojom.h"
 #include "services/service_manager/public/cpp/service_binding.h"
 
 namespace device {
@@ -23,7 +28,8 @@
   void UpdateLocation(const mojom::Geoposition& position);
   const mojom::Geoposition& GetGeoposition() const;
 
-  void BindForOverrideService(mojom::GeolocationContextRequest request);
+  void BindForOverrideService(
+      mojo::PendingReceiver<mojom::GeolocationContext> receiver);
 
   // mojom::GeolocationContext implementation:
   void BindGeolocation(
@@ -35,7 +41,7 @@
   mojom::Geoposition position_;
   mojom::GeopositionPtr override_position_;
   std::vector<std::unique_ptr<FakeGeolocation>> impls_;
-  mojo::BindingSet<mojom::GeolocationContext> context_bindings_;
+  mojo::ReceiverSet<mojom::GeolocationContext> context_receivers_;
 };
 
 class ScopedGeolocationOverrider::FakeGeolocation : public mojom::Geolocation {
@@ -137,8 +143,8 @@
 }
 
 void ScopedGeolocationOverrider::FakeGeolocationContext::BindForOverrideService(
-    mojom::GeolocationContextRequest request) {
-  context_bindings_.AddBinding(this, std::move(request));
+    mojo::PendingReceiver<mojom::GeolocationContext> receiver) {
+  context_receivers_.Add(this, std::move(receiver));
 }
 
 void ScopedGeolocationOverrider::FakeGeolocationContext::BindGeolocation(
diff --git a/services/device/public/cpp/test/scoped_geolocation_overrider.h b/services/device/public/cpp/test/scoped_geolocation_overrider.h
index 5533a4d..978f45b 100644
--- a/services/device/public/cpp/test/scoped_geolocation_overrider.h
+++ b/services/device/public/cpp/test/scoped_geolocation_overrider.h
@@ -6,12 +6,7 @@
 #define SERVICES_DEVICE_PUBLIC_CPP_TEST_SCOPED_GEOLOCATION_OVERRIDER_H_
 
 #include "base/bind.h"
-#include "mojo/public/cpp/bindings/binding_set.h"
-#include "mojo/public/cpp/bindings/receiver.h"
-#include "services/device/public/mojom/geolocation.mojom.h"
-#include "services/device/public/mojom/geolocation_context.mojom.h"
 #include "services/device/public/mojom/geoposition.mojom.h"
-#include "services/service_manager/public/cpp/bind_source_info.h"
 
 namespace device {
 
diff --git a/services/service_manager/public/cpp/service_binding.h b/services/service_manager/public/cpp/service_binding.h
index 102b8d3..421f9df 100644
--- a/services/service_manager/public/cpp/service_binding.h
+++ b/services/service_manager/public/cpp/service_binding.h
@@ -118,7 +118,7 @@
   template <typename Interface>
   using TypedBinderWithInfoForTesting =
       base::RepeatingCallback<void(const BindSourceInfo&,
-                                   mojo::InterfaceRequest<Interface>)>;
+                                   mojo::PendingReceiver<Interface>)>;
   template <typename Interface>
   static void OverrideInterfaceBinderForTesting(
       const std::string& service_name,
@@ -129,13 +129,13 @@
             [](const TypedBinderWithInfoForTesting<Interface>& binder,
                const BindSourceInfo& info, mojo::ScopedMessagePipeHandle pipe) {
               binder.Run(info,
-                         mojo::InterfaceRequest<Interface>(std::move(pipe)));
+                         mojo::PendingReceiver<Interface>(std::move(pipe)));
             },
             binder));
   }
   template <typename Interface>
   using TypedBinderForTesting =
-      base::RepeatingCallback<void(mojo::InterfaceRequest<Interface>)>;
+      base::RepeatingCallback<void(mojo::PendingReceiver<Interface>)>;
   template <typename Interface>
   static void OverrideInterfaceBinderForTesting(
       const std::string& service_name,
@@ -144,8 +144,8 @@
         service_name, base::BindRepeating(
                           [](const TypedBinderForTesting<Interface>& binder,
                              const BindSourceInfo& info,
-                             mojo::InterfaceRequest<Interface> request) {
-                            binder.Run(std::move(request));
+                             mojo::PendingReceiver<Interface> receiver) {
+                            binder.Run(std::move(receiver));
                           },
                           binder));
   }
diff --git a/storage/browser/blob/blob_flattener_unittest.cc b/storage/browser/blob/blob_flattener_unittest.cc
index 9af353d..6909afe 100644
--- a/storage/browser/blob/blob_flattener_unittest.cc
+++ b/storage/browser/blob/blob_flattener_unittest.cc
@@ -117,7 +117,7 @@
   base::ScopedTempDir temp_dir_;
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
 
-  base::test::TaskEnvironment task_environment;
+  base::test::SingleThreadTaskEnvironment task_environment;
   std::unique_ptr<BlobStorageContext> context_;
 };
 
diff --git a/storage/browser/blob/blob_memory_controller_unittest.cc b/storage/browser/blob/blob_memory_controller_unittest.cc
index e5d5599..87f5620 100644
--- a/storage/browser/blob/blob_memory_controller_unittest.cc
+++ b/storage/browser/blob/blob_memory_controller_unittest.cc
@@ -165,7 +165,7 @@
 
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
 };
 
 TEST_F(BlobMemoryControllerTest, Strategy) {
diff --git a/storage/browser/blob/blob_registry_impl.cc b/storage/browser/blob/blob_registry_impl.cc
index f3ae0c6..8e37c517 100644
--- a/storage/browser/blob/blob_registry_impl.cc
+++ b/storage/browser/blob/blob_registry_impl.cc
@@ -633,8 +633,10 @@
   if (result) {
     DCHECK_EQ(BlobStatus::DONE, result->GetBlobStatus());
     blob = blink::mojom::SerializedBlob::New(
-        result->uuid(), result->content_type(), result->size(), nullptr);
-    BlobImpl::Create(std::move(result), MakeRequest(&blob->blob));
+        result->uuid(), result->content_type(), result->size(),
+        mojo::NullRemote());
+    BlobImpl::Create(std::move(result),
+                     blob->blob.InitWithNewPipeAndPassReceiver());
   }
   std::move(callback).Run(std::move(blob));
 }
diff --git a/storage/browser/blob/blob_storage_context_unittest.cc b/storage/browser/blob/blob_storage_context_unittest.cc
index 6e80f438..eaa8927 100644
--- a/storage/browser/blob/blob_storage_context_unittest.cc
+++ b/storage/browser/blob/blob_storage_context_unittest.cc
@@ -115,7 +115,7 @@
   base::ScopedTempDir temp_dir_;
   scoped_refptr<TestSimpleTaskRunner> file_runner_ = new TestSimpleTaskRunner();
 
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   std::unique_ptr<BlobStorageContext> context_;
 };
 
diff --git a/storage/browser/blob/blob_url_store_impl.cc b/storage/browser/blob/blob_url_store_impl.cc
index 0d1b5b7..c3563efb 100644
--- a/storage/browser/blob/blob_url_store_impl.cc
+++ b/storage/browser/blob/blob_url_store_impl.cc
@@ -5,6 +5,7 @@
 #include "storage/browser/blob/blob_url_store_impl.h"
 
 #include "base/bind.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
 #include "storage/browser/blob/blob_impl.h"
 #include "storage/browser/blob/blob_storage_context.h"
@@ -132,14 +133,15 @@
 
 void BlobURLStoreImpl::Resolve(const GURL& url, ResolveCallback callback) {
   if (!context_) {
-    std::move(callback).Run(nullptr);
+    std::move(callback).Run(mojo::NullRemote());
     return;
   }
-  blink::mojom::BlobPtr blob;
+  mojo::PendingRemote<blink::mojom::Blob> blob;
   std::unique_ptr<BlobDataHandle> blob_handle =
       context_->GetBlobDataFromPublicURL(url);
   if (blob_handle)
-    BlobImpl::Create(std::move(blob_handle), MakeRequest(&blob));
+    BlobImpl::Create(std::move(blob_handle),
+                     blob.InitWithNewPipeAndPassReceiver());
   std::move(callback).Run(std::move(blob));
 }
 
diff --git a/storage/browser/blob/blob_url_store_impl_unittest.cc b/storage/browser/blob/blob_url_store_impl_unittest.cc
index 7965517..a4f785a 100644
--- a/storage/browser/blob/blob_url_store_impl_unittest.cc
+++ b/storage/browser/blob/blob_url_store_impl_unittest.cc
@@ -8,6 +8,7 @@
 #include "base/test/bind_test_util.h"
 #include "base/test/task_environment.h"
 #include "mojo/core/embedder/embedder.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
 #include "mojo/public/cpp/bindings/remote.h"
 #include "mojo/public/cpp/bindings/self_owned_receiver.h"
 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
@@ -19,7 +20,6 @@
 #include "storage/browser/test/mock_blob_registry_delegate.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
-using blink::mojom::BlobPtr;
 using blink::mojom::BlobURLStore;
 
 namespace storage {
@@ -42,14 +42,15 @@
     bad_messages_.push_back(error);
   }
 
-  BlobPtr CreateBlobFromString(const std::string& uuid,
-                               const std::string& contents) {
+  mojo::PendingRemote<blink::mojom::Blob> CreateBlobFromString(
+      const std::string& uuid,
+      const std::string& contents) {
     auto builder = std::make_unique<BlobDataBuilder>(uuid);
     builder->set_content_type("text/plain");
     builder->AppendData(contents);
-    BlobPtr blob;
+    mojo::PendingRemote<blink::mojom::Blob> blob;
     BlobImpl::Create(context_->AddFinishedBlob(std::move(builder)),
-                     MakeRequest(&blob));
+                     blob.InitWithNewPipeAndPassReceiver());
     return blob;
   }
 
@@ -75,22 +76,27 @@
     return result;
   }
 
-  void RegisterURL(BlobURLStore* store, BlobPtr blob, const GURL& url) {
+  void RegisterURL(BlobURLStore* store,
+                   mojo::PendingRemote<blink::mojom::Blob> blob,
+                   const GURL& url) {
     base::RunLoop loop;
-    store->Register(blob.PassInterface(), url, loop.QuitClosure());
+    store->Register(std::move(blob), url, loop.QuitClosure());
     loop.Run();
   }
 
-  BlobPtr ResolveURL(BlobURLStore* store, const GURL& url) {
-    BlobPtr result;
+  mojo::PendingRemote<blink::mojom::Blob> ResolveURL(BlobURLStore* store,
+                                                     const GURL& url) {
+    mojo::PendingRemote<blink::mojom::Blob> result;
     base::RunLoop loop;
-    store->Resolve(kValidUrl, base::BindOnce(
-                                  [](base::OnceClosure done, BlobPtr* blob_out,
-                                     BlobPtr blob) {
-                                    *blob_out = std::move(blob);
-                                    std::move(done).Run();
-                                  },
-                                  loop.QuitClosure(), &result));
+    store->Resolve(kValidUrl,
+                   base::BindOnce(
+                       [](base::OnceClosure done,
+                          mojo::PendingRemote<blink::mojom::Blob>* blob_out,
+                          mojo::PendingRemote<blink::mojom::Blob> blob) {
+                         *blob_out = std::move(blob);
+                         std::move(done).Run();
+                       },
+                       loop.QuitClosure(), &result));
     loop.Run();
     return result;
   }
@@ -108,7 +114,8 @@
 };
 
 TEST_F(BlobURLStoreImplTest, BasicRegisterRevoke) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   // Register a URL and make sure the URL keeps the blob alive.
   BlobURLStoreImpl url_store(context_->AsWeakPtr(), &delegate_);
@@ -130,7 +137,8 @@
 }
 
 TEST_F(BlobURLStoreImplTest, RegisterInvalidScheme) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   mojo::Remote<BlobURLStore> url_store(CreateURLStore());
   RegisterURL(url_store.get(), std::move(blob), kInvalidUrl);
@@ -139,7 +147,8 @@
 }
 
 TEST_F(BlobURLStoreImplTest, RegisterCantCommit) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   delegate_.can_commit_url_result = false;
 
@@ -150,7 +159,8 @@
 }
 
 TEST_F(BlobURLStoreImplTest, RegisterUrlFragment) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   mojo::Remote<BlobURLStore> url_store(CreateURLStore());
   RegisterURL(url_store.get(), std::move(blob), kFragmentUrl);
@@ -160,13 +170,14 @@
 
 TEST_F(BlobURLStoreImplTest, ImplicitRevoke) {
   const GURL kValidUrl2("blob:id2");
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
-  BlobPtr blob2;
-  blob->Clone(MakeRequest(&blob2));
+  mojo::Remote<blink::mojom::Blob> blob(
+      CreateBlobFromString(kId, "hello world"));
+  mojo::PendingRemote<blink::mojom::Blob> blob2;
+  blob->Clone(blob2.InitWithNewPipeAndPassReceiver());
 
   auto url_store =
       std::make_unique<BlobURLStoreImpl>(context_->AsWeakPtr(), &delegate_);
-  RegisterURL(url_store.get(), std::move(blob), kValidUrl);
+  RegisterURL(url_store.get(), blob.Unbind(), kValidUrl);
   EXPECT_TRUE(context_->GetBlobDataFromPublicURL(kValidUrl));
   RegisterURL(url_store.get(), std::move(blob2), kValidUrl2);
   EXPECT_TRUE(context_->GetBlobDataFromPublicURL(kValidUrl2));
@@ -178,7 +189,8 @@
 }
 
 TEST_F(BlobURLStoreImplTest, RevokeThroughDifferentURLStore) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   BlobURLStoreImpl url_store1(context_->AsWeakPtr(), &delegate_);
   BlobURLStoreImpl url_store2(context_->AsWeakPtr(), &delegate_);
@@ -225,23 +237,28 @@
 }
 
 TEST_F(BlobURLStoreImplTest, Resolve) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   BlobURLStoreImpl url_store(context_->AsWeakPtr(), &delegate_);
   RegisterURL(&url_store, std::move(blob), kValidUrl);
 
   blob = ResolveURL(&url_store, kValidUrl);
   ASSERT_TRUE(blob);
-  EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
+  mojo::Remote<blink::mojom::Blob> blob_remote(std::move(blob));
+  EXPECT_EQ(kId, UUIDFromBlob(blob_remote.get()));
   blob = ResolveURL(&url_store, kFragmentUrl);
   ASSERT_TRUE(blob);
-  EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
+  blob_remote.reset();
+  blob_remote.Bind(std::move(blob));
+  EXPECT_EQ(kId, UUIDFromBlob(blob_remote.get()));
 }
 
 TEST_F(BlobURLStoreImplTest, ResolveNonExistentURL) {
   BlobURLStoreImpl url_store(context_->AsWeakPtr(), &delegate_);
 
-  BlobPtr blob = ResolveURL(&url_store, kValidUrl);
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      ResolveURL(&url_store, kValidUrl);
   EXPECT_FALSE(blob);
   blob = ResolveURL(&url_store, kFragmentUrl);
   EXPECT_FALSE(blob);
@@ -250,12 +267,14 @@
 TEST_F(BlobURLStoreImplTest, ResolveInvalidURL) {
   BlobURLStoreImpl url_store(context_->AsWeakPtr(), &delegate_);
 
-  BlobPtr blob = ResolveURL(&url_store, kInvalidUrl);
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      ResolveURL(&url_store, kInvalidUrl);
   EXPECT_FALSE(blob);
 }
 
 TEST_F(BlobURLStoreImplTest, ResolveAsURLLoaderFactory) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   BlobURLStoreImpl url_store(context_->AsWeakPtr(), &delegate_);
   RegisterURL(&url_store, std::move(blob), kValidUrl);
@@ -279,7 +298,8 @@
 }
 
 TEST_F(BlobURLStoreImplTest, ResolveForNavigation) {
-  BlobPtr blob = CreateBlobFromString(kId, "hello world");
+  mojo::PendingRemote<blink::mojom::Blob> blob =
+      CreateBlobFromString(kId, "hello world");
 
   BlobURLStoreImpl url_store(context_->AsWeakPtr(), &delegate_);
   RegisterURL(&url_store, std::move(blob), kValidUrl);
diff --git a/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc b/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
index 82d8831..cd0c8c9 100644
--- a/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
+++ b/storage/browser/fileapi/copy_or_move_operation_delegate_unittest.cc
@@ -736,8 +736,8 @@
   base::WriteFile(source_path, kTestData,
                   base::size(kTestData) - 1);  // Exclude trailing '\0'.
 
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
   base::Thread file_thread("file_thread");
   ASSERT_TRUE(file_thread.Start());
   ScopedThreadStopper thread_stopper(&file_thread);
@@ -794,8 +794,8 @@
   base::WriteFile(source_path, kTestData,
                   base::size(kTestData) - 1);  // Exclude trailing '\0'.
 
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
   base::Thread file_thread("file_thread");
   ASSERT_TRUE(file_thread.Start());
   ScopedThreadStopper thread_stopper(&file_thread);
@@ -848,8 +848,8 @@
   base::WriteFile(source_path, kTestData,
                   base::size(kTestData) - 1);  // Exclude trailing '\0'.
 
-  base::test::TaskEnvironment task_environment(
-      base::test::TaskEnvironment::MainThreadType::IO);
+  base::test::SingleThreadTaskEnvironment task_environment(
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO);
   base::Thread file_thread("file_thread");
   ASSERT_TRUE(file_thread.Start());
   ScopedThreadStopper thread_stopper(&file_thread);
diff --git a/storage/browser/fileapi/dragged_file_util_unittest.cc b/storage/browser/fileapi/dragged_file_util_unittest.cc
index 8911dc0..751e36f 100644
--- a/storage/browser/fileapi/dragged_file_util_unittest.cc
+++ b/storage/browser/fileapi/dragged_file_util_unittest.cc
@@ -281,8 +281,8 @@
 
   base::ScopedTempDir data_dir_;
   base::ScopedTempDir partition_dir_;
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   std::string filesystem_id_;
   scoped_refptr<FileSystemContext> file_system_context_;
   std::map<base::FilePath, base::FilePath> toplevel_root_map_;
diff --git a/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc b/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc
index 7df61e4..0020d60 100644
--- a/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc
+++ b/storage/browser/fileapi/file_system_file_stream_reader_unittest.cc
@@ -116,8 +116,8 @@
         base::FilePath().AppendASCII(file_name));
   }
 
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   base::ScopedTempDir temp_dir_;
   scoped_refptr<FileSystemContext> file_system_context_;
   base::Time test_file_modification_time_;
diff --git a/storage/browser/fileapi/file_system_usage_cache_unittest.cc b/storage/browser/fileapi/file_system_usage_cache_unittest.cc
index dd87d68..6641e9ee 100644
--- a/storage/browser/fileapi/file_system_usage_cache_unittest.cc
+++ b/storage/browser/fileapi/file_system_usage_cache_unittest.cc
@@ -43,7 +43,7 @@
 
  private:
   base::test::ScopedFeatureList feature_list_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::ScopedTempDir data_dir_;
   FileSystemUsageCache usage_cache_;
 
diff --git a/storage/browser/fileapi/local_file_stream_reader_unittest.cc b/storage/browser/fileapi/local_file_stream_reader_unittest.cc
index 7881a62e..55d3050 100644
--- a/storage/browser/fileapi/local_file_stream_reader_unittest.cc
+++ b/storage/browser/fileapi/local_file_stream_reader_unittest.cc
@@ -105,8 +105,8 @@
   }
 
  private:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   base::Thread file_thread_;
   base::ScopedTempDir dir_;
   base::Time test_file_modification_time_;
diff --git a/storage/browser/fileapi/local_file_stream_writer_unittest.cc b/storage/browser/fileapi/local_file_stream_writer_unittest.cc
index fd9902a0..5eb2afc 100644
--- a/storage/browser/fileapi/local_file_stream_writer_unittest.cc
+++ b/storage/browser/fileapi/local_file_stream_writer_unittest.cc
@@ -73,8 +73,8 @@
   }
 
  private:
-  base::test::TaskEnvironment task_environment_{
-      base::test::TaskEnvironment::MainThreadType::IO};
+  base::test::SingleThreadTaskEnvironment task_environment_{
+      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
   base::Thread file_thread_;
   base::ScopedTempDir temp_dir_;
 };
diff --git a/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc b/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc
index b95275b1..7ea42e2 100644
--- a/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc
+++ b/storage/browser/fileapi/plugin_private_file_system_backend_unittest.cc
@@ -69,7 +69,7 @@
   const base::FilePath& base_path() const { return backend()->base_path(); }
 
   base::ScopedTempDir data_dir_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   scoped_refptr<FileSystemContext> context_;
 };
 
diff --git a/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc b/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc
index c831845..a8d346b 100644
--- a/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc
+++ b/storage/browser/fileapi/quota/quota_backend_impl_unittest.cc
@@ -159,7 +159,7 @@
   }
 
   base::test::ScopedFeatureList feature_list_;
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::ScopedTempDir data_dir_;
   std::unique_ptr<leveldb::Env> in_memory_env_;
   std::unique_ptr<ObfuscatedFileUtil> file_util_;
diff --git a/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc b/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc
index dadb30b0..c4a930b 100644
--- a/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc
+++ b/storage/browser/fileapi/quota/quota_reservation_manager_unittest.cc
@@ -211,7 +211,7 @@
   const base::FilePath& file_path() const { return file_path_; }
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   base::ScopedTempDir work_dir_;
   base::FilePath file_path_;
   std::unique_ptr<QuotaReservationManager> reservation_manager_;
diff --git a/storage/browser/quota/quota_features.cc b/storage/browser/quota/quota_features.cc
index 6acac112..771e905 100644
--- a/storage/browser/quota/quota_features.cc
+++ b/storage/browser/quota/quota_features.cc
@@ -8,25 +8,14 @@
 
 namespace features {
 
-#if defined(OS_CHROMEOS)
-// Chrome OS is given a larger fraction, as web content is the considered
-// the primary use of the platform. Chrome OS itself maintains free space by
-// starting to evict data (old profiles) when less than 1GB remains,
-// stopping eviction once 2GB is free.
-// Prior to M66 this was 1/3, same as other platforms.
-const constexpr double kTemporaryPoolSizeRatioThirds = 2.0 / 3.0;  // 66%
-#else
-const constexpr double kTemporaryPoolSizeRatioThirds = 1.0 / 3.0;  // 33%
-#endif
-
 const base::Feature kQuotaExpandPoolSize{"QuotaExpandPoolSize",
-                                         base::FEATURE_DISABLED_BY_DEFAULT};
+                                         base::FEATURE_ENABLED_BY_DEFAULT};
 
 constexpr base::FeatureParam<double> kExperimentalPoolSizeRatio{
-    &kQuotaExpandPoolSize, "PoolSizeRatio", kTemporaryPoolSizeRatioThirds};
+    &kQuotaExpandPoolSize, "PoolSizeRatio", 0.8};
 
 constexpr base::FeatureParam<double> kPerHostRatio{&kQuotaExpandPoolSize,
-                                                   "PerHostRatio", 0.2};
+                                                   "PerHostRatio", 0.75};
 
 // StaticHostQuota enables a simpler per-host quota model, where the quota is
 // only based on disk capacity (partition size). When the flag is disabled, the
diff --git a/storage/browser/quota/quota_settings_unittest.cc b/storage/browser/quota/quota_settings_unittest.cc
index 022463a3..55a4990c 100644
--- a/storage/browser/quota/quota_settings_unittest.cc
+++ b/storage/browser/quota/quota_settings_unittest.cc
@@ -42,6 +42,26 @@
   DISALLOW_COPY_AND_ASSIGN(QuotaSettingsTest);
 };
 
+TEST_F(QuotaSettingsTest, Default) {
+  MockQuotaDiskInfoHelper disk_info_helper;
+  ON_CALL(disk_info_helper, AmountOfTotalDiskSpace(_))
+      .WillByDefault(::testing::Return(2000));
+
+  bool callback_executed = false;
+  GetNominalDynamicSettings(
+      profile_path(), false, &disk_info_helper,
+      base::BindLambdaForTesting([&](base::Optional<QuotaSettings> settings) {
+        callback_executed = true;
+        ASSERT_NE(settings, base::nullopt);
+        // 1600 = 2000 * default PoolSizeRatio (0.8)
+        EXPECT_EQ(settings->pool_size, 1600);
+        // 1200 = 1600 * default PerHostRatio (.75)
+        EXPECT_EQ(settings->per_host_quota, 1200);
+      }));
+  task_environment_.RunUntilIdle();
+  EXPECT_TRUE(callback_executed);
+}
+
 TEST_F(QuotaSettingsTest, ExpandedTempPool) {
   MockQuotaDiskInfoHelper disk_info_helper;
   ON_CALL(disk_info_helper, AmountOfTotalDiskSpace(_))
diff --git a/testing/buildbot/chromium.android.fyi.json b/testing/buildbot/chromium.android.fyi.json
index 00f270b..88d424225 100644
--- a/testing/buildbot/chromium.android.fyi.json
+++ b/testing/buildbot/chromium.android.fyi.json
@@ -1328,6 +1328,62 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_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_modern_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_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
diff --git a/testing/buildbot/chromium.android.json b/testing/buildbot/chromium.android.json
index de07e4e2..7455da8 100644
--- a/testing/buildbot/chromium.android.json
+++ b/testing/buildbot/chromium.android.json
@@ -8518,6 +8518,51 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "LMY48I",
+              "device_os_type": "userdebug",
+              "device_type": "hammerhead",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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_modern_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -11706,6 +11751,51 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "LMY49B",
+              "device_os_type": "userdebug",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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_modern_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -14589,6 +14679,50 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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_modern_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -17722,6 +17856,51 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MRA58Z",
+              "device_os_type": "userdebug",
+              "device_type": "flo",
+              "os": "Android"
+            }
+          ],
+          "expiration": 10800,
+          "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_modern_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -19889,6 +20068,41 @@
     "gtest_tests": [
       {
         "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "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": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "1",
+              "device_os": "NMF26U",
+              "device_os_type": "userdebug",
+              "device_type": "marlin",
+              "os": "Android"
+            }
+          ]
+        },
+        "test": "chrome_public_smoke_test"
+      },
+      {
+        "args": [
           "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json",
           "--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
           "--gs-results-bucket=chromium-result-details",
@@ -20005,6 +20219,41 @@
           ]
         },
         "test": "chrome_public_test_vr_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "android_devices": "1",
+              "device_os": "NMF26U",
+              "device_os_type": "userdebug",
+              "device_type": "marlin",
+              "os": "Android"
+            }
+          ]
+        },
+        "test": "monochrome_public_smoke_test"
       }
     ]
   },
@@ -20015,6 +20264,50 @@
           "--gs-results-bucket=chromium-result-details",
           "--recover-devices"
         ],
+        "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": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "OPR3.170623.008",
+              "device_os_type": "userdebug",
+              "device_type": "marlin",
+              "os": "Android"
+            }
+          ],
+          "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"
+        ],
         "experiment_percentage": 0,
         "merge": {
           "args": [
@@ -20252,6 +20545,50 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "OPR3.170623.008",
+              "device_os_type": "userdebug",
+              "device_type": "marlin",
+              "os": "Android"
+            }
+          ],
+          "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": "monochrome_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--additional-apk=//third_party/arcore-android-sdk/test-apks/arcore/arcore_current.apk"
         ],
@@ -25570,6 +25907,50 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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_modern_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -27826,155 +28207,54 @@
       }
     ]
   },
-  "android-oreo-arm64-rel": {
-    "gtest_tests": [
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "system_webview_shell_layout_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "device_os": "OPM4.171019.021.P2",
-              "device_os_flavor": "google",
-              "device_os_type": "userdebug",
-              "device_type": "walleye",
-              "os": "Android"
-            }
-          ],
-          "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": "system_webview_shell_layout_test_apk"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "webview_cts_tests"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "chromium/android_webview/tools/cts_archive",
-              "location": "android_webview/tools/cts_archive",
-              "revision": "version:1.7"
-            },
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "device_os": "OPM4.171019.021.P2",
-              "device_os_flavor": "google",
-              "device_os_type": "userdebug",
-              "device_type": "walleye",
-              "os": "Android"
-            }
-          ],
-          "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": "webview_cts_tests"
-      },
-      {
-        "args": [
-          "--gs-results-bucket=chromium-result-details",
-          "--recover-devices"
-        ],
-        "merge": {
-          "args": [
-            "--bucket",
-            "chromium-result-details",
-            "--test-name",
-            "webview_ui_test_app_test_apk"
-          ],
-          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
-        },
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "cipd_packages": [
-            {
-              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
-              "location": "bin",
-              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
-            }
-          ],
-          "dimension_sets": [
-            {
-              "device_os": "OPM4.171019.021.P2",
-              "device_os_flavor": "google",
-              "device_os_type": "userdebug",
-              "device_type": "walleye",
-              "os": "Android"
-            }
-          ],
-          "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": "webview_ui_test_app_test_apk"
-      }
-    ]
-  },
   "android-pie-arm64-dbg": {
     "gtest_tests": [
       {
         "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "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": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ1A.190105.004",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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": [
           "--shared-prefs-file=//chrome/android/shared_preference_files/test/vr_cardboard_skipdon_setupcomplete.json",
           "--replace-system-package=com.google.vr.vrcore,//third_party/gvr-android-sdk/test-apks/vr_services/vr_services_current.apk",
           "--gs-results-bucket=chromium-result-details",
@@ -28122,6 +28402,50 @@
       {
         "args": [
           "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ1A.190105.004",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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": "monochrome_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
           "--recover-devices",
           "--replace-system-package=com.google.ar.core,//third_party/arcore-android-sdk/test-apks/arcore/arcore_current.apk"
         ],
@@ -28209,5 +28533,240 @@
         "test": "services_unittests"
       }
     ]
+  },
+  "android-pie-arm64-rel": {
+    "gtest_tests": [
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "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": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "monochrome_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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": "monochrome_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "system_webview_shell_layout_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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": "system_webview_shell_layout_test_apk"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_cts_tests"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "chromium/android_webview/tools/cts_archive",
+              "location": "android_webview/tools/cts_archive",
+              "revision": "version:1.7"
+            },
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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": "webview_cts_tests"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
+            "webview_ui_test_app_test_apk"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "PQ3A.190801.002",
+              "device_os_flavor": "google",
+              "device_os_type": "userdebug",
+              "device_type": "walleye",
+              "os": "Android"
+            }
+          ],
+          "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": "webview_ui_test_app_test_apk"
+      }
+    ]
   }
 }
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index a106f51..9ec3c0d 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -4907,6 +4907,51 @@
             "--bucket",
             "chromium-result-details",
             "--test-name",
+            "chrome_modern_public_smoke_test"
+          ],
+          "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
+        },
+        "swarming": {
+          "can_use_on_swarming_builders": true,
+          "cipd_packages": [
+            {
+              "cipd_package": "infra/tools/luci/logdog/butler/${platform}",
+              "location": "bin",
+              "revision": "git_revision:ff387eadf445b24c935f1cf7d6ddd279f8a6b04c"
+            }
+          ],
+          "dimension_sets": [
+            {
+              "device_os": "MMB29Q",
+              "device_os_type": "userdebug",
+              "device_type": "bullhead",
+              "os": "Android"
+            }
+          ],
+          "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_modern_public_smoke_test"
+      },
+      {
+        "args": [
+          "--gs-results-bucket=chromium-result-details",
+          "--recover-devices"
+        ],
+        "isolate_coverage_data": true,
+        "merge": {
+          "args": [
+            "--bucket",
+            "chromium-result-details",
+            "--test-name",
             "chrome_public_smoke_test"
           ],
           "script": "//build/android/pylib/results/presentation/test_results_presentation.py"
@@ -23418,6 +23463,7 @@
           "--build-revision=${got_revision}",
           "--test-launcher-filter-file=../../testing/buildbot/filters/pixel_browser_tests.filter",
           "--browser-ui-tests-verify-pixels",
+          "--test-launcher-jobs=1",
           "--enable-pixel-output-in-tests"
         ],
         "experiment_percentage": 100,
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index 8c1991e808..c119355 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -616,6 +616,10 @@
     "label": "//chromeos:chrome_login_tast_tests",
     "type": "raw",
   },
+  "chrome_modern_public_smoke_test": {
+    "label": "//chrome/android:chrome_modern_public_smoke_test",
+    "type": "console_test_launcher",
+  },
   "chrome_official_builder": {
     "label": "//:chrome_official_builder",
     "type": "additional_compile_target",
@@ -1732,6 +1736,10 @@
     "label": "//components/module_installer/android:module_installer_junit_tests",
     "type": "junit_test",
   },
+  "monochrome_public_smoke_test": {
+    "label": "//chrome/android:monochrome_public_smoke_test",
+    "type": "console_test_launcher",
+  },
   "mojo_core_channel_fuzzer": {
     "label": "//mojo/core:mojo_core_channel_fuzzer",
     "type": "fuzzer",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 395b205..e8e5766 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -81,6 +81,14 @@
       },
     },
 
+    'android_modern_smoke_tests': {
+      'chrome_modern_public_smoke_test': {},
+    },
+
+    'android_monochrome_smoke_tests': {
+      'monochrome_public_smoke_test': {},
+    },
+
     'android_oreo_standard_gtests': {
       'chrome_public_test_apk': {
         'swarming': {
@@ -4572,6 +4580,7 @@
           '--build-revision=${got_revision}',
           '--test-launcher-filter-file=../../testing/buildbot/filters/pixel_browser_tests.filter',
           '--browser-ui-tests-verify-pixels',
+          '--test-launcher-jobs=1',
           '--enable-pixel-output-in-tests',
         ],
         'experiment_percentage': 100,
@@ -5083,27 +5092,50 @@
   ##############################################################################
 
   'compound_suites': {
-    'android_oreo_gtests': [
+    'android_lollipop_marshmallow_gtests': [
+      'android_modern_smoke_tests',
+      'android_smoke_tests',
+      'android_specific_chromium_gtests',  # Already includes gl_gtests.
+      'chromium_gtests',
+      'chromium_gtests_for_devices_with_graphical_output',
+      'linux_flavor_specific_chromium_gtests',
+      'vr_platform_specific_chromium_gtests',
+      'network_service_android_gtests',
+    ],
+
+    'android_nougat_gtests': [
       'android_ddready_vr_gtests',
+      'android_monochrome_smoke_tests',
+      'android_smoke_tests',
+    ],
+
+    'android_oreo_gtests': [
       'android_ar_gtests',
+      'android_ddready_vr_gtests',
+      'android_monochrome_smoke_tests',
       'android_oreo_standard_gtests',
+      'android_smoke_tests',
       'android_vega_vr_gtests',
     ],
 
-    'android_oreo_rel_gtests': [
-      'system_webview_shell_instrumentation_tests',
-      'webview_cts_tests_gtest',
-      'webview_ui_instrumentation_tests',
-    ],
-
     'android_pie_gtests': [
-      'android_ddready_vr_gtests',
       'android_ar_gtests',
+      'android_ddready_vr_gtests',
+      'android_monochrome_smoke_tests',
+      'android_smoke_tests',
       'chromium_tracing_gtests',
       # No standard tests due to capacity, no Vega tests since it's currently
       # O only.
     ],
 
+    'android_pie_rel_gtests': [
+      'android_monochrome_smoke_tests',
+      'android_smoke_tests',
+      'system_webview_shell_instrumentation_tests',
+      'webview_cts_tests_gtest',
+      'webview_ui_instrumentation_tests',
+    ],
+
     'bfcache_android_gtests': [
       'bfcache_generic_gtests',
       'bfcache_android_specific_gtests',
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 9d8cf9f..f53a67c 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -331,7 +331,7 @@
       },
       'Lollipop Phone Tester': {
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
         },
         'swarming': {
           'dimension_sets': [
@@ -347,7 +347,7 @@
       },
       'Lollipop Tablet Tester': {
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
         },
         'swarming': {
           'dimension_sets': [
@@ -367,13 +367,13 @@
           'bullhead',
         ],
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
         },
         'os_type': 'android',
       },
       'Marshmallow Tablet Tester': {
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
         },
         'swarming': {
           'dimension_sets': [
@@ -389,7 +389,7 @@
       },
       'Nougat Phone Tester': {
         'test_suites': {
-          'gtest_tests': 'android_ddready_vr_gtests',
+          'gtest_tests': 'android_nougat_gtests',
         },
         'swarming': {
           'dimension_sets': [
@@ -587,22 +587,11 @@
           'weblayer_shell',
         ],
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
           'isolated_scripts': 'marshmallow_isolated_scripts',
         },
         'os_type': 'android',
       },
-      'android-oreo-arm64-rel': {
-        'mixins': [
-          'oreo_fleet',
-          'walleye',
-        ],
-        'test_suites': {
-          'gtest_tests': 'android_oreo_rel_gtests',
-        },
-        'use_swarming': True,
-        'os_type': 'android',
-      },
       'android-pie-arm64-dbg': {
         'swarming': {
           'dimension_sets': [
@@ -618,6 +607,17 @@
           'gtest_tests': 'android_pie_gtests',
         }
       },
+      'android-pie-arm64-rel': {
+        'mixins': [
+          'pie_fleet',
+          'walleye',
+        ],
+        'test_suites': {
+          'gtest_tests': 'android_pie_rel_gtests',
+        },
+        'use_swarming': True,
+        'os_type': 'android',
+      },
     },
   },
   {
@@ -682,7 +682,7 @@
           'monochrome_static_initializers',
         ],
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
           'isolated_scripts': 'marshmallow_isolated_scripts',
         }
       },
@@ -1595,7 +1595,7 @@
           'bullhead',
         ],
         'test_suites': {
-          'gtest_tests': 'chromium_android_gtests',
+          'gtest_tests': 'android_lollipop_marshmallow_gtests',
           'junit_tests': 'chromium_junit_tests',
         },
         'os_type': 'android',
diff --git a/third_party/blink/common/features.cc b/third_party/blink/common/features.cc
index 8b3a71d..cc56f78 100644
--- a/third_party/blink/common/features.cc
+++ b/third_party/blink/common/features.cc
@@ -59,18 +59,8 @@
                                      base::FEATURE_DISABLED_BY_DEFAULT};
 
 // Enable a new CSS property called backdrop-filter.
-const base::Feature kCSSBackdropFilter {
-  "CSSBackdropFilter",
-#if defined(OS_ANDROID)
-      // There are two bugs in the backdrop-filter feature for Android Webview
-      // only (crbug.com/990535 and crbug.com/991869). Because there is no
-      // compile-time flag for Webview, this disables all of Android, and the
-      // feature will be re-enabled for non-Webview Android by Finch.
-      base::FEATURE_DISABLED_BY_DEFAULT
-#else
-      base::FEATURE_ENABLED_BY_DEFAULT
-#endif
-};
+const base::Feature kCSSBackdropFilter{"CSSBackdropFilter",
+                                       base::FEATURE_ENABLED_BY_DEFAULT};
 
 // Enable Display Locking JavaScript APIs.
 const base::Feature kDisplayLocking{"DisplayLocking",
diff --git a/third_party/blink/common/messaging/cloneable_message.cc b/third_party/blink/common/messaging/cloneable_message.cc
index 75fd2b4..2984fd1 100644
--- a/third_party/blink/common/messaging/cloneable_message.cc
+++ b/third_party/blink/common/messaging/cloneable_message.cc
@@ -4,6 +4,8 @@
 
 #include "third_party/blink/public/common/messaging/cloneable_message.h"
 
+#include "mojo/public/cpp/bindings/pending_remote.h"
+#include "mojo/public/cpp/bindings/remote.h"
 #include "third_party/blink/public/mojom/blob/blob.mojom.h"
 #include "third_party/blink/public/mojom/messaging/cloneable_message.mojom.h"
 
@@ -22,17 +24,17 @@
     // NOTE: We dubiously exercise dual ownership of the blob's pipe handle here
     // so that we can temporarily bind and send a message over the pipe without
     // mutating the state of this CloneableMessage.
-    mojo::ScopedMessagePipeHandle handle(blob->blob.handle().get());
-    mojom::BlobPtr blob_proxy(
-        mojom::BlobPtrInfo(std::move(handle), blob->blob.version()));
-    mojom::BlobPtrInfo blob_clone_info;
-    blob_proxy->Clone(MakeRequest(&blob_clone_info));
+    mojo::ScopedMessagePipeHandle handle(blob->blob.Pipe().get());
+    mojo::Remote<mojom::Blob> blob_proxy(mojo::PendingRemote<mojom::Blob>(
+        std::move(handle), blob->blob.version()));
+    mojo::PendingRemote<mojom::Blob> blob_clone_remote;
+    blob_proxy->Clone(blob_clone_remote.InitWithNewPipeAndPassReceiver());
     clone.blobs.push_back(
         mojom::SerializedBlob::New(blob->uuid, blob->content_type, blob->size,
-                                   std::move(blob_clone_info)));
+                                   std::move(blob_clone_remote)));
 
     // Not leaked - still owned by |blob->blob|.
-    ignore_result(blob_proxy.PassInterface().PassHandle().release());
+    ignore_result(blob_proxy.Unbind().PassPipe().release());
   }
   return clone;
 }
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index de1440ba..1e386f68 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -134,12 +134,6 @@
     "platform/mac/web_scrollbar_theme.h",
     "platform/media/webmediaplayer_delegate.h",
     "platform/modules/indexeddb/web_idb_database_exception.h",
-    "platform/modules/media_capabilities/web_audio_configuration.h",
-    "platform/modules/media_capabilities/web_media_capabilities_info.h",
-    "platform/modules/media_capabilities/web_media_capabilities_key_system_configuration.h",
-    "platform/modules/media_capabilities/web_media_configuration.h",
-    "platform/modules/media_capabilities/web_media_decoding_configuration.h",
-    "platform/modules/media_capabilities/web_video_configuration.h",
     "platform/modules/mediastream/media_stream_audio_deliverer.h",
     "platform/modules/mediastream/media_stream_audio_level_calculator.h",
     "platform/modules/mediastream/media_stream_audio_processor_options.h",
diff --git a/third_party/blink/public/mojom/blob/blob_url_store.mojom b/third_party/blink/public/mojom/blob/blob_url_store.mojom
index 33f4779..fcdb204 100644
--- a/third_party/blink/public/mojom/blob/blob_url_store.mojom
+++ b/third_party/blink/public/mojom/blob/blob_url_store.mojom
@@ -22,7 +22,7 @@
   Revoke(url.mojom.Url url);
 
   // Resolves a public Blob URL.
-  Resolve(url.mojom.Url url) => (blink.mojom.Blob? blob);
+  Resolve(url.mojom.Url url) => (pending_remote<blink.mojom.Blob>? blob);
 
   // Resolves a public Blob URL to a URLLoaderFactory that can only load the
   // specified URL. The reason the API is shaped like this rather than just
diff --git a/third_party/blink/public/mojom/blob/data_element.mojom b/third_party/blink/public/mojom/blob/data_element.mojom
index 7afa95a..3767c31c7 100644
--- a/third_party/blink/public/mojom/blob/data_element.mojom
+++ b/third_party/blink/public/mojom/blob/data_element.mojom
@@ -103,7 +103,7 @@
 // A reference to a slice of another blob.
 struct DataElementBlob {
   // The blob being referenced.
-  blink.mojom.Blob blob;
+  pending_remote<blink.mojom.Blob> blob;
   // Offset to the beginning of the slice.
   uint64 offset;
   // Length of the slice.
diff --git a/third_party/blink/public/mojom/blob/serialized_blob.mojom b/third_party/blink/public/mojom/blob/serialized_blob.mojom
index f346205f..f99f2b10 100644
--- a/third_party/blink/public/mojom/blob/serialized_blob.mojom
+++ b/third_party/blink/public/mojom/blob/serialized_blob.mojom
@@ -14,5 +14,5 @@
   string uuid;
   string content_type;
   uint64 size;
-  Blob blob;
+  pending_remote<Blob> blob;
 };
diff --git a/third_party/blink/public/mojom/indexeddb/indexeddb.mojom b/third_party/blink/public/mojom/indexeddb/indexeddb.mojom
index 60c24c2..d173ba7 100644
--- a/third_party/blink/public/mojom/indexeddb/indexeddb.mojom
+++ b/third_party/blink/public/mojom/indexeddb/indexeddb.mojom
@@ -176,7 +176,7 @@
 };
 
 struct IDBBlobInfo {
-  blink.mojom.Blob blob;
+  pending_remote<blink.mojom.Blob> blob;
   string uuid;
   mojo_base.mojom.String16 mime_type;
   int64 size;
diff --git a/third_party/blink/renderer/core/animation/BUILD.gn b/third_party/blink/renderer/core/animation/BUILD.gn
index a048dcf..d1530f95 100644
--- a/third_party/blink/renderer/core/animation/BUILD.gn
+++ b/third_party/blink/renderer/core/animation/BUILD.gn
@@ -282,6 +282,7 @@
     "document_timeline_test.cc",
     "effect_input_test.cc",
     "effect_stack_test.cc",
+    "interpolable_length_test.cc",
     "interpolable_value_test.cc",
     "interpolation_effect_test.cc",
     "keyframe_effect_model_test.cc",
diff --git a/third_party/blink/renderer/core/animation/animation_effect.cc b/third_party/blink/renderer/core/animation/animation_effect.cc
index 238faeb..8d185980 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect.cc
@@ -97,16 +97,10 @@
                               calculated.phase == Timing::kPhaseNone;
 
     // If the animation was canceled, we need to fire the event condition before
-    // updating the timing so that the cancelation time can be determined.
+    // updating the calculated timing so that the cancellation time can be
+    // determined.
     if (was_canceled && event_delegate_) {
-      // TODO(jortaylo): OnEventCondition uses the new phase but the old current
-      // iterations. That is why we partially update *calculated_* here with the
-      // new phase. This pseudo state can be very confusing. It may be either a
-      // bug or required but at the very least instead of partially updating
-      // *calculated_* we should pass in current phase and the old "current
-      // iterations" more explicitly. https://crbug.com/994850
-      calculated_.phase = calculated.phase;
-      event_delegate_->OnEventCondition(*this);
+      event_delegate_->OnEventCondition(*this, calculated.phase);
     }
 
     calculated_ = calculated;
@@ -119,7 +113,7 @@
   if (reason == kTimingUpdateForAnimationFrame &&
       (!owner_ || owner_->IsEventDispatchAllowed())) {
     if (event_delegate_)
-      event_delegate_->OnEventCondition(*this);
+      event_delegate_->OnEventCondition(*this, calculated_.phase);
   }
 
   if (needs_update) {
diff --git a/third_party/blink/renderer/core/animation/animation_effect.h b/third_party/blink/renderer/core/animation/animation_effect.h
index d23432c5..f4e6e7dd 100644
--- a/third_party/blink/renderer/core/animation/animation_effect.h
+++ b/third_party/blink/renderer/core/animation/animation_effect.h
@@ -71,7 +71,7 @@
    public:
     virtual ~EventDelegate() = default;
     virtual bool RequiresIterationEvents(const AnimationEffect&) = 0;
-    virtual void OnEventCondition(const AnimationEffect&) = 0;
+    virtual void OnEventCondition(const AnimationEffect&, Timing::Phase) = 0;
     virtual void Trace(blink::Visitor* visitor) {}
   };
 
diff --git a/third_party/blink/renderer/core/animation/animation_effect_test.cc b/third_party/blink/renderer/core/animation/animation_effect_test.cc
index 4b0e7ea..9f97b2694 100644
--- a/third_party/blink/renderer/core/animation/animation_effect_test.cc
+++ b/third_party/blink/renderer/core/animation/animation_effect_test.cc
@@ -56,7 +56,8 @@
 
 class TestAnimationEffectEventDelegate : public AnimationEffect::EventDelegate {
  public:
-  void OnEventCondition(const AnimationEffect& animation_node) override {
+  void OnEventCondition(const AnimationEffect& animation_node,
+                        Timing::Phase current_phase) override {
     event_triggered_ = true;
   }
   bool RequiresIterationEvents(const AnimationEffect& animation_node) override {
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.cc b/third_party/blink/renderer/core/animation/css/css_animations.cc
index 99decf1..3b156fd8 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.cc
+++ b/third_party/blink/renderer/core/animation/css/css_animations.cc
@@ -1121,8 +1121,8 @@
 }
 
 void CSSAnimations::AnimationEventDelegate::OnEventCondition(
-    const AnimationEffect& animation_node) {
-  const Timing::Phase current_phase = animation_node.GetPhase();
+    const AnimationEffect& animation_node,
+    Timing::Phase current_phase) {
   const base::Optional<double> current_iteration =
       animation_node.CurrentIteration();
 
@@ -1174,8 +1174,8 @@
 }
 
 void CSSAnimations::TransitionEventDelegate::OnEventCondition(
-    const AnimationEffect& animation_node) {
-  const Timing::Phase current_phase = animation_node.GetPhase();
+    const AnimationEffect& animation_node,
+    Timing::Phase current_phase) {
   if (current_phase == previous_phase_)
     return;
 
diff --git a/third_party/blink/renderer/core/animation/css/css_animations.h b/third_party/blink/renderer/core/animation/css/css_animations.h
index 9ba4abe..ebf71a33 100644
--- a/third_party/blink/renderer/core/animation/css/css_animations.h
+++ b/third_party/blink/renderer/core/animation/css/css_animations.h
@@ -204,7 +204,7 @@
           name_(name),
           previous_phase_(Timing::kPhaseNone) {}
     bool RequiresIterationEvents(const AnimationEffect&) override;
-    void OnEventCondition(const AnimationEffect&) override;
+    void OnEventCondition(const AnimationEffect&, Timing::Phase) override;
     void Trace(blink::Visitor*) override;
 
    private:
@@ -231,7 +231,7 @@
     bool RequiresIterationEvents(const AnimationEffect&) override {
       return false;
     }
-    void OnEventCondition(const AnimationEffect&) override;
+    void OnEventCondition(const AnimationEffect&, Timing::Phase) override;
     void Trace(blink::Visitor*) override;
 
    private:
diff --git a/third_party/blink/renderer/core/animation/interpolable_length.cc b/third_party/blink/renderer/core/animation/interpolable_length.cc
index 9f6a511f..006a040 100644
--- a/third_party/blink/renderer/core/animation/interpolable_length.cc
+++ b/third_party/blink/renderer/core/animation/interpolable_length.cc
@@ -14,27 +14,37 @@
 
 namespace blink {
 
+using UnitType = CSSPrimitiveValue::UnitType;
+
+namespace {
+
+CSSMathExpressionNode* NumberNode(double number) {
+  return CSSMathExpressionNumericLiteral::Create(
+      CSSNumericLiteralValue::Create(number, UnitType::kNumber));
+}
+
+CSSMathExpressionNode* PercentageNode(double number) {
+  return CSSMathExpressionNumericLiteral::Create(
+      CSSNumericLiteralValue::Create(number, UnitType::kPercentage));
+}
+
+}  // namespace
+
 // static
 std::unique_ptr<InterpolableLength> InterpolableLength::CreatePixels(
     double pixels) {
-  CSSLengthArray length_array;
-  length_array.values[CSSPrimitiveValue::kUnitTypePixels] = pixels;
-  length_array.type_flags.set(CSSPrimitiveValue::kUnitTypePixels);
-  return std::make_unique<InterpolableLength>(std::move(length_array));
+  return std::make_unique<InterpolableLength>(pixels, UnitType::kPixels);
 }
 
 // static
 std::unique_ptr<InterpolableLength> InterpolableLength::CreatePercent(
     double percent) {
-  CSSLengthArray length_array;
-  length_array.values[CSSPrimitiveValue::kUnitTypePercentage] = percent;
-  length_array.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
-  return std::make_unique<InterpolableLength>(std::move(length_array));
+  return std::make_unique<InterpolableLength>(percent, UnitType::kPercentage);
 }
 
 // static
 std::unique_ptr<InterpolableLength> InterpolableLength::CreateNeutral() {
-  return std::make_unique<InterpolableLength>(CSSLengthArray());
+  return std::make_unique<InterpolableLength>();
 }
 
 // static
@@ -48,14 +58,13 @@
       !primitive_value->IsCalculatedPercentageWithLength())
     return nullptr;
 
-  CSSLengthArray length_array;
-  if (!primitive_value->AccumulateLengthArray(length_array)) {
-    // TODO(crbug.com/991672): Implement interpolation when CSS comparison
-    // functions min/max are involved.
-    return nullptr;
+  if (const auto* numeric_literal =
+          DynamicTo<CSSNumericLiteralValue>(primitive_value)) {
+    return std::make_unique<InterpolableLength>(*numeric_literal);
   }
 
-  return std::make_unique<InterpolableLength>(std::move(length_array));
+  return std::make_unique<InterpolableLength>(
+      *To<CSSMathFunctionValue>(primitive_value)->ExpressionNode());
 }
 
 // static
@@ -65,24 +74,16 @@
   if (!length.IsSpecified())
     return nullptr;
 
-  if (length.IsCalculated() && length.GetCalculationValue().IsExpression()) {
-    // TODO(crbug.com/991672): Support interpolation on min/max results.
-    return nullptr;
-  }
+  // Do not use CSSPrimitiveValue::CreateFromLength(), as it might drop 0% in
+  // calculated lengths.
+  // TODO(crbug.com/991672): Try not to drop 0% there.
 
-  PixelsAndPercent pixels_and_percent = length.GetPixelsAndPercent();
-  CSSLengthArray length_array;
-
-  length_array.values[CSSPrimitiveValue::kUnitTypePixels] =
-      pixels_and_percent.pixels / zoom;
-  length_array.type_flags[CSSPrimitiveValue::kUnitTypePixels] =
-      pixels_and_percent.pixels != 0;
-
-  length_array.values[CSSPrimitiveValue::kUnitTypePercentage] =
-      pixels_and_percent.percent;
-  length_array.type_flags[CSSPrimitiveValue::kUnitTypePercentage] =
-      length.IsPercentOrCalc();
-  return std::make_unique<InterpolableLength>(std::move(length_array));
+  if (length.IsFixed())
+    return CreatePixels(length.Pixels() / zoom);
+  if (length.IsPercent())
+    return CreatePercent(length.Percent());
+  return std::make_unique<InterpolableLength>(
+      *CSSMathExpressionNode::Create(length.GetCalculationValue()));
 }
 
 // static
@@ -94,107 +95,168 @@
   // should stop doing that.
   auto& start_length = To<InterpolableLength>(*start);
   auto& end_length = To<InterpolableLength>(*end);
-  start_length.length_array_.type_flags |= end_length.length_array_.type_flags;
-  end_length.length_array_.type_flags = start_length.length_array_.type_flags;
+  if (start_length.HasPercentage() || end_length.HasPercentage()) {
+    start_length.SetHasPercentage();
+    end_length.SetHasPercentage();
+  }
   return PairwiseInterpolationValue(std::move(start), std::move(end));
 }
 
+InterpolableLength::InterpolableLength(double value, UnitType unit_type) {
+  SetNumericLiteral(value, unit_type);
+}
+
+InterpolableLength::InterpolableLength()
+    : InterpolableLength(0, UnitType::kPixels) {}
+
+InterpolableLength::InterpolableLength(const CSSNumericLiteralValue& value)
+    : InterpolableLength(value.DoubleValue(), value.GetType()) {}
+
+InterpolableLength::InterpolableLength(
+    const CSSMathExpressionNode& expression) {
+  SetExpression(expression);
+}
+
+std::unique_ptr<InterpolableValue> InterpolableLength::Clone() const {
+  return std::make_unique<InterpolableLength>(*this);
+}
+
+void InterpolableLength::SetNumericLiteral(
+    double value,
+    CSSPrimitiveValue::UnitType unit_type) {
+  type_ = Type::kNumericLiteral;
+  single_value_ = value;
+  unit_type_ = unit_type;
+  expression_.Clear();
+}
+
+void InterpolableLength::SetNumericLiteral(
+    const CSSNumericLiteralValue& value) {
+  SetNumericLiteral(value.DoubleValue(), value.GetType());
+}
+
+void InterpolableLength::SetExpression(
+    const CSSMathExpressionNode& expression) {
+  if (expression.IsNumericLiteral()) {
+    return SetNumericLiteral(
+        *To<CSSMathExpressionNumericLiteral>(expression).GetValue());
+  }
+
+  type_ = Type::kExpression;
+  expression_ = &expression;
+}
+
+const CSSMathExpressionNode& InterpolableLength::AsExpression() const {
+  if (IsExpression())
+    return *expression_;
+  return *CSSMathExpressionNumericLiteral::Create(
+      CSSNumericLiteralValue::Create(single_value_, unit_type_));
+}
+
+bool InterpolableLength::HasPercentage() const {
+  if (IsNumericLiteral())
+    return unit_type_ == UnitType::kPercentage;
+  return expression_->HasPercentage();
+}
+
+void InterpolableLength::SetHasPercentage() {
+  DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, zero_percent,
+                      {PercentageNode(0)});
+  if (HasPercentage())
+    return;
+  if (IsZeroLength())
+    return SetNumericLiteral(0, UnitType::kPercentage);
+  SetExpression(*CSSMathExpressionBinaryOperation::Create(
+      &AsExpression(), zero_percent, CSSMathOperator::kAdd));
+}
+
 void InterpolableLength::SubtractFromOneHundredPercent() {
-  for (double& value : length_array_.values)
-    value *= -1;
-  length_array_.values[CSSPrimitiveValue::kUnitTypePercentage] += 100;
-  length_array_.type_flags.set(CSSPrimitiveValue::kUnitTypePercentage);
+  DEFINE_STATIC_LOCAL(Persistent<CSSMathExpressionNode>, hundred_percent,
+                      {PercentageNode(100)});
+  if (IsNumericLiteral()) {
+    if (unit_type_ == UnitType::kPercentage) {
+      single_value_ = 100 - single_value_;
+      return;
+    }
+    if (IsZeroLength())
+      return SetNumericLiteral(100, UnitType::kPercentage);
+
+    // Fall through, as the result requires an expression to represent
+  }
+
+  SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
+      hundred_percent, &AsExpression(), CSSMathOperator::kSubtract));
 }
 
 static double ClampToRange(double x, ValueRange range) {
   return (range == kValueRangeNonNegative && x < 0) ? 0 : x;
 }
 
-static CSSPrimitiveValue::UnitType IndexToUnitType(wtf_size_t index) {
-  return CSSPrimitiveValue::LengthUnitTypeToUnitType(
-      static_cast<CSSPrimitiveValue::LengthUnitType>(index));
-}
-
 Length InterpolableLength::CreateLength(
     const CSSToLengthConversionData& conversion_data,
     ValueRange range) const {
-  bool has_percentage = HasPercentage();
-  double pixels = 0;
-  double percentage = 0;
-  for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
-    double value = length_array_.values[i];
-    if (value == 0)
-      continue;
-    if (i == CSSPrimitiveValue::kUnitTypePercentage) {
-      percentage = value;
-    } else {
-      pixels += conversion_data.ZoomedComputedPixels(value, IndexToUnitType(i));
+  if (IsNumericLiteral()) {
+    const double value = ClampToRange(single_value_, range);
+    if (CSSPrimitiveValue::IsLength(unit_type_)) {
+      const double value_px =
+          conversion_data.ZoomedComputedPixels(value, unit_type_);
+      return Length::Fixed(CSSPrimitiveValue::ClampToCSSLengthRange(value_px));
     }
+    DCHECK_EQ(UnitType::kPercentage, unit_type_);
+    return Length::Percent(value);
   }
 
-  if (percentage != 0)
-    has_percentage = true;
-  if (pixels != 0 && has_percentage) {
-    return Length(CalculationValue::Create(
-        PixelsAndPercent(clampTo<float>(pixels), clampTo<float>(percentage)),
-        range));
-  }
-  if (has_percentage)
-    return Length::Percent(ClampToRange(percentage, range));
-  return Length::Fixed(
-      CSSPrimitiveValue::ClampToCSSLengthRange(ClampToRange(pixels, range)));
+  // In the uncommon case where |this| is a math expression, this implementation
+  // avoids a lot of code complexity at the cost of creating a temporary
+  // |CSSMathFunctionValue| object.
+  return CreateCSSValue(range)->ConvertToLength(conversion_data);
 }
 
 const CSSPrimitiveValue* InterpolableLength::CreateCSSValue(
     ValueRange range) const {
-  bool has_percentage = HasPercentage();
+  if (IsExpression())
+    return CSSMathFunctionValue::Create(&AsExpression(), range);
 
-  CSSMathExpressionNode* root_node = nullptr;
-  CSSNumericLiteralValue* first_value = nullptr;
+  DCHECK(IsNumericLiteral());
+  return CSSNumericLiteralValue::Create(ClampToRange(single_value_, range),
+                                        unit_type_);
+}
 
-  for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
-    double value = length_array_.values[i];
-    if (value == 0 &&
-        (i != CSSPrimitiveValue::kUnitTypePercentage || !has_percentage)) {
-      continue;
-    }
-    CSSNumericLiteralValue* current_value =
-        CSSNumericLiteralValue::Create(value, IndexToUnitType(i));
-
-    if (!first_value) {
-      DCHECK(!root_node);
-      first_value = current_value;
-      continue;
-    }
-    CSSMathExpressionNode* current_node =
-        CSSMathExpressionNumericLiteral::Create(current_value);
-    if (!root_node) {
-      root_node = CSSMathExpressionNumericLiteral::Create(first_value);
-    }
-    root_node = CSSMathExpressionBinaryOperation::Create(
-        root_node, current_node, CSSMathOperator::kAdd);
+void InterpolableLength::Scale(double scale) {
+  if (IsNumericLiteral()) {
+    single_value_ *= scale;
+    return;
   }
 
-  if (root_node) {
-    return CSSMathFunctionValue::Create(root_node, range);
-  }
-  if (first_value) {
-    if (range == kValueRangeNonNegative && first_value->DoubleValue() < 0)
-      return CSSNumericLiteralValue::Create(0, first_value->GetType());
-    return first_value;
-  }
-  return CSSNumericLiteralValue::Create(0,
-                                        CSSPrimitiveValue::UnitType::kPixels);
+  DCHECK(IsExpression());
+  SetExpression(*CSSMathExpressionBinaryOperation::CreateSimplified(
+      &AsExpression(), NumberNode(scale), CSSMathOperator::kMultiply));
 }
 
 void InterpolableLength::ScaleAndAdd(double scale,
                                      const InterpolableValue& other) {
   const InterpolableLength& other_length = To<InterpolableLength>(other);
-  for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
-    length_array_.values[i] =
-        length_array_.values[i] * scale + other_length.length_array_.values[i];
+  if (IsNumericLiteral() && other_length.IsNumericLiteral() &&
+      unit_type_ == other_length.unit_type_) {
+    single_value_ = single_value_ * scale + other_length.single_value_;
+    return;
   }
-  length_array_.type_flags |= other_length.length_array_.type_flags;
+
+  // Avoid creating an addition expression with a zero-length operand.
+  if (IsZeroLength()) {
+    *this = other_length;
+    return;
+  }
+  if (other_length.IsZeroLength())
+    return Scale(scale);
+
+  CSSMathExpressionNode* scaled =
+      CSSMathExpressionBinaryOperation::CreateSimplified(
+          &AsExpression(), NumberNode(scale), CSSMathOperator::kMultiply);
+  CSSMathExpressionNode* result =
+      CSSMathExpressionBinaryOperation::CreateSimplified(
+          scaled, &other_length.AsExpression(), CSSMathOperator::kAdd);
+  SetExpression(*result);
 }
 
 void InterpolableLength::AssertCanInterpolateWith(
@@ -204,23 +266,47 @@
   // two |InterpolableLength| objects should also assign them the same shape
   // (i.e. type flags) after merging into a |PairwiseInterpolationValue|. We
   // currently fail to do that, and hit the following DCHECK:
-  // DCHECK_EQ(length_array_.type_flags,
-  //           To<InterpolableLength>(other).length_array_.type_flags);
+  // DCHECK_EQ(HasPercentage(),
+  //           To<InterpolableLength>(other).HasPercentage());
 }
 
 void InterpolableLength::Interpolate(const InterpolableValue& to,
                                      const double progress,
                                      InterpolableValue& result) const {
-  const CSSLengthArray& to_length_array =
-      To<InterpolableLength>(to).length_array_;
-  CSSLengthArray& result_length_array =
-      To<InterpolableLength>(result).length_array_;
-  for (wtf_size_t i = 0; i < length_array_.values.size(); ++i) {
-    result_length_array.values[i] =
-        Blend(length_array_.values[i], to_length_array.values[i], progress);
+  const auto& to_length = To<InterpolableLength>(to);
+  auto& result_length = To<InterpolableLength>(result);
+
+  if (IsNumericLiteral() && to_length.IsNumericLiteral() &&
+      unit_type_ == to_length.unit_type_) {
+    return result_length.SetNumericLiteral(
+        Blend(single_value_, to_length.single_value_, progress), unit_type_);
   }
-  result_length_array.type_flags =
-      length_array_.type_flags | to_length_array.type_flags;
+
+  // Avoid creating an addition expression with a zero-length operand.
+  if (IsZeroLength()) {
+    result_length = to_length;
+    return result_length.Scale(progress);
+  }
+  if (to_length.IsZeroLength()) {
+    result_length = *this;
+    return result_length.Scale(1 - progress);
+  }
+
+  CSSMathExpressionNode* blended_from =
+      CSSMathExpressionBinaryOperation::CreateSimplified(
+          &AsExpression(), NumberNode(1 - progress),
+          CSSMathOperator::kMultiply);
+  CSSMathExpressionNode* blended_to =
+      CSSMathExpressionBinaryOperation::CreateSimplified(
+          &to_length.AsExpression(), NumberNode(progress),
+          CSSMathOperator::kMultiply);
+  CSSMathExpressionNode* result_expression =
+      CSSMathExpressionBinaryOperation::CreateSimplified(
+          blended_from, blended_to, CSSMathOperator::kAdd);
+  result_length.SetExpression(*result_expression);
+
+  DCHECK_EQ(result_length.HasPercentage(),
+            HasPercentage() || to_length.HasPercentage());
 }
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/animation/interpolable_length.h b/third_party/blink/renderer/core/animation/interpolable_length.h
index bd06bcf..37e7ac5 100644
--- a/third_party/blink/renderer/core/animation/interpolable_length.h
+++ b/third_party/blink/renderer/core/animation/interpolable_length.h
@@ -10,19 +10,24 @@
 #include "third_party/blink/renderer/core/animation/pairwise_interpolation_value.h"
 #include "third_party/blink/renderer/core/css/css_primitive_value.h"
 #include "third_party/blink/renderer/platform/geometry/length.h"
+#include "third_party/blink/renderer/platform/heap/persistent.h"
 #include "third_party/blink/renderer/platform/wtf/casting.h"
 
 namespace blink {
 
 class CSSToLengthConversionData;
+class CSSMathExpressionNode;
+class CSSNumericLiteralValue;
+class CSSValue;
 
 class CORE_EXPORT InterpolableLength final : public InterpolableValue {
  public:
   ~InterpolableLength() final {}
-  InterpolableLength(const CSSLengthArray& length_array)
-      : length_array_(length_array) {}
-  InterpolableLength(CSSLengthArray&& length_array)
-      : length_array_(std::move(length_array)) {}
+
+  InterpolableLength();
+  InterpolableLength(double value, CSSPrimitiveValue::UnitType);
+  explicit InterpolableLength(const CSSNumericLiteralValue& value);
+  explicit InterpolableLength(const CSSMathExpressionNode& expression);
 
   static std::unique_ptr<InterpolableLength> CreatePixels(double pixels);
   static std::unique_ptr<InterpolableLength> CreatePercent(double pixels);
@@ -45,10 +50,9 @@
   // expressions.
   const CSSPrimitiveValue* CreateCSSValue(ValueRange range) const;
 
-  bool HasPercentage() const {
-    return length_array_.type_flags.test(
-        CSSPrimitiveValue::kUnitTypePercentage);
-  }
+  // Mix the length with the percentage type, if it's not mixed already
+  void SetHasPercentage();
+  bool HasPercentage() const;
   void SubtractFromOneHundredPercent();
 
   // InterpolableValue:
@@ -57,27 +61,44 @@
     NOTREACHED();
     return false;
   }
-  std::unique_ptr<InterpolableValue> Clone() const final {
-    return std::make_unique<InterpolableLength>(length_array_);
-  }
+  std::unique_ptr<InterpolableValue> Clone() const final;
   std::unique_ptr<InterpolableValue> CloneAndZero() const final {
-    return std::make_unique<InterpolableLength>(CSSLengthArray());
+    return std::make_unique<InterpolableLength>();
   }
-  void Scale(double scale) final {
-    for (double& value : length_array_.values) {
-      value *= scale;
-    }
-  }
+  void Scale(double scale) final;
   void ScaleAndAdd(double scale, const InterpolableValue& other) final;
   void AssertCanInterpolateWith(const InterpolableValue& other) const final;
 
  private:
+  friend class InterpolableLengthTest;
+
   // InterpolableValue:
   void Interpolate(const InterpolableValue& to,
                    const double progress,
                    InterpolableValue& result) const final;
 
-  CSSLengthArray length_array_;
+  bool IsNumericLiteral() const { return type_ == Type::kNumericLiteral; }
+  bool IsExpression() const { return type_ == Type::kExpression; }
+
+  bool IsZeroLength() const {
+    return IsNumericLiteral() && CSSPrimitiveValue::IsLength(unit_type_) &&
+           single_value_ == 0;
+  }
+
+  void SetNumericLiteral(double value, CSSPrimitiveValue::UnitType unit_type);
+  void SetNumericLiteral(const CSSNumericLiteralValue& value);
+  void SetExpression(const CSSMathExpressionNode& expression);
+  const CSSMathExpressionNode& AsExpression() const;
+
+  enum class Type { kNumericLiteral, kExpression };
+  Type type_;
+
+  // Set when |type_| is |kNumericLiteral|.
+  double single_value_;
+  CSSPrimitiveValue::UnitType unit_type_;
+
+  // Non-null when |type_| is |kExpression|.
+  Persistent<const CSSMathExpressionNode> expression_;
 };
 
 template <>
diff --git a/third_party/blink/renderer/core/animation/interpolable_length_test.cc b/third_party/blink/renderer/core/animation/interpolable_length_test.cc
new file mode 100644
index 0000000..08562ba4
--- /dev/null
+++ b/third_party/blink/renderer/core/animation/interpolable_length_test.cc
@@ -0,0 +1,132 @@
+// 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/animation/interpolable_length.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blink {
+
+using UnitType = CSSPrimitiveValue::UnitType;
+
+// Verifies the "shape" of the |InterpolableLength| after various operations.
+// Subject to change if the internal representation of |InterpolableLength| is
+// changed.
+class InterpolableLengthTest : public ::testing::Test {
+ protected:
+  std::unique_ptr<InterpolableLength> Create(double value,
+                                             UnitType type) const {
+    return std::make_unique<InterpolableLength>(value, type);
+  }
+
+  std::unique_ptr<InterpolableLength> Interpolate(
+      const InterpolableLength& from,
+      const InterpolableLength& to,
+      double progress) {
+    std::unique_ptr<InterpolableLength> result =
+        InterpolableLength::CreateNeutral();
+    from.Interpolate(to, progress, *result);
+    return result;
+  }
+
+  bool IsNumericLiteral(const InterpolableLength& length) const {
+    return length.IsNumericLiteral();
+  }
+
+  double GetSingleValue(const InterpolableLength& length) const {
+    DCHECK(length.IsNumericLiteral());
+    return length.single_value_;
+  }
+
+  UnitType GetUnitType(const InterpolableLength& length) const {
+    DCHECK(length.IsNumericLiteral());
+    return length.unit_type_;
+  }
+
+  bool IsExpression(const InterpolableLength& length) const {
+    return length.IsExpression();
+  }
+};
+
+TEST_F(InterpolableLengthTest, InterpolateSameUnits) {
+  {
+    auto length_a = InterpolableLength::CreatePixels(4);
+    auto length_b = InterpolableLength::CreatePixels(6);
+    auto result = Interpolate(*length_a, *length_b, 0.5);
+
+    EXPECT_TRUE(IsNumericLiteral(*result));
+    EXPECT_EQ(5, GetSingleValue(*result));
+    EXPECT_EQ(UnitType::kPixels, GetUnitType(*result));
+    EXPECT_FALSE(result->HasPercentage());
+  }
+
+  {
+    auto length_a = Create(4, UnitType::kEms);
+    auto length_b = Create(6, UnitType::kEms);
+    auto result = Interpolate(*length_a, *length_b, 0.5);
+
+    EXPECT_TRUE(IsNumericLiteral(*result));
+    EXPECT_EQ(5, GetSingleValue(*result));
+    EXPECT_EQ(UnitType::kEms, GetUnitType(*result));
+    EXPECT_FALSE(result->HasPercentage());
+  }
+
+  {
+    auto length_a = InterpolableLength::CreatePercent(4);
+    auto length_b = InterpolableLength::CreatePercent(6);
+    auto result = Interpolate(*length_a, *length_b, 0.5);
+
+    EXPECT_TRUE(IsNumericLiteral(*result));
+    EXPECT_EQ(5, GetSingleValue(*result));
+    EXPECT_EQ(UnitType::kPercentage, GetUnitType(*result));
+    EXPECT_TRUE(result->HasPercentage());
+  }
+}
+
+TEST_F(InterpolableLengthTest, InterpolateIncompatibleUnits) {
+  {
+    auto length_a = InterpolableLength::CreatePixels(4);
+    auto length_b = Create(6, UnitType::kEms);
+    auto result = Interpolate(*length_a, *length_b, 0.5);
+
+    EXPECT_TRUE(IsExpression(*result));
+    EXPECT_FALSE(result->HasPercentage());
+    EXPECT_EQ("calc(2px + 3em)",
+              result->CreateCSSValue(kValueRangeAll)->CustomCSSText());
+  }
+}
+
+TEST_F(InterpolableLengthTest, SetHasPercentage) {
+  {
+    auto length = InterpolableLength::CreateNeutral();
+    length->SetHasPercentage();
+
+    EXPECT_TRUE(length->HasPercentage());
+    EXPECT_TRUE(IsNumericLiteral(*length));
+    EXPECT_EQ(0, GetSingleValue(*length));
+    EXPECT_EQ(UnitType::kPercentage, GetUnitType(*length));
+  }
+
+  {
+    auto length = InterpolableLength::CreatePercent(10);
+    length->SetHasPercentage();
+
+    EXPECT_TRUE(length->HasPercentage());
+    EXPECT_TRUE(IsNumericLiteral(*length));
+    EXPECT_EQ(10, GetSingleValue(*length));
+    EXPECT_EQ(UnitType::kPercentage, GetUnitType(*length));
+  }
+
+  {
+    auto length = InterpolableLength::CreatePixels(10);
+    length->SetHasPercentage();
+
+    EXPECT_TRUE(length->HasPercentage());
+    EXPECT_TRUE(IsExpression(*length));
+    EXPECT_EQ("calc(10px + 0%)",
+              length->CreateCSSValue(kValueRangeAll)->CustomCSSText());
+  }
+}
+
+}  // namespace blink
diff --git a/third_party/blink/renderer/core/css/css_math_expression_node.h b/third_party/blink/renderer/core/css/css_math_expression_node.h
index a22e4ac4..2301e235 100644
--- a/third_party/blink/renderer/core/css/css_math_expression_node.h
+++ b/third_party/blink/renderer/core/css/css_math_expression_node.h
@@ -114,6 +114,11 @@
   virtual bool IsComputationallyIndependent() const = 0;
 
   CalculationCategory Category() const { return category_; }
+  bool HasPercentage() const {
+    return category_ == kCalcPercent || category_ == kCalcPercentNumber ||
+           category_ == kCalcPercentLength ||
+           category_ == kCalcPercentLengthNumber;
+  }
 
   // Returns the unit type of the math expression *without doing any type
   // conversion* (e.g., 1px + 1em needs type conversion to resolve).
@@ -159,6 +164,7 @@
                                   bool is_integer);
 
   bool IsNumericLiteral() const final { return true; }
+  const CSSNumericLiteralValue* GetValue() const { return value_; }
 
   bool IsZero() const final;
   String CustomCSSText() const final;
diff --git a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
index 53bee65..5027d13 100644
--- a/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
+++ b/third_party/blink/renderer/core/fileapi/file_reader_loader.cc
@@ -344,9 +344,10 @@
   DEFINE_THREAD_SAFE_STATIC_LOCAL(SparseHistogram,
                                   file_reader_loader_read_errors_histogram,
                                   ("Storage.Blob.FileReaderLoader.ReadError"));
+  file_reader_loader_read_errors_histogram.Sample(std::max(0, -net_error_));
+
   if (status != net::OK) {
     net_error_ = status;
-    file_reader_loader_read_errors_histogram.Sample(std::max(0, -net_error_));
     Failed(status == net::ERR_FILE_NOT_FOUND ? FileErrorCode::kNotFoundErr
                                              : FileErrorCode::kNotReadableErr,
            FailureType::kBackendReadError);
diff --git a/third_party/blink/renderer/core/frame/deprecation.cc b/third_party/blink/renderer/core/frame/deprecation.cc
index 9e53da36..ed90060 100644
--- a/third_party/blink/renderer/core/frame/deprecation.cc
+++ b/third_party/blink/renderer/core/frame/deprecation.cc
@@ -338,7 +338,8 @@
 
     case WebFeature::kCSSDeepCombinator:
       return {"CSSDeepCombinator", kM65,
-              "/deep/ combinator is no longer supported in CSS dynamic profile."
+              "/deep/ combinator is no longer supported in CSS dynamic "
+              "profile. "
               "It is now effectively no-op, acting as if it were a descendant "
               "combinator. /deep/ combinator will be removed, and will be "
               "invalid at M65. You should remove it. See "
diff --git a/third_party/blink/renderer/core/layout/layout_text_control.cc b/third_party/blink/renderer/core/layout/layout_text_control.cc
index 05e17b9..fd3a106 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_control.cc
@@ -132,10 +132,10 @@
   if (!inner_editor->GetLayoutObject())
     return;
 
-  PhysicalOffset local_point = hit_test_location.Point() - accumulated_offset -
-                               inner_editor->GetLayoutBox()->PhysicalLocation();
-  if (HasOverflowClip())
-    local_point += PhysicalOffset(ScrolledContentOffset());
+  PhysicalOffset local_point =
+      hit_test_location.Point() - accumulated_offset -
+      inner_editor->GetLayoutObject()->LocalToAncestorPoint(PhysicalOffset(),
+                                                            this);
   result.SetNodeAndPosition(inner_editor, local_point);
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_text_control_test.cc b/third_party/blink/renderer/core/layout/layout_text_control_test.cc
index 8e6066e..4645cb1 100644
--- a/third_party/blink/renderer/core/layout/layout_text_control_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_text_control_test.cc
@@ -101,6 +101,21 @@
   EXPECT_FALSE(selectedText->ShouldInvalidateSelection());
 }
 
+TEST_F(LayoutTextControlTest, HitTestSearchInput) {
+  SetBodyInnerHTML(R"HTML(
+    <input id="input" type="search"
+           style="border-width: 20px; font-size: 30px; padding: 0">
+  )HTML");
+
+  auto* input = GetHTMLInputElementById("input");
+  HitTestResult result;
+  HitTestLocation location(PhysicalOffset(40, 30));
+  EXPECT_TRUE(input->GetLayoutObject()->HitTestAllPhases(result, location,
+                                                         PhysicalOffset()));
+  EXPECT_EQ(PhysicalOffset(20, 10), result.LocalPoint());
+  EXPECT_EQ(input->InnerEditorElement(), result.InnerElement());
+}
+
 }  // anonymous namespace
 
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/messaging/blink_transferable_message.cc b/third_party/blink/renderer/core/messaging/blink_transferable_message.cc
index 004317075..5416a89 100644
--- a/third_party/blink/renderer/core/messaging/blink_transferable_message.cc
+++ b/third_party/blink/renderer/core/messaging/blink_transferable_message.cc
@@ -64,8 +64,8 @@
         BlobDataHandle::Create(
             String::FromUTF8(blob->uuid), String::FromUTF8(blob->content_type),
             blob->size,
-            mojom::blink::BlobPtrInfo(blob->blob.PassHandle(),
-                                      mojom::Blob::Version_)));
+            mojo::PendingRemote<mojom::blink::Blob>(blob->blob.PassPipe(),
+                                                    mojom::Blob::Version_)));
   }
   result.sender_stack_trace_id = v8_inspector::V8StackTraceId(
       static_cast<uintptr_t>(message.stack_trace_id),
diff --git a/third_party/blink/renderer/core/svg/svg_element.cc b/third_party/blink/renderer/core/svg/svg_element.cc
index ced9ad4..6ca79db 100644
--- a/third_party/blink/renderer/core/svg/svg_element.cc
+++ b/third_party/blink/renderer/core/svg/svg_element.cc
@@ -1066,10 +1066,8 @@
     return Element::EnsureComputedStyle(pseudo_element_specifier);
 
   const ComputedStyle* parent_style = nullptr;
-  if (Element* parent = ParentOrShadowHostElement()) {
-    if (LayoutObject* layout_object = parent->GetLayoutObject())
-      parent_style = layout_object->Style();
-  }
+  if (ContainerNode* parent = LayoutTreeBuilderTraversal::Parent(*this))
+    parent_style = parent->EnsureComputedStyle();
 
   return SvgRareData()->OverrideComputedStyle(this, parent_style);
 }
diff --git a/third_party/blink/renderer/devtools/front_end/cookie_table/CookiesTable.js b/third_party/blink/renderer/devtools/front_end/cookie_table/CookiesTable.js
index 0052ded..298d756 100644
--- a/third_party/blink/renderer/devtools/front_end/cookie_table/CookiesTable.js
+++ b/third_party/blink/renderer/devtools/front_end/cookie_table/CookiesTable.js
@@ -33,12 +33,13 @@
  */
 CookieTable.CookiesTable = class extends UI.VBox {
   /**
+   * @param {boolean=} renderInline
    * @param {function(!SDK.Cookie, ?SDK.Cookie): !Promise<boolean>=} saveCallback
    * @param {function()=} refreshCallback
    * @param {function()=} selectedCallback
    * @param {function(!SDK.Cookie, function())=} deleteCallback
    */
-  constructor(saveCallback, refreshCallback, selectedCallback, deleteCallback) {
+  constructor(renderInline, saveCallback, refreshCallback, selectedCallback, deleteCallback) {
     super();
 
     this._saveCallback = saveCallback;
@@ -49,7 +50,7 @@
 
     const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
       {
-        id: 'name',
+        id: SDK.Cookie.Attributes.Name,
         title: ls`Name`,
         sortable: true,
         disclosure: editable,
@@ -58,14 +59,39 @@
         weight: 24,
         editable: editable
       },
-      {id: 'value', title: ls`Value`, sortable: true, longText: true, weight: 34, editable: editable},
-      {id: 'domain', title: ls`Domain`, sortable: true, weight: 7, editable: editable},
-      {id: 'path', title: ls`Path`, sortable: true, weight: 7, editable: editable},
-      {id: 'expires', title: ls`Expires / Max-Age`, sortable: true, weight: 7, editable: editable},
-      {id: 'size', title: ls`Size`, sortable: true, align: DataGrid.DataGrid.Align.Right, weight: 7},
-      {id: 'httpOnly', title: ls`HttpOnly`, sortable: true, align: DataGrid.DataGrid.Align.Center, weight: 7},
-      {id: 'secure', title: ls`Secure`, sortable: true, align: DataGrid.DataGrid.Align.Center, weight: 7},
-      {id: 'sameSite', title: ls`SameSite`, sortable: true, align: DataGrid.DataGrid.Align.Center, weight: 7}
+      {
+        id: SDK.Cookie.Attributes.Value,
+        title: ls`Value`,
+        sortable: true,
+        longText: true,
+        weight: 34,
+        editable: editable
+      },
+      {id: SDK.Cookie.Attributes.Domain, title: ls`Domain`, sortable: true, weight: 7, editable: editable},
+      {id: SDK.Cookie.Attributes.Path, title: ls`Path`, sortable: true, weight: 7, editable: editable},
+      {id: SDK.Cookie.Attributes.Expires, title: ls`Expires / Max-Age`, sortable: true, weight: 7, editable: editable},
+      {
+        id: SDK.Cookie.Attributes.Size,
+        title: ls`Size`,
+        sortable: true,
+        align: DataGrid.DataGrid.Align.Right,
+        weight: 7
+      },
+      {
+        id: SDK.Cookie.Attributes.HttpOnly,
+        title: ls`HttpOnly`,
+        sortable: true,
+        align: DataGrid.DataGrid.Align.Center,
+        weight: 7
+      },
+      {
+        id: SDK.Cookie.Attributes.Secure,
+        title: ls`Secure`,
+        sortable: true,
+        align: DataGrid.DataGrid.Align.Center,
+        weight: 7
+      },
+      {id: SDK.Cookie.Attributes.SameSite, title: ls`SameSite`, sortable: true, weight: 7}
     ]);
 
     if (editable) {
@@ -75,9 +101,10 @@
       this._dataGrid = new DataGrid.DataGrid(columns);
     }
     this._dataGrid.setStriped(true);
-
     this._dataGrid.setName('cookiesTable');
     this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this._rebuildTable, this);
+    if (renderInline)
+      this._dataGrid.renderInline();
 
     if (selectedCallback)
       this._dataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, selectedCallback, this);
@@ -90,20 +117,26 @@
 
     /** @type {string} */
     this._cookieDomain = '';
+
+    /** @type {?Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>} */
+    this._cookieToBlockedReasons = null;
   }
 
   /**
    * @param {!Array.<!SDK.Cookie>} cookies
+   * @param {!Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>=} cookieToBlockedReasons
    */
-  setCookies(cookies) {
-    this.setCookieFolders([{cookies: cookies}]);
+  setCookies(cookies, cookieToBlockedReasons) {
+    this.setCookieFolders([{cookies: cookies}], cookieToBlockedReasons);
   }
 
   /**
    * @param {!Array.<!{folderName: ?string, cookies: ?Array.<!SDK.Cookie>}>} cookieFolders
+   * @param {!Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>=} cookieToBlockedReasons
    */
-  setCookieFolders(cookieFolders) {
+  setCookieFolders(cookieFolders, cookieToBlockedReasons) {
     this._data = cookieFolders;
+    this._cookieToBlockedReasons = cookieToBlockedReasons || null;
     this._rebuildTable();
   }
 
@@ -184,17 +217,17 @@
       const item = this._data[i];
       const selectedCookie = this._findSelectedCookie(selectionCookies, item.cookies);
       if (item.folderName) {
-        const groupData = {
-          name: item.folderName,
-          value: '',
-          domain: '',
-          path: '',
-          expires: '',
-          size: this._totalSize(item.cookies),
-          httpOnly: '',
-          secure: '',
-          sameSite: ''
-        };
+        const groupData = {};
+        groupData[SDK.Cookie.Attributes.Name] = item.folderName;
+        groupData[SDK.Cookie.Attributes.Value] = '';
+        groupData[SDK.Cookie.Attributes.Size] = this._totalSize(item.cookies);
+        groupData[SDK.Cookie.Attributes.Domain] = '';
+        groupData[SDK.Cookie.Attributes.Path] = '';
+        groupData[SDK.Cookie.Attributes.Expires] = '';
+        groupData[SDK.Cookie.Attributes.HttpOnly] = '';
+        groupData[SDK.Cookie.Attributes.Secure] = '';
+        groupData[SDK.Cookie.Attributes.SameSite] = '';
+
         const groupNode = new DataGrid.DataGridNode(groupData);
         groupNode.selectable = true;
         this._dataGrid.rootNode().appendChild(groupNode);
@@ -275,6 +308,7 @@
      * @param {string} property
      * @param {!SDK.Cookie} cookie1
      * @param {!SDK.Cookie} cookie2
+     * @return {number}
      */
     function compareTo(property, cookie1, cookie2) {
       return sortDirection * getValue(cookie1, property).compareTo(getValue(cookie2, property));
@@ -283,6 +317,7 @@
     /**
      * @param {!SDK.Cookie} cookie1
      * @param {!SDK.Cookie} cookie2
+     * @return {number}
      */
     function numberCompare(cookie1, cookie2) {
       return sortDirection * (cookie1.size() - cookie2.size());
@@ -291,6 +326,7 @@
     /**
      * @param {!SDK.Cookie} cookie1
      * @param {!SDK.Cookie} cookie2
+     * @return {number}
      */
     function expiresCompare(cookie1, cookie2) {
       if (cookie1.session() !== cookie2.session())
@@ -323,34 +359,37 @@
    */
   _createGridNode(cookie) {
     const data = {};
-    data.name = cookie.name();
-    data.value = cookie.value();
-    if (cookie.type() === SDK.Cookie.Type.Request) {
-      data.domain = Common.UIString('N/A');
-      data.path = Common.UIString('N/A');
-      data.expires = Common.UIString('N/A');
-    } else {
-      data.domain = cookie.domain() || '';
-      data.path = cookie.path() || '';
-      if (cookie.maxAge()) {
-        data.expires = Number.secondsToString(parseInt(cookie.maxAge(), 10));
-      } else if (cookie.expires()) {
-        if (cookie.expires() < 0)
-          data.expires = CookieTable.CookiesTable._expiresSessionValue;
-        else
-          data.expires = new Date(cookie.expires()).toISOString();
-      } else {
-        data.expires = CookieTable.CookiesTable._expiresSessionValue;
-      }
-    }
-    data.size = cookie.size();
-    const checkmark = '\u2713';
-    data.httpOnly = (cookie.httpOnly() ? checkmark : '');
-    data.secure = (cookie.secure() ? checkmark : '');
-    data.sameSite = cookie.sameSite() || '';
+    data[SDK.Cookie.Attributes.Name] = cookie.name();
+    data[SDK.Cookie.Attributes.Value] = cookie.value();
 
-    const node = new DataGrid.DataGridNode(data);
-    node.cookie = cookie;
+    if (cookie.type() === SDK.Cookie.Type.Request) {
+      data[SDK.Cookie.Attributes.Domain] = cookie.domain() ? cookie.domain() : ls`N/A`;
+      data[SDK.Cookie.Attributes.Path] = cookie.path() ? cookie.path() : ls`N/A`;
+    } else {
+      data[SDK.Cookie.Attributes.Domain] = cookie.domain() || '';
+      data[SDK.Cookie.Attributes.Path] = cookie.path() || '';
+    }
+
+    if (cookie.maxAge()) {
+      data[SDK.Cookie.Attributes.Expires] = Number.secondsToString(parseInt(cookie.maxAge(), 10));
+    } else if (cookie.expires()) {
+      if (cookie.expires() < 0)
+        data[SDK.Cookie.Attributes.Expires] = CookieTable.CookiesTable._expiresSessionValue;
+      else
+        data[SDK.Cookie.Attributes.Expires] = new Date(cookie.expires()).toISOString();
+    } else {
+      data[SDK.Cookie.Attributes.Expires] =
+          cookie.type() === SDK.Cookie.Type.Request ? ls`N/A` : CookieTable.CookiesTable._expiresSessionValue;
+    }
+
+    data[SDK.Cookie.Attributes.Size] = cookie.size();
+    const checkmark = '\u2713';
+    data[SDK.Cookie.Attributes.HttpOnly] = (cookie.httpOnly() ? checkmark : '');
+    data[SDK.Cookie.Attributes.Secure] = (cookie.secure() ? checkmark : '');
+    data[SDK.Cookie.Attributes.SameSite] = cookie.sameSite() || '';
+
+    const node = new CookieTable.DataGridNode(
+        data, cookie, this._cookieToBlockedReasons ? this._cookieToBlockedReasons.get(cookie) : null);
     node.selectable = true;
     return node;
   }
@@ -382,16 +421,16 @@
    * @param {!DataGrid.DataGridNode} node
    */
   _setDefaults(node) {
-    if (node.data.name === null)
-      node.data.name = '';
-    if (node.data.value === null)
-      node.data.value = '';
-    if (node.data.domain === null)
-      node.data.domain = this._cookieDomain;
-    if (node.data.path === null)
-      node.data.path = '/';
-    if (node.data.expires === null)
-      node.data.expires = CookieTable.CookiesTable._expiresSessionValue;
+    if (node.data[SDK.Cookie.Attributes.Name] === null)
+      node.data[SDK.Cookie.Attributes.Name] = '';
+    if (node.data[SDK.Cookie.Attributes.Value] === null)
+      node.data[SDK.Cookie.Attributes.Value] = '';
+    if (node.data[SDK.Cookie.Attributes.Domain] === null)
+      node.data[SDK.Cookie.Attributes.Domain] = this._cookieDomain;
+    if (node.data[SDK.Cookie.Attributes.Path] === null)
+      node.data[SDK.Cookie.Attributes.Path] = '/';
+    if (node.data[SDK.Cookie.Attributes.Expires] === null)
+      node.data[SDK.Cookie.Attributes.Expires] = CookieTable.CookiesTable._expiresSessionValue;
   }
 
   /**
@@ -410,22 +449,22 @@
   }
 
   /**
-   * @param {!Object.<string, *>} data
+   * @param {!Object.<string, string>} data
    * @returns {!SDK.Cookie}
    */
   _createCookieFromData(data) {
-    const cookie = new SDK.Cookie(data.name, data.value, null);
-    cookie.addAttribute('domain', data.domain);
-    cookie.addAttribute('path', data.path);
+    const cookie = new SDK.Cookie(data[SDK.Cookie.Attributes.Name], data[SDK.Cookie.Attributes.Value], null);
+    cookie.addAttribute(SDK.Cookie.Attributes.Domain, data[SDK.Cookie.Attributes.Domain]);
+    cookie.addAttribute(SDK.Cookie.Attributes.Path, data[SDK.Cookie.Attributes.Path]);
     if (data.expires && data.expires !== CookieTable.CookiesTable._expiresSessionValue)
-      cookie.addAttribute('expires', (new Date(data.expires)).toUTCString());
-    if (data.httpOnly)
-      cookie.addAttribute('httpOnly');
-    if (data.secure)
-      cookie.addAttribute('secure');
-    if (data.sameSite)
-      cookie.addAttribute('sameSite', data.sameSite);
-    cookie.setSize(data.name.length + data.value.length);
+      cookie.addAttribute(SDK.Cookie.Attributes.Expires, (new Date(data[SDK.Cookie.Attributes.Expires])).toUTCString());
+    if (data[SDK.Cookie.Attributes.HttpOnly])
+      cookie.addAttribute(SDK.Cookie.Attributes.HttpOnly);
+    if (data[SDK.Cookie.Attributes.Secure])
+      cookie.addAttribute(SDK.Cookie.Attributes.Secure);
+    if (data[SDK.Cookie.Attributes.SameSite])
+      cookie.addAttribute(SDK.Cookie.Attributes.SameSite, data[SDK.Cookie.Attributes.SameSite]);
+    cookie.setSize(data[SDK.Cookie.Attributes.Name].length + data[SDK.Cookie.Attributes.Value].length);
     return cookie;
   }
 
@@ -472,5 +511,63 @@
   }
 };
 
+CookieTable.DataGridNode = class extends DataGrid.DataGridNode {
+  /**
+   * @param {!Object<string, *>} data
+   * @param {!SDK.Cookie} cookie
+   * @param {?Array<!CookieTable.BlockedReason>} blockedReasons
+   */
+  constructor(data, cookie, blockedReasons) {
+    super(data);
+    this.cookie = cookie;
+    this._blockedReasons = blockedReasons;
+  }
+
+  /**
+   * @override
+   * @param {!Element} element
+   */
+  createCells(element) {
+    super.createCells(element);
+    if (this._blockedReasons && this._blockedReasons.length)
+      element.classList.add('flagged-cookie-attribute-row');
+  }
+
+  /**
+   * @override
+   * @param {string} columnId
+   * @return {!Element}
+   */
+  createCell(columnId) {
+    const cell = super.createCell(columnId);
+    cell.title = cell.textContent;
+
+    let blockedReasonString = '';
+    if (this._blockedReasons) {
+      for (const blockedReason of this._blockedReasons) {
+        const attributeMatches = blockedReason.attribute === /** @type {!SDK.Cookie.Attributes} */ (columnId);
+        const useNameColumn = !blockedReason.attribute && columnId === SDK.Cookie.Attribute.Name;
+        if (attributeMatches || useNameColumn) {
+          if (blockedReasonString)
+            blockedReasonString += '\n';
+          blockedReasonString += blockedReason.uiString;
+        }
+      }
+    }
+
+    if (blockedReasonString) {
+      const infoElement = UI.Icon.create('smallicon-info', 'cookie-warning-icon');
+      infoElement.title = blockedReasonString;
+      cell.insertBefore(infoElement, cell.firstChild);
+      cell.classList.add('flagged-cookie-attribute-cell');
+    }
+
+    return cell;
+  }
+};
+
 /** @const */
 CookieTable.CookiesTable._expiresSessionValue = Common.UIString('Session');
+
+/** @typedef {!{uiString: string, attribute: ?SDK.Cookie.Attributes}} */
+CookieTable.BlockedReason;
diff --git a/third_party/blink/renderer/devtools/front_end/network/NetworkItemView.js b/third_party/blink/renderer/devtools/front_end/network/NetworkItemView.js
index 09955a6c..d2bf0da 100644
--- a/third_party/blink/renderer/devtools/front_end/network/NetworkItemView.js
+++ b/third_party/blink/renderer/devtools/front_end/network/NetworkItemView.js
@@ -35,6 +35,7 @@
    */
   constructor(request, calculator) {
     super();
+    this._request = request;
     this.element.classList.add('network-item-view');
 
     this._resourceViewTabSetting = Common.settings.createSetting('resourceViewTab', 'preview');
@@ -72,18 +73,12 @@
           Common.UIString('Raw response data'));
     }
 
-    if (request.requestCookies || request.responseCookies) {
-      this._cookiesView = new Network.RequestCookiesView(request);
-      this.appendTab(
-          Network.NetworkItemView.Tabs.Cookies, Common.UIString('Cookies'), this._cookiesView,
-          Common.UIString('Request and response cookies'));
-    }
-
     this.appendTab(
         Network.NetworkItemView.Tabs.Timing, Common.UIString('Timing'),
         new Network.RequestTimingView(request, calculator), Common.UIString('Request and response timeline'));
 
-    this._request = request;
+    /** @type {?Network.RequestCookiesView} */
+    this._cookiesView = null;
   }
 
   /**
@@ -91,10 +86,36 @@
    */
   wasShown() {
     super.wasShown();
+    this._request.addEventListener(
+        SDK.NetworkRequest.Events.RequestHeadersChanged, this._maybeAppendCookiesPanel, this);
+    this._request.addEventListener(
+        SDK.NetworkRequest.Events.ResponseHeadersChanged, this._maybeAppendCookiesPanel, this);
+    this._maybeAppendCookiesPanel();
     this._selectTab();
   }
 
   /**
+   * @override
+   */
+  willHide() {
+    this._request.removeEventListener(
+        SDK.NetworkRequest.Events.RequestHeadersChanged, this._maybeAppendCookiesPanel, this);
+    this._request.removeEventListener(
+        SDK.NetworkRequest.Events.ResponseHeadersChanged, this._maybeAppendCookiesPanel, this);
+  }
+
+  _maybeAppendCookiesPanel() {
+    const cookiesPresent = this._request.requestCookies || this._request.responseCookies;
+    console.assert(cookiesPresent || !this._cookiesView, 'Cookies were introduced in headers and then removed!');
+    if (cookiesPresent && !this._cookiesView) {
+      this._cookiesView = new Network.RequestCookiesView(this._request);
+      this.appendTab(
+          Network.NetworkItemView.Tabs.Cookies, Common.UIString('Cookies'), this._cookiesView,
+          Common.UIString('Request and response cookies'));
+    }
+  }
+
+  /**
    * @param {string=} tabId
    */
   _selectTab(tabId) {
diff --git a/third_party/blink/renderer/devtools/front_end/network/RequestCookiesView.js b/third_party/blink/renderer/devtools/front_end/network/RequestCookiesView.js
index fee8596..0bbcfef78 100644
--- a/third_party/blink/renderer/devtools/front_end/network/RequestCookiesView.js
+++ b/third_party/blink/renderer/devtools/front_end/network/RequestCookiesView.js
@@ -28,10 +28,7 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-/**
- * @unrestricted
- */
-Network.RequestCookiesView = class extends UI.VBox {
+Network.RequestCookiesView = class extends UI.Widget {
   /**
    * @param {!SDK.NetworkRequest} request
    */
@@ -40,56 +37,227 @@
     this.registerRequiredCSS('network/requestCookiesView.css');
     this.element.classList.add('request-cookies-view');
 
+    /** @type {!SDK.NetworkRequest} */
     this._request = request;
+    /** @type {?Array<!SDK.Cookie>} */
+    this._detailedRequestCookies = null;
+    this._showFilteredOutCookiesSetting =
+        Common.settings.createSetting('show-filtered-out-request-cookies', /* defaultValue */ false);
+
+    this._emptyWidget = new UI.EmptyWidget(Common.UIString('This request has no cookies.'));
+    this._emptyWidget.show(this.element);
+
+    this._requestCookiesTitle = this.element.createChild('div');
+    const titleText = this._requestCookiesTitle.createChild('span', 'request-cookies-title');
+    titleText.textContent = ls`Request Cookies`;
+    titleText.title = ls`Cookies that were sent to the server in the 'cookie' header of the request`;
+
+    const requestCookiesCheckbox = UI.SettingsUI.createSettingCheckbox(
+        ls`show filtered out request cookies`, this._showFilteredOutCookiesSetting,
+        /* omitParagraphElement */ true);
+    requestCookiesCheckbox.checkboxElement.addEventListener('change', () => {
+      this._refreshRequestCookiesView();
+    });
+    this._requestCookiesTitle.appendChild(requestCookiesCheckbox);
+
+    this._requestCookiesTable = new CookieTable.CookiesTable(/* renderInline */ true);
+    this._requestCookiesTable.contentElement.classList.add('cookie-table');
+    this._requestCookiesTable.show(this.element);
+
+    this._responseCookiesTitle = this.element.createChild('div', 'request-cookies-title');
+    this._responseCookiesTitle.textContent = ls`Response Cookies`;
+    this._responseCookiesTitle.title =
+        ls`Cookies that were received from the server in the 'set-cookie' header of the response`;
+
+    this._responseCookiesTable = new CookieTable.CookiesTable(/* renderInline */ true);
+    this._responseCookiesTable.contentElement.classList.add('cookie-table');
+    this._responseCookiesTable.show(this.element);
+
+    this._malformedResponseCookiesTitle = this.element.createChild('div', 'request-cookies-title');
+    this._malformedResponseCookiesTitle.textContent = ls`Malformed Response Cookies`;
+    this._malformedResponseCookiesTitle.title = ls
+    `Cookies that were received from the server in the 'set-cookie' header of the response but were malformed`;
+
+    this._malformedResponseCookiesList = this.element.createChild('div');
+  }
+
+  /**
+   * @return {!{requestCookies: !Array<!SDK.Cookie>, requestCookieToBlockedReasons: !Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>}}
+   */
+  _getRequestCookies() {
+    let requestCookies = [];
+    /** @type {!Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>} */
+    const requestCookieToBlockedReasons = new Map();
+
+    if (this._request.requestCookies) {
+      // request.requestCookies are generated from headers which are missing
+      // cookie attributes that we can fetch from the backend.
+      requestCookies = this._request.requestCookies.slice();
+      if (this._detailedRequestCookies) {
+        requestCookies = requestCookies.map(cookie => {
+          for (const detailedCookie of (this._detailedRequestCookies || [])) {
+            if (detailedCookie.name() === cookie.name() && detailedCookie.value() === cookie.value())
+              return detailedCookie;
+          }
+          return cookie;
+        });
+      }
+
+      if (this._showFilteredOutCookiesSetting.get()) {
+        const blockedRequestCookies = this._request.blockedRequestCookies().slice();
+        for (const blockedCookie of blockedRequestCookies) {
+          requestCookieToBlockedReasons.set(
+              blockedCookie.cookie, [{
+                attribute: SDK.NetworkRequest.cookieBlockedReasonToAttribute(blockedCookie.blockedReason),
+                uiString: SDK.NetworkRequest.cookieBlockedReasonToUiString(blockedCookie.blockedReason)
+              }]);
+          requestCookies.push(blockedCookie.cookie);
+        }
+      }
+
+      if (!this._detailedRequestCookies) {
+        const networkManager = SDK.NetworkManager.forRequest(this._request);
+        if (networkManager) {
+          const cookieModel = networkManager.target().model(SDK.CookieModel);
+          if (cookieModel) {
+            cookieModel.getCookies([this._request.url()]).then(cookies => {
+              this._detailedRequestCookies = cookies;
+              this._refreshRequestCookiesView();
+            });
+          }
+        }
+      }
+    }
+
+    return {requestCookies, requestCookieToBlockedReasons};
+  }
+
+  /**
+   * @return {!{responseCookies: !Array<!SDK.Cookie>, responseCookieToBlockedReasons: !Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>, malformedResponseCookies: !Array<!SDK.NetworkRequest.BlockedSetCookieWithReason>}}
+   */
+  _getResponseCookies() {
+    /** @type {!Array<!SDK.Cookie>} */
+    let responseCookies = [];
+    /** @type {!Map<!SDK.Cookie, !Array<!CookieTable.BlockedReason>>} */
+    const responseCookieToBlockedReasons = new Map();
+    /** @type {!Array<!SDK.NetworkRequest.BlockedSetCookieWithReason>} */
+    const malformedResponseCookies = [];
+
+    if (this._request.responseCookies) {
+      const blockedCookieLines = this._request.blockedResponseCookies().map(blockedCookie => blockedCookie.cookieLine);
+      responseCookies = this._request.responseCookies.filter(cookie => {
+        // remove the regular cookies that would overlap with blocked cookies
+        if (blockedCookieLines.includes(cookie.getCookieLine())) {
+          blockedCookieLines.remove(cookie.getCookieLine(), /* firstOnly */ true);
+          return false;
+        }
+        return true;
+      });
+
+      for (const blockedCookie of this._request.blockedResponseCookies()) {
+        const parsedCookies = SDK.CookieParser.parseSetCookie(blockedCookie.cookieLine);
+        if (!parsedCookies.length ||
+            blockedCookie.blockedReason === Protocol.Network.SetCookieBlockedReason.SyntaxError) {
+          malformedResponseCookies.push(blockedCookie);
+          continue;
+        }
+
+        const cookie = parsedCookies[0];
+        responseCookieToBlockedReasons.set(
+            cookie, [{
+              attribute: SDK.NetworkRequest.setCookieBlockedReasonToAttribute(blockedCookie.blockedReason),
+              uiString: SDK.NetworkRequest.setCookieBlockedReasonToUiString(blockedCookie.blockedReason)
+            }]);
+        responseCookies.push(cookie);
+      }
+    }
+
+    return {responseCookies, responseCookieToBlockedReasons, malformedResponseCookies};
+  }
+
+  _refreshRequestCookiesView() {
+    if (!this.isShowing())
+      return;
+
+    const {requestCookies, requestCookieToBlockedReasons} = this._getRequestCookies();
+    const {responseCookies, responseCookieToBlockedReasons, malformedResponseCookies} = this._getResponseCookies();
+
+    if (requestCookies.length) {
+      this._requestCookiesTitle.classList.remove('hidden');
+      this._requestCookiesTable.showWidget();
+      this._requestCookiesTable.setCookies(requestCookies, requestCookieToBlockedReasons);
+    } else {
+      this._requestCookiesTitle.classList.add('hidden');
+      this._requestCookiesTable.hideWidget();
+    }
+
+    if (responseCookies.length) {
+      this._responseCookiesTitle.classList.remove('hidden');
+      this._responseCookiesTable.showWidget();
+      this._responseCookiesTable.setCookies(responseCookies, responseCookieToBlockedReasons);
+    } else {
+      this._responseCookiesTitle.classList.add('hidden');
+      this._responseCookiesTable.hideWidget();
+    }
+
+    if (malformedResponseCookies.length) {
+      this._malformedResponseCookiesTitle.classList.remove('hidden');
+      this._malformedResponseCookiesList.classList.remove('hidden');
+
+      this._malformedResponseCookiesList.removeChildren();
+      for (const malformedCookie of malformedResponseCookies) {
+        const listItem = this._malformedResponseCookiesList.createChild('span', 'cookie-line source-code');
+        const icon = UI.Icon.create('smallicon-error', '');
+        listItem.appendChild(icon);
+        listItem.createTextChild(malformedCookie.cookieLine);
+        listItem.title = SDK.NetworkRequest.setCookieBlockedReasonToUiString(malformedCookie.blockedReason);
+      }
+    } else {
+      this._malformedResponseCookiesTitle.classList.add('hidden');
+      this._malformedResponseCookiesList.classList.add('hidden');
+    }
   }
 
   /**
    * @override
    */
   wasShown() {
-    this._request.addEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this._refreshCookies, this);
-    this._request.addEventListener(SDK.NetworkRequest.Events.ResponseHeadersChanged, this._refreshCookies, this);
+    this._request.addEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this._cookiesUpdated, this);
+    this._request.addEventListener(SDK.NetworkRequest.Events.ResponseHeadersChanged, this._cookiesUpdated, this);
 
-    if (!this._gotCookies) {
-      if (!this._emptyWidget) {
-        this._emptyWidget = new UI.EmptyWidget(Common.UIString('This request has no cookies.'));
-        this._emptyWidget.show(this.element);
-      }
-      return;
+    if (this._gotCookies()) {
+      this._refreshRequestCookiesView();
+      this._emptyWidget.hideWidget();
+    } else {
+      this._emptyWidget.showWidget();
     }
-
-    if (!this._cookiesTable)
-      this._buildCookiesTable();
   }
 
   /**
    * @override
    */
   willHide() {
-    this._request.removeEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this._refreshCookies, this);
-    this._request.removeEventListener(SDK.NetworkRequest.Events.ResponseHeadersChanged, this._refreshCookies, this);
+    this._request.removeEventListener(SDK.NetworkRequest.Events.RequestHeadersChanged, this._cookiesUpdated, this);
+    this._request.removeEventListener(SDK.NetworkRequest.Events.ResponseHeadersChanged, this._cookiesUpdated, this);
   }
 
-  get _gotCookies() {
-    return (this._request.requestCookies && this._request.requestCookies.length) ||
-        (this._request.responseCookies && this._request.responseCookies.length);
+  /**
+   * @return {boolean}
+   */
+  _gotCookies() {
+    return !!(this._request.requestCookies && this._request.requestCookies.length) ||
+        !!(this._request.responseCookies && this._request.responseCookies.length);
   }
 
-  _buildCookiesTable() {
-    this.detachChildWidgets();
-
-    this._cookiesTable = new CookieTable.CookiesTable();
-    this._cookiesTable.setCookieFolders([
-      {folderName: Common.UIString('Request Cookies'), cookies: this._request.requestCookies},
-      {folderName: Common.UIString('Response Cookies'), cookies: this._request.responseCookies}
-    ]);
-    this._cookiesTable.show(this.element);
-  }
-
-  _refreshCookies() {
-    delete this._cookiesTable;
-    if (!this._gotCookies || !this.isShowing())
+  _cookiesUpdated() {
+    if (!this.isShowing())
       return;
-    this._buildCookiesTable();
+
+    if (this._gotCookies()) {
+      this._refreshRequestCookiesView();
+      this._emptyWidget.hideWidget();
+    } else {
+      this._emptyWidget.showWidget();
+    }
   }
 };
diff --git a/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp b/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp
index df07c9a2..5c131ff7 100644
--- a/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp
+++ b/third_party/blink/renderer/devtools/front_end/network/network_strings.grdp
@@ -42,6 +42,9 @@
   <message name="IDS_DEVTOOLS_0f591b1d87c91e77d4fa11dc6e44288c" desc="Text in Network Item View of the Network panel">
     Request and response timeline
   </message>
+  <message name="IDS_DEVTOOLS_100b560e280fea6e7d5ab12179d3f0e2" desc="Tooltip to explain what malformed response cookies are">
+    Cookies that were received from the server in the &apos;set-cookie&apos; header of the response but were malformed
+  </message>
   <message name="IDS_DEVTOOLS_117068a6e55846888cbb60978fe82ca2" desc="Text in Request Timing View of the Network panel">
     Stalled
   </message>
@@ -294,6 +297,9 @@
   <message name="IDS_DEVTOOLS_6dd7482f131dc036ea08395a7f3f5e08" desc="Other user agent element placeholder in Network Config View of the Network panel">
     Enter a custom user agent
   </message>
+  <message name="IDS_DEVTOOLS_6dec00f9ae08edb5af58e6efb699dba0" desc="Label for showing request cookies that were not actually sent">
+    show filtered out request cookies
+  </message>
   <message name="IDS_DEVTOOLS_6e68a529a38966508d348e9f65d7ea31" desc="Title of an action in the network tool to toggle recording">
     Record network log
   </message>
@@ -585,6 +591,9 @@
   <message name="IDS_DEVTOOLS_c02504bf83e5cc0d2f582f189a804aef" desc="Text in Request Cookies View of the Network panel">
     Request Cookies
   </message>
+  <message name="IDS_DEVTOOLS_c0c7145bdb8a2311e11528eaacf7f41d" desc="Tooltip to explain what request cookies are">
+    Cookies that were sent to the server in the &apos;cookie&apos; header of the request
+  </message>
   <message name="IDS_DEVTOOLS_c2cc7082a89c1ad6631a2f66af5f00c0" desc="Text in Network Log View Columns of the Network panel">
     Connection
   </message>
@@ -618,6 +627,9 @@
   <message name="IDS_DEVTOOLS_cc0af601bfd673427a8abb171f62c707" desc="Reason in Network Data Grid Node of the Network panel">
     content-type
   </message>
+  <message name="IDS_DEVTOOLS_ce9d64b8fde226fd9e5fa4c0947c7ee0" desc="Tooltip to explain what response cookies are">
+    Cookies that were received from the server in the &apos;set-cookie&apos; header of the response
+  </message>
   <message name="IDS_DEVTOOLS_cebd9a3a94f022d3600e0f81a9aa2060" desc="Text in Network Log View of the Network panel">
     <ph name="NUMBER_BYTESTOSTRING_SELECTEDRESOURCESIZE_">$1s<ex>40MB</ex></ph> / <ph name="NUMBER_BYTESTOSTRING_RESOURCESIZE_">$2s<ex>50MB</ex></ph> resources
   </message>
@@ -648,6 +660,9 @@
   <message name="IDS_DEVTOOLS_d82a834165b99c4ea0969316296a2bc2" desc="Text in Network Log View Columns of the Network panel">
     Vary
   </message>
+  <message name="IDS_DEVTOOLS_d8e381f360a750d29368d3ec51346006" desc="Label for response cookies with invalid syntax">
+    Malformed Response Cookies
+  </message>
   <message name="IDS_DEVTOOLS_db67b2de0114bd82fe1383aa067fa6b2" desc="A context menu item in the Network Log View of the Network panel">
     Unblock <ph name="CROPPEDURL">$1s<ex>example.com</ex></ph>
   </message>
@@ -789,4 +804,4 @@
   <message name="IDS_DEVTOOLS_fe1bc3eb2f3e1a9a90b5401eb6baa5b9" desc="Text in Network Log View of the Network panel">
     Copy all as cURL
   </message>
-</grit-part>
\ No newline at end of file
+</grit-part>
diff --git a/third_party/blink/renderer/devtools/front_end/network/requestCookiesView.css b/third_party/blink/renderer/devtools/front_end/network/requestCookiesView.css
index d771b1a..a8a4b30 100644
--- a/third_party/blink/renderer/devtools/front_end/network/requestCookiesView.css
+++ b/third_party/blink/renderer/devtools/front_end/network/requestCookiesView.css
@@ -5,17 +5,42 @@
  */
 
 .request-cookies-view {
-    display: flex;
     overflow: auto;
-    margin: 12px;
+    padding: 12px;
     height: 100%;
 }
 
-.request-cookies-view .data-grid {
-    flex: auto;
-    height: 100%;
-}
-
-.request-cookies-view .data-grid .row-group {
+.request-cookies-view .request-cookies-title {
+    font-size: 12px;
     font-weight: bold;
+    margin-right: 30px;
+    color: rgb(97, 97, 97);
+}
+
+.request-cookies-view .cookie-line {
+    margin-top: 6px;
+    display: inline-block;
+}
+
+.request-cookies-view .cookie-table {
+    margin-top: 6px;
+    margin-bottom: 16px;
+    flex: none;
+}
+
+.request-cookies-view .cookie-table .flagged-cookie-row {
+    background-color: yellow;
+}
+
+td.flagged-cookie-attribute-cell .cookie-warning-icon {
+    margin-right: 4px;
+    filter: grayscale();
+}
+
+.request-cookies-view tr.revealed.data-grid-data-grid-node.flagged-cookie-attribute-row:not(.selected):nth-child(odd) {
+    background-color: hsl(51, 85%, 80%);
+}
+
+.request-cookies-view tr.revealed.data-grid-data-grid-node.flagged-cookie-attribute-row:not(.selected):nth-child(even) {
+    background-color: rgb(255, 240, 155);
 }
diff --git a/third_party/blink/renderer/devtools/front_end/resources/CookieItemsView.js b/third_party/blink/renderer/devtools/front_end/resources/CookieItemsView.js
index fcb7abe..b3f2338 100644
--- a/third_party/blink/renderer/devtools/front_end/resources/CookieItemsView.js
+++ b/third_party/blink/renderer/devtools/front_end/resources/CookieItemsView.js
@@ -92,10 +92,8 @@
 
     if (!this._cookiesTable) {
       this._cookiesTable = new CookieTable.CookiesTable(
-          this._saveCookie.bind(this),
-          this.refreshItems.bind(this),
-          () => this.setCanDeleteSelected(!!this._cookiesTable.selectedCookie()),
-          this._deleteCookie.bind(this));
+          /* renderInline */ false, this._saveCookie.bind(this), this.refreshItems.bind(this),
+          () => this.setCanDeleteSelected(!!this._cookiesTable.selectedCookie()), this._deleteCookie.bind(this));
     }
 
     const parsedURL = this._cookieDomain.asParsedURL();
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/CookieModel.js b/third_party/blink/renderer/devtools/front_end/sdk/CookieModel.js
index 44e6823..ea23dd0 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/CookieModel.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/CookieModel.js
@@ -11,27 +11,6 @@
   }
 
   /**
-   * @param {!Protocol.Network.Cookie} protocolCookie
-   * @return {!SDK.Cookie}
-   */
-  static _parseProtocolCookie(protocolCookie) {
-    const cookie = new SDK.Cookie(protocolCookie.name, protocolCookie.value, null);
-    cookie.addAttribute('domain', protocolCookie['domain']);
-    cookie.addAttribute('path', protocolCookie['path']);
-    cookie.addAttribute('port', protocolCookie['port']);
-    if (protocolCookie['expires'])
-      cookie.addAttribute('expires', protocolCookie['expires'] * 1000);
-    if (protocolCookie['httpOnly'])
-      cookie.addAttribute('httpOnly');
-    if (protocolCookie['secure'])
-      cookie.addAttribute('secure');
-    if (protocolCookie['sameSite'])
-      cookie.addAttribute('sameSite', protocolCookie['sameSite']);
-    cookie.setSize(protocolCookie['size']);
-    return cookie;
-  }
-
-  /**
    * @param {!SDK.Cookie} cookie
    * @param {string} resourceURL
    * @return {boolean}
@@ -63,7 +42,7 @@
    */
   getCookies(urls) {
     return this.target().networkAgent().getCookies(urls).then(
-        cookies => (cookies || []).map(cookie => SDK.CookieModel._parseProtocolCookie(cookie)));
+        cookies => (cookies || []).map(cookie => SDK.Cookie.fromProtocolCookie(cookie)));
   }
 
   /**
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/CookieParser.js b/third_party/blink/renderer/devtools/front_end/sdk/CookieParser.js
index f5f0d76e..a18fd52 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/CookieParser.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/CookieParser.js
@@ -112,14 +112,18 @@
       return false;
     this._cookies = [];
     this._lastCookie = null;
+    this._lastCookieLine = '';
     this._originalInputLength = this._input.length;
     return true;
   }
 
   _flushCookie() {
-    if (this._lastCookie)
+    if (this._lastCookie) {
       this._lastCookie.setSize(this._originalInputLength - this._input.length - this._lastCookiePosition);
+      this._lastCookie._setCookieLine(this._lastCookieLine.replace('\n', ''));
+    }
     this._lastCookie = null;
+    this._lastCookieLine = '';
   }
 
   /**
@@ -141,6 +145,7 @@
 
     const result = new SDK.CookieParser.KeyValue(
         keyValueMatch[1], keyValueMatch[2] && keyValueMatch[2].trim(), this._originalInputLength - this._input.length);
+    this._lastCookieLine += keyValueMatch[0];
     this._input = this._input.slice(keyValueMatch[0].length);
     return result;
   }
@@ -152,6 +157,7 @@
     const match = /^\s*[\n;]\s*/.exec(this._input);
     if (!match)
       return false;
+    this._lastCookieLine += match[0];
     this._input = this._input.slice(match[0].length);
     return match[0].match('\n') !== null;
   }
@@ -205,6 +211,29 @@
     this._type = type;
     this._attributes = {};
     this._size = 0;
+    /** @type {string|null} */
+    this._cookieLine = null;
+  }
+
+  /**
+   * @param {!Protocol.Network.Cookie} protocolCookie
+   * @return {!SDK.Cookie}
+   */
+  static fromProtocolCookie(protocolCookie) {
+    const cookie = new SDK.Cookie(protocolCookie.name, protocolCookie.value, null);
+    cookie.addAttribute('domain', protocolCookie['domain']);
+    cookie.addAttribute('path', protocolCookie['path']);
+    cookie.addAttribute('port', protocolCookie['port']);
+    if (protocolCookie['expires'])
+      cookie.addAttribute('expires', protocolCookie['expires'] * 1000);
+    if (protocolCookie['httpOnly'])
+      cookie.addAttribute('httpOnly');
+    if (protocolCookie['secure'])
+      cookie.addAttribute('secure');
+    if (protocolCookie['sameSite'])
+      cookie.addAttribute('sameSite', protocolCookie['sameSite']);
+    cookie.setSize(protocolCookie['size']);
+    return cookie;
   }
 
   /**
@@ -345,6 +374,20 @@
   addAttribute(key, value) {
     this._attributes[key.toLowerCase()] = value;
   }
+
+  /**
+   * @param {string} cookieLine
+   */
+  _setCookieLine(cookieLine) {
+    this._cookieLine = cookieLine;
+  }
+
+  /**
+   * @return {string|null}
+   */
+  getCookieLine() {
+    return this._cookieLine;
+  }
 };
 
 /**
@@ -354,3 +397,18 @@
   Request: 0,
   Response: 1
 };
+
+/**
+ * @enum {string}
+ */
+SDK.Cookie.Attributes = {
+  Name: 'name',
+  Value: 'value',
+  Size: 'size',
+  Domain: 'domain',
+  Path: 'path',
+  Expires: 'expires',
+  HttpOnly: 'httpOnly',
+  Secure: 'secure',
+  SameSite: 'sameSite',
+};
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/NetworkManager.js b/third_party/blink/renderer/devtools/front_end/sdk/NetworkManager.js
index 16f1d66..f8624fe 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/NetworkManager.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/NetworkManager.js
@@ -785,7 +785,12 @@
   requestWillBeSentExtraInfo(requestId, blockedCookies, headers) {
     /** @type {!SDK.NetworkRequest.ExtraRequestInfo} */
     const extraRequestInfo = {
-      blockedRequestCookies: blockedCookies,
+      blockedRequestCookies: blockedCookies.map(blockedCookie => {
+        return {
+          blockedReason: blockedCookie.blockedReason,
+          cookie: SDK.Cookie.fromProtocolCookie(blockedCookie.cookie)
+        };
+      }),
       requestHeaders: this._headersMapToHeadersArray(headers)
     };
     this._getExtraInfoBuilder(requestId).addRequestExtraInfo(extraRequestInfo);
@@ -801,7 +806,13 @@
   responseReceivedExtraInfo(requestId, blockedCookies, headers, headersText) {
     /** @type {!SDK.NetworkRequest.ExtraResponseInfo} */
     const extraResponseInfo = {
-      blockedResponseCookies: blockedCookies,
+      blockedResponseCookies: blockedCookies.map(blockedCookie => {
+        return {
+          blockedReason: blockedCookie.blockedReason,
+          cookieLine: blockedCookie.cookieLine,
+          cookie: blockedCookie.cookie ? SDK.Cookie.fromProtocolCookie(blockedCookie.cookie) : null
+        };
+      }),
       responseHeaders: this._headersMapToHeadersArray(headers),
       responseHeadersText: headersText
     };
diff --git a/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js b/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js
index 8ec9250..4fdec2a 100644
--- a/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js
+++ b/third_party/blink/renderer/devtools/front_end/sdk/NetworkRequest.js
@@ -117,6 +117,11 @@
     this._hasExtraRequestInfo = false;
     /** @type {boolean} */
     this._hasExtraResponseInfo = false;
+
+    /** @type {!Array<!SDK.NetworkRequest.BlockedCookieWithReason>} */
+    this._blockedRequestCookies = [];
+    /** @type {!Array<!SDK.NetworkRequest.BlockedSetCookieWithReason>} */
+    this._blockedResponseCookies = [];
   }
 
   /**
@@ -715,14 +720,14 @@
    * @return {string|undefined}
    */
   requestHeaderValue(headerName) {
-    if (headerName in this._requestHeaderValues)
+    if (this._requestHeaderValues[headerName])
       return this._requestHeaderValues[headerName];
     this._requestHeaderValues[headerName] = this._computeHeaderValue(this.requestHeaders(), headerName);
     return this._requestHeaderValues[headerName];
   }
 
   /**
-   * @return {!Array.<!SDK.Cookie>}
+   * @return {?Array.<!SDK.Cookie>}
    */
   get requestCookies() {
     if (!this._requestCookies)
@@ -1314,6 +1319,13 @@
   }
 
   /**
+   * @return {!Array<!SDK.NetworkRequest.BlockedCookieWithReason>}
+   */
+  blockedRequestCookies() {
+    return this._blockedRequestCookies;
+  }
+
+  /**
    * @param {!SDK.NetworkRequest.ExtraResponseInfo} extraResponseInfo
    */
   addExtraResponseInfo(extraResponseInfo) {
@@ -1346,6 +1358,13 @@
   hasExtraResponseInfo() {
     return this._hasExtraResponseInfo;
   }
+
+  /**
+   * @return {!Array<!SDK.NetworkRequest.BlockedSetCookieWithReason>}
+   */
+  blockedResponseCookies() {
+    return this._blockedResponseCookies;
+  }
 };
 
 /** @enum {symbol} */
@@ -1389,8 +1408,148 @@
 SDK.NetworkRequest.ContentData;
 
 /**
+ * @param {!Protocol.Network.CookieBlockedReason} blockedReason
+ * @return {string}
+ */
+SDK.NetworkRequest.cookieBlockedReasonToUiString = function(blockedReason) {
+  switch (blockedReason) {
+    case Protocol.Network.CookieBlockedReason.SecureOnly:
+      return ls`This cookie had the "Secure" attribute and the connection was not secure.`;
+    case Protocol.Network.CookieBlockedReason.NotOnPath:
+      return ls`This cookie's path was not within the request url's path.`;
+    case Protocol.Network.CookieBlockedReason.DomainMismatch:
+      return ls
+      `This cookie's domain is not configured to match the request url's domain, even though they share a common TLD+1 (TLD+1 of foo.bar.example.com is example.com).`;
+    case Protocol.Network.CookieBlockedReason.SameSiteStrict:
+      return ls
+      `This cookie had the "SameSite=Strict" attribute and the request was made on on a different site. This includes navigation requests initiated by other sites.`;
+    case Protocol.Network.CookieBlockedReason.SameSiteLax:
+      return ls
+      `This cookie had the "SameSite=Lax" attribute and the request was made on a different site. This does not include navigation requests initiated by other sites.`;
+    case Protocol.Network.CookieBlockedReason.SameSiteExtended:
+      return ls
+      `This cookie had the "SameSite=Extended" attribute and the request was made on a different site. The different site is outside of the cookie's trusted first-party set.`;
+    case Protocol.Network.CookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
+      return ls
+      `This cookie didn't specify a SameSite attribute when it was stored and was defaulted to "SameSite=Lax" and broke the same rules specified in the SameSiteLax value. The cookie had to have been set with "SameSite=None" to enable third-party usage.`;
+    case Protocol.Network.CookieBlockedReason.SameSiteNoneInsecure:
+      return ls
+      `This cookie had the "SameSite=None" attribute and the connection was not secure. Cookies without SameSite restrictions must be sent over a secure connection.`;
+    case Protocol.Network.CookieBlockedReason.UserPreferences:
+      return ls`This cookie was not sent due to user preferences.`;
+    case Protocol.Network.CookieBlockedReason.UnknownError:
+      return ls`An unknown error was encountered when trying to send this cookie.`;
+  }
+  return '';
+};
+
+/**
+ * @param {!Protocol.Network.SetCookieBlockedReason} blockedReason
+ * @return {string}
+ */
+SDK.NetworkRequest.setCookieBlockedReasonToUiString = function(blockedReason) {
+  switch (blockedReason) {
+    case Protocol.Network.SetCookieBlockedReason.SecureOnly:
+      return ls
+      `This set-cookie had the "Secure" attribute but was not received over a secure connection.`;
+    case Protocol.Network.SetCookieBlockedReason.SameSiteStrict:
+      return ls
+      `This set-cookie had the "SameSite=Strict" attribute but came from a cross-origin response. This includes navigation requests intitiated by other origins.`;
+    case Protocol.Network.SetCookieBlockedReason.SameSiteLax:
+      return ls`This set-cookie had the "SameSite=Lax" attribute but came from a cross-origin response.`;
+    case Protocol.Network.SetCookieBlockedReason.SameSiteExtended:
+      return ls`This set-cookie had the "SameSite=Extended" attribute but came from a cross-origin response.`;
+    case Protocol.Network.SetCookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
+      return ls
+      `This set-cookie didn't specify a "SameSite" attribute and was defaulted to "SameSite=Lax" and broke the same rules specified in the SameSiteLax value.`;
+    case Protocol.Network.SetCookieBlockedReason.SameSiteNoneInsecure:
+      return ls
+      `This set-cookie had the "SameSite=None" attribute but did not have the "Secure" attribute, which is required in order to use "SameSite=None".`;
+    case Protocol.Network.SetCookieBlockedReason.UserPreferences:
+      return ls`This set-cookie was not stored due to user preferences.`;
+    case Protocol.Network.SetCookieBlockedReason.SyntaxError:
+      return ls`This set-cookie had invalid syntax.`;
+    case Protocol.Network.SetCookieBlockedReason.SchemeNotSupported:
+      return ls`The scheme of this connection is not allowed to store cookies.`;
+    case Protocol.Network.SetCookieBlockedReason.OverwriteSecure:
+      return ls
+      `This set-cookie was not sent over a secure connection and would have overwritten a cookie with the Secure attribute.`;
+    case Protocol.Network.SetCookieBlockedReason.InvalidDomain:
+      return ls`This set-cookie's Domain attribute was invalid with regards to the current host url.`;
+    case Protocol.Network.SetCookieBlockedReason.InvalidPrefix:
+      return ls
+      `This set-cookie used the "__Secure-" or "__Host-" prefix in its name and broke the additional rules applied to cookies with these prefixes as defined in https://tools.ietf.org/html/draft-west-cookie-prefixes-05.`;
+    case Protocol.Network.SetCookieBlockedReason.UnknownError:
+      return ls`An unknown error was encountered when trying to store this cookie.`;
+  }
+  return '';
+};
+
+/**
+ * @param {!Protocol.Network.CookieBlockedReason} blockedReason
+ * @return {?SDK.Cookie.Attributes}
+ */
+SDK.NetworkRequest.cookieBlockedReasonToAttribute = function(blockedReason) {
+  switch (blockedReason) {
+    case Protocol.Network.CookieBlockedReason.SecureOnly:
+      return SDK.Cookie.Attributes.Secure;
+    case Protocol.Network.CookieBlockedReason.NotOnPath:
+      return SDK.Cookie.Attributes.Path;
+    case Protocol.Network.CookieBlockedReason.DomainMismatch:
+      return SDK.Cookie.Attributes.Domain;
+    case Protocol.Network.CookieBlockedReason.SameSiteStrict:
+    case Protocol.Network.CookieBlockedReason.SameSiteLax:
+    case Protocol.Network.CookieBlockedReason.SameSiteExtended:
+    case Protocol.Network.CookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
+    case Protocol.Network.CookieBlockedReason.SameSiteNoneInsecure:
+      return SDK.Cookie.Attributes.SameSite;
+    case Protocol.Network.CookieBlockedReason.UserPreferences:
+    case Protocol.Network.CookieBlockedReason.UnknownError:
+      return null;
+  }
+  return null;
+};
+
+/**
+ * @param {!Protocol.Network.SetCookieBlockedReason} blockedReason
+ * @return {?SDK.Cookie.Attributes}
+ */
+SDK.NetworkRequest.setCookieBlockedReasonToAttribute = function(blockedReason) {
+  switch (blockedReason) {
+    case Protocol.Network.SetCookieBlockedReason.SecureOnly:
+    case Protocol.Network.SetCookieBlockedReason.OverwriteSecure:
+      return SDK.Cookie.Attributes.Secure;
+    case Protocol.Network.SetCookieBlockedReason.SameSiteStrict:
+    case Protocol.Network.SetCookieBlockedReason.SameSiteLax:
+    case Protocol.Network.SetCookieBlockedReason.SameSiteExtended:
+    case Protocol.Network.SetCookieBlockedReason.SameSiteUnspecifiedTreatedAsLax:
+    case Protocol.Network.SetCookieBlockedReason.SameSiteNoneInsecure:
+      return SDK.Cookie.Attributes.SameSite;
+    case Protocol.Network.SetCookieBlockedReason.InvalidDomain:
+      return SDK.Cookie.Attributes.Domain;
+    case Protocol.Network.SetCookieBlockedReason.InvalidPrefix:
+      return SDK.Cookie.Attributes.Name;
+    case Protocol.Network.SetCookieBlockedReason.UserPreferences:
+    case Protocol.Network.SetCookieBlockedReason.SyntaxError:
+    case Protocol.Network.SetCookieBlockedReason.SchemeNotSupported:
+    case Protocol.Network.SetCookieBlockedReason.UnknownError:
+      return null;
+  }
+  return null;
+};
+
+
+/**
  * @typedef {!{
- *   blockedRequestCookies: !Array<!Protocol.Network.BlockedCookieWithReason>,
+ *   blockedReason: !Protocol.Network.CookieBlockedReason,
+ *   cookie: !SDK.Cookie
+ * }}
+ */
+SDK.NetworkRequest.BlockedCookieWithReason;
+
+/**
+ * @typedef {!{
+ *   blockedRequestCookies: !Array<!SDK.NetworkRequest.BlockedCookieWithReason>,
  *   requestHeaders: !Array<!SDK.NetworkRequest.NameValue>
  * }}
  */
@@ -1398,7 +1557,16 @@
 
 /**
  * @typedef {!{
- *   blockedResponseCookies: !Array<!Protocol.Network.BlockedSetCookieWithReason>,
+ *   blockedReason: !Protocol.Network.SetCookieBlockedReason,
+ *   cookieLine: string,
+ *   cookie: ?SDK.Cookie
+ * }}
+ */
+SDK.NetworkRequest.BlockedSetCookieWithReason;
+
+/**
+ * @typedef {!{
+ *   blockedResponseCookies: !Array<!SDK.NetworkRequest.BlockedSetCookieWithReason>,
  *   responseHeaders: !Array<!SDK.NetworkRequest.NameValue>,
  *   responseHeadersText: (string|undefined)
  * }}
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 ed3b138..2dc383e 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
@@ -1,5 +1,8 @@
 <?xml version="1.0" encoding="utf-8"?>
 <grit-part>
+  <message name="IDS_DEVTOOLS_021aa7730980fe55529e460ee1367179" desc="Tooltip to explain why a cookie was blocked">
+    This cookie had the &quot;SameSite=Extended&quot; attribute and the request was made on a different site. The different site is outside of the cookie&apos;s trusted first-party set.
+  </message>
   <message name="IDS_DEVTOOLS_07553a11db31a4433684be32cc4716e3" desc="Text in Network Manager">
     Cross-Origin Read Blocking (CORB) blocked cross-origin response <ph name="NETWORKREQUEST_URL__">$1s<ex>https://example.com</ex></ph> with MIME type <ph name="NETWORKREQUEST_MIMETYPE">$2s<ex>application</ex></ph>. See https://www.chromestatus.com/feature/5629709824032768 for more details.
   </message>
@@ -9,6 +12,9 @@
   <message name="IDS_DEVTOOLS_09cdd6a7321c64bae05b8cca859f1461" desc="Title of a setting under the Network category that can be invoked through the Command Menu">
     Enable cache
   </message>
+  <message name="IDS_DEVTOOLS_0b4e8f2008c602c781fee001c917dbf9" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie had invalid syntax.
+  </message>
   <message name="IDS_DEVTOOLS_0fe1f9158e2f164da1332501f9e65702" desc="Text in Console Model">
     Navigated to <ph name="EVENT_DATA_URL">$1s<ex>https://example.com</ex></ph>
   </message>
@@ -21,6 +27,9 @@
   <message name="IDS_DEVTOOLS_1985576209a186984f171efd81e91e3d" desc="Text in Network Log">
     &lt;anonymous&gt;
   </message>
+  <message name="IDS_DEVTOOLS_1a4157cb54ce78ee862a88ecd1a66916" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie used the &quot;__Secure-&quot; or &quot;__Host-&quot; prefix in its name and broke the additional rules applied to cookies with these prefixes as defined in https://tools.ietf.org/html/draft-west-cookie-prefixes-05.
+  </message>
   <message name="IDS_DEVTOOLS_21070fd87b02611a5426d726a70a5502" desc="Text in Server Timing">
     No value found for parameter &quot;<ph name="PARAMNAME">$1s<ex>https</ex></ph>&quot;.
   </message>
@@ -33,12 +42,21 @@
   <message name="IDS_DEVTOOLS_23e8e5daea7e9dc939ae4298a6f74e36" desc="Text in Server Timing">
     ServerTiming: <ph name="MSG">$1s<ex>Extraneous trailing characters.</ex></ph>
   </message>
+  <message name="IDS_DEVTOOLS_25121e2acd3c495847c5592661c2a2f9" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie&apos;s Domain attribute was invalid with regards to the current host url.
+  </message>
+  <message name="IDS_DEVTOOLS_27d7b4e470e9a00b0fc6cf26fb3c5100" desc="Tooltip to explain why a cookie was blocked">
+    This cookie&apos;s domain is not configured to match the request url&apos;s domain, even though they share a common TLD+1 (TLD+1 of foo.bar.example.com is example.com).
+  </message>
   <message name="IDS_DEVTOOLS_28e486be4b3bad598b6e9eaa34ebec76" desc="Text in Network Manager">
     Set-Cookie header is ignored in response from url: <ph name="RESPONSE_URL">$1s<ex>https://example.com</ex></ph>. Cookie length should be less than or equal to 4096 characters.
   </message>
   <message name="IDS_DEVTOOLS_2973958e77629e3a167a1a4c60dd5c5f" desc="Text in Network Manager">
     Slow 3G
   </message>
+  <message name="IDS_DEVTOOLS_2cc895b2288b4709e1620a1510322de0" desc="Tooltip to explain why a cookie was blocked">
+    This cookie was not sent due to user preferences.
+  </message>
   <message name="IDS_DEVTOOLS_3034e08ec0b14678ddb50284eef02ee4" desc="Title of a setting under the Console category that can be invoked through the Command Menu">
     Do not preserve log upon navigation
   </message>
@@ -57,6 +75,12 @@
   <message name="IDS_DEVTOOLS_3b563524fdb17b4a86590470d40bef74" desc="Text in DOMDebugger Model">
     Media
   </message>
+  <message name="IDS_DEVTOOLS_3c000cf06aef2d48a8bfe568e1d6ef65" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie was not sent over a secure connection and would have overwritten a cookie with the Secure attribute.
+  </message>
+  <message name="IDS_DEVTOOLS_3c2de12122c9b36f60cb989a6e0589b9" desc="Tooltip to explain why a cookie was blocked">
+    This cookie&apos;s path was not within the request url&apos;s path.
+  </message>
   <message name="IDS_DEVTOOLS_3d9320c6e796b65a6c91fcd64e27fa88" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Hide frames per second (FPS) meter
   </message>
@@ -84,6 +108,9 @@
   <message name="IDS_DEVTOOLS_4cc6684df7b4a92b1dec6fce3264fac8" desc="Text in Debugger Model">
     Global
   </message>
+  <message name="IDS_DEVTOOLS_50103f373f2d49a21139259d81f472d9" desc="Tooltip to explain why a cookie was blocked">
+    This cookie had the &quot;Secure&quot; attribute and the connection was not secure.
+  </message>
   <message name="IDS_DEVTOOLS_509820290d57f333403f490dde7316f4" desc="Text in Debugger Model">
     Local
   </message>
@@ -93,6 +120,9 @@
   <message name="IDS_DEVTOOLS_52da3e7fcf6abefc2a8807df4b759ef8" desc="Text in the Event Listener Breakpoints Panel of the JavaScript Debugger in the Sources Panel">
     setInterval fired
   </message>
+  <message name="IDS_DEVTOOLS_58c00b9a5a2a60122f4ac08e4610d9e2" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie had the &quot;SameSite=Extended&quot; attribute but came from a cross-origin response.
+  </message>
   <message name="IDS_DEVTOOLS_599eba19aa93a929cb8589f148b8a6c4" desc="A drop-down menu option to emulate css screen media type">
     screen
   </message>
@@ -105,12 +135,18 @@
   <message name="IDS_DEVTOOLS_61cf8510205077b6f5491d38cd44c0f7" desc="Text in DOMDebugger Model">
     Pointer
   </message>
+  <message name="IDS_DEVTOOLS_6242258a3bddacbd457df5c049591257" desc="Tooltip to explain why a cookie was blocked">
+    An unknown error was encountered when trying to store this cookie.
+  </message>
   <message name="IDS_DEVTOOLS_62efb9ec331e364b96efe68c8b03ca20" desc="Text in DOMDebugger Model">
     Worker
   </message>
   <message name="IDS_DEVTOOLS_62fce3f916fe47f75c391fd5e4fc3eca" desc="Text in Network Log">
     Consider disabling <ph name="COMMON_UISTRING__CHROME_DATA_SAVER__">$1s<ex>Chrome Data Saver</ex></ph> while debugging. For more info see: <ph name="_HTTPS___SUPPORT_GOOGLE_COM_CHROME__P_DATASAVER_">$2s<ex>https://example.com</ex></ph>
   </message>
+  <message name="IDS_DEVTOOLS_646554c61e2132b9c32db710eaca838b" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie was not stored due to user preferences.
+  </message>
   <message name="IDS_DEVTOOLS_6617a779f50f7afb7949c7ea3ad52b28" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Show hit-test borders
   </message>
@@ -132,15 +168,30 @@
   <message name="IDS_DEVTOOLS_7121afd196f5c52bef488d5a0f4c097b" desc="Text in the Event Listener Breakpoints Panel of the JavaScript Debugger in the Sources Panel">
     Script First Statement
   </message>
+  <message name="IDS_DEVTOOLS_77454122d17d16885c51c0d57f5c7bd3" desc="Tooltip to explain why a cookie was blocked">
+    This cookie had the &quot;SameSite=Strict&quot; attribute and the request was made on on a different site. This includes navigation requests initiated by other sites.
+  </message>
+  <message name="IDS_DEVTOOLS_792c21f9d971ea4d5a4fa7891dcebe02" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie didn&apos;t specify a &quot;SameSite&quot; attribute and was defaulted to &quot;SameSite=Lax&quot; and broke the same rules specified in the SameSiteLax value.
+  </message>
   <message name="IDS_DEVTOOLS_794f64c7f20487f6e13679201deeab3d" desc="Text in DOMDebugger Model">
     Picture-in-Picture
   </message>
+  <message name="IDS_DEVTOOLS_7a3a5ae00a1133b1042cf4edf671736a" desc="Tooltip to explain why a cookie was blocked">
+    This cookie had the &quot;SameSite=None&quot; attribute and the connection was not secure. Cookies without SameSite restrictions must be sent over a secure connection.
+  </message>
   <message name="IDS_DEVTOOLS_7c2bc755363ab11a1611bfa369654ff8" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Show frames per second (FPS) meter
   </message>
+  <message name="IDS_DEVTOOLS_7d38391da564c2d9f767c440bbe531f9" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie had the &quot;SameSite=None&quot; attribute but did not have the &quot;Secure&quot; attribute, which is required in order to use &quot;SameSite=None&quot;.
+  </message>
   <message name="IDS_DEVTOOLS_81961fe251f4d1cb4df131561dedf319" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Hide layer borders
   </message>
+  <message name="IDS_DEVTOOLS_829450ef4b9cf5fb35bbc1da8aeec72c" desc="Tooltip to explain why a cookie was blocked">
+    The scheme of this connection is not allowed to store cookies.
+  </message>
   <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>
@@ -174,6 +225,9 @@
   <message name="IDS_DEVTOOLS_9850063efe194af1c63d2aa61ef94c62" desc="Text in the Event Listener Breakpoints Panel of the JavaScript Debugger in the Sources Panel">
     Create canvas context
   </message>
+  <message name="IDS_DEVTOOLS_9995eb02238160b5b11b6097b8eb5008" desc="Tooltip to explain why a cookie was blocked">
+    This cookie didn&apos;t specify a SameSite attribute when it was stored and was defaulted to &quot;SameSite=Lax&quot; and broke the same rules specified in the SameSiteLax value. The cookie had to have been set with &quot;SameSite=None&quot; to enable third-party usage.
+  </message>
   <message name="IDS_DEVTOOLS_9dd7b9f5cc1c19a830f153c3e8f1ad89" desc="Text in Network Manager">
     Fast 3G
   </message>
@@ -189,12 +243,21 @@
   <message name="IDS_DEVTOOLS_a720fc15eb9ec85751969e8615ace9e1" desc="Text in Server Timing">
     Duplicate parameter &quot;<ph name="PARAMNAME">$1s<ex>https</ex></ph>&quot; ignored.
   </message>
+  <message name="IDS_DEVTOOLS_ab91d7ba3d80985f31629857bb57db5a" desc="Tooltip to explain why a cookie was blocked">
+    This cookie had the &quot;SameSite=Lax&quot; attribute and the request was made on a different site. This does not include navigation requests initiated by other sites.
+  </message>
   <message name="IDS_DEVTOOLS_ac4aac1ba23d844f4976e9fcd1fd4a61" desc="Text in DOMDebugger Model">
     WebGL Error Fired (<ph name="ERRORNAME">$1s<ex>Snag Error</ex></ph>)
   </message>
+  <message name="IDS_DEVTOOLS_ae0d59f889c8a03301b9adb5ff1f5bd2" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie had the &quot;SameSite=Strict&quot; attribute but came from a cross-origin response. This includes navigation requests intitiated by other origins.
+  </message>
   <message name="IDS_DEVTOOLS_af4bb376939e77df0e7c2332b837a866" desc="Text in Debugger Model">
     Closure
   </message>
+  <message name="IDS_DEVTOOLS_b1d4660db82a3d7b85dca58e8635ce13" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie had the &quot;SameSite=Lax&quot; attribute but came from a cross-origin response.
+  </message>
   <message name="IDS_DEVTOOLS_b28354b543375bfa94dabaeda722927f" desc="Text in Resource Tree Model">
     top
   </message>
@@ -234,6 +297,9 @@
   <message name="IDS_DEVTOOLS_c9ad95228aa735bdda1aebf38da022af" desc="Text in DOMDebugger Model">
     Parse
   </message>
+  <message name="IDS_DEVTOOLS_ccab918ce9f085b521a52ac13f653c0b" desc="Tooltip to explain why a cookie was blocked">
+    An unknown error was encountered when trying to send this cookie.
+  </message>
   <message name="IDS_DEVTOOLS_cedab6b9e4e794e93bba797e8aff218a" desc="Message in Network Manager">
     <ph name="NETWORKREQUEST_RESOURCETYPE___TITLE__">$1s<ex>XHR</ex></ph> finished loading: <ph name="NETWORKREQUEST_REQUESTMETHOD">$2s<ex>GET</ex></ph> &quot;<ph name="NETWORKREQUEST_URL__">$3s<ex>https://example.com</ex></ph>&quot;.
   </message>
@@ -255,6 +321,9 @@
   <message name="IDS_DEVTOOLS_df9f4292c1d025cbada13b25744d34e5" desc="Title of a setting under the Rendering category that can be invoked through the Command Menu">
     Do not emulate CSS media type
   </message>
+  <message name="IDS_DEVTOOLS_dfc84699a635ac79c7512494a2ebec91" desc="Tooltip to explain why a cookie was blocked">
+    This set-cookie had the &quot;Secure&quot; attribute but was not received over a secure connection.
+  </message>
   <message name="IDS_DEVTOOLS_e1e4c8c9ccd9fc39c391da4bcd093fb2" desc="Text in Debugger Model">
     Block
   </message>
diff --git a/third_party/blink/renderer/modules/accessibility/ax_object.cc b/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 9f4a0bc1..46e0dce8 100644
--- a/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -1605,8 +1605,7 @@
   if (Node* node = GetNode()) {
     auto* element = DynamicTo<Element>(node);
     if (element && node->isConnected()) {
-      scoped_refptr<ComputedStyle> style =
-          document->EnsureStyleResolver().StyleForElement(element);
+      const ComputedStyle* style = element->EnsureComputedStyle();
       return style->Display() == EDisplay::kNone ||
              style->Visibility() != EVisibility::kVisible;
     }
diff --git a/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.cc b/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.cc
index 8196754..b897257f 100644
--- a/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.cc
+++ b/third_party/blink/renderer/modules/indexeddb/indexed_db_blink_mojom_traits.cc
@@ -244,10 +244,10 @@
                                    blink::FilePathToWebString(info->file->path),
                                    info->file->name, info->mime_type,
                                    info->file->last_modified.ToDoubleT(),
-                                   info->size, info->blob.PassHandle());
+                                   info->size, info->blob.PassPipe());
     } else {
       value_blob_info.emplace_back(info->uuid, info->mime_type, info->size,
-                                   info->blob.PassHandle());
+                                   info->blob.PassPipe());
     }
   }
 
diff --git a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
index 467ba54..f0cd0a6 100644
--- a/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
+++ b/third_party/blink/renderer/modules/media_capabilities/media_capabilities.cc
@@ -15,9 +15,6 @@
 #include "services/service_manager/public/cpp/interface_provider.h"
 #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h"
 #include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_decoding_configuration.h"
 #include "third_party/blink/public/platform/task_type.h"
 #include "third_party/blink/public/platform/web_encrypted_media_client.h"
 #include "third_party/blink/public/platform/web_encrypted_media_request.h"
@@ -47,6 +44,9 @@
 #include "third_party/blink/renderer/platform/heap/heap_allocator.h"
 #include "third_party/blink/renderer/platform/heap/member.h"
 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_info.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_decoding_configuration.h"
 #include "third_party/blink/renderer/platform/network/parsed_content_type.h"
 #include "third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
diff --git a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
index 297295c..9767c19a 100644
--- a/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
+++ b/third_party/blink/renderer/modules/mediarecorder/media_recorder_handler.cc
@@ -16,13 +16,13 @@
 #include "media/base/video_codecs.h"
 #include "media/base/video_frame.h"
 #include "media/muxers/webm_muxer.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
 #include "third_party/blink/public/platform/modules/mediastream/webrtc_uma_histograms.h"
 #include "third_party/blink/public/web/modules/mediastream/media_stream_video_track.h"
 #include "third_party/blink/renderer/modules/mediarecorder/buildflags.h"
 #include "third_party/blink/renderer/modules/mediarecorder/media_recorder.h"
 #include "third_party/blink/renderer/platform/heap/heap.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_info.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_component.h"
 #include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.h"
 #include "third_party/blink/renderer/platform/wtf/functional.h"
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.cc b/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.cc
index 9c37226..0ff66af2 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.cc
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.cc
@@ -49,7 +49,7 @@
   static_cast<LocalFrame*>(WebFrame::ToCoreFrame(*frame))
       ->GetInterfaceRegistry()
       ->AddInterface(WTF::BindRepeating(
-          &MediaStreamDeviceObserver::BindMediaStreamDeviceObserverRequest,
+          &MediaStreamDeviceObserver::BindMediaStreamDeviceObserverReceiver,
           WTF::Unretained(this)));
 }
 
@@ -133,7 +133,7 @@
   }
 }
 
-void MediaStreamDeviceObserver::BindMediaStreamDeviceObserverRequest(
+void MediaStreamDeviceObserver::BindMediaStreamDeviceObserverReceiver(
     mojo::PendingReceiver<mojom::blink::MediaStreamDeviceObserver> receiver) {
   receiver_.Bind(std::move(receiver));
 }
diff --git a/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.h b/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.h
index b79f3e4..cf44776 100644
--- a/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.h
+++ b/third_party/blink/renderer/modules/mediastream/media_stream_device_observer.h
@@ -15,6 +15,7 @@
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
 #include "base/threading/thread_checker.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "mojo/public/cpp/bindings/receiver.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-blink.h"
@@ -75,7 +76,7 @@
                        const MediaStreamDevice& old_device,
                        const MediaStreamDevice& new_device) override;
 
-  void BindMediaStreamDeviceObserverRequest(
+  void BindMediaStreamDeviceObserverReceiver(
       mojo::PendingReceiver<mojom::blink::MediaStreamDeviceObserver> receiver);
 
   mojo::Receiver<mojom::blink::MediaStreamDeviceObserver> receiver_{this};
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index f579983f..6e50b580 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1184,6 +1184,12 @@
     "mac/web_core_ns_cell_extras.h",
     "mac/web_core_ns_cell_extras.mm",
     "media/webaudiosourceprovider_impl.cc",
+    "media_capabilities/web_audio_configuration.h",
+    "media_capabilities/web_media_capabilities_info.h",
+    "media_capabilities/web_media_capabilities_key_system_configuration.h",
+    "media_capabilities/web_media_configuration.h",
+    "media_capabilities/web_media_decoding_configuration.h",
+    "media_capabilities/web_video_configuration.h",
     "mediastream/aec_dump_agent_impl.cc",
     "mediastream/aec_dump_agent_impl.h",
     "mediastream/audio_service_audio_processor_proxy.cc",
diff --git a/third_party/blink/renderer/platform/blob/blob_data_test.cc b/third_party/blink/renderer/platform/blob/blob_data_test.cc
index 508b485c..205596b 100644
--- a/third_party/blink/renderer/platform/blob/blob_data_test.cc
+++ b/third_party/blink/renderer/platform/blob/blob_data_test.cc
@@ -72,9 +72,9 @@
   static ExpectedElement Blob(const String& uuid,
                               uint64_t offset,
                               uint64_t length) {
-    return ExpectedElement{
-        DataElement::NewBlob(DataElementBlob::New(nullptr, offset, length)),
-        uuid};
+    return ExpectedElement{DataElement::NewBlob(DataElementBlob::New(
+                               mojo::NullRemote(), offset, length)),
+                           uuid};
   }
 };
 
diff --git a/third_party/blink/renderer/platform/blob/testing/fake_blob_url_store.cc b/third_party/blink/renderer/platform/blob/testing/fake_blob_url_store.cc
index 12e2eb64..9533966 100644
--- a/third_party/blink/renderer/platform/blob/testing/fake_blob_url_store.cc
+++ b/third_party/blink/renderer/platform/blob/testing/fake_blob_url_store.cc
@@ -23,11 +23,11 @@
 void FakeBlobURLStore::Resolve(const KURL& url, ResolveCallback callback) {
   auto it = registrations.find(url);
   if (it == registrations.end()) {
-    std::move(callback).Run(nullptr);
+    std::move(callback).Run(mojo::NullRemote());
     return;
   }
-  mojom::blink::BlobPtr blob;
-  it->value->Clone(MakeRequest(&blob));
+  mojo::PendingRemote<mojom::blink::Blob> blob;
+  it->value->Clone(blob.InitWithNewPipeAndPassReceiver());
   std::move(callback).Run(std::move(blob));
 }
 
diff --git a/third_party/blink/renderer/platform/media_capabilities/DEPS b/third_party/blink/renderer/platform/media_capabilities/DEPS
new file mode 100644
index 0000000..3bb5207e
--- /dev/null
+++ b/third_party/blink/renderer/platform/media_capabilities/DEPS
@@ -0,0 +1,10 @@
+include_rules = [
+    # Don't depend on platform/.
+    "-third_party/blink/renderer/platform",
+
+    # Module.
+    "+third_party/blink/renderer/platform/media_capabilities",
+
+    # Dependencies.
+    "+media/base/eme_constants.h",
+]
diff --git a/third_party/blink/public/platform/modules/media_capabilities/OWNERS b/third_party/blink/renderer/platform/media_capabilities/OWNERS
similarity index 100%
rename from third_party/blink/public/platform/modules/media_capabilities/OWNERS
rename to third_party/blink/renderer/platform/media_capabilities/OWNERS
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_audio_configuration.h b/third_party/blink/renderer/platform/media_capabilities/web_audio_configuration.h
similarity index 71%
rename from third_party/blink/public/platform/modules/media_capabilities/web_audio_configuration.h
rename to third_party/blink/renderer/platform/media_capabilities/web_audio_configuration.h
index 060b9a8..0ffec90 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_audio_configuration.h
+++ b/third_party/blink/renderer/platform/media_capabilities/web_audio_configuration.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 THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_AUDIO_CONFIGURATION_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_AUDIO_CONFIGURATION_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_AUDIO_CONFIGURATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_AUDIO_CONFIGURATION_H_
 
 #include "base/optional.h"
 #include "third_party/blink/public/platform/web_string.h"
@@ -25,4 +25,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_AUDIO_CONFIGURATION_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_AUDIO_CONFIGURATION_H_
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h b/third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_info.h
similarity index 60%
rename from third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h
rename to third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_info.h
index 7a15df2a..792fc53 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h
+++ b/third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_info.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 THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_INFO_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_INFO_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_INFO_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_INFO_H_
 
 namespace blink {
 
@@ -17,4 +17,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_INFO_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_INFO_H_
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_key_system_configuration.h b/third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_key_system_configuration.h
similarity index 83%
rename from third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_key_system_configuration.h
rename to third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_key_system_configuration.h
index 90f7ae2..7e1c5b0 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_key_system_configuration.h
+++ b/third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_key_system_configuration.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 THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_KEY_SYSTEM_CONFIGURATION_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_KEY_SYSTEM_CONFIGURATION_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_KEY_SYSTEM_CONFIGURATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_KEY_SYSTEM_CONFIGURATION_H_
 
 #include "media/base/eme_constants.h"
 #include "third_party/blink/public/platform/web_encrypted_media_types.h"
@@ -46,4 +46,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_KEY_SYSTEM_CONFIGURATION_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CAPABILITIES_KEY_SYSTEM_CONFIGURATION_H_
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h b/third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h
similarity index 70%
rename from third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h
rename to third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h
index 0f0e91b..68657673 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h
+++ b/third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CONFIGURATION_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CONFIGURATION_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CONFIGURATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CONFIGURATION_H_
 
 #include "base/optional.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_audio_configuration.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_audio_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_video_configuration.h"
 
 namespace blink {
 
@@ -43,4 +43,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_CONFIGURATION_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_CONFIGURATION_H_
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_media_decoding_configuration.h b/third_party/blink/renderer/platform/media_capabilities/web_media_decoding_configuration.h
similarity index 67%
rename from third_party/blink/public/platform/modules/media_capabilities/web_media_decoding_configuration.h
rename to third_party/blink/renderer/platform/media_capabilities/web_media_decoding_configuration.h
index 568fa41..5495149 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_media_decoding_configuration.h
+++ b/third_party/blink/renderer/platform/media_capabilities/web_media_decoding_configuration.h
@@ -2,12 +2,12 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_DECODING_CONFIGURATION_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_DECODING_CONFIGURATION_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_DECODING_CONFIGURATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_DECODING_CONFIGURATION_H_
 
 #include "base/optional.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_key_system_configuration.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_key_system_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h"
 
 namespace blink {
 
@@ -32,4 +32,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_MEDIA_DECODING_CONFIGURATION_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_MEDIA_DECODING_CONFIGURATION_H_
diff --git a/third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h b/third_party/blink/renderer/platform/media_capabilities/web_video_configuration.h
similarity index 67%
rename from third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h
rename to third_party/blink/renderer/platform/media_capabilities/web_video_configuration.h
index 9189ba35..2dbd73c 100644
--- a/third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h
+++ b/third_party/blink/renderer/platform/media_capabilities/web_video_configuration.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 THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_VIDEO_CONFIGURATION_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_VIDEO_CONFIGURATION_H_
+#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_VIDEO_CONFIGURATION_H_
+#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_VIDEO_CONFIGURATION_H_
 
 #include "third_party/blink/public/platform/web_string.h"
 
@@ -23,4 +23,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIA_CAPABILITIES_WEB_VIDEO_CONFIGURATION_H_
+#endif  // THIRD_PARTY_BLINK_RENDERER_PLATFORM_MEDIA_CAPABILITIES_WEB_VIDEO_CONFIGURATION_H_
diff --git a/third_party/blink/renderer/platform/peerconnection/DEPS b/third_party/blink/renderer/platform/peerconnection/DEPS
index 909e5b662..e64c7ff 100644
--- a/third_party/blink/renderer/platform/peerconnection/DEPS
+++ b/third_party/blink/renderer/platform/peerconnection/DEPS
@@ -4,6 +4,7 @@
 
     # Module.
     "+third_party/blink/renderer/platform/peerconnection",
+    "+third_party/blink/renderer/platform/media_capabilities",
     "+third_party/blink/renderer/platform/webrtc",
 
     # Dependencies.
diff --git a/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.cc b/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.cc
index c2cfdb24..753f0e8 100644
--- a/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.cc
+++ b/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.cc
@@ -10,11 +10,11 @@
 #include "base/cpu.h"
 #include "base/logging.h"
 #include "base/system/sys_info.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h"
 #include "third_party/blink/public/platform/modules/peerconnection/audio_codec_factory.h"
 #include "third_party/blink/public/platform/modules/peerconnection/video_codec_factory.h"
 #include "third_party/blink/public/platform/platform.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_video_configuration.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
 #include "third_party/webrtc/api/audio_codecs/audio_encoder_factory.h"
 #include "third_party/webrtc/api/audio_codecs/audio_format.h"
diff --git a/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.h b/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.h
index 719cb6f..dbb9ecec 100644
--- a/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.h
+++ b/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler.h
@@ -9,7 +9,7 @@
 
 #include "base/callback_forward.h"
 #include "base/macros.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_capabilities_info.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_capabilities_info.h"
 #include "third_party/blink/renderer/platform/platform_export.h"
 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
diff --git a/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler_test.cc b/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler_test.cc
index c466837..6454d94 100644
--- a/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler_test.cc
+++ b/third_party/blink/renderer/platform/peerconnection/transmission_encoding_info_handler_test.cc
@@ -11,10 +11,10 @@
 #include "media/base/video_codecs.h"
 #include "media/video/video_encode_accelerator.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_audio_configuration.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_media_configuration.h"
-#include "third_party/blink/public/platform/modules/media_capabilities/web_video_configuration.h"
 #include "third_party/blink/public/platform/web_string.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_audio_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_media_configuration.h"
+#include "third_party/blink/renderer/platform/media_capabilities/web_video_configuration.h"
 #include "third_party/webrtc/api/video_codecs/sdp_video_format.h"
 #include "third_party/webrtc/api/video_codecs/video_encoder.h"
 #include "third_party/webrtc/api/video_codecs/video_encoder_factory.h"
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 a53aa83..92b7bfa 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
@@ -89917,6 +89917,18 @@
      {}
     ]
    ],
+   "css/css-values/minmax-length-percentage-interpolate.html": [
+    [
+     "css/css-values/minmax-length-percentage-interpolate.html",
+     [
+      [
+       "/css/reference/ref-filled-green-100px-square-only.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-values/q-unit-case-insensitivity-001.html": [
     [
      "css/css-values/q-unit-case-insensitivity-001.html",
@@ -238970,6 +238982,12 @@
      }
     ]
    ],
+   "geolocation-API/non-secure-contexts.http.html": [
+    [
+     "geolocation-API/non-secure-contexts.http.html",
+     {}
+    ]
+   ],
    "geolocation-API/watchPosition_TypeError.html": [
     [
      "geolocation-API/watchPosition_TypeError.html",
@@ -327575,7 +327593,7 @@
    "support"
   ],
   "README.md": [
-   "3abdf15ca2bea2db47cb958f05f586e238a42ee0",
+   "8c5b201818e388ea56b582d7f546bb4d67c31dd2",
    "support"
   ],
   "WebCryptoAPI/META.yml": [
@@ -339415,7 +339433,7 @@
    "reftest"
   ],
   "css/CSS2/linebox/inline-negative-margin-001.html": [
-   "e8a00ec09bd798d45f110de2ef231fff9c102905",
+   "6ebabd15dee475c33a5f003287d9de1973209300",
    "testharness"
   ],
   "css/CSS2/linebox/leading-001-ref.xht": [
@@ -343107,7 +343125,7 @@
    "support"
   ],
   "css/CSS2/normal-flow/max-width-107.xht": [
-   "e321880149f8aff914da0bb115b451fd34a78b04",
+   "edf4a63fecdb59e7cfb6c3e732f54d2c5dadc48e",
    "reftest"
   ],
   "css/CSS2/normal-flow/max-width-108.xht": [
@@ -362663,11 +362681,11 @@
    "manual"
   ],
   "css/css-fonts/font-family-name-025-ref.html": [
-   "035ed5f29d0fb9e7f08f130a20c0e82fe89f7225",
+   "5ed4361b7ad798c68064aedb293d7eae182b45c5",
    "support"
   ],
   "css/css-fonts/font-family-name-025.html": [
-   "d6ee10db24820cc98102b0fd21e2b4c6af4f4a65",
+   "a4d5359fbfb4dff9ad27ccac73fa66a4ee5f5dc5",
    "reftest"
   ],
   "css/css-fonts/font-family-name-mixcase-ref.xht": [
@@ -363779,7 +363797,7 @@
    "support"
   ],
   "css/css-fonts/parsing/font-style-computed.html": [
-   "c14757406a9774586dcecb075784fd4304863211",
+   "46074c42198361818d8e28c36a9416e5748d766a",
    "testharness"
   ],
   "css/css-fonts/parsing/font-style-invalid.html": [
@@ -371091,11 +371109,11 @@
    "testharness"
   ],
   "css/css-grid/alignment/grid-block-axis-alignment-auto-margins-005.html": [
-   "f1fd9e22a00f4d6a9595d9d655a01e02be2b9349",
+   "5f3d2fa8e23c862e45637b30582adad05c387d97",
    "testharness"
   ],
   "css/css-grid/alignment/grid-block-axis-alignment-auto-margins-006.html": [
-   "0ceaf7c62e6ca539840836f95d0d6f9f92e1911d",
+   "566a878d42b75bcf1701cdd78b8ab6679d6fd2ab",
    "testharness"
   ],
   "css/css-grid/alignment/grid-block-axis-alignment-auto-margins-007.html": [
@@ -371103,7 +371121,7 @@
    "testharness"
   ],
   "css/css-grid/alignment/grid-block-axis-alignment-auto-margins-008.html": [
-   "e757728f7a14bd1dae4952bf000ee8f8449bab74",
+   "9b489ac11940b14e45fb2c4a03219f605ffe0e47",
    "reftest"
   ],
   "css/css-grid/alignment/grid-column-axis-alignment-positioned-items-001.html": [
@@ -371479,11 +371497,11 @@
    "testharness"
   ],
   "css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-005.html": [
-   "fdd58c72faa9f101f5e90d78c3d21b252bf6ac1d",
+   "dc8a2e96f0231f33d1647a1583ea2504e8a857b3",
    "testharness"
   ],
   "css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-006.html": [
-   "ea4748f0ed1d742d171bab350f3e96cd659daccb",
+   "ab933844e9c2f1bec3b766c74578cfb1ae7ba940",
    "testharness"
   ],
   "css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-007.html": [
@@ -371491,7 +371509,7 @@
    "testharness"
   ],
   "css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-008.html": [
-   "e71e30687ef133d7b9fb661814949f395f80a988",
+   "eb9666465542a34cc29853dd71dc326ff4cbbd63",
    "reftest"
   ],
   "css/css-grid/alignment/grid-row-axis-alignment-positioned-items-001.html": [
@@ -373163,7 +373181,7 @@
    "support"
   ],
   "css/css-grid/reference/grid-block-axis-alignment-auto-margins-008-ref.html": [
-   "720bf55f2a2d2cb74511da6865739b9ec091d020",
+   "474f16b5fa3aec7bf80521d341a2d71a15b2c4aa",
    "support"
   ],
   "css/css-grid/reference/grid-different-gutters-ref.html": [
@@ -373183,7 +373201,7 @@
    "support"
   ],
   "css/css-grid/reference/grid-inline-axis-alignment-auto-margins-008-ref.html": [
-   "78d199004fb8a78c530d96cb2a138b3aafa205ca",
+   "d5f993d051001046baaabb4bbb0a1ce641b53a44",
    "support"
   ],
   "css/css-grid/reference/grid-layout-auto-tracks-ref.html": [
@@ -375779,15 +375797,15 @@
    "reftest"
   ],
   "css/css-masking/clip-path/clip-path-inline-001.html": [
-   "21acae0ee7e06da76a6b5830e5aaa6b5e0d2e6a6",
+   "76018614baab3fc3123f9c3d41053effcb64c224",
    "reftest"
   ],
   "css/css-masking/clip-path/clip-path-inline-002.html": [
-   "b99bb20a86d64096e3a0003eb942cb0e4d61e3ad",
+   "ca8634b90328b12f965e28fefbac749e4eadf37a",
    "reftest"
   ],
   "css/css-masking/clip-path/clip-path-inline-003.html": [
-   "89fd44abc7142c9ec4de357e0bb3507c48727152",
+   "471c54c1c6fc891f005e62081986d1f0e822e128",
    "reftest"
   ],
   "css/css-masking/clip-path/clip-path-path-001.html": [
@@ -384811,27 +384829,27 @@
    "support"
   ],
   "css/css-text-decor/reference/text-underline-offset-001-notref.html": [
-   "8b1dacfbf48a68b88e4ae47a852c810b3bc4becf",
+   "c9aea5de2f0da6a75f6ed89f9475dafdc89f17af",
    "support"
   ],
   "css/css-text-decor/reference/text-underline-offset-002-ref.html": [
-   "6e7d5a65f0373ae49452daaa0ad401bed3f1af3e",
+   "8e8d1ff09870912f96520bea51ca467034f99ae7",
    "support"
   ],
   "css/css-text-decor/reference/text-underline-offset-scroll-001-notref.html": [
-   "fbfdadff0df100bed16f721d3ddd3dd99fc283d4",
+   "c76a6a4945d0ad5324508cb372ba88fe52005fa9",
    "support"
   ],
   "css/css-text-decor/reference/text-underline-offset-scroll-001-ref.html": [
-   "5b321033d352b84c1ddf68487a484849c95d3710",
+   "8a74da6d2c05209a1e791e8dc282d8c822bc6c04",
    "support"
   ],
   "css/css-text-decor/reference/text-underline-offset-vertical-001-ref.html": [
-   "7ab3b1dcd99d0a06d24db489325506a30af7488c",
+   "50deb80a556d45b747a1b73528b08b1dcfbc1cf8",
    "support"
   ],
   "css/css-text-decor/reference/text-underline-offset-vertical-002-ref.html": [
-   "30a85700d8af5a2a9b083af44bc33ecc1e931236",
+   "438fb1432d25d184082f4d8c3033f8c6c2bce6f8",
    "support"
   ],
   "css/css-text-decor/text-decoration-001-manual.html": [
@@ -385379,11 +385397,11 @@
    "testharness"
   ],
   "css/css-text-decor/text-underline-offset-001.html": [
-   "f7aeb543df3a77d277b1a8e99d1e50c3bd9a87ea",
+   "88de83a0cfc60b66c09cafb8e4d3dcf1cdcdacb3",
    "reftest"
   ],
   "css/css-text-decor/text-underline-offset-002.html": [
-   "dc65db7723fb31cfaa6c00b4af7e37f5b23affb2",
+   "1b21079b34c23b6fa8889529e67c1a35ef264ae2",
    "reftest"
   ],
   "css/css-text-decor/text-underline-offset-computed-expected.txt": [
@@ -385407,7 +385425,7 @@
    "testharness"
   ],
   "css/css-text-decor/text-underline-offset-scroll-001.html": [
-   "0a046fb33e06ae6f7c9d4eefd4fd6745ba0b2d7e",
+   "6fb53aa421ab8a869a6e43125b9b04badc1dad10",
    "reftest"
   ],
   "css/css-text-decor/text-underline-offset-valid-expected.txt": [
@@ -385419,11 +385437,11 @@
    "testharness"
   ],
   "css/css-text-decor/text-underline-offset-vertical-001.html": [
-   "8a7a5ac9d6a2356482be72f2e9e6dcfc6f5c122b",
+   "a61b9dceff42f5d8887cdf77ed5b20f1455bb681",
    "reftest"
   ],
   "css/css-text-decor/text-underline-offset-vertical-002.html": [
-   "74491c823dd96c3a28cfdabbc2adf22f518a1ca1",
+   "71b5a677926544a796727cd21f2d60bfa0fdbb8f",
    "reftest"
   ],
   "css/css-text-decor/text-underline-position-019-manual.html": [
@@ -385583,7 +385601,7 @@
    "reftest"
   ],
   "css/css-text/hyphens/hyphens-overflow-001.html": [
-   "cddaa6b040b1b92e5280546979c2f7933234e0e0",
+   "948b728254a3a097964e3a341f8475c0c93b4340",
    "reftest"
   ],
   "css/css-text/hyphens/hyphens-shaping-001.html": [
@@ -385607,7 +385625,7 @@
    "support"
   ],
   "css/css-text/hyphens/reference/hyphens-overflow-001-ref.html": [
-   "3bbd5fe8f1464d1662b3fc132c266d84eb9c305d",
+   "08d521d33960ea5d495de39ce9bb2be5fc5c29a0",
    "support"
   ],
   "css/css-text/hyphens/reference/hyphens-shaping-001-ref.html": [
@@ -389383,7 +389401,7 @@
    "reftest"
   ],
   "css/css-text/tab-size/tab-size.html": [
-   "80ddcefcdc69eaf7f512bd487550afb9fe958007",
+   "d9b1b57fec50a19de45a471cd8682feff3a326f5",
    "testharness"
   ],
   "css/css-text/text-align/reference/text-align-end-ref-001.html": [
@@ -391227,59 +391245,59 @@
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-001.html": [
-   "6d17921e17c28664533f3e091de9a8075770b544",
+   "756a04dea56eb95f92334e9da3c2e74ef57f07fc",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-002.html": [
-   "082bce78c4ff56dd4b5af1c60a51c04a0df070c2",
+   "b3136f37faec71bc0c97e1f41b943f8b7fb4c59d",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-003.html": [
-   "2bdef018e72250cd8672ea89fe16cb26971bad7a",
+   "797eb25b39bbaf01d273f1e63be39b07cac71178",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-004.html": [
-   "ea409af2ab9da5c6a651f0c937bba5905bcd7b7e",
+   "882ffc610c7f1f25b58f8b1e87eb1e350ea05751",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-005.html": [
-   "ccf613051ffa661fc2ba60c563b5fba3d2b09f7d",
+   "a896a44dcc568a31ad54709b243b5ead0ad6eab4",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-006.html": [
-   "de1c0de11c67871c5ce8df1dd7e1ee268182c8c6",
+   "3d64853dcb07f536b772acd9bc831593ca30922f",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-007.html": [
-   "9089466338f9dba6c60b8e786ff905d4856488fc",
+   "7ab8bca45ca8937d0f59d9fc0307cdcffdef2670",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-008.html": [
-   "f112a0c20b9586f365345227e926ac03cc2edf8a",
+   "aa5ab5b408c890c25ede17a120213e5cc1dbbc6c",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-009.html": [
-   "2fe58aa5ceb5abc19d00db01d01452c7cf3ec1d5",
+   "f49a8ee9ea1e441d54b7198cfed360efee9d05c1",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-010.html": [
-   "b1b14ea3afbabbce55996cdcc47a9995a4c5e418",
+   "64db37410267b3e012e23fe8973797998c11cacc",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-011.html": [
-   "6167e9ce4538f64baa0a81cd498838be3c759664",
+   "7a003fecb373e9868c6f52409bd27202bfc292fc",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-012.html": [
-   "657cd89d89fbbee229ed481d71b73766ff76c0b0",
+   "c9e85413009f5661f7b00b326bb344a7229ac75e",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-013.html": [
-   "476f76398daaaa962f09ad5e67f949b529bf8802",
+   "d089902f346ac1c9eee48588704401911fc7f4c9",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-leading-spaces-014.html": [
-   "ab2759fdc7b094fa9f4012b557abc27fb6bf8ffd",
+   "b3ad933f9eca9c202d28f7c8d29bdc3f5d9749ca",
    "reftest"
   ],
   "css/css-text/white-space/pre-wrap-tab-001.html": [
@@ -391495,7 +391513,7 @@
    "support"
   ],
   "css/css-text/white-space/reference/white-space-pre-wrap-trailing-spaces-004-ref.html": [
-   "72e4642022af70b543990934c615471600b2bd46",
+   "0cbc7b231f0e3c7dd524ed94562663baf04667ed",
    "support"
   ],
   "css/css-text/white-space/reference/white-space-wrap-after-nowrap-001-ref.html": [
@@ -391827,7 +391845,7 @@
    "reftest"
   ],
   "css/css-text/white-space/white-space-pre-wrap-trailing-spaces-005.html": [
-   "f62e2e5c005d4043c02894eed87422fa6c59b824",
+   "62ecb84757e61b6866213b71503c4dd6ae204410",
    "reftest"
   ],
   "css/css-text/white-space/white-space-wrap-after-nowrap-001.html": [
@@ -402947,7 +402965,7 @@
    "reftest"
   ],
   "css/css-ui/text-overflow-016.html": [
-   "e123adf5de346c93d0520fc1f3e96242388519b4",
+   "d66b64d58a819246413284b27b856b44638c5943",
    "reftest"
   ],
   "css/css-ui/text-overflow-017.html": [
@@ -403574,6 +403592,10 @@
    "ee086ef269d07bf6b0db5d1306a0f24af0ad8fab",
    "testharness"
   ],
+  "css/css-values/minmax-length-percentage-interpolate.html": [
+   "14af1352d1740d32cd52ab069ab1799f87803f5d",
+   "reftest"
+  ],
   "css/css-values/minmax-number-computed.html": [
    "c72c276625466a193bf8829e3ddbb87d7a565dc0",
    "testharness"
@@ -410939,7 +410961,7 @@
    "testharness"
   ],
   "css/cssom-view/getBoundingClientRect-empty-inline.html": [
-   "443ded230f465b40d1c327d4837ffcbe7c2a1ada",
+   "f5c6bee98507518291c2113391595e4725e596d8",
    "testharness"
   ],
   "css/cssom-view/getClientRects-br-htb-ltr.html": [
@@ -430838,6 +430860,10 @@
    "fe4ac8895dfb6e889eded7d694e3c87905868b4f",
    "testharness"
   ],
+  "geolocation-API/non-secure-contexts.http.html": [
+   "af1a5cd81ad9d4c429b3568dacdfe252b0112d66",
+   "testharness"
+  ],
   "geolocation-API/support.js": [
    "960b5721c376973d8721cdd4e602c43f08897679",
    "support"
@@ -452999,7 +453025,7 @@
    "support"
   ],
   "interfaces/speech-api.idl": [
-   "2ed9c2ce8b81756091267843bbe55708c6ecc86c",
+   "0b28fd44ac5141599ee1564f4058aa05385e535b",
    "support"
   ],
   "interfaces/storage.idl": [
@@ -453723,7 +453749,7 @@
    "testharness"
   ],
   "lint.whitelist": [
-   "17ccf19f80a8bd25e9729902e3223074f1d03b42",
+   "8b02cdce5496de59589f65fd6ff9aee675f773cc",
    "support"
   ],
   "loading/lazyload/common.js": [
@@ -454027,7 +454053,7 @@
    "reftest"
   ],
   "mathml/presentation-markup/direction/direction-009-ref.html": [
-   "7ed4796814a7c0561b3ecaeeedf2eac7a5533e60",
+   "8b80b7e6bead29d97540896d5ac1ae14b46881f1",
    "support"
   ],
   "mathml/presentation-markup/direction/direction-009.html": [
@@ -468023,7 +468049,7 @@
    "testharness"
   ],
   "preload/single-download-preload.html": [
-   "16d893ca7e54adde5fec3744b95a14f7e2cf3f34",
+   "74dc00a4d74723dfc5048bde569f17e44b09ec35",
    "testharness"
   ],
   "preload/subresource-integrity-expected.txt": [
@@ -468411,7 +468437,7 @@
    "support"
   ],
   "quirks/reference/table-cell-width-calculation-abspos-ref.html": [
-   "3d365d25ad669f145a7e2ef5e4e6d0552713546a",
+   "41d2203b4a08323a2d6b9894f1ad133e35130265",
    "support"
   ],
   "quirks/support/test-ref-iframe.js": [
@@ -468427,7 +468453,7 @@
    "testharness"
   ],
   "quirks/table-cell-width-calculation-abspos.html": [
-   "f26d06040775c8e2447ade4230fcf28ec06dee3b",
+   "4b9cb213879b034c174b78ad7d8764a2cd0588d2",
    "reftest"
   ],
   "quirks/table-cell-width-calculation.html": [
@@ -479851,11 +479877,11 @@
    "testharness"
   ],
   "scroll-to-text-fragment/scroll-to-text-fragment-target.html": [
-   "fa229dd032f29d45e847d9f03f7cd877852dbd87",
+   "bc513126ae625846ef16e15f23ff174e78709fdb",
    "support"
   ],
   "scroll-to-text-fragment/scroll-to-text-fragment.html": [
-   "db1b1ded3065775f7d763e21fa332c4d36b507c7",
+   "21db280dc90eb21613f152520725f3436b1e8118",
    "testharness"
   ],
   "secure-contexts/META.yml": [
@@ -488655,11 +488681,11 @@
    "support"
   ],
   "tools/lint/lint.py": [
-   "7397de7fb89b7869b85036bc2f4d098004e758bf",
+   "48a275bcf29df6264258da3885e0e5f83a7ec85a",
    "support"
   ],
   "tools/lint/rules.py": [
-   "f354a33fa4905573ceef8ee6ae58ed0cd0b67e3b",
+   "c6f416e90ed7f257c3216024cc77426eba94a7a7",
    "support"
   ],
   "tools/localpaths.py": [
@@ -492851,7 +492877,7 @@
    "support"
   ],
   "tools/webdriver/webdriver/client.py": [
-   "0bddd7b13738321ed57c6df1ed6f316b3a5c7eb5",
+   "5425572016076494cd1d7da6576a6c7a53c9015e",
    "support"
   ],
   "tools/webdriver/webdriver/error.py": [
@@ -495591,7 +495617,7 @@
    "testharness"
   ],
   "web-animations/interfaces/Animatable/animate-expected.txt": [
-   "c2776548ad758510f8e3d6cc8c6fca71e83e8e74",
+   "9c1192054e58c15b31812eeb1bb3852b04cd8bbf",
    "support"
   ],
   "web-animations/interfaces/Animatable/animate-no-browsing-context-expected.txt": [
@@ -495739,7 +495765,7 @@
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/constructor-expected.txt": [
-   "1ccc49f0ee99da10c531673b64454f271ae1099c",
+   "3f9b9840c37070b7e080dea6c6ae1b66076e22c5",
    "support"
   ],
   "web-animations/interfaces/KeyframeEffect/constructor.html": [
@@ -495779,7 +495805,7 @@
    "testharness"
   ],
   "web-animations/interfaces/KeyframeEffect/setKeyframes-expected.txt": [
-   "6d66e8120bec1ced6db5a71194d3680f7125c246",
+   "6a5fee9431438e7fbd7901256e2e05bfebf37a45",
    "support"
   ],
   "web-animations/interfaces/KeyframeEffect/setKeyframes.html": [
@@ -495807,7 +495833,7 @@
    "support"
   ],
   "web-animations/resources/keyframe-tests.js": [
-   "43716801fb19fbb552b6eb1b39998c1756dbe65c",
+   "3cf3cf22bf8666a3800dfd3cc3cd4e37c7f7598e",
    "support"
   ],
   "web-animations/resources/keyframe-utils.js": [
@@ -502159,19 +502185,19 @@
    "reftest"
   ],
   "webvtt/rendering/cues-with-video/processing-model/2_tracks-ref.html": [
-   "fa969b1ab08ed393425c38b19b1b1f54a31e341c",
+   "464fbcff8bff1201ca5725bae22a6995431c3eff",
    "support"
   ],
   "webvtt/rendering/cues-with-video/processing-model/2_tracks.html": [
-   "7648d876928dad228e3e84a2afa4a5ab8276773f",
+   "1a29be86a48d1bb30e056c6a017caa79e39511b7",
    "reftest"
   ],
   "webvtt/rendering/cues-with-video/processing-model/3_tracks-ref.html": [
-   "9fc52199fd01b5662e5db2a9a527a446bbe748cd",
+   "3f155dd0ade2fdfecfa2122faf1bef041f35ca39",
    "support"
   ],
   "webvtt/rendering/cues-with-video/processing-model/3_tracks.html": [
-   "c0d5c161cb918ee6738199f8b5220861b0401805",
+   "bdb45f1110dab9ed129bd8ef99c875719ac6308e",
    "reftest"
   ],
   "webvtt/rendering/cues-with-video/processing-model/align_center-ref.html": [
diff --git a/third_party/blink/web_tests/external/wpt/README.md b/third_party/blink/web_tests/external/wpt/README.md
index 3abdf15c..8c5b201 100644
--- a/third_party/blink/web_tests/external/wpt/README.md
+++ b/third_party/blink/web_tests/external/wpt/README.md
@@ -1,4 +1,4 @@
-The web-platform-tests Project [![IRC chat](https://goo.gl/6nCIks)](http://irc.w3.org/?channels=testing)
+The web-platform-tests Project
 ==============================
 
 The web-platform-tests Project is a W3C-coordinated attempt to build a
@@ -12,6 +12,30 @@
 layers of abstraction to paper over the gaps left by specification
 editors and implementors.
 
+The most important sources of information and activity are:
+
+- [github.com/web-platform-tests/wpt](https://github.com/web-platform-tests/wpt):
+  the canonical location of the project's source code revision history and the
+  discussion forum for changes to the code
+- [web-platform-tests.org](https://web-platform-tests.org): the documentation
+  website; details how to set up the project, how to write tests, how to give
+  and receive peer review, how to serve as an administrator, and more
+- [web-platform-tests.live](http://web-platform-tests.live): a public
+  deployment of the test suite, allowing anyone to run the tests by visiting
+  from an Internet-enabled browser of their choice
+- [wpt.fyi](https://wpt.fyi): an archive of test results collected from an
+  array of web browsers on a regular basis
+- [Real-time chat room](http://irc.w3.org/?channels=testing): the
+  [IRC](http://www.irchelp.org/) chat room named `#testing` on
+  [irc.w3.org](https://www.w3.org/wiki/IRC); includes participants located
+  around the world, but busiest during the European working day; [all
+  discussion is archived here](https://w3.logbot.info/testing)
+- [Mailing list](https://lists.w3.org/Archives/Public/public-test-infra/): a
+  public and low-traffic discussion list
+
+**If you'd like clarification about anything**, don't hesitate to ask in the
+chat room or on the mailing list.
+
 Setting Up the Repo
 ===================
 
@@ -251,25 +275,3 @@
 For more details, see the [lint-tool documentation][lint-tool].
 
 [lint-tool]: https://web-platform-tests.org/writing-tests/lint-tool.html
-
-Getting Involved
-================
-
-If you wish to contribute actively, you're very welcome to join the
-public-test-infra@w3.org mailing list (low traffic) by
-[signing up to our mailing list](mailto:public-test-infra-request@w3.org?subject=subscribe).
-The mailing list is [archived][mailarchive].
-
-Join us on irc #testing ([irc.w3.org][ircw3org], port 6665). The channel
-is [archived][ircarchive].
-
-[contributing]: https://github.com/web-platform-tests/wpt/blob/master/CONTRIBUTING.md
-[ircw3org]: https://www.w3.org/wiki/IRC
-[ircarchive]: https://w3.logbot.info/testing
-[mailarchive]: https://lists.w3.org/Archives/Public/public-test-infra/
-
-Documentation
-=============
-
-* [How to write and review tests](https://web-platform-tests.org/)
-* [Documentation for the wptserve server](http://wptserve.readthedocs.org/en/latest/)
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/inline-negative-margin-001.html b/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/inline-negative-margin-001.html
index e8a00ec..6ebabd1 100644
--- a/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/inline-negative-margin-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/linebox/inline-negative-margin-001.html
@@ -6,6 +6,7 @@
 <link rel="help" href="https://crbug.com/979894">
 <link rel="help" href="https://drafts.csswg.org/css2/visudet.html#inline-width">
 <link rel="author" title="Koji Ishii" href="mailto:kojii@chromium.org">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 html, body { margin: 0; }
 div {
diff --git a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-107.xht b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-107.xht
index e321880..edf4a63 100644
--- a/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-107.xht
+++ b/third_party/blink/web_tests/external/wpt/css/CSS2/normal-flow/max-width-107.xht
@@ -14,7 +14,7 @@
 
   <meta content="ahem" name="flags" />
   <meta content="'max-width' specifies a fixed maximum used width. If the used width is greater than max-width, then the computed value of max-width is used as the computed value for width." name="assert" />
-
+  <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
   <style type="text/css"><![CDATA[
   div
   {
@@ -43,4 +43,4 @@
   <div id="control-green-overlapping">X</div>
 
  </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025-ref.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025-ref.html
index 035ed5f..5ed4361 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025-ref.html
@@ -3,6 +3,7 @@
 <link rel="help" href="http://www.w3.org/TR/CSS21/fonts.html#propdef-font-family" />
 <link rel="help" href="http://www.w3.org/TR/CSS21/fonts.html#font-family-prop" />
 <meta name="assert" content="The 'font-family' property set to and installed font renders the appropriate font." />
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style type="text/css">
 body { font-size: 36px; }
 span#verify { font-family: CSSTest Verify; }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025.html
index d6ee10d..a4d5359f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font-family-name-025.html
@@ -5,6 +5,7 @@
 <link rel="help" href="http://www.w3.org/TR/css-fonts-3/#font-family-prop" />
 <link rel="match" href="font-family-name-025-ref.html" />
 <meta name="assert" content="The 'font-family' property set to and installed font renders the appropriate font. Postscript name should not match." />
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 body { font-size: 36px; }
 span#verify { font-family: CSSTest Verify; }
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-style-computed.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-style-computed.html
index c1475740..46074c42 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-style-computed.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/parsing/font-style-computed.html
@@ -8,6 +8,7 @@
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <script src="/css/support/computed-testcommon.js"></script>
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #target {
     font-family: Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-005.html
index f1fd9e2..5f3d2fa 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-005.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
 <link rel="help" title="10.2 Aligning with auto margins" href="https://drafts.csswg.org/css-grid/#auto-margins">
 <meta name="assert" content="The 'top' and 'bottom' margins must be recomputed whenever the grid item's height changes.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-006.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-006.html
index 0ceaf7c..566a878 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-006.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
 <link rel="help" title="10.2 Aligning with auto margins" href="https://drafts.csswg.org/css-grid/#auto-margins">
 <meta name="assert" content="The 'top' and 'bottom' margins must be recomputed whenever the grid item's height changes.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-008.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-008.html
index e757728..9b489ac1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-block-axis-alignment-auto-margins-008.html
@@ -5,6 +5,7 @@
 <link rel="help" title="10.2 Aligning with auto margins" href="https://drafts.csswg.org/css-grid/#auto-margins">
 <link rel="match" href="../reference/grid-block-axis-alignment-auto-margins-008-ref.html">
 <meta name="assert" content="The 'top' and 'bottom' margins must be recomputed after the grid's intrinsic size is determined.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-005.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-005.html
index fdd58c7..dc8a2e9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-005.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
 <link rel="help" title="10.2 Aligning with auto margins" href="https://drafts.csswg.org/css-grid/#auto-margins">
 <meta name="assert" content="The 'left' and 'right' margins must be recomputed whenever the grid items's width changes.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
@@ -35,6 +36,7 @@
     <div id="item2">XXXXX</div>
 </div>
 <script>
+document.fonts.ready.then(() => {
     item1.setAttribute("data-offset-x", "50");
     item2.setAttribute("data-offset-x", "325");
     checkLayout('#grid');
@@ -44,4 +46,5 @@
     item1.setAttribute("data-offset-x", "50");
     item2.setAttribute("data-offset-x", "275");
     checkLayout('#grid');
+});
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-006.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-006.html
index ea4748f..ab93384 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-006.html
@@ -4,6 +4,7 @@
 <link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
 <link rel="help" title="10.2 Aligning with auto margins" href="https://drafts.csswg.org/css-grid/#auto-margins">
 <meta name="assert" content="The 'left' and 'right' margins must be recomputed whenever the grid items's width changes.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
@@ -34,6 +35,7 @@
     <div id="item2">XX</div>
 </div>
 <script>
+document.fonts.ready.then(() => {
     item1.setAttribute("data-offset-x", "80");
     item2.setAttribute("data-offset-x", "340");
     checkLayout('#grid');
@@ -43,4 +45,5 @@
     item1.setAttribute("data-offset-x", "50");
     item2.setAttribute("data-offset-x", "325");
     checkLayout('#grid');
+});
 </script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-008.html b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-008.html
index e71e306..eb96664 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/alignment/grid-inline-axis-alignment-auto-margins-008.html
@@ -5,6 +5,7 @@
 <link rel="help" title="10.2 Aligning with auto margins" href="https://drafts.csswg.org/css-grid/#auto-margins">
 <link rel="match" href="../reference/grid-inline-axis-alignment-auto-margins-008-ref.html">
 <meta name="assert" content="The 'left' and 'right' margins must be recomputed after the grid's intrinsic size is determined.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-block-axis-alignment-auto-margins-008-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-block-axis-alignment-auto-margins-008-ref.html
index 720bf55..474f16b 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-block-axis-alignment-auto-margins-008-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-block-axis-alignment-auto-margins-008-ref.html
@@ -2,6 +2,7 @@
 <meta charset="utf-8">
 <title>CSS Grid Layout Reference: Aligning grid items using 'auto' margins</title>
 <link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-inline-axis-alignment-auto-margins-008-ref.html b/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-inline-axis-alignment-auto-margins-008-ref.html
index 78d19900..d5f993d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-inline-axis-alignment-auto-margins-008-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-grid/reference/grid-inline-axis-alignment-auto-margins-008-ref.html
@@ -2,6 +2,7 @@
 <meta charset="utf-8">
 <title>CSS Grid Layout Reference: Aligning grid items using 'auto' margins</title>
 <link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   #grid {
       display: grid;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html
index 21acae0..7601861 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-001.html
@@ -4,6 +4,7 @@
 <link rel="help" href="https://www.w3.org/TR/css-masking-1/#the-clip-path" title="5.1 Clipping Shape: the clip-path property">
 <link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
 <meta content="ahem" name="flags">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   body {
     overflow: hidden;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html
index b99bb20..ca8634b9 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-002.html
@@ -4,6 +4,7 @@
 <link rel="help" href="https://www.w3.org/TR/css-masking-1/#the-clip-path" title="5.1 Clipping Shape: the clip-path property">
 <link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
 <meta content="ahem" name="flags">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   body {
     overflow: hidden;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-003.html b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-003.html
index 89fd44a..471c54c1 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-masking/clip-path/clip-path-inline-003.html
@@ -4,6 +4,7 @@
 <link rel="help" href="https://www.w3.org/TR/css-masking-1/#the-clip-path" title="5.1 Clipping Shape: the clip-path property">
 <link rel="match" href="../../reference/ref-filled-green-100px-square.xht">
 <meta content="ahem" name="flags">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
   body {
     overflow: hidden;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-001-notref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-001-notref.html
index 8b1dacf..c9aea5d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-001-notref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-001-notref.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>Non-reference case for text-underline-offset</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         #main {
             margin: 2em;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-002-ref.html
index 6e7d5a65..8e8d1ff 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-002-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-002-ref.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>Reference case for text-underline-offset</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         #main{
             border-bottom: 1px solid cyan;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-notref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-notref.html
index fbfdadff..c76a6a4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-notref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-notref.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>Non-reference case for text-underline-offset</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         #text{
             border: black dashed;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-ref.html
index 5b321033..8a74da6 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-scroll-001-ref.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>Reference case for text-underline-offset</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         #text{
             border: black dashed;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-001-ref.html
index 7ab3b1dc..50deb80a5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-001-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-001-ref.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>Reference case for text-underline-offset</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         span{
             font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-002-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-002-ref.html
index 30a85700..438fb14 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-002-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/reference/text-underline-offset-vertical-002-ref.html
@@ -3,6 +3,7 @@
 <head>
     <meta charset="utf-8">
     <title>Reference case for text-underline-offset</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         div{
             font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-001.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-001.html
index f7aeb54..88de83a0 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-001.html
@@ -8,6 +8,7 @@
     <link rel="author" title="Mozilla" href="https://www.mozilla.org">
     <link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
     <link rel="mismatch" href="reference/text-underline-offset-001-notref.html">
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         #main {
             margin: 2em;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-002.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-002.html
index dc65db7..1b21079 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-002.html
@@ -7,6 +7,7 @@
     <link rel="author" title="Mozilla" href="https://www.mozilla.org">
     <link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
     <link rel="match" href="reference/text-underline-offset-002-ref.html">
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         #main{
             border-bottom: 1px solid cyan;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-scroll-001.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-scroll-001.html
index 0a046fb..6fb53aa 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-scroll-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-scroll-001.html
@@ -9,6 +9,7 @@
     <link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
     <link rel="match" href="reference/text-underline-offset-scroll-001-ref.html">
     <link rel="mismatch" href="reference/text-underline-offset-scroll-001-notref.html">
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         /*
          * Testing to make sure that positioning the underline
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-001.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-001.html
index 8a7a5ac..a61b9dc 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-001.html
@@ -8,6 +8,7 @@
     <link rel="author" title="Mozilla" href="https://www.mozilla.org">
     <link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
     <link rel="match" href="reference/text-underline-offset-vertical-001-ref.html">
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         span{
             margin-left: 5em;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-002.html b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-002.html
index 74491c8..71b5a67 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text-decor/text-underline-offset-vertical-002.html
@@ -8,6 +8,7 @@
     <link rel="author" title="Mozilla" href="https://www.mozilla.org">
     <link rel="help" href="https://drafts.csswg.org/css-text-decor-4/#underline-offset">
     <link rel="match" href="reference/text-underline-offset-vertical-002-ref.html">
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
         div{
             font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-overflow-001.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-overflow-001.html
index cddaa6b..948b728 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-overflow-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/hyphens-overflow-001.html
@@ -3,6 +3,7 @@
 <link rel="match" href="reference/hyphens-overflow-001-ref.html">
 <link rel="help" href="https://drafts.csswg.org/css-text-3/#hyphens-property">
 <link rel="author" href="mailto:kojii@chromium.org">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font-size: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-overflow-001-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-overflow-001-ref.html
index 3bbd5fe8..08d521d3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-overflow-001-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/hyphens/reference/hyphens-overflow-001-ref.html
@@ -1,4 +1,5 @@
 <!DOCTYPE html>
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font-size: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/tab-size.html b/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/tab-size.html
index 80ddcef..d9b1b57 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/tab-size.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/tab-size/tab-size.html
@@ -2,6 +2,7 @@
 <meta charset="utf-8">
 <title>Test: CSS value type of the CSS property 'tab-size'</title>
 <link rel="help" href="https://drafts.csswg.org/css-text-3/#tab-size-property">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 body {
   font-family: Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html
index 6d17921..756a04de 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-001.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space at the beginning of the line are breaking opportunities when white-space is pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 50px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html
index 082bce7..b3136f3 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-002.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 25px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html
index 2bdef01..797eb25 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-003.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 25px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html
index ea409af..882ffc61 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-004.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html
index ccf61305..a896a44 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-005.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html
index de1c0de..3d64853d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-006.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html
index 9089466..7ab8bca 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-007.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html
index f112a0c..aa5ab5b4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-008.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html
index 2fe58aa..f49a8ee 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-009.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html
index b1b14ea..64db37410 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-010.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html
index 6167e9ce..7a003fe 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-011.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should not be collapsed, honoring white-space: pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html
index 657cd89d..c9e8541 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-012.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 25px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html
index 476f763..d089902f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-013.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 25px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html
index ab2759fd..b3ad933f 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/pre-wrap-leading-spaces-014.html
@@ -8,6 +8,7 @@
 <link rel="match" href="reference/white-space-break-spaces-005-ref.html">
 <meta name="assert" content="Preserved white space after forced breaks become leading white-spaces and should be breaking opportunities when white-space is pre-wrap.">
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 20px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/reference/white-space-pre-wrap-trailing-spaces-004-ref.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/reference/white-space-pre-wrap-trailing-spaces-004-ref.html
index 72e464202..0cbc7b23 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/reference/white-space-pre-wrap-trailing-spaces-004-ref.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/reference/white-space-pre-wrap-trailing-spaces-004-ref.html
@@ -3,6 +3,7 @@
 <title>CSS test Reference</title>
 <link rel="author" title="Javier Fernandez" href="mailto:jfernandez@igalia.com" />
 
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 25px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-005.html b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-005.html
index f62e2e5..62ecb84 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-005.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/white-space/white-space-pre-wrap-trailing-spaces-005.html
@@ -5,6 +5,7 @@
 <link rel="help" href="https://drafts.csswg.org/css-text-3/#white-space-phase-2">
 <link rel="match" href="reference/white-space-pre-wrap-trailing-spaces-004-ref.html">
 <meta name="assert" content="Preserved white space at the end of the line is hanged when white-space is pre-wrap.">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 10px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-ui/text-overflow-016.html b/third_party/blink/web_tests/external/wpt/css/css-ui/text-overflow-016.html
index e123adf5..d66b64d 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-ui/text-overflow-016.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-ui/text-overflow-016.html
@@ -6,6 +6,7 @@
 <link rel="match" href="reference/text-overflow-016-ref.html">
 <meta name="flags" content="ahem">
 <meta name="assert" content="If there is insufficient space for the ellipsis, then clip the rendering of the ellipsis itself">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 .test, .test2 {
   overflow: hidden;
diff --git a/third_party/blink/web_tests/external/wpt/css/css-values/minmax-length-percentage-interpolate.html b/third_party/blink/web_tests/external/wpt/css/css-values/minmax-length-percentage-interpolate.html
new file mode 100644
index 0000000..14af1352
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-values/minmax-length-percentage-interpolate.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<link rel="help" href="https://drafts.csswg.org/css-values-4/#comp-func">
+<link rel="author" title="Xiaocheng Hu" href="mailto:xiaochengh@chromium.org">
+<link rel="match" href="../reference/ref-filled-green-100px-square-only.html">
+<title>Tests interpolation between CSS comparison functions</title>
+<style>
+@keyframes anim {
+  from {
+    width:  min(50px, 30%);
+    height: min(75%, 160px);
+  }
+  to {
+    width:  max(75%, 100px);
+    height: max(50px, 20%);
+  }
+}
+
+.test {
+  background-color: green;
+  animation: anim 2000000s linear;
+  animation-delay: -1000000s;
+}
+
+.container {
+  position: absolute;
+  width: 200px;
+  height: 200px;
+}
+</style>
+<p>Test passes if there is a filled green square.</p>
+<div class="container">
+  <div class="test"></div>
+</div>
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-empty-inline.html b/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-empty-inline.html
index 443ded23..f5c6bee9 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-empty-inline.html
+++ b/third_party/blink/web_tests/external/wpt/css/cssom-view/getBoundingClientRect-empty-inline.html
@@ -3,6 +3,7 @@
 <link rel="author" title="Koji Ishii" href="mailto:kojii@chromium.org">
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 div {
   font: 10px/1 Ahem;
@@ -18,6 +19,7 @@
     <span class="inline-block"></span>
   </div>
 <script>
+document.fonts.ready.then(() => {
 run(document.getElementById('empty'));
 function run(element) {
   test(() => {
@@ -28,5 +30,6 @@
     assert_equals(rect.height, 10, "height");
   });
 }
+});
 </script>
 </body>
diff --git a/third_party/blink/web_tests/external/wpt/geolocation-API/non-secure-contexts.http.html b/third_party/blink/web_tests/external/wpt/geolocation-API/non-secure-contexts.http.html
new file mode 100644
index 0000000..af1a5cd8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/geolocation-API/non-secure-contexts.http.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<meta charset="utf-8" />
+<title>Geolocation Test: non-secure contexts</title>
+<link rel="help" href="https://github.com/w3c/geolocation-api/pull/34" />
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+  promise_test(() => {
+    return new Promise(resolve => {
+      let isAsync = true;
+      const successCallback = () => {
+        assert_unreached(
+          "successCallback must never be invoked in non-secure contexts."
+        );
+      };
+      const errorCallBack = () => {
+        isAsync = false;
+        resolve();
+      };
+      navigator.geolocation.getCurrentPosition(successCallback, errorCallBack);
+      assert_true(
+        isAsync,
+        "Expected the errorCallback to be called asynchronously."
+      );
+    });
+  }, "When in a non-secure context, getCurrentPosition()'s errorCallback is asynchronously called.");
+
+  promise_test(async () => {
+    return new Promise(resolve => {
+      let isAsync = true;
+      const successCallback = () => {
+        assert_unreached(
+          "successCallback must never be invoked in non-secure contexts."
+        );
+      };
+      const errorCallBack = () => {
+        isAsync = false;
+        resolve();
+      };
+      navigator.geolocation.watchPosition(successCallback, errorCallBack);
+      assert_true(isAsync, "errorCallback must be called asynchronously.");
+    });
+  }, "When in a non-secure context, watchPosition()'s errorCallback is asynchronously called.");
+
+  promise_test(async () => {
+    const positionErrorPromise = new Promise(errorCallBack => {
+      const successCallback = () => {
+        assert_unreached(
+          "successCallback must never be invoked in non-secure contexts."
+        );
+      };
+      navigator.geolocation.getCurrentPosition(successCallback, errorCallBack);
+    });
+    const positionError = await positionErrorPromise;
+    assert_equals(
+      positionError.code,
+      1,
+      "Expected the value for PERMISSION_DENIED, which is 1."
+    );
+  }, "When in a non-secure context, the getCurrentPosition() errorCallBack gets a PositionError with the correct error code.");
+
+  promise_test(async () => {
+    const positionErrorPromise = new Promise(errorCallBack => {
+      const successCallback = () => {
+        assert_unreached(
+          "successCallback must never be invoked in non-secure contexts."
+        );
+      };
+      const id = navigator.geolocation.watchPosition(
+        successCallback,
+        errorCallBack
+      );
+      assert_true(Number.isInteger(id), "Must return an identifier.");
+    });
+    const positionError = await positionErrorPromise;
+    assert_equals(
+      positionError.code,
+      1,
+      "Expected the value for PERMISSION_DENIED, which is 1."
+    );
+  }, "When in a non-secure context, the watchPosition() errorCallBack gets a PositionError with the correct error code.");
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl b/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl
index 2ed9c2c..0b28fd4 100644
--- a/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl
+++ b/third_party/blink/web_tests/external/wpt/interfaces/speech-api.idl
@@ -3,8 +3,10 @@
 // (https://github.com/tidoust/reffy-reports)
 // Source: Web Speech API (https://w3c.github.io/speech-api/)
 
-[Exposed=Window, Constructor]
+[Exposed=Window]
 interface SpeechRecognition : EventTarget {
+    constructor();
+
     // recognition parameters
     attribute SpeechGrammarList grammars;
     attribute DOMString lang;
@@ -42,9 +44,9 @@
     "language-not-supported"
 };
 
-[Exposed=Window,
- Constructor(DOMString type, SpeechRecognitionErrorEventInit eventInitDict)]
+[Exposed=Window]
 interface SpeechRecognitionErrorEvent : Event {
+    constructor(DOMString type, SpeechRecognitionErrorEventInit eventInitDict);
     readonly attribute SpeechRecognitionErrorCode error;
     readonly attribute DOMString message;
 };
@@ -77,9 +79,9 @@
 };
 
 // A full response, which could be interim or final, part of a continuous response or not
-[Exposed=Window,
- Constructor(DOMString type, SpeechRecognitionEventInit eventInitDict)]
+[Exposed=Window]
 interface SpeechRecognitionEvent : Event {
+    constructor(DOMString type, SpeechRecognitionEventInit eventInitDict);
     readonly attribute unsigned long resultIndex;
     readonly attribute SpeechRecognitionResultList results;
 };
@@ -97,8 +99,9 @@
 };
 
 // The object representing a speech grammar collection
-[Exposed=Window, Constructor]
+[Exposed=Window]
 interface SpeechGrammarList {
+    constructor();
     readonly attribute unsigned long length;
     getter SpeechGrammar item(unsigned long index);
     void addFromURI(DOMString src,
@@ -126,9 +129,10 @@
     [SameObject] readonly attribute SpeechSynthesis speechSynthesis;
 };
 
-[Exposed=Window,
-  Constructor(optional DOMString text)]
+[Exposed=Window]
 interface SpeechSynthesisUtterance : EventTarget {
+    constructor(optional DOMString text);
+
     attribute DOMString text;
     attribute DOMString lang;
     attribute SpeechSynthesisVoice? voice;
@@ -145,9 +149,9 @@
     attribute EventHandler onboundary;
 };
 
-[Exposed=Window,
- Constructor(DOMString type, SpeechSynthesisEventInit eventInitDict)]
+[Exposed=Window]
 interface SpeechSynthesisEvent : Event {
+    constructor(DOMString type, SpeechSynthesisEventInit eventInitDict);
     readonly attribute SpeechSynthesisUtterance utterance;
     readonly attribute unsigned long charIndex;
     readonly attribute unsigned long charLength;
@@ -178,9 +182,9 @@
     "not-allowed",
 };
 
-[Exposed=Window,
- Constructor(DOMString type, SpeechSynthesisErrorEventInit eventInitDict)]
+[Exposed=Window]
 interface SpeechSynthesisErrorEvent : SpeechSynthesisEvent {
+    constructor(DOMString type, SpeechSynthesisErrorEventInit eventInitDict);
     readonly attribute SpeechSynthesisErrorCode error;
 };
 
diff --git a/third_party/blink/web_tests/external/wpt/lint.whitelist b/third_party/blink/web_tests/external/wpt/lint.whitelist
index 17ccf19..8b02cdc 100644
--- a/third_party/blink/web_tests/external/wpt/lint.whitelist
+++ b/third_party/blink/web_tests/external/wpt/lint.whitelist
@@ -827,3 +827,19 @@
 MISSING DEPENDENCY: shape-detection/resources/shapedetection-helpers.js
 MISSING DEPENDENCY: webxr/resources/webxr_util.js
 MISSING DEPENDENCY: contacts/resources/helpers.js
+
+# Tests that are false positives for using Ahem as a system font
+AHEM SYSTEM FONT: acid/acid3/test.html
+AHEM SYSTEM FONT: resource-timing/resources/all_resource_types.htm
+AHEM SYSTEM FONT: resource-timing/resources/iframe-reload-TAO.sub.html
+
+# These tests are imported from mozilla-central and can't be modified in WPT.
+# They do load Ahem as a web font, but they use their own copy which trips the
+# lint rule. Basically false positives.
+AHEM SYSTEM FONT: css/vendor-imports/mozilla/mozilla-central-reftests/*
+
+# TODO: The following should be deleted along with the Ahem web font cleanup
+# PR (https://github.com/web-platform-tests/wpt/pull/18702)
+AHEM SYSTEM FONT: infrastructure/assumptions/ahem-ref.html
+AHEM SYSTEM FONT: infrastructure/assumptions/ahem.html
+
diff --git a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/direction/direction-009-ref.html b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/direction/direction-009-ref.html
index 7ed4796..8b80b7e 100644
--- a/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/direction/direction-009-ref.html
+++ b/third_party/blink/web_tests/external/wpt/mathml/presentation-markup/direction/direction-009-ref.html
@@ -3,6 +3,7 @@
   <head>
     <meta charset="utf-8"/>
     <title>RTL ms lquote="X" rquote="p"</title>
+    <link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
     <style>
       math {
           font: 25px/1 Ahem;
diff --git a/third_party/blink/web_tests/external/wpt/preload/single-download-preload.html b/third_party/blink/web_tests/external/wpt/preload/single-download-preload.html
index 16d893c..74dc00a 100644
--- a/third_party/blink/web_tests/external/wpt/preload/single-download-preload.html
+++ b/third_party/blink/web_tests/external/wpt/preload/single-download-preload.html
@@ -23,10 +23,10 @@
         background-image: url(resources/square.png?backgroundi&single-download-preload);
     }
     @font-face {
-      font-family:ahem;
+      font-family:myFont;
       src: url(/fonts/CanvasTest.ttf?single-download-preload);
     }
-    span { font-family: ahem, Arial; }
+    span { font-family: myFont, Arial; }
 </style>
 <link rel="stylesheet" href="resources/dummy.css?single-download-preload">
 <script src="resources/dummy.js?single-download-preload"></script>
diff --git a/third_party/blink/web_tests/external/wpt/quirks/reference/table-cell-width-calculation-abspos-ref.html b/third_party/blink/web_tests/external/wpt/quirks/reference/table-cell-width-calculation-abspos-ref.html
index 3d365d2..41d2203 100644
--- a/third_party/blink/web_tests/external/wpt/quirks/reference/table-cell-width-calculation-abspos-ref.html
+++ b/third_party/blink/web_tests/external/wpt/quirks/reference/table-cell-width-calculation-abspos-ref.html
@@ -1,3 +1,4 @@
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 table {
   font-size: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/quirks/table-cell-width-calculation-abspos.html b/third_party/blink/web_tests/external/wpt/quirks/table-cell-width-calculation-abspos.html
index f26d060..4b9cb21 100644
--- a/third_party/blink/web_tests/external/wpt/quirks/table-cell-width-calculation-abspos.html
+++ b/third_party/blink/web_tests/external/wpt/quirks/table-cell-width-calculation-abspos.html
@@ -1,6 +1,7 @@
 <title>An out-of-flow imagef in the table cell width calculation quirk</title>
 <link rel="match" href="reference/table-cell-width-calculation-abspos-ref.html">
 <link rel="author" title="Koji Ishii" href="mailto:kojii@chromium.org">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 table {
   font-size: 10px;
diff --git a/third_party/blink/web_tests/external/wpt/tools/lint/lint.py b/third_party/blink/web_tests/external/wpt/tools/lint/lint.py
index 7397de7..48a275b 100644
--- a/third_party/blink/web_tests/external/wpt/tools/lint/lint.py
+++ b/third_party/blink/web_tests/external/wpt/tools/lint/lint.py
@@ -680,6 +680,21 @@
     return errors
 
 
+ahem_font_re = re.compile(b"font.*:.*ahem", flags=re.IGNORECASE)
+ahem_stylesheet_re = re.compile(b"\/fonts\/ahem\.css", flags=re.IGNORECASE)
+
+
+def check_ahem_system_font(repo_root, path, f):
+    # type: (str, str, IO[bytes]) -> List[rules.Error]
+    if not path.endswith((".html", ".htm", ".xht", ".xhtml")):
+        return []
+    contents = f.read()
+    errors = []
+    if ahem_font_re.search(contents) and not ahem_stylesheet_re.search(contents):
+        errors.append(rules.AhemSystemFont.error(path))
+    return errors
+
+
 def check_path(repo_root, path):
     # type: (str, str) -> List[rules.Error]
     """
@@ -918,7 +933,8 @@
 path_lints = [check_file_type, check_path_length, check_worker_collision, check_ahem_copy,
               check_gitignore_file]
 all_paths_lints = [check_css_globally_unique]
-file_lints = [check_regexp_line, check_parsed, check_python_ast, check_script_metadata]
+file_lints = [check_regexp_line, check_parsed, check_python_ast, check_script_metadata,
+              check_ahem_system_font]
 
 # Don't break users of the lint that don't have git installed.
 try:
diff --git a/third_party/blink/web_tests/external/wpt/tools/lint/rules.py b/third_party/blink/web_tests/external/wpt/tools/lint/rules.py
index f354a33f..c6f416e 100644
--- a/third_party/blink/web_tests/external/wpt/tools/lint/rules.py
+++ b/third_party/blink/web_tests/external/wpt/tools/lint/rules.py
@@ -78,6 +78,11 @@
     description = "Don't add extra copies of Ahem, use /fonts/Ahem.ttf"
 
 
+class AhemSystemFont(Rule):
+    name = "AHEM SYSTEM FONT"
+    description = "Don't use Ahem as a system font, use /fonts/ahem.css"
+
+
 # TODO: Add tests for this rule
 class IgnoredPath(Rule):
     name = "IGNORED PATH"
diff --git a/third_party/blink/web_tests/external/wpt/tools/webdriver/webdriver/client.py b/third_party/blink/web_tests/external/wpt/tools/webdriver/webdriver/client.py
index 0bddd7b..5425572 100644
--- a/third_party/blink/web_tests/external/wpt/tools/webdriver/webdriver/client.py
+++ b/third_party/blink/web_tests/external/wpt/tools/webdriver/webdriver/client.py
@@ -114,17 +114,17 @@
         """Perform all queued actions."""
         self.session.actions.perform([self.dict])
 
-    def _key_action(self, subtype, value):
-        self._actions.append({"type": subtype, "value": value})
+    def _key_action(self, subtype, value, async_dispatch=False):
+        self._actions.append({"type": subtype, "value": value, "asyncDispatch": async_dispatch})
 
-    def _pointer_action(self, subtype, button):
-        self._actions.append({"type": subtype, "button": button})
+    def _pointer_action(self, subtype, button, async_dispatch=False):
+        self._actions.append({"type": subtype, "button": button, "asyncDispatch": async_dispatch})
 
     def pause(self, duration):
         self._actions.append({"type": "pause", "duration": duration})
         return self
 
-    def pointer_move(self, x, y, duration=None, origin=None):
+    def pointer_move(self, x, y, duration=None, origin=None, async_dispatch=False):
         """Queue a pointerMove action.
 
         :param x: Destination x-axis coordinate of pointer in CSS pixels.
@@ -143,28 +143,29 @@
             action["duration"] = duration
         if origin is not None:
             action["origin"] = origin
+        action["asyncDispatch"] = async_dispatch
         self._actions.append(action)
         return self
 
-    def pointer_up(self, button=0):
+    def pointer_up(self, button=0, async_dispatch=False):
         """Queue a pointerUp action for `button`.
 
         :param button: Pointer button to perform action with.
                        Default: 0, which represents main device button.
         """
-        self._pointer_action("pointerUp", button)
+        self._pointer_action("pointerUp", button, async_dispatch)
         return self
 
-    def pointer_down(self, button=0):
+    def pointer_down(self, button=0, async_dispatch=False):
         """Queue a pointerDown action for `button`.
 
         :param button: Pointer button to perform action with.
                        Default: 0, which represents main device button.
         """
-        self._pointer_action("pointerDown", button)
+        self._pointer_action("pointerDown", button, async_dispatch)
         return self
 
-    def click(self, element=None, button=0):
+    def click(self, element=None, button=0, async_dispatch=False):
         """Queue a click with the specified button.
 
         If an element is given, move the pointer to that element first,
@@ -175,33 +176,33 @@
                        with. Default: 0, which represents main device button.
         """
         if element:
-            self.pointer_move(0, 0, origin=element)
-        return self.pointer_down(button).pointer_up(button)
+            self.pointer_move(0, 0, origin=element, async_dispatch=async_dispatch)
+        return self.pointer_down(button, async_dispatch).pointer_up(button, async_dispatch)
 
-    def key_up(self, value):
+    def key_up(self, value, async_dispatch=False):
         """Queue a keyUp action for `value`.
 
         :param value: Character to perform key action with.
         """
-        self._key_action("keyUp", value)
+        self._key_action("keyUp", value, async_dispatch)
         return self
 
-    def key_down(self, value):
+    def key_down(self, value, async_dispatch=False):
         """Queue a keyDown action for `value`.
 
         :param value: Character to perform key action with.
         """
-        self._key_action("keyDown", value)
+        self._key_action("keyDown", value, async_dispatch)
         return self
 
-    def send_keys(self, keys):
+    def send_keys(self, keys, async_dispatch=False):
         """Queue a keyDown and keyUp action for each character in `keys`.
 
         :param keys: String of keys to perform key actions with.
         """
         for c in keys:
-            self.key_down(c)
-            self.key_up(c)
+            self.key_down(c, async_dispatch)
+            self.key_up(c, async_dispatch)
         return self
 
 
diff --git a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks-ref.html b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks-ref.html
index fa969b1..464fbcf 100644
--- a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks-ref.html
+++ b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks-ref.html
@@ -2,6 +2,7 @@
 <html class="reftest-wait">
 <title>Reference for WebVTT rendering, 2 tracks enabled at the same time</title>
 <script src="/common/reftest-wait.js"></script>
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 html { overflow:hidden }
 body { margin:0 }
diff --git a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks.html b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks.html
index 7648d87..1a29be8 100644
--- a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks.html
+++ b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/2_tracks.html
@@ -2,6 +2,7 @@
 <html class="reftest-wait">
 <title>WebVTT rendering, 2 tracks enabled at the same time</title>
 <link rel="match" href="2_tracks-ref.html">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 html { overflow:hidden }
 body { margin:0 }
diff --git a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks-ref.html b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks-ref.html
index 9fc5219..3f155dd0 100644
--- a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks-ref.html
+++ b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks-ref.html
@@ -2,6 +2,7 @@
 <html class="reftest-wait">
 <title>Reference for WebVTT rendering, 3 tracks enabled at the same time</title>
 <script src="/common/reftest-wait.js"></script>
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 html { overflow:hidden }
 body { margin:0 }
diff --git a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks.html b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks.html
index c0d5c161..bdb45f1 100644
--- a/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks.html
+++ b/third_party/blink/web_tests/external/wpt/webvtt/rendering/cues-with-video/processing-model/3_tracks.html
@@ -2,6 +2,7 @@
 <html class="reftest-wait">
 <title>WebVTT rendering, 3 tracks enabled at the same time</title>
 <link rel="match" href="3_tracks-ref.html">
+<link rel="stylesheet" type="text/css" href="/fonts/ahem.css" />
 <style>
 html { overflow:hidden }
 body { margin:0 }
diff --git a/third_party/blink/web_tests/fast/dom/shadow/apply-deep-in-document-scope-expected.txt b/third_party/blink/web_tests/fast/dom/shadow/apply-deep-in-document-scope-expected.txt
index faba39cd1..c6891e7 100644
--- a/third_party/blink/web_tests/fast/dom/shadow/apply-deep-in-document-scope-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/shadow/apply-deep-in-document-scope-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE WARNING: /deep/ combinator is no longer supported in CSS dynamic profile.It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.
+CONSOLE WARNING: /deep/ combinator is no longer supported in CSS dynamic profile. It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.
 /deep/ as a descendant selector in document without shadow trees.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
diff --git a/third_party/blink/web_tests/fast/dom/shadow/deep-combinator-for-video-expected.txt b/third_party/blink/web_tests/fast/dom/shadow/deep-combinator-for-video-expected.txt
index 76214ba..6d8f4ee 100644
--- a/third_party/blink/web_tests/fast/dom/shadow/deep-combinator-for-video-expected.txt
+++ b/third_party/blink/web_tests/fast/dom/shadow/deep-combinator-for-video-expected.txt
@@ -1 +1 @@
-CONSOLE WARNING: /deep/ combinator is no longer supported in CSS dynamic profile.It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.
+CONSOLE WARNING: /deep/ combinator is no longer supported in CSS dynamic profile. It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.
diff --git a/third_party/blink/web_tests/http/tests/devtools/cookie-resource-match.js b/third_party/blink/web_tests/http/tests/devtools/cookie-resource-match.js
index 8786669a..250ebe4 100644
--- a/third_party/blink/web_tests/http/tests/devtools/cookie-resource-match.js
+++ b/third_party/blink/web_tests/http/tests/devtools/cookie-resource-match.js
@@ -76,6 +76,6 @@
       session: true
     };
     var target = SDK.targetManager.mainTarget();
-    return SDK.CookieModel._parseProtocolCookie(protocolCookie);
+    return SDK.Cookie.fromProtocolCookie(protocolCookie);
   }
 })();
diff --git a/third_party/blink/web_tests/http/tests/devtools/network/network-cookies-pane-expected.txt b/third_party/blink/web_tests/http/tests/devtools/network/network-cookies-pane-expected.txt
index 148c514..01ea5a8 100644
--- a/third_party/blink/web_tests/http/tests/devtools/network/network-cookies-pane-expected.txt
+++ b/third_party/blink/web_tests/http/tests/devtools/network/network-cookies-pane-expected.txt
@@ -1,6 +1,7 @@
 Tests cookie pane rendering in Network panel
 
 --------------------------
+Request Cookies
 Name
 Value
 Domain
@@ -11,8 +12,6 @@
 Secure
 SameSite
 Name	Value	Domain	Path	Expires / Max-Age	Size	HttpOnly	Secure	SameSite
-Request Cookies					33
 mycookie	myvalue	N/A	N/A	N/A	17
 myother	myvalue2	N/A	N/A	N/A	16
-Response Cookies					0
 
diff --git a/third_party/blink/web_tests/shadow-dom/v0/closed-mode-deep-combinator-and-shadow-pseudo-expected.txt b/third_party/blink/web_tests/shadow-dom/v0/closed-mode-deep-combinator-and-shadow-pseudo-expected.txt
index 94ca512..647d5094 100644
--- a/third_party/blink/web_tests/shadow-dom/v0/closed-mode-deep-combinator-and-shadow-pseudo-expected.txt
+++ b/third_party/blink/web_tests/shadow-dom/v0/closed-mode-deep-combinator-and-shadow-pseudo-expected.txt
@@ -1,4 +1,4 @@
-CONSOLE WARNING: line 306: /deep/ combinator is no longer supported in CSS dynamic profile.It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.
+CONSOLE WARNING: line 306: /deep/ combinator is no longer supported in CSS dynamic profile. It is now effectively no-op, acting as if it were a descendant combinator. /deep/ combinator will be removed, and will be invalid at M65. You should remove it. See https://www.chromestatus.com/features/4964279606312960 for more details.
 (1/6) /deep/ style rule on top-level document.
 PASS backgroundColorOf('host_open_open') is "rgba(0, 0, 0, 0)"
 PASS backgroundColorOf('host_open_open/div1') is "rgba(0, 0, 0, 0)"
diff --git a/third_party/feed/README.chromium b/third_party/feed/README.chromium
index 3e5b712..61fcb4e 100644
--- a/third_party/feed/README.chromium
+++ b/third_party/feed/README.chromium
@@ -2,7 +2,7 @@
 Short name: feed
 URL: https://chromium.googlesource.com/feed
 Version: 0
-Revision: 760bb171ed66f8385aa3720d90ca532ae51354cc
+Revision: 8b5b413652b197f591d4d9ec03e366225954e868
 License: Apache 2.0
 License File: LICENSE
 Security Critical: yes
diff --git a/tools/clang/scripts/update.py b/tools/clang/scripts/update.py
index 7c3ff72f8..88d6b8f 100755
--- a/tools/clang/scripts/update.py
+++ b/tools/clang/scripts/update.py
@@ -37,8 +37,8 @@
 # Do NOT CHANGE this if you don't know what you're doing -- see
 # https://chromium.googlesource.com/chromium/src/+/master/docs/updating_clang.md
 # Reverting problematic clang rolls is safe, though.
-CLANG_REVISION = '6964027315f70c6817217d8dba0368fd3a274ba3'
-CLANG_SVN_REVISION = '370156'
+CLANG_REVISION = '8455294f2ac13d587b13d728038a9bffa7185f2b'
+CLANG_SVN_REVISION = '371202'
 CLANG_SUB_REVISION = 1
 
 PACKAGE_VERSION = '%s-%s-%s' % (CLANG_SVN_REVISION, CLANG_REVISION[:8],
diff --git a/tools/mb/mb_config.pyl b/tools/mb/mb_config.pyl
index 52af5585..25d46e9 100644
--- a/tools/mb/mb_config.pyl
+++ b/tools/mb/mb_config.pyl
@@ -71,7 +71,8 @@
       # This bot must use the gpu_tests mixin to match 'Android FYI Release (Nexus 5X)'
       # on the chromium.gpu waterfall, which it mirrors via trybots.pyl.
       'android-marshmallow-arm64-rel': 'gpu_tests_android_release_bot_minimal_symbols_arm64',
-      'android-oreo-arm64-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
+
+      'android-pie-arm64-rel': 'android_release_bot_minimal_symbols_arm64_webview_google',
     },
 
     'chromium.android.fyi': {
@@ -648,7 +649,7 @@
       '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-arm64-rel': 'android_release_trybot_arm64_webview_google',
       'android-pie-x86-fyi-rel': 'android_release_trybot_x86',
       'android_archive_rel_ng': 'android_release_trybot',
       'android_arm64_dbg_recipe': 'android_debug_trybot_compile_only_arm64',
@@ -1107,7 +1108,7 @@
 
     'android_release_bot_minimal_symbols_x86_resource_whitelisting': [
       'android', 'release_bot', 'minimal_symbols', 'x86',
-      'strip_debug_info', 'resource_whitelisting',
+      'resource_whitelisting',
     ],
 
     'android_release_thumb_bot': [
@@ -1136,8 +1137,7 @@
     ],
 
     'android_release_trybot_x86_resource_whitelisting': [
-      'android', 'release_trybot', 'strip_debug_info', 'x86',
-      'resource_whitelisting',
+      'android', 'release_trybot', 'x86', 'resource_whitelisting',
     ],
 
     'android_webview_google_debug_static_bot': [
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index ca96e32..fbf528f7 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -20553,6 +20553,7 @@
   <int value="1380" label="LOGINSTATE_GETSESSIONSTATE"/>
   <int value="1381" label="AUTOTESTPRIVATE_GETARCSTARTTIME"/>
   <int value="1382" label="AUTOTESTPRIVATE_SETOVERVIEWMODESTATE"/>
+  <int value="1383" label="AUTOTESTPRIVATE_TAKESCREENSHOTFORDISPLAY"/>
 </enum>
 
 <enum name="ExtensionIconState">
@@ -36037,6 +36038,8 @@
   <int value="-169745744" label="SyncPseudoUSSPreferences:enabled"/>
   <int value="-167744090" label="EnableHomeLauncher:enabled"/>
   <int value="-165756594" label="enable-touch-feedback"/>
+  <int value="-164539906"
+      label="OmniboxPreserveDefaultMatchAgainstAsyncUpdate:disabled"/>
   <int value="-161782023" label="AndroidMessagesProdEndpoint:enabled"/>
   <int value="-159877930" label="MaterialDesignUserManager:disabled"/>
   <int value="-158549277" label="enable-embeddedsearch-api"/>
@@ -37535,6 +37538,8 @@
   <int value="1918984253"
       label="OmniboxUIExperimentBlueSearchLoopAndSearchQuery:disabled"/>
   <int value="1919917329" label="ImplicitRootScroller:disabled"/>
+  <int value="1920894670"
+      label="OmniboxPreserveDefaultMatchAgainstAsyncUpdate:enabled"/>
   <int value="1924192543" label="ProactiveTabFreezeAndDiscard:enabled"/>
   <int value="1925627218" label="FullscreenToolbarReveal:disabled"/>
   <int value="1926524951" label="SystemWebApps:disabled"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 9db4ecd4..e6404c68 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -4897,6 +4897,38 @@
   </summary>
 </histogram>
 
+<histogram name="Apps.AppList.ZeroStateResultsList.Clicked"
+    enum="BooleanClicked">
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    Whether an item was clicked from zero state search results, or if results
+    were displayed for some amount time but not clicked. These clicks and
+    impressions are used for calculating CTR metrics.
+  </summary>
+</histogram>
+
+<histogram name="Apps.AppList.ZeroStateResultsList.LaunchedItemPosition"
+    units="position">
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    The position index of an item launched from zero state search results.
+  </summary>
+</histogram>
+
+<histogram name="Apps.AppList.ZeroStateResultsList.NumImpressionTypes"
+    units="count">
+  <owner>wrong@chromium.org</owner>
+  <owner>tby@chromium.org</owner>
+  <owner>jiameng@chromium.org</owner>
+  <summary>
+    The number of item types included in each zero state impression set.
+  </summary>
+</histogram>
+
 <histogram name="Apps.AppList.ZeroStateSearchResultRemovalDecision"
     enum="AppListZeroStateResultRemovalConfirmation" expires_after="2020-03-01">
   <owner>jennyz@chromium.org</owner>
@@ -137142,7 +137174,10 @@
   </summary>
 </histogram>
 
-<histogram name="Startup.SameVersionStartupCount" expires_after="2020-02-23">
+<histogram name="Startup.SameVersionStartupCount">
+  <obsolete>
+    Deprecated 8/2019. See startup_metric_utils.cc for a summary of old data.
+  </obsolete>
   <owner>fdoray@chromium.org</owner>
   <owner>gab@chromium.org</owner>
   <summary>
@@ -173139,6 +173174,9 @@
 </histogram_suffixes>
 
 <histogram_suffixes name="SameVersionStartupCounts" separator=".">
+  <obsolete>
+    Deprecated 8/2019. Was used to better understand DLL prefetching.
+  </obsolete>
   <suffix name="1" label="1st startup with same version"/>
   <suffix name="2" label="2nd startup with same version"/>
   <suffix name="3" label="3rd startup with same version"/>
diff --git a/tools/perf/OWNERS b/tools/perf/OWNERS
index b1cbb97..937bd08e 100644
--- a/tools/perf/OWNERS
+++ b/tools/perf/OWNERS
@@ -14,6 +14,9 @@
 # For expectations.config file format infrastructure changes.
 rmhasan@google.com
 
+# For gtest perf tests
+bsheedy@chromium.org
+
 # For system health stories.
 charliea@chromium.org
 eyaich@chromium.org
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index fbac672..94644b4 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -318,6 +318,7 @@
 # Benchmark: system_health.memory_desktop
 crbug.com/984599 [ linux ] system_health.memory_desktop/long_running:tools:gmail-foreground [ Skip ]
 crbug.com/984599 [ mac ] system_health.memory_desktop/long_running:tools:gmail-background [ Skip ]
+crbug.com/1000426 [ win ] system_health.memory_desktop/long_running:tools:gmail-background [ Skip ]
 crbug.com/984599 [ mac ] system_health.memory_desktop/long_running:tools:gmail-foreground [ Skip ]
 crbug.com/649392 system_health.memory_desktop/play:media:google_play_music [ Skip ]
 crbug.com/742475 [ mac ] system_health.memory_desktop/multitab:misc:typical24 [ Skip ]
diff --git a/ui/android/java/res/values/colors.xml b/ui/android/java/res/values/colors.xml
index 2887b953..9dde0091 100644
--- a/ui/android/java/res/values/colors.xml
+++ b/ui/android/java/res/values/colors.xml
@@ -68,7 +68,10 @@
     <color name="dropdown_dark_divider_color">#C0C0C0</color>
 
     <!-- Snackbar colors -->
-    <color name="snackbar_background_color" tools:ignore="UnusedResources">@color/modern_primary_color</color>
+    <color name="snackbar_background_color">@color/modern_primary_color</color>
+
+    <!-- Infobar colors -->
+    <color name="infobar_background_color" tools:ignore="UnusedResources">@color/snackbar_background_color</color>
 
     <!-- Bottom sheet colors -->
     <color name="sheet_bg_color" tools:ignore="UnusedResources">@color/modern_primary_color</color>
diff --git a/ui/android/resources/resource_manager_impl_unittest.cc b/ui/android/resources/resource_manager_impl_unittest.cc
index 1142ce9..70096bdc 100644
--- a/ui/android/resources/resource_manager_impl_unittest.cc
+++ b/ui/android/resources/resource_manager_impl_unittest.cc
@@ -113,7 +113,7 @@
   }
 
  private:
-  base::test::TaskEnvironment task_environment_;
+  base::test::SingleThreadTaskEnvironment task_environment_;
   WindowAndroid* window_android_;
 
  protected:
diff --git a/ui/base/mpris/README.md b/ui/base/mpris/README.md
new file mode 100644
index 0000000..65c971b4
--- /dev/null
+++ b/ui/base/mpris/README.md
@@ -0,0 +1,9 @@
+The Media Player Remote Interfacing Specification (MPRIS) is a D-Bus interface
+for controlling media players:
+https://specifications.freedesktop.org/mpris-spec/2.2/
+
+This component is Chromium's implementation of the MPRIS interface. This allows
+MPRIS clients to control media playback and view metadata about currently
+playing media.
+
+MPRIS is available for desktop Linux when D-Bus is available.
diff --git a/ui/base/mpris/buildflags/buildflags.gni b/ui/base/mpris/buildflags/buildflags.gni
index d3dc1f2..cc386dfb 100644
--- a/ui/base/mpris/buildflags/buildflags.gni
+++ b/ui/base/mpris/buildflags/buildflags.gni
@@ -5,5 +5,7 @@
 import("//build/config/features.gni")
 
 declare_args() {
+  # Enables Chromium implementation of the MPRIS D-Bus interface for controlling
+  # media playback. See ../README.md for details.
   use_mpris = is_desktop_linux && use_dbus
 }
diff --git a/ui/gl/features.gni b/ui/gl/features.gni
index 456990c..a90d220 100644
--- a/ui/gl/features.gni
+++ b/ui/gl/features.gni
@@ -14,5 +14,5 @@
   # Should Dawn support be compiled to back the WebGPU implementation.
   # Also controls linking Dawn depedencies in such as SPIRV-Tools and
   # SPIRV-Cross
-  use_dawn = false
+  use_dawn = is_mac
 }
diff --git a/ui/gl/gl_image_d3d.cc b/ui/gl/gl_image_d3d.cc
index 63a66d970..e523833 100644
--- a/ui/gl/gl_image_d3d.cc
+++ b/ui/gl/gl_image_d3d.cc
@@ -24,7 +24,6 @@
       texture_(std::move(texture)),
       swap_chain_(std::move(swap_chain)) {
   DCHECK(texture_);
-  DCHECK(swap_chain_);
 }
 
 GLImageD3D::~GLImageD3D() {
diff --git a/ui/shell_dialogs/BUILD.gn b/ui/shell_dialogs/BUILD.gn
index 872767c..2a2a423 100644
--- a/ui/shell_dialogs/BUILD.gn
+++ b/ui/shell_dialogs/BUILD.gn
@@ -112,3 +112,17 @@
     deps += [ "//components/remote_cocoa/app_shim" ]
   }
 }
+
+source_set("test_support") {
+  testonly = true
+  sources = [
+    "fake_select_file_dialog.cc",
+    "fake_select_file_dialog.h",
+  ]
+  public_deps = [
+    ":shell_dialogs",
+  ]
+  deps = [
+    "//base",
+  ]
+}
diff --git a/ui/shell_dialogs/fake_select_file_dialog.cc b/ui/shell_dialogs/fake_select_file_dialog.cc
new file mode 100644
index 0000000..1018798
--- /dev/null
+++ b/ui/shell_dialogs/fake_select_file_dialog.cc
@@ -0,0 +1,88 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ui/shell_dialogs/fake_select_file_dialog.h"
+
+#include "base/logging.h"
+#include "ui/shell_dialogs/select_file_policy.h"
+
+namespace ui {
+
+FakeSelectFileDialog::Factory::Factory() = default;
+FakeSelectFileDialog::Factory::~Factory() = default;
+
+ui::SelectFileDialog* FakeSelectFileDialog::Factory::Create(
+    ui::SelectFileDialog::Listener* listener,
+    std::unique_ptr<ui::SelectFilePolicy> policy) {
+  FakeSelectFileDialog* dialog =
+      new FakeSelectFileDialog(opened_callback_, listener, std::move(policy));
+  last_dialog_ = dialog->GetWeakPtr();
+  return dialog;
+}
+
+FakeSelectFileDialog* FakeSelectFileDialog::Factory::GetLastDialog() const {
+  return last_dialog_.get();
+}
+
+void FakeSelectFileDialog::Factory::SetOpenCallback(
+    base::RepeatingClosure callback) {
+  opened_callback_ = callback;
+}
+
+// static
+FakeSelectFileDialog::Factory* FakeSelectFileDialog::RegisterFactory() {
+  Factory* factory = new Factory;
+  ui::SelectFileDialog::SetFactory(factory);
+  return factory;
+}
+
+FakeSelectFileDialog::FakeSelectFileDialog(
+    const base::RepeatingClosure& opened,
+    Listener* listener,
+    std::unique_ptr<ui::SelectFilePolicy> policy)
+    : ui::SelectFileDialog(listener, std::move(policy)), opened_(opened) {}
+
+FakeSelectFileDialog::~FakeSelectFileDialog() = default;
+
+bool FakeSelectFileDialog::HasMultipleFileTypeChoicesImpl() {
+  return true;
+}
+
+bool FakeSelectFileDialog::IsRunning(gfx::NativeWindow owning_window) const {
+  return true;
+}
+
+void FakeSelectFileDialog::SelectFileImpl(
+    Type type,
+    const base::string16& title,
+    const base::FilePath& default_path,
+    const FileTypeInfo* file_types,
+    int file_type_index,
+    const base::FilePath::StringType& default_extension,
+    gfx::NativeWindow owning_window,
+    void* params) {
+  title_ = title;
+  params_ = params;
+  if (file_types)
+    file_types_ = *file_types;
+  default_extension_ = base::FilePath(default_extension).MaybeAsASCII();
+  opened_.Run();
+}
+
+bool FakeSelectFileDialog::CallFileSelected(const base::FilePath& file_path,
+                                            base::StringPiece filter_text) {
+  for (size_t index = 0; index < file_types_.extensions.size(); ++index) {
+    for (const base::FilePath::StringType& ext :
+         file_types_.extensions[index]) {
+      if (base::FilePath(ext).MaybeAsASCII() == filter_text) {
+        // FileSelected accepts a 1-based index.
+        listener_->FileSelected(file_path, index + 1, params_);
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+}  // namespace ui
diff --git a/ui/shell_dialogs/fake_select_file_dialog.h b/ui/shell_dialogs/fake_select_file_dialog.h
new file mode 100644
index 0000000..1846254
--- /dev/null
+++ b/ui/shell_dialogs/fake_select_file_dialog.h
@@ -0,0 +1,111 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef UI_SHELL_DIALOGS_FAKE_SELECT_FILE_DIALOG_H_
+#define UI_SHELL_DIALOGS_FAKE_SELECT_FILE_DIALOG_H_
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
+#include "ui/shell_dialogs/select_file_dialog.h"
+#include "ui/shell_dialogs/select_file_dialog_factory.h"
+#include "ui/shell_dialogs/shell_dialogs_export.h"
+
+namespace ui {
+
+// A test fake SelectFileDialog. Usage:
+//
+// FakeSelectFileDialog::Factory* factory =
+//   FakeSelectFileDialog::RegisterFactory();
+// factory->SetOpenCallback(open_callback);
+//
+// Now calls to SelectFileDialog::Create() will create a |FakeSelectFileDialog|,
+// and open_callback is invoked when the dialog is opened.
+//
+// Once the dialog is opened, use factory->GetLastDialog() to access the dialog
+// to query file information and select a file.
+class FakeSelectFileDialog : public SelectFileDialog {
+ public:
+  // A |FakeSelectFileDialog::Factory| which creates |FakeSelectFileDialog|
+  // instances.
+  class Factory : public SelectFileDialogFactory {
+   public:
+    Factory();
+    ~Factory() override;
+    Factory(const Factory&) = delete;
+    Factory& operator=(const Factory&) = delete;
+
+    // SelectFileDialog::Factory.
+    ui::SelectFileDialog* Create(
+        ui::SelectFileDialog::Listener* listener,
+        std::unique_ptr<ui::SelectFilePolicy> policy) override;
+
+    // Returns the last opened dialog, or null if one has not been opened yet.
+    FakeSelectFileDialog* GetLastDialog() const;
+
+    // Sets a callback to be called when a new fake dialog has been opened.
+    void SetOpenCallback(base::RepeatingClosure callback);
+
+   private:
+    base::RepeatingClosure opened_callback_;
+    base::WeakPtr<FakeSelectFileDialog> last_dialog_;
+  };
+
+  // Creates a |Factory| and registers it with |SelectFileDialog::SetFactory|,
+  // which owns the new factory. This factory will create new
+  // |FakeSelectFileDialog| instances upon calls to
+  // |SelectFileDialog::Create()|.
+  static Factory* RegisterFactory();
+
+  FakeSelectFileDialog(const base::RepeatingClosure& opened,
+                       Listener* listener,
+                       std::unique_ptr<SelectFilePolicy> policy);
+  FakeSelectFileDialog(const FakeSelectFileDialog&) = delete;
+  FakeSelectFileDialog& operator=(const FakeSelectFileDialog&) = delete;
+
+  // SelectFileDialog.
+  void SelectFileImpl(Type type,
+                      const base::string16& title,
+                      const base::FilePath& default_path,
+                      const FileTypeInfo* file_types,
+                      int file_type_index,
+                      const base::FilePath::StringType& default_extension,
+                      gfx::NativeWindow owning_window,
+                      void* params) override;
+  bool HasMultipleFileTypeChoicesImpl() override;
+  bool IsRunning(gfx::NativeWindow owning_window) const override;
+  void ListenerDestroyed() override {}
+
+  // Returns the file title provided to the dialog.
+  const base::string16& title() const { return title_; }
+  // Returns the file types provided to the dialog.
+  const FileTypeInfo& file_types() const { return file_types_; }
+  // Returns the default file extension provided to the dialog.
+  const std::string& default_extension() const { return default_extension_; }
+
+  // Calls the |FileSelected()| method on listener(). |filter_text| selects
+  // which file extension filter to report.
+  bool CallFileSelected(const base::FilePath& file_path,
+                        base::StringPiece filter_text) WARN_UNUSED_RESULT;
+
+  base::WeakPtr<FakeSelectFileDialog> GetWeakPtr() {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
+ private:
+  ~FakeSelectFileDialog() override;
+
+  base::RepeatingClosure opened_;
+  base::string16 title_;
+  FileTypeInfo file_types_;
+  std::string default_extension_;
+  void* params_;
+  base::WeakPtrFactory<FakeSelectFileDialog> weak_ptr_factory_{this};
+};
+
+}  // namespace ui
+
+#endif  // UI_SHELL_DIALOGS_FAKE_SELECT_FILE_DIALOG_H_
diff --git a/ui/views/controls/focus_ring.cc b/ui/views/controls/focus_ring.cc
index 01e1d6e..e40f16d 100644
--- a/ui/views/controls/focus_ring.cc
+++ b/ui/views/controls/focus_ring.cc
@@ -184,9 +184,9 @@
 SkPath GetHighlightPath(const View* view) {
   SkPath path = GetHighlightPathInternal(view);
   if (view->flip_canvas_on_paint_for_rtl_ui()) {
-    gfx::Point center = view->GetContentsBounds().CenterPoint();
+    gfx::Point center = view->GetLocalBounds().CenterPoint();
     SkMatrix flip;
-    flip.setRotate(180, center.x(), center.y());
+    flip.setScale(-1, 1, center.x(), center.y());
     path.transform(flip);
   }
   return path;
diff --git a/ui/views/controls/scrollbar/scroll_bar_views.cc b/ui/views/controls/scrollbar/scroll_bar_views.cc
index bef64bf..0242df1 100644
--- a/ui/views/controls/scrollbar/scroll_bar_views.cc
+++ b/ui/views/controls/scrollbar/scroll_bar_views.cc
@@ -253,12 +253,14 @@
                             size.height());
   }
 
+  // All that matters here is updating the thumb X or Y coordinate (for vertical
+  // or horizontal scrollbars, respectively); the rest of the bounds will be
+  // overwritten by ScrollBar::Update() shortly.
   GetThumb()->SetBoundsRect(GetTrackBounds());
 }
 
 void ScrollBarViews::OnPaint(gfx::Canvas* canvas) {
   gfx::Rect bounds = GetTrackBounds();
-
   if (bounds.IsEmpty())
     return;
 
@@ -267,8 +269,28 @@
   params_.scrollbar_track.track_width = bounds.width();
   params_.scrollbar_track.track_height = bounds.height();
   params_.scrollbar_track.classic_state = 0;
+  const BaseScrollBarThumb* thumb = GetThumb();
 
-  GetNativeTheme()->Paint(canvas->sk_canvas(), part_, state_, bounds, params_);
+  params_.scrollbar_track.is_upper = true;
+  gfx::Rect upper_bounds = bounds;
+  if (IsHorizontal())
+    upper_bounds.set_width(thumb->x() - upper_bounds.x());
+  else
+    upper_bounds.set_height(thumb->y() - upper_bounds.y());
+  if (!upper_bounds.IsEmpty()) {
+    GetNativeTheme()->Paint(canvas->sk_canvas(), part_, state_, upper_bounds,
+                            params_);
+  }
+
+  params_.scrollbar_track.is_upper = false;
+  if (IsHorizontal())
+    bounds.Inset(thumb->bounds().right() - bounds.x(), 0, 0, 0);
+  else
+    bounds.Inset(0, thumb->bounds().bottom() - bounds.y(), 0, 0);
+  if (!bounds.IsEmpty()) {
+    GetNativeTheme()->Paint(canvas->sk_canvas(), part_, state_, bounds,
+                            params_);
+  }
 }
 
 gfx::Size ScrollBarViews::CalculatePreferredSize() const {
diff --git a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
index 3a5d162..86cb490c 100644
--- a/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
+++ b/ui/webui/resources/cr_components/chromeos/network/network_siminfo.js
@@ -525,7 +525,8 @@
   /**
    * Checks whether |pin1| is of the proper length and contains only digits.
    * If opt_pin2 is not undefined, then it also checks whether pin1 and
-   * opt_pin2 match. On any failure, sets |this.error_| and returns false.
+   * opt_pin2 match. On any failure, sets |this.error_|, focuses the invalid
+   * PIN, and returns false.
    * @param {string} pin1
    * @param {string=} opt_pin2
    * @return {boolean} True if the pins match and are of minimum length.
@@ -537,10 +538,12 @@
     }
     if (pin1.length < PIN_MIN_LENGTH || !DIGITS_ONLY_REGEX.test(pin1)) {
       this.error_ = ErrorType.INVALID_PIN;
+      this.focusDialogInput_();
       return false;
     }
     if (opt_pin2 != undefined && pin1 != opt_pin2) {
       this.error_ = ErrorType.MISMATCHED_PIN;
+      this.focusDialogInput_();
       return false;
     }
     return true;
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
index b33f602..8088f17 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.html
@@ -195,7 +195,7 @@
     <div id="root" on-contextmenu="onContextMenu_" on-tap="onRootTap_">
       <div id="pinInputDiv">
         <cr-input id="pinInput" type="password" value="{{value}}"
-            is-input-rtl$="[[isInputRtl_(value)]]"
+            is-input-rtl$="[[isInputRtl_(value)]]" aria-label="[[ariaLabel]]"
             has-content$="[[hasInput_(value)]]" invalid="[[hasError]]"
             placeholder="[[getInputPlaceholder_(enablePassword,
                 enablePlaceholder)]]"
diff --git a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
index 622117cf..315513c 100644
--- a/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
+++ b/ui/webui/resources/cr_components/chromeos/quick_unlock/pin_keyboard.js
@@ -138,6 +138,13 @@
       type: Boolean,
       value: false,
     },
+
+    /**
+     * The aria label to be used for the input element.
+     */
+    ariaLabel: {
+      type: String,
+    },
   },
 
   listeners: {
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.html b/ui/webui/resources/cr_elements/cr_input/cr_input.html
index 3ff417ad..073b2c6 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.html
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.html
@@ -120,6 +120,7 @@
               readonly$="[[readonly]]" maxlength$="[[maxlength]]"
               pattern$="[[pattern]]" required="[[required]]"
               minlength$="[[minlength]]"
+              aria-label$="[[getAriaLabel_(ariaLabel, label, placeholder)]]"
               max="[[max]]" min="[[min]]" on-focus="onInputFocus_"
               on-blur="onInputBlur_" on-change="onInputChange_"
               on-keydown="onInputKeydown_" part="input">
diff --git a/ui/webui/resources/cr_elements/cr_input/cr_input.js b/ui/webui/resources/cr_elements/cr_input/cr_input.js
index eed22f86..8604791 100644
--- a/ui/webui/resources/cr_elements/cr_input/cr_input.js
+++ b/ui/webui/resources/cr_elements/cr_input/cr_input.js
@@ -38,7 +38,10 @@
   is: 'cr-input',
 
   properties: {
-    ariaLabel: String,
+    ariaLabel: {
+      type: String,
+      value: '',
+    },
 
     autofocus: {
       type: Boolean,
@@ -109,6 +112,7 @@
 
     placeholder: {
       type: String,
+      value: null,
       observer: 'placeholderChanged_',
     },
 
@@ -156,11 +160,6 @@
 
   /** @override */
   attached: function() {
-    const ariaLabel = this.ariaLabel || this.label || this.placeholder;
-    if (ariaLabel) {
-      this.inputElement.setAttribute('aria-label', ariaLabel);
-    }
-
     // Run this for the first time in attached instead of in disabledChanged_
     // since this.tabindex might not be set yet then.
     if (this.disabled) {
@@ -173,6 +172,15 @@
     return /** @type {!HTMLInputElement} */ (this.$.input);
   },
 
+  /**
+   * Returns the aria label to be used with the input element.
+   * @return {string}
+   * @private
+   */
+  getAriaLabel_: function(ariaLabel, label, placeholder) {
+    return ariaLabel || label || placeholder;
+  },
+
   /** @private */
   disabledChanged_: function(current, previous) {
     this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');