diff --git a/BUILD.gn b/BUILD.gn
index 281dccc8a..8571278 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -218,7 +218,9 @@
   } else if (is_fuchsia) {
     deps += [
       ":d8_fuchsia",
-      "build/fuchsia/fidlgen_js:fidlgen_js_unittests",
+      "//build/fuchsia/fidlgen_js:fidlgen_js_unittests",
+      "//fuchsia:gn_all",
+      "//headless",
     ]
   }
 
@@ -763,15 +765,6 @@
     }
   }
 
-  if (is_fuchsia) {
-    deps += [
-      "//fuchsia:webrunner_unittests",
-      "//fuchsia/http:http_service_tests",
-      "//fuchsia/runners:cast_runner_unittests",
-      "//headless",
-    ]
-  }
-
   if (enable_vulkan) {
     deps += [ "//gpu/vulkan/demo" ]
   }
diff --git a/DEPS b/DEPS
index 37fe6b9..9b8bbcec 100644
--- a/DEPS
+++ b/DEPS
@@ -121,11 +121,11 @@
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling Skia
   # and whatever else without interference from each other.
-  'skia_revision': 'd20cf33472417ef8156553033f5faa2f2d8850bb',
+  'skia_revision': '1345366c21ea285b4225ce9bf0de4ef401d76d72',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling V8
   # and whatever else without interference from each other.
-  'v8_revision': 'd00f37dbe0937f28b8d74e49506789bbd16ff8b5',
+  'v8_revision': '8aa4af2f9d67491825b354b6c7207ed47e0595f5',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling swarming_client
   # and whatever else without interference from each other.
@@ -141,11 +141,11 @@
   # 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': '5ebd2c0fafca2f0d570ca25605092b5c7aaadd42',
+  'swiftshader_revision': 'ebe5f7fad06476b2828271977ee0d56ee45385ac',
   # 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': '96a7c58e4c7196b2f789b3e7177f580caed3417d',
+  'pdfium_revision': 'a2e2d7f291cfa232c3e5ef48d086c0002b0e06df',
   # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling openmax_dl
   # and whatever else without interference from each other.
@@ -694,7 +694,7 @@
 
   # Build tools for Chrome OS. Note: This depends on third_party/pyelftools.
   'src/third_party/chromite': {
-      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + 'baf62c129325a527911fa12a625498ecb5cd39a8',
+      'url': Var('chromium_git') + '/chromiumos/chromite.git' + '@' + '6e669b46b4792ae79198377d36757053c9b445aa',
       'condition': 'checkout_linux',
   },
 
@@ -1054,7 +1054,7 @@
   },
 
   'src/third_party/perfetto':
-    Var('android_git') + '/platform/external/perfetto.git' + '@' +  'faa547c0731a568980a712c238b22692d29649f4',
+    Var('android_git') + '/platform/external/perfetto.git' + '@' +  '11f908788289acc0e395bdfc38fdf19fecfeec26',
 
   'src/third_party/perl': {
       'url': Var('chromium_git') + '/chromium/deps/perl.git' + '@' + 'ac0d98b5cee6c024b0cffeb4f8f45b6fc5ccdb78',
@@ -1217,7 +1217,7 @@
     Var('chromium_git') + '/external/khronosgroup/webgl.git' + '@' + 'db52df17f0d012983dc281e4864c71485a86bd0e',
 
   'src/third_party/webrtc':
-    Var('webrtc_git') + '/src.git' + '@' + '9f3a44f5152816e3a0cb6902e0dadf365ff4e7d0',
+    Var('webrtc_git') + '/src.git' + '@' + '01f64e0eb22d855fc769b6976ba62dd0d94a071a',
 
   'src/third_party/xdg-utils': {
       'url': Var('chromium_git') + '/chromium/deps/xdg-utils.git' + '@' + 'd80274d5869b17b8c9067a1022e4416ee7ed5e0d',
@@ -1258,7 +1258,7 @@
     Var('chromium_git') + '/v8/v8.git' + '@' +  Var('v8_revision'),
 
   'src-internal': {
-    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@0ec02f9abd06dcf9f97c6612927cd537d12baaad',
+    'url': 'https://chrome-internal.googlesource.com/chrome/src-internal.git@26fd840339f85a6169bbbc0cd0d4cd2d847ea0e5',
     'condition': 'checkout_src_internal',
   },
 
diff --git a/OWNERS b/OWNERS
index e2b9e16..61637e8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -13,7 +13,6 @@
 per-file .vpython=dpranke@chromium.org
 per-file .vpython=iannucci@chromium.org
 per-file .vpython=jbudorick@chromium.org
-per-file .vpython=nednguyen@chromium.org
 per-file AUTHORS=*
 per-file BUILD.gn=file://build/OWNERS
 per-file codereview.settings=agable@chromium.org
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/SharedStatics.java b/android_webview/glue/java/src/com/android/webview/chromium/SharedStatics.java
index 3a9cc34b..18db7a5 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/SharedStatics.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/SharedStatics.java
@@ -19,6 +19,8 @@
 import org.chromium.base.MemoryPressureLevel;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.memory.MemoryPressureMonitor;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.List;
 
@@ -70,7 +72,7 @@
 
     public void freeMemoryForTests() {
         if (ActivityManager.isRunningInTestHarness()) {
-            ThreadUtils.postOnUiThread(() -> {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 // This variable is needed to prevent weird formatting by "git cl format".
                 MemoryPressureMonitor pressureMonitor = MemoryPressureMonitor.INSTANCE;
                 pressureMonitor.notifyPressure(MemoryPressureLevel.CRITICAL);
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
index 59c739c9..ee1f234d 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromium.java
@@ -67,9 +67,11 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.CachedMetrics.TimesHistogramSample;
+import org.chromium.base.task.PostTask;
 import org.chromium.components.autofill.AutofillProvider;
 import org.chromium.content_public.browser.NavigationHistory;
 import org.chromium.content_public.browser.SmartClipProvider;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -428,7 +430,7 @@
     private void checkThread() {
         if (!ThreadUtils.runningOnUiThread()) {
             final RuntimeException threadViolation = createThreadException();
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     throw threadViolation;
@@ -2306,7 +2308,7 @@
     public void setBackgroundColor(final int color) {
         mFactory.startYourEngines(false);
         if (checkNeedsPost()) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     setBackgroundColor(color);
@@ -2323,7 +2325,7 @@
         // is still null. We set the layer type in initForReal in that case.
         if (mAwContents == null) return;
         if (checkNeedsPost()) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     setLayerType(layerType, paint);
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
index a873c92..20a6636 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewChromiumAwInit.java
@@ -48,6 +48,8 @@
 import org.chromium.base.metrics.CachedMetrics;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
 
 /**
@@ -280,7 +282,7 @@
 
         // We must post to the UI thread to cover the case that the user has invoked Chromium
         // startup by using the (thread-safe) CookieManager rather than creating a WebView.
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 synchronized (mLock) {
@@ -457,7 +459,7 @@
                 return;
             }
 
-            ThreadUtils.postOnUiThread(() -> FieldTrialList.logActiveTrials());
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> FieldTrialList.logActiveTrials());
         });
     }
 
diff --git a/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java b/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java
index 7cb28892..ffa63d1 100644
--- a/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java
+++ b/android_webview/glue/java/src/com/android/webview/chromium/WebViewContentsClientAdapter.java
@@ -51,8 +51,9 @@
 import org.chromium.android_webview.permission.Resource;
 import org.chromium.base.Callback;
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.ref.WeakReference;
 import java.security.Principal;
@@ -433,14 +434,11 @@
             // no further updates after onPageStarted, we'll fail the test by timing
             // out waiting for a Picture.
             if (mPictureListener != null) {
-                ThreadUtils.postOnUiThreadDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (mPictureListener != null) {
-                            if (TRACE) Log.i(TAG, "onPageFinished-fake");
-                            mPictureListener.onNewPicture(mWebView,
-                                    mPictureListenerInvalidateOnly ? null : new Picture());
-                        }
+                PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
+                    if (mPictureListener != null) {
+                        if (TRACE) Log.i(TAG, "onPageFinished-fake");
+                        mPictureListener.onNewPicture(
+                                mWebView, mPictureListenerInvalidateOnly ? null : new Picture());
                     }
                 }, 100);
             }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwContents.java b/android_webview/java/src/org/chromium/android_webview/AwContents.java
index fad31c8..f8df2c5 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwContents.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwContents.java
@@ -59,6 +59,7 @@
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.components.autofill.AutofillProvider;
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 import org.chromium.components.navigation_interception.NavigationParams;
@@ -77,6 +78,7 @@
 import org.chromium.content_public.browser.SelectionClient;
 import org.chromium.content_public.browser.SelectionPopupController;
 import org.chromium.content_public.browser.SmartClipProvider;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsAccessibility;
@@ -1838,7 +1840,7 @@
         // This is a workaround for an issue with PlzNavigate and one of Samsung's OEM mail apps.
         // See http://crbug.com/781535.
         if (isSamsungMailApp() && SAMSUNG_WORKAROUND_BASE_URL.equals(loadUrlParams.getBaseUrl())) {
-            ThreadUtils.postOnUiThreadDelayed(
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
                     () -> loadUrl(loadUrlParams), SAMSUNG_WORKAROUND_DELAY);
             return;
         }
diff --git a/android_webview/java/src/org/chromium/android_webview/AwGeolocationPermissions.java b/android_webview/java/src/org/chromium/android_webview/AwGeolocationPermissions.java
index 6587df2..15e0365 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwGeolocationPermissions.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwGeolocationPermissions.java
@@ -6,7 +6,8 @@
 
 import android.content.SharedPreferences;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.GURLUtils;
 
 import java.util.HashSet;
@@ -100,7 +101,7 @@
      */
     public void getAllowed(String origin, final org.chromium.base.Callback<Boolean> callback) {
         final boolean finalAllowed = isOriginAllowed(origin);
-        ThreadUtils.postOnUiThread(() -> callback.onResult(finalAllowed));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onResult(finalAllowed));
     }
 
     /**
@@ -113,7 +114,7 @@
                 origins.add(name.substring(PREF_PREFIX.length()));
             }
         }
-        ThreadUtils.postOnUiThread(() -> callback.onResult(origins));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onResult(origins));
     }
 
     /**
diff --git a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
index 0422649f..dc410ea 100644
--- a/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
+++ b/android_webview/java/src/org/chromium/android_webview/AwWebContentsObserver.java
@@ -5,7 +5,8 @@
 package org.chromium.android_webview;
 
 import org.chromium.android_webview.AwContents.VisualStateCallback;
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.content_public.common.ContentUrlConstants;
@@ -121,7 +122,7 @@
         // Only invoke the onPageCommitVisible callback when navigating to a different document,
         // but not when navigating to a different fragment within the same document.
         if (!isSameDocument) {
-            ThreadUtils.postOnUiThread(() -> {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 AwContents awContents = mAwContents.get();
                 if (awContents != null) {
                     awContents.insertVisualStateCallbackIfNotDestroyed(
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
index d5e813b9..5840b9f 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/VisualStateTest.java
@@ -27,10 +27,12 @@
 import org.chromium.android_webview.test.util.GraphicsTestUtils;
 import org.chromium.android_webview.test.util.JavascriptEventObserver;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.Feature;
 import org.chromium.content_public.browser.JavascriptInjector;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.util.DOMUtils;
 
@@ -108,7 +110,8 @@
         @Override
         public InputStream getData() {
             final DelayedInputStream stream = (DelayedInputStream) super.getData();
-            ThreadUtils.postOnUiThreadDelayed(() -> stream.allowReads(), IMAGE_LOADING_DELAY_MS);
+            PostTask.postDelayedTask(
+                    UiThreadTaskTraits.DEFAULT, () -> stream.allowReads(), IMAGE_LOADING_DELAY_MS);
             return stream;
         }
     }
@@ -427,7 +430,7 @@
         // JS will notify this observer once it has changed the background color of the page.
         final Object pageChangeNotifier = new Object() {
             public void onPageChanged() {
-                ThreadUtils.postOnUiThread(
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT,
                         () -> awContents.insertVisualStateCallback(20, new VisualStateCallback() {
                             @Override
                             public void onComplete(long id) {
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 17631d33..93f9ea74 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1391,8 +1391,9 @@
   testonly = true
   sources = [
     "../ui/views/test/test_views_delegate_aura.cc",
-    "shell/app_list.cc",
     "shell/bubble.cc",
+    "shell/example_app_list_client.cc",
+    "shell/example_app_list_client.h",
     "shell/example_factory.h",
     "shell/example_session_controller_client.cc",
     "shell/example_session_controller_client.h",
diff --git a/ash/app_list/model/search/search_result.h b/ash/app_list/model/search/search_result.h
index f3f7760..d3255cc 100644
--- a/ash/app_list/model/search/search_result.h
+++ b/ash/app_list/model/search/search_result.h
@@ -141,6 +141,9 @@
   ash::mojom::SearchResultMetadataPtr TakeMetadata() {
     return std::move(metadata_);
   }
+  ash::mojom::SearchResultMetadataPtr CloneMetadata() const {
+    return metadata_->Clone();
+  }
 
  protected:
   void set_id(const std::string& id) { metadata_->id = id; }
diff --git a/ash/app_list/test/test_app_list_client.cc b/ash/app_list/test/test_app_list_client.cc
index f54e2995..f800f497 100644
--- a/ash/app_list/test/test_app_list_client.cc
+++ b/ash/app_list/test/test_app_list_client.cc
@@ -4,7 +4,7 @@
 
 #include "ash/app_list/test/test_app_list_client.h"
 
-#include "ash/shell.h"
+#include <utility>
 
 namespace ash {
 
@@ -18,4 +18,16 @@
   return ptr;
 }
 
+void TestAppListClient::GetSearchResultContextMenuModel(
+    const std::string& result_id,
+    GetContextMenuModelCallback callback) {
+  std::move(callback).Run({});
+}
+
+void TestAppListClient::GetContextMenuModel(
+    const std::string& id,
+    GetContextMenuModelCallback callback) {
+  std::move(callback).Run({});
+}
+
 }  // namespace ash
diff --git a/ash/app_list/test/test_app_list_client.h b/ash/app_list/test/test_app_list_client.h
index 098c89f..28752b90 100644
--- a/ash/app_list/test/test_app_list_client.h
+++ b/ash/app_list/test/test_app_list_client.h
@@ -33,7 +33,7 @@
                                 int event_flags) override {}
   void GetSearchResultContextMenuModel(
       const std::string& result_id,
-      GetContextMenuModelCallback callback) override {}
+      GetContextMenuModelCallback callback) override;
   void SearchResultContextMenuItemSelected(const std::string& result_id,
                                            int command_id,
                                            int event_flags) override {}
@@ -41,7 +41,7 @@
   void ViewShown(int64_t display_id) override {}
   void ActivateItem(const std::string& id, int event_flags) override {}
   void GetContextMenuModel(const std::string& id,
-                           GetContextMenuModelCallback callback) override {}
+                           GetContextMenuModelCallback callback) override;
   void ContextMenuItemSelected(const std::string& id,
                                int command_id,
                                int event_flags) override {}
diff --git a/ash/app_list/views/app_list_view.cc b/ash/app_list/views/app_list_view.cc
index 8963047f..6d7b026 100644
--- a/ash/app_list/views/app_list_view.cc
+++ b/ash/app_list/views/app_list_view.cc
@@ -88,9 +88,6 @@
 constexpr float kAppListAnimationDurationMs = 200;
 constexpr float kAppListAnimationDurationFromFullscreenMs = 250;
 
-// The background corner radius in peeking and fullscreen state.
-constexpr int kAppListBackgroundRadius = 28;
-
 // Events within this threshold from the top of the view will be reserved for
 // home launcher gestures, if they can be processed.
 constexpr int kAppListHomeLaucherGesturesThreshold = 32;
@@ -1225,6 +1222,12 @@
   RecordStateTransitionForUma(new_state_override);
   model_->SetStateFullscreen(new_state_override);
   app_list_state_ = new_state_override;
+
+  // Animations are skipped for side shelf mode, so trigger a layout to update
+  // children immediately.
+  if (is_side_shelf_)
+    Layout();
+
   if (new_state_override == AppListViewState::CLOSED) {
     return;
   }
diff --git a/ash/app_list/views/app_list_view.h b/ash/app_list/views/app_list_view.h
index 1065c5e..cc8e466 100644
--- a/ash/app_list/views/app_list_view.h
+++ b/ash/app_list/views/app_list_view.h
@@ -43,6 +43,11 @@
 class SearchModel;
 class TransitionAnimationObserver;
 
+namespace {
+// The background corner radius in peeking and fullscreen state.
+constexpr int kAppListBackgroundRadius = 28;
+}
+
 // AppListView is the top-level view and controller of app list UI. It creates
 // and hosts a AppsGridView and passes AppListModel to it for display.
 // TODO(newcomer|weidongg): Organize the cc file to match the order of
@@ -241,6 +246,10 @@
     onscreen_keyboard_shown_ = onscreen_keyboard_shown;
   }
 
+  int get_background_radius_for_test() const {
+    return kAppListBackgroundRadius;
+  }
+
   views::View* GetAppListBackgroundShieldForTest();
 
   SkColor GetAppListBackgroundShieldColorForTest();
diff --git a/ash/app_list/views/app_list_view_unittest.cc b/ash/app_list/views/app_list_view_unittest.cc
index a148dd9..685c62e 100644
--- a/ash/app_list/views/app_list_view_unittest.cc
+++ b/ash/app_list/views/app_list_view_unittest.cc
@@ -1342,13 +1342,22 @@
   ASSERT_EQ(AppListViewState::PEEKING, view_->app_list_state());
 }
 
-// Tests that in side shelf mode, the app list opens in fullscreen by default.
+// Tests that in side shelf mode, the app list opens in fullscreen by default
+// and verifies that the top rounded corners of the app list background are
+// hidden (see https://crbug.com/920082).
 TEST_F(AppListViewTest, ShowFullscreenWhenInSideShelfMode) {
   Initialize(0, false, true);
 
   Show();
 
-  ASSERT_EQ(AppListViewState::FULLSCREEN_ALL_APPS, view_->app_list_state());
+  EXPECT_EQ(AppListViewState::FULLSCREEN_ALL_APPS, view_->app_list_state());
+
+  // Get the end point of the rounded corner and transform it into screen
+  // coordinates. It should be on the screen's bottom line.
+  gfx::PointF end_of_rounded_corner(0, view_->get_background_radius_for_test());
+  view_->GetAppListBackgroundShieldForTest()->GetTransform().TransformPoint(
+      &end_of_rounded_corner);
+  EXPECT_EQ(0.0f, end_of_rounded_corner.y());
 }
 
 // Tests that in tablet mode, the app list opens in fullscreen by default.
diff --git a/ash/login/ui/lock_contents_view.cc b/ash/login/ui/lock_contents_view.cc
index 94f721b..b9899255 100644
--- a/ash/login/ui/lock_contents_view.cc
+++ b/ash/login/ui/lock_contents_view.cc
@@ -48,6 +48,7 @@
 #include "ui/display/manager/display_manager.h"
 #include "ui/display/manager/managed_display_info.h"
 #include "ui/gfx/geometry/insets.h"
+#include "ui/gfx/geometry/point.h"
 #include "ui/gfx/geometry/rect.h"
 #include "ui/gfx/geometry/size.h"
 #include "ui/gfx/geometry/vector2d.h"
@@ -358,15 +359,25 @@
   Shell::Get()->login_screen_controller()->AddObserver(this);
   Shell::Get()->system_tray_notifier()->AddSystemTrayFocusObserver(this);
   keyboard::KeyboardController::Get()->AddObserver(this);
-  auth_error_bubble_ = LoginErrorBubble::CreateDefault();
-  supervised_user_deprecation_bubble_ = LoginErrorBubble::CreateDefault();
+
+  auth_error_bubble_ = new LoginErrorBubble();
+  AddChildView(auth_error_bubble_);
+
+  supervised_user_deprecation_bubble_ = new LoginErrorBubble();
   supervised_user_deprecation_bubble_->SetPersistent(true);
-  detachable_base_error_bubble_ = LoginErrorBubble::CreateDefault();
+  AddChildView(supervised_user_deprecation_bubble_);
+
+  detachable_base_error_bubble_ = new LoginErrorBubble();
   detachable_base_error_bubble_->SetPersistent(true);
+  AddChildView(detachable_base_error_bubble_);
+
   tooltip_bubble_ = new LoginTooltipView(base::UTF8ToUTF16("") /*message*/,
                                          nullptr /*anchor_view*/);
-  warning_banner_bubble_ = LoginErrorBubble::CreateDefault();
+  AddChildView(tooltip_bubble_);
+
+  warning_banner_bubble_ = new LoginErrorBubble();
   warning_banner_bubble_->SetPersistent(true);
+  AddChildView(warning_banner_bubble_);
 
   // We reuse the focusable state on this view as a signal that focus should
   // switch to the system tray. LockContentsView should otherwise not be
@@ -428,8 +439,6 @@
   }
   chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(
       this);
-
-  CleanupBubbles();
 }
 
 void LockContentsView::FocusNextUser() {
@@ -801,13 +810,14 @@
   if (!big_user || !big_user->auth_user())
     return;
 
-  if (tooltip_bubble_->IsVisible())
+  if (tooltip_bubble_->visible())
     tooltip_bubble_->Hide();
 
   if (icon->autoshow_tooltip) {
     tooltip_bubble_->SetAnchorView(big_user->auth_user()->password_view());
     tooltip_bubble_->SetText(icon->tooltip);
     tooltip_bubble_->Show();
+    tooltip_bubble_->SetVisible(true);
   }
 }
 
@@ -818,7 +828,7 @@
                   "warning banner.";
     return;
   }
-  if (warning_banner_bubble_->IsVisible())
+  if (warning_banner_bubble_->visible())
     warning_banner_bubble_->Hide();
   // Shows warning banner as a persistent error bubble.
   views::Label* label =
@@ -829,14 +839,14 @@
   label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
   label->SetEnabledColor(SK_ColorWHITE);
 
-  warning_banner_bubble_->SetContent(label);
   warning_banner_bubble_->SetAnchorView(
       CurrentBigUserView()->auth_user()->password_view());
+  warning_banner_bubble_->SetContent(label);
   warning_banner_bubble_->Show();
 }
 
 void LockContentsView::OnHideWarningBanner() {
-  if (warning_banner_bubble_->IsVisible())
+  if (warning_banner_bubble_->visible())
     warning_banner_bubble_->Hide();
 }
 
@@ -983,12 +993,12 @@
       (pairing_status == DetachableBasePairingStatus::kAuthenticated &&
        detachable_base_model_->PairedBaseMatchesLastUsedByUser(
            *CurrentBigUserView()->GetCurrentUser()->basic_user_info))) {
-    if (detachable_base_error_bubble_->IsVisible())
+    if (detachable_base_error_bubble_->visible())
       detachable_base_error_bubble_->Hide();
     return;
   }
 
-  if (auth_error_bubble_->IsVisible())
+  if (auth_error_bubble_->visible())
     auth_error_bubble_->Hide();
 
   base::string16 error_text =
@@ -1323,10 +1333,10 @@
 
 void LockContentsView::OnAuthenticate(bool auth_success) {
   if (auth_success) {
-    if (auth_error_bubble_->IsVisible())
+    if (auth_error_bubble_->visible())
       auth_error_bubble_->Hide();
 
-    if (detachable_base_error_bubble_->IsVisible())
+    if (detachable_base_error_bubble_->visible())
       detachable_base_error_bubble_->Hide();
 
     // Now that the user has been authenticated, update the user's last used
@@ -1525,7 +1535,7 @@
     supervised_user_deprecation_bubble_->SetAnchorView(
         CurrentBigUserView()->auth_user()->password_view());
     supervised_user_deprecation_bubble_->Show();
-  } else if (supervised_user_deprecation_bubble_->IsVisible()) {
+  } else if (supervised_user_deprecation_bubble_->visible()) {
     supervised_user_deprecation_bubble_->Hide();
   }
 
@@ -1534,7 +1544,7 @@
   OnDetachableBasePairingStatusChanged(
       detachable_base_model_->GetPairingStatus());
 
-  if (!detachable_base_error_bubble_->IsVisible())
+  if (!detachable_base_error_bubble_->visible())
     CurrentBigUserView()->RequestFocus();
 }
 
@@ -1629,9 +1639,6 @@
   container->AddChildView(label);
   container->AddChildView(learn_more_button);
 
-  if (auth_error_bubble_->IsVisible())
-    auth_error_bubble_->Hide();
-
   auth_error_bubble_->SetAnchorView(big_view->auth_user()->password_view());
   auth_error_bubble_->SetContent(container);
   auth_error_bubble_->SetPersistent(false);
@@ -1849,29 +1856,4 @@
   }
 }
 
-void LockContentsView::CleanupBubbles() {
-  // If any of the error bubbles have been shown once, it is hosted by a
-  // widget under the Ash menu container, so we should Close the widget when we
-  // destroy LockContentsView (otherwise it will live for the entire lifetime of
-  // the Menu container). If a bubble has never been shown, then we can just
-  // delete it, since it is owned directly by LockContentsView.
-  auto cleanup = [](LoginBaseBubbleView* bubble) {
-    if (bubble->GetWidget())
-      bubble->GetWidget()->Close();
-    else
-      delete bubble;
-  };
-
-  cleanup(auth_error_bubble_);
-  auth_error_bubble_ = nullptr;
-  cleanup(detachable_base_error_bubble_);
-  detachable_base_error_bubble_ = nullptr;
-  cleanup(tooltip_bubble_);
-  tooltip_bubble_ = nullptr;
-  cleanup(warning_banner_bubble_);
-  warning_banner_bubble_ = nullptr;
-  cleanup(supervised_user_deprecation_bubble_);
-  supervised_user_deprecation_bubble_ = nullptr;
-}
-
 }  // namespace ash
diff --git a/ash/login/ui/lock_contents_view.h b/ash/login/ui/lock_contents_view.h
index f24826f..5d0ca98 100644
--- a/ash/login/ui/lock_contents_view.h
+++ b/ash/login/ui/lock_contents_view.h
@@ -339,10 +339,6 @@
   // Performs the specified accelerator action.
   void PerformAction(AcceleratorAction action);
 
-  // Deletes the various bubbles, either by Close()-ing the hosting widget or
-  // deleting any orphaned views.
-  void CleanupBubbles();
-
   const LockScreen::ScreenType screen_type_;
 
   std::vector<UserState> users_;
@@ -377,8 +373,9 @@
       this};
   ScopedSessionObserver session_observer_{this};
 
-  // Error bubbles are owned by LockContentsView, or by the Ash menu container
-  // if they have been shown. Bubble for displaying authentication error.
+  // All error bubbles and the tooltip view are child views of LockContentsView,
+  // and will be torn down when LockContentsView is torn down.
+  // Bubble for displaying authentication error.
   LoginErrorBubble* auth_error_bubble_;
   // Bubble for displaying detachable base errors.
   LoginErrorBubble* detachable_base_error_bubble_;
diff --git a/ash/login/ui/lock_contents_view_unittest.cc b/ash/login/ui/lock_contents_view_unittest.cc
index 3fa8427e..a69b4fe 100644
--- a/ash/login/ui/lock_contents_view_unittest.cc
+++ b/ash/login/ui/lock_contents_view_unittest.cc
@@ -726,7 +726,7 @@
 
   LockContentsView::TestApi test_api(lock);
   // Creating lock screen does not show tooltip bubble.
-  EXPECT_FALSE(test_api.tooltip_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.tooltip_bubble()->visible());
 
   // Show an icon with |autoshow_tooltip| is false. Tooltip bubble is not
   // activated.
@@ -735,13 +735,13 @@
   icon->autoshow_tooltip = false;
   DataDispatcher()->ShowEasyUnlockIcon(users()[0]->basic_user_info->account_id,
                                        icon);
-  EXPECT_FALSE(test_api.tooltip_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.tooltip_bubble()->visible());
 
   // Show icon with |autoshow_tooltip| set to true. Tooltip bubble is shown.
   icon->autoshow_tooltip = true;
   DataDispatcher()->ShowEasyUnlockIcon(users()[0]->basic_user_info->account_id,
                                        icon);
-  EXPECT_TRUE(test_api.tooltip_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.tooltip_bubble()->visible());
 }
 
 // Verifies that easy unlock icon state persists when changing auth user.
@@ -860,12 +860,12 @@
   generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.auth_error_bubble()->visible());
 
   // The error bubble is expected to close on a user action - e.g. if they start
   // typing the password again.
   generator->PressKey(ui::KeyboardCode::VKEY_B, 0);
-  EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.auth_error_bubble()->visible());
 }
 
 // Gaia is never shown on lock, no mater how many times auth fails.
@@ -957,17 +957,17 @@
   LockContentsView::TestApi test_api(contents);
   ui::test::EventGenerator* generator = GetEventGenerator();
 
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 
   // Change detachable base to a base different than the one previously used by
   // the user - verify that a detachable base error bubble is shown.
   detachable_base_model->SetPairingStatus(
       DetachableBasePairingStatus::kAuthenticated, "5678");
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // Verify that the bubble is not hidden if the user starts typing.
   generator->PressKey(ui::KeyboardCode::VKEY_B, 0);
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // Switching to the user that doesn't have previously used detachable base
   // (and should thus not be warned about the detachable base missmatch) should
@@ -978,7 +978,7 @@
       secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
 
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 
   // The error should be shown again when switching back to the primary user.
   LoginAuthUserView::TestApi primary_test_api(
@@ -987,7 +987,7 @@
       primary_test_api.user_view()->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
 
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
   EXPECT_FALSE(primary_test_api.password_view()->HasFocus());
 
   EXPECT_EQ("1234",
@@ -1036,16 +1036,16 @@
   LockContentsView::TestApi test_api(contents);
   ui::test::EventGenerator* generator = GetEventGenerator();
 
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 
   // Show notification if unauthenticated base is attached.
   detachable_base_model->SetPairingStatus(
       DetachableBasePairingStatus::kNotAuthenticated, "");
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // Verify that the bubble is not hidden if the user starts typing.
   generator->PressKey(ui::KeyboardCode::VKEY_B, 0);
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // Switching to another user should not hide the error bubble.
   LoginAuthUserView::TestApi secondary_test_api(
@@ -1054,7 +1054,7 @@
       secondary_test_api.user_view()->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
 
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
   EXPECT_FALSE(secondary_test_api.password_view()->HasFocus());
 
   // The last trusted detachable used by the user should not be overriden by
@@ -1103,12 +1103,12 @@
   // the user - verify that a detachable base error bubble is shown.
   detachable_base_model->SetPairingStatus(
       DetachableBasePairingStatus::kAuthenticated, "5678");
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // The notification should be hidden if the base gets detached.
   detachable_base_model->SetPairingStatus(DetachableBasePairingStatus::kNone,
                                           "");
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 }
 
 TEST_F(LockContentsViewUnitTest, DetachableBaseErrorClearsAuthError) {
@@ -1136,7 +1136,7 @@
   LockContentsView::TestApi test_api(contents);
   ui::test::EventGenerator* generator = GetEventGenerator();
 
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 
   // Attempt and fail user auth - an auth error is expected to be shown.
   std::unique_ptr<MockLoginScreenClient> client = BindMockLoginScreenClient();
@@ -1149,8 +1149,8 @@
   generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.auth_error_bubble()->visible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 
   // Change detachable base to a base different than the one previously used by
   // the user - verify that a detachable base error bubble is shown, and the
@@ -1158,8 +1158,8 @@
   detachable_base_model->SetPairingStatus(
       DetachableBasePairingStatus::kAuthenticated, "5678");
 
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
-  EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
+  EXPECT_FALSE(test_api.auth_error_bubble()->visible());
 }
 
 TEST_F(LockContentsViewUnitTest, AuthErrorDoesNotRemoveDetachableBaseError) {
@@ -1187,7 +1187,7 @@
   LockContentsView::TestApi test_api(contents);
   ui::test::EventGenerator* generator = GetEventGenerator();
 
-  EXPECT_FALSE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.detachable_base_error_bubble()->visible());
 
   // Change detachable base to a base different than the one previously used by
   // the user - verify that a detachable base error bubble is shown, and the
@@ -1195,7 +1195,7 @@
   detachable_base_model->SetPairingStatus(
       DetachableBasePairingStatus::kAuthenticated, "5678");
 
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // Attempt and fail user auth - an auth error is expected to be shown.
   // Detachable base error should not be hidden.
@@ -1212,15 +1212,15 @@
   generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.auth_error_bubble()->visible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
 
   // User action, like pressing a key should close the auth error bubble, but
   // not the detachable base error bubble.
   generator->PressKey(ui::KeyboardCode::VKEY_A, 0);
 
-  EXPECT_TRUE(test_api.detachable_base_error_bubble()->IsVisible());
-  EXPECT_FALSE(test_api.auth_error_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.detachable_base_error_bubble()->visible());
+  EXPECT_FALSE(test_api.auth_error_bubble()->visible());
 }
 
 TEST_F(LockContentsViewKeyboardUnitTest, SwitchPinAndVirtualKeyboard) {
@@ -2039,19 +2039,19 @@
   ui::test::EventGenerator* generator = GetEventGenerator();
 
   // Creating lock screen does not show warning banner bubble.
-  EXPECT_FALSE(test_api.warning_banner_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.warning_banner_bubble()->visible());
 
   // Verifies that a warning banner is shown by giving a non-empty message.
   DataDispatcher()->ShowWarningBanner(base::ASCIIToUTF16("foo"));
-  EXPECT_TRUE(test_api.warning_banner_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.warning_banner_bubble()->visible());
 
   // Verifies that a warning banner is hidden by HideWarningBanner().
   DataDispatcher()->HideWarningBanner();
-  EXPECT_FALSE(test_api.warning_banner_bubble()->IsVisible());
+  EXPECT_FALSE(test_api.warning_banner_bubble()->visible());
 
   // Shows a warning banner again.
   DataDispatcher()->ShowWarningBanner(base::ASCIIToUTF16("foo"));
-  EXPECT_TRUE(test_api.warning_banner_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.warning_banner_bubble()->visible());
 
   // Attempt and fail user auth - an auth error is expected to be shown.
   // The warning banner should not be hidden.
@@ -2068,8 +2068,8 @@
   generator->PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(test_api.auth_error_bubble()->IsVisible());
-  EXPECT_TRUE(test_api.warning_banner_bubble()->IsVisible());
+  EXPECT_TRUE(test_api.auth_error_bubble()->visible());
+  EXPECT_TRUE(test_api.warning_banner_bubble()->visible());
 }
 
 TEST_F(LockContentsViewUnitTest, RemoveUserFocusMovesBackToPrimaryUser) {
diff --git a/ash/login/ui/login_base_bubble_view.cc b/ash/login/ui/login_base_bubble_view.cc
index ab6689a..e673860 100644
--- a/ash/login/ui/login_base_bubble_view.cc
+++ b/ash/login/ui/login_base_bubble_view.cc
@@ -4,6 +4,9 @@
 
 #include "ash/login/ui/login_base_bubble_view.h"
 
+#include <memory>
+
+#include "ash/login/ui/views_utils.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/shell.h"
 #include "base/scoped_observer.h"
@@ -12,6 +15,7 @@
 #include "ui/compositor/layer_animator.h"
 #include "ui/compositor/scoped_layer_animation_settings.h"
 #include "ui/events/event_handler.h"
+#include "ui/views/background.h"
 #include "ui/views/layout/box_layout.h"
 #include "ui/wm/core/coordinate_conversion.h"
 
@@ -30,6 +34,9 @@
 // Bottom margin of the bubble view.
 constexpr int kBubbleBottomMarginDp = 18;
 
+// Spacing between the child view inside the bubble view.
+constexpr int kBubbleBetweenChildSpacingDp = 6;
+
 // The amount of time for bubble show/hide animation.
 constexpr base::TimeDelta kBubbleAnimationDuration =
     base::TimeDelta::FromMilliseconds(300);
@@ -38,13 +45,10 @@
 
 // This class handles keyboard, mouse, and focus events, and dismisses the
 // associated bubble in response.
-class LoginBubbleHandler : public ui::EventHandler,
-                           public aura::client::FocusChangeObserver {
+class LoginBubbleHandler : public ui::EventHandler {
  public:
   LoginBubbleHandler(LoginBaseBubbleView* bubble) : bubble_(bubble) {
     Shell::Get()->AddPreTargetHandler(this);
-    focus_observer_.Add(
-        aura::client::GetFocusClient(Shell::GetPrimaryRootWindow()));
   }
 
   ~LoginBubbleHandler() override { Shell::Get()->RemovePreTargetHandler(this); }
@@ -68,37 +72,22 @@
       return;
     }
 
-    if (!bubble_->IsVisible())
+    if (!bubble_->visible())
       return;
 
     if (bubble_->GetBubbleOpener() && bubble_->GetBubbleOpener()->HasFocus())
       return;
 
-    if (bubble_->GetWidget()->IsActive())
+    if (login_views_utils::HasFocusInAnyChildView(bubble_))
       return;
 
     if (!bubble_->IsPersistent())
       bubble_->Hide();
   }
 
-  // aura::client::FocusChangeObserver:
-  void OnWindowFocused(aura::Window* gained_focus,
-                       aura::Window* lost_focus) override {
-    if (!bubble_->IsVisible())
-      return;
-
-    if (gained_focus &&
-        bubble_->GetWidget()->GetNativeView()->Contains(gained_focus)) {
-      return;
-    }
-
-    if (!bubble_->IsPersistent())
-      bubble_->Hide();
-  }
-
  private:
   void ProcessPressedEvent(const ui::LocatedEvent* event) {
-    if (!bubble_->IsVisible())
+    if (!bubble_->visible())
       return;
 
     gfx::Point screen_location = event->location();
@@ -122,9 +111,6 @@
 
   LoginBaseBubbleView* bubble_;
 
-  ScopedObserver<aura::client::FocusClient, aura::client::FocusChangeObserver>
-      focus_observer_{this};
-
   DISALLOW_COPY_AND_ASSIGN(LoginBubbleHandler);
 };
 
@@ -133,36 +119,28 @@
 
 LoginBaseBubbleView::LoginBaseBubbleView(views::View* anchor_view,
                                          aura::Window* parent_window)
-    : BubbleDialogDelegateView(anchor_view, views::BubbleBorder::NONE),
+    : anchor_view_(anchor_view),
       bubble_handler_(std::make_unique<LoginBubbleHandler>(this)) {
-  set_margins(gfx::Insets(kBubbleTopMarginDp, kBubbleHorizontalMarginDp,
-                          kBubbleBottomMarginDp, kBubbleHorizontalMarginDp));
-  set_color(SK_ColorBLACK);
-  set_can_activate(false);
-  set_close_on_deactivate(false);
+  SetLayoutManager(std::make_unique<views::BoxLayout>(
+      views::BoxLayout::kVertical,
+      gfx::Insets(kBubbleTopMarginDp, kBubbleHorizontalMarginDp,
+                  kBubbleBottomMarginDp, kBubbleHorizontalMarginDp),
+      kBubbleBetweenChildSpacingDp));
+
+  SetVisible(false);
+  SetBackground(views::CreateSolidBackground(SK_ColorBLACK));
 
   // Layer rendering is needed for animation.
   SetPaintToLayer();
-  layer()->SetFillsBoundsOpaquely(false);
-
-  set_parent_window(parent_window);
 }
 
 LoginBaseBubbleView::~LoginBaseBubbleView() = default;
 
 void LoginBaseBubbleView::Show() {
-  views::Widget* widget = GetWidget();
-
-  if (!widget)
-    widget = views::BubbleDialogDelegateView::CreateBubble(this);
-
   layer()->GetAnimator()->RemoveObserver(this);
 
-  Layout();
-  SizeToContents();
-
-  widget->ShowInactive();
-  widget->StackAtTop();
+  SetSize(GetPreferredSize());
+  SetPosition(CalculatePosition());
 
   ScheduleAnimation(true /*visible*/);
 
@@ -172,12 +150,7 @@
 }
 
 void LoginBaseBubbleView::Hide() {
-  if (GetWidget())
-    ScheduleAnimation(false /*visible*/);
-}
-
-bool LoginBaseBubbleView::IsVisible() {
-  return GetWidget() && GetWidget()->IsVisible();
+  ScheduleAnimation(false /*visible*/);
 }
 
 LoginButton* LoginBaseBubbleView::GetBubbleOpener() const {
@@ -190,31 +163,25 @@
 
 void LoginBaseBubbleView::SetPersistent(bool persistent) {}
 
-void LoginBaseBubbleView::OnBeforeBubbleWidgetInit(
-    views::Widget::InitParams* params,
-    views::Widget* widget) const {
-  // This case only gets called if the bubble has no anchor and no parent
-  // container was specified. In this case, the parent container should default
-  // to MenuContainer, so that login bubbles are visible over the shelf and
-  // virtual keyboard. Shell may be null in tests.
-  if (!params->parent && Shell::HasInstance()) {
-    params->parent = Shell::GetContainer(Shell::GetPrimaryRootWindow(),
-                                         kShellWindowId_MenuContainer);
+gfx::Point LoginBaseBubbleView::CalculatePosition() {
+  if (GetAnchorView()) {
+    gfx::Point bottom_left = GetAnchorView()->bounds().bottom_left();
+    ConvertPointToTarget(GetAnchorView()->parent() /*source*/,
+                         parent() /*target*/, &bottom_left);
+    return bottom_left;
   }
-}
 
-int LoginBaseBubbleView::GetDialogButtons() const {
-  return ui::DIALOG_BUTTON_NONE;
+  return gfx::Point();
 }
 
 void LoginBaseBubbleView::SetAnchorView(views::View* anchor_view) {
-  views::BubbleDialogDelegateView::SetAnchorView(anchor_view);
+  anchor_view_ = anchor_view;
 }
 
 void LoginBaseBubbleView::OnLayerAnimationEnded(
     ui::LayerAnimationSequence* sequence) {
   layer()->GetAnimator()->RemoveObserver(this);
-  GetWidget()->Hide();
+  SetVisible(false);
 }
 
 gfx::Size LoginBaseBubbleView::CalculatePreferredSize() const {
@@ -224,15 +191,20 @@
   return size;
 }
 
-void LoginBaseBubbleView::OnWidgetVisibilityChanged(views::Widget* widget,
-                                                    bool visible) {
-  if (visible)
-    EnsureInScreen();
+void LoginBaseBubbleView::Layout() {
+  views::View::Layout();
+
+  // If a Layout() is called while the bubble is visible (i.e. due to Show()),
+  // its bounds may change because of the parent's LayoutManager. This allows
+  // the bubbles to always determine their own size and position.
+  if (visible()) {
+    SetSize(GetPreferredSize());
+    SetPosition(CalculatePosition());
+  }
 }
 
-void LoginBaseBubbleView::OnWidgetBoundsChanged(views::Widget* widget,
-                                                const gfx::Rect& new_bounds) {
-  EnsureInScreen();
+void LoginBaseBubbleView::OnBlur() {
+  Hide();
 }
 
 void LoginBaseBubbleView::ScheduleAnimation(bool visible) {
@@ -251,6 +223,8 @@
     std::swap(opacity_start, opacity_end);
     // We only need to handle animation ending if we're hiding the bubble.
     layer()->GetAnimator()->AddObserver(this);
+  } else {
+    SetVisible(true);
   }
 
   layer()->SetOpacity(opacity_start);
@@ -264,26 +238,4 @@
   }
 }
 
-void LoginBaseBubbleView::EnsureInScreen() {
-  DCHECK(GetWidget());
-
-  const gfx::Rect view_bounds = GetBoundsInScreen();
-  const gfx::Rect work_area =
-      display::Screen::GetScreen()
-          ->GetDisplayNearestWindow(GetWidget()->GetNativeWindow())
-          .work_area();
-
-  int horizontal_offset = 0;
-
-  // If the widget extends past the right side of the screen, make it go to
-  // the left instead.
-  if (work_area.right() < view_bounds.right()) {
-    horizontal_offset = -view_bounds.width();
-  }
-
-  set_anchor_view_insets(
-      anchor_view_insets().Offset(gfx::Vector2d(horizontal_offset, 0)));
-  OnAnchorBoundsChanged();
-}
-
 }  // namespace ash
diff --git a/ash/login/ui/login_base_bubble_view.h b/ash/login/ui/login_base_bubble_view.h
index 91217b4..a93e3daf 100644
--- a/ash/login/ui/login_base_bubble_view.h
+++ b/ash/login/ui/login_base_bubble_view.h
@@ -17,7 +17,7 @@
 class LoginBubbleHandler;
 
 // Base bubble view for login screen bubbles.
-class ASH_EXPORT LoginBaseBubbleView : public views::BubbleDialogDelegateView,
+class ASH_EXPORT LoginBaseBubbleView : public views::View,
                                        public ui::LayerAnimationObserver {
  public:
   // Without specifying a parent_window, the bubble will default to being in the
@@ -30,8 +30,6 @@
   void Show();
   void Hide();
 
-  bool IsVisible();
-
   // Returns the button responsible for opening this bubble.
   virtual LoginButton* GetBubbleOpener() const;
 
@@ -40,11 +38,11 @@
   // Change the persistence of the bubble.
   virtual void SetPersistent(bool persistent);
 
-  // views::BubbleDialogDelegateView:
-  void OnBeforeBubbleWidgetInit(views::Widget::InitParams* params,
-                                views::Widget* widget) const override;
-  int GetDialogButtons() const override;
+  // Determine the position of the bubble prior to showing.
+  virtual gfx::Point CalculatePosition();
+
   void SetAnchorView(views::View* anchor_view);
+  views::View* GetAnchorView() const { return anchor_view_; }
 
   // ui::LayerAnimationObserver:
   void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override;
@@ -54,15 +52,13 @@
 
   // views::View:
   gfx::Size CalculatePreferredSize() const override;
-
-  // views::WidgetObserver:
-  void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
-  void OnWidgetBoundsChanged(views::Widget* widget,
-                             const gfx::Rect& new_bounds) override;
+  void Layout() override;
+  void OnBlur() override;
 
  private:
   void ScheduleAnimation(bool visible);
-  void EnsureInScreen();
+
+  views::View* anchor_view_;
 
   std::unique_ptr<LoginBubbleHandler> bubble_handler_;
 
diff --git a/ash/login/ui/login_base_bubble_view_unittest.cc b/ash/login/ui/login_base_bubble_view_unittest.cc
index 23853bda..ddc5be7 100644
--- a/ash/login/ui/login_base_bubble_view_unittest.cc
+++ b/ash/login/ui/login_base_bubble_view_unittest.cc
@@ -15,15 +15,6 @@
 namespace {
 // Total width of the bubble view.
 constexpr int kBubbleTotalWidthDp = 178;
-
-// Horizontal margin of the bubble view.
-constexpr int kBubbleHorizontalMarginDp = 14;
-
-// Top margin of the bubble view.
-constexpr int kBubbleTopMarginDp = 13;
-
-// Bottom margin of the bubble view.
-constexpr int kBubbleBottomMarginDp = 18;
 }  // namespace
 
 class LoginBaseBubbleViewTest : public LoginTestBase {
@@ -36,6 +27,7 @@
     LoginTestBase::SetUp();
 
     anchor_ = new views::View();
+    anchor_->SetSize(gfx::Size(0, 25));
     container_ = new views::View();
     container_->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
@@ -50,6 +42,8 @@
     bubble_->SetLayoutManager(
         std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
     bubble_->AddChildView(label);
+
+    container_->AddChildView(bubble_);
   }
 
   LoginBaseBubbleView* bubble_;
@@ -61,58 +55,54 @@
 };
 
 TEST_F(LoginBaseBubbleViewTest, BasicProperties) {
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 
   bubble_->Show();
-  EXPECT_TRUE(bubble_->IsVisible());
+  EXPECT_TRUE(bubble_->visible());
 
-  EXPECT_EQ(bubble_->GetDialogButtons(), ui::DIALOG_BUTTON_NONE);
   EXPECT_EQ(bubble_->width(), kBubbleTotalWidthDp);
-  EXPECT_EQ(bubble_->color(), SK_ColorBLACK);
-  EXPECT_EQ(bubble_->margins(),
-            gfx::Insets(kBubbleTopMarginDp, kBubbleHorizontalMarginDp,
-                        kBubbleBottomMarginDp, kBubbleHorizontalMarginDp));
+  EXPECT_EQ(bubble_->background()->get_color(), SK_ColorBLACK);
 
   bubble_->Hide();
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 }
 
 TEST_F(LoginBaseBubbleViewTest, KeyEventHandling) {
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 
   // Verify that a random key event won't open the bubble.
   ui::test::EventGenerator* generator = GetEventGenerator();
   container_->RequestFocus();
   generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 
   // Verify that a key event will close the bubble if it is open.
   bubble_->Show();
-  EXPECT_TRUE(bubble_->IsVisible());
+  EXPECT_TRUE(bubble_->visible());
   generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 }
 
 TEST_F(LoginBaseBubbleViewTest, MouseEventHandling) {
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 
   // Verify that a random mouse event won't open the bubble.
   ui::test::EventGenerator* generator = GetEventGenerator();
   generator->MoveMouseTo(container_->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 
   // Verify that a click event on the bubble won't close it.
   bubble_->Show();
-  EXPECT_TRUE(bubble_->IsVisible());
+  EXPECT_TRUE(bubble_->visible());
   generator->MoveMouseTo(bubble_->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
-  EXPECT_TRUE(bubble_->IsVisible());
+  EXPECT_TRUE(bubble_->visible());
 
   // Verify that a click event outside the bubble will close it if it is open.
   generator->MoveMouseTo(anchor_->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
-  EXPECT_FALSE(bubble_->IsVisible());
+  EXPECT_FALSE(bubble_->visible());
 }
 
 }  // namespace ash
diff --git a/ash/login/ui/login_error_bubble.cc b/ash/login/ui/login_error_bubble.cc
index 73a42e6..2ef1ef7 100644
--- a/ash/login/ui/login_error_bubble.cc
+++ b/ash/login/ui/login_error_bubble.cc
@@ -17,48 +17,20 @@
 namespace ash {
 namespace {
 
-// Vertical spacing between the anchor view and error bubble.
-constexpr int kAnchorViewErrorBubbleVerticalSpacingDp = 48;
-
 // The size of the alert icon in the error bubble.
 constexpr int kAlertIconSizeDp = 20;
 
-// Margin/inset of the entries for the user menu.
-constexpr int kUserMenuMarginWidth = 14;
-constexpr int kUserMenuMarginHeight = 18;
-
-// Spacing between the child view inside the bubble view.
-constexpr int kBubbleBetweenChildSpacingDp = 6;
-
 }  // namespace
 
-// static
-LoginErrorBubble* LoginErrorBubble::CreateDefault() {
-  aura::Window* menu_container = Shell::GetContainer(
-      Shell::GetPrimaryRootWindow(), kShellWindowId_MenuContainer);
-  return new LoginErrorBubble(nullptr /* content */, nullptr /*anchor_view*/,
-                              menu_container /*parent_container*/,
-                              false /*is_persistent*/);
-}
+LoginErrorBubble::LoginErrorBubble()
+    : LoginErrorBubble(nullptr /*content*/,
+                       nullptr /*anchor_view*/,
+                       false /*is_persistent*/) {}
 
 LoginErrorBubble::LoginErrorBubble(views::View* content,
                                    views::View* anchor_view,
-                                   aura::Window* parent_container,
                                    bool is_persistent)
-    : LoginBaseBubbleView(anchor_view, parent_container),
-      is_persistent_(is_persistent) {
-  set_anchor_view_insets(
-      gfx::Insets(kAnchorViewErrorBubbleVerticalSpacingDp, 0));
-
-  gfx::Insets margins(kUserMenuMarginHeight, kUserMenuMarginWidth);
-
-  set_margins(gfx::Insets(0, margins.left(), 0, margins.right()));
-
-  SetLayoutManager(std::make_unique<views::BoxLayout>(
-      views::BoxLayout::kVertical,
-      gfx::Insets(margins.top(), 0, margins.bottom(), 0),
-      kBubbleBetweenChildSpacingDp));
-
+    : LoginBaseBubbleView(anchor_view), is_persistent_(is_persistent) {
   auto* alert_view = new NonAccessibleView("AlertIconContainer");
   alert_view->SetLayoutManager(
       std::make_unique<views::BoxLayout>(views::BoxLayout::kHorizontal));
@@ -93,6 +65,16 @@
   is_persistent_ = persistent;
 }
 
+gfx::Size LoginErrorBubble::CalculatePreferredSize() const {
+  gfx::Size size;
+
+  if (GetAnchorView())
+    size.set_width(GetAnchorView()->width());
+
+  size.set_height(GetHeightForWidth(size.width()));
+  return size;
+}
+
 const char* LoginErrorBubble::GetClassName() const {
   return "LoginErrorBubble";
 }
diff --git a/ash/login/ui/login_error_bubble.h b/ash/login/ui/login_error_bubble.h
index eab8fc22..3acd155a 100644
--- a/ash/login/ui/login_error_bubble.h
+++ b/ash/login/ui/login_error_bubble.h
@@ -13,14 +13,9 @@
 
 class ASH_EXPORT LoginErrorBubble : public LoginBaseBubbleView {
  public:
-  // Factory method to create a non-persistent error bubble in the Ash menu
-  // container. The caller should manually Close() or delete the bubble, or
-  // else it will persist for the full lifetime of the Ash menu container.
-  static LoginErrorBubble* CreateDefault();
-
+  LoginErrorBubble();
   LoginErrorBubble(views::View* content,
                    views::View* anchor_view,
-                   aura::Window* parent_container,
                    bool is_persistent);
   ~LoginErrorBubble() override;
 
@@ -31,6 +26,7 @@
   void SetPersistent(bool persistent) override;
 
   // views::View:
+  gfx::Size CalculatePreferredSize() const override;
   const char* GetClassName() const override;
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
diff --git a/ash/login/ui/login_error_bubble_unittest.cc b/ash/login/ui/login_error_bubble_unittest.cc
index 061b5aa..37e2611 100644
--- a/ash/login/ui/login_error_bubble_unittest.cc
+++ b/ash/login/ui/login_error_bubble_unittest.cc
@@ -27,31 +27,32 @@
                                  views::style::STYLE_PRIMARY);
 
   auto* bubble = new LoginErrorBubble(label /*content*/, anchor_view,
-                                      widget()->GetNativeView() /*container*/,
                                       true /*is_persistent*/);
-  EXPECT_FALSE(bubble->IsVisible());
+  container->AddChildView(bubble);
+
+  EXPECT_FALSE(bubble->visible());
 
   bubble->Show();
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   ui::test::EventGenerator* generator = GetEventGenerator();
 
   generator->MoveMouseTo(anchor_view->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   generator->MoveMouseTo(bubble->GetBoundsInScreen().CenterPoint());
   generator->ClickLeftButton();
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   generator->GestureTapAt(anchor_view->GetBoundsInScreen().CenterPoint());
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   generator->GestureTapAt(bubble->GetBoundsInScreen().CenterPoint());
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   generator->PressKey(ui::KeyboardCode::VKEY_A, ui::EF_NONE);
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 }
 
 }  // namespace ash
diff --git a/ash/login/ui/login_expanded_public_account_view.cc b/ash/login/ui/login_expanded_public_account_view.cc
index 541e275..87d7a6b 100644
--- a/ash/login/ui/login_expanded_public_account_view.cc
+++ b/ash/login/ui/login_expanded_public_account_view.cc
@@ -13,6 +13,7 @@
 #include "ash/login/ui/login_button.h"
 #include "ash/login/ui/login_user_view.h"
 #include "ash/login/ui/public_account_warning_dialog.h"
+#include "ash/login/ui/views_utils.h"
 #include "ash/resources/vector_icons/vector_icons.h"
 #include "ash/shell.h"
 #include "ash/strings/grit/ash_strings.h"
@@ -415,20 +416,10 @@
           current_user_->basic_user_info->account_id,
           selected_language_item_.value, selected_keyboard_item_.value);
     } else if (sender == language_selection_) {
-      if (language_menu_view_ && language_menu_view_->IsVisible()) {
+      DCHECK(language_menu_view_);
+      if (language_menu_view_->visible()) {
         language_menu_view_->Hide();
       } else {
-        if (language_menu_view_) {
-          language_menu_view_->GetWidget()->Close();
-          language_menu_view_ = nullptr;
-        }
-
-        language_menu_view_ = new LoginMenuView(
-            language_items_, language_selection_ /*anchor_view*/,
-            language_selection_ /*bubble_opener*/,
-            base::BindRepeating(&RightPaneView::OnLanguageSelected,
-                                weak_factory_.GetWeakPtr()));
-
         bool opener_had_focus = language_selection_->HasFocus();
 
         language_menu_view_->Show();
@@ -437,19 +428,10 @@
           language_menu_view_->RequestFocus();
       }
     } else if (sender == keyboard_selection_) {
-      if (keyboard_menu_view_ && keyboard_menu_view_->IsVisible()) {
+      DCHECK(keyboard_menu_view_);
+      if (keyboard_menu_view_->visible()) {
         keyboard_menu_view_->Hide();
       } else {
-        if (keyboard_menu_view_) {
-          keyboard_menu_view_->GetWidget()->Close();
-          keyboard_menu_view_ = nullptr;
-        }
-
-        keyboard_menu_view_ = new LoginMenuView(
-            keyboard_items_, keyboard_selection_ /*anchor_view*/,
-            keyboard_selection_ /*bubble_opener*/,
-            base::BindRepeating(&RightPaneView::OnKeyboardSelected,
-                                weak_factory_.GetWeakPtr()));
         bool opener_had_focus = keyboard_selection_->HasFocus();
 
         keyboard_menu_view_->Show();
@@ -496,7 +478,6 @@
   }
 
   void OnLanguageSelected(LoginMenuView::Item item) {
-    language_menu_view_ = nullptr;
     language_changed_by_user_ = true;
     selected_language_item_ = item;
     language_selection_->SetText(base::UTF8ToUTF16(item.title));
@@ -511,7 +492,6 @@
   }
 
   void OnKeyboardSelected(LoginMenuView::Item item) {
-    keyboard_menu_view_ = nullptr;
     selected_keyboard_item_ = item;
     keyboard_selection_->SetText(base::UTF8ToUTF16(item.title));
   }
@@ -534,6 +514,14 @@
       if (selected_language_item_.value == locale->language_code)
         selected_language_item_ = item;
     }
+
+    language_menu_view_ = new LoginMenuView(
+        language_items_, language_selection_ /*anchor_view*/,
+        language_selection_ /*bubble_opener*/,
+        base::BindRepeating(&RightPaneView::OnLanguageSelected,
+                            weak_factory_.GetWeakPtr()));
+    login_views_utils::GetTopLevelParentView(this)->AddChildView(
+        language_menu_view_);
   }
 
   void PopulateKeyboardItems(
@@ -550,6 +538,14 @@
       if (keyboard->selected)
         selected_keyboard_item_ = item;
     }
+
+    keyboard_menu_view_ = new LoginMenuView(
+        keyboard_items_, keyboard_selection_ /*anchor_view*/,
+        keyboard_selection_ /*bubble_opener*/,
+        base::BindRepeating(&RightPaneView::OnKeyboardSelected,
+                            weak_factory_.GetWeakPtr()));
+    login_views_utils::GetTopLevelParentView(this)->AddChildView(
+        keyboard_menu_view_);
   }
 
   LoginBaseBubbleView* GetLanguageMenuView() { return language_menu_view_; }
@@ -558,16 +554,6 @@
 
   // Close language and keyboard menus and reset local states.
   void Reset() {
-    if (language_menu_view_) {
-      language_menu_view_->GetWidget()->Close();
-      language_menu_view_ = nullptr;
-    }
-
-    if (keyboard_menu_view_) {
-      keyboard_menu_view_->GetWidget()->Close();
-      keyboard_menu_view_ = nullptr;
-    }
-
     show_advanced_changed_by_user_ = false;
     language_changed_by_user_ = false;
   }
@@ -587,11 +573,9 @@
   views::StyledLabel* learn_more_label_ = nullptr;
   MonitoringWarningView* monitoring_warning_view_ = nullptr;
 
-  // |language_menu_view_| and |keyboard_menu_view_| are owned by their
-  // respective bubble widgets, which are always initialized with a Show() call
-  // after construction. menu_view_->GetWidget()->Close() is called on Reset()
-  // and before creating a new instance to avoid memory leaks. The views
-  // themselves should never be deleted directly.
+  // |language_menu_view_| and |keyboard_menu_view_| are parented by the top
+  // level view, either LockContentsView or LockDebugView. This allows the menu
+  // items to be clicked outside the bounds of the right pane view.
   LoginMenuView* language_menu_view_ = nullptr;
   LoginMenuView* keyboard_menu_view_ = nullptr;
 
@@ -730,20 +714,14 @@
 
   // Ignore press event inside the language and keyboard menu.
   LoginBaseBubbleView* language_menu_view = right_pane_->GetLanguageMenuView();
-  LoginBaseBubbleView* keyboard_menu_view = right_pane_->GetKeyboardMenuView();
-  if (language_menu_view) {
-    const gfx::Rect bounds =
-        language_menu_view->GetWidget()->GetWindowBoundsInScreen();
-    if (bounds.Contains(event->root_location()))
-      return;
-  }
+  if (language_menu_view &&
+      language_menu_view->GetBoundsInScreen().Contains(event->root_location()))
+    return;
 
-  if (keyboard_menu_view) {
-    const gfx::Rect bounds =
-        keyboard_menu_view->GetWidget()->GetWindowBoundsInScreen();
-    if (bounds.Contains(event->root_location()))
-      return;
-  }
+  LoginBaseBubbleView* keyboard_menu_view = right_pane_->GetKeyboardMenuView();
+  if (keyboard_menu_view &&
+      keyboard_menu_view->GetBoundsInScreen().Contains(event->root_location()))
+    return;
 
   Hide();
 }
diff --git a/ash/login/ui/login_expanded_public_account_view_unittest.cc b/ash/login/ui/login_expanded_public_account_view_unittest.cc
index b40e7f8b..1dc294b 100644
--- a/ash/login/ui/login_expanded_public_account_view_unittest.cc
+++ b/ash/login/ui/login_expanded_public_account_view_unittest.cc
@@ -162,14 +162,14 @@
   views::View* link_view = styled_label_test.link_targets().begin()->first;
   TapOnView(link_view);
   EXPECT_NE(test_api.warning_dialog(), nullptr);
-  EXPECT_TRUE(test_api.warning_dialog()->IsVisible());
+  EXPECT_TRUE(test_api.warning_dialog()->visible());
 
   // When warning dialog is shown, tap outside of public account expanded view
   // should not hide it.
   TapOnView(other_view_);
   EXPECT_TRUE(public_account_->visible());
   EXPECT_NE(test_api.warning_dialog(), nullptr);
-  EXPECT_TRUE(test_api.warning_dialog()->IsVisible());
+  EXPECT_TRUE(test_api.warning_dialog()->visible());
 
   // If the warning dialog is shown, escape key should close the waring dialog,
   // but not the public account view.
@@ -222,10 +222,8 @@
   EXPECT_TRUE(test_api.advanced_view()->visible());
 
   // Tap on language selection button should bring up the language menu.
-  // Before the first show, language_menu_view is not initialized to anything.
-  EXPECT_EQ(nullptr, test_api.language_menu_view());
   TapOnView(test_api.language_selection_button());
-  EXPECT_TRUE(test_api.language_menu_view()->IsVisible());
+  EXPECT_TRUE(test_api.language_menu_view()->visible());
 
   // First language item is selected, and selected item should have focus.
   EXPECT_EQ(test_api.selected_language_item().value, kEnglishLanguageCode);
@@ -235,13 +233,11 @@
 
   // Select language item should close the language menu.
   GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
-  EXPECT_EQ(nullptr, test_api.language_menu_view());
+  EXPECT_FALSE(test_api.language_menu_view()->visible());
 
   // Tap on keyboard selection button should bring up the keyboard menu.
-  // Before the first show, keyboard_menu_view is not initialized to anything.
-  EXPECT_EQ(nullptr, test_api.keyboard_menu_view());
   TapOnView(test_api.keyboard_selection_button());
-  EXPECT_TRUE(test_api.keyboard_menu_view()->IsVisible());
+  EXPECT_TRUE(test_api.keyboard_menu_view()->visible());
 
   // Second keyboard item is selected, and selected item should have focus.
   EXPECT_EQ(test_api.selected_keyboard_item().value, kKeyboardIdForItem2);
@@ -251,7 +247,7 @@
 
   // Select keyboard item should close the keyboard menu.
   GetEventGenerator()->PressKey(ui::KeyboardCode::VKEY_RETURN, 0);
-  EXPECT_EQ(nullptr, test_api.keyboard_menu_view());
+  EXPECT_FALSE(test_api.keyboard_menu_view()->visible());
 }
 
 TEST_P(LoginExpandedPublicAccountViewTest, ChangeMenuSelection) {
@@ -263,7 +259,7 @@
   // Try to change language selection.
   // Open language menu.
   TapOnView(test_api.language_selection_button());
-  EXPECT_TRUE(test_api.language_menu_view()->IsVisible());
+  EXPECT_TRUE(test_api.language_menu_view()->visible());
 
   // Select second language item:
   // 1. Language menu will be closed automatically.
@@ -278,14 +274,14 @@
   EXPECT_EQ(test_api.selected_language_item().value, kEnglishLanguageCode);
   LoginMenuView::TestApi language_test_api(test_api.language_menu_view());
   TapOnView(language_test_api.contents()->child_at(1));
-  EXPECT_EQ(nullptr, test_api.language_menu_view());
+  EXPECT_FALSE(test_api.language_menu_view()->visible());
   EXPECT_EQ(test_api.selected_language_item().value, kFrenchLanguageCode);
   base::RunLoop().RunUntilIdle();
 
   // Try to change keyboard selection.
   // Open keyboard menu.
   TapOnView(test_api.keyboard_selection_button());
-  EXPECT_TRUE(test_api.keyboard_menu_view()->IsVisible());
+  EXPECT_TRUE(test_api.keyboard_menu_view()->visible());
 
   // Select first keyboard item:
   // 1. Keyboard menu will be closed automatically.
@@ -293,7 +289,7 @@
   EXPECT_EQ(test_api.selected_keyboard_item().value, kKeyboardIdForItem2);
   LoginMenuView::TestApi keyboard_test_api(test_api.keyboard_menu_view());
   TapOnView(keyboard_test_api.contents()->child_at(0));
-  EXPECT_EQ(nullptr, test_api.keyboard_menu_view());
+  EXPECT_FALSE(test_api.keyboard_menu_view()->visible());
   EXPECT_EQ(test_api.selected_keyboard_item().value, kKeyboardIdForItem1);
 }
 
diff --git a/ash/login/ui/login_menu_view.cc b/ash/login/ui/login_menu_view.cc
index f83e98c..d4ec5c0 100644
--- a/ash/login/ui/login_menu_view.cc
+++ b/ash/login/ui/login_menu_view.cc
@@ -73,7 +73,6 @@
       return;
 
     on_highlight_.Run(true /*by_selection*/);
-    GetWidget()->Close();
   }
 
   void OnHover(bool has_hover) {
@@ -127,9 +126,7 @@
                              LoginButton* opener,
                              const OnSelect& on_select)
     : LoginBaseBubbleView(anchor_view), opener_(opener), on_select_(on_select) {
-  set_can_activate(true);
-  set_margins(gfx::Insets());
-  set_color(kMenuBackgroundColor);
+  SetBackground(views::CreateSolidBackground(kMenuBackgroundColor));
   SetFocusBehavior(views::View::FocusBehavior::ALWAYS);
 
   scroller_ = new views::ScrollView();
@@ -174,6 +171,7 @@
   }
 
   if (by_selection) {
+    SetVisible(false);
     MenuItemView* menu_view = static_cast<MenuItemView*>(highlight_item);
     on_select_.Run(menu_view->item());
   }
diff --git a/ash/login/ui/login_tooltip_view.cc b/ash/login/ui/login_tooltip_view.cc
index 8b7f3a8..c91ec00 100644
--- a/ash/login/ui/login_tooltip_view.cc
+++ b/ash/login/ui/login_tooltip_view.cc
@@ -13,8 +13,6 @@
 LoginTooltipView::LoginTooltipView(const base::string16& message,
                                    views::View* anchor_view)
     : LoginBaseBubbleView(anchor_view) {
-  SetLayoutManager(
-      std::make_unique<views::BoxLayout>(views::BoxLayout::kVertical));
   SetText(message);
 }
 
@@ -32,4 +30,14 @@
   node_data->role = ax::mojom::Role::kTooltip;
 }
 
+gfx::Size LoginTooltipView::CalculatePreferredSize() const {
+  gfx::Size size;
+
+  if (GetAnchorView())
+    size.set_width(GetAnchorView()->width());
+
+  size.set_height(GetHeightForWidth(size.width()));
+  return size;
+}
+
 }  // namespace ash
diff --git a/ash/login/ui/login_tooltip_view.h b/ash/login/ui/login_tooltip_view.h
index c1836ba..cf3a6a2 100644
--- a/ash/login/ui/login_tooltip_view.h
+++ b/ash/login/ui/login_tooltip_view.h
@@ -21,6 +21,9 @@
   // LoginBaseBubbleView:
   void GetAccessibleNodeData(ui::AXNodeData* node_data) override;
 
+  // views::View:
+  gfx::Size CalculatePreferredSize() const override;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(LoginTooltipView);
 };
diff --git a/ash/login/ui/login_user_menu_view.cc b/ash/login/ui/login_user_menu_view.cc
index ad7fec3..5ab0776 100644
--- a/ash/login/ui/login_user_menu_view.cc
+++ b/ash/login/ui/login_user_menu_view.cc
@@ -38,9 +38,6 @@
 // Margin around remove user button.
 constexpr int kUserMenuMarginAroundRemoveUserButtonDp = 4;
 
-// Horizontal spacing with the anchor view.
-constexpr int kAnchorViewUserMenuHorizontalSpacingDp = 98;
-
 // Vertical spacing between the anchor view and user menu.
 constexpr int kAnchorViewUserMenuVerticalSpacingDp = 4;
 
@@ -141,16 +138,8 @@
       bubble_opener_(bubble_opener),
       on_remove_user_warning_shown_(on_remove_user_warning_shown),
       on_remove_user_requested_(on_remove_user_requested) {
-  // This view has content the user can interact with if the remove user
-  // button is displayed.
-  set_can_activate(show_remove_user);
-
-  set_anchor_view_insets(gfx::Insets(kAnchorViewUserMenuVerticalSpacingDp,
-                                     kAnchorViewUserMenuHorizontalSpacingDp));
-
   // LoginUserMenuView does not use the parent margins. Further, because the
   // splitter spans the entire view set_margins cannot be used.
-  set_margins(gfx::Insets());
   // The bottom margin is less the margin around the remove user button, which
   // is always visible.
   gfx::Insets margins(
@@ -274,8 +263,8 @@
   if (!remove_user_confirm_data_->visible()) {
     remove_user_confirm_data_->SetVisible(true);
     remove_user_label_->SetEnabledColor(kRemoveUserConfirmColor);
+
     SetSize(GetPreferredSize());
-    SizeToContents();
     Layout();
 
     // Fire an accessibility alert to make ChromeVox read the warning message
@@ -290,15 +279,24 @@
     return;
   }
 
-  // Immediately hide the widget with no animation before running the remove
+  // Immediately hide the bubble with no animation before running the remove
   // user callback. If an animation is triggered while the the views hierarchy
   // for this bubble is being torn down, we can get a crash.
-  GetWidget()->Hide();
+  SetVisible(false);
 
   if (on_remove_user_requested_)
     std::move(on_remove_user_requested_).Run();
 }
 
+gfx::Point LoginUserMenuView::CalculatePosition() {
+  gfx::Point position = LoginBaseBubbleView::CalculatePosition();
+
+  if (GetAnchorView())
+    position.set_y(position.y() + kAnchorViewUserMenuVerticalSpacingDp);
+
+  return position;
+}
+
 void LoginUserMenuView::RequestFocus() {
   // This view has no actual interesting contents to focus, so immediately
   // forward to the button.
@@ -306,26 +304,11 @@
     remove_user_button_->RequestFocus();
 }
 
-void LoginUserMenuView::AddedToWidget() {
-  LoginBaseBubbleView::AddedToWidget();
-  // Set up focus traversable parent so that keyboard focus can continue in
-  // the lock window, otherwise focus will be trapped inside the bubble.
-  if (GetAnchorView()) {
-    GetWidget()->SetFocusTraversableParent(
-        anchor_widget()->GetFocusTraversable());
-    GetWidget()->SetFocusTraversableParentView(GetAnchorView());
-  }
+bool LoginUserMenuView::HasFocus() const {
+  return remove_user_button_ && remove_user_button_->HasFocus();
 }
 
 const char* LoginUserMenuView::GetClassName() const {
   return "LoginUserMenuView";
 }
-
-gfx::Size LoginUserMenuView::CalculatePreferredSize() const {
-  gfx::Size size = LoginBaseBubbleView::CalculatePreferredSize();
-  // We don't use margins() directly which means that we need to account for
-  // the margin width here. Margin height is accounted for by the layout code.
-  size.Enlarge(kUserMenuMarginWidth, 0);
-  return size;
-}
 }  // namespace ash
diff --git a/ash/login/ui/login_user_menu_view.h b/ash/login/ui/login_user_menu_view.h
index 7d29f83..d14a1fe 100644
--- a/ash/login/ui/login_user_menu_view.h
+++ b/ash/login/ui/login_user_menu_view.h
@@ -47,15 +47,15 @@
 
   // LoginBaseBubbleView:
   LoginButton* GetBubbleOpener() const override;
+  gfx::Point CalculatePosition() override;
 
   // views::ButtonListener:
   void ButtonPressed(views::Button* sender, const ui::Event& event) override;
 
   // views::View:
   void RequestFocus() override;
-  void AddedToWidget() override;
+  bool HasFocus() const override;
   const char* GetClassName() const override;
-  gfx::Size CalculatePreferredSize() const override;
 
  private:
   LoginButton* bubble_opener_ = nullptr;
diff --git a/ash/login/ui/login_user_menu_view_unittest.cc b/ash/login/ui/login_user_menu_view_unittest.cc
index 749199a..81427f1 100644
--- a/ash/login/ui/login_user_menu_view_unittest.cc
+++ b/ash/login/ui/login_user_menu_view_unittest.cc
@@ -38,9 +38,11 @@
                           &remove_warning_called),
       base::BindRepeating([](bool* remove_called) { *remove_called = true; },
                           &remove_called));
+  anchor->AddChildView(bubble);
+
   bubble->Show();
 
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   // Focus the remove user button (the menu should forward focus to the remove
   // button).
@@ -72,9 +74,10 @@
       nullptr /*bubble_opener*/, true /*show_remove_user*/, base::DoNothing(),
       base::DoNothing());
 
+  anchor->AddChildView(bubble);
   bubble->Show();
 
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
 
   LoginUserMenuView::TestApi test_api(bubble);
   views::View* remove_user_button = test_api.remove_user_button();
@@ -122,15 +125,17 @@
       false /*is_owner*/, container /*anchor*/, bubble_opener,
       true /*show_remove_user*/, base::DoNothing(), base::DoNothing());
 
+  container->AddChildView(bubble);
+
   bubble->Show();
-  EXPECT_TRUE(bubble->IsVisible());
+  EXPECT_TRUE(bubble->visible());
   EXPECT_TRUE(ink_drop_api.HasInkDrop());
   EXPECT_EQ(ink_drop_api.GetInkDrop()->GetTargetInkDropState(),
             views::InkDropState::ACTIVATED);
   EXPECT_TRUE(ink_drop_api.GetInkDrop()->IsHighlightFadingInOrVisible());
 
   bubble->Hide();
-  EXPECT_FALSE(bubble->IsVisible());
+  EXPECT_FALSE(bubble->visible());
   EXPECT_EQ(ink_drop_api.GetInkDrop()->GetTargetInkDropState(),
             views::InkDropState::HIDDEN);
   EXPECT_FALSE(ink_drop_api.GetInkDrop()->IsHighlightFadingInOrVisible());
diff --git a/ash/login/ui/login_user_view.cc b/ash/login/ui/login_user_view.cc
index be48aba..4c4b6f7 100644
--- a/ash/login/ui/login_user_view.cc
+++ b/ash/login/ui/login_user_view.cc
@@ -422,17 +422,26 @@
       this, base::Bind(&LoginUserView::OnHover, base::Unretained(this)));
 }
 
-LoginUserView::~LoginUserView() {
-  if (menu_) {
-    menu_->GetWidget()->Close();
-    menu_ = nullptr;
-  }
-}
+LoginUserView::~LoginUserView() = default;
 
 void LoginUserView::UpdateForUser(const mojom::LoginUserInfoPtr& user,
                                   bool animate) {
   current_user_ = user->Clone();
 
+  if (menu_ && menu_->parent()) {
+    menu_->parent()->RemoveChildView(menu_);
+    delete menu_;
+  }
+
+  menu_ = new LoginUserMenuView(
+      base::UTF8ToUTF16(current_user_->basic_user_info->display_name),
+      base::UTF8ToUTF16(current_user_->basic_user_info->display_email),
+      current_user_->basic_user_info->type, current_user_->is_device_owner,
+      dropdown_ /*anchor_view*/, dropdown_ /*bubble_opener*/,
+      current_user_->can_remove /*show_remove_user*/, on_remove_warning_shown_,
+      on_remove_);
+  menu_->SetVisible(false);
+
   if (animate) {
     // Stop any existing animation.
     user_image_->layer()->GetAnimator()->StopAnimating();
@@ -529,30 +538,20 @@
   // Handle click on the dropdown arrow.
   if (sender == dropdown_) {
     DCHECK(dropdown_);
+    DCHECK(menu_);
 
     // If menu is showing, just close it
-    if (menu_ && menu_->IsVisible()) {
+    if (menu_->visible()) {
       menu_->Hide();
       return;
     }
 
-    // If the menu exists but is hidden, delete it and create a new menu.
-    if (menu_) {
-      menu_->GetWidget()->Close();
-      menu_ = nullptr;
-    }
-
-    menu_ = new LoginUserMenuView(
-        base::UTF8ToUTF16(current_user_->basic_user_info->display_name),
-        base::UTF8ToUTF16(current_user_->basic_user_info->display_email),
-        current_user_->basic_user_info->type, current_user_->is_device_owner,
-        dropdown_ /*anchor_view*/, dropdown_ /*bubble_opener*/,
-        current_user_->can_remove /*show_remove_user*/,
-        on_remove_warning_shown_, on_remove_);
-
     bool opener_focused =
         menu_->GetBubbleOpener() && menu_->GetBubbleOpener()->HasFocus();
 
+    if (!menu_->parent())
+      login_views_utils::GetTopLevelParentView(this)->AddChildView(menu_);
+
     menu_->Show();
 
     // If the menu was opened by pressing Enter on the focused dropdown, focus
@@ -644,6 +643,7 @@
   AddChildView(tap_button_);
   if (dropdown_)
     AddChildView(dropdown_);
+
   if (user_domain_)
     AddChildView(user_domain_);
 
diff --git a/ash/login/ui/login_user_view.h b/ash/login/ui/login_user_view.h
index 59b69f4..61300eb 100644
--- a/ash/login/ui/login_user_view.h
+++ b/ash/login/ui/login_user_view.h
@@ -120,9 +120,9 @@
   LoginButton* dropdown_ = nullptr;
   TapButton* tap_button_ = nullptr;
 
-  // Bubble used for displaying the user dropdown menu. Owned by its widget,
-  // which is owned by LoginUserView. This widget is closed in
-  // LoginUserMenuView's d'tor.
+  // Bubble used for displaying the user dropdown menu. Its parent is the top
+  // level view, either LockContentsView or LockDebugView. This allows the menu
+  // to be clicked outside the bounds of the user view.
   LoginBaseBubbleView* menu_ = nullptr;
 
   // Show the domain information for public account user.
diff --git a/ash/login/ui/views_utils.cc b/ash/login/ui/views_utils.cc
index 5b306df..a977b39 100644
--- a/ash/login/ui/views_utils.cc
+++ b/ash/login/ui/views_utils.cc
@@ -74,5 +74,14 @@
   return label;
 }
 
+views::View* GetTopLevelParentView(views::View* view) {
+  views::View* v = view;
+
+  while (v->parent() != nullptr)
+    v = v->parent();
+
+  return v;
+}
+
 }  // namespace login_views_utils
 }  // namespace ash
diff --git a/ash/login/ui/views_utils.h b/ash/login/ui/views_utils.h
index d4fd2b1..4e1f7c0 100644
--- a/ash/login/ui/views_utils.h
+++ b/ash/login/ui/views_utils.h
@@ -30,6 +30,9 @@
 // Creates a standard text label for use in the login bubbles.
 views::Label* CreateBubbleLabel(const base::string16& message, SkColor color);
 
+// Get the topmost level parent view for |view|.
+views::View* GetTopLevelParentView(views::View* view);
+
 }  // namespace login_views_utils
 
 }  // namespace ash
diff --git a/ash/shell/app_list.cc b/ash/shell/app_list.cc
deleted file mode 100644
index 3187358..0000000
--- a/ash/shell/app_list.cc
+++ /dev/null
@@ -1,354 +0,0 @@
-// Copyright (c) 2012 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <memory>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "ash/app_list/app_list_controller_impl.h"
-#include "ash/app_list/app_list_view_delegate.h"
-#include "ash/app_list/model/app_list_item.h"
-#include "ash/app_list/model/app_list_item_list.h"
-#include "ash/app_list/model/app_list_model.h"
-#include "ash/app_list/model/search/search_box_model.h"
-#include "ash/app_list/model/search/search_model.h"
-#include "ash/app_list/model/search/search_result.h"
-#include "ash/session/session_controller.h"
-#include "ash/shell.h"
-#include "ash/shell/example_factory.h"
-#include "ash/shell/toplevel_window.h"
-#include "base/bind_helpers.h"
-#include "base/callback.h"
-#include "base/files/file_path.h"
-#include "base/i18n/case_conversion.h"
-#include "base/i18n/string_search.h"
-#include "base/stl_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
-#include "base/strings/utf_string_conversions.h"
-#include "ui/gfx/canvas.h"
-#include "ui/gfx/font_list.h"
-#include "ui/gfx/geometry/rect.h"
-#include "ui/gfx/image/image_skia.h"
-#include "ui/views/examples/example_base.h"
-#include "ui/views/examples/examples_window.h"
-
-namespace ash {
-namespace shell {
-
-namespace {
-
-// WindowTypeShelfItem is an app item of app list. It carries a window
-// launch type and launches corresponding example window when activated.
-class WindowTypeShelfItem : public app_list::AppListItem {
- public:
-  enum Type {
-    TOPLEVEL_WINDOW = 0,
-    NON_RESIZABLE_WINDOW,
-    LOCK_SCREEN,
-    WIDGETS_WINDOW,
-    EXAMPLES_WINDOW,
-    LAST_TYPE,
-  };
-
-  WindowTypeShelfItem(const std::string& id, Type type);
-  ~WindowTypeShelfItem() override;
-
-  static gfx::ImageSkia GetIcon(Type type) {
-    static const SkColor kColors[] = {
-        SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN,
-    };
-
-    const int kIconSize = 128;
-    SkBitmap icon;
-    icon.allocN32Pixels(kIconSize, kIconSize);
-    icon.eraseColor(kColors[static_cast<int>(type) % base::size(kColors)]);
-    return gfx::ImageSkia::CreateFrom1xBitmap(icon);
-  }
-
-  // The text below is not localized as this is an example code.
-  static std::string GetTitle(Type type) {
-    switch (type) {
-      case TOPLEVEL_WINDOW:
-        return "Create Window";
-      case NON_RESIZABLE_WINDOW:
-        return "Create Non-Resizable Window";
-      case LOCK_SCREEN:
-        return "Lock Screen";
-      case WIDGETS_WINDOW:
-        return "Show Example Widgets";
-      case EXAMPLES_WINDOW:
-        return "Open Views Examples Window";
-      default:
-        return "Unknown window type.";
-    }
-  }
-
-  // The text below is not localized as this is an example code.
-  static std::string GetDetails(Type type) {
-    // Assigns details only to some types so that we see both one-line
-    // and two-line results.
-    switch (type) {
-      case WIDGETS_WINDOW:
-        return "Creates a window to show example widgets";
-      case EXAMPLES_WINDOW:
-        return "Creates a window to show views example.";
-      default:
-        return std::string();
-    }
-  }
-
-  static void ActivateItem(Type type, int event_flags) {
-    switch (type) {
-      case TOPLEVEL_WINDOW: {
-        ToplevelWindow::CreateParams params;
-        params.can_resize = true;
-        ToplevelWindow::CreateToplevelWindow(params);
-        break;
-      }
-      case NON_RESIZABLE_WINDOW: {
-        ToplevelWindow::CreateToplevelWindow(ToplevelWindow::CreateParams());
-        break;
-      }
-      case LOCK_SCREEN: {
-        Shell::Get()->session_controller()->LockScreen();
-        break;
-      }
-      case WIDGETS_WINDOW: {
-        CreateWidgetsWindow();
-        break;
-      }
-      case EXAMPLES_WINDOW: {
-        views::examples::ShowExamplesWindow(base::DoNothing());
-        break;
-      }
-      default:
-        break;
-    }
-  }
-
-  void Activate(int event_flags) { ActivateItem(type_, event_flags); }
-
- private:
-  Type type_;
-
-  DISALLOW_COPY_AND_ASSIGN(WindowTypeShelfItem);
-};
-
-WindowTypeShelfItem::WindowTypeShelfItem(const std::string& id, Type type)
-    : app_list::AppListItem(id), type_(type) {
-  std::string title(GetTitle(type));
-  SetIcon(GetIcon(type));
-  SetName(title);
-}
-
-WindowTypeShelfItem::~WindowTypeShelfItem() = default;
-
-// ExampleSearchResult is an app list search result. It provides what icon to
-// show, what should title and details text look like. It also carries the
-// matching window launch type so that AppListViewDelegate knows how to open
-// it.
-class ExampleSearchResult : public app_list::SearchResult {
- public:
-  ExampleSearchResult(WindowTypeShelfItem::Type type,
-                      const base::string16& query)
-      : type_(type) {
-    SetIcon(WindowTypeShelfItem::GetIcon(type_));
-
-    base::string16 title =
-        base::UTF8ToUTF16(WindowTypeShelfItem::GetTitle(type_));
-    set_title(title);
-
-    Tags title_tags;
-    const size_t match_len = query.length();
-
-    // Highlight matching parts in title with bold.
-    // Note the following is not a proper way to handle i18n string.
-    title = base::i18n::ToLower(title);
-    size_t match_start = title.find(query);
-    while (match_start != base::string16::npos) {
-      title_tags.push_back(
-          Tag(Tag::MATCH, match_start, match_start + match_len));
-      match_start = title.find(query, match_start + match_len);
-    }
-    set_title_tags(title_tags);
-
-    base::string16 details =
-        base::UTF8ToUTF16(WindowTypeShelfItem::GetDetails(type_));
-    set_details(details);
-    Tags details_tags;
-    details_tags.push_back(Tag(Tag::DIM, 0, details.length()));
-    set_details_tags(details_tags);
-  }
-
-  WindowTypeShelfItem::Type type() const { return type_; }
-
- private:
-  WindowTypeShelfItem::Type type_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExampleSearchResult);
-};
-
-class ExampleAppListViewDelegate : public app_list::AppListViewDelegate {
- public:
-  ExampleAppListViewDelegate()
-      : model_(std::make_unique<app_list::AppListModel>()),
-        search_model_(std::make_unique<app_list::SearchModel>()) {
-    PopulateApps();
-    DecorateSearchBox(search_model_->search_box());
-  }
-
- private:
-  void PopulateApps() {
-    for (int i = 0; i < static_cast<int>(WindowTypeShelfItem::LAST_TYPE); ++i) {
-      WindowTypeShelfItem::Type type =
-          static_cast<WindowTypeShelfItem::Type>(i);
-      std::string id = base::IntToString(i);
-      std::unique_ptr<WindowTypeShelfItem> shelf_item(
-          new WindowTypeShelfItem(id, type));
-      model_->AddItem(std::move(shelf_item));
-    }
-  }
-
-  void DecorateSearchBox(app_list::SearchBoxModel* search_box_model) {
-    search_box_model->SetHintText(base::ASCIIToUTF16("Type to search..."));
-  }
-
-  // Overridden from app_list::AppListViewDelegate:
-  app_list::AppListModel* GetModel() override { return model_.get(); }
-
-  app_list::SearchModel* GetSearchModel() override {
-    return search_model_.get();
-  }
-
-  void OpenSearchResult(const std::string& result_id,
-                        int event_flags) override {
-    const ExampleSearchResult* example_result =
-        static_cast<const ExampleSearchResult*>(
-            search_model_->FindSearchResult(result_id));
-    WindowTypeShelfItem::ActivateItem(example_result->type(), event_flags);
-  }
-
-  void LogSearchClick(const std::string& result_id,
-                      int suggestion_index) override {}
-
-  void InvokeSearchResultAction(const std::string& result_id,
-                                int action_index,
-                                int event_flags) override {
-    NOTIMPLEMENTED();
-  }
-
-  void StartAssistant() override { NOTIMPLEMENTED(); }
-
-  void StartSearch(const base::string16& raw_query) override {
-    base::string16 query;
-    base::TrimWhitespace(raw_query, base::TRIM_ALL, &query);
-    query = base::i18n::ToLower(query);
-
-    search_model_->results()->DeleteAll();
-    if (query.empty())
-      return;
-
-    for (int i = 0; i < static_cast<int>(WindowTypeShelfItem::LAST_TYPE); ++i) {
-      WindowTypeShelfItem::Type type =
-          static_cast<WindowTypeShelfItem::Type>(i);
-
-      base::string16 title =
-          base::UTF8ToUTF16(WindowTypeShelfItem::GetTitle(type));
-      if (base::i18n::StringSearchIgnoringCaseAndAccents(query, title, NULL,
-                                                         NULL)) {
-        search_model_->results()->Add(
-            std::make_unique<ExampleSearchResult>(type, query));
-      }
-    }
-  }
-
-  void ViewShown(int64_t display_id) override {
-    // Nothing needs to be done.
-  }
-
-  void DismissAppList() override {
-    Shell::Get()->app_list_controller()->DismissAppList();
-  }
-
-  void ViewClosing() override {
-    // Nothing needs to be done.
-  }
-
-  void ViewClosed() override {
-    // Nothing needs to be done.
-  }
-
-  void GetWallpaperProminentColors(
-      GetWallpaperProminentColorsCallback callback) override {
-    NOTIMPLEMENTED();
-  }
-
-  void ActivateItem(const std::string& id, int event_flags) override {
-    WindowTypeShelfItem* item =
-        static_cast<WindowTypeShelfItem*>(model_->FindItem(id));
-    if (!item)
-      return;
-    item->Activate(event_flags);
-  }
-
-  void GetContextMenuModel(const std::string& id,
-                           GetContextMenuModelCallback callback) override {
-    NOTIMPLEMENTED();
-  }
-
-  void GetSearchResultContextMenuModel(
-      const std::string& result_id,
-      GetContextMenuModelCallback callback) override {
-    NOTIMPLEMENTED();
-  }
-
-  void ContextMenuItemSelected(const std::string& id,
-                               int command_id,
-                               int event_flags) override {
-    NOTIMPLEMENTED();
-  }
-
-  void ShowWallpaperContextMenu(const gfx::Point& onscreen_location,
-                                ui::MenuSourceType source_type) override {
-    NOTIMPLEMENTED();
-  }
-
-  void SearchResultContextMenuItemSelected(const std::string& result_id,
-                                           int command_id,
-                                           int event_flags) override {
-    NOTIMPLEMENTED();
-  }
-
-  bool ProcessHomeLauncherGesture(ui::GestureEvent* event,
-                                  const gfx::Point& screen_location) override {
-    NOTIMPLEMENTED();
-    return false;
-  }
-
-  bool CanProcessEventsOnApplistViews() override {
-    NOTIMPLEMENTED();
-    return true;
-  }
-
-  void GetNavigableContentsFactory(
-      content::mojom::NavigableContentsFactoryRequest request) override {
-    NOTIMPLEMENTED();
-  }
-
-  std::unique_ptr<app_list::AppListModel> model_;
-  std::unique_ptr<app_list::SearchModel> search_model_;
-
-  DISALLOW_COPY_AND_ASSIGN(ExampleAppListViewDelegate);
-};
-
-}  // namespace
-
-app_list::AppListViewDelegate* CreateAppListViewDelegate() {
-  return new ExampleAppListViewDelegate;
-}
-
-}  // namespace shell
-}  // namespace ash
diff --git a/ash/shell/content/client/shell_browser_main_parts.cc b/ash/shell/content/client/shell_browser_main_parts.cc
index 8860ec0..ba370fc 100644
--- a/ash/shell/content/client/shell_browser_main_parts.cc
+++ b/ash/shell/content/client/shell_browser_main_parts.cc
@@ -16,6 +16,7 @@
 #include "ash/public/cpp/window_properties.h"
 #include "ash/shell.h"
 #include "ash/shell/content/embedded_browser.h"
+#include "ash/shell/example_app_list_client.h"
 #include "ash/shell/example_session_controller_client.h"
 #include "ash/shell/shell_delegate_impl.h"
 #include "ash/shell/shell_views_delegate.h"
@@ -146,6 +147,9 @@
                           base::Unretained(browser_context_.get()),
                           GURL("https://www.google.com")));
 
+  example_app_list_client_ = std::make_unique<ExampleAppListClient>(
+      Shell::Get()->app_list_controller());
+
   ash::Shell::GetPrimaryRootWindow()->GetHost()->Show();
 
   // TODO(https://crbug.com/904148): These should not use |WarmService()|.
diff --git a/ash/shell/content/client/shell_browser_main_parts.h b/ash/shell/content/client/shell_browser_main_parts.h
index 7286e929..7778da8 100644
--- a/ash/shell/content/client/shell_browser_main_parts.h
+++ b/ash/shell/content/client/shell_browser_main_parts.h
@@ -31,6 +31,7 @@
 namespace ash {
 namespace shell {
 
+class ExampleAppListClient;
 class ExampleSessionControllerClient;
 class WindowWatcher;
 
@@ -59,6 +60,7 @@
   std::unique_ptr<wm::WMState> wm_state_;
   std::unique_ptr<ExampleSessionControllerClient>
       example_session_controller_client_;
+  std::unique_ptr<ExampleAppListClient> example_app_list_client_;
   std::unique_ptr<views::MusClient> mus_client_;
 
   DISALLOW_COPY_AND_ASSIGN(ShellBrowserMainParts);
diff --git a/ash/shell/example_app_list_client.cc b/ash/shell/example_app_list_client.cc
new file mode 100644
index 0000000..2c49b70c
--- /dev/null
+++ b/ash/shell/example_app_list_client.cc
@@ -0,0 +1,258 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ash/shell/example_app_list_client.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "ash/app_list/app_list_controller_impl.h"
+#include "ash/app_list/model/app_list_item.h"
+#include "ash/app_list/model/search/search_result.h"
+#include "ash/public/interfaces/constants.mojom.h"
+#include "ash/session/session_controller.h"
+#include "ash/shell.h"
+#include "ash/shell/example_factory.h"
+#include "ash/shell/toplevel_window.h"
+#include "base/bind_helpers.h"
+#include "base/i18n/case_conversion.h"
+#include "base/i18n/string_search.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/examples/example_base.h"
+#include "ui/views/examples/examples_window.h"
+
+namespace ash {
+namespace shell {
+
+// WindowTypeShelfItem is an app item of app list. It carries a window
+// launch type and launches corresponding example window when activated.
+class WindowTypeShelfItem : public app_list::AppListItem {
+ public:
+  enum Type {
+    TOPLEVEL_WINDOW = 0,
+    NON_RESIZABLE_WINDOW,
+    LOCK_SCREEN,
+    WIDGETS_WINDOW,
+    EXAMPLES_WINDOW,
+    LAST_TYPE,
+  };
+
+  WindowTypeShelfItem(const std::string& id, Type type);
+  ~WindowTypeShelfItem() override;
+
+  static gfx::ImageSkia GetIcon(Type type) {
+    static const SkColor kColors[] = {
+        SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorYELLOW, SK_ColorCYAN,
+    };
+
+    const int kIconSize = 128;
+    SkBitmap icon;
+    icon.allocN32Pixels(kIconSize, kIconSize);
+    icon.eraseColor(kColors[static_cast<int>(type) % base::size(kColors)]);
+    return gfx::ImageSkia::CreateFrom1xBitmap(icon);
+  }
+
+  // The text below is not localized as this is an example code.
+  static std::string GetTitle(Type type) {
+    switch (type) {
+      case TOPLEVEL_WINDOW:
+        return "Create Window";
+      case NON_RESIZABLE_WINDOW:
+        return "Create Non-Resizable Window";
+      case LOCK_SCREEN:
+        return "Lock Screen";
+      case WIDGETS_WINDOW:
+        return "Show Example Widgets";
+      case EXAMPLES_WINDOW:
+        return "Open Views Examples Window";
+      default:
+        return "Unknown window type.";
+    }
+  }
+
+  // The text below is not localized as this is an example code.
+  static std::string GetDetails(Type type) {
+    // Assigns details only to some types so that we see both one-line
+    // and two-line results.
+    switch (type) {
+      case WIDGETS_WINDOW:
+        return "Creates a window to show example widgets";
+      case EXAMPLES_WINDOW:
+        return "Creates a window to show views example.";
+      default:
+        return std::string();
+    }
+  }
+
+  static void ActivateItem(Type type, int event_flags) {
+    switch (type) {
+      case TOPLEVEL_WINDOW: {
+        ToplevelWindow::CreateParams params;
+        params.can_resize = true;
+        ToplevelWindow::CreateToplevelWindow(params);
+        break;
+      }
+      case NON_RESIZABLE_WINDOW: {
+        ToplevelWindow::CreateToplevelWindow(ToplevelWindow::CreateParams());
+        break;
+      }
+      case LOCK_SCREEN: {
+        Shell::Get()->session_controller()->LockScreen();
+        break;
+      }
+      case WIDGETS_WINDOW: {
+        CreateWidgetsWindow();
+        break;
+      }
+      case EXAMPLES_WINDOW: {
+        views::examples::ShowExamplesWindow(base::DoNothing());
+        break;
+      }
+      default:
+        break;
+    }
+  }
+
+  Type type() const { return type_; }
+
+ private:
+  Type type_;
+
+  DISALLOW_COPY_AND_ASSIGN(WindowTypeShelfItem);
+};
+
+WindowTypeShelfItem::WindowTypeShelfItem(const std::string& id, Type type)
+    : app_list::AppListItem(id), type_(type) {
+  std::string title(GetTitle(type));
+  SetIcon(GetIcon(type));
+  SetName(title);
+}
+
+WindowTypeShelfItem::~WindowTypeShelfItem() = default;
+
+// ExampleSearchResult is an app list search result. It provides what icon to
+// show, what should title and details text look like. It also carries the
+// matching window launch type so that AppListViewDelegate knows how to open
+// it.
+class ExampleSearchResult : public app_list::SearchResult {
+ public:
+  ExampleSearchResult(WindowTypeShelfItem::Type type,
+                      const base::string16& query)
+      : type_(type) {
+    SetIcon(WindowTypeShelfItem::GetIcon(type_));
+
+    base::string16 title =
+        base::UTF8ToUTF16(WindowTypeShelfItem::GetTitle(type_));
+    set_title(title);
+
+    if (query.empty()) {
+      set_display_type(ash::SearchResultDisplayType::kRecommendation);
+      SetChipIcon(WindowTypeShelfItem::GetIcon(type_));
+    } else {
+      Tags title_tags;
+
+      // Highlight matching parts in title with bold.
+      // Note the following is not a proper way to handle i18n string.
+      title = base::i18n::ToLower(title);
+      const size_t match_len = query.length();
+      size_t match_start = title.find(query);
+      while (match_start != base::string16::npos) {
+        title_tags.push_back(
+            Tag(Tag::MATCH, match_start, match_start + match_len));
+        match_start = title.find(query, match_start + match_len);
+      }
+      set_title_tags(title_tags);
+    }
+
+    base::string16 details =
+        base::UTF8ToUTF16(WindowTypeShelfItem::GetDetails(type_));
+    set_details(details);
+    Tags details_tags;
+    details_tags.push_back(Tag(Tag::DIM, 0, details.length()));
+    set_details_tags(details_tags);
+  }
+
+  WindowTypeShelfItem::Type type() const { return type_; }
+
+ private:
+  WindowTypeShelfItem::Type type_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExampleSearchResult);
+};
+
+ExampleAppListClient::ExampleAppListClient(AppListControllerImpl* controller)
+    : controller_(controller) {
+  controller_->SetClient(CreateInterfacePtrAndBind());
+
+  PopulateApps();
+  DecorateSearchBox();
+}
+
+ExampleAppListClient::~ExampleAppListClient() = default;
+
+void ExampleAppListClient::PopulateApps() {
+  for (int i = 0; i < static_cast<int>(WindowTypeShelfItem::LAST_TYPE); ++i) {
+    WindowTypeShelfItem::Type type = static_cast<WindowTypeShelfItem::Type>(i);
+    const std::string id = base::IntToString(i);
+    auto app = std::make_unique<WindowTypeShelfItem>(id, type);
+    controller_->AddItem(app->CloneMetadata());
+    apps_.emplace_back(std::move(app));
+  }
+}
+
+void ExampleAppListClient::DecorateSearchBox() {
+  controller_->SetSearchHintText(base::ASCIIToUTF16("Type to search..."));
+}
+
+void ExampleAppListClient::StartSearch(const base::string16& trimmed_query) {
+  base::string16 query;
+  query = base::i18n::ToLower(trimmed_query);
+
+  search_results_.clear();
+  std::vector<ash::mojom::SearchResultMetadataPtr> result_data;
+  for (int i = 0; i < static_cast<int>(WindowTypeShelfItem::LAST_TYPE); ++i) {
+    WindowTypeShelfItem::Type type = static_cast<WindowTypeShelfItem::Type>(i);
+
+    const base::string16 title =
+        base::UTF8ToUTF16(WindowTypeShelfItem::GetTitle(type));
+    if (query.empty() || base::i18n::StringSearchIgnoringCaseAndAccents(
+                             query, title, nullptr, nullptr)) {
+      search_results_.emplace_back(
+          std::make_unique<ExampleSearchResult>(type, query));
+      result_data.emplace_back(search_results_.back()->CloneMetadata());
+    }
+  }
+  controller_->PublishSearchResults(std::move(result_data));
+}
+
+void ExampleAppListClient::OpenSearchResult(const std::string& result_id,
+                                            int event_flags) {
+  auto it = std::find_if(
+      search_results_.begin(), search_results_.end(),
+      [&result_id](const std::unique_ptr<ExampleSearchResult>& result) {
+        return result->id() == result_id;
+      });
+  if (it == search_results_.end())
+    return;
+
+  WindowTypeShelfItem::ActivateItem((*it)->type(), event_flags);
+}
+
+void ExampleAppListClient::ActivateItem(const std::string& id,
+                                        int event_flags) {
+  auto it =
+      std::find_if(apps_.begin(), apps_.end(),
+                   [&id](const std::unique_ptr<WindowTypeShelfItem>& app) {
+                     return app->id() == id;
+                   });
+  if (it == apps_.end())
+    return;
+
+  WindowTypeShelfItem::ActivateItem((*it)->type(), event_flags);
+}
+
+}  // namespace shell
+}  // namespace ash
diff --git a/ash/shell/example_app_list_client.h b/ash/shell/example_app_list_client.h
new file mode 100644
index 0000000..810f1bf
--- /dev/null
+++ b/ash/shell/example_app_list_client.h
@@ -0,0 +1,49 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef ASH_SHELL_EXAMPLE_APP_LIST_CLIENT_H_
+#define ASH_SHELL_EXAMPLE_APP_LIST_CLIENT_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ash/app_list/test/test_app_list_client.h"
+#include "base/macros.h"
+
+namespace ash {
+
+class AppListControllerImpl;
+
+namespace shell {
+
+class WindowTypeShelfItem;
+class ExampleSearchResult;
+
+class ExampleAppListClient : public TestAppListClient {
+ public:
+  explicit ExampleAppListClient(AppListControllerImpl* controller);
+  ~ExampleAppListClient() override;
+
+ private:
+  void PopulateApps();
+  void DecorateSearchBox();
+
+  // TestAppListClient:
+  void StartSearch(const base::string16& trimmed_query) override;
+  void OpenSearchResult(const std::string& result_id, int event_flags) override;
+  void ActivateItem(const std::string& id, int event_flags) override;
+
+  AppListControllerImpl* controller_;
+
+  std::vector<std::unique_ptr<WindowTypeShelfItem>> apps_;
+  std::vector<std::unique_ptr<ExampleSearchResult>> search_results_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExampleAppListClient);
+};
+
+}  // namespace shell
+}  // namespace ash
+
+#endif  // ASH_SHELL_EXAMPLE_APP_LIST_CLIENT_H_
diff --git a/base/android/java/src/org/chromium/base/task/TaskRunner.java b/base/android/java/src/org/chromium/base/task/TaskRunner.java
index b901b64f..920ee0e 100644
--- a/base/android/java/src/org/chromium/base/task/TaskRunner.java
+++ b/base/android/java/src/org/chromium/base/task/TaskRunner.java
@@ -27,12 +27,17 @@
     void destroy();
 
     /**
+     * Set this for instances that are cached between tests.
+     */
+    void disableLifetimeCheck();
+
+    /**
      * Posts a task to run after a specified delay.
      *
      * @param task The task to be run.
      * @param delay The delay in milliseconds before the task can be run.
      */
-    public void postDelayedTask(Runnable task, long delay);
+    void postDelayedTask(Runnable task, long delay);
 
     /**
      * Instructs the TaskRunner to initialize the native TaskRunner and migrate any tasks over to
diff --git a/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java b/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java
index d672f08e..f351f68 100644
--- a/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java
+++ b/base/android/java/src/org/chromium/base/task/TaskRunnerImpl.java
@@ -66,10 +66,8 @@
         }
     }
 
-    /**
-     * Set this for instances that are cached between tests.
-     */
-    void disableLifetimeCheck() {
+    @Override
+    public void disableLifetimeCheck() {
         LifetimeAssert.setSafeToGc(mLifetimeAssert, true);
     }
 
diff --git a/build/fuchsia/update_sdk.py b/build/fuchsia/update_sdk.py
index 1adf8b8..90a5622 100755
--- a/build/fuchsia/update_sdk.py
+++ b/build/fuchsia/update_sdk.py
@@ -26,7 +26,16 @@
 
 def GetSdkHashForPlatform():
   filename = '{platform}.sdk.sha1'.format(platform =  GetHostOsFromPlatform())
-  return os.path.join(os.path.dirname(__file__), filename)
+  hash_file = os.path.join(os.path.dirname(__file__), filename)
+
+  with open(hash_file, 'r') as f:
+    sdk_hash = f.read().strip()
+
+  if not sdk_hash:
+    print >>sys.stderr, 'No SHA1 found in %s' % hash_file
+    return 1
+
+  return sdk_hash
 
 def GetBucketForPlatform():
   return 'gs://fuchsia/sdk/core/{platform}-amd64/'.format(
@@ -78,12 +87,8 @@
   sdk_root = os.path.join(REPOSITORY_ROOT, 'third_party', 'fuchsia-sdk')
   Cleanup(sdk_root)
 
-  hash_file = GetSdkHashForPlatform()
-  with open(hash_file, 'r') as f:
-    sdk_hash = f.read().strip()
-
+  sdk_hash = GetSdkHashForPlatform()
   if not sdk_hash:
-    print >>sys.stderr, 'No SHA1 found in %s' % hash_file
     return 1
 
   output_dir = os.path.join(sdk_root, 'sdk')
diff --git a/cc/paint/paint_op_reader.cc b/cc/paint/paint_op_reader.cc
index a8b777f..96c632a 100644
--- a/cc/paint/paint_op_reader.cc
+++ b/cc/paint/paint_op_reader.cc
@@ -33,9 +33,17 @@
          static_cast<uint8_t>(PaintShader::Type::kShaderCount);
 }
 
-bool IsValidSkShaderTileMode(SkShader::TileMode mode) {
-  // When Skia adds Decal, update this (skbug.com/7638)
-  return mode <= SkShader::kMirror_TileMode;
+// SkShader::TileMode has no defined backing type, so read/write int32_t's.
+// If read_mode is a valid tile mode, this returns true and updates mode to the
+// equivalent enum value. Otherwise false is returned and mode is not modified.
+bool ValidateAndGetSkShaderTileMode(int32_t read_mode,
+                                    SkShader::TileMode* mode) {
+  if (read_mode < 0 || read_mode >= SkShader::kTileModeCount) {
+    return false;
+  }
+
+  *mode = static_cast<SkShader::TileMode>(read_mode);
+  return true;
 }
 
 bool IsValidPaintShaderScalingBehavior(PaintShader::ScalingBehavior behavior) {
@@ -460,10 +468,16 @@
   ReadSimple(&ref.flags_);
   ReadSimple(&ref.end_radius_);
   ReadSimple(&ref.start_radius_);
-  ReadSimple(&ref.tx_);
-  ReadSimple(&ref.ty_);
-  if (!IsValidSkShaderTileMode(ref.tx_) || !IsValidSkShaderTileMode(ref.ty_))
+
+  // See ValidateAndGetSkShaderTileMode
+  int32_t tx = 0;
+  int32_t ty = 0;
+  Read(&tx);
+  Read(&ty);
+  if (!ValidateAndGetSkShaderTileMode(tx, &ref.tx_) ||
+      !ValidateAndGetSkShaderTileMode(ty, &ref.ty_)) {
     SetInvalid();
+  }
   ReadSimple(&ref.fallback_color_);
   ReadSimple(&ref.scaling_behavior_);
   if (!IsValidPaintShaderScalingBehavior(ref.scaling_behavior_))
diff --git a/cc/paint/paint_op_writer.cc b/cc/paint/paint_op_writer.cc
index 72783966..492e0fbc 100644
--- a/cc/paint/paint_op_writer.cc
+++ b/cc/paint/paint_op_writer.cc
@@ -397,8 +397,10 @@
   WriteSimple(shader->flags_);
   WriteSimple(shader->end_radius_);
   WriteSimple(shader->start_radius_);
-  WriteSimple(shader->tx_);
-  WriteSimple(shader->ty_);
+  // SkShader::TileMode does not have an explicitly defined backing type, so
+  // write a consistently sized value.
+  Write(static_cast<int32_t>(shader->tx_));
+  Write(static_cast<int32_t>(shader->ty_));
   WriteSimple(shader->fallback_color_);
   WriteSimple(shader->scaling_behavior_);
   if (shader->local_matrix_) {
diff --git a/cc/tiles/gpu_image_decode_cache_unittest.cc b/cc/tiles/gpu_image_decode_cache_unittest.cc
index 5cb51724f..c4b19ee 100644
--- a/cc/tiles/gpu_image_decode_cache_unittest.cc
+++ b/cc/tiles/gpu_image_decode_cache_unittest.cc
@@ -284,6 +284,10 @@
         PaintImage::kDefaultGeneratorClientId, color_space.ToSkColorSpace());
   }
 
+  gfx::Size GetLargeImageSize() const {
+    return gfx::Size(1, max_texture_size_ + 1);
+  }
+
   PaintImage CreatePaintImageInternal(
       const gfx::Size& size,
       sk_sp<SkColorSpace> color_space = nullptr,
@@ -910,7 +914,7 @@
   bool is_decomposable = true;
   SkFilterQuality quality = kHigh_SkFilterQuality;
 
-  PaintImage image = CreatePaintImageInternal(gfx::Size(1, 24000));
+  PaintImage image = CreatePaintImageInternal(GetLargeImageSize());
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
                        quality,
                        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
@@ -1121,7 +1125,8 @@
   bool is_decomposable = true;
   SkFilterQuality quality = kHigh_SkFilterQuality;
 
-  PaintImage image = CreatePaintImageInternal(gfx::Size(1, 48000));
+  PaintImage image = CreatePaintImageInternal(
+      gfx::Size(GetLargeImageSize().width(), GetLargeImageSize().height() * 2));
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
                        quality,
                        CreateMatrix(SkSize::Make(0.5f, 0.5f), is_decomposable),
@@ -1142,8 +1147,8 @@
   EXPECT_TRUE(decoded_draw_image.image());
   EXPECT_TRUE(decoded_draw_image.is_budgeted());
   // The mip level scale should never go below 0 in any dimension.
-  EXPECT_EQ(1, decoded_draw_image.image()->width());
-  EXPECT_EQ(24000, decoded_draw_image.image()->height());
+  EXPECT_EQ(GetLargeImageSize().width(), decoded_draw_image.image()->width());
+  EXPECT_EQ(GetLargeImageSize().height(), decoded_draw_image.image()->height());
 
   EXPECT_EQ(decoded_draw_image.filter_quality(), kMedium_SkFilterQuality);
 
@@ -1241,7 +1246,7 @@
   SkFilterQuality quality = kHigh_SkFilterQuality;
 
   cache->SetWorkingSetLimitsForTesting(0 /* max_bytes */, 0 /* max_items */);
-  PaintImage image = CreatePaintImageInternal(gfx::Size(1, 24000));
+  PaintImage image = CreatePaintImageInternal(GetLargeImageSize());
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
                        quality,
                        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
@@ -1533,13 +1538,14 @@
   EXPECT_FALSE(low_result.task.get() == medium_result.task.get());
 
   // Get the same image at kHigh_FilterQuality. We should re-use medium.
-  DrawImage large_draw_image(
+  DrawImage high_quality_draw_image(
       image, SkIRect::MakeWH(image.width(), image.height()),
       kHigh_SkFilterQuality, matrix, PaintImage::kDefaultFrameIndex);
-  ImageDecodeCache::TaskResult large_result = cache->GetTaskForImageAndRef(
-      large_draw_image, ImageDecodeCache::TracingInfo());
-  EXPECT_TRUE(large_result.need_unref);
-  EXPECT_TRUE(medium_result.task.get() == large_result.task.get());
+  ImageDecodeCache::TaskResult high_quality_result =
+      cache->GetTaskForImageAndRef(high_quality_draw_image,
+                                   ImageDecodeCache::TracingInfo());
+  EXPECT_TRUE(high_quality_result.need_unref);
+  EXPECT_TRUE(medium_result.task.get() == high_quality_result.task.get());
 
   TestTileTaskRunner::ProcessTask(low_result.task->dependencies()[0].get());
   TestTileTaskRunner::ProcessTask(low_result.task.get());
@@ -1548,7 +1554,7 @@
 
   cache->UnrefImage(low_draw_image);
   cache->UnrefImage(medium_draw_image);
-  cache->UnrefImage(large_draw_image);
+  cache->UnrefImage(high_quality_draw_image);
 }
 
 // Ensure that switching to a mipped version of an image after the initial
@@ -1822,7 +1828,7 @@
   SkFilterQuality quality = kHigh_SkFilterQuality;
 
   // Create an image that's too large to cache.
-  PaintImage image = CreatePaintImageInternal(gfx::Size(1, 24000));
+  PaintImage image = CreatePaintImageInternal(GetLargeImageSize());
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
                        quality,
                        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
@@ -2080,7 +2086,7 @@
   SkFilterQuality quality = kHigh_SkFilterQuality;
 
   // Create an image that's too large to upload.
-  PaintImage image = CreatePaintImageInternal(gfx::Size(1, 24000));
+  PaintImage image = CreatePaintImageInternal(GetLargeImageSize());
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
                        quality,
                        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
@@ -2249,7 +2255,7 @@
   bool is_decomposable = true;
   SkFilterQuality quality = kHigh_SkFilterQuality;
 
-  PaintImage image = CreateBitmapImageInternal(gfx::Size(10, 24000));
+  PaintImage image = CreateBitmapImageInternal(GetLargeImageSize());
   DrawImage draw_image(image, SkIRect::MakeWH(image.width(), image.height()),
                        quality,
                        CreateMatrix(SkSize::Make(1.0f, 1.0f), is_decomposable),
@@ -2359,21 +2365,21 @@
                                .set_paint_image_generator(generator)
                                .TakePaintImage();
 
-  DrawImage draw_image1(
+  DrawImage draw_image(
       paint_image, SkIRect::MakeWH(paint_image.width(), paint_image.height()),
       quality, CreateMatrix(SkSize::Make(0.5, 0.5), is_decomposable),
       PaintImage::kDefaultFrameIndex);
-  DecodedDrawImage decoded_image1 =
-      EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image1));
-  ASSERT_TRUE(decoded_image1.image());
-  EXPECT_EQ(decoded_image1.image()->width(), 50);
-  EXPECT_EQ(decoded_image1.image()->height(), 50);
+  DecodedDrawImage decoded_image =
+      EnsureImageBacked(cache->GetDecodedImageForDraw(draw_image));
+  ASSERT_TRUE(decoded_image.image());
+  EXPECT_EQ(decoded_image.image()->width(), 50);
+  EXPECT_EQ(decoded_image.image()->height(), 50);
 
   // We should have requested a scaled decode from the generator.
   ASSERT_EQ(generator->decode_infos().size(), 1u);
   EXPECT_EQ(generator->decode_infos().at(0).width(), 50);
   EXPECT_EQ(generator->decode_infos().at(0).height(), 50);
-  cache->DrawWithImageFinished(draw_image1, decoded_image1);
+  cache->DrawWithImageFinished(draw_image, decoded_image);
 }
 
 TEST_P(GpuImageDecodeCacheTest, DecodeToScaleNoneQuality) {
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java
index 7b3be4b..1265b25 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java
@@ -20,12 +20,13 @@
 import org.chromium.base.Callback;
 import org.chromium.base.DiscardableReferencePool;
 import org.chromium.base.SysUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.cached_image_fetcher.CachedImageFetcher;
 import org.chromium.chrome.browser.cached_image_fetcher.InMemoryCachedImageFetcher;
 import org.chromium.chrome.browser.suggestions.ThumbnailGradient;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.Iterator;
 import java.util.List;
@@ -84,7 +85,7 @@
             Iterator<String> urlsIter, int widthPx, int heightPx, Consumer<Drawable> consumer) {
         if (!urlsIter.hasNext() || mCachedImageFetcher == null) {
             // Post to ensure callback is not run synchronously.
-            ThreadUtils.postOnUiThread(() -> consumer.accept(null));
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> consumer.accept(null));
             return;
         }
 
@@ -95,7 +96,7 @@
                 loadDrawableWithIter(urlsIter, widthPx, heightPx, consumer);
             } else {
                 // Post to ensure callback is not run synchronously.
-                ThreadUtils.postOnUiThread(() -> consumer.accept(drawable));
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> consumer.accept(drawable));
             }
         } else if (url.startsWith(OVERLAY_IMAGE_PREFIX)) {
             Uri uri = Uri.parse(url);
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/TestNetworkClient.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/TestNetworkClient.java
index 0957e5c..23594ae 100644
--- a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/TestNetworkClient.java
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/TestNetworkClient.java
@@ -24,8 +24,9 @@
 import com.google.search.now.wire.feed.mockserver.MockServerProto.ConditionalResponse;
 import com.google.search.now.wire.feed.mockserver.MockServerProto.MockServer;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -118,7 +119,7 @@
         if (mResponseDelay <= 0) {
             maybeAccept(httpResponse, responseConsumer);
         } else {
-            ThreadUtils.postOnUiThreadDelayed(
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
                     () -> maybeAccept(httpResponse, responseConsumer), mResponseDelay);
         }
     }
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index eea9faee..dca67fac 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -323,6 +323,8 @@
                 <data android:mimeType="audio/*" />
                 <data android:mimeType="image/*" />
                 <data android:mimeType="video/*" />
+                <data android:scheme="file" />
+                <data android:scheme="content" />
             </intent-filter>
         </activity>
 
diff --git a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
index 0a4d36b..f6ce084 100644
--- a/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
+++ b/chrome/android/java/monochrome_public_apk.AndroidManifest.expected
@@ -372,6 +372,8 @@
                 <data android:mimeType="audio/*" />
                 <data android:mimeType="image/*" />
                 <data android:mimeType="video/*" />
+                <data android:scheme="file" />
+                <data android:scheme="content" />
             </intent-filter>
         </activity>
         <activity
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
index 1ab4aa1..96708ac5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.java
@@ -49,6 +49,7 @@
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.IntentHandler.IntentHandlerDelegate;
 import org.chromium.chrome.browser.IntentHandler.TabOpenType;
@@ -148,6 +149,7 @@
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsAccessibility;
 import org.chromium.content_public.common.ContentSwitches;
@@ -2437,7 +2439,7 @@
         Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
         tracker.notifyEvent(EventConstants.SCREENSHOT_TAKEN_CHROME_IN_FOREGROUND);
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 getToolbarManager().showDownloadPageTextBubble(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java
index e5ea4f9..c2f8c24 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ServiceTabLauncher.java
@@ -15,8 +15,8 @@
 
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.browserservices.BrowserServicesMetrics;
 import org.chromium.chrome.browser.browserservices.TrustedWebActivityClient;
@@ -29,6 +29,7 @@
 import org.chromium.chrome.browser.webapps.WebappDataStorage;
 import org.chromium.chrome.browser.webapps.WebappRegistry;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.content_public.common.ResourceRequestBody;
@@ -73,7 +74,8 @@
         // Note that this is used by PaymentRequestEvent.openWindow().
         if (disposition == WindowOpenDisposition.NEW_POPUP) {
             if (!createPopupCustomTab(requestId, url, incognito)) {
-                ThreadUtils.postOnUiThread(() -> onWebContentsForRequestAvailable(requestId, null));
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                        () -> onWebContentsForRequestAvailable(requestId, null));
             }
             return;
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
index d94425b..f0c9341 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/SwipeRefreshHandler.java
@@ -12,6 +12,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
 import org.chromium.chrome.browser.gesturenav.SideSlideLayout;
@@ -20,6 +21,7 @@
 import org.chromium.chrome.browser.tab.TabWebContentsUserData;
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.third_party.android.swiperefresh.SwipeRefreshLayout;
 import org.chromium.ui.OverscrollAction;
@@ -114,8 +116,8 @@
 
         mSwipeRefreshLayout.setOnRefreshListener(() -> {
             cancelStopRefreshingRunnable();
-            ThreadUtils.postOnUiThreadDelayed(
-                    getStopRefreshingRunnable(), MAX_REFRESH_ANIMATION_DURATION_MS);
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, getStopRefreshingRunnable(),
+                    MAX_REFRESH_ANIMATION_DURATION_MS);
             if (mAccessibilityRefreshString == null) {
                 int resId = R.string.accessibility_swipe_refresh;
                 mAccessibilityRefreshString = context.getResources().getString(resId);
@@ -130,7 +132,7 @@
                 mDetachRefreshLayoutRunnable = null;
                 detachSwipeRefreshLayoutIfNecessary();
             };
-            ThreadUtils.postOnUiThread(mDetachRefreshLayoutRunnable);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, mDetachRefreshLayoutRunnable);
         });
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java
index 6bc5f7c..e8df2f06 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill/AutofillNameFixFlowBridge.java
@@ -6,12 +6,13 @@
 
 import android.app.Activity;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ResourceId;
 import org.chromium.chrome.browser.autofill.AutofillNameFixFlowPrompt.AutofillNameFixFlowPromptDelegate;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.modaldialog.DialogDismissalCause;
 
@@ -42,7 +43,7 @@
             mNameFixFlowPrompt = null;
             // Clean up the native counterpart. This is posted to allow the native counterpart
             // to fully finish the construction of this glue object before we attempt to delete it.
-            ThreadUtils.postOnUiThread(() -> onPromptDismissed());
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> onPromptDismissed());
         }
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
index 6919170..4af9c65c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AssistantCoordinator.java
@@ -7,7 +7,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.autofill_assistant.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.autofill_assistant.header.AssistantHeaderModel;
@@ -20,6 +20,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.snackbar.Snackbar;
 import org.chromium.chrome.browser.snackbar.SnackbarManager;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 
 /**
@@ -119,7 +120,7 @@
             mModel.getHeaderModel().set(AssistantHeaderModel.STATUS_MESSAGE,
                     mActivity.getString(R.string.autofill_assistant_give_up));
         }
-        ThreadUtils.postOnUiThreadDelayed(
+        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
                 () -> shutdownImmediately(reason), GRACEFUL_SHUTDOWN_DELAY_MS);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
index 755a801..05137cf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/autofill_assistant/AutofillAssistantFacade.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.autofill_assistant.metrics.DropOutReason;
+import org.chromium.chrome.browser.metrics.UmaSessionStats;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.util.IntentUtils;
 
@@ -52,6 +53,15 @@
             "com.google.android.googlequicksearchbox", // GSA
     };
 
+    /**
+     * Synthetic field trial names and group names should match those specified in
+     * google3/analysis/uma/dashboards/
+     * .../variations/generate_server_hashes.py and
+     * .../website/components/variations_dash/variations_histogram_entry.js.
+     */
+    private static final String SYNTHETIC_TRIAL = "AutofillAssistantTriggered";
+    private static final String ENABLED_GROUP = "Enabled";
+
     /** Returns true if conditions are satisfied to attempt to start Autofill Assistant. */
     public static boolean isConfigured(@Nullable Bundle intentExtras) {
         return getBooleanParameter(intentExtras, PARAMETER_ENABLED);
@@ -59,6 +69,8 @@
 
     /** Starts Autofill Assistant on the given {@code activity}. */
     public static void start(ChromeActivity activity) {
+        // Register synthetic trial as soon as possible.
+        UmaSessionStats.registerSyntheticFieldTrial(SYNTHETIC_TRIAL, ENABLED_GROUP);
         // Have an "attempted starts" baseline for the drop out histogram.
         AutofillAssistantMetrics.recordDropOut(DropOutReason.AA_START);
         if (canStart(activity.getInitialIntent())) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java b/chrome/android/java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java
index 066d50be..53fe9f7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java
@@ -12,6 +12,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.init.BrowserParts;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.init.EmptyBrowserParts;
@@ -19,6 +20,7 @@
 import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerExternalUma;
 import org.chromium.components.background_task_scheduler.TaskParameters;
 import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -76,7 +78,7 @@
             // Do not pass in wrappedCallback because this is a short-circuit reschedule. For UMA
             // purposes, tasks are started when runWithNative is called and does not consider
             // short-circuit reschedules such as this.
-            ThreadUtils.postOnUiThread(buildRescheduleRunnable(callback));
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, buildRescheduleRunnable(callback));
             return true;
         }
 
@@ -115,14 +117,14 @@
     protected final void runWithNative(final Context context,
             final Runnable startWithNativeRunnable, final Runnable rescheduleRunnable) {
         if (isNativeLoaded()) {
-            ThreadUtils.postOnUiThread(startWithNativeRunnable);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, startWithNativeRunnable);
             return;
         }
 
         final BrowserParts parts = new EmptyBrowserParts() {
             @Override
             public void finishNativeInitialization() {
-                ThreadUtils.postOnUiThread(startWithNativeRunnable);
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, startWithNativeRunnable);
             }
             @Override
             public boolean startServiceManagerOnly() {
@@ -130,11 +132,11 @@
             }
             @Override
             public void onStartupFailure() {
-                ThreadUtils.postOnUiThread(rescheduleRunnable);
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, rescheduleRunnable);
             }
         };
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 // If task was stopped before we got here, don't start native initialization.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java
index 5ae8239..3d50a33 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/PostMessageHandler.java
@@ -10,12 +10,13 @@
 import android.support.customtabs.PostMessageBackend;
 
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.browserservices.OriginVerifier.OriginVerificationListener;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.content_public.browser.MessagePort;
 import org.chromium.content_public.browser.MessagePort.MessageCallback;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsObserver;
 
@@ -135,7 +136,7 @@
         if (mWebContents == null || mWebContents.isDestroyed()) {
             return CustomTabsService.RESULT_FAILURE_MESSAGING_ERROR;
         }
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 // It is still possible that the page has navigated while this task is in the queue.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java b/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
index 7194d6b..ceefb78 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/childaccounts/ChildAccountService.java
@@ -10,8 +10,10 @@
 import org.chromium.base.Callback;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.task.PostTask;
 import org.chromium.components.signin.AccountManagerFacade;
 import org.chromium.components.signin.ChildAccountStatus;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.base.WindowAndroid;
 
 /**
@@ -65,7 +67,8 @@
 
         Activity activity = windowAndroid.getActivity().get();
         if (activity == null) {
-            ThreadUtils.postOnUiThread(() -> nativeOnReauthenticationResult(nativeCallback, false));
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                    () -> nativeOnReauthenticationResult(nativeCallback, false));
             return;
         }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
index 238bfc1..df9e78f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
@@ -17,13 +17,14 @@
 import android.view.View.OnCreateContextMenuListener;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.share.ShareHelper;
 import org.chromium.chrome.browser.share.ShareParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.base.MenuSourceType;
 import org.chromium.ui.base.WindowAndroid;
@@ -126,7 +127,7 @@
             List<Pair<Integer, List<ContextMenuItem>>> items =
                     mPopulator.buildContextMenu(null, mActivity, mCurrentContextMenuParams);
             if (items.isEmpty()) {
-                ThreadUtils.postOnUiThread(mOnMenuClosed);
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, mOnMenuClosed);
                 return;
             }
 
@@ -259,7 +260,7 @@
                 mPopulator.buildContextMenu(menu, v.getContext(), mCurrentContextMenuParams);
 
         if (items.isEmpty()) {
-            ThreadUtils.postOnUiThread(mOnMenuClosed);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, mOnMenuClosed);
             return;
         }
         ContextMenuUi menuUi = new PlatformContextMenuUi(menu);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java
index 971b51f1..4e2f0a22 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crash/MinidumpUploadService.java
@@ -31,6 +31,8 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -65,6 +67,7 @@
     private static AtomicBoolean sDidBrowserCrashRecently = new AtomicBoolean();
 
     @StringDef({ProcessType.BROWSER, ProcessType.RENDERER, ProcessType.GPU, ProcessType.OTHER})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ProcessType {
         String BROWSER = "Browser";
         String RENDERER = "Renderer";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
index 6e18787..f976d9b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/crypto/CipherFactory.java
@@ -10,8 +10,9 @@
 import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
 import org.chromium.base.SecureRandomInitializer;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
@@ -164,7 +165,7 @@
                     mData = data;
 
                     // Posting an asynchronous task to notify the observers.
-                    ThreadUtils.postOnUiThread(new Runnable() {
+                    PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                         @Override
                         public void run() {
                             notifyCipherDataGenerated();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 7f79b6c..5e23274 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -42,6 +42,7 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabTaskDescriptionHelper;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -89,13 +90,13 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
-
 /**
  * The activity for custom tabs. It will be launched on top of a client's task.
  */
@@ -713,12 +714,8 @@
             // memory consumption, as the current renderer goes away. We create a renderer as a lot
             // of users open several Custom Tabs in a row. The delay is there to avoid jank in the
             // transition animation when closing the tab.
-            ThreadUtils.postOnUiThreadDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    WarmupManager.getInstance().createSpareWebContents();
-                }
-            }, 500);
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
+                    () -> WarmupManager.getInstance().createSpareWebContents(), 500);
         }
 
         handleFinishAndClose();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
index add9ec0..5f440b18 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabsConnection.java
@@ -45,6 +45,7 @@
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeApplication;
@@ -68,6 +69,7 @@
 import org.chromium.chrome.browser.util.IntentUtils;
 import org.chromium.content_public.browser.BrowserStartupController;
 import org.chromium.content_public.browser.ChildProcessLauncherHelper;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.network.mojom.ReferrerPolicy;
@@ -559,7 +561,7 @@
             return false;
         }
 
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             doMayLaunchUrlOnUiThread(
                     lowConfidence, session, uid, urlString, extras, otherLikelyBundles, true);
         });
@@ -579,7 +581,7 @@
             if (!BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
                             .isStartupSuccessfullyCompleted()) {
                 if (retryIfNotLoaded) {
-                    ThreadUtils.postOnUiThread(() -> {
+                    PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                         doMayLaunchUrlOnUiThread(lowConfidence, session, uid, urlString, extras,
                                 otherLikelyBundles, false);
                     });
@@ -684,7 +686,7 @@
         if (!mClientManager.bindToPostMessageServiceForSession(session)) return false;
 
         final int uid = Binder.getCallingUid();
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             // If the API is not enabled, we don't set the post message origin, which will avoid
             // PostMessageHandler initialization and disallow postMessage calls.
             if (!ChromeFeatureList.isEnabled(ChromeFeatureList.CCT_POST_MESSAGE_API)) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java
index 39f1860..5a6862f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/dynamicmodule/DynamicModuleCoordinator.java
@@ -19,9 +19,9 @@
 import android.view.ViewGroup;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.UrlConstants;
@@ -45,6 +45,7 @@
 import org.chromium.chrome.browser.util.UrlUtilities;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationHandleProxy;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 
 import java.lang.annotation.Retention;
@@ -81,8 +82,8 @@
     @Nullable
     private PostMessageHandler mDynamicModulePostMessageHandler;
 
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({View.VISIBLE, View.INVISIBLE, View.GONE})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface ToolbarVisibility {}
 
     // Default visibility of the Toolbar prior to any header customization.
@@ -307,8 +308,10 @@
     public boolean requestPostMessageChannel(Uri postMessageOrigin) {
         if (mDynamicModulePostMessageHandler == null) return false;
 
-        ThreadUtils.postOnUiThread(() ->
-                mDynamicModulePostMessageHandler.initializeWithPostMessageUri(postMessageOrigin));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                ()
+                        -> mDynamicModulePostMessageHandler.initializeWithPostMessageUri(
+                                postMessageOrigin));
         return true;
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
index 163fb551..c1bb778 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/externalnav/ExternalNavigationDelegateImpl.java
@@ -32,6 +32,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity2;
@@ -53,6 +54,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.common.Referrer;
 import org.chromium.network.mojom.ReferrerPolicy;
 import org.chromium.ui.base.PageTransition;
@@ -557,7 +559,7 @@
             // Loading URL will start a new navigation which cancels the current one
             // that this clobbering is being done for. It leads to UAF. To avoid that,
             // we're loading URL asynchronously. See https://crbug.com/732260.
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     // Tab might be closed when this is run. See https://crbug.com/662877
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitor.java b/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitor.java
index e157129..b44a6d4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitor.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitor.java
@@ -4,13 +4,16 @@
 
 package org.chromium.chrome.browser.feature_engagement;
 
+import android.Manifest;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Handler;
 import android.provider.MediaStore;
 import android.provider.MediaStore.Images.Media;
+import android.support.v4.content.ContextCompat;
 import android.util.DisplayMetrics;
 import android.view.WindowManager;
 
@@ -18,6 +21,9 @@
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /**
  * This class detects screenshots by monitoring the screenshots directory on internal and external
@@ -77,7 +83,7 @@
 
             if (!doesChangeLookLikeScreenshot(uri)) return;
 
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     if (mScreenshotMonitor == null) return;
@@ -102,6 +108,15 @@
         String[] mediaProjection = new String[] {MediaStore.Images.ImageColumns.DATE_TAKEN,
                 MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.HEIGHT,
                 MediaStore.MediaColumns.WIDTH, MediaStore.MediaColumns._ID};
+
+        // Check if READ_EXTERNAL_STORAGE permission are enabled.
+        if (ContextCompat.checkSelfPermission(
+                    ContextUtils.getApplicationContext(), Manifest.permission.READ_EXTERNAL_STORAGE)
+                != PackageManager.PERMISSION_GRANTED) {
+            RecordUserAction.record("Tab.Screenshot.WithoutStoragePermission");
+            return false;
+        }
+
         try {
             cursor = ContextUtils.getApplicationContext().getContentResolver().query(
                     storeUri, mediaProjection, null, null, null);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
index a76758e1..255957c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityChecker.java
@@ -10,7 +10,9 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -52,7 +54,7 @@
     }
 
     private static void postResult(final ConnectivityCheckerCallback callback, final int result) {
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 callback.onResult(result);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityTask.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityTask.java
index 3a491cd..540ba17 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ConnectivityTask.java
@@ -11,7 +11,9 @@
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.ConnectionType;
 import org.chromium.net.NetworkChangeNotifier;
 
@@ -255,7 +257,7 @@
 
         private void postCallbackResult() {
             if (mCallback == null) return;
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     mCallback.onResult(get());
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java
index 1812d24..cdd2c1a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/FeedbackCollector.java
@@ -15,8 +15,10 @@
 import org.chromium.base.CollectionUtil;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -216,7 +218,7 @@
         final Callback<FeedbackCollector> callback = mCallback;
         mCallback = null;
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 callback.onResult(FeedbackCollector.this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java
index e07a2228..a4f7aba 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/ScreenshotTask.java
@@ -10,12 +10,13 @@
 import android.graphics.Rect;
 import android.support.annotation.Nullable;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.tab.SadTab;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.UiUtils;
 import org.chromium.ui.base.WindowAndroid;
 
@@ -54,7 +55,7 @@
 
         // If neither the compositor nor the Android view screenshot tasks were kicked off, admit
         // defeat and return a {@code null} screenshot.
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 onBitmapReceived(null);
@@ -101,7 +102,7 @@
     private boolean takeAndroidViewScreenshot(@Nullable final Activity activity) {
         if (activity == null) return false;
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 Bitmap bitmap = UiUtils.generateScaledScreenshot(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/feedback/StaticScreenshotSource.java b/chrome/android/java/src/org/chromium/chrome/browser/feedback/StaticScreenshotSource.java
index b912d4e8..ee22958b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/feedback/StaticScreenshotSource.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/feedback/StaticScreenshotSource.java
@@ -6,7 +6,8 @@
 
 import android.graphics.Bitmap;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /** A implementation of {@link ScreenshotSource} that returns back a {@link Bitmap} given to it. */
 class StaticScreenshotSource implements ScreenshotSource {
@@ -23,7 +24,7 @@
     // ScreenshotSource implementation.
     @Override
     public void capture(Runnable callback) {
-        ThreadUtils.postOnUiThread(callback);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, callback);
     }
 
     @Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
index 59e58bb..73f0a5d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/firstrun/DefaultSearchEngineFirstRunFragment.java
@@ -11,14 +11,15 @@
 import android.view.ViewGroup;
 import android.widget.Button;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.locale.DefaultSearchEngineDialogHelper;
 import org.chromium.chrome.browser.locale.LocaleManager;
 import org.chromium.chrome.browser.locale.LocaleManager.SearchEnginePromoType;
 import org.chromium.chrome.browser.search_engines.TemplateUrlService;
 import org.chromium.chrome.browser.widget.RadioButtonLayout;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /** A {@link Fragment} that presents a set of search engines for the user to choose from. */
 public class DefaultSearchEngineFirstRunFragment extends Fragment implements FirstRunFragment {
@@ -72,7 +73,7 @@
 
         if (isVisibleToUser) {
             if (mSearchEnginePromoDialoType == LocaleManager.SearchEnginePromoType.DONT_SHOW) {
-                ThreadUtils.postOnUiThread(new Runnable() {
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                     @Override
                     public void run() {
                         getPageDelegate().advanceToNextPage();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
index 98d60622..4e87d81 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/ChromeFullscreenManager.java
@@ -18,9 +18,9 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ApplicationStatus.ActivityStateListener;
 import org.chromium.base.ApplicationStatus.WindowFocusChangedListener;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.library_loader.LibraryLoader;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.fullscreen.FullscreenHtmlApiHandler.FullscreenHtmlApiDelegate;
 import org.chromium.chrome.browser.tab.BrowserControlsVisibilityDelegate;
@@ -31,6 +31,7 @@
 import org.chromium.chrome.browser.tabmodel.TabSelectionType;
 import org.chromium.chrome.browser.vr.VrModuleProvider;
 import org.chromium.chrome.browser.widget.ControlContainer;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.common.BrowserControlsState;
 
 import java.lang.annotation.Retention;
@@ -264,12 +265,10 @@
             // notification bar when this was done in onStart()).
             exitPersistentFullscreenMode();
         } else if (newState == ActivityState.STARTED) {
-            ThreadUtils.postOnUiThreadDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    mBrowserVisibilityDelegate.showControlsTransient();
-                }
-            }, ACTIVITY_RETURN_SHOW_REQUEST_DELAY_MS);
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
+                    ()
+                            -> mBrowserVisibilityDelegate.showControlsTransient(),
+                    ACTIVITY_RETURN_SHOW_REQUEST_DELAY_MS);
         } else if (newState == ActivityState.DESTROYED) {
             ApplicationStatus.unregisterActivityStateListener(this);
             ApplicationStatus.unregisterWindowFocusChangedListener(this);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
index a28b64f..75cffabd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/HistoryNavigationLayout.java
@@ -16,6 +16,9 @@
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.Tab;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * FrameLayout that supports side-wise slide gesture for history navigation. Inheriting
  * class may need to override {@link #isGestureConsumed()} if {@link #onTouchEvent} cannot
@@ -24,6 +27,7 @@
  */
 public class HistoryNavigationLayout extends FrameLayout {
     @IntDef({GestureState.NONE, GestureState.STARTED, GestureState.DRAGGED})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface GestureState {
         int NONE = 0;
         int STARTED = 1;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java
index 2697a55b..1560d89e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/gesturenav/SideSlideLayout.java
@@ -17,6 +17,9 @@
 import org.chromium.chrome.R;
 import org.chromium.third_party.android.swiperefresh.CircleImageView;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * The SideSlideLayout can be used whenever the user navigates the contents
  * of a view using horizontal gesture. Shows an arrow widget moving horizontally
@@ -34,6 +37,7 @@
             UmaNavigationType.BACK_TOUCHPAD, UmaNavigationType.FORWARD_TOUCHSCREEN,
             UmaNavigationType.BACK_TOUCHSCREEN, UmaNavigationType.RELOAD_TOUCHPAD,
             UmaNavigationType.RELOAD_TOUCHSCREEN, UmaNavigationType.NAVIGATION_TYPE_COUNT})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface UmaNavigationType {
         int NAVIGATION_TYPE_NONE = 0;
         int FORWARD_TOUCHPAD = 1;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
index d5916fb..78885772c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java
@@ -14,6 +14,7 @@
 import org.chromium.chrome.browser.notifications.ChromeNotificationBuilder;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
+import org.chromium.chrome.browser.notifications.NotificationMetadata;
 import org.chromium.chrome.browser.notifications.NotificationUmaTracker;
 import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
 
@@ -37,8 +38,13 @@
 
         ChromeNotificationBuilder builder =
                 NotificationBuilderFactory
-                        .createChromeNotificationBuilder(
-                                true /* preferCompat */, ChannelDefinitions.ChannelId.INCOGNITO)
+                        .createChromeNotificationBuilder(true /* preferCompat */,
+                                ChannelDefinitions.ChannelId.INCOGNITO,
+                                null /* remoteAppPackageName */,
+                                new NotificationMetadata(
+                                        NotificationUmaTracker.SystemNotificationType
+                                                .CLOSE_INCOGNITO,
+                                        INCOGNITO_TABS_OPEN_TAG, INCOGNITO_TABS_OPEN_ID))
                         .setContentTitle(title)
                         .setContentIntent(
                                 IncognitoNotificationService.getRemoveAllIncognitoTabsIntent(
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java
index 9c3c9563..f09691a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationService.java
@@ -26,6 +26,7 @@
 import org.chromium.chrome.browser.ChromeTabbedActivity;
 import org.chromium.chrome.browser.document.ChromeLauncherActivity;
 import org.chromium.chrome.browser.document.DocumentUtils;
+import org.chromium.chrome.browser.notifications.PendingIntentProvider;
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.content_public.browser.BrowserStartupController;
 
@@ -43,10 +44,11 @@
             "com.google.android.apps.chrome.incognito.CLOSE_ALL_INCOGNITO";
 
     @VisibleForTesting
-    public static PendingIntent getRemoveAllIncognitoTabsIntent(Context context) {
+    public static PendingIntentProvider getRemoveAllIncognitoTabsIntent(Context context) {
         Intent intent = new Intent(context, IncognitoNotificationService.class);
         intent.setAction(ACTION_CLOSE_ALL_INCOGNITO);
-        return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+        return PendingIntentProvider.getService(
+                context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     /** Empty public constructor needed by Android. */
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/infobar/DownloadProgressInfoBar.java b/chrome/android/java/src/org/chromium/chrome/browser/infobar/DownloadProgressInfoBar.java
index 01cab91..a646f11 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/infobar/DownloadProgressInfoBar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/infobar/DownloadProgressInfoBar.java
@@ -13,12 +13,13 @@
 import android.view.View;
 import android.widget.TextView;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.download.DownloadInfoBarController;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.offline_items_collection.ContentId;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /**
  * An {@link InfoBar} to provide information about currently running downloads.
@@ -151,7 +152,7 @@
     }
 
     private void restartIconAnimation() {
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             if (mAnimatedDrawable == null) return;
             mAnimatedDrawable.start();
         });
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java
index df09423..c57af3e8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/installedapp/InstalledAppProviderImpl.java
@@ -19,7 +19,9 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.instantapps.InstantAppsHandler;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.installedapp.mojom.InstalledAppProvider;
 import org.chromium.installedapp.mojom.RelatedApplication;
 import org.chromium.mojo.system.MojoException;
@@ -27,7 +29,6 @@
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
-
 /**
  * Android implementation of the InstalledAppProvider service defined in
  * installed_app_provider.mojom
@@ -353,6 +354,6 @@
      * @return True if the Runnable was successfully placed into the message queue.
      */
     protected void delayThenRun(Runnable r, long delayMillis) {
-        ThreadUtils.postOnUiThreadDelayed(r, delayMillis);
+        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, r, delayMillis);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java b/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
index c8970253..389455c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/locale/SogouPromoDialog.java
@@ -36,9 +36,9 @@
 public class SogouPromoDialog extends PromoDialog {
     // These constants are here to back a uma histogram. Append new constants at the end of this
     // list (do not rearrange) and don't forget to update NUM_ENTRIES.
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({UserChoice.USE_SOGOU, UserChoice.KEEP_GOOGLE, UserChoice.SETTINGS,
             UserChoice.BACK_KEY})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface UserChoice {
         int USE_SOGOU = 0;
         int KEEP_GOOGLE = 1;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
index b821b83..b433f07 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ActionItem.java
@@ -32,8 +32,8 @@
  * show a progress indicator over the same space. See {@link State}.
  */
 public class ActionItem extends OptionalLeaf {
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({State.HIDDEN, State.BUTTON, State.LOADING})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface State {
         int HIDDEN = 0;
         int BUTTON = 1;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ScrollToLoadListener.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ScrollToLoadListener.java
index a76287e..e6146c217 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ScrollToLoadListener.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/cards/ScrollToLoadListener.java
@@ -7,7 +7,8 @@
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /**
  * The ScrollToLoadListener requests fetching more items when the user approaches the end of their
@@ -77,7 +78,7 @@
         if (sentinelBecameVisible || sentinelVisibleButTooFewItemsFetched) {
             mPreviousItemCount = mAdapter.getItemCount();
             // We need to post this since onScrolled may run during a measure & layout pass.
-            ThreadUtils.postOnUiThread(mSections::fetchMore);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, mSections::fetchMore);
         }
 
         mSentinelPreviouslyVisible = sentinelVisible;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java
index d598503..750536f 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/offlinepages/indicator/ConnectivityDetector.java
@@ -47,9 +47,9 @@
     public static final int PROBE_WITH_URL_COUNT = 2;
 
     // Denotes the connection state.
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({ConnectionState.NONE, ConnectionState.DISCONNECTED, ConnectionState.NO_INTERNET,
             ConnectionState.CAPTIVE_PORTAL, ConnectionState.VALIDATED})
+    @Retention(RetentionPolicy.SOURCE)
     public @interface ConnectionState {
         // Initial state or connection state can't be evaluated.
         int NONE = 0;
@@ -67,10 +67,10 @@
     }
 
     // Denotes how the connectivity check is done.
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({ConnectivityCheckingState.NOT_STARTED, ConnectivityCheckingState.FROM_SYSTEM,
             ConnectivityCheckingState.PROBE_DEFAULT_URL,
             ConnectivityCheckingState.PROBE_FALLBACK_URL})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface ConnectivityCheckingState {
         // Not started.
         int NOT_STARTED = 0;
@@ -85,11 +85,11 @@
     // The result of the HTTP probing. Defined in tools/metrics/histograms/enums.xml.
     // These values are persisted to logs. Entries should not be renumbered and
     // numeric values should never be reused.
-    @Retention(RetentionPolicy.SOURCE)
     @IntDef({ProbeResult.NO_INTERNET, ProbeResult.SERVER_ERROR, ProbeResult.NOT_VALIDATED,
             ProbeResult.VALIDATED_WITH_NO_CONTENT,
             ProbeResult.VALIDATED_WITH_OK_BUT_ZERO_CONTENT_LENGTH,
             ProbeResult.VALIDATED_WITH_OK_BUT_NO_CONTENT_LENGTH})
+    @Retention(RetentionPolicy.SOURCE)
     private @interface ProbeResult {
         // The network is connected, but it can't reach the Internet, i.e. connecting to a hotspot
         // that is not conencted to Internet.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java b/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java
index f9518e8..04958d11 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/partnercustomizations/PartnerBrowserCustomizations.java
@@ -19,6 +19,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeSwitches;
 import org.chromium.chrome.browser.ChromeVersionInfo;
@@ -26,6 +27,7 @@
 import org.chromium.chrome.browser.ntp.NewTabPage;
 import org.chromium.chrome.browser.partnerbookmarks.PartnerBookmarksReader;
 import org.chromium.chrome.browser.util.UrlUtilities;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -307,7 +309,7 @@
      */
     public static void setOnInitializeAsyncFinished(final Runnable callback) {
         if (sIsInitialized) {
-            ThreadUtils.postOnUiThread(callback);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, callback);
         } else {
             sInitializeAsyncCallbacks.add(callback);
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java
index dec8abe..5456047 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/PaymentInstrument.java
@@ -7,8 +7,9 @@
 import android.graphics.drawable.Drawable;
 import android.support.annotation.Nullable;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.widget.prefeditor.EditableOption;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.payments.mojom.PaymentDetailsModifier;
 import org.chromium.payments.mojom.PaymentItem;
 import org.chromium.payments.mojom.PaymentMethodData;
@@ -178,7 +179,7 @@
      * @param callback The callback to return abort result.
      */
     public void abortPaymentApp(AbortCallback callback) {
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 callback.onInstrumentAbortResult(false);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
index 955b035..48d0a9d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/ServiceWorkerPaymentAppBridge.java
@@ -16,11 +16,13 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNIAdditionalImport;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeFeatureList;
 import org.chromium.chrome.browser.tab.EmptyTabObserver;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.components.payments.OriginSecurityChecker;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.payments.mojom.PaymentDetailsModifier;
 import org.chromium.payments.mojom.PaymentItem;
@@ -90,7 +92,7 @@
         ThreadUtils.assertOnUiThread();
 
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS)) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     callback.onHasServiceWorkerPaymentAppsResponse(false);
@@ -111,7 +113,7 @@
         ThreadUtils.assertOnUiThread();
 
         if (!ChromeFeatureList.isEnabled(ChromeFeatureList.SERVICE_WORKER_PAYMENT_APPS)) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     callback.onGetServiceWorkerPaymentAppsInfo(
@@ -142,7 +144,7 @@
         ThreadUtils.assertOnUiThread();
 
         if (sCanMakePaymentForTesting) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     callback.onCanMakePaymentResponse(true);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/payments/SettingsAutofillAndPaymentsObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/payments/SettingsAutofillAndPaymentsObserver.java
index fdff69d..73d2ed5 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/payments/SettingsAutofillAndPaymentsObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/payments/SettingsAutofillAndPaymentsObserver.java
@@ -5,7 +5,9 @@
 package org.chromium.chrome.browser.payments;
 
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.autofill.PersonalDataManager.CreditCard;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -84,7 +86,7 @@
      */
     public void notifyOnAddressUpdated(AutofillAddress address) {
         for (Observer observer : sObservers) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     observer.onAddressUpdated(address);
@@ -100,7 +102,7 @@
      */
     public void notifyOnAddressDeleted(String guid) {
         for (Observer observer : sObservers) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     observer.onAddressDeleted(guid);
@@ -116,7 +118,7 @@
      */
     public void notifyOnCreditCardUpdated(CreditCard card) {
         for (Observer observer : sObservers) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     observer.onCreditCardUpdated(card);
@@ -132,7 +134,7 @@
      */
     public void notifyOnCreditCardDeleted(String guid) {
         for (Observer observer : sObservers) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     observer.onCreditCardDeleted(guid);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManageSyncPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManageSyncPreferences.java
index fbd9263..3205e22 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManageSyncPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/ManageSyncPreferences.java
@@ -23,9 +23,9 @@
 import android.view.MenuItem;
 
 import org.chromium.base.ApiCompatibilityUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
@@ -39,6 +39,7 @@
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.sync.ModelType;
 import org.chromium.components.sync.PassphraseType;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -187,7 +188,7 @@
     public boolean onPreferenceChange(Preference preference, Object o) {
         // A change to Preference state hasn't been applied yet. Defer
         // updateSyncStateFromSelectedModelTypes so it gets the updated state from isChecked().
-        ThreadUtils.postOnUiThread(this::updateSyncStateFromSelectedModelTypes);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncStateFromSelectedModelTypes);
         return true;
     }
 
@@ -201,7 +202,7 @@
     public void syncStateChanged() {
         // This is invoked synchronously from ProfileSyncService.setChosenDataTypes, postpone the
         // update to let updateSyncStateFromSelectedModelTypes finish saving the state.
-        ThreadUtils.postOnUiThread(this::updateSyncPreferences);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncPreferences);
     }
 
     /**
@@ -232,7 +233,7 @@
                 mSyncEverything.isChecked(), getSelectedModelTypes());
         PersonalDataManager.setPaymentsIntegrationEnabled(mSyncPaymentsIntegration.isChecked());
         // Some calls to setChosenDataTypes don't trigger syncStateChanged, so schedule update here.
-        ThreadUtils.postOnUiThread(this::updateSyncPreferences);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncPreferences);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java
index 7b409164..8d44c5b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/SyncAndServicesPreferences.java
@@ -27,9 +27,9 @@
 
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsEnabledStateUtils;
 import org.chromium.chrome.browser.contextualsearch.ContextualSearchFieldTrial;
@@ -50,6 +50,7 @@
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.sync.AndroidSyncSettings;
 import org.chromium.components.sync.ProtocolErrorClientAction;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -277,7 +278,7 @@
         if (PREF_SYNC_REQUESTED.equals(key)) {
             assert canDisableSync();
             SyncPreferenceUtils.enableSync((boolean) newValue);
-            ThreadUtils.postOnUiThread(this::updatePreferences);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updatePreferences);
         } else if (PREF_SEARCH_SUGGESTIONS.equals(key)) {
             mPrefServiceBridge.setSearchSuggestEnabled((boolean) newValue);
         } else if (PREF_SAFE_BROWSING.equals(key)) {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/DialogManager.java b/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/DialogManager.java
index c4af4bf..8cd3b6e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/DialogManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/preferences/password/DialogManager.java
@@ -9,7 +9,8 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -135,7 +136,7 @@
         if (mDialogFragment != null) mDialogFragment.dismiss();
         // Post the callback to ensure that it is always run asynchronously, even if hide() took a
         // shortcut for a missing shown().
-        if (mCallback != null) ThreadUtils.postOnUiThread(mCallback);
+        if (mCallback != null) PostTask.postTask(UiThreadTaskTraits.DEFAULT, mCallback);
         reset();
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java b/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java
index b4c9385..e5e033130 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/provider/ChromeBrowserProvider.java
@@ -34,12 +34,14 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.IntentHandler;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.database.SQLiteCursor;
 import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1160,7 +1162,7 @@
             UserHandle callingUserHandle = Binder.getCallingUserHandle();
             if (callingUserHandle != null
                     && !callingUserHandle.equals(android.os.Process.myUserHandle())) {
-                ThreadUtils.postOnUiThread(new Runnable() {
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                     @Override
                     public void run() {
                         getContext().getContentResolver().notifyChange(uri, null);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java b/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java
index 9cba473..3aa6620a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/search_engines/TemplateUrlService.java
@@ -10,6 +10,8 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -202,7 +204,7 @@
         // If the load has already been completed, post a load complete to the observer.  Done
         // as an asynchronous call to keep the client code predictable in the loaded/unloaded state.
         if (isLoaded()) {
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     if (!mLoadListeners.hasObserver(listener)) return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GcmUma.java b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GcmUma.java
index fe61a68..ef39d8c0 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GcmUma.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/GcmUma.java
@@ -6,11 +6,12 @@
 
 import android.content.Context;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.content_public.browser.BrowserStartupController;
 import org.chromium.content_public.browser.BrowserStartupController.StartupCallback;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /**
  * Helper Class for GCM UMA Collection.
@@ -59,7 +60,7 @@
     }
 
     private static void onNativeLaunched(final Context context, final Runnable task) {
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 BrowserStartupController.get(LibraryProcessType.PROCESS_BROWSER)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java
index 0ec0a21c..fb0a3ca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/services/gcm/InvalidationGcmUpstreamSender.java
@@ -19,10 +19,12 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.init.ProcessInitializationHandler;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.signin.OAuth2TokenService;
 import org.chromium.components.sync.SyncConstants;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.IOException;
 import java.util.UUID;
@@ -40,7 +42,7 @@
     public void deliverMessage(final String to, final Bundle data) {
         final Bundle dataToSend = createDeepCopy(data);
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 doDeliverMessage(ContextUtils.getApplicationContext(), to, dataToSend);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
index 9a50b94b..3f09eff 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/signin/SigninManager.java
@@ -24,6 +24,7 @@
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
 import org.chromium.chrome.browser.externalauth.UserRecoverableErrorHandler;
 import org.chromium.chrome.browser.sync.SyncUserDataWiper;
@@ -32,6 +33,7 @@
 import org.chromium.components.signin.AccountTrackerService;
 import org.chromium.components.signin.ChromeSigninController;
 import org.chromium.components.sync.AndroidSyncSettings;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -328,7 +330,7 @@
     }
 
     private void notifySignInAllowedChanged() {
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             for (SignInAllowedObserver observer : mSignInAllowedObservers) {
                 observer.onSignInAllowedChanged();
             }
@@ -542,13 +544,13 @@
             mCallbacksWaitingForPendingOperation.add(runnable);
             return;
         }
-        ThreadUtils.postOnUiThread(runnable);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, runnable);
     }
 
     private void notifyCallbacksWaitingForOperation() {
         ThreadUtils.assertOnUiThread();
         for (Runnable callback : mCallbacksWaitingForPendingOperation) {
-            ThreadUtils.postOnUiThread(callback);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, callback);
         }
         mCallbacksWaitingForPendingOperation.clear();
     }
@@ -725,7 +727,7 @@
         assert mSignOutState != null;
 
         if (mSignOutState.mCallback != null) {
-            ThreadUtils.postOnUiThread(mSignOutState.mCallback);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, mSignOutState.mCallback);
         }
         mSignOutState = null;
         notifyCallbacksWaitingForOperation();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
index 43d0327..579b6e8 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/SyncCustomizationFragment.java
@@ -31,8 +31,8 @@
 import org.chromium.base.BuildInfo;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.StrictModeContext;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.autofill.PersonalDataManager;
 import org.chromium.chrome.browser.invalidation.InvalidationController;
@@ -52,6 +52,7 @@
 import org.chromium.components.sync.ModelType;
 import org.chromium.components.sync.PassphraseType;
 import org.chromium.components.sync.ProtocolErrorClientAction;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -187,7 +188,7 @@
             SyncPreferenceUtils.enableSync((boolean) newValue);
             // Must be done asynchronously because the switch state isn't updated
             // until after this function exits.
-            ThreadUtils.postOnUiThread(this::updateSyncStateFromSwitch);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateSyncStateFromSwitch);
             return true;
         });
 
@@ -210,13 +211,13 @@
     @Override
     public boolean onPreferenceChange(Preference preference, Object newValue) {
         if (preference == mSyncEverything) {
-            ThreadUtils.postOnUiThread(this::updateDataTypeState);
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::updateDataTypeState);
             return true;
         }
         if (isSyncTypePreference(preference)) {
             final boolean syncAutofillToggled = preference == mSyncAutofill;
             final boolean preferenceChecked = (boolean) newValue;
-            ThreadUtils.postOnUiThread(() -> {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 if (syncAutofillToggled) {
                     // If the user checks the autofill sync checkbox, then enable and check the
                     // payments integration checkbox.
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
index 7a08e73..e6b4dd74 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/InterceptNavigationDelegateImpl.java
@@ -4,9 +4,9 @@
 
 package org.chromium.chrome.browser.tab;
 
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -18,6 +18,7 @@
 import org.chromium.components.navigation_interception.InterceptNavigationDelegate;
 import org.chromium.components.navigation_interception.NavigationParams;
 import org.chromium.content_public.browser.NavigationController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.ConsoleMessageLevel;
 
@@ -258,7 +259,7 @@
             }
             // Defer closing a tab (and the associated WebContents) till the navigation
             // request and the throttle finishes the job with it.
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     mTab.getTabModelSelector().closeTab(mTab);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
index feab79b..51c44b78 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -34,11 +34,11 @@
 import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
 import org.chromium.base.ObserverList.RewindableIterator;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.UserDataHost;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.AppHooks;
 import org.chromium.chrome.browser.ChromeActionModeCallback;
@@ -100,6 +100,7 @@
 import org.chromium.content_public.browser.ImeEventObserver;
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.SelectionPopupController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsAccessibility;
 import org.chromium.content_public.common.BrowserControlsState;
@@ -2867,7 +2868,7 @@
             mDownloadIPHBubble.addOnDismissListener(new OnDismissListener() {
                 @Override
                 public void onDismiss() {
-                    ThreadUtils.postOnUiThread(new Runnable() {
+                    PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                         @Override
                         public void run() {
                             hideMediaDownloadInProductHelp();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
index 2b39c16..604941d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabPersistentStore.java
@@ -26,6 +26,7 @@
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.UrlConstants;
 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager;
 import org.chromium.chrome.browser.ntp.NewTabPage;
@@ -33,6 +34,7 @@
 import org.chromium.chrome.browser.tab.TabIdManager;
 import org.chromium.chrome.browser.tab.TabState;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
@@ -1244,7 +1246,7 @@
                             TimeUnit.MILLISECONDS);
                 }
 
-                ThreadUtils.postOnUiThread(new Runnable() {
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                     @Override
                     public void run() {
                         // This eventually calls serializeTabModelSelector() which much be called
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
index 434abc7..2af807a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/LocationBarModel.java
@@ -427,7 +427,7 @@
 
     @Override
     public boolean shouldDisplaySearchTerms() {
-        return getDisplaySearchTerms() != null;
+        return getDisplaySearchTerms() != null && !isPreview();
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
index eb98b15..6bbd85d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarButtonInProductHelpController.java
@@ -10,7 +10,7 @@
 import android.view.View;
 
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -28,6 +28,7 @@
 import org.chromium.components.feature_engagement.EventConstants;
 import org.chromium.components.feature_engagement.FeatureConstants;
 import org.chromium.components.feature_engagement.Tracker;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.widget.ViewRectProvider;
 
 /**
@@ -185,7 +186,7 @@
         // Post a request to show the IPH bubble to allow time for a layout pass. Since the bubble
         // is shown on startup, the anchor view may not have a height initially see
         // https://crbug.com/871537.
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             if (activity.isActivityDestroyed()) return;
 
             if (TextUtils.equals(featureName, FeatureConstants.NTP_BUTTON_FEATURE)
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
index 6f19bbe6..394f83d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/ToolbarManager.java
@@ -23,12 +23,12 @@
 
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.Callback;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.CachedMetrics.ActionEvent;
 import org.chromium.base.metrics.CachedMetrics.EnumeratedHistogramSample;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -108,6 +108,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.NavigationEntry;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.ui.AsyncViewProvider;
 import org.chromium.ui.base.DeviceFormFactor;
@@ -1563,7 +1564,7 @@
         // Record startup performance statistics
         long elapsedTime = SystemClock.elapsedRealtime() - activityCreationTimeMs;
         if (elapsedTime < RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS) {
-            ThreadUtils.postOnUiThreadDelayed(() -> {
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
                 onDeferredStartup(activityCreationTimeMs, activityName);
             }, RECORD_UMA_PERFORMANCE_METRICS_DELAY_MS - elapsedTime);
             return;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java
index dab1986..4fa4a3b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java
@@ -42,6 +42,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
 import org.chromium.chrome.browser.WindowDelegate;
@@ -62,6 +63,7 @@
 import org.chromium.chrome.browser.util.ColorUtils;
 import org.chromium.chrome.browser.widget.TintedDrawable;
 import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.common.ContentUrlConstants;
 import org.chromium.net.GURLUtils;
 import org.chromium.ui.base.Clipboard;
@@ -368,7 +370,8 @@
                 && !title.equals(getToolbarDataProvider().getCurrentUrl())
                 && !title.equals(ContentUrlConstants.ABOUT_BLANK_DISPLAY_URL)) {
             // Delay the title animation until security icon animation finishes.
-            ThreadUtils.postOnUiThreadDelayed(mTitleAnimationStarter, TITLE_ANIM_DELAY_MS);
+            PostTask.postDelayedTask(
+                    UiThreadTaskTraits.DEFAULT, mTitleAnimationStarter, TITLE_ANIM_DELAY_MS);
         }
 
         mTitleBar.setText(title);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingController.java b/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingController.java
index 475b6765..3b2b600 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tracing/TracingController.java
@@ -14,11 +14,12 @@
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ObserverList;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.task.AsyncTask;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.preferences.developer.TracingPreferences;
 import org.chromium.content_public.browser.TracingControllerAndroid;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.widget.Toast;
 
 import java.io.File;
@@ -247,7 +248,7 @@
 
             TracingNotificationManager.updateTracingActiveNotification(pair.first);
 
-            ThreadUtils.postOnUiThreadDelayed(
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
                     () -> { updateBufferUsage(); }, UPDATE_BUFFER_USAGE_INTERVAL_MILLIS);
         });
     }
@@ -303,7 +304,7 @@
         // Delete the file after an hour. This won't work if the app quits in the meantime, so we
         // also check for old files when TraceController is created.
         File tracingTempFile = mTracingTempFile;
-        ThreadUtils.postOnUiThreadDelayed(() -> {
+        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
             new DeleteTempFileTask(tracingTempFile)
                     .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
         }, DELETE_AFTER_SHARE_TIMEOUT_MILLIS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
index c026a13..b34df98 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrDelegateFallback.java
@@ -14,12 +14,13 @@
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.ContextUtils;
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.compat.ApiHelperForN;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.metrics.CachedMetrics;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.widget.Toast;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -157,7 +158,7 @@
             onVrModuleInstallFinished(success);
         });
 
-        ThreadUtils.postOnUiThreadDelayed(() -> {
+        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
             if (enterVrHandled.getAndSet(true)) return;
             assert !VrModuleProvider.isModuleInstalled();
             onVrModuleInstallFailure(activity);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrFallbackUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrFallbackUtils.java
index 4ef5bad9..18dee2e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrFallbackUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrFallbackUtils.java
@@ -8,13 +8,14 @@
 import android.content.Context;
 import android.support.v4.app.NotificationCompat;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.notifications.NotificationBuilderFactory;
 import org.chromium.chrome.browser.notifications.NotificationConstants;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxy;
 import org.chromium.chrome.browser.notifications.NotificationManagerProxyImpl;
 import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /** Class providing utils for when the VR module is not installed. */
 /* package */ class VrFallbackUtils {
@@ -23,7 +24,7 @@
 
     /** Shows immersive notification informing the user that the VR browser is not ready yet. */
     public static void showFailureNotification(Context context) {
-        ThreadUtils.postOnUiThreadDelayed(() -> {
+        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
             NotificationManagerProxy notificationManager =
                     new NotificationManagerProxyImpl(context);
             Notification notification =
@@ -44,7 +45,7 @@
 
             // Close notification after a few seconds as it is only really relevant right after
             // accessing the VR browser failed.
-            ThreadUtils.postOnUiThreadDelayed(() -> {
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT, () -> {
                 notificationManager.cancel(NotificationConstants.NOTIFICATION_ID_PREPARING_VR);
             }, PREPARING_VR_NOTIFICATION_TIMEOUT_MS);
         }, PREPARING_VR_NOTIFICATION_DELAY_MS);
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
index 251d1c7..7dc0a49 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/vr/VrShell.java
@@ -32,6 +32,7 @@
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ChromeActivity;
 import org.chromium.chrome.browser.ChromeTabbedActivity;
@@ -54,6 +55,7 @@
 import org.chromium.chrome.browser.vr.keyboard.VrInputMethodManagerWrapper;
 import org.chromium.content_public.browser.ImeAdapter;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.ViewEventSink;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.common.BrowserControlsState;
@@ -569,7 +571,7 @@
                     @Override
                     public void onRequestPermissionsResult(
                             String[] permissions, int[] grantResults) {
-                        ThreadUtils.postOnUiThread(new Runnable() {
+                        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                             @Override
                             public void run() {
                                 VrShellDelegate.enterVrIfNecessary();
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
index 776594a..00b5a48 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappActivity.java
@@ -25,11 +25,11 @@
 import org.chromium.base.ApiCompatibilityUtils;
 import org.chromium.base.ApplicationStatus;
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.TraceEvent;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.base.task.PostTask;
 import org.chromium.blink_public.platform.WebDisplayMode;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.IntentHandler;
@@ -57,6 +57,7 @@
 import org.chromium.content_public.browser.LoadUrlParams;
 import org.chromium.content_public.browser.NavigationController;
 import org.chromium.content_public.browser.ScreenOrientationProvider;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
 import org.chromium.ui.base.PageTransition;
 
@@ -202,13 +203,14 @@
                         WebappActivity.this, getControlContainerLayoutId(), getToolbarLayoutId());
                 if (WebappActivity.this.isActivityFinishing()) return;
                 if (mainView != null) {
-                    ThreadUtils.postOnUiThread(() -> {
+                    PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                         if (WebappActivity.this.isActivityFinishing()) return;
                         onLayoutInflated(mainView);
                     });
                 } else {
                     if (WebappActivity.this.isActivityFinishing()) return;
-                    ThreadUtils.postOnUiThread(() -> WebappActivity.super.doLayoutInflation());
+                    PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                            () -> WebappActivity.super.doLayoutInflation());
                 }
             }
         }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/ThumbnailProviderImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/ThumbnailProviderImpl.java
index 93196a79..dc101dd 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/ThumbnailProviderImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/ThumbnailProviderImpl.java
@@ -12,8 +12,10 @@
 
 import org.chromium.base.DiscardableReferencePool;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.BitmapCache;
 import org.chromium.chrome.browser.util.ConversionUtils;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
@@ -133,7 +135,7 @@
     }
 
     private void processQueue() {
-        ThreadUtils.postOnUiThread(this::processNextRequest);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, this::processNextRequest);
     }
 
     private String getKey(String contentId, int bitmapSizePx) {
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java
index 3997bee8..f8f37cf 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ServicificationBackgroundService.java
@@ -11,10 +11,12 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.init.BrowserParts;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.init.EmptyBrowserParts;
 import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 
@@ -73,7 +75,7 @@
         // the BrowserStartupControllerImpl#browserStartupComplete() is called on the UI thread when
         // the full browser starts. So we can use it to checks whether the
         // {@link mFullBrowserStartupDone} has been set to true.
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 Assert.assertFalse("The full browser is started instead of ServiceManager only.",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java
index ba79d0fd..7b05035 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/browserservices/OriginVerifierTest.java
@@ -16,6 +16,7 @@
 
 import org.chromium.base.ContextUtils;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.chrome.browser.ChromeActivity;
@@ -28,6 +29,7 @@
 import org.chromium.chrome.browser.preferences.privacy.BrowsingDataBridge;
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -107,13 +109,15 @@
     @SmallTest
     public void testOnlyHttpsAllowed() throws InterruptedException {
         Origin origin = new Origin(Uri.parse("LOL"));
-        ThreadUtils.postOnUiThread(() ->
-                mHandleAllUrlsVerifier.start(new TestOriginVerificationListener(), origin));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                () -> mHandleAllUrlsVerifier.start(new TestOriginVerificationListener(), origin));
         Assert.assertTrue(
                 mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertFalse(mLastVerified);
-        ThreadUtils.postOnUiThread(() ->
-                mHandleAllUrlsVerifier.start(new TestOriginVerificationListener(), mHttpOrigin));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                ()
+                        -> mHandleAllUrlsVerifier.start(
+                                new TestOriginVerificationListener(), mHttpOrigin));
         Assert.assertTrue(
                 mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertFalse(mLastVerified);
@@ -122,11 +126,14 @@
     @Test
     @SmallTest
     public void testMultipleRelationships() throws Exception {
-        ThreadUtils.postOnUiThread(() ->
-                OriginVerifier.addVerificationOverride(
-                        PACKAGE_NAME, mHttpsOrigin, CustomTabsService.RELATION_USE_AS_ORIGIN));
-        ThreadUtils.postOnUiThread(() ->
-                mUseAsOriginVerifier.start(new TestOriginVerificationListener(), mHttpsOrigin));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                ()
+                        -> OriginVerifier.addVerificationOverride(PACKAGE_NAME, mHttpsOrigin,
+                                CustomTabsService.RELATION_USE_AS_ORIGIN));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT,
+                ()
+                        -> mUseAsOriginVerifier.start(
+                                new TestOriginVerificationListener(), mHttpsOrigin));
         Assert.assertTrue(
                 mVerificationResultSemaphore.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
         Assert.assertTrue(mLastVerified);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
index 20b50ef..8a20858 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityIncognitoTest.java
@@ -98,7 +98,9 @@
     public void incognitoNotificationClosesCustomTab() throws Exception {
         launchIncognitoCustomTab();
 
-        IncognitoNotificationService.getRemoveAllIncognitoTabsIntent(mActivity).send();
+        IncognitoNotificationService.getRemoveAllIncognitoTabsIntent(mActivity)
+                .getPendingIntent()
+                .send();
 
         CriteriaHelper.pollUiThread(mActivity::isFinishing);
     }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
index ed52e482..6b60827 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/CustomTabActivityTest.java
@@ -68,6 +68,7 @@
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
@@ -114,6 +115,7 @@
 import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil;
 import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
 import org.chromium.content_public.browser.LoadUrlParams;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContentsObserver;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
@@ -1511,7 +1513,7 @@
                 renderProcessCallback.notifyCalled();
             }
         };
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             WebContentsUtils.simulateRendererKilled(
                     mCustomTabActivityTestRule.getActivity().getActivityTab().getWebContents(),
                     false);
@@ -2536,7 +2538,7 @@
             }
         };
         tabToBeReparented.addObserver(observer);
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             getActivity().openCurrentUrlInBrowser(true);
             Assert.assertNull(getActivity().getActivityTab());
         });
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
index c7e93a9..eb07b24 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/customtabs/TrustedCdnPublisherUrlTest.java
@@ -34,6 +34,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.library_loader.LibraryLoader;
 import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.AnnotationRule;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
@@ -55,6 +56,7 @@
 import org.chromium.chrome.test.util.browser.Features;
 import org.chromium.components.offlinepages.SavePageResult;
 import org.chromium.components.url_formatter.UrlFormatter;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
 import org.chromium.content_public.browser.test.util.TestTouchUtils;
@@ -278,7 +280,7 @@
                         ChromeTabbedActivity.class.getName(), /* result = */ null, false);
         CustomTabActivity customTabActivity = mCustomTabActivityTestRule.getActivity();
         final Tab tab = customTabActivity.getActivityTab();
-        ThreadUtils.postOnUiThread(() -> {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
             Assert.assertEquals(publisherUrl, tab.getTrustedCdnPublisherUrl());
             customTabActivity.openCurrentUrlInBrowser(true);
             Assert.assertNull(customTabActivity.getActivityTab());
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java
index 866f4e4..757dc9d 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feature_engagement/ScreenshotMonitorTest.java
@@ -15,8 +15,10 @@
 
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.Feature;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
@@ -132,7 +134,7 @@
     private void startMonitoringOnUiThreadBlocking() {
         final Semaphore semaphore = new Semaphore(0);
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mTestScreenshotMonitor.startMonitoring();
@@ -150,7 +152,7 @@
     private void stopMonitoringOnUiThreadBlocking() {
         final Semaphore semaphore = new Semaphore(0);
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mTestScreenshotMonitor.stopMonitoring();
@@ -169,7 +171,7 @@
     private void assertScreenshotShowUiCountOnUiThreadBlocking(int expectedCount) {
         final Semaphore semaphore = new Semaphore(0);
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 semaphore.release();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/ConsumerSyncWrapper.java b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/ConsumerSyncWrapper.java
index a0e047e..1ff5769 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/feed/ConsumerSyncWrapper.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/feed/ConsumerSyncWrapper.java
@@ -8,7 +8,8 @@
 
 import org.junit.Assert;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -52,7 +53,7 @@
     static public <T> void waitForConsumer(
             Consumer<T> consumer, Consumer<Consumer<T>> operation, long timeoutMs) {
         ConsumerSyncWrapper<T> wrapper = new ConsumerSyncWrapper<>(consumer);
-        ThreadUtils.postOnUiThread(() -> operation.accept(wrapper));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> operation.accept(wrapper));
         wrapper.blockAndWrappedAccept(timeoutMs);
     }
 }
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java
index d8ad1bd..928be153 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/ToastHWATest.java
@@ -21,6 +21,7 @@
 
 import org.chromium.base.BaseSwitches;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.DisabledTest;
@@ -35,6 +36,7 @@
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.browser.contextmenu.ContextMenuUtils;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.test.EmbeddedTestServer;
 import org.chromium.ui.widget.Toast;
 
@@ -170,7 +172,7 @@
         final AtomicBoolean accelerated = new AtomicBoolean();
         final CallbackHelper listenerCalled = new CallbackHelper();
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 // We are using Toast.makeText(context, ...) instead of new Toast(context)
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java
index 09263697..7daa33b 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/hardware_acceleration/Utils.java
@@ -12,8 +12,10 @@
 
 import org.chromium.base.SysUtils;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CallbackHelper;
 import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -82,7 +84,7 @@
         final AtomicBoolean accelerated = new AtomicBoolean();
         final CallbackHelper listenerCalled = new CallbackHelper();
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 final Dialog dialog = new Dialog(activity);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
index 3802b288..6035185 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/incognito/IncognitoNotificationServiceTest.java
@@ -57,8 +57,10 @@
     }
 
     private void sendClearIncognitoIntent() throws CanceledException {
-        PendingIntent clearIntent = IncognitoNotificationService.getRemoveAllIncognitoTabsIntent(
-                InstrumentationRegistry.getTargetContext());
+        PendingIntent clearIntent =
+                IncognitoNotificationService
+                        .getRemoveAllIncognitoTabsIntent(InstrumentationRegistry.getTargetContext())
+                        .getPendingIntent();
         clearIntent.send();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java
index 12b9ce0..a4e6564 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/init/ChainedTasksTest.java
@@ -11,7 +11,9 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -158,7 +160,7 @@
 
         tasks.start(false);
         Assert.assertTrue(secondTaskFinished.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        ThreadUtils.postOnUiThread(new TestRunnable(messages, "High Priority"));
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new TestRunnable(messages, "High Priority"));
         waitForHighPriorityTask.release();
 
         Assert.assertTrue(finished.tryAcquire(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/router/MockMediaRouteProvider.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/router/MockMediaRouteProvider.java
index de97c09..ff4be40 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/router/MockMediaRouteProvider.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/router/MockMediaRouteProvider.java
@@ -7,7 +7,8 @@
 import android.support.annotation.Nullable;
 
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -88,12 +89,10 @@
         final ArrayList<MediaSink> sinks = new ArrayList<MediaSink>();
         sinks.add(new MediaSink(SINK_ID1, SINK_NAME1, null));
         sinks.add(new MediaSink(SINK_ID2, SINK_NAME2, null));
-        ThreadUtils.postOnUiThreadDelayed(new Runnable() {
-                @Override
-                public void run() {
-                    mManager.onSinksReceived(sourceId, MockMediaRouteProvider.this, sinks);
-                }
-            }, mSinksObservedDelayMillis);
+        PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
+                ()
+                        -> mManager.onSinksReceived(sourceId, MockMediaRouteProvider.this, sinks),
+                mSinksObservedDelayMillis);
     }
 
     @Override
@@ -112,13 +111,11 @@
         if (mCreateRouteDelayMillis == 0) {
             doCreateRoute(sourceId, sinkId, presentationId, origin, tabId, nativeRequestId);
         } else {
-            ThreadUtils.postOnUiThreadDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        doCreateRoute(
-                                sourceId, sinkId, presentationId, origin, tabId, nativeRequestId);
-                    }
-                }, mCreateRouteDelayMillis);
+            PostTask.postDelayedTask(UiThreadTaskTraits.DEFAULT,
+                    ()
+                            -> doCreateRoute(sourceId, sinkId, presentationId, origin, tabId,
+                                    nativeRequestId),
+                    mCreateRouteDelayMillis);
         }
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NtpUiCaptureTestData.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NtpUiCaptureTestData.java
index b72678c1..b6edce5 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NtpUiCaptureTestData.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NtpUiCaptureTestData.java
@@ -12,7 +12,7 @@
 
 import android.graphics.Bitmap;
 
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.chrome.browser.favicon.IconType;
 import org.chromium.chrome.browser.favicon.LargeIconBridge;
 import org.chromium.chrome.browser.ntp.snippets.KnownCategories;
@@ -26,6 +26,7 @@
 import org.chromium.chrome.test.util.browser.suggestions.FakeMostVisitedSites;
 import org.chromium.chrome.test.util.browser.suggestions.FakeSuggestionsSource;
 import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.Arrays;
 import java.util.Calendar;
@@ -193,7 +194,7 @@
             @Override
             public boolean getLargeIconForUrl(
                     String url, int desiredSizePx, LargeIconCallback callback) {
-                ThreadUtils.postOnUiThread(() -> {
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                     int fallbackColor =
                             colorMap.containsKey(url) ? colorMap.get(url) : DEFAULT_ICON_COLOR;
                     callback.onLargeIconAvailable(
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
index 4bf6f924..153d074 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/snippets/ArticleSnippetsTest.java
@@ -29,6 +29,7 @@
 import org.chromium.base.Callback;
 import org.chromium.base.DiscardableReferencePool;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.UrlUtils;
@@ -66,6 +67,7 @@
 import org.chromium.chrome.test.util.browser.suggestions.DummySuggestionsEventReporter;
 import org.chromium.chrome.test.util.browser.suggestions.FakeSuggestionsSource;
 import org.chromium.chrome.test.util.browser.suggestions.SuggestionsDependenciesRule;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
 
 import java.io.IOException;
@@ -492,7 +494,7 @@
         public void makeFaviconRequest(
                 SnippetArticle suggestion, final Callback<Bitmap> faviconCallback) {
             // Run the callback asynchronously in case the caller made that assumption.
-            ThreadUtils.postOnUiThread(() -> {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 // Return an arbitrary drawable.
                 faviconCallback.onResult(getBitmap(R.drawable.star_green));
             });
@@ -502,7 +504,7 @@
         public void makeLargeIconRequest(final String url, final int largeIconSizePx,
                 final LargeIconBridge.LargeIconCallback callback) {
             // Run the callback asynchronously in case the caller made that assumption.
-            ThreadUtils.postOnUiThread(() -> {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
                 // Return an arbitrary drawable.
                 callback.onLargeIconAvailable(
                         getBitmap(R.drawable.star_green), largeIconSizePx, true, IconType.INVALID);
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
index 1b67a0b7..ed5a7f4 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/toolbar/top/BrandColorTest.java
@@ -18,6 +18,7 @@
 import org.chromium.base.ObserverList.RewindableIterator;
 import org.chromium.base.SysUtils;
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.CommandLineFlags;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.Restriction;
@@ -34,6 +35,7 @@
 import org.chromium.chrome.test.ChromeActivityTestRule;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 import org.chromium.chrome.test.util.DisableInTabbedMode;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.test.InterstitialPageDelegateAndroid;
 import org.chromium.content_public.browser.test.util.Criteria;
 import org.chromium.content_public.browser.test.util.CriteriaHelper;
@@ -171,7 +173,7 @@
     @Feature({"Omnibox"})
     public void testBrandColorWithLoadStarted() throws InterruptedException {
         startMainActivityWithURL(getUrlWithBrandColor(BRAND_COLOR_1));
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 Tab tab = mActivityTestRule.getActivity().getActivityTab();
diff --git a/chrome/browser/android/webapk/webapk_post_share_target_navigator.cc b/chrome/browser/android/webapk/webapk_post_share_target_navigator.cc
index eb51ca1..33ee5cf8 100644
--- a/chrome/browser/android/webapk/webapk_post_share_target_navigator.cc
+++ b/chrome/browser/android/webapk/webapk_post_share_target_navigator.cc
@@ -4,12 +4,13 @@
 
 #include "chrome/browser/android/webapk/webapk_post_share_target_navigator.h"
 
-#include <sstream>
-
 #include <jni.h>
 
+#include <sstream>
+
 #include "base/android/jni_array.h"
 #include "base/android/jni_string.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversion_utils.h"
 #include "content/public/browser/web_contents.h"
 #include "jni/WebApkPostShareTargetNavigator_jni.h"
diff --git a/chrome/browser/chrome_browser_main_browsertest.cc b/chrome/browser/chrome_browser_main_browsertest.cc
index d72118d..75ad33a 100644
--- a/chrome/browser/chrome_browser_main_browsertest.cc
+++ b/chrome/browser/chrome_browser_main_browsertest.cc
@@ -36,31 +36,6 @@
 
 namespace {
 
-// ChromeBrowserMainExtraParts is used to initialize the network state.
-class ChromeBrowserMainExtraPartsNetFactoryInstaller
-    : public ChromeBrowserMainExtraParts {
- public:
-  explicit ChromeBrowserMainExtraPartsNetFactoryInstaller(
-      content::NetworkConnectionChangeSimulator* network_change_simulator)
-      : network_change_simulator_(network_change_simulator) {
-    EXPECT_TRUE(network_change_simulator_);
-  }
-
-  // ChromeBrowserMainExtraParts:
-  void PreEarlyInitialization() override {}
-  void ServiceManagerConnectionStarted(
-      content::ServiceManagerConnection* connection) override {
-    network_change_simulator_->SetConnectionType(
-        network::mojom::ConnectionType::CONNECTION_NONE);
-  }
-
- private:
-  content::NetworkConnectionChangeSimulator* network_change_simulator_ =
-      nullptr;
-
-  DISALLOW_COPY_AND_ASSIGN(ChromeBrowserMainExtraPartsNetFactoryInstaller);
-};
-
 class ChromeBrowserMainBrowserTest : public InProcessBrowserTest {
  public:
   ChromeBrowserMainBrowserTest() {
@@ -87,17 +62,8 @@
         static_cast<ChromeBrowserMainParts*>(browser_main_parts);
     ChromeBrowserMainPartsTestApi(chrome_browser_main_parts)
         .EnableVariationsServiceInit();
-    network_change_simulator_ =
-        std::make_unique<content::NetworkConnectionChangeSimulator>();
-    extra_parts_ = new ChromeBrowserMainExtraPartsNetFactoryInstaller(
-        network_change_simulator_.get());
-    chrome_browser_main_parts->AddParts(extra_parts_);
   }
 
-  std::unique_ptr<content::NetworkConnectionChangeSimulator>
-      network_change_simulator_;
-  ChromeBrowserMainExtraPartsNetFactoryInstaller* extra_parts_ = nullptr;
-
  private:
   base::test::ScopedFeatureList scoped_feature_list_;
 
@@ -110,16 +76,28 @@
 // instead of performing an actual request.
 IN_PROC_BROWSER_TEST_F(ChromeBrowserMainBrowserTest,
                        VariationsServiceStartsRequestOnNetworkChange) {
-  const int initial_request_count =
-      g_browser_process->variations_service()->request_count();
-  ASSERT_TRUE(extra_parts_);
-  network_change_simulator_->SetConnectionType(
+  variations::VariationsService* variations_service =
+      g_browser_process->variations_service();
+  variations_service->CancelCurrentRequestForTesting();
+
+  content::NetworkConnectionChangeSimulator network_change_simulator;
+  network_change_simulator.SetConnectionType(
+      network::mojom::ConnectionType::CONNECTION_NONE);
+  const int initial_request_count = variations_service->request_count();
+
+  // The variations service will only send a request the first time the
+  // connection goes online, or after the 30min delay. Tell it that it hasn't
+  // sent a request yet to make sure the next time we go online a request will
+  // be sent.
+  variations_service->GetResourceRequestAllowedNotifierForTesting()
+      ->SetObserverRequestedForTesting(true);
+
+  network_change_simulator.SetConnectionType(
       network::mojom::ConnectionType::CONNECTION_WIFI);
   // NotifyObserversOfNetworkChangeForTests uses PostTask, so run the loop until
   // idle to ensure VariationsService processes the network change.
   base::RunLoop().RunUntilIdle();
-  const int final_request_count =
-      g_browser_process->variations_service()->request_count();
+  const int final_request_count = variations_service->request_count();
   EXPECT_EQ(initial_request_count + 1, final_request_count);
 }
 
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc
index 9f87ae1a..f109b8a8 100644
--- a/chrome/browser/chrome_content_browser_client.cc
+++ b/chrome/browser/chrome_content_browser_client.cc
@@ -494,6 +494,8 @@
 #include "chrome/browser/extensions/bookmark_app_navigation_throttle.h"
 #include "chrome/browser/extensions/chrome_content_browser_client_extensions_part.h"
 #include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
+#include "chrome/browser/extensions/convert_web_app.h"
+#include "chrome/browser/extensions/extension_util.h"
 #include "chrome/browser/extensions/user_script_listener.h"
 #include "chrome/browser/media/cast_transport_host_filter.h"
 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
@@ -3152,13 +3154,38 @@
       web_prefs->embedded_media_experience_enabled =
           tab_android->ShouldEnableEmbeddedMediaExperience();
 
-      web_prefs->web_app_scope = tab_android->GetWebappManifestScope();
-
       web_prefs->picture_in_picture_enabled =
           tab_android->IsPictureInPictureEnabled();
     }
 #endif  // defined(OS_ANDROID)
 
+    // web_app_scope value is platform specific.
+#if defined(OS_ANDROID)
+    if (tab_android)
+      web_prefs->web_app_scope = tab_android->GetWebappManifestScope();
+#elif BUILDFLAG(ENABLE_EXTENSIONS)
+    {
+      const extensions::Extension* extension = nullptr;
+      Browser* browser = chrome::FindBrowserWithWebContents(contents);
+      if (base::FeatureList::IsEnabled(features::kDesktopPWAWindowing) &&
+          browser && browser->hosted_app_controller() &&
+          browser->hosted_app_controller()->created_for_installed_pwa()) {
+        // When a new window is created to host a PWA, this method will return
+        // the scope of the given PWA. It will stay the same for the PWA as
+        // scopes never change after the window was created. It is not
+        // guaranteed that this method will be called on every navigation but
+        // this is not required for things to work, we only need it to be called
+        // at the window creation time.
+        extension = extensions::util::GetInstalledPwaForUrl(
+            contents->GetBrowserContext(), contents->GetLastCommittedURL());
+      }
+
+      web_prefs->web_app_scope =
+          extension ? extensions::GetScopeURLFromBookmarkApp(extension)
+                    : GURL();
+    }
+#endif
+
 #if BUILDFLAG(ENABLE_EXTENSIONS)
     Browser* browser = chrome::FindBrowserWithWebContents(contents);
     if (browser && browser->hosted_app_controller() &&
diff --git a/chrome/browser/chromeos/accessibility/chromevox_panel.cc b/chrome/browser/chromeos/accessibility/chromevox_panel.cc
index deadc8f..b2474a0 100644
--- a/chrome/browser/chromeos/accessibility/chromevox_panel.cc
+++ b/chrome/browser/chromeos/accessibility/chromevox_panel.cc
@@ -7,6 +7,7 @@
 #include "ash/public/interfaces/accessibility_controller.mojom.h"
 #include "ash/public/interfaces/constants.mojom.h"
 #include "chrome/browser/chromeos/accessibility/accessibility_manager.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/service_manager_connection.h"
 #include "extensions/common/constants.h"
 #include "services/service_manager/public/cpp/connector.h"
diff --git a/chrome/browser/chromeos/android_sms/fcm_connection_establisher.cc b/chrome/browser/chromeos/android_sms/fcm_connection_establisher.cc
index ba560bef..5d68e47 100644
--- a/chrome/browser/chromeos/android_sms/fcm_connection_establisher.cc
+++ b/chrome/browser/chromeos/android_sms/fcm_connection_establisher.cc
@@ -6,6 +6,7 @@
 
 #include <utility>
 
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/task/post_task.h"
 #include "chromeos/components/multidevice/logging/logging.h"
@@ -41,10 +42,10 @@
 FcmConnectionEstablisher::PendingServiceWorkerMessage::
     PendingServiceWorkerMessage(
         GURL service_worker_scope,
-        std::string message_content,
+        MessageType message_type,
         content::ServiceWorkerContext* service_worker_context)
     : service_worker_scope(service_worker_scope),
-      message_content(message_content),
+      message_type(message_type),
       service_worker_context(service_worker_context) {}
 
 FcmConnectionEstablisher::InFlightMessage::InFlightMessage(
@@ -65,7 +66,7 @@
       base::BindOnce(
           &FcmConnectionEstablisher::SendMessageToServiceWorkerWithRetries,
           weak_ptr_factory_.GetWeakPtr(), url,
-          GetMessageForConnectionMode(connection_mode),
+          GetMessageTypeForConnectionMode(connection_mode),
           service_worker_context));
 }
 
@@ -76,18 +77,33 @@
       FROM_HERE, {content::BrowserThread::IO},
       base::BindOnce(
           &FcmConnectionEstablisher::SendMessageToServiceWorkerWithRetries,
-          weak_ptr_factory_.GetWeakPtr(), url, kStopFcmMessage,
+          weak_ptr_factory_.GetWeakPtr(), url, MessageType::kStop,
           service_worker_context));
 }
 
 // static
-std::string FcmConnectionEstablisher::GetMessageForConnectionMode(
+FcmConnectionEstablisher::MessageType
+FcmConnectionEstablisher::GetMessageTypeForConnectionMode(
     ConnectionMode connection_mode) {
   switch (connection_mode) {
     case ConnectionMode::kStartConnection:
-      return kStartFcmMessage;
+      return MessageType::kStart;
     case ConnectionMode::kResumeExistingConnection:
+      return MessageType::kResume;
+  }
+  NOTREACHED();
+}
+
+// static
+std::string FcmConnectionEstablisher::GetMessageStringForType(
+    MessageType message_type) {
+  switch (message_type) {
+    case MessageType::kStart:
+      return kStartFcmMessage;
+    case MessageType::kResume:
       return kResumeFcmMessage;
+    case MessageType::kStop:
+      return kStopFcmMessage;
   }
   NOTREACHED();
   return "";
@@ -95,11 +111,11 @@
 
 void FcmConnectionEstablisher::SendMessageToServiceWorkerWithRetries(
     const GURL& url,
-    std::string message_string,
+    MessageType message_type,
     content::ServiceWorkerContext* service_worker_context) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
 
-  message_queue_.emplace(url, message_string, service_worker_context);
+  message_queue_.emplace(url, message_type, service_worker_context);
   ProcessMessageQueue();
 }
 
@@ -119,11 +135,11 @@
 void FcmConnectionEstablisher::SendInFlightMessage() {
   const PendingServiceWorkerMessage& message = in_flight_message_->message;
   blink::TransferableMessage msg;
-  msg.owned_encoded_message =
-      blink::EncodeStringMessage(base::UTF8ToUTF16(message.message_content));
+  msg.owned_encoded_message = blink::EncodeStringMessage(
+      base::UTF8ToUTF16(GetMessageStringForType(message.message_type)));
   msg.encoded_message = msg.owned_encoded_message;
 
-  PA_LOG(VERBOSE) << "Dispatching message " << message.message_content;
+  PA_LOG(VERBOSE) << "Dispatching message " << message.message_type;
   message.service_worker_context->StartServiceWorkerAndDispatchMessage(
       message.service_worker_scope, std::move(msg),
       base::BindOnce(&FcmConnectionEstablisher::OnMessageDispatchResult,
@@ -133,12 +149,16 @@
 void FcmConnectionEstablisher::OnMessageDispatchResult(bool status) {
   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
   DCHECK(in_flight_message_);
-  PA_LOG(VERBOSE) << "Service worker message returned status: " << status;
+  PA_LOG(VERBOSE) << "Service worker message returned status " << status
+                  << " for message "
+                  << in_flight_message_->message.message_type;
 
   if (!status && in_flight_message_->retry_count < kMaxRetryCount) {
     base::TimeDelta retry_delay =
         kRetryDelay * (1 << in_flight_message_->retry_count);
     in_flight_message_->retry_count++;
+    UMA_HISTOGRAM_ENUMERATION("AndroidSms.FcmMessageDispatchRetry",
+                              in_flight_message_->message.message_type);
     PA_LOG(VERBOSE) << "Scheduling retry with delay " << retry_delay;
     retry_timer_->Start(
         FROM_HERE, retry_delay,
@@ -147,15 +167,37 @@
     return;
   }
 
-  if (in_flight_message_->retry_count >= kMaxRetryCount) {
+  if (status) {
+    UMA_HISTOGRAM_ENUMERATION("AndroidSms.FcmMessageDispatchSuccess",
+                              in_flight_message_->message.message_type);
+  } else {
+    UMA_HISTOGRAM_ENUMERATION("AndroidSms.FcmMessageDispatchFailure",
+                              in_flight_message_->message.message_type);
     PA_LOG(WARNING) << "Max retries attempted when dispatching message "
-                    << in_flight_message_->message.message_content;
+                    << in_flight_message_->message.message_type;
   }
 
   in_flight_message_.reset();
   ProcessMessageQueue();
 }
 
+std::ostream& operator<<(
+    std::ostream& stream,
+    const FcmConnectionEstablisher::MessageType& message_type) {
+  switch (message_type) {
+    case FcmConnectionEstablisher::MessageType::kStart:
+      stream << "MessageType::kStart";
+      break;
+    case FcmConnectionEstablisher::MessageType::kResume:
+      stream << "MessageType::kResume";
+      break;
+    case FcmConnectionEstablisher::MessageType::kStop:
+      stream << "MessageType::kStop";
+      break;
+  }
+  return stream;
+}
+
 }  // namespace android_sms
 
 }  // namespace chromeos
diff --git a/chrome/browser/chromeos/android_sms/fcm_connection_establisher.h b/chrome/browser/chromeos/android_sms/fcm_connection_establisher.h
index 70905ee..926623a 100644
--- a/chrome/browser/chromeos/android_sms/fcm_connection_establisher.h
+++ b/chrome/browser/chromeos/android_sms/fcm_connection_establisher.h
@@ -36,13 +36,24 @@
       content::ServiceWorkerContext* service_worker_context) override;
 
  private:
+  // This enum is used for logging metrics. It should be kept in sync with
+  // AndroidSmsFcmMessageType in enums.xml
+  enum class MessageType {
+    kStart = 0,
+    kResume = 1,
+    kStop = 2,
+    kMaxValue = kStop,
+  };
+  friend std::ostream& operator<<(std::ostream& stream,
+                                  const MessageType& message_type);
+
   struct PendingServiceWorkerMessage {
     PendingServiceWorkerMessage(
         GURL service_worker_scope,
-        std::string message_content,
+        MessageType message_type,
         content::ServiceWorkerContext* service_worker_context);
     GURL service_worker_scope;
-    std::string message_content;
+    MessageType message_type;
     content::ServiceWorkerContext* service_worker_context;
   };
 
@@ -57,12 +68,14 @@
   FRIEND_TEST_ALL_PREFIXES(FcmConnectionEstablisherTest,
                            TestTearDownConnection);
 
-  static std::string GetMessageForConnectionMode(
+  static MessageType GetMessageTypeForConnectionMode(
       ConnectionMode connection_mode);
 
+  static std::string GetMessageStringForType(MessageType message_type);
+
   void SendMessageToServiceWorkerWithRetries(
       const GURL& url,
-      std::string message_string,
+      MessageType message_type,
       content::ServiceWorkerContext* service_worker_context);
 
   void ProcessMessageQueue();
diff --git a/chrome/browser/chromeos/android_sms/fcm_connection_establisher_unittest.cc b/chrome/browser/chromeos/android_sms/fcm_connection_establisher_unittest.cc
index bb382db..12c95b7 100644
--- a/chrome/browser/chromeos/android_sms/fcm_connection_establisher_unittest.cc
+++ b/chrome/browser/chromeos/android_sms/fcm_connection_establisher_unittest.cc
@@ -8,6 +8,7 @@
 
 #include "base/run_loop.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
 #include "base/timer/mock_timer.h"
 #include "chrome/browser/chromeos/android_sms/android_sms_urls.h"
 #include "content/public/test/fake_service_worker_context.h"
@@ -44,6 +45,7 @@
 TEST_F(FcmConnectionEstablisherTest, TestEstablishConnection) {
   auto mock_retry_timer = std::make_unique<base::MockOneShotTimer>();
   base::MockOneShotTimer* mock_retry_timer_ptr = mock_retry_timer.get();
+  base::HistogramTester histogram_tester;
 
   content::FakeServiceWorkerContext fake_service_worker_context;
   FcmConnectionEstablisher fcm_connection_establisher(
@@ -64,11 +66,15 @@
                              message_dispatch_calls[0]);
 
   // Return success to result callback and verify that no retries are attempted
+  // and success histogram is recorded.
   std::move(std::get<content::ServiceWorkerContext::ResultCallback>(
                 message_dispatch_calls[0]))
       .Run(true /* status */);
   ASSERT_EQ(1u, message_dispatch_calls.size());
   EXPECT_FALSE(mock_retry_timer_ptr->IsRunning());
+  histogram_tester.ExpectBucketCount(
+      "AndroidSms.FcmMessageDispatchSuccess",
+      FcmConnectionEstablisher::MessageType::kStart, 1);
 
   // Verify that when multiple requests are sent only the first one is
   // dispatched while the others are queued.
@@ -91,6 +97,10 @@
       .Run(false /* status */);
   ASSERT_EQ(2u, message_dispatch_calls.size());
   EXPECT_TRUE(mock_retry_timer_ptr->IsRunning());
+  // Retry shouldn't count success.
+  histogram_tester.ExpectBucketCount(
+      "AndroidSms.FcmMessageDispatchSuccess",
+      FcmConnectionEstablisher::MessageType::kStart, 1);
   mock_retry_timer_ptr->Fire();
   ASSERT_EQ(3u, message_dispatch_calls.size());
   VerifyTransferrableMessage(FcmConnectionEstablisher::kStartFcmMessage,
@@ -119,6 +129,11 @@
       &fake_service_worker_context);
   base::RunLoop().RunUntilIdle();
 
+  int last_retry_bucket_count = histogram_tester.GetBucketCount(
+      "AndroidSms.FcmMessageDispatchRetry",
+      static_cast<base::HistogramBase::Sample>(
+          FcmConnectionEstablisher::MessageType::kStart));
+
   int retry_count = 0;
   while (true) {
     ASSERT_EQ(5u + retry_count, message_dispatch_calls.size());
@@ -134,6 +149,13 @@
   }
 
   EXPECT_EQ(FcmConnectionEstablisher::kMaxRetryCount, retry_count);
+  histogram_tester.ExpectBucketCount(
+      "AndroidSms.FcmMessageDispatchRetry",
+      FcmConnectionEstablisher::MessageType::kStart,
+      retry_count + last_retry_bucket_count);
+  histogram_tester.ExpectBucketCount(
+      "AndroidSms.FcmMessageDispatchFailure",
+      FcmConnectionEstablisher::MessageType::kStart, 1);
 }
 
 TEST_F(FcmConnectionEstablisherTest, TestTearDownConnection) {
diff --git a/chrome/browser/chromeos/dbus/chrome_features_service_provider.h b/chrome/browser/chromeos/dbus/chrome_features_service_provider.h
index 5b5acd1..54931c3 100644
--- a/chrome/browser/chromeos/dbus/chrome_features_service_provider.h
+++ b/chrome/browser/chromeos/dbus/chrome_features_service_provider.h
@@ -25,16 +25,24 @@
 //     --dest=org.chromium.ChromeFeaturesService
 //     /org/chromium/ChromeFeaturesService
 //     org.chromium.ChromeFeaturesServiceInterface.IsCrostiniEnabled
+//     string:"|user id hash|"
 //
-// % (returns true if Crostini is enabled, otherwise returns false)
+// % (If |user id hash| is set correctly, returns true if Crostini is enabled
+//    for the user identified by the hash, and false otherwise)
 //
 // IsPluginVmEnabled:
 // % dbus-send --system --type=method_call --print-reply
 //     --dest=org.chromium.ChromeFeaturesService
 //     /org/chromium/ChromeFeaturesService
 //     org.chromium.ChromeFeaturesServiceInterface.IsPluginVmEnabled
+//     string:"|user id hash|"
 //
-// % (returns true if Plugin VMs are enabled, otherwise returns false)
+// % (If |user id hash| is set correctly, returns true if Plugin VMs are enabled
+//    for the user identified by the hash, and false otherwise)
+//
+// Both methods will return an error if the user ID hash parameter is missing.
+// Passing an empty string as the user ID hash to either method will
+// result in the active user profile being used.
 
 class ChromeFeaturesServiceProvider
     : public CrosDBusService::ServiceProviderInterface {
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 9e8674e..c876501 100644
--- a/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
+++ b/chrome/browser/chromeos/extensions/autotest_private/autotest_private_api.cc
@@ -1271,6 +1271,13 @@
 
 void AutotestPrivateSendAssistantTextQueryFunction::OnInteractionFinished(
     AssistantInteractionResolution resolution) {
+  // Only return a result to the caller and stop the timer when |result_|
+  // is not empty to avoid an early return before the entire interaction is
+  // completed. This happens when sending queries to modify device settings,
+  // e.g. "turn on bluetooth", which results in two rounds of interaction.
+  if (result_->empty())
+    return;
+
   if (resolution != AssistantInteractionResolution::kNormal) {
     Respond(Error("Interaction ends abnormally."));
     timeout_timer_.AbandonAndStop();
diff --git a/chrome/browser/chromeos/login/signin/oauth2_browsertest.cc b/chrome/browser/chromeos/login/signin/oauth2_browsertest.cc
index 9318b44..3603371 100644
--- a/chrome/browser/chromeos/login/signin/oauth2_browsertest.cc
+++ b/chrome/browser/chromeos/login/signin/oauth2_browsertest.cc
@@ -28,7 +28,6 @@
 #include "chrome/browser/extensions/chrome_extension_test_notification_observer.h"
 #include "chrome/browser/profiles/profile_manager.h"
 #include "chrome/browser/signin/identity_manager_factory.h"
-#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
 #include "chrome/browser/ui/javascript_dialogs/javascript_dialog_tab_helper.h"
@@ -52,7 +51,6 @@
 #include "extensions/browser/process_manager.h"
 #include "extensions/test/extension_test_message_listener.h"
 #include "extensions/test/result_catcher.h"
-#include "google_apis/gaia/oauth2_token_service_delegate.h"
 #include "net/cookies/canonical_cookie.h"
 #include "net/cookies/cookie_store.h"
 #include "net/test/embedded_test_server/http_request.h"
@@ -60,6 +58,7 @@
 #include "net/url_request/url_request_context.h"
 #include "net/url_request/url_request_context_getter.h"
 #include "services/identity/public/cpp/identity_manager.h"
+#include "services/identity/public/cpp/identity_test_utils.h"
 
 using net::test_server::BasicHttpResponse;
 using net::test_server::HttpRequest;
@@ -740,8 +739,9 @@
   ASSERT_NE(OAuth2LoginManager::SESSION_RESTORE_DONE, login_manager->state());
 
   // Generate an auth error.
-  ProfileOAuth2TokenServiceFactory::GetForProfile(profile())->UpdateCredentials(
-      kTestEmail, OAuth2TokenServiceDelegate::kInvalidRefreshToken);
+  identity::SetInvalidRefreshTokenForAccount(
+      IdentityManagerFactory::GetInstance()->GetForProfile(profile()),
+      kTestEmail);
 
   // Let go /ListAccounts request.
   list_accounts_request_deferer.UnblockRequest();
diff --git a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc b/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
index ac65679..a23e5f4 100644
--- a/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
+++ b/chrome/browser/chromeos/policy/device_status_collector_browsertest.cc
@@ -2707,6 +2707,8 @@
 TEST_F(ConsumerDeviceStatusCollectorTimeLimitEnabledTest, ActivityKeptInPref) {
   EXPECT_TRUE(
       profile_pref_service_.GetDictionary(prefs::kUserActivityTimes)->empty());
+  base::Time initial_time = base::Time::Now() + kHour;
+  status_collector_->SetBaselineTime(initial_time);
 
   DeviceStateTransitions test_states[] = {
       DeviceStateTransitions::kEnterSessionActive,
@@ -2732,6 +2734,7 @@
                          base::BindRepeating(&GetEmptyCPUTempInfo),
                          base::BindRepeating(&GetEmptyAndroidStatus),
                          base::BindRepeating(&GetEmptyTpmStatus));
+  status_collector_->SetBaselineTime(initial_time);
   SimulateStateChanges(test_states,
                        sizeof(test_states) / sizeof(DeviceStateTransitions));
 
diff --git a/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc b/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc
index 207bae8..4dbc135 100644
--- a/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc
+++ b/chrome/browser/extensions/api/messaging/native_messaging_policy_handler.cc
@@ -31,7 +31,9 @@
 void NativeMessagingHostListPolicyHandler::ApplyList(
     std::unique_ptr<base::ListValue> filtered_list,
     PrefValueMap* prefs) {
-  prefs->SetValue(pref_path_, std::move(filtered_list));
+  DCHECK(filtered_list);
+  prefs->SetValue(pref_path_,
+                  base::Value::FromUniquePtrValue(std::move(filtered_list)));
 }
 
 }  // namespace extensions
diff --git a/chrome/browser/extensions/extension_storage_monitor_browsertest.cc b/chrome/browser/extensions/extension_storage_monitor_browsertest.cc
index fdd1eff..8aebcca 100644
--- a/chrome/browser/extensions/extension_storage_monitor_browsertest.cc
+++ b/chrome/browser/extensions/extension_storage_monitor_browsertest.cc
@@ -395,9 +395,7 @@
 // Exercises the case where two hosted apps are same-origin but have non-
 // overlapping extents. Disabling one should not suppress storage monitoring for
 // the other.
-// Disabled for flakiness. crbug.com/799022
-IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest,
-                       DISABLED_TwoHostedAppsInSameOrigin) {
+IN_PROC_BROWSER_TEST_F(ExtensionStorageMonitorTest, TwoHostedAppsInSameOrigin) {
   ASSERT_TRUE(embedded_test_server()->Start());
 
   GURL url1 = embedded_test_server()->GetURL(
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 8ee3295..b57c0ec 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -1300,7 +1300,7 @@
 
 const char kNostatePrefetchName[] = "NoState Prefetch";
 const char kNostatePrefetchDescription[] =
-    R"*(If enabled, pre-downloads resources to improve page load speed.)*";
+    "If enabled, pre-downloads resources to improve page load speed.";
 
 const char kNotificationIndicatorName[] = "Notification Indicators";
 const char kNotificationIndicatorDescription[] =
@@ -3509,12 +3509,12 @@
 // Force UI Mode
 const char kUiModeName[] = "Force Ui Mode";
 const char kUiModeDescription[] =
-    R"*(This flag can be used to force a certain mode on to a chromebook, )*"
-    R"*(despite its current orientation. "Tablet" means that the )*"
-    R"*(chromebook will act as if it were in tablet mode. "Clamshell" )*"
-    R"*(means that the chromebook will act as if it were in clamshell )*"
-    R"*(mode . "Auto" means that the chromebook will alternate between )*"
-    R"*(the two, based on its orientation.)*";
+    "This flag can be used to force a certain mode on to a chromebook, "
+    "despite its current orientation. \"Tablet\" means that the "
+    "chromebook will act as if it were in tablet mode. \"Clamshell\" "
+    "means that the chromebook will act as if it were in clamshell "
+    "mode. \"Auto\" means that the chromebook will alternate between "
+    "the two, based on its orientation.";
 const char kUiModeTablet[] = "Tablet";
 const char kUiModeClamshell[] = "Clamshell";
 const char kUiModeAuto[] = "Auto (default)";
diff --git a/chrome/browser/media/unified_autoplay_browsertest.cc b/chrome/browser/media/unified_autoplay_browsertest.cc
index 6877beb..78c22c4d 100644
--- a/chrome/browser/media/unified_autoplay_browsertest.cc
+++ b/chrome/browser/media/unified_autoplay_browsertest.cc
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #include "base/test/scoped_feature_list.h"
+#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
@@ -11,7 +12,9 @@
 #include "components/content_settings/core/browser/host_content_settings_map.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents_observer.h"
+#include "content/public/common/web_preferences.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_frame_navigation_observer.h"
 #include "content/public/test/test_navigation_observer.h"
@@ -27,6 +30,27 @@
 
 static constexpr char const kTestPagePath[] = "/media/unified_autoplay.html";
 
+class ChromeContentBrowserClientOverrideWebAppScope
+    : public ChromeContentBrowserClient {
+ public:
+  ChromeContentBrowserClientOverrideWebAppScope() = default;
+  ~ChromeContentBrowserClientOverrideWebAppScope() override = default;
+
+  void OverrideWebkitPrefs(content::RenderViewHost* rvh,
+                           content::WebPreferences* web_prefs) override {
+    ChromeContentBrowserClient::OverrideWebkitPrefs(rvh, web_prefs);
+
+    web_prefs->web_app_scope = web_app_scope_;
+  }
+
+  void set_web_app_scope(const GURL& web_app_scope) {
+    web_app_scope_ = web_app_scope;
+  }
+
+ private:
+  GURL web_app_scope_;
+};
+
 }  // anonymous namespace
 
 // Integration tests for the unified autoplay policy that require the //chrome
@@ -325,6 +349,69 @@
   EXPECT_FALSE(AttemptPlay(GetWebContents()));
 }
 
+IN_PROC_BROWSER_TEST_F(UnifiedAutoplayBrowserTest,
+                       MatchingWebAppScopeAllowsAutoplay_Origin) {
+  GURL kTestPageUrl(
+      embedded_test_server()->GetURL("example.com", kTestPagePath));
+
+  ChromeContentBrowserClientOverrideWebAppScope browser_client;
+  browser_client.set_web_app_scope(kTestPageUrl.GetOrigin());
+
+  content::ContentBrowserClient* old_browser_client =
+      content::SetBrowserClientForTesting(&browser_client);
+
+  GetWebContents()->GetRenderViewHost()->OnWebkitPreferencesChanged();
+
+  ui_test_utils::NavigateToURL(browser(), kTestPageUrl);
+  content::WaitForLoadStop(GetWebContents());
+
+  EXPECT_TRUE(AttemptPlay(GetWebContents()));
+
+  content::SetBrowserClientForTesting(old_browser_client);
+}
+
+IN_PROC_BROWSER_TEST_F(UnifiedAutoplayBrowserTest,
+                       MatchingWebAppScopeAllowsAutoplay_Path) {
+  GURL kTestPageUrl(
+      embedded_test_server()->GetURL("example.com", kTestPagePath));
+
+  ChromeContentBrowserClientOverrideWebAppScope browser_client;
+  browser_client.set_web_app_scope(kTestPageUrl.GetWithoutFilename());
+
+  content::ContentBrowserClient* old_browser_client =
+      content::SetBrowserClientForTesting(&browser_client);
+
+  GetWebContents()->GetRenderViewHost()->OnWebkitPreferencesChanged();
+
+  ui_test_utils::NavigateToURL(browser(), kTestPageUrl);
+  content::WaitForLoadStop(GetWebContents());
+
+  EXPECT_TRUE(AttemptPlay(GetWebContents()));
+
+  content::SetBrowserClientForTesting(old_browser_client);
+}
+
+IN_PROC_BROWSER_TEST_F(UnifiedAutoplayBrowserTest,
+                       NotMatchingWebAppScopeDoesNotAllowAutoplay) {
+  GURL kTestPageUrl(
+      embedded_test_server()->GetURL("example.com", kTestPagePath));
+
+  ChromeContentBrowserClientOverrideWebAppScope browser_client;
+  browser_client.set_web_app_scope(GURL("http://www.foobar.com"));
+
+  content::ContentBrowserClient* old_browser_client =
+      content::SetBrowserClientForTesting(&browser_client);
+
+  GetWebContents()->GetRenderViewHost()->OnWebkitPreferencesChanged();
+
+  ui_test_utils::NavigateToURL(browser(), kTestPageUrl);
+  content::WaitForLoadStop(GetWebContents());
+
+  EXPECT_FALSE(AttemptPlay(GetWebContents()));
+
+  content::SetBrowserClientForTesting(old_browser_client);
+}
+
 // Integration tests for the new unified autoplay sound settings UI.
 
 class UnifiedAutoplaySettingBrowserTest : public UnifiedAutoplayBrowserTest {
diff --git a/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc b/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
index e54a139..ba6720d81 100644
--- a/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
+++ b/chrome/browser/media/webrtc/native_desktop_media_list_unittest.cc
@@ -68,7 +68,6 @@
     DCHECK(callback_);
     std::unique_ptr<webrtc::DesktopFrame> frame(
         new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
-    memset(frame->data(), 0, frame->stride() * frame->size().height());
     callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
                                std::move(frame));
   }
diff --git a/chrome/browser/password_manager/password_store_signin_notifier_impl.h b/chrome/browser/password_manager/password_store_signin_notifier_impl.h
index 5d54b38..7b32a1d 100644
--- a/chrome/browser/password_manager/password_store_signin_notifier_impl.h
+++ b/chrome/browser/password_manager/password_store_signin_notifier_impl.h
@@ -27,10 +27,8 @@
   void SubscribeToSigninEvents(PasswordStore* store) override;
   void UnsubscribeFromSigninEvents() override;
 
-  // SigninManagerBase::Observer implementations.
-  void OnPrimaryAccountCleared(const AccountInfo& account_info) override;
-
   // IdentityManager::Observer implementations.
+  void OnPrimaryAccountCleared(const AccountInfo& account_info) override;
   void OnAccountRemovedWithInfo(const AccountInfo& info) override;
 
  private:
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
index f8aa3b9..7a08fdf6 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_window_controller_browsertest.cc
@@ -9,6 +9,7 @@
 #include "base/strings/utf_string_conversions.h"
 #include "base/test/scoped_feature_list.h"
 #include "build/build_config.h"
+#include "chrome/browser/chrome_content_browser_client.h"
 #include "chrome/browser/devtools/devtools_window_testing.h"
 #include "chrome/browser/extensions/browsertest_util.h"
 #include "chrome/browser/extensions/extension_browsertest.h"
@@ -26,8 +27,10 @@
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/overlay_window.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_view_host.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/web_preferences.h"
 #include "content/public/test/browser_test_utils.h"
 #include "content/public/test/test_navigation_observer.h"
 #include "media/base/media_switches.h"
@@ -2024,6 +2027,31 @@
   EXPECT_TRUE(in_picture_in_picture);
 }
 
+namespace {
+
+class ChromeContentBrowserClientOverrideWebAppScope
+    : public ChromeContentBrowserClient {
+ public:
+  ChromeContentBrowserClientOverrideWebAppScope() = default;
+  ~ChromeContentBrowserClientOverrideWebAppScope() override = default;
+
+  void OverrideWebkitPrefs(content::RenderViewHost* rvh,
+                           content::WebPreferences* web_prefs) override {
+    ChromeContentBrowserClient::OverrideWebkitPrefs(rvh, web_prefs);
+
+    web_prefs->web_app_scope = web_app_scope_;
+  }
+
+  void set_web_app_scope(const GURL& web_app_scope) {
+    web_app_scope_ = web_app_scope;
+  }
+
+ private:
+  GURL web_app_scope_;
+};
+
+}  // namespace
+
 class WebAppPictureInPictureWindowControllerBrowserTest
     : public extensions::ExtensionBrowserTest {
  public:
@@ -2060,6 +2088,20 @@
     web_contents_ = app_browser->tab_strip_model()->GetActiveWebContents();
     EXPECT_TRUE(content::WaitForLoadStop(web_contents_));
     ASSERT_NE(nullptr, web_contents_);
+
+    SetWebAppScope(app_url.GetOrigin());
+  }
+
+  void SetWebAppScope(const GURL web_app_scope) {
+    ChromeContentBrowserClientOverrideWebAppScope browser_client_;
+    browser_client_.set_web_app_scope(web_app_scope);
+
+    content::ContentBrowserClient* original_browser_client_ =
+        content::SetBrowserClientForTesting(&browser_client_);
+
+    web_contents_->GetRenderViewHost()->OnWebkitPreferencesChanged();
+
+    content::SetBrowserClientForTesting(original_browser_client_);
   }
 
   content::WebContents* web_contents() { return web_contents_; }
@@ -2098,6 +2140,34 @@
 }
 
 // Show pwa page and check that Auto Picture-in-Picture is not triggered if
+// document is not inside the scope specified in the Web App Manifest.
+IN_PROC_BROWSER_TEST_F(
+    WebAppPictureInPictureWindowControllerBrowserTest,
+    AutoPictureInPictureNotTriggeredIfDocumentNotInWebAppScope) {
+  InstallAndLaunchPWA();
+  SetWebAppScope(GURL("http://www.foobar.com"));
+  bool result = false;
+  ASSERT_TRUE(content::ExecuteScriptAndExtractBool(web_contents(),
+                                                   "playVideo();", &result));
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(content::ExecuteScript(web_contents(),
+                                     "video.autoPictureInPicture = true;"));
+
+  // Hide page and check that the video did not entered
+  // Picture-in-Picture automatically.
+  web_contents()->WasHidden();
+  base::string16 expected_title = base::ASCIIToUTF16("hidden");
+  EXPECT_EQ(
+      expected_title,
+      content::TitleWatcher(web_contents(), expected_title).WaitAndGetTitle());
+
+  bool in_picture_in_picture = false;
+  ASSERT_TRUE(ExecuteScriptAndExtractBool(
+      web_contents(), "isInPictureInPicture();", &in_picture_in_picture));
+  EXPECT_FALSE(in_picture_in_picture);
+}
+
+// Show pwa page and check that Auto Picture-in-Picture is not triggered if
 // video is not playing.
 IN_PROC_BROWSER_TEST_F(WebAppPictureInPictureWindowControllerBrowserTest,
                        AutoPictureInPictureNotTriggeredIfVideoNotPlaying) {
diff --git a/chrome/browser/resources/local_ntp/doodles.js b/chrome/browser/resources/local_ntp/doodles.js
index f75b650..9708c8a 100644
--- a/chrome/browser/resources/local_ntp/doodles.js
+++ b/chrome/browser/resources/local_ntp/doodles.js
@@ -557,6 +557,7 @@
     case doodles.LOGO_TYPE.INTERACTIVE:
       logoDoodleIframe.title = doodles.targetDoodle.metadata.altText;
       logoDoodleIframe.src = doodles.targetDoodle.metadata.fullPageUrl;
+      logoDoodleIframe.allow = 'autoplay';
       document.body.style.setProperty(
           '--logo-iframe-width',
           doodles.targetDoodle.metadata.iframeWidthPx + 'px');
diff --git a/chrome/browser/resources/print_preview/data/destination_match.js b/chrome/browser/resources/print_preview/data/destination_match.js
index e30339c..9e3e930 100644
--- a/chrome/browser/resources/print_preview/data/destination_match.js
+++ b/chrome/browser/resources/print_preview/data/destination_match.js
@@ -8,8 +8,7 @@
    * Converts DestinationOrigin to PrinterType.
    * @param {!print_preview.DestinationOrigin} origin The printer's
    *     destination origin.
-   * return {?print_preview.PrinterType} The corresponding PrinterType.
-   *     Returns null if no match is found.
+   * return {!print_preview.PrinterType} The corresponding PrinterType.
    */
   const originToType = function(origin) {
     if (origin === print_preview.DestinationOrigin.LOCAL ||
@@ -22,7 +21,8 @@
     if (origin === print_preview.DestinationOrigin.EXTENSION) {
       return print_preview.PrinterType.EXTENSION_PRINTER;
     }
-    return null;
+    assert(print_preview.CloudOrigins.includes(origin));
+    return print_preview.PrinterType.CLOUD_PRINTER;
   };
 
   class DestinationMatch {
@@ -107,12 +107,11 @@
     }
 
     /**
-     * @return {!Set<?print_preview.PrinterType>} The printer types that
-     *     correspond to this destination match. A null element in the set
-     *     indicates the match may represent a Cloud destination.
+     * @return {!Set<!print_preview.PrinterType>} The printer types that
+     *     correspond to this destination match.
      */
     getTypes() {
-      return new Set(this.origins_.map(origin => originToType(origin)));
+      return new Set(this.origins_.map(originToType));
     }
   }
 
diff --git a/chrome/browser/resources/print_preview/data/destination_store.js b/chrome/browser/resources/print_preview/data/destination_store.js
index bc5c9814..66c3e9e 100644
--- a/chrome/browser/resources/print_preview/data/destination_store.js
+++ b/chrome/browser/resources/print_preview/data/destination_store.js
@@ -372,64 +372,59 @@
         return;
       }
 
-      const serializedDestination = {
-        id: '',
-        origin: this.platformOrigin_,
+      let startedAutoSelect = false;
+      let selected = false;
+      // Run through the destinations forward. As soon as we find a
+      // destination, don't select any future destinations, just fetch their
+      // capabilities in case the user switches to them later.
+      for (const destination of recentDestinations) {
+        const candidate = this.destinationMap_.get(
+            print_preview.createRecentDestinationKey(destination));
+        const shouldSelectDestination =
+            !this.useSystemDefaultAsDefault_ && !selected && !startedAutoSelect;
+        if (candidate != undefined) {
+          // Destination is already in the store. Select it, if we haven't
+          // started selecting a destination already.
+          if (shouldSelectDestination) {
+            this.selectDestination(candidate);
+            selected = true;
+          }
+        } else {
+          // Pre-fetch the destination and start auto select if needed.
+          const startedFetch = this.fetchPreselectedDestination_(
+              destination, shouldSelectDestination);
+          startedAutoSelect =
+              startedAutoSelect || (startedFetch && shouldSelectDestination);
+        }
+      }
+
+      if ((selected || startedAutoSelect) && !this.useSystemDefaultAsDefault_) {
+        // Return early since we already selected a destination.
+        return;
+      }
+
+      // Try the system default
+      const serializedSystemDefault = {
+        id: this.systemDefaultDestinationId_,
+        origin: this.systemDefaultDestinationId_ ==
+                print_preview.Destination.GooglePromotedId.SAVE_AS_PDF ?
+            print_preview.DestinationOrigin.LOCAL :
+            this.platformOrigin_,
         account: '',
         capabilities: null,
         displayName: '',
         extensionId: '',
         extensionName: '',
       };
-      let foundDestination = false;
-      // Run through the destinations forward. As soon as we find a
-      // destination, don't select any future destinations, just mark
-      // them recent. Otherwise, there is a race condition between selecting
-      // destinations/updating the print ticket and this selecting a new
-      // destination that causes random print preview errors.
-      for (const destination of recentDestinations) {
-        const candidate = this.destinationMap_.get(
-            print_preview.createRecentDestinationKey(destination));
-        if (candidate != undefined) {
-          if (!foundDestination && !this.useSystemDefaultAsDefault_) {
-            this.selectDestination(candidate);
-          }
-          foundDestination = true;
-        } else {
-          // Only automatically select the destination if we are not using the
-          // system default and we have not already found a destination to
-          // select.
-          const autoSelect =
-              !foundDestination && !this.useSystemDefaultAsDefault_;
-          const foundNewDestination =
-              this.fetchPreselectedDestination_(destination, autoSelect);
-          foundDestination = foundDestination || foundNewDestination;
-        }
-      }
-
-      if (foundDestination && !this.useSystemDefaultAsDefault_) {
-        this.startAutoSelectTimeout_();
-        return;
-      }
-
-      // Try the system default
-      serializedDestination.id = this.systemDefaultDestinationId_;
-      serializedDestination.origin = serializedDestination.id ==
-              print_preview.Destination.GooglePromotedId.SAVE_AS_PDF ?
-          print_preview.DestinationOrigin.LOCAL :
-          this.platformOrigin_;
-      serializedDestination.account = '';
       const systemDefaultCandidate = this.destinationMap_.get(
-          print_preview.createRecentDestinationKey(serializedDestination));
+          print_preview.createRecentDestinationKey(serializedSystemDefault));
       if (systemDefaultCandidate != undefined) {
         this.selectDestination(systemDefaultCandidate);
-        this.startAutoSelectTimeout_();
         return;
       }
 
       if (this.fetchPreselectedDestination_(
-              serializedDestination, true /* autoSelect */)) {
-        this.startAutoSelectTimeout_();
+              serializedSystemDefault, true /* autoSelect */)) {
         return;
       }
 
@@ -461,57 +456,64 @@
             this.createExactDestinationMatch_(origin, id);
       }
 
+      let error = false;
       const type = print_preview.originToType(origin);
-      if (type == print_preview.PrinterType.LOCAL_PRINTER) {
-        this.nativeLayer_.getPrinterCapabilities(id, type).then(
-            this.onCapabilitiesSet_.bind(this, origin, id),
-            this.onGetCapabilitiesFail_.bind(this, origin, id));
-        return true;
+      switch (type) {
+        case print_preview.PrinterType.LOCAL_PRINTER:
+          this.nativeLayer_.getPrinterCapabilities(id, type).then(
+              this.onCapabilitiesSet_.bind(this, origin, id),
+              this.onGetCapabilitiesFail_.bind(this, origin, id));
+          break;
+        case print_preview.PrinterType.PRIVET_PRINTER:
+        case print_preview.PrinterType.EXTENSION_PRINTER:
+          // TODO(noamsml): Resolve a specific printer instead of listing all
+          // privet or extension printers in this case.
+          this.startLoadDestinations_(type);
+
+          if (autoSelect) {
+            // Create a fake selectedDestination_ that is not actually in the
+            // destination store. When the real destination is created, this
+            // destination will be overwritten.
+            const params =
+                (origin === print_preview.DestinationOrigin.PRIVET) ? {} : {
+                  description: '',
+                  extensionId: serializedDestination.extensionId,
+                  extensionName: serializedDestination.extensionName,
+                  provisionalType: print_preview.DestinationProvisionalType.NONE
+                };
+            this.selectedDestination_ = new print_preview.Destination(
+                id, print_preview.DestinationType.LOCAL, origin,
+                serializedDestination.displayName,
+                print_preview.DestinationConnectionStatus.ONLINE, params);
+
+            if (serializedDestination.capabilities) {
+              this.selectedDestination_.capabilities =
+                  serializedDestination.capabilities;
+              this.dispatchEvent(new CustomEvent(
+                  DestinationStore.EventType
+                      .SELECTED_DESTINATION_CAPABILITIES_READY));
+            }
+          }
+          break;
+        case print_preview.PrinterType.CLOUD_PRINTER:
+          if (this.cloudPrintInterface_) {
+            this.inFlightCloudPrintRequests_.add(key);
+            this.cloudPrintInterface_.printer(
+                id, origin, serializedDestination.account);
+          } else {
+            // No cloud print interface.
+            error = true;
+          }
+          break;
+        default:
+          // Unknown type.
+          error = true;
       }
 
-      if (this.cloudPrintInterface_ &&
-          print_preview.CloudOrigins.includes(origin)) {
-        this.inFlightCloudPrintRequests_.add(key);
-        this.cloudPrintInterface_.printer(
-            id, origin, serializedDestination.account);
-        return true;
+      if (!error && autoSelect) {
+        this.startAutoSelectTimeout_();
       }
-
-      if (origin == print_preview.DestinationOrigin.PRIVET ||
-          origin == print_preview.DestinationOrigin.EXTENSION) {
-        // TODO(noamsml): Resolve a specific printer instead of listing all
-        // privet or extension printers in this case.
-        this.startLoadDestinations_(type);
-
-        if (!autoSelect) {
-          return true;
-        }
-
-        // Create a fake selectedDestination_ that is not actually in the
-        // destination store. When the real destination is created, this
-        // destination will be overwritten.
-        const params =
-            (origin === print_preview.DestinationOrigin.PRIVET) ? {} : {
-              description: '',
-              extensionId: serializedDestination.extensionId,
-              extensionName: serializedDestination.extensionName,
-              provisionalType: print_preview.DestinationProvisionalType.NONE
-            };
-        this.selectedDestination_ = new print_preview.Destination(
-            id, print_preview.DestinationType.LOCAL, origin,
-            serializedDestination.displayName,
-            print_preview.DestinationConnectionStatus.ONLINE, params);
-
-        if (serializedDestination.capabilities) {
-          this.selectedDestination_.capabilities =
-              serializedDestination.capabilities;
-          this.dispatchEvent(
-              new CustomEvent(DestinationStore.EventType
-                                  .SELECTED_DESTINATION_CAPABILITIES_READY));
-        }
-        return true;
-      }
-      return false;
+      return !error;
     }
 
     /**
@@ -523,7 +525,8 @@
       this.autoSelectMatchingDestination_ = destinationMatch;
       const types = destinationMatch.getTypes();
       types.forEach(type => {
-        if (type != null) {  // Local, extension, or privet printer
+        if (type != print_preview.PrinterType.CLOUD_PRINTER) {
+          // Local, extension, or privet printer
           this.startLoadDestinations_(type);
         } else if (print_preview.CloudOrigins.some(origin => {
                      return destinationMatch.matchOrigin(origin);
@@ -669,8 +672,8 @@
      */
     selectDestination(destination) {
       this.autoSelectMatchingDestination_ = null;
-      // When auto select expires, DESTINATION_SELECT event has to be dispatched
-      // anyway (see isAutoSelectDestinationInProgress() logic).
+      // Clear the timeout. Otherwise, when it expires, we will fall back to the
+      // default destination.
       if (this.autoSelectTimeout_) {
         clearTimeout(this.autoSelectTimeout_);
         this.autoSelectTimeout_ = null;
@@ -709,7 +712,7 @@
       // known yet.
       if (destination.capabilities == null) {
         const type = print_preview.originToType(destination.origin);
-        if (type !== null) {
+        if (type !== print_preview.PrinterType.CLOUD_PRINTER) {
           this.nativeLayer_.getPrinterCapabilities(destination.id, type)
               .then(
                   (caps) => this.onCapabilitiesSet_(
@@ -1146,13 +1149,10 @@
       this.inFlightCloudPrintRequests_.clear();
       this.selectDestination(null);
       this.loadedCloudOrigins_.clear();
-      for (const printerType of Object.values(print_preview.PrinterType)) {
-        if (printerType !== print_preview.PrinterType.PDF_PRINTER) {
-          this.destinationSearchStatus_.set(
-              printerType,
-              print_preview.DestinationStorePrinterSearchStatus.START);
-        }
-      }
+      this.destinationSearchStatus_.forEach((status, type) => {
+        this.destinationSearchStatus_.set(
+            type, print_preview.DestinationStorePrinterSearchStatus.START);
+      });
       this.startAutoSelectTimeout_();
       this.dispatchEvent(
           new CustomEvent(DestinationStore.EventType.DESTINATIONS_RESET));
diff --git a/chrome/browser/resources/settings/controls/settings_slider.html b/chrome/browser/resources/settings/controls/settings_slider.html
index 872792b..15257fa0 100644
--- a/chrome/browser/resources/settings/controls/settings_slider.html
+++ b/chrome/browser/resources/settings/controls/settings_slider.html
@@ -26,7 +26,7 @@
         display: flex;
         flex-direction: column;
         margin: 8px 0;
-        min-width: 200px;
+        min-width: var(--cr-text-element-min-width);
       }
 
       #labels {
diff --git a/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html b/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html
index a25cf48..bdeecaf6 100644
--- a/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html
+++ b/chrome/browser/resources/settings/google_assistant_page/google_assistant_page.html
@@ -14,7 +14,11 @@
 
 <dom-module id="settings-google-assistant-page">
   <template>
-    <style include="settings-shared md-select"></style>
+    <style include="settings-shared md-select">
+      .text-area {
+        margin-inline-end: 24px;
+      }
+    </style>
     <settings-toggle-button id="googleAssistantEnable"
         class="first primary-toggle"
         pref="{{prefs.settings.voice_interaction.enabled}}"
diff --git a/chrome/browser/sync/chrome_sync_client.cc b/chrome/browser/sync/chrome_sync_client.cc
index 8c36199..070889d 100644
--- a/chrome/browser/sync/chrome_sync_client.cc
+++ b/chrome/browser/sync/chrome_sync_client.cc
@@ -30,6 +30,7 @@
 #include "chrome/browser/sync/glue/theme_data_type_controller.h"
 #include "chrome/browser/sync/model_type_store_service_factory.h"
 #include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
 #include "chrome/browser/sync/user_event_service_factory.h"
 #include "chrome/browser/themes/theme_service.h"
@@ -63,10 +64,12 @@
 #include "components/password_manager/core/browser/sync/password_model_worker.h"
 #include "components/search_engines/search_engine_data_type_controller.h"
 #include "components/search_engines/search_engine_model_type_controller.h"
+#include "components/send_tab_to_self/send_tab_to_self_service.h"
 #include "components/spellcheck/spellcheck_buildflags.h"
 #include "components/sync/base/pref_names.h"
 #include "components/sync/base/report_unrecoverable_error.h"
 #include "components/sync/driver/async_directory_type_controller.h"
+#include "components/sync/driver/model_type_controller.h"
 #include "components/sync/driver/sync_api_component_factory.h"
 #include "components/sync/driver/sync_driver_switches.h"
 #include "components/sync/driver/sync_util.h"
@@ -75,6 +78,7 @@
 #include "components/sync/engine/sequenced_model_worker.h"
 #include "components/sync/engine/ui_model_worker.h"
 #include "components/sync/model/model_type_store_service.h"
+#include "components/sync/model_impl/forwarding_model_type_controller_delegate.h"
 #include "components/sync/user_events/user_event_service.h"
 #include "components/sync_bookmarks/bookmark_sync_service.h"
 #include "components/sync_preferences/pref_service_syncable.h"
@@ -454,6 +458,16 @@
   }
 #endif  // defined(OS_CHROMEOS)
 
+  if (!disabled_types.Has(syncer::SEND_TAB_TO_SELF) &&
+      base::FeatureList::IsEnabled(switches::kSyncSendTabToSelf)) {
+    controllers.push_back(std::make_unique<syncer::ModelTypeController>(
+        syncer::SEND_TAB_TO_SELF,
+        std::make_unique<syncer::ForwardingModelTypeControllerDelegate>(
+            SendTabToSelfSyncServiceFactory::GetForProfile(profile_)
+                ->GetControllerDelegate()
+                .get())));
+  }
+
   return controllers;
 }
 
@@ -620,6 +634,7 @@
     case syncer::AUTOFILL_WALLET_METADATA:
     case syncer::BOOKMARKS:
     case syncer::DEVICE_INFO:
+    case syncer::SEND_TAB_TO_SELF:
     case syncer::SESSIONS:
     case syncer::TYPED_URLS:
       NOTREACHED();
diff --git a/chrome/browser/sync/profile_sync_service_factory.cc b/chrome/browser/sync/profile_sync_service_factory.cc
index 6b3eff2..ab3c7ee 100644
--- a/chrome/browser/sync/profile_sync_service_factory.cc
+++ b/chrome/browser/sync/profile_sync_service_factory.cc
@@ -35,6 +35,7 @@
 #include "chrome/browser/sync/chrome_sync_client.h"
 #include "chrome/browser/sync/device_info_sync_service_factory.h"
 #include "chrome/browser/sync/model_type_store_service_factory.h"
+#include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
 #include "chrome/browser/sync/session_sync_service_factory.h"
 #include "chrome/browser/sync/user_event_service_factory.h"
 #include "chrome/browser/themes/theme_service_factory.h"
@@ -152,6 +153,7 @@
   DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance());
   DependsOn(ModelTypeStoreServiceFactory::GetInstance());
   DependsOn(PasswordStoreFactory::GetInstance());
+  DependsOn(SendTabToSelfSyncServiceFactory::GetInstance());
   DependsOn(SpellcheckServiceFactory::GetInstance());
 #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
   DependsOn(SupervisedUserSettingsServiceFactory::GetInstance());
diff --git a/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc b/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc
index c3f1f1f..f74fb3a 100644
--- a/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc
+++ b/chrome/browser/sync/send_tab_to_self_sync_service_factory.cc
@@ -8,10 +8,11 @@
 #include "base/memory/singleton.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/signin/chrome_device_id_helper.h"
+#include "chrome/browser/sync/device_info_sync_service_factory.h"
 #include "chrome/common/channel_info.h"
 #include "components/keyed_service/content/browser_context_dependency_manager.h"
 #include "components/send_tab_to_self/send_tab_to_self_service.h"
-#include "components/sync/device_info/local_device_info_provider_impl.h"
+#include "components/sync/device_info/device_info_sync_service.h"
 #include "ui/base/device_form_factor.h"
 
 // static
@@ -30,7 +31,9 @@
 SendTabToSelfSyncServiceFactory::SendTabToSelfSyncServiceFactory()
     : BrowserContextKeyedServiceFactory(
           "SendTabToSelfSyncService",
-          BrowserContextDependencyManager::GetInstance()) {}
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(DeviceInfoSyncServiceFactory::GetInstance());
+}
 
 SendTabToSelfSyncServiceFactory::~SendTabToSelfSyncServiceFactory() {}
 
@@ -38,19 +41,12 @@
     content::BrowserContext* context) const {
   Profile* profile = Profile::FromBrowserContext(context);
 
-  syncer::LocalDeviceInfoProviderImpl::SigninScopedDeviceIdCallback
-      signin_scoped_device_id_callback =
-          base::BindRepeating(&GetSigninScopedDeviceIdForProfile, profile);
-
-  std::unique_ptr<syncer::LocalDeviceInfoProviderImpl>
-      local_device_info_provider =
-          std::make_unique<syncer::LocalDeviceInfoProviderImpl>(
-              chrome::GetChannel(), chrome::GetVersionString(),
-              ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_TABLET,
-              signin_scoped_device_id_callback);
+  syncer::LocalDeviceInfoProvider* local_device_info_provider =
+      DeviceInfoSyncServiceFactory::GetForProfile(profile)
+          ->GetLocalDeviceInfoProvider();
 
   // TODO(jeffreycohen): use KeyedService to provide a DeviceInfo ptr.
 
   return new send_tab_to_self::SendTabToSelfService(chrome::GetChannel(),
-                                                    nullptr);
+                                                    local_device_info_provider);
 }
diff --git a/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc b/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc
new file mode 100644
index 0000000..7e2c6e4
--- /dev/null
+++ b/chrome/browser/sync/test/integration/send_tab_to_self_helper.cc
@@ -0,0 +1,106 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sync/test/integration/send_tab_to_self_helper.h"
+
+#include "base/logging.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
+#include "components/send_tab_to_self/send_tab_to_self_entry.h"
+#include "components/send_tab_to_self/send_tab_to_self_model.h"
+#include "components/send_tab_to_self/send_tab_to_self_model_observer.h"
+#include "components/send_tab_to_self/send_tab_to_self_service.h"
+
+namespace send_tab_to_self_helper {
+
+SendTabToSelfUrlChecker::SendTabToSelfUrlChecker(
+    send_tab_to_self::SendTabToSelfService* service,
+    const GURL& url)
+    : url_(url), service_(service) {
+  service->GetSendTabToSelfModel()->AddObserver(this);
+}
+
+SendTabToSelfUrlChecker::~SendTabToSelfUrlChecker() {
+  service_->GetSendTabToSelfModel()->RemoveObserver(this);
+}
+
+bool SendTabToSelfUrlChecker::IsExitConditionSatisfied() {
+  send_tab_to_self::SendTabToSelfModel* model =
+      service_->GetSendTabToSelfModel();
+  for (auto const& guid : model->GetAllGuids()) {
+    if (model->GetEntryByGUID(guid)->GetURL() == url_) {
+      return true;
+    }
+  }
+  return false;
+}
+
+std::string SendTabToSelfUrlChecker::GetDebugMessage() const {
+  return "Waiting for data for url '" + url_.spec() + "' to be populated.";
+}
+
+void SendTabToSelfUrlChecker::SendTabToSelfModelLoaded() {
+  CheckExitCondition();
+}
+
+void SendTabToSelfUrlChecker::SendTabToSelfModelChanged() {
+  CheckExitCondition();
+}
+
+SendTabToSelfModelEqualityChecker::SendTabToSelfModelEqualityChecker(
+    send_tab_to_self::SendTabToSelfService* service0,
+    send_tab_to_self::SendTabToSelfService* service1)
+    : service0_(service0), service1_(service1) {
+  service0->GetSendTabToSelfModel()->AddObserver(this);
+  service1->GetSendTabToSelfModel()->AddObserver(this);
+}
+
+SendTabToSelfModelEqualityChecker::~SendTabToSelfModelEqualityChecker() {
+  service0_->GetSendTabToSelfModel()->RemoveObserver(this);
+  service1_->GetSendTabToSelfModel()->RemoveObserver(this);
+}
+
+bool SendTabToSelfModelEqualityChecker::IsExitConditionSatisfied() {
+  const send_tab_to_self::SendTabToSelfModel* model0 =
+      service0_->GetSendTabToSelfModel();
+  const send_tab_to_self::SendTabToSelfModel* model1 =
+      service1_->GetSendTabToSelfModel();
+
+  if (model0->GetAllGuids() != model1->GetAllGuids()) {
+    return false;
+  }
+  for (auto const& guid : model0->GetAllGuids()) {
+    const send_tab_to_self::SendTabToSelfEntry* entry0 =
+        model0->GetEntryByGUID(guid);
+    const send_tab_to_self::SendTabToSelfEntry* entry1 =
+        model1->GetEntryByGUID(guid);
+
+    DCHECK_NE(entry0, nullptr);
+    DCHECK_NE(entry1, nullptr);
+
+    if (entry0->GetGUID() != entry1->GetGUID() ||
+        entry0->GetURL() != entry1->GetURL() ||
+        entry0->GetTitle() != entry1->GetTitle() ||
+        entry0->GetSharedTime() != entry1->GetSharedTime() ||
+        entry0->GetOriginalNavigationTime() !=
+            entry1->GetOriginalNavigationTime() ||
+        entry0->GetDeviceName() != entry1->GetDeviceName()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+std::string SendTabToSelfModelEqualityChecker::GetDebugMessage() const {
+  return "Waiting for services to converge";
+}
+
+void SendTabToSelfModelEqualityChecker::SendTabToSelfModelLoaded() {
+  CheckExitCondition();
+}
+
+void SendTabToSelfModelEqualityChecker::SendTabToSelfModelChanged() {
+  CheckExitCondition();
+}
+
+}  // namespace send_tab_to_self_helper
diff --git a/chrome/browser/sync/test/integration/send_tab_to_self_helper.h b/chrome/browser/sync/test/integration/send_tab_to_self_helper.h
new file mode 100644
index 0000000..56fe2894
--- /dev/null
+++ b/chrome/browser/sync/test/integration/send_tab_to_self_helper.h
@@ -0,0 +1,74 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_SYNC_TEST_INTEGRATION_SEND_TAB_TO_SELF_HELPER_H_
+#define CHROME_BROWSER_SYNC_TEST_INTEGRATION_SEND_TAB_TO_SELF_HELPER_H_
+
+#include <string>
+#include <vector>
+
+#include "chrome/browser/sync/test/integration/status_change_checker.h"
+#include "components/send_tab_to_self/send_tab_to_self_model_observer.h"
+#include "url/gurl.h"
+
+namespace send_tab_to_self {
+class SendTabToSelfService;
+}  // namespace send_tab_to_self
+
+namespace send_tab_to_self_helper {
+
+// Class that allows waiting until a particular |url| is exposed by the
+// SendTabToSelfModel in|service|.
+class SendTabToSelfUrlChecker
+    : public StatusChangeChecker,
+      public send_tab_to_self::SendTabToSelfModelObserver {
+ public:
+  SendTabToSelfUrlChecker(send_tab_to_self::SendTabToSelfService* service,
+                          const GURL& url);
+  ~SendTabToSelfUrlChecker() override;
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied() override;
+  std::string GetDebugMessage() const override;
+
+  // SendTabToSelfModelObserver implementation.
+  void SendTabToSelfModelLoaded() override;
+  void SendTabToSelfModelChanged() override;
+
+ private:
+  const GURL url_;
+  send_tab_to_self::SendTabToSelfService* const service_;
+
+  DISALLOW_COPY_AND_ASSIGN(SendTabToSelfUrlChecker);
+};
+
+// Class that allows waiting the number of entries in until |service0|
+// matches the number of entries in |service1|.
+class SendTabToSelfModelEqualityChecker
+    : public StatusChangeChecker,
+      public send_tab_to_self::SendTabToSelfModelObserver {
+ public:
+  SendTabToSelfModelEqualityChecker(
+      send_tab_to_self::SendTabToSelfService* service0,
+      send_tab_to_self::SendTabToSelfService* service1);
+  ~SendTabToSelfModelEqualityChecker() override;
+
+  // StatusChangeChecker implementation.
+  bool IsExitConditionSatisfied() override;
+  std::string GetDebugMessage() const override;
+
+  // SendTabToSelfModelObserver implementation.
+  void SendTabToSelfModelLoaded() override;
+  void SendTabToSelfModelChanged() override;
+
+ private:
+  send_tab_to_self::SendTabToSelfService* const service0_;
+  send_tab_to_self::SendTabToSelfService* const service1_;
+
+  DISALLOW_COPY_AND_ASSIGN(SendTabToSelfModelEqualityChecker);
+};
+
+}  // namespace send_tab_to_self_helper
+
+#endif  // CHROME_BROWSER_SYNC_TEST_INTEGRATION_SEND_TAB_TO_SELF_HELPER_H_
diff --git a/chrome/browser/sync/test/integration/single_client_send_tab_to_self_sync_test.cc b/chrome/browser/sync/test/integration/single_client_send_tab_to_self_sync_test.cc
new file mode 100644
index 0000000..fef3bdd5
--- /dev/null
+++ b/chrome/browser/sync/test/integration/single_client_send_tab_to_self_sync_test.cc
@@ -0,0 +1,52 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
+#include "chrome/browser/sync/test/integration/send_tab_to_self_helper.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
+#include "components/sync/driver/sync_driver_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+namespace {
+
+class SingleClientSendTabToSelfSyncTest : public SyncTest {
+ public:
+  SingleClientSendTabToSelfSyncTest() : SyncTest(SINGLE_CLIENT) {
+    scoped_list_.InitAndEnableFeature(switches::kSyncSendTabToSelf);
+  }
+
+  ~SingleClientSendTabToSelfSyncTest() override {}
+
+ private:
+  base::test::ScopedFeatureList scoped_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(SingleClientSendTabToSelfSyncTest);
+};
+
+IN_PROC_BROWSER_TEST_F(SingleClientSendTabToSelfSyncTest,
+                       DownloadWhenSyncEnabled) {
+  const std::string kUrl("https://www.example.com");
+  const std::string kGuid("kGuid");
+
+  sync_pb::EntitySpecifics specifics;
+  sync_pb::SendTabToSelfSpecifics* send_tab_to_self =
+      specifics.mutable_send_tab_to_self();
+  send_tab_to_self->set_url(kUrl);
+  send_tab_to_self->set_guid(kGuid);
+
+  fake_server_->InjectEntity(
+      syncer::PersistentUniqueClientEntity::CreateFromSpecificsForTesting(
+          "non_unique_name", kGuid, specifics, /*creation_time=*/0,
+          /*last_modified_time=*/0));
+
+  ASSERT_TRUE(SetupSync());
+
+  EXPECT_TRUE(send_tab_to_self_helper::SendTabToSelfUrlChecker(
+                  SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0)),
+                  GURL(kUrl))
+                  .Wait());
+}
+
+}  // namespace
diff --git a/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc b/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
index 744d125..e6f9ea5 100644
--- a/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_preferences_sync_test.cc
@@ -67,6 +67,20 @@
   EXPECT_NE(0, histogram_tester.GetBucketCount(
                    "Sync.ModelTypeEntityChange3.PREFERENCE",
                    /*REMOTE_NON_INITIAL_UPDATE=*/4));
+
+  // Metrics below are instrumented for the USS codepath only.
+  if (GetParam()) {
+    EXPECT_EQ(
+        1U,
+        histogram_tester
+            .GetAllSamples(
+                "Sync.NonReflectionUpdateFreshnessPossiblySkewed.PREFERENCE")
+            .size());
+    EXPECT_NE(0U, histogram_tester
+                      .GetAllSamples(
+                          "Sync.NonReflectionUpdateFreshnessPossiblySkewed")
+                      .size());
+  }
 }
 
 IN_PROC_BROWSER_TEST_P(TwoClientPreferencesSyncTest, E2E_ENABLED(BooleanPref)) {
diff --git a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
new file mode 100644
index 0000000..fde87c7
--- /dev/null
+++ b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
@@ -0,0 +1,80 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/sync/send_tab_to_self_sync_service_factory.h"
+#include "chrome/browser/sync/test/integration/send_tab_to_self_helper.h"
+#include "chrome/browser/sync/test/integration/sync_test.h"
+#include "components/send_tab_to_self/send_tab_to_self_model.h"
+#include "components/send_tab_to_self/send_tab_to_self_service.h"
+#include "components/sync/driver/sync_driver_switches.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "url/gurl.h"
+
+class TwoClientSendTabToSelfSyncTest : public SyncTest {
+ public:
+  TwoClientSendTabToSelfSyncTest() : SyncTest(TWO_CLIENT) {
+    scoped_list_.InitAndEnableFeature(switches::kSyncSendTabToSelf);
+  }
+
+  ~TwoClientSendTabToSelfSyncTest() override {}
+
+ private:
+  base::test::ScopedFeatureList scoped_list_;
+
+  DISALLOW_COPY_AND_ASSIGN(TwoClientSendTabToSelfSyncTest);
+};
+
+IN_PROC_BROWSER_TEST_F(TwoClientSendTabToSelfSyncTest,
+                       AddedUrlFoundWhenBothClientsAlreadySyncing) {
+  const GURL kUrl("https://www.example.com");
+  const std::string kTitle("example");
+  const base::Time kTime = base::Time::FromDoubleT(0);
+
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+
+  send_tab_to_self::SendTabToSelfModel* model0 =
+      SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0))
+          ->GetSendTabToSelfModel();
+
+  model0->AddEntry(kUrl, kTitle, kTime);
+
+  send_tab_to_self::SendTabToSelfService* service1 =
+      SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1));
+
+  EXPECT_TRUE(
+      send_tab_to_self_helper::SendTabToSelfUrlChecker(service1, kUrl).Wait());
+}
+
+IN_PROC_BROWSER_TEST_F(TwoClientSendTabToSelfSyncTest,
+                       ModelsMatchAfterAddWhenBothClientsAlreadySyncing) {
+  const GURL kGurl0("https://www.example0.com");
+  const std::string kTitle0("example0");
+  const base::Time kTime0 = base::Time::FromDoubleT(0);
+
+  const GURL kGurl1("https://www.example1.com");
+  const std::string kTitle1("example1");
+  const base::Time kTime1 = base::Time::FromDoubleT(1);
+
+  const GURL kGurl2("https://www.example2.com");
+  const std::string kTitle2("example2");
+  const base::Time kTime2 = base::Time::FromDoubleT(2);
+
+  ASSERT_TRUE(SetupSync()) << "SetupSync() failed.";
+
+  send_tab_to_self::SendTabToSelfModel* model0 =
+      SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0))
+          ->GetSendTabToSelfModel();
+
+  model0->AddEntry(kGurl0, kTitle0, kTime0);
+  model0->AddEntry(kGurl1, kTitle1, kTime1);
+
+  SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1))
+      ->GetSendTabToSelfModel()
+      ->AddEntry(kGurl2, kTitle2, kTime2);
+
+  EXPECT_TRUE(send_tab_to_self_helper::SendTabToSelfModelEqualityChecker(
+                  SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1)),
+                  SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(0)))
+                  .Wait());
+}
diff --git a/chrome/browser/ui/tab_ui_helper.h b/chrome/browser/ui/tab_ui_helper.h
index 9ceef10..0c16790 100644
--- a/chrome/browser/ui/tab_ui_helper.h
+++ b/chrome/browser/ui/tab_ui_helper.h
@@ -12,6 +12,7 @@
 #include "base/task/cancelable_task_tracker.h"
 #include "components/favicon_base/favicon_callback.h"
 #include "components/favicon_base/favicon_types.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "ui/gfx/image/image.h"
 #include "ui/gfx/image/image_skia.h"
diff --git a/chrome/browser/ui/tabs/tab_activity_simulator.cc b/chrome/browser/ui/tabs/tab_activity_simulator.cc
index e6b1544..c3fe2ef 100644
--- a/chrome/browser/ui/tabs/tab_activity_simulator.cc
+++ b/chrome/browser/ui/tabs/tab_activity_simulator.cc
@@ -8,6 +8,7 @@
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/web_contents_tester.h"
 
diff --git a/chrome/browser/ui/webui/chromeos/login/discover/wait_for_did_start_navigate.h b/chrome/browser/ui/webui/chromeos/login/discover/wait_for_did_start_navigate.h
index 1c28383..a6d32dd1 100644
--- a/chrome/browser/ui/webui/chromeos/login/discover/wait_for_did_start_navigate.h
+++ b/chrome/browser/ui/webui/chromeos/login/discover/wait_for_did_start_navigate.h
@@ -7,7 +7,7 @@
 
 #include "base/macros.h"
 #include "base/run_loop.h"
-#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "url/gurl.h"
 
 namespace chromeos {
diff --git a/chrome/browser/ui/webui/help/version_updater_chromeos.cc b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
index 66060e64..943d9bd0 100644
--- a/chrome/browser/ui/webui/help/version_updater_chromeos.cc
+++ b/chrome/browser/ui/webui/help/version_updater_chromeos.cc
@@ -218,8 +218,8 @@
     // One time permission is set successfully, so we can proceed to update.
     CheckForUpdate(callback_, VersionUpdater::PromoteCallback());
   } else {
-    // TODO(weidongg/691108): invoke callback to signal about page to show
-    // appropriate error message.
+    // TODO(https://crbug.com/927452): invoke callback to signal about page to
+    // show appropriate error message.
     LOG(ERROR) << "Error setting update over cellular one time permission.";
     callback_.Run(VersionUpdater::FAILED, 0, false, std::string(), 0,
                   base::string16());
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_provider.cc b/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
index 73ae295..231e5f7 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_provider.cc
@@ -56,25 +56,6 @@
 void CGaiaCredentialProvider::FinalRelease() {
   LOGFN(INFO);
   ClearTransient();
-
-  // Delete the startup sentinel file if any still exists. It can still exist
-  // in 2 cases:
-
-  // 1. The FinalRelease should only occur after the user has logged in, so if
-  // they never selected any gaia credential and just used normal credentials
-  // this function will be called in that situation and it is guaranteed that
-  // the user has at least been able provide some input to winlogon.
-  // 2. When no usage scenario is supported, none of the credentials will be
-  // selected and thus the gcpw startup sentinel file will not be deleted.
-  // So in the case where the user is asked for CPUS_CRED_UI enough times,
-  // the sentinel file size will keep growing without being deleted and
-  // eventually GCPW will be disabled completed. In the unsupported usage
-  // scenario, FinalRelease will be called shortly after SetUsageScenario
-  // if the function returns E_NOTIMPL so try to catch potential crashes
-  // of the destruction of the provider when it is not used because
-  // crashes in this case will prevent the cred ui from coming up and not
-  // allow the user to access their desired resource.
-  DeleteStartupSentinel();
 }
 
 HRESULT CGaiaCredentialProvider::CreateGaiaCredential() {
@@ -380,6 +361,26 @@
   ClearTransient();
   HRESULT hr = DestroyCredentials();
   LOGFN(INFO) << "hr=" << putHR(hr);
+
+  // Delete the startup sentinel file if any still exists. It can still exist
+  // in 2 cases:
+
+  // 1. The UnAdvise should only occur after the user has logged in, so if
+  // they never selected any gaia credential and just used normal credentials
+  // this function will be called in that situation and it is guaranteed that
+  // the user has at least been able provide some input to winlogon.
+  // 2. When no usage scenario is supported, none of the credentials will be
+  // selected and thus the gcpw startup sentinel file will not be deleted.
+  // So in the case where the user is asked for CPUS_CRED_UI enough times,
+  // the sentinel file size will keep growing without being deleted and
+  // eventually GCPW will be disabled completely. In the unsupported usage
+  // scenario, FinalRelease will be called shortly after SetUsageScenario
+  // if the function returns E_NOTIMPL so try to catch potential crashes
+  // of the destruction of the provider when it is not used because
+  // crashes in this case will prevent the cred ui from coming up and not
+  // allow the user to access their desired resource.
+  DeleteStartupSentinel();
+
   return S_OK;
 }
 
diff --git a/chrome/services/media_gallery_util/BUILD.gn b/chrome/services/media_gallery_util/BUILD.gn
index db40c64..3292488 100644
--- a/chrome/services/media_gallery_util/BUILD.gn
+++ b/chrome/services/media_gallery_util/BUILD.gn
@@ -29,6 +29,8 @@
     sources += [
       "media_parser_android.cc",
       "media_parser_android.h",
+      "video_thumbnail_parser.cc",
+      "video_thumbnail_parser.h",
     ]
   }
 
diff --git a/chrome/services/media_gallery_util/media_parser_android.cc b/chrome/services/media_gallery_util/media_parser_android.cc
index a6078d6..57c94b6 100644
--- a/chrome/services/media_gallery_util/media_parser_android.cc
+++ b/chrome/services/media_gallery_util/media_parser_android.cc
@@ -4,100 +4,30 @@
 
 #include "chrome/services/media_gallery_util/media_parser_android.h"
 
+#include <utility>
+
 #include "base/bind.h"
 #include "base/optional.h"
 #include "base/task/post_task.h"
-#include "base/task/task_traits.h"
 #include "chrome/services/media_gallery_util/ipc_data_source.h"
-#include "media/base/bind_to_current_loop.h"
-#include "media/base/video_codecs.h"
-#include "media/base/video_thumbnail_decoder.h"
-#include "media/filters/android/video_frame_extractor.h"
-#include "media/filters/vpx_video_decoder.h"
-#include "media/media_buildflags.h"
-#include "media/mojo/common/mojo_shared_buffer_video_frame.h"
+#include "chrome/services/media_gallery_util/video_thumbnail_parser.h"
 
 namespace {
 
-// Return the video frame back to browser process. A valid |config| is
-// needed for deserialization.
-void OnSoftwareVideoFrameDecoded(
-    std::unique_ptr<media::VideoThumbnailDecoder>,
-    MediaParser::ExtractVideoFrameCallback video_frame_callback,
-    const media::VideoDecoderConfig& config,
-    scoped_refptr<media::VideoFrame> frame) {
-  DCHECK(video_frame_callback);
-
-  if (!frame) {
-    std::move(video_frame_callback)
-        .Run(false, chrome::mojom::VideoFrameData::New(), base::nullopt);
-    return;
-  }
-
-  std::move(video_frame_callback)
-      .Run(true,
-           chrome::mojom::VideoFrameData::NewDecodedFrame(
-               media::MojoSharedBufferVideoFrame::CreateFromYUVFrame(*frame)),
-           config);
-}
-
-void OnEncodedVideoFrameExtracted(
-    std::unique_ptr<media::VideoFrameExtractor> video_frame_extractor,
+void OnVideoFrameExtracted(
+    std::unique_ptr<VideoThumbnailParser>,
     MediaParser::ExtractVideoFrameCallback video_frame_callback,
     bool success,
-    std::vector<uint8_t> data,
-    const media::VideoDecoderConfig& config) {
-  if (!success || data.empty()) {
-    std::move(video_frame_callback)
-        .Run(false, chrome::mojom::VideoFrameData::New(), base::nullopt);
-    return;
-  }
-
-#if BUILDFLAG(USE_PROPRIETARY_CODECS) && \
-    !BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
-  // H264 currently needs to be decoded in GPU process when no software decoder
-  // is provided.
-  if (config.codec() == media::VideoCodec::kCodecH264) {
-    std::move(video_frame_callback)
-        .Run(success,
-             chrome::mojom::VideoFrameData::NewEncodedData(std::move(data)),
-             config);
-    return;
-  }
-#endif
-
-  if (config.codec() != media::VideoCodec::kCodecVP8 &&
-      config.codec() != media::VideoCodec::kCodecVP9) {
-    std::move(video_frame_callback)
-        .Run(false, chrome::mojom::VideoFrameData::New(), base::nullopt);
-    return;
-  }
-
-  // Decode with libvpx for vp8, vp9.
-  auto thumbnail_decoder = std::make_unique<media::VideoThumbnailDecoder>(
-      std::make_unique<media::VpxVideoDecoder>(), config, std::move(data));
-
-  thumbnail_decoder->Start(
-      base::BindOnce(&OnSoftwareVideoFrameDecoded, std::move(thumbnail_decoder),
-                     std::move(video_frame_callback), config));
-}
-
-void ExtractVideoFrameOnMediaThread(
-    media::DataSource* data_source,
-    MediaParser::ExtractVideoFrameCallback video_frame_callback) {
-  auto extractor = std::make_unique<media::VideoFrameExtractor>(data_source);
-  extractor->Start(base::BindOnce(&OnEncodedVideoFrameExtracted,
-                                  std::move(extractor),
-                                  std::move(video_frame_callback)));
+    chrome::mojom::VideoFrameDataPtr frame_data,
+    const base::Optional<media::VideoDecoderConfig>& config) {
+  std::move(video_frame_callback).Run(success, std::move(frame_data), config);
 }
 
 }  // namespace
 
 MediaParserAndroid::MediaParserAndroid(
     std::unique_ptr<service_manager::ServiceContextRef> service_ref)
-    : MediaParser(std::move(service_ref)),
-      media_task_runner_(
-          base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})) {}
+    : MediaParser(std::move(service_ref)) {}
 
 MediaParserAndroid::~MediaParserAndroid() = default;
 
@@ -106,12 +36,14 @@
     uint32_t total_size,
     chrome::mojom::MediaDataSourcePtr media_data_source,
     MediaParser::ExtractVideoFrameCallback video_frame_callback) {
-  data_source_ = std::make_unique<IPCDataSource>(
+  auto data_source = std::make_unique<IPCDataSource>(
       std::move(media_data_source), static_cast<int64_t>(total_size));
 
-  media_task_runner_->PostTask(
-      FROM_HERE,
-      base::BindOnce(
-          &ExtractVideoFrameOnMediaThread, data_source_.get(),
-          media::BindToCurrentLoop(std::move(video_frame_callback))));
+  // Leak |parser| on utility main thread, because |data_source| lives on main
+  // thread and is used on another thread as raw pointer. Leaked |parser| will
+  // be deleted when utility process dies or |OnVideoFrameExtracted| callback
+  // is called.
+  auto parser = std::make_unique<VideoThumbnailParser>(std::move(data_source));
+  parser->Start(base::BindOnce(&OnVideoFrameExtracted, std::move(parser),
+                               std::move(video_frame_callback)));
 }
diff --git a/chrome/services/media_gallery_util/media_parser_android.h b/chrome/services/media_gallery_util/media_parser_android.h
index 2eb33d4..369f16f8 100644
--- a/chrome/services/media_gallery_util/media_parser_android.h
+++ b/chrome/services/media_gallery_util/media_parser_android.h
@@ -10,10 +10,6 @@
 #include "base/macros.h"
 #include "chrome/services/media_gallery_util/media_parser.h"
 
-namespace media {
-class DataSource;
-}  // namespace media
-
 // The media parser on Android that provides video thumbnail generation utility.
 class MediaParserAndroid : public MediaParser {
  public:
@@ -29,11 +25,6 @@
       ExtractVideoFrameCallback video_frame_callback) override;
 
  private:
-  // The task runner to do blocking IO. The utility thread cannot be blocked.
-  scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
-
-  std::unique_ptr<media::DataSource> data_source_;
-
   DISALLOW_COPY_AND_ASSIGN(MediaParserAndroid);
 };
 
diff --git a/chrome/services/media_gallery_util/video_thumbnail_parser.cc b/chrome/services/media_gallery_util/video_thumbnail_parser.cc
new file mode 100644
index 0000000..83d57c2
--- /dev/null
+++ b/chrome/services/media_gallery_util/video_thumbnail_parser.cc
@@ -0,0 +1,114 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/services/media_gallery_util/video_thumbnail_parser.h"
+
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/optional.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "chrome/services/media_gallery_util/ipc_data_source.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/video_codecs.h"
+#include "media/base/video_thumbnail_decoder.h"
+#include "media/filters/android/video_frame_extractor.h"
+#include "media/filters/vpx_video_decoder.h"
+#include "media/media_buildflags.h"
+#include "media/mojo/common/mojo_shared_buffer_video_frame.h"
+
+namespace {
+
+// Return the video frame back to browser process. A valid |config| is
+// needed for deserialization.
+void OnSoftwareVideoFrameDecoded(
+    std::unique_ptr<media::VideoThumbnailDecoder>,
+    MediaParser::ExtractVideoFrameCallback video_frame_callback,
+    const media::VideoDecoderConfig& config,
+    scoped_refptr<media::VideoFrame> frame) {
+  DCHECK(video_frame_callback);
+
+  if (!frame) {
+    std::move(video_frame_callback)
+        .Run(false, chrome::mojom::VideoFrameData::New(), base::nullopt);
+    return;
+  }
+
+  std::move(video_frame_callback)
+      .Run(true,
+           chrome::mojom::VideoFrameData::NewDecodedFrame(
+               media::MojoSharedBufferVideoFrame::CreateFromYUVFrame(*frame)),
+           config);
+}
+
+void OnEncodedVideoFrameExtracted(
+    std::unique_ptr<media::VideoFrameExtractor> video_frame_extractor,
+    MediaParser::ExtractVideoFrameCallback video_frame_callback,
+    bool success,
+    std::vector<uint8_t> data,
+    const media::VideoDecoderConfig& config) {
+  if (!success || data.empty()) {
+    std::move(video_frame_callback)
+        .Run(false, chrome::mojom::VideoFrameData::New(), base::nullopt);
+    return;
+  }
+
+#if BUILDFLAG(USE_PROPRIETARY_CODECS) && \
+    !BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS)
+  // H264 currently needs to be decoded in GPU process when no software decoder
+  // is provided.
+  if (config.codec() == media::VideoCodec::kCodecH264) {
+    std::move(video_frame_callback)
+        .Run(success,
+             chrome::mojom::VideoFrameData::NewEncodedData(std::move(data)),
+             config);
+    return;
+  }
+#endif
+
+  if (config.codec() != media::VideoCodec::kCodecVP8 &&
+      config.codec() != media::VideoCodec::kCodecVP9) {
+    std::move(video_frame_callback)
+        .Run(false, chrome::mojom::VideoFrameData::New(), base::nullopt);
+    return;
+  }
+
+  // Decode with libvpx for vp8, vp9.
+  auto thumbnail_decoder = std::make_unique<media::VideoThumbnailDecoder>(
+      std::make_unique<media::VpxVideoDecoder>(), config, std::move(data));
+
+  thumbnail_decoder->Start(
+      base::BindOnce(&OnSoftwareVideoFrameDecoded, std::move(thumbnail_decoder),
+                     std::move(video_frame_callback), config));
+}
+
+void ExtractVideoFrameOnMediaThread(
+    media::DataSource* data_source,
+    MediaParser::ExtractVideoFrameCallback video_frame_callback) {
+  auto extractor = std::make_unique<media::VideoFrameExtractor>(data_source);
+  extractor->Start(base::BindOnce(&OnEncodedVideoFrameExtracted,
+                                  std::move(extractor),
+                                  std::move(video_frame_callback)));
+}
+
+}  // namespace
+
+VideoThumbnailParser::VideoThumbnailParser(
+    std::unique_ptr<media::DataSource> source)
+    : data_source_(std::move(source)),
+      media_task_runner_(
+          base::CreateSequencedTaskRunnerWithTraits({base::MayBlock()})) {}
+
+VideoThumbnailParser::~VideoThumbnailParser() = default;
+
+void VideoThumbnailParser::Start(
+    MediaParser::ExtractVideoFrameCallback video_frame_callback) {
+  media_task_runner_->PostTask(
+      FROM_HERE,
+      base::BindOnce(
+          &ExtractVideoFrameOnMediaThread, data_source_.get(),
+          media::BindToCurrentLoop(std::move(video_frame_callback))));
+}
diff --git a/chrome/services/media_gallery_util/video_thumbnail_parser.h b/chrome/services/media_gallery_util/video_thumbnail_parser.h
new file mode 100644
index 0000000..7b2d857
--- /dev/null
+++ b/chrome/services/media_gallery_util/video_thumbnail_parser.h
@@ -0,0 +1,40 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_SERVICES_MEDIA_GALLERY_UTIL_VIDEO_THUMBNAIL_PARSER_H_
+#define CHROME_SERVICES_MEDIA_GALLERY_UTIL_VIDEO_THUMBNAIL_PARSER_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/scoped_refptr.h"
+#include "base/sequenced_task_runner.h"
+#include "chrome/services/media_gallery_util/media_parser.h"
+
+namespace media {
+class DataSource;
+}  // namespace media
+
+// Parses a video frame. This object is created on utility process main thread,
+// and will perform actual parsing on a media thread.
+class VideoThumbnailParser {
+ public:
+  explicit VideoThumbnailParser(std::unique_ptr<media::DataSource> source);
+  ~VideoThumbnailParser();
+
+  void Start(MediaParser::ExtractVideoFrameCallback video_frame_callback);
+
+ private:
+  // The data source that provides video data. Created and destroyed on utility
+  // main thread because it's binded to mojo object. Must be used on
+  // |media_task_runner_|.
+  std::unique_ptr<media::DataSource> data_source_;
+
+  scoped_refptr<base::SequencedTaskRunner> media_task_runner_;
+
+  DISALLOW_COPY_AND_ASSIGN(VideoThumbnailParser);
+};
+
+#endif  // CHROME_SERVICES_MEDIA_GALLERY_UTIL_VIDEO_THUMBNAIL_PARSER_H_
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 69babd6..d7369ec 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -481,6 +481,7 @@
       "//chrome/browser/chromeos/login/test/https_forwarder.py",
       "//chrome/browser/resources/chromeos/wallpaper_manager/",
       "//chrome/browser/resources/chromeos/zip_archiver/",
+      "//google_apis/test/",
       "//chrome/browser/resources/chromeos/zip_archiver/test/",
       "//chromeos/test/data/",
       "//ui/file_manager/base/",
@@ -502,16 +503,55 @@
       "//ash:ash_service_resources",
       "//chrome",
       "//ui/keyboard:resources",
+      "//ui/file_manager:unit_test_data",
     ]
 
+    if (use_dbus) {
+      deps += [ "//dbus:test_support" ]
+    }
+
     if (enable_nacl) {
       data_deps += [
         "//components/nacl/loader:nacl_helper",
         "//ppapi/native_client:irt",
       ]
+
+      if (enable_nacl_nonsfi) {
+        data_deps += [ "//components/nacl/loader:helper_nonsfi" ]
+      }
     }
 
-    deps += [ "//chrome/browser/chromeos:test_support" ]
+    deps += [
+      "//ash:interactive_ui_test_support",
+      "//ash:test_support",
+      "//ash/public/interfaces:test_interfaces",
+      "//chrome/browser/chromeos:arc_test_support",
+      "//chrome/browser/chromeos:test_support",
+      "//chrome/browser/media/router:test_support",
+      "//chrome/browser/resources/chromeos/chromevox:browser_tests",
+      "//chrome/browser/resources/chromeos/select_to_speak:browser_tests",
+      "//chrome/browser/resources/chromeos/switch_access:browser_tests",
+      "//chrome/services/file_util/public/cpp:browser_tests",
+      "//chromeos:test_support",
+      "//chromeos/components/drivefs:test_support",
+      "//chromeos/dbus:test_support",
+      "//components/arc:arc_test_support",
+      "//components/exo:test_support",
+      "//components/prefs",
+      "//components/user_manager:test_support",
+      "//content/public/common:feature_h264_with_openh264_ffmpeg",
+      "//mojo/core/embedder",
+      "//services/audio/public/cpp:test_support",
+      "//services/identity/public/cpp",
+      "//services/network/public/mojom",
+      "//services/preferences/public/cpp",
+      "//services/preferences/public/mojom",
+      "//services/service_manager/public/cpp",
+      "//services/ws/public/cpp/input_devices:test_support",
+      "//ui/keyboard:test_support",
+      "//ui/login:resources",
+      "//url",
+    ]
   }
 
   # TODO(jbudorick): In progress. See crbug.com/611756
@@ -1496,6 +1536,7 @@
           "../browser/accessibility/accessibility_extension_api_browsertest.cc",
           "../browser/apps/platform_apps/api/arc_apps_private/arc_apps_private_apitest.cc",
           "../browser/extensions/api/system_display/system_display_chromeos_apitest.cc",
+          "../browser/extensions/clipboard_extension_apitest_chromeos.cc",
         ]
       }
 
@@ -1650,10 +1691,18 @@
     if (is_chromeos) {
       assert(enable_app_list)
       sources += [
+        "../browser/apps/platform_apps/app_window_interactive_uitest.cc",
+        "../browser/apps/platform_apps/app_window_interactive_uitest.h",
         "../browser/chromeos/accessibility/accessibility_manager_browsertest.cc",
+        "../browser/chromeos/accessibility/dictation_chromeos_browsertest.cc",
+        "../browser/chromeos/accessibility/magnification_controller_browsertest.cc",
         "../browser/chromeos/accessibility/magnification_manager_browsertest.cc",
+        "../browser/chromeos/accessibility/select_to_speak_browsertest.cc",
         "../browser/chromeos/accessibility/speech_monitor.cc",
         "../browser/chromeos/accessibility/speech_monitor.h",
+        "../browser/chromeos/accessibility/spoken_feedback_browsertest.cc",
+        "../browser/chromeos/accessibility/sticky_keys_browsertest.cc",
+        "../browser/chromeos/accessibility/switch_access_browsertest.cc",
         "../browser/chromeos/accessibility/touch_exploration_controller_browsertest.cc",
         "../browser/chromeos/app_mode/arc/arc_kiosk_app_manager_browsertest.cc",
         "../browser/chromeos/app_mode/kiosk_app_manager_browsertest.cc",
@@ -1717,9 +1766,12 @@
         "../browser/chromeos/first_run/drive_first_run_browsertest.cc",
         "../browser/chromeos/first_run/goodies_displayer_browsertest.cc",
         "../browser/chromeos/input_method/input_method_engine_browsertests.cc",
+        "../browser/chromeos/input_method/textinput_browsertest.cc",
+        "../browser/chromeos/input_method/textinput_surroundingtext_browsertest.cc",
         "../browser/chromeos/input_method/textinput_test_helper.cc",
         "../browser/chromeos/input_method/textinput_test_helper.h",
         "../browser/chromeos/lock_screen_apps/note_taking_browsertest.cc",
+        "../browser/chromeos/login/active_directory_login_browsertest.cc",
         "../browser/chromeos/login/active_directory_test_helper.cc",
         "../browser/chromeos/login/active_directory_test_helper.h",
         "../browser/chromeos/login/auto_launched_kiosk_browsertest.cc",
@@ -1742,13 +1794,19 @@
         "../browser/chromeos/login/lock/screen_locker_browsertest.cc",
         "../browser/chromeos/login/lock/screen_locker_tester.cc",
         "../browser/chromeos/login/lock/screen_locker_tester.h",
+        "../browser/chromeos/login/login_auth_recorder_browsertest.cc",
+        "../browser/chromeos/login/login_browsertest.cc",
         "../browser/chromeos/login/login_manager_test.cc",
         "../browser/chromeos/login/login_manager_test.h",
         "../browser/chromeos/login/login_screen_policy_browsertest.cc",
+        "../browser/chromeos/login/login_ui_browsertest.cc",
+        "../browser/chromeos/login/login_ui_hide_supervised_users_browsertest.cc",
         "../browser/chromeos/login/login_ui_keyboard_browsertest.cc",
         "../browser/chromeos/login/login_utils_browsertest.cc",
         "../browser/chromeos/login/mixin_based_in_process_browser_test.cc",
         "../browser/chromeos/login/mixin_based_in_process_browser_test.h",
+        "../browser/chromeos/login/oobe_browsertest.cc",
+        "../browser/chromeos/login/oobe_interactive_ui_test.cc",
         "../browser/chromeos/login/oobe_localization_browsertest.cc",
         "../browser/chromeos/login/proxy_auth_dialog_browsertest.cc",
         "../browser/chromeos/login/quick_unlock/pin_migration_browsertest.cc",
@@ -1771,6 +1829,18 @@
         "../browser/chromeos/login/screens/network_screen_browsertest.cc",
         "../browser/chromeos/login/screens/update_screen_browsertest.cc",
         "../browser/chromeos/login/screens/user_selection_screen_browsertest.cc",
+        "../browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric.h",
+        "../browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric_cpu.cpp",
+        "../browser/chromeos/login/screenshot_testing/SkImageDiffer.cpp",
+        "../browser/chromeos/login/screenshot_testing/SkImageDiffer.h",
+        "../browser/chromeos/login/screenshot_testing/SkPMetric.cpp",
+        "../browser/chromeos/login/screenshot_testing/SkPMetric.h",
+        "../browser/chromeos/login/screenshot_testing/SkPMetricUtil_gen.h",
+        "../browser/chromeos/login/screenshot_testing/login_screen_areas.h",
+        "../browser/chromeos/login/screenshot_testing/screenshot_tester.cc",
+        "../browser/chromeos/login/screenshot_testing/screenshot_tester.h",
+        "../browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.cc",
+        "../browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.h",
         "../browser/chromeos/login/session/chrome_session_manager_browsertest.cc",
         "../browser/chromeos/login/session_login_browsertest.cc",
         "../browser/chromeos/login/signin/device_id_browsertest.cc",
@@ -1832,6 +1902,7 @@
         "../browser/chromeos/shutdown_policy_browsertest.cc",
         "../browser/chromeos/system/device_disabling_browsertest.cc",
         "../browser/chromeos/system/tray_accessibility_browsertest.cc",
+        "../browser/download/notification/download_notification_interactive_uitest.cc",
         "../browser/drive/drive_notification_manager_factory_browsertest.cc",
         "../browser/extensions/api/certificate_provider/certificate_provider_apitest.cc",
         "../browser/extensions/api/networking_private/networking_private_apitest.cc",
@@ -1859,10 +1930,12 @@
         "../browser/ui/ash/shelf_browsertest.cc",
         "../browser/ui/ash/system_tray_client_browsertest.cc",
         "../browser/ui/ash/system_tray_tray_cast_browsertest_media_router_chromeos.cc",
+        "../browser/ui/ash/tab_scrubber_browsertest.cc",
         "../browser/ui/ash/tablet_mode_page_behavior_browsertest.cc",
         "../browser/ui/ash/time_to_first_present_recorder_browsertest.cc",
         "../browser/ui/ash/volume_controller_browsertest.cc",
         "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
+        "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_interactive_uitest.cc",
         "../browser/ui/views/arc_app_dialog_view_browsertest.cc",
         "../browser/ui/views/crostini/crostini_browser_test_util.cc",
         "../browser/ui/views/crostini/crostini_browser_test_util.h",
@@ -1873,6 +1946,7 @@
         "../browser/ui/views/extensions/extension_dialog_bounds_browsertest.cc",
         "../browser/ui/views/frame/browser_frame_ash_browsertest.cc",
         "../browser/ui/views/frame/browser_non_client_frame_view_ash_browsertest.cc",
+        "../browser/ui/views/frame/hosted_app_ash_interactive_ui_test.cc",
         "../browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc",
         "../browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc",
         "../browser/ui/views/plugin_vm/plugin_vm_launcher_view_browsertest.cc",
@@ -1884,13 +1958,32 @@
         "../browser/ui/webui/chromeos/login/discover/modules/discover_module_sync_files_test.cc",
         "../browser/ui/webui/chromeos/login/discover/wait_for_did_start_navigate.cc",
         "../browser/ui/webui/chromeos/login/discover/wait_for_did_start_navigate.h",
+        "../browser/ui/webui/chromeos/login/oobe_display_chooser_browsertest.cc",
         "../browser/ui/webui/chromeos/system_web_dialog_browsertest.cc",
         "../browser/ui/webui/settings/chromeos/device_power_handler_browsertest.cc",
+        "../browser/ui/window_sizer/window_sizer_ash_uitest.cc",
+        "//ash/accelerators/accelerator_interactive_uitest_chromeos.cc",
+        "//ash/app_list/app_list_interactive_uitest.cc",
+        "//ash/drag_drop/drag_drop_interactive_uitest.cc",
+        "//ash/wm/native_cursor_manager_ash_interactive_uitest.cc",
+        "base/interactive_test_utils.cc",
+        "base/interactive_test_utils.h",
+        "base/interactive_test_utils_aura.cc",
+        "base/interactive_test_utils_aura.h",
+        "base/interactive_test_utils_common_views.cc",
+        "base/interactive_test_utils_views.cc",
+        "data/chromeos/service_login.html",
       ]
       if (enable_cros_assistant) {
         sources +=
             [ "../browser/ui/ash/assistant/assistant_context_browsertest.cc" ]
       }
+      if (is_chrome_branded) {
+        sources += [
+          # The screen this test is checking exists in official build only.
+          "../browser/chromeos/login/sync_consent_interactive_ui_test.cc",
+        ]
+      }
       sources -= [
         "../../apps/load_and_launch_browsertest.cc",
         "../browser/policy/policy_startup_browsertest.cc",
@@ -1913,36 +2006,6 @@
         # chromeos does not support machine level user cloud policies
         "../browser/policy/cloud/machine_level_user_cloud_policy_browsertest.cc",
       ]
-      deps += [
-        "//ash:test_support",
-        "//ash/public/interfaces:test_interfaces",
-        "//chrome/browser/chromeos:arc_test_support",
-        "//chrome/browser/chromeos:test_support",
-        "//chrome/browser/resources/chromeos/chromevox:browser_tests",
-        "//chrome/browser/resources/chromeos/select_to_speak:browser_tests",
-        "//chrome/browser/resources/chromeos/switch_access:browser_tests",
-        "//chrome/services/file_util/public/cpp:browser_tests",
-        "//chromeos/components/drivefs:test_support",
-        "//components/arc:arc_test_support",
-        "//components/exo:test_support",
-        "//components/prefs",
-        "//components/user_manager:test_support",
-        "//content/public/common:feature_h264_with_openh264_ffmpeg",
-        "//services/audio/public/cpp:test_support",
-        "//services/identity/public/cpp",
-        "//services/network/public/mojom",
-        "//services/preferences/public/cpp",
-        "//services/preferences/public/mojom",
-        "//services/service_manager/public/cpp",
-        "//services/ws/public/cpp/input_devices:test_support",
-        "//ui/keyboard:test_support",
-        "//ui/login:resources",
-        "//url",
-      ]
-      data_deps += [ "//ui/file_manager:unit_test_data" ]
-      if (use_dbus) {
-        deps += [ "//dbus:test_support" ]
-      }
     } else {  # !is_chromeos
       sources -= [
         "../browser/invalidation/deprecated_profile_invalidation_provider_factory_browsertest.cc",
@@ -2268,8 +2331,8 @@
     "//testing/xvfb.py",
     "//testing/scripts/run_telemetry_as_googletest.py",
 
-    # For smoke testing run_telemetry_benchmark_as_googletest
-    "//testing/scripts/run_telemetry_benchmark_as_googletest.py",
+    # For smoke testing run_performance_tests.py
+    "//testing/scripts/run_performance_tests.py",
 
     # For tests in tools/perf/process_perf_results_unittest.py
     "//build/android/pylib/",
@@ -2294,7 +2357,6 @@
     # Needed for isolate script to execute.
     "//testing/scripts/common.py",
     "//testing/xvfb.py",
-    "//testing/scripts/run_telemetry_benchmark_as_googletest.py",
   ]
 }
 
@@ -4778,7 +4840,6 @@
       "../browser/extensions/api/tabs/tabs_interactive_test.cc",
       "../browser/extensions/chrome_extension_test_notification_observer.cc",
       "../browser/extensions/chrome_extension_test_notification_observer.h",
-      "../browser/extensions/clipboard_extension_apitest_chromeos.cc",
       "../browser/extensions/extension_apitest.cc",
       "../browser/extensions/extension_browsertest.cc",
       "../browser/extensions/extension_commands_global_registry_apitest.cc",
@@ -5034,97 +5095,13 @@
     }
 
     if (is_chromeos) {
-      sources += [
-        "../browser/chromeos/accessibility/dictation_chromeos_browsertest.cc",
-        "../browser/chromeos/accessibility/magnification_controller_browsertest.cc",
-        "../browser/chromeos/accessibility/select_to_speak_browsertest.cc",
-        "../browser/chromeos/accessibility/spoken_feedback_browsertest.cc",
-        "../browser/chromeos/accessibility/sticky_keys_browsertest.cc",
-        "../browser/chromeos/accessibility/switch_access_browsertest.cc",
-        "../browser/chromeos/input_method/textinput_browsertest.cc",
-        "../browser/chromeos/input_method/textinput_surroundingtext_browsertest.cc",
-        "../browser/chromeos/input_method/textinput_test_helper.cc",
-        "../browser/chromeos/input_method/textinput_test_helper.h",
-        "../browser/chromeos/login/active_directory_login_browsertest.cc",
-        "../browser/chromeos/login/active_directory_test_helper.cc",
-        "../browser/chromeos/login/active_directory_test_helper.h",
-        "../browser/chromeos/login/login_auth_recorder_browsertest.cc",
-        "../browser/chromeos/login/login_browsertest.cc",
-        "../browser/chromeos/login/login_manager_test.cc",
-        "../browser/chromeos/login/login_manager_test.h",
-        "../browser/chromeos/login/login_ui_browsertest.cc",
-        "../browser/chromeos/login/login_ui_hide_supervised_users_browsertest.cc",
-        "../browser/chromeos/login/mixin_based_in_process_browser_test.cc",
-        "../browser/chromeos/login/mixin_based_in_process_browser_test.h",
-        "../browser/chromeos/login/oobe_browsertest.cc",
-        "../browser/chromeos/login/oobe_interactive_ui_test.cc",
-        "../browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric.h",
-        "../browser/chromeos/login/screenshot_testing/SkDiffPixelsMetric_cpu.cpp",
-        "../browser/chromeos/login/screenshot_testing/SkImageDiffer.cpp",
-        "../browser/chromeos/login/screenshot_testing/SkImageDiffer.h",
-        "../browser/chromeos/login/screenshot_testing/SkPMetric.cpp",
-        "../browser/chromeos/login/screenshot_testing/SkPMetric.h",
-        "../browser/chromeos/login/screenshot_testing/SkPMetricUtil_gen.h",
-        "../browser/chromeos/login/screenshot_testing/login_screen_areas.h",
-        "../browser/chromeos/login/screenshot_testing/screenshot_tester.cc",
-        "../browser/chromeos/login/screenshot_testing/screenshot_tester.h",
-        "../browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.cc",
-        "../browser/chromeos/login/screenshot_testing/screenshot_testing_mixin.h",
-        "../browser/chromeos/login/test/https_forwarder.cc",
-        "../browser/chromeos/login/test/https_forwarder.h",
-        "../browser/chromeos/login/test/oobe_base_test.cc",
-        "../browser/chromeos/login/test/oobe_base_test.h",
-        "../browser/download/notification/download_notification_interactive_uitest.cc",
-        "../browser/ui/ash/tab_scrubber_browsertest.cc",
-        "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_interactive_uitest.cc",
-        "../browser/ui/views/frame/hosted_app_ash_interactive_ui_test.cc",
-        "../browser/ui/webui/chromeos/login/oobe_display_chooser_browsertest.cc",
-        "../browser/ui/window_sizer/window_sizer_ash_uitest.cc",
-        "//ash/accelerators/accelerator_interactive_uitest_chromeos.cc",
-        "//ash/app_list/app_list_interactive_uitest.cc",
-        "//ash/drag_drop/drag_drop_interactive_uitest.cc",
-        "//ash/wm/native_cursor_manager_ash_interactive_uitest.cc",
-        "data/chromeos/service_login.html",
-      ]
-      if (is_chrome_branded) {
-        sources += [
-          # The screen this test is checking exists in official build only.
-          "../browser/chromeos/login/sync_consent_interactive_ui_test.cc",
-        ]
-      }
+      deps += [ "//chrome/browser/media/router:test_support" ]
       sources -= [
         "../browser/ui/signin_view_controller_interactive_uitest.cc",
 
         # Use only the _chromeos version on Ash / Chrome OS.
         "base/view_event_test_platform_part_default.cc",
       ]
-      deps += [
-        "//ash:interactive_ui_test_support",
-        "//ash/public/interfaces:test_interfaces",
-        "//chrome/browser/chromeos:test_support",
-        "//chrome/browser/media/router:test_support",
-        "//chromeos/dbus:test_support",
-        "//mojo/core/embedder",
-      ]
-
-      data += [
-        "//chrome/browser/chromeos/login/test/https_forwarder.py",
-        "//google_apis/test/",
-        "$root_out_dir/resources/chromeos/",
-      ]
-
-      data_deps += [ "//ui/keyboard:resources" ]
-
-      if (enable_nacl) {
-        data_deps += [
-          "//components/nacl/loader:nacl_helper",
-          "//ppapi/native_client:irt",
-        ]
-
-        if (enable_nacl_nonsfi) {
-          data_deps += [ "//components/nacl/loader:helper_nonsfi" ]
-        }
-      }
     } else {  # ! is_chromeos
       # Non-ChromeOS notifications tests.
       sources += [
@@ -5396,6 +5373,8 @@
       "../browser/sync/test/integration/search_engines_helper.h",
       "../browser/sync/test/integration/secondary_account_helper.cc",
       "../browser/sync/test/integration/secondary_account_helper.h",
+      "../browser/sync/test/integration/send_tab_to_self_helper.cc",
+      "../browser/sync/test/integration/send_tab_to_self_helper.h",
       "../browser/sync/test/integration/session_hierarchy_match_checker.cc",
       "../browser/sync/test/integration/session_hierarchy_match_checker.h",
       "../browser/sync/test/integration/sessions_helper.cc",
@@ -5501,6 +5480,7 @@
       "../browser/sync/test/integration/single_client_preferences_sync_test.cc",
       "../browser/sync/test/integration/single_client_search_engines_sync_test.cc",
       "../browser/sync/test/integration/single_client_secondary_account_sync_test.cc",
+      "../browser/sync/test/integration/single_client_send_tab_to_self_sync_test.cc",
       "../browser/sync/test/integration/single_client_sessions_sync_test.cc",
       "../browser/sync/test/integration/single_client_standalone_transport_sync_test.cc",
       "../browser/sync/test/integration/single_client_themes_sync_test.cc",
@@ -5524,6 +5504,7 @@
       "../browser/sync/test/integration/two_client_polling_sync_test.cc",
       "../browser/sync/test/integration/two_client_preferences_sync_test.cc",
       "../browser/sync/test/integration/two_client_search_engines_sync_test.cc",
+      "../browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc",
       "../browser/sync/test/integration/two_client_sessions_sync_test.cc",
       "../browser/sync/test/integration/two_client_themes_sync_test.cc",
       "../browser/sync/test/integration/two_client_typed_urls_sync_test.cc",
diff --git a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java
index 379e898f..eb94e7a 100644
--- a/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java
+++ b/chrome/test/android/javatests/src/org/chromium/chrome/test/util/browser/suggestions/FakeSuggestionsSource.java
@@ -11,7 +11,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.base.ObserverList;
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.chrome.browser.ntp.cards.SuggestionsCategoryInfo;
 import org.chromium.chrome.browser.ntp.snippets.CategoryInt;
@@ -19,6 +19,7 @@
 import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
 import org.chromium.chrome.browser.ntp.snippets.SuggestionsSource;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -244,7 +245,7 @@
     public void fetchSuggestionImage(
             final SnippetArticle suggestion, final Callback<Bitmap> callback) {
         if (mThumbnails.containsKey(suggestion.mIdWithinCategory)) {
-            ThreadUtils.postOnUiThread(
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT,
                     () -> callback.onResult(mThumbnails.get(suggestion.mIdWithinCategory)));
         }
     }
@@ -253,7 +254,8 @@
     public void fetchSuggestionFavicon(final SnippetArticle suggestion, int minimumSizePx,
             int desiredSizePx, final Callback<Bitmap> callback) {
         final Bitmap favicon = getFaviconForId(suggestion.mIdWithinCategory);
-        if (favicon != null) ThreadUtils.postOnUiThread(() -> callback.onResult(favicon));
+        if (favicon != null)
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> callback.onResult(favicon));
     }
 
     private Bitmap getFaviconForId(String id) {
diff --git a/chrome/test/base/interactive_ui_tests_main.cc b/chrome/test/base/interactive_ui_tests_main.cc
index 5e296bb..6774007 100644
--- a/chrome/test/base/interactive_ui_tests_main.cc
+++ b/chrome/test/base/interactive_ui_tests_main.cc
@@ -38,29 +38,19 @@
 
     ChromeTestSuite::Initialize();
 
-    // Only allow ui_controls to be used in interactive_ui_tests, since they
-    // depend on focus and can't be sharded.
-    ui_controls::EnableUIControls();
-
 #if defined(OS_CHROMEOS)
     // Do not InstallUIControlsAura in ChromeOS, it will be installed in
     // InProcessBrowserTest::PreRunTestOnMainThread().
-#elif defined(USE_AURA)
-#if defined(OS_WIN)
+#elif defined(OS_WIN)
     com_initializer_.reset(new base::win::ScopedCOMInitializer());
-#endif
-
-#if defined(OS_LINUX)
-#if defined(USE_OZONE)
-    NOTIMPLEMENTED();
-#else
+    ui_controls::InstallUIControlsAura(
+        aura::test::CreateUIControlsAura(nullptr));
+#elif defined(OS_LINUX) && !defined(USE_OZONE)
     ui_controls::InstallUIControlsAura(
         views::test::CreateUIControlsDesktopAura());
-#endif  // defined(USE_OZONE)
 #else
-    ui_controls::InstallUIControlsAura(aura::test::CreateUIControlsAura(NULL));
-#endif  // defined(OS_LINUX)
-#endif  // defined(USE_AURA)
+    ui_controls::EnableUIControls();
+#endif
   }
 
   void Shutdown() override {
diff --git a/chrome/test/chromedriver/chrome/chrome_impl.cc b/chrome/test/chromedriver/chrome/chrome_impl.cc
index 657f2f2..9fa588f 100644
--- a/chrome/test/chromedriver/chrome/chrome_impl.cc
+++ b/chrome/test/chromedriver/chrome/chrome_impl.cc
@@ -319,7 +319,7 @@
   if (status.IsError())
     return status;
 
-  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(1000));
+  base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
   std::string state;
   if (!bounds->GetString("windowState", &state))
     return Status(kOk);
diff --git a/chrome/test/chromedriver/test/test_expectations b/chrome/test/chromedriver/test/test_expectations
index 2ab5348..41d864ab 100644
--- a/chrome/test/chromedriver/test/test_expectations
+++ b/chrome/test/chromedriver/test/test_expectations
@@ -40,6 +40,8 @@
 _OS_NEGATIVE_FILTER['linux'] = [
 ]
 _OS_NEGATIVE_FILTER['mac'] = [
+    # https://bugs.chromium.org/p/chromedriver/issues/detail?id=2663
+    'WindowTest.testSetsThePositionOfTheCurrentWindow',
 ]
 
 _SPECIFIC_OS_REVISION_NEGATIVE_FILTER = {}
diff --git a/chrome/test/data/webui/print_preview/destination_select_test.js b/chrome/test/data/webui/print_preview/destination_select_test.js
index c5f7cdd..3048e10 100644
--- a/chrome/test/data/webui/print_preview/destination_select_test.js
+++ b/chrome/test/data/webui/print_preview/destination_select_test.js
@@ -14,6 +14,7 @@
     KioskModeSelectsFirstPrinter: 'kiosk mode selects first printer',
     NoPrintersShowsError: 'no printers shows error',
     UnreachableRecentCloudPrinter: 'unreachable recent cloud printer',
+    RecentSaveAsPdf: 'recent save as pdf',
   };
 
   const suiteName = 'DestinationSelectTests';
@@ -59,6 +60,7 @@
       print_preview.NativeLayer.setInstance(nativeLayer);
       const cloudPrintInterface = new print_preview.CloudPrintInterfaceStub();
       cloudprint.setCloudPrintInterfaceForTesting(cloudPrintInterface);
+      print_preview.DestinationStore.AUTO_SELECT_TIMEOUT_ = 0;
       PolymerTest.clearBody();
       page = document.createElement('print-preview-app');
       document.body.appendChild(page);
@@ -80,9 +82,10 @@
      * Checks that a printer is displayed to the user with the name given
      * by |printerName|.
      * @param {string} printerName The printer name that should be displayed.
+     * @param {boolean} disabled Whether the dropdown should be disabled.
      * @return {!Promise} Promise that resolves when checks are complete.
      */
-    function assertPrinterDisplay(printerName) {
+    function assertPrinterDisplay(printerName, disabled) {
       const destinationSettings = page.$$('print-preview-destination-settings');
       const destinationSelect = destinationSettings.$.destinationSelect;
 
@@ -91,7 +94,7 @@
         // Check that the throbber is hidden and the dropdown is shown.
         assertTrue(destinationSettings.$$('.throbber-container').hidden);
         assertFalse(destinationSelect.hidden);
-        assertFalse(destinationSelect.disabled);
+        assertEquals(disabled, destinationSelect.disabled);
 
         const options = destinationSelect.shadowRoot.querySelectorAll('option');
         const selectedOption =
@@ -117,7 +120,7 @@
         assertEquals('ID1', args.destinationId);
         assertEquals(print_preview.PrinterType.LOCAL, args.type);
         assertEquals('ID1', page.destination_.id);
-        return assertPrinterDisplay('One');
+        return assertPrinterDisplay('One', false);
       });
     });
 
@@ -142,7 +145,7 @@
             assertEquals('ID1', args.destinationId);
             assertEquals(print_preview.PrinterType.LOCAL, args.type);
             assertEquals('ID1', page.destination_.id);
-            return assertPrinterDisplay('One');
+            return assertPrinterDisplay('One', false);
           })
           .then(function() {
             // Verify the correct printers are marked as recent in the store.
@@ -213,7 +216,7 @@
         assertEquals('ID4', args.destinationId);
         assertEquals(print_preview.PrinterType.LOCAL, args.type);
         assertEquals('ID4', page.destination_.id);
-        return assertPrinterDisplay('Four');
+        return assertPrinterDisplay('Four', false);
       });
     });
 
@@ -247,7 +250,7 @@
             // Need to load FooDevice as the printer, since it is the system
             // default.
             assertEquals('FooDevice', page.destination_.id);
-            assertPrinterDisplay('FooName');
+            assertPrinterDisplay('FooName', false);
           });
     });
 
@@ -268,7 +271,7 @@
         assertEquals(destinations[0].id, args.destinationId);
         assertEquals(print_preview.PrinterType.LOCAL, args.type);
         assertEquals(destinations[0].id, page.destination_.id);
-        return assertPrinterDisplay(destinations[0].displayName);
+        return assertPrinterDisplay(destinations[0].displayName, false);
       });
     });
 
@@ -324,9 +327,38 @@
         assertEquals('FooDevice', args.destinationId);
         assertEquals(print_preview.PrinterType.LOCAL, args.type);
         assertEquals('FooDevice', page.destination_.id);
-        return assertPrinterDisplay('FooName');
+        return assertPrinterDisplay('FooName', false);
       });
     });
+
+    /**
+     * Tests that if the user has a recent destination that is already in the
+     * store (PDF printer), the DestinationStore does not try to select a
+     * printer again later. Regression test for https://crbug.com/927162.
+     */
+    test(assert(TestNames.RecentSaveAsPdf), function() {
+      const pdfPrinter = print_preview_test_utils.getSaveAsPdfDestination();
+      const recentDestination = print_preview.makeRecentDestination(pdfPrinter);
+      initialSettings.serializedAppStateStr = JSON.stringify({
+        version: 2,
+        recentDestinations: [recentDestination],
+      });
+
+      return setInitialSettings()
+          .then(function() {
+            assertEquals(print_preview_new.State.READY, page.state);
+            assertPrinterDisplay('Save as PDF', false);
+            // Simulate setting a bad ticket value.
+            page.$.state.transitTo(print_preview_new.State.INVALID_TICKET);
+            return new Promise(resolve => setTimeout(resolve));
+          })
+          .then(function() {
+            // Should still have Save as PDF. Dropdown is disabled due to
+            // invalid ticket.
+            assertPrinterDisplay('Save as PDF', true);
+            assertEquals(print_preview_new.State.INVALID_TICKET, page.state);
+          });
+    });
   });
 
   return {
diff --git a/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js b/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
index ef78621..3605c28 100644
--- a/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
+++ b/chrome/test/data/webui/print_preview/new_print_preview_ui_browsertest.js
@@ -662,6 +662,10 @@
           destination_select_test.TestNames.UnreachableRecentCloudPrinter);
     });
 
+TEST_F('PrintPreviewDestinationSelectTest', 'RecentSaveAsPdf', function() {
+  this.runMochaTest(destination_select_test.TestNames.RecentSaveAsPdf);
+});
+
 PrintPreviewDestinationDialogTest = class extends NewPrintPreviewTest {
   /** @override */
   get browsePreload() {
diff --git a/chrome/test/data/webui/settings/bluetooth_page_tests.js b/chrome/test/data/webui/settings/bluetooth_page_tests.js
index a6fcd05..2672009 100644
--- a/chrome/test/data/webui/settings/bluetooth_page_tests.js
+++ b/chrome/test/data/webui/settings/bluetooth_page_tests.js
@@ -27,30 +27,37 @@
   /** @type {BluetoothPrivate} */
   let bluetoothPrivateApi_;
 
-  /** @type {!Array<!chrome.bluetooth.Device>} */ const fakeDevices_ = [
-    {
-      address: '10:00:00:00:00:01',
-      name: 'FakePairedDevice1',
-      paired: true,
-      connected: true,
-    },
-    {
-      address: '10:00:00:00:00:02',
-      name: 'FakePairedDevice2',
-      paired: true,
-      connected: false,
-    },
-    {
-      address: '00:00:00:00:00:01',
-      name: 'FakeUnpairedDevice1',
-      paired: false,
-    },
-    {
-      address: '00:00:00:00:00:02',
-      name: 'FakeUnpairedDevice2',
-      paired: false,
-    },
-  ];
+  /** @type {!chrome.bluetooth.Device} */
+  let fakeUnpairedDevice1 = {
+    address: '00:00:00:00:00:01',
+    name: 'FakeUnpairedDevice1',
+    paired: false,
+    connected: false,
+  };
+
+  /** @type {!chrome.bluetooth.Device} */
+  let fakeUnpairedDevice2 = {
+    address: '00:00:00:00:00:02',
+    name: 'FakeUnpairedDevice2',
+    paired: false,
+    connected: false,
+  };
+
+  /** @type {!chrome.bluetooth.Device} */
+  let fakePairedDevice1 = {
+    address: '10:00:00:00:00:01',
+    name: 'FakePairedDevice1',
+    paired: true,
+    connected: true,
+  };
+
+  /** @type {!chrome.bluetooth.Device} */
+  let fakePairedDevice2 = {
+    address: '10:00:00:00:00:02',
+    name: 'FakePairedDevice2',
+    paired: true,
+    connected: false,
+  };
 
   suiteSetup(function() {
     loadTimeData.overrideValues({
@@ -148,57 +155,11 @@
       });
     }
 
-    test('paired device list', async function() {
-      const pairedContainer = subpage.$.pairedContainer;
-      assertTrue(!!pairedContainer);
-      assertTrue(pairedContainer.hidden);
-      assertFalse(subpage.$.noPairedDevices.hidden);
-
-      bluetoothApi_.setDevicesForTest(fakeDevices_);
-
-      await waitForListUpdateTimeout();
-
-      Polymer.dom.flush();
-      assertEquals(4, subpage.deviceList_.length);
-      assertEquals(2, subpage.pairedDeviceList_.length);
-      assertTrue(subpage.$.noPairedDevices.hidden);
-
-      const ironList = subpage.$.pairedDevices;
-      assertTrue(!!ironList);
-      ironList.notifyResize();
-      Polymer.dom.flush();
-      const devices = ironList.querySelectorAll('bluetooth-device-list-item');
-      assertEquals(2, devices.length);
-      assertTrue(devices[0].device.connected);
-      assertFalse(devices[1].device.connected);
-    });
-
-    test('unpaired device list', async function() {
-      const unpairedContainer = subpage.$.unpairedContainer;
-      assertTrue(!!unpairedContainer);
-      assertTrue(unpairedContainer.hidden);
-      assertFalse(subpage.$.noUnpairedDevices.hidden);
-
-      bluetoothApi_.setDevicesForTest(fakeDevices_);
-      await waitForListUpdateTimeout();
-
-      Polymer.dom.flush();
-      assertEquals(4, subpage.deviceList_.length);
-      assertEquals(2, subpage.unpairedDeviceList_.length);
-      assertTrue(subpage.$.noUnpairedDevices.hidden);
-
-      const ironList = subpage.$.unpairedDevices;
-      assertTrue(!!ironList);
-      ironList.notifyResize();
-      Polymer.dom.flush();
-      const devices = ironList.querySelectorAll('bluetooth-device-list-item');
-      assertEquals(2, devices.length);
-      assertFalse(devices[0].device.paired);
-      assertFalse(devices[1].device.paired);
-    });
-
     test('pair device', async function() {
-      bluetoothApi_.setDevicesForTest(fakeDevices_);
+      bluetoothApi_.setDevicesForTest([
+        fakeUnpairedDevice1, fakeUnpairedDevice2, fakePairedDevice1,
+        fakePairedDevice2
+      ]);
       await waitForListUpdateTimeout();
 
       Polymer.dom.flush();
@@ -216,7 +177,10 @@
     });
 
     test('pair dialog', async function() {
-      bluetoothApi_.setDevicesForTest(fakeDevices_);
+      bluetoothApi_.setDevicesForTest([
+        fakeUnpairedDevice1, fakeUnpairedDevice2, fakePairedDevice1,
+        fakePairedDevice2
+      ]);
       await waitForListUpdateTimeout();
 
       Polymer.dom.flush();
@@ -230,5 +194,117 @@
       assertTrue(dialog.$.dialog.open);
     });
 
+    suite('Device List', function() {
+      function deviceList() {
+        return subpage.deviceList_;
+      }
+
+      function unpairedDeviceList() {
+        return subpage.unpairedDeviceList_;
+      }
+
+      function pairedDeviceList() {
+        return subpage.pairedDeviceList_;
+      }
+
+      let unpairedContainer;
+      let unpairedDeviceIronList;
+
+      let pairedContainer;
+      let pairedDeviceIronList;
+
+      setup(function() {
+        unpairedContainer = subpage.$.unpairedContainer;
+        assertTrue(!!unpairedContainer);
+        assertTrue(unpairedContainer.hidden);
+        unpairedDeviceIronList = subpage.$.unpairedDevices;
+        assertTrue(!!unpairedDeviceIronList);
+
+        pairedContainer = subpage.$.pairedContainer;
+        assertTrue(!!pairedContainer);
+        assertTrue(pairedContainer.hidden);
+        pairedDeviceIronList = subpage.$.pairedDevices;
+        assertTrue(!!pairedDeviceIronList);
+      });
+
+      test('Unpaired devices: devices added', async function() {
+        bluetoothApi_.setDevicesForTest(
+            [fakeUnpairedDevice1, fakeUnpairedDevice2]);
+        await waitForListUpdateTimeout();
+        Polymer.dom.flush();
+
+        assertEquals(2, deviceList().length);
+        assertEquals(2, unpairedDeviceList().length);
+        assertEquals(0, pairedDeviceList().length);
+        assertTrue(subpage.$.noUnpairedDevices.hidden);
+        assertFalse(subpage.$.noPairedDevices.hidden);
+
+        unpairedDeviceIronList.notifyResize();
+        Polymer.dom.flush();
+
+        const devices = unpairedDeviceIronList.querySelectorAll(
+            'bluetooth-device-list-item');
+        assertEquals(2, devices.length);
+        assertFalse(devices[0].device.paired);
+        assertFalse(devices[1].device.paired);
+      });
+
+      test('Paired devices: devices added', async function() {
+        bluetoothApi_.setDevicesForTest([fakePairedDevice1, fakePairedDevice2]);
+        await waitForListUpdateTimeout();
+        Polymer.dom.flush();
+
+        assertEquals(2, deviceList().length);
+        assertEquals(0, unpairedDeviceList().length);
+        assertEquals(2, pairedDeviceList().length);
+        assertFalse(subpage.$.noUnpairedDevices.hidden);
+        assertTrue(subpage.$.noPairedDevices.hidden);
+
+        pairedDeviceIronList.notifyResize();
+        Polymer.dom.flush();
+
+        const devices =
+            pairedDeviceIronList.querySelectorAll('bluetooth-device-list-item');
+        assertEquals(2, devices.length);
+        assertTrue(devices[0].device.paired);
+        assertTrue(devices[0].device.connected);
+        assertTrue(devices[1].device.paired);
+        assertFalse(devices[1].device.connected);
+      });
+
+      test('Unpaired and paired devices: devices added', async function() {
+        bluetoothApi_.setDevicesForTest([
+          fakeUnpairedDevice1, fakeUnpairedDevice2, fakePairedDevice1,
+          fakePairedDevice2
+        ]);
+
+        await waitForListUpdateTimeout();
+        Polymer.dom.flush();
+
+        assertEquals(4, deviceList().length);
+        assertEquals(2, unpairedDeviceList().length);
+        assertEquals(2, pairedDeviceList().length);
+        assertTrue(subpage.$.noUnpairedDevices.hidden);
+        assertTrue(subpage.$.noPairedDevices.hidden);
+
+        pairedDeviceIronList.notifyResize();
+        unpairedDeviceIronList.notifyResize();
+        Polymer.dom.flush();
+
+        const unpairedDevices = unpairedDeviceIronList.querySelectorAll(
+            'bluetooth-device-list-item');
+        assertEquals(2, unpairedDevices.length);
+        assertFalse(unpairedDevices[0].device.paired);
+        assertFalse(unpairedDevices[1].device.paired);
+
+        const pairedDevices =
+            pairedDeviceIronList.querySelectorAll('bluetooth-device-list-item');
+        assertEquals(2, pairedDevices.length);
+        assertTrue(pairedDevices[0].device.paired);
+        assertTrue(pairedDevices[0].device.connected);
+        assertTrue(pairedDevices[1].device.paired);
+        assertFalse(pairedDevices[1].device.connected);
+      });
+    });
   });
 });
diff --git a/components/arc/metrics/arc_metrics_service.cc b/components/arc/metrics/arc_metrics_service.cc
index 68eae7da..f89c196 100644
--- a/components/arc/metrics/arc_metrics_service.cc
+++ b/components/arc/metrics/arc_metrics_service.cc
@@ -279,7 +279,18 @@
     return;
   }
 
-  // Retrieve ARC start time from session manager.
+  if (IsArcVmEnabled()) {
+    // For VM builds, do not call into session_manager since we don't use it
+    // for the builds. Using base::TimeTicks() is fine for now because 1) the
+    // clocks in host and guest are not synchronized, and 2) the guest does not
+    // support mini container.
+    // TODO(yusukes): Once the guest supports mini container (details TBD), we
+    // should have the guest itself report the timing of the upgrade.
+    OnArcStartTimeRetrieved(std::move(events), boot_type, base::TimeTicks());
+    return;
+  }
+
+  // Retrieve ARC full container's start time from session manager.
   chromeos::SessionManagerClient* session_manager_client =
       chromeos::DBusThreadManager::Get()->GetSessionManagerClient();
   session_manager_client->GetArcStartTime(base::BindOnce(
diff --git a/components/browser_sync/DEPS b/components/browser_sync/DEPS
index 6dcdc6d..83aedd9 100644
--- a/components/browser_sync/DEPS
+++ b/components/browser_sync/DEPS
@@ -10,6 +10,7 @@
   "+components/pref_registry",
   "+components/prefs",
   "+components/reading_list/features",
+  "+components/send_tab_to_self",
   "+components/signin/core/browser",
   # Use identity_manager.h instead of the below files;
   # see https://groups.google.com/a/chromium.org/d/msg/chromium-dev/dgFLuxqZt1o/iEqkyoQQBwAJ for help and info.
diff --git a/components/cdm/browser/media_drm_storage_impl.cc b/components/cdm/browser/media_drm_storage_impl.cc
index 366614b..3c5ffe3 100644
--- a/components/cdm/browser/media_drm_storage_impl.cc
+++ b/components/cdm/browser/media_drm_storage_impl.cc
@@ -4,16 +4,22 @@
 
 #include "components/cdm/browser/media_drm_storage_impl.h"
 
+#include <map>
 #include <memory>
+#include <tuple>
 
 #include "base/bind.h"
 #include "base/logging.h"
+#include "base/no_destructor.h"
+#include "base/strings/string_util.h"
 #include "base/value_conversions.h"
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
 #include "content/public/browser/navigation_handle.h"
 #include "media/base/android/media_drm_key_type.h"
+#include "url/origin.h"
+#include "url/url_constants.h"
 
 // The storage will be managed by PrefService. All data will be stored in a
 // dictionary under the key "media.media_drm_storage". The dictionary is
@@ -428,6 +434,116 @@
   return origin_data->origin_id();
 }
 
+// Map shared by all MediaDrmStorageImpl objects to prevent multiple Origin IDs
+// being allocated for the same origin. Initialize() can be called multiple
+// times for the same web origin (and profile). This class groups together
+// requests for the same web origin (and profile), calling |get_origin_id_cb|
+// only on the first request. This avoids a race condition where simultaneous
+// requests could result in different Origin IDs allocated for the same origin
+// (and only the last one saved in the preference).
+class InitializationSerializer {
+ public:
+  // Origin IDs are unique per preference service and origin.
+  struct PreferenceAndOriginKey {
+    // Allow use as a key in std::map.
+    bool operator<(const PreferenceAndOriginKey& other) const {
+      return std::tie(pref_service, origin) <
+             std::tie(other.pref_service, other.origin);
+    }
+
+    PrefService* pref_service;
+    const url::Origin origin;
+  };
+
+  // The InitializationSerializer is a global map shared by all
+  // MediaDrmStorageImpl instances.
+  static InitializationSerializer& GetInstance() {
+    static base::NoDestructor<InitializationSerializer>
+        s_origin_id_request_impl;
+    return *s_origin_id_request_impl;
+  }
+
+  InitializationSerializer() = default;
+  ~InitializationSerializer() = default;
+
+  void FetchOriginId(
+      PrefService* pref_service,
+      const url::Origin& origin,
+      MediaDrmStorageImpl::GetOriginIdCB get_origin_id_cb,
+      MediaDrmStorageImpl::OriginIdObtainedCB origin_id_obtained_cb) {
+    DVLOG(3) << __func__ << " origin: " << origin;
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    // Check if the preference has an existing origin ID.
+    const base::DictionaryValue* storage_dict =
+        pref_service->GetDictionary(kMediaDrmStorage);
+    base::UnguessableToken origin_id =
+        GetOriginIdForOrigin(storage_dict, origin);
+    if (origin_id) {
+      std::move(origin_id_obtained_cb).Run(origin_id);
+      return;
+    }
+
+    // No origin ID found, so check if another Initialize() call is in progress.
+    PreferenceAndOriginKey key{pref_service, origin};
+    auto entry = pending_requests_.find(key);
+    if (entry != pending_requests_.end()) {
+      // Entry already exists, so simply add |origin_id_obtained_cb| to be
+      // called once the Origin ID is obtained.
+      entry->second.emplace_back(std::move(origin_id_obtained_cb));
+      return;
+    }
+
+    // Entry does not exist, so create a new one.
+    std::vector<MediaDrmStorageImpl::OriginIdObtainedCB>
+        origin_id_obtained_cb_list;
+    origin_id_obtained_cb_list.emplace_back(std::move(origin_id_obtained_cb));
+    pending_requests_.emplace(key, std::move(origin_id_obtained_cb_list));
+
+    // Now call |get_origin_id_cb|. It will call OnOriginIdObtained() when done,
+    // which will call all the callbacks saved for this preference and origin
+    // pair. Use of base::Unretained() is valid as |this| is a singleton stored
+    // in a static variable.
+    get_origin_id_cb.Run(
+        base::BindOnce(&InitializationSerializer::OnOriginIdObtained,
+                       base::Unretained(this), pref_service, origin));
+  }
+
+ private:
+  void OnOriginIdObtained(PrefService* pref_service,
+                          const url::Origin& origin,
+                          const base::UnguessableToken& origin_id) {
+    DVLOG(3) << __func__ << " origin: " << origin;
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    // Save the origin ID in the preference as long as it is not null.
+    if (origin_id) {
+      DictionaryPrefUpdate update(pref_service, kMediaDrmStorage);
+      CreateOriginDictAndReturnSessionsDict(update.Get(), origin, origin_id);
+    }
+
+    // Now call any callbacks waiting for this origin ID to be allocated.
+    auto entry = pending_requests_.find({pref_service, origin});
+    DCHECK(entry != pending_requests_.end());
+
+    std::vector<MediaDrmStorageImpl::OriginIdObtainedCB> callbacks;
+    callbacks.swap(entry->second);
+    pending_requests_.erase(entry);
+
+    for (auto& callback : callbacks)
+      std::move(callback).Run(origin_id);
+  }
+
+  // Note that this map is never deleted. As entries are removed when an origin
+  // ID is allocated, so it should never get too large.
+  std::map<PreferenceAndOriginKey,
+           std::vector<MediaDrmStorageImpl::OriginIdObtainedCB>>
+      pending_requests_;
+
+  THREAD_CHECKER(thread_checker_);
+  DISALLOW_COPY_AND_ASSIGN(InitializationSerializer);
+};
+
 }  // namespace
 
 // static
@@ -548,55 +664,28 @@
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
   DCHECK(!init_cb_);
 
-  if (IsInitialized()) {
+  if (is_initialized_) {
     std::move(callback).Run(origin_id_);
     return;
   }
   DCHECK(!origin_id_);
 
-  // Check if the preference has an existing origin ID.
-  const base::DictionaryValue* storage_dict =
-      pref_service_->GetDictionary(kMediaDrmStorage);
-  origin_id_ = GetOriginIdForOrigin(storage_dict, origin());
-  if (origin_id_) {
-    std::move(callback).Run(origin_id_);
-    return;
-  }
-
-  // No origin ID, so fetch one asynchronously. OnOriginIdObtained() is called
-  // to finish initialization.
+  // Call using SerializeInitializeRequests() to handle the case where
+  // Initialize() is called concurrently for the same origin (and preference
+  // service). Note that if there is already an existing origin ID for this web
+  // origin saved in the preference, OnOriginIdObtained() will be called
+  // immediately.
   init_cb_ = std::move(callback);
-  get_origin_id_cb_.Run(base::BindOnce(&MediaDrmStorageImpl::OnOriginIdObtained,
-                                       weak_factory_.GetWeakPtr()));
+  InitializationSerializer::GetInstance().FetchOriginId(
+      pref_service_, origin(), get_origin_id_cb_,
+      base::BindOnce(&MediaDrmStorageImpl::OnOriginIdObtained,
+                     weak_factory_.GetWeakPtr()));
 }
 
 void MediaDrmStorageImpl::OnOriginIdObtained(
     const base::UnguessableToken& origin_id) {
-  // If multiple MediaDrmStorage instances from the same web origin call
-  // Initialize concurrently, then a previous instance may have successfully
-  // obtained an origin ID and stored it for this origin. So check again to see
-  // if an origin ID has been saved, and use it instead of |origin_id|. This
-  // does mean that |origin_id| will be lost.
-  // TODO(crbug.com/919228): Once pre-provisioned origins are used, |origin_id|
-  // needs to be unprovisioned rather than dropped.
-  DictionaryPrefUpdate update(pref_service_, kMediaDrmStorage);
-  auto stored_origin_id = GetOriginIdForOrigin(update.Get(), origin());
-  if (stored_origin_id) {
-    origin_id_ = stored_origin_id;
-    std::move(init_cb_).Run(origin_id_);
-    return;
-  }
-
-  // As there is no current value, persist the origin ID into storage now.
-  // TODO(crbug.com/917527): When pre-provisioned origins are supported, there
-  // may not be any available, so an empty origin ID can be used temporarily.
-  // The empty origin ID should not be saved in the preference, but should be
-  // returned. Note that having |origin_id_| set is used to determine if this
-  // object is initialized or not, so temporarily using an empty origin ID will
-  // affect that, and that needs to be fixed too.
-  DCHECK(origin_id) << "Empty origin ID not handled.";
+  is_initialized_ = true;
   origin_id_ = origin_id;
-  CreateOriginDictAndReturnSessionsDict(update.Get(), origin(), origin_id_);
   std::move(init_cb_).Run(origin_id_);
 }
 
@@ -604,12 +693,19 @@
   DVLOG(1) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!IsInitialized()) {
+  if (!is_initialized_) {
     DVLOG(1) << __func__ << ": Not initialized.";
     std::move(callback).Run(false);
     return;
   }
 
+  // If this is using an empty origin ID, it should not be provisioned.
+  if (!origin_id_) {
+    DVLOG(1) << __func__ << ": Empty origin ID.";
+    std::move(callback).Run(false);
+    return;
+  }
+
   DictionaryPrefUpdate update(pref_service_, kMediaDrmStorage);
   base::DictionaryValue* storage_dict = update.Get();
   DCHECK(storage_dict);
@@ -628,12 +724,19 @@
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!IsInitialized()) {
+  if (!is_initialized_) {
     DVLOG(1) << __func__ << ": Not initialized.";
     std::move(callback).Run(false);
     return;
   }
 
+  // If this is using an empty origin ID, it cannot save persistent data.
+  if (!origin_id_) {
+    DVLOG(1) << __func__ << ": Empty origin ID.";
+    std::move(callback).Run(false);
+    return;
+  }
+
   DictionaryPrefUpdate update(pref_service_, kMediaDrmStorage);
   base::DictionaryValue* storage_dict = update.Get();
   DCHECK(storage_dict);
@@ -674,12 +777,19 @@
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!IsInitialized()) {
+  if (!is_initialized_) {
     DVLOG(1) << __func__ << ": Not initialized.";
     std::move(callback).Run(nullptr);
     return;
   }
 
+  // If this is using an empty origin ID, it cannot save persistent data.
+  if (!origin_id_) {
+    DVLOG(1) << __func__ << ": Empty origin ID.";
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
   const base::Value* sessions_dict =
       GetSessionsDictFromStorageDict<const base::Value>(
           pref_service_->GetDictionary(kMediaDrmStorage), origin().Serialize());
@@ -714,12 +824,19 @@
   DVLOG(2) << __func__;
   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
 
-  if (!IsInitialized()) {
+  if (!is_initialized_) {
     DVLOG(1) << __func__ << ": Not initialized.";
     std::move(callback).Run(false);
     return;
   }
 
+  // If this is using an empty origin ID, it cannot save persistent data.
+  if (!origin_id_) {
+    DVLOG(1) << __func__ << ": Empty origin ID.";
+    std::move(callback).Run(false);
+    return;
+  }
+
   DictionaryPrefUpdate update(pref_service_, kMediaDrmStorage);
 
   base::Value* sessions_dict = GetSessionsDictFromStorageDict<base::Value>(
diff --git a/components/cdm/browser/media_drm_storage_impl.h b/components/cdm/browser/media_drm_storage_impl.h
index 4ca4ae1..e4a97f7e 100644
--- a/components/cdm/browser/media_drm_storage_impl.h
+++ b/components/cdm/browser/media_drm_storage_impl.h
@@ -6,6 +6,7 @@
 #define COMPONENTS_CDM_BROWSER_MEDIA_DRM_STORAGE_IMPL_H_
 
 #include <set>
+#include <string>
 #include <vector>
 
 #include "base/callback.h"
@@ -36,8 +37,9 @@
 class MediaDrmStorageImpl final
     : public content::FrameServiceBase<media::mojom::MediaDrmStorage> {
  public:
-  using GetOriginIdCB = base::RepeatingCallback<void(
-      base::OnceCallback<void(const base::UnguessableToken&)>)>;
+  using OriginIdObtainedCB =
+      base::OnceCallback<void(const base::UnguessableToken&)>;
+  using GetOriginIdCB = base::RepeatingCallback<void(OriginIdObtainedCB)>;
 
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
@@ -79,8 +81,6 @@
   void RemovePersistentSession(const std::string& session_id,
                                RemovePersistentSessionCallback callback) final;
 
-  bool IsInitialized() const { return !!origin_id_; }
-
  private:
   // |this| can only be destructed as a FrameServiceBase.
   ~MediaDrmStorageImpl() final;
@@ -89,7 +89,7 @@
   // of Initialize();
   void OnOriginIdObtained(const base::UnguessableToken& origin_id);
 
-  PrefService* const pref_service_ = nullptr;
+  PrefService* const pref_service_;
   GetOriginIdCB get_origin_id_cb_;
 
   // ID for the current origin. Per EME spec on individualization,
@@ -100,6 +100,9 @@
   // necessary.
   InitializeCallback init_cb_;
 
+  // Set when initialized.
+  bool is_initialized_ = false;
+
   // NOTE: Weak pointers must be invalidated before all other member variables.
   base::WeakPtrFactory<MediaDrmStorageImpl> weak_factory_;
 };
diff --git a/components/gwp_asan/BUILD.gn b/components/gwp_asan/BUILD.gn
index 48db66c..5176c8c 100644
--- a/components/gwp_asan/BUILD.gn
+++ b/components/gwp_asan/BUILD.gn
@@ -7,7 +7,7 @@
   deps = [
     "//components/gwp_asan/common:unit_tests",
   ]
-  if (is_win) {
+  if (is_win || is_mac) {
     deps += [
       "//components/gwp_asan/client:unit_tests",
       "//components/gwp_asan/crash_handler:unit_tests",
diff --git a/components/gwp_asan/client/guarded_page_allocator.cc b/components/gwp_asan/client/guarded_page_allocator.cc
index 798583809..a50d076 100644
--- a/components/gwp_asan/client/guarded_page_allocator.cc
+++ b/components/gwp_asan/client/guarded_page_allocator.cc
@@ -17,6 +17,7 @@
 #include "components/crash/core/common/crash_key.h"
 #include "components/gwp_asan/common/allocator_state.h"
 #include "components/gwp_asan/common/crash_key_name.h"
+#include "components/gwp_asan/common/pack_stack_trace.h"
 
 namespace gwp_asan {
 namespace internal {
@@ -174,10 +175,13 @@
   slots_[slot].alloc_size = size;
   slots_[slot].alloc_ptr = reinterpret_cast<uintptr_t>(ptr);
 
+  void* trace[AllocatorState::kMaxStackFrames];
+  size_t len =
+      base::debug::CollectStackTrace(trace, AllocatorState::kMaxStackFrames);
+  slots_[slot].alloc.trace_len = Pack(reinterpret_cast<uintptr_t*>(trace), len,
+                                      slots_[slot].alloc.packed_trace,
+                                      sizeof(slots_[slot].alloc.packed_trace));
   slots_[slot].alloc.tid = base::PlatformThread::CurrentId();
-  slots_[slot].alloc.trace_len = base::debug::CollectStackTrace(
-      reinterpret_cast<void**>(&slots_[slot].alloc.trace),
-      AllocatorState::kMaxStackFrames);
   slots_[slot].alloc.trace_collected = true;
 
   slots_[slot].dealloc.tid = base::kInvalidThreadId;
@@ -187,10 +191,14 @@
 }
 
 void GuardedPageAllocator::RecordDeallocationInSlot(size_t slot) {
+  void* trace[AllocatorState::kMaxStackFrames];
+  size_t len =
+      base::debug::CollectStackTrace(trace, AllocatorState::kMaxStackFrames);
+  slots_[slot].dealloc.trace_len =
+      Pack(reinterpret_cast<uintptr_t*>(trace), len,
+           slots_[slot].dealloc.packed_trace,
+           sizeof(slots_[slot].dealloc.packed_trace));
   slots_[slot].dealloc.tid = base::PlatformThread::CurrentId();
-  slots_[slot].dealloc.trace_len = base::debug::CollectStackTrace(
-      reinterpret_cast<void**>(&slots_[slot].dealloc.trace),
-      AllocatorState::kMaxStackFrames);
   slots_[slot].dealloc.trace_collected = true;
 }
 
diff --git a/components/gwp_asan/client/sampling_allocator_shims.cc b/components/gwp_asan/client/sampling_allocator_shims.cc
index 07c59be..c02bab6b 100644
--- a/components/gwp_asan/client/sampling_allocator_shims.cc
+++ b/components/gwp_asan/client/sampling_allocator_shims.cc
@@ -18,10 +18,7 @@
 #include "components/gwp_asan/client/guarded_page_allocator.h"
 
 #if defined(OS_MACOSX)
-// TODO(https://crbug.com/829078): thread_local is not currently supported on
-// macOS; however, it works correctly on other platforms and is noticeably
-// faster.
-#error "macOS does not support thread_local"
+#include <pthread.h>
 #endif
 
 namespace gwp_asan {
@@ -40,6 +37,10 @@
   void Init(size_t sampling_frequency) {
     DCHECK_GT(sampling_frequency, 0U);
     sampling_frequency_ = sampling_frequency;
+
+#if defined(OS_MACOSX)
+    pthread_key_create(&tls_key_, nullptr);
+#endif
   }
 
   // Return true if this allocation should be sampled.
@@ -50,12 +51,11 @@
     //
     // Instead, use zero to mean 'get a new counter value' and one to mean
     // that this allocation should be sampled.
-    static thread_local size_t tls_counter = 0;
-    size_t samples_left = tls_counter;
+    size_t samples_left = GetCounter();
     if (UNLIKELY(!samples_left))
       samples_left = NextSample();
 
-    tls_counter = samples_left - 1;
+    SetCounter(samples_left - 1);
     return (samples_left == 1);
   }
 
@@ -72,6 +72,26 @@
     return next_sample;
   }
 
+#if !defined(OS_MACOSX)
+  ALWAYS_INLINE size_t GetCounter() { return tls_counter_; }
+  ALWAYS_INLINE void SetCounter(size_t value) { tls_counter_ = value; }
+
+  static thread_local size_t tls_counter_;
+#else
+  // On macOS, the first use of a thread_local variable on a new thread will
+  // cause a malloc(), causing infinite recursion. Instead, use pthread TLS to
+  // store the counter.
+  ALWAYS_INLINE size_t GetCounter() {
+    return reinterpret_cast<size_t>(pthread_getspecific(tls_key_));
+  }
+
+  ALWAYS_INLINE void SetCounter(size_t value) {
+    pthread_setspecific(tls_key_, reinterpret_cast<void*>(value));
+  }
+
+  pthread_key_t tls_key_ = 0;
+#endif
+
   size_t sampling_frequency_ = 0;
 
   // Stores the number of allocations we need to skip to reach the end of the
@@ -79,6 +99,10 @@
   size_t increment_ = 0;
 };
 
+#if !defined(OS_MACOSX)
+thread_local size_t SamplingState::tls_counter_ = 0;
+#endif
+
 // By being implemented as a global with inline method definitions, method calls
 // and member acceses are inlined and as efficient as possible in the
 // performance-sensitive allocation hot-path.
diff --git a/components/gwp_asan/common/allocator_state.cc b/components/gwp_asan/common/allocator_state.cc
index ccd2a7d..8aa839f 100644
--- a/components/gwp_asan/common/allocator_state.cc
+++ b/components/gwp_asan/common/allocator_state.cc
@@ -15,6 +15,7 @@
 // TODO: Delete out-of-line constexpr defininitons once C++17 is in use.
 constexpr size_t AllocatorState::kGpaMaxPages;
 constexpr size_t AllocatorState::kMaxStackFrames;
+constexpr size_t AllocatorState::kMaxPackedTraceLength;
 
 AllocatorState::AllocatorState() {}
 
diff --git a/components/gwp_asan/common/allocator_state.h b/components/gwp_asan/common/allocator_state.h
index a7ff46ed..2f1f88b 100644
--- a/components/gwp_asan/common/allocator_state.h
+++ b/components/gwp_asan/common/allocator_state.h
@@ -37,9 +37,12 @@
 class AllocatorState {
  public:
   // Maximum number of pages this class can allocate.
-  static constexpr size_t kGpaMaxPages = 128;
+  static constexpr size_t kGpaMaxPages = 256;
   // Maximum number of stack trace frames to collect.
   static constexpr size_t kMaxStackFrames = 60;
+  // Number of bytes to allocate for packed stack traces. This can hold
+  // approximately kMaxStackFrames under normal conditions.
+  static constexpr size_t kMaxPackedTraceLength = 200;
 
   enum class ErrorType {
     kUseAfterFree = 0,
@@ -65,9 +68,9 @@
       // (De)allocation thread id or base::kInvalidThreadId if no (de)allocation
       // occurred.
       base::PlatformThreadId tid = base::kInvalidThreadId;
-      // Stack trace contents.
-      uintptr_t trace[kMaxStackFrames];
-      // Stack trace length.
+      // Packed stack trace.
+      uint8_t packed_trace[kMaxPackedTraceLength];
+      // Length used to encode the packed stack trace.
       size_t trace_len = 0;
       // Whether a stack trace has been collected for this (de)allocation.
       bool trace_collected = false;
diff --git a/components/gwp_asan/crash_handler/crash_analyzer.cc b/components/gwp_asan/crash_handler/crash_analyzer.cc
index 04e70d1..95f1223 100644
--- a/components/gwp_asan/crash_handler/crash_analyzer.cc
+++ b/components/gwp_asan/crash_handler/crash_analyzer.cc
@@ -14,6 +14,7 @@
 #include "build/build_config.h"
 #include "components/gwp_asan/common/allocator_state.h"
 #include "components/gwp_asan/common/crash_key_name.h"
+#include "components/gwp_asan/common/pack_stack_trace.h"
 #include "components/gwp_asan/crash_handler/crash.pb.h"
 #include "third_party/crashpad/crashpad/client/annotation.h"
 #include "third_party/crashpad/crashpad/snapshot/cpu_context.h"
@@ -158,12 +159,21 @@
     return;
   }
 
-  // On 32-bit platforms we can't copy directly to
+  uintptr_t unpacked_stack_trace[AllocatorState::kMaxPackedTraceLength];
+  size_t unpacked_len =
+      Unpack(slot_info.packed_trace, slot_info.trace_len, unpacked_stack_trace,
+             AllocatorState::kMaxPackedTraceLength);
+  if (!unpacked_len) {
+    DLOG(ERROR) << "Failed to unpack stack trace.";
+    return;
+  }
+
+  // On 32-bit platforms we can't copy directly into
   // proto_info->mutable_stack_trace()->mutable_data().
-  proto_info->mutable_stack_trace()->Resize(slot_info.trace_len, 0);
+  proto_info->mutable_stack_trace()->Resize(unpacked_len, 0);
   uint64_t* output = proto_info->mutable_stack_trace()->mutable_data();
-  for (size_t i = 0; i < slot_info.trace_len; i++)
-    output[i] = slot_info.trace[i];
+  for (size_t i = 0; i < unpacked_len; i++)
+    output[i] = unpacked_stack_trace[i];
 }
 
 }  // namespace internal
diff --git a/components/omnibox/browser/autocomplete_match.cc b/components/omnibox/browser/autocomplete_match.cc
index 3714ba5..9da0bd7 100644
--- a/components/omnibox/browser/autocomplete_match.cc
+++ b/components/omnibox/browser/autocomplete_match.cc
@@ -643,6 +643,10 @@
   // TODO(orinj): It may make more sense to start from a clean slate and
   // apply only the bits of state relevant to the Pedal, rather than
   // eliminating parts of an existing match that are no longer useful.
+  // But while Pedal suggestions are derived from triggering suggestions by
+  // copy, it is necessary to be careful that we don't inherit fields that
+  // might cause issues.
+  allowed_to_be_default_match = false;
 
   type = Type::PEDAL;
   destination_url = pedal->GetNavigationUrl();
diff --git a/components/omnibox/browser/autocomplete_result.cc b/components/omnibox/browser/autocomplete_result.cc
index 1eb96847..1da1333 100644
--- a/components/omnibox/browser/autocomplete_result.cc
+++ b/components/omnibox/browser/autocomplete_result.cc
@@ -236,18 +236,48 @@
 void AutocompleteResult::AppendDedicatedPedalMatches(
     AutocompleteProviderClient* client,
     const AutocompleteInput& input) {
-  ACMatches pedal_suggestions;
   const OmniboxPedalProvider* provider = client->GetPedalProvider();
+  ACMatches pedal_suggestions;
+  // Map from Pedal to a vector and index so that we can update instead of
+  // adding a duplicate.  Existing Pedals are fully indexed before the next
+  // loop that adds|updates because, e.g. the first match may trigger a Pedal
+  // which is already applied on the last.  We should update it, but without the
+  // fully built index the below logic would add new, resulting in a duplicate.
+  std::unordered_map<OmniboxPedal*, std::pair<ACMatches&, size_t>> pedals_found;
+  for (size_t match_index = 0; match_index < matches_.size(); ++match_index) {
+    OmniboxPedal* const pedal = matches_[match_index].pedal;
+    if (pedal) {
+      const auto insertion =
+          pedals_found.insert({pedal, {matches_, match_index}});
+      DCHECK(insertion.second) << "Found existing duplicate Pedal suggestion.";
+    }
+  }
   for (const auto& match : matches_) {
     if (match.pedal)
       continue;
-    OmniboxPedal* pedal = provider->FindPedalMatch(match.contents);
+    OmniboxPedal* const pedal = provider->FindPedalMatch(match.contents);
     if (pedal) {
-      AutocompleteMatch suggestion = match;
-      suggestion.relevance--;
-      suggestion.pedal = pedal;
-      suggestion.ApplyPedal();
-      pedal_suggestions.push_back(suggestion);
+      auto derive_pedal_suggestion = [&match, pedal]() {
+        AutocompleteMatch suggestion = match;
+        suggestion.relevance--;
+        suggestion.pedal = pedal;
+        suggestion.ApplyPedal();
+        return suggestion;
+      };
+      const auto insertion = pedals_found.insert(
+          {pedal, {pedal_suggestions, pedal_suggestions.size()}});
+      if (insertion.second) {
+        // This is the first use of the found pedal; add new suggestion.
+        pedal_suggestions.push_back(derive_pedal_suggestion());
+      } else {
+        // This is a subsequent use of the found pedal; update its suggestion to
+        // ensure that it is derived from the most relevant matching suggestion.
+        const auto& map_value_pair = insertion.first->second;
+        auto& suggestion = map_value_pair.first[map_value_pair.second];
+        if (suggestion.relevance < match.relevance - 1) {
+          suggestion = derive_pedal_suggestion();
+        }
+      }
     }
   }
   if (!pedal_suggestions.empty()) {
diff --git a/components/omnibox/browser/autocomplete_result.h b/components/omnibox/browser/autocomplete_result.h
index 746cbee7..747b278 100644
--- a/components/omnibox/browser/autocomplete_result.h
+++ b/components/omnibox/browser/autocomplete_result.h
@@ -55,7 +55,9 @@
   void SortAndCull(const AutocompleteInput& input,
                    TemplateURLService* template_url_service);
 
-  // Creates and adds any dedicated Pedal matches triggered by existing match.
+  // Creates and adds any dedicated Pedal matches triggered by existing matches.
+  // This should be the only place where new Pedal suggestions are introduced
+  // because it doesn't dedupe; it just carefully avoids adding duplicates.
   void AppendDedicatedPedalMatches(AutocompleteProviderClient* client,
                                    const AutocompleteInput& input);
 
diff --git a/components/omnibox/browser/autocomplete_result_unittest.cc b/components/omnibox/browser/autocomplete_result_unittest.cc
index 734366aa..2c73f6c 100644
--- a/components/omnibox/browser/autocomplete_result_unittest.cc
+++ b/components/omnibox/browser/autocomplete_result_unittest.cc
@@ -85,7 +85,7 @@
  public:
   struct TestData {
     // Used to build a url for the AutocompleteMatch. The URL becomes
-    // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://b").
+    // "http://" + ('a' + |url_id|) (e.g. an ID of 2 yields "http://c").
     int url_id;
 
     // ID of the provider.
@@ -1161,3 +1161,79 @@
                                                  OmniboxEventProto::HOME_PAGE));
   CheckRelevanceExpectations(first, second, 1000, 600, "", "");
 }
+
+TEST_F(AutocompleteResultTest, PedalSuggestionsCantBeDefaultMatch) {
+  TestData data[] = {
+      {1, 1, 500, true},
+      {0, 1, 1100, true},
+  };
+
+  ACMatches matches;
+  PopulateAutocompleteMatches(data, base::size(data), &matches);
+  matches[0].contents = base::UTF8ToUTF16("clear chrome history");
+  matches[1].contents = base::UTF8ToUTF16("open incognito tab");
+
+  AutocompleteInput input(base::ASCIIToUTF16("a"),
+                          metrics::OmniboxEventProto::OTHER,
+                          TestSchemeClassifier());
+  AutocompleteResult result;
+  result.AppendMatches(input, matches);
+
+  FakeAutocompleteProviderClient client;
+  result.AppendDedicatedPedalMatches(&client, input);
+
+  // Two distinct Pedals should be appended.
+  EXPECT_EQ(result.size(), 4u);
+  EXPECT_NE(result.match_at(2)->pedal, nullptr);
+  EXPECT_NE(result.match_at(3)->pedal, nullptr);
+
+  // Neither should be allowed to be default match, even though they were both
+  // derived from suggestions where the field is set true.
+  EXPECT_TRUE(result.match_at(0)->allowed_to_be_default_match);
+  EXPECT_TRUE(result.match_at(1)->allowed_to_be_default_match);
+  EXPECT_FALSE(result.match_at(2)->allowed_to_be_default_match);
+  EXPECT_FALSE(result.match_at(3)->allowed_to_be_default_match);
+}
+
+TEST_F(AutocompleteResultTest, PedalSuggestionsRemainUnique) {
+  TestData data[] = {
+      {1, 1, 500, true},
+      {0, 1, 1100, true},
+      {2, 1, 1000, true},
+      {0, 1, 1200, true},
+  };
+
+  ACMatches matches;
+  PopulateAutocompleteMatches(data, base::size(data), &matches);
+  matches[0].contents = base::UTF8ToUTF16("clear chrome history");
+  matches[1].contents = base::UTF8ToUTF16("open incognito tab");
+  matches[2].contents = base::UTF8ToUTF16("clear chrome history");
+
+  AutocompleteInput input(base::ASCIIToUTF16("a"),
+                          metrics::OmniboxEventProto::OTHER,
+                          TestSchemeClassifier());
+  AutocompleteResult result;
+  result.AppendMatches(input, matches);
+
+  FakeAutocompleteProviderClient client;
+  result.AppendDedicatedPedalMatches(&client, input);
+
+  // Exactly 2 (not 3) unique Pedals should be added with relevance close to max
+  // of the triggering suggestions.
+  EXPECT_EQ(result.size(), 6u);
+  EXPECT_NE(result.match_at(4)->pedal, nullptr);
+  EXPECT_NE(result.match_at(5)->pedal, nullptr);
+  EXPECT_NE(result.match_at(4)->pedal, result.match_at(5)->pedal);
+  EXPECT_EQ(result.match_at(4)->relevance, 999);
+  EXPECT_EQ(result.match_at(5)->relevance, 1099);
+
+  // Now artificially modify existing suggestions and run again to ensure that
+  // no duplicates are added, but the existing Pedal suggestion is updated.
+  result.match_at(3)->contents = base::UTF8ToUTF16("open incognito tab");
+  result.AppendDedicatedPedalMatches(&client, input);
+  EXPECT_EQ(result.size(), 6u);
+  EXPECT_NE(result.match_at(4)->pedal, nullptr);
+  EXPECT_NE(result.match_at(5)->pedal, nullptr);
+  EXPECT_NE(result.match_at(4)->pedal, result.match_at(5)->pedal);
+  EXPECT_EQ(result.match_at(5)->relevance, 1199);
+}
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder.cc b/components/password_manager/core/browser/password_form_metrics_recorder.cc
index e407765..82610742cd 100644
--- a/components/password_manager/core/browser/password_form_metrics_recorder.cc
+++ b/components/password_manager/core/browser/password_form_metrics_recorder.cc
@@ -254,6 +254,15 @@
     FillingAssistance filling_assistance = *filling_assistance_;
     UMA_HISTOGRAM_ENUMERATION("PasswordManager.FillingAssistance",
                               filling_assistance);
+
+    if (is_main_frame_secure_) {
+      UMA_HISTOGRAM_ENUMERATION(
+          "PasswordManager.FillingAssistance.SecureOrigin", filling_assistance);
+    } else {
+      UMA_HISTOGRAM_ENUMERATION(
+          "PasswordManager.FillingAssistance.InsecureOrigin",
+          filling_assistance);
+    }
   }
 
   ukm_entry_builder_.Record(ukm::UkmRecorder::Get());
diff --git a/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc b/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc
index d9fd683..8d31f79 100644
--- a/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc
+++ b/components/password_manager/core/browser/password_form_metrics_recorder_unittest.cc
@@ -754,36 +754,53 @@
 
 void CheckFillingAssistanceTestCase(
     const FillingAssistanceTestCase& test_case) {
-  SCOPED_TRACE(testing::Message("Test description: ")
-               << test_case.description_for_logging);
+  for (bool is_main_frame_secure : {false, true}) {
+    SCOPED_TRACE(testing::Message("Test description: ")
+                 << test_case.description_for_logging
+                 << ", is_main_frame_secure: " << std::boolalpha
+                 << is_main_frame_secure);
 
-  base::test::ScopedTaskEnvironment scoped_task_environment_;
-  base::HistogramTester histogram_tester;
+    base::test::ScopedTaskEnvironment scoped_task_environment_;
+    base::HistogramTester histogram_tester;
 
-  FormData form_data = ConvertToFormData(test_case.fields);
-  std::set<base::string16> saved_usernames =
-      ConvertToString16Set(test_case.saved_usernames);
-  std::set<base::string16> saved_passwords =
-      ConvertToString16Set(test_case.saved_passwords);
+    FormData form_data = ConvertToFormData(test_case.fields);
+    std::set<base::string16> saved_usernames =
+        ConvertToString16Set(test_case.saved_usernames);
+    std::set<base::string16> saved_passwords =
+        ConvertToString16Set(test_case.saved_passwords);
 
-  auto recorder =
-      CreatePasswordFormMetricsRecorder(true /*is_main_frame_secure*/, nullptr);
-  if (test_case.submission_detected) {
-    recorder->CalculateFillingAssistanceMetric(form_data, saved_usernames,
-                                               saved_passwords,
-                                               test_case.interactions_stats);
-  }
+    auto recorder =
+        CreatePasswordFormMetricsRecorder(is_main_frame_secure, nullptr);
+    if (test_case.submission_detected) {
+      recorder->CalculateFillingAssistanceMetric(form_data, saved_usernames,
+                                                 saved_passwords,
+                                                 test_case.interactions_stats);
+    }
 
-  if (test_case.submission_is_successful)
-    recorder->LogSubmitPassed();
-  recorder.reset();
+    if (test_case.submission_is_successful)
+      recorder->LogSubmitPassed();
+    recorder.reset();
 
-  int expected_count = test_case.expectation ? 1 : 0;
-  histogram_tester.ExpectTotalCount("PasswordManager.FillingAssistance",
-                                    expected_count);
-  if (test_case.expectation) {
-    histogram_tester.ExpectUniqueSample("PasswordManager.FillingAssistance",
-                                        test_case.expectation.value(), 1);
+    int expected_count = test_case.expectation ? 1 : 0;
+    int expected_insecure_count = !is_main_frame_secure ? expected_count : 0;
+    int expected_secure_count = is_main_frame_secure ? expected_count : 0;
+    histogram_tester.ExpectTotalCount("PasswordManager.FillingAssistance",
+                                      expected_count);
+    histogram_tester.ExpectTotalCount(
+        "PasswordManager.FillingAssistance.InsecureOrigin",
+        expected_insecure_count);
+    histogram_tester.ExpectTotalCount(
+        "PasswordManager.FillingAssistance.SecureOrigin",
+        expected_secure_count);
+    if (test_case.expectation) {
+      histogram_tester.ExpectUniqueSample("PasswordManager.FillingAssistance",
+                                          *test_case.expectation, 1);
+      histogram_tester.ExpectUniqueSample(
+          is_main_frame_secure
+              ? "PasswordManager.FillingAssistance.SecureOrigin"
+              : "PasswordManager.FillingAssistance.InsecureOrigin",
+          *test_case.expectation, 1);
+    }
   }
 }
 
diff --git a/components/safe_browsing/password_protection/visual_utils.cc b/components/safe_browsing/password_protection/visual_utils.cc
index e24d3ad..e395851 100644
--- a/components/safe_browsing/password_protection/visual_utils.cc
+++ b/components/safe_browsing/password_protection/visual_utils.cc
@@ -3,13 +3,73 @@
 // found in the LICENSE file.
 
 #include <unordered_map>
+#include <vector>
 
 #include "base/numerics/checked_math.h"
 #include "components/safe_browsing/password_protection/visual_utils.h"
 
+#include "base/logging.h"
+#include "base/numerics/checked_math.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "third_party/skia/include/core/SkPixmap.h"
+
 namespace safe_browsing {
 namespace visual_utils {
 
+namespace {
+
+// Constants used in getting the luminance of a Rec 2020 RGB value.
+// Drawn from:
+// https://en.wikipedia.org/wiki/Rec._2020#RGB_and_luma-chroma_formats
+const uint32_t kWeightRed = 263;
+const uint32_t kWeightGreen = 678;
+const uint32_t kWeightBlue = 59;
+
+// WARNING: The following parameters are highly privacy and performance
+// sensitive. These should not be changed without thorough review.
+const int kPHashDownsampleWidth = 288;
+const int kPHashDownsampleHeight = 288;
+const int kPHashBlockSize = 6;
+
+// Returns the median value of the list.
+uint8_t GetMedian(const std::vector<uint8_t>& samples) {
+  std::vector<uint8_t> samples_copy = samples;
+  std::vector<uint8_t>::iterator middle =
+      samples_copy.begin() + (samples_copy.size() / 2);
+  std::nth_element(samples_copy.begin(), middle, samples_copy.end());
+  return *middle;
+}
+
+// Encode the luminances as a bitstring, with a "1" bit if the luminance is
+// above the cutoff, and a "0" if it's below.
+void EncodeHash(const std::vector<uint8_t>& luminances,
+                uint8_t cutoff_luminance,
+                std::string* output) {
+  int current_bits = 0;
+  uint8_t current_byte = 0;
+  *output = "";
+
+  for (uint8_t luminance : luminances) {
+    current_bits++;
+    current_byte <<= 1;
+    if (luminance >= cutoff_luminance)
+      current_byte |= 1;
+
+    if (current_bits == 8) {
+      *output += static_cast<char>(current_byte);
+      current_bits = 0;
+      current_byte = 0;
+    }
+  }
+
+  if (current_bits != 0) {
+    current_byte <<= (8 - current_bits);
+    *output += static_cast<char>(current_byte);
+  }
+}
+
+}  // namespace
+
 // A QuantizedColor takes the highest 3 bits of R, G, and B, and concatenates
 // them.
 QuantizedColor SkColorToQuantizedColor(SkColor color) {
@@ -67,5 +127,93 @@
   return true;
 }
 
+bool GetBlurredImage(const SkBitmap& image,
+                     VisualFeatures::BlurredImage* blurred_image) {
+  if (image.drawsNothing())
+    return false;
+
+  // Use the Rec. 2020 color space, in case the user input is wide-gamut.
+  sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(
+      {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0},
+      SkNamedGamut::kRec2020);
+
+  // We scale down twice, once with medium quality, once with none quality to be
+  // consistent with the backend.
+  // TODO(drubery): Investigate whether this is necessary for performance or
+  // not.
+  SkImageInfo downsampled_info =
+      SkImageInfo::Make(kPHashDownsampleWidth, kPHashDownsampleHeight,
+                        SkColorType::kRGBA_8888_SkColorType,
+                        SkAlphaType::kUnpremul_SkAlphaType, rec2020);
+  SkBitmap downsampled;
+  if (!downsampled.tryAllocPixels(downsampled_info))
+    return false;
+  image.pixmap().scalePixels(downsampled.pixmap(),
+                             SkFilterQuality::kMedium_SkFilterQuality);
+
+  SkImageInfo blurred_info =
+      SkImageInfo::Make(kPHashDownsampleWidth / kPHashBlockSize,
+                        kPHashDownsampleHeight / kPHashBlockSize,
+                        SkColorType::kRGBA_8888_SkColorType,
+                        SkAlphaType::kUnpremul_SkAlphaType, rec2020);
+  SkBitmap blurred;
+  if (!blurred.tryAllocPixels(blurred_info))
+    return false;
+  downsampled.pixmap().scalePixels(blurred.pixmap(),
+                                   SkFilterQuality::kNone_SkFilterQuality);
+
+  blurred_image->set_width(blurred.width());
+  blurred_image->set_height(blurred.height());
+  blurred_image->clear_data();
+
+  const uint32_t* rgba = blurred.getAddr32(0, 0);
+  for (int i = 0; i < blurred.width() * blurred.height(); i++) {
+    *blurred_image->mutable_data() += static_cast<char>((rgba[i] >> 0) & 0xff);
+    *blurred_image->mutable_data() += static_cast<char>((rgba[i] >> 8) & 0xff);
+    *blurred_image->mutable_data() += static_cast<char>((rgba[i] >> 16) & 0xff);
+  }
+
+  return true;
+}
+
+// Computes the final PHash value for the BlurredImage. For each pixel in the
+// blurred image, we compute its luminance. Then we create a bitstring, where
+// each pixel gives a "1" if the luminance is at least the median, and a "0"
+// otherwise.
+bool GetPHash(const VisualFeatures::BlurredImage& blurred_image,
+              std::string* phash) {
+  DCHECK_LE(blurred_image.width(), kPHashDownsampleWidth);
+  DCHECK_LE(blurred_image.height(), kPHashDownsampleHeight);
+
+  int width = blurred_image.width();
+  int height = blurred_image.height();
+
+  base::CheckedNumeric<int> expected_size =
+      base::CheckMul(3u, base::CheckMul(width, height));
+  if (!expected_size.IsValid())
+    return false;
+  if (blurred_image.data().size() != expected_size.ValueOrDie())
+    return false;
+
+  std::vector<uint8_t> luminances;
+  for (int y = 0; y < height; y++) {
+    for (int x = 0; x < width; x++) {
+      int current_offset = 3 * width * y + 3 * x;
+      uint8_t r = blurred_image.data()[current_offset];
+      uint8_t g = blurred_image.data()[current_offset + 1];
+      uint8_t b = blurred_image.data()[current_offset + 2];
+
+      uint8_t luminance =
+          (kWeightRed * r + kWeightGreen * g + kWeightBlue * b) /
+          (kWeightRed + kWeightGreen + kWeightBlue);
+      luminances.push_back(luminance);
+    }
+  }
+
+  uint8_t cutoff_luminance = GetMedian(luminances);
+  EncodeHash(luminances, cutoff_luminance, phash);
+  return true;
+}
+
 }  // namespace visual_utils
 }  // namespace safe_browsing
diff --git a/components/safe_browsing/password_protection/visual_utils.h b/components/safe_browsing/password_protection/visual_utils.h
index 269fefe..70a037f 100644
--- a/components/safe_browsing/password_protection/visual_utils.h
+++ b/components/safe_browsing/password_protection/visual_utils.h
@@ -5,6 +5,8 @@
 #ifndef COMPONENTS_SAFE_BROWSING_PASSWORD_PROTECTION_VISUAL_UTILS_H_
 #define COMPONENTS_SAFE_BROWSING_PASSWORD_PROTECTION_VISUAL_UTILS_H_
 
+#include <string>
+
 #include "components/safe_browsing/proto/csd.pb.h"
 #include "third_party/skia/include/core/SkBitmap.h"
 
@@ -24,6 +26,18 @@
 bool GetHistogramForImage(const SkBitmap& image,
                           VisualFeatures::ColorHistogram* histogram);
 
+// Computes the BlurredImage for the given input image. This involves
+// downsampling the image to a certain fixed resolution, then blurring
+// by taking an average over fixed-size blocks of pixels.
+bool GetBlurredImage(const SkBitmap& image,
+                     VisualFeatures::BlurredImage* blurred_image);
+
+// Computes the pHash from the Blurred image. This involves computing the
+// luminance for each pixel, then outputs a bitstring, where each pixel
+// contributes a "1" if the luminance is above the median, and a "0" otherwise.
+bool GetPHash(const VisualFeatures::BlurredImage& blurred_image,
+              std::string* phash);
+
 }  // namespace visual_utils
 }  // namespace safe_browsing
 
diff --git a/components/safe_browsing/password_protection/visual_utils_unittest.cc b/components/safe_browsing/password_protection/visual_utils_unittest.cc
index 049bfa7..5cc0994 100644
--- a/components/safe_browsing/password_protection/visual_utils_unittest.cc
+++ b/components/safe_browsing/password_protection/visual_utils_unittest.cc
@@ -4,6 +4,7 @@
 
 #include "components/safe_browsing/password_protection/visual_utils.h"
 
+#include "base/test/test_discardable_memory_allocator.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -12,7 +13,35 @@
 
 using ::testing::FloatEq;
 
-TEST(VisualUtilsTest, TestSkColorToQuantizedColor) {
+class VisualUtilsTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    base::DiscardableMemoryAllocator::SetInstance(&test_allocator_);
+
+    sk_sp<SkColorSpace> rec2020 = SkColorSpace::MakeRGB(
+        {2.22222f, 0.909672f, 0.0903276f, 0.222222f, 0.0812429f, 0, 0},
+        SkNamedGamut::kRec2020);
+    SkImageInfo bitmap_info =
+        SkImageInfo::Make(1000, 1000, SkColorType::kBGRA_8888_SkColorType,
+                          SkAlphaType::kUnpremul_SkAlphaType, rec2020);
+
+    ASSERT_TRUE(bitmap_.tryAllocPixels(bitmap_info));
+  }
+
+  void TearDown() override {
+    base::DiscardableMemoryAllocator::SetInstance(nullptr);
+  }
+
+  // A test bitmap to work with. Initialized to be 1000x1000 in the Rec 2020
+  // color space.
+  SkBitmap bitmap_;
+
+ private:
+  // A DiscardableMemoryAllocator is needed for certain Skia operations.
+  base::TestDiscardableMemoryAllocator test_allocator_;
+};
+
+TEST_F(VisualUtilsTest, TestSkColorToQuantizedColor) {
   // Test quantization
   EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 0, 31)), 0u);
   EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(0, 0, 32)), 1u);
@@ -28,76 +57,191 @@
   EXPECT_EQ(SkColorToQuantizedColor(SkColorSetRGB(255, 255, 255)), 511u);
 }
 
-TEST(VisualUtilsTest, GetQuantizedR) {
+TEST_F(VisualUtilsTest, GetQuantizedR) {
   EXPECT_EQ(GetQuantizedR(0), 0);
   EXPECT_EQ(GetQuantizedR(64), 1);
   EXPECT_EQ(GetQuantizedR(448), 7);
 }
 
-TEST(VisualUtilsTest, GetQuantizedG) {
+TEST_F(VisualUtilsTest, GetQuantizedG) {
   EXPECT_EQ(GetQuantizedG(0), 0);
   EXPECT_EQ(GetQuantizedG(8), 1);
   EXPECT_EQ(GetQuantizedG(56), 7);
 }
 
-TEST(VisualUtilsTest, GetQuantizedB) {
+TEST_F(VisualUtilsTest, GetQuantizedB) {
   EXPECT_EQ(GetQuantizedB(0), 0);
   EXPECT_EQ(GetQuantizedB(1), 1);
   EXPECT_EQ(GetQuantizedB(7), 7);
 }
 
-TEST(VisualUtilsTest, GetHistogramForImageWhite) {
+TEST_F(VisualUtilsTest, GetHistogramForImageWhite) {
   VisualFeatures::ColorHistogram histogram;
   SkBitmap bitmap;
 
-  bitmap.allocN32Pixels(200, 200);
-  for (int x = 0; x < 200; x++)
-    for (int y = 0; y < 200; y++)
-      *bitmap.getAddr32(x, y) = SkColorSetRGB(255, 255, 255);
+  // Draw white over half the image
+  for (int x = 0; x < 1000; x++)
+    for (int y = 0; y < 1000; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(255, 255, 255);
 
-  ASSERT_TRUE(GetHistogramForImage(bitmap, &histogram));
+  ASSERT_TRUE(GetHistogramForImage(bitmap_, &histogram));
   ASSERT_EQ(histogram.bins_size(), 1);
   EXPECT_THAT(histogram.bins(0).centroid_x(),
-              FloatEq(0.4975));  // All pixels are the same color, so centroid_x
-                                 // is (0+1+...+199)/200/200 = 0.4975
-  EXPECT_THAT(histogram.bins(0).centroid_y(), FloatEq(0.4975));
+              FloatEq(0.4995));  // All pixels are the same color, so centroid_x
+                                 // is (0+1+...+999)/1000/1000 = 0.4995
+  EXPECT_THAT(histogram.bins(0).centroid_y(), FloatEq(0.4995));
   EXPECT_EQ(histogram.bins(0).quantized_r(), 7);
   EXPECT_EQ(histogram.bins(0).quantized_g(), 7);
   EXPECT_EQ(histogram.bins(0).quantized_b(), 7);
   EXPECT_THAT(histogram.bins(0).weight(), FloatEq(1.0));
 }
 
-TEST(VisualUtilsTest, GetHistogramForImageHalfWhiteHalfBlack) {
+TEST_F(VisualUtilsTest, GetHistogramForImageHalfWhiteHalfBlack) {
   VisualFeatures::ColorHistogram histogram;
-  SkBitmap bitmap;
-  bitmap.allocN32Pixels(200, 200);
+
+  // Draw white over half the image
+  for (int x = 0; x < 1000; x++)
+    for (int y = 0; y < 500; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(255, 255, 255);
 
   // Draw black over half the image.
-  for (int x = 0; x < 200; x++)
-    for (int y = 0; y < 100; y++)
-      *bitmap.getAddr32(x, y) = SkColorSetRGB(0, 0, 0);
+  for (int x = 0; x < 1000; x++)
+    for (int y = 500; y < 1000; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(0, 0, 0);
 
-  for (int x = 0; x < 200; x++)
-    for (int y = 100; y < 200; y++)
-      *bitmap.getAddr32(x, y) = SkColorSetRGB(255, 255, 255);
-
-  ASSERT_TRUE(GetHistogramForImage(bitmap, &histogram));
+  ASSERT_TRUE(GetHistogramForImage(bitmap_, &histogram));
   ASSERT_EQ(histogram.bins_size(), 2);
 
-  EXPECT_THAT(histogram.bins(0).centroid_x(), FloatEq(0.4975));
-  EXPECT_THAT(histogram.bins(0).centroid_y(), FloatEq(0.7475));
-  EXPECT_EQ(histogram.bins(0).quantized_r(), 7);
-  EXPECT_EQ(histogram.bins(0).quantized_g(), 7);
-  EXPECT_EQ(histogram.bins(0).quantized_b(), 7);
+  EXPECT_THAT(histogram.bins(0).centroid_x(), FloatEq(0.4995));
+  EXPECT_THAT(histogram.bins(0).centroid_y(), FloatEq(0.7495));
+  EXPECT_EQ(histogram.bins(0).quantized_r(), 0);
+  EXPECT_EQ(histogram.bins(0).quantized_g(), 0);
+  EXPECT_EQ(histogram.bins(0).quantized_b(), 0);
   EXPECT_THAT(histogram.bins(0).weight(), FloatEq(0.5));
 
-  EXPECT_THAT(histogram.bins(1).centroid_x(), FloatEq(0.4975));
-  EXPECT_THAT(histogram.bins(1).centroid_y(), FloatEq(0.2475));
-  EXPECT_EQ(histogram.bins(1).quantized_r(), 0);
-  EXPECT_EQ(histogram.bins(1).quantized_g(), 0);
-  EXPECT_EQ(histogram.bins(1).quantized_b(), 0);
+  EXPECT_THAT(histogram.bins(1).centroid_x(), FloatEq(0.4995));
+  EXPECT_THAT(histogram.bins(1).centroid_y(), FloatEq(0.2495));
+  EXPECT_EQ(histogram.bins(1).quantized_r(), 7);
+  EXPECT_EQ(histogram.bins(1).quantized_g(), 7);
+  EXPECT_EQ(histogram.bins(1).quantized_b(), 7);
   EXPECT_THAT(histogram.bins(1).weight(), FloatEq(0.5));
 }
 
+TEST_F(VisualUtilsTest, BlurImageWhite) {
+  VisualFeatures::BlurredImage blurred;
+
+  // Draw white over the image
+  for (int x = 0; x < 1000; x++)
+    for (int y = 0; y < 1000; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(255, 255, 255);
+
+  ASSERT_TRUE(GetBlurredImage(bitmap_, &blurred));
+  ASSERT_EQ(48, blurred.width());
+  ASSERT_EQ(48, blurred.height());
+  ASSERT_EQ(3u * 48u * 48u, blurred.data().size());
+  for (size_t i = 0; i < 48u * 48u; i++) {
+    EXPECT_EQ('\xff', blurred.data()[3 * i]);
+    EXPECT_EQ('\xff', blurred.data()[3 * i + 1]);
+    EXPECT_EQ('\xff', blurred.data()[3 * i + 2]);
+  }
+}
+
+TEST_F(VisualUtilsTest, BlurImageRed) {
+  VisualFeatures::BlurredImage blurred;
+
+  // Draw red over the image.
+  for (int x = 0; x < 1000; x++)
+    for (int y = 0; y < 1000; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(255, 0, 0);
+
+  ASSERT_TRUE(GetBlurredImage(bitmap_, &blurred));
+  ASSERT_EQ(48, blurred.width());
+  ASSERT_EQ(48, blurred.height());
+  ASSERT_EQ(3u * 48u * 48u, blurred.data().size());
+  for (size_t i = 0; i < 48u * 48u; i++) {
+    EXPECT_EQ('\xff', blurred.data()[3 * i]);
+    EXPECT_EQ('\x00', blurred.data()[3 * i + 1]);
+    EXPECT_EQ('\x00', blurred.data()[3 * i + 2]);
+  }
+}
+
+TEST_F(VisualUtilsTest, BlurImageHalfWhiteHalfBlack) {
+  VisualFeatures::BlurredImage blurred;
+
+  // Draw black over half the image.
+  for (int x = 0; x < 1000; x++)
+    for (int y = 0; y < 500; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(0, 0, 0);
+
+  // Draw white over half the image
+  for (int x = 0; x < 1000; x++)
+    for (int y = 500; y < 1000; y++)
+      *bitmap_.getAddr32(x, y) = SkColorSetRGB(255, 255, 255);
+
+  ASSERT_TRUE(GetBlurredImage(bitmap_, &blurred));
+  ASSERT_EQ(48, blurred.width());
+  ASSERT_EQ(48, blurred.height());
+  ASSERT_EQ(3u * 48u * 48u, blurred.data().size());
+  // The middle blocks may have been blurred to something between white and
+  // black, so only verify the first 22 and last 22 rows.
+  for (size_t i = 0; i < 22u * 48u; i++) {
+    EXPECT_EQ('\x00', blurred.data()[3 * i]);
+    EXPECT_EQ('\x00', blurred.data()[3 * i + 1]);
+    EXPECT_EQ('\x00', blurred.data()[3 * i + 2]);
+  }
+
+  for (size_t i = 26u * 48u; i < 48u * 48u; i++) {
+    EXPECT_EQ('\xff', blurred.data()[3 * i]);
+    EXPECT_EQ('\xff', blurred.data()[3 * i + 1]);
+    EXPECT_EQ('\xff', blurred.data()[3 * i + 2]);
+  }
+}
+
+TEST_F(VisualUtilsTest, PHashUniformImage) {
+  // Create 8x1 image.
+  VisualFeatures::BlurredImage blurred;
+  blurred.set_width(1);
+  blurred.set_height(8);
+  for (int i = 0; i < 8; i++) {
+    *blurred.mutable_data() += "\x30\x30\x30";
+  }
+
+  std::string phash;
+  ASSERT_TRUE(GetPHash(blurred, &phash));
+  EXPECT_EQ("\xff", phash);
+}
+
+TEST_F(VisualUtilsTest, PHashPadsExtraBits) {
+  // Create 9x1 image.
+  VisualFeatures::BlurredImage blurred;
+  blurred.set_width(1);
+  blurred.set_height(9);
+  for (int i = 0; i < 9; i++) {
+    *blurred.mutable_data() += "\x30\x30\x30";
+  }
+
+  std::string phash;
+  ASSERT_TRUE(GetPHash(blurred, &phash));
+  EXPECT_EQ("\xff\x80", phash);
+}
+
+TEST_F(VisualUtilsTest, PHashDistinctLuminances) {
+  // Create 9x1 image, with all pixels distinct
+  VisualFeatures::BlurredImage blurred;
+  blurred.set_width(1);
+  blurred.set_height(9);
+  for (int i = 0; i < 9; i++) {
+    *blurred.mutable_data() += "\x30\x30";
+    // Using 10*i to ensure that the luminances are distinct.
+    *blurred.mutable_data() += static_cast<char>(10 * i);
+  }
+
+  // With 9 distinct pixels, the first 4 will be below the median, and the last
+  // 5 will be at least the median.
+  std::string phash;
+  ASSERT_TRUE(GetPHash(blurred, &phash));
+  EXPECT_EQ("\x0f\x80", phash);
+}
+
 }  // namespace visual_utils
 }  // namespace safe_browsing
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.cc b/components/send_tab_to_self/send_tab_to_self_bridge.cc
index 549d513..c11495f8 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.cc
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.cc
@@ -24,12 +24,12 @@
 
 SendTabToSelfBridge::SendTabToSelfBridge(
     std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
-    syncer::LocalDeviceInfoProvider* local_device_info_provider,
+    const syncer::LocalDeviceInfoProvider* device_info_provider,
     base::Clock* clock)
     : ModelTypeSyncBridge(std::move(change_processor)),
-      local_device_info_provider_(local_device_info_provider),
+      local_device_info_provider_(device_info_provider),
       clock_(clock) {
-  DCHECK(local_device_info_provider_);
+  DCHECK(device_info_provider);
   DCHECK(clock_);
   auto batch = std::make_unique<syncer::MetadataBatch>();
   this->change_processor()->ModelReadyToSync(std::move(batch));
@@ -173,7 +173,11 @@
 
   change_processor()->Put(guid, std::move(entity_data), &metadata_change_list);
 
-  return entries_.emplace(guid, std::move(entry)).first->second.get();
+  const SendTabToSelfEntry* result =
+      entries_.emplace(guid, std::move(entry)).first->second.get();
+  NotifySendTabToSelfModelChanged();
+
+  return result;
 }
 
 void SendTabToSelfBridge::NotifySendTabToSelfModelChanged() {
diff --git a/components/send_tab_to_self/send_tab_to_self_bridge.h b/components/send_tab_to_self/send_tab_to_self_bridge.h
index 9cfc7e23..6e96ab3 100644
--- a/components/send_tab_to_self/send_tab_to_self_bridge.h
+++ b/components/send_tab_to_self/send_tab_to_self_bridge.h
@@ -37,7 +37,7 @@
   // |clock| must not be null and must outlive this object.
   SendTabToSelfBridge(
       std::unique_ptr<syncer::ModelTypeChangeProcessor> change_processor,
-      syncer::LocalDeviceInfoProvider* local_device_info_provider,
+      const syncer::LocalDeviceInfoProvider* local_device_info_provider,
       base::Clock* clock);
   ~SendTabToSelfBridge() override;
 
diff --git a/components/send_tab_to_self/send_tab_to_self_service.cc b/components/send_tab_to_self/send_tab_to_self_service.cc
index 49916b6..1acd8fc 100644
--- a/components/send_tab_to_self/send_tab_to_self_service.cc
+++ b/components/send_tab_to_self/send_tab_to_self_service.cc
@@ -16,7 +16,7 @@
 
 SendTabToSelfService::SendTabToSelfService(
     version_info::Channel channel,
-    syncer::LocalDeviceInfoProvider* local_device_info_provider) {
+    const syncer::LocalDeviceInfoProvider* local_device_info_provider) {
   bridge_ = std::make_unique<send_tab_to_self::SendTabToSelfBridge>(
       std::make_unique<syncer::ClientTagBasedModelTypeProcessor>(
           syncer::SEND_TAB_TO_SELF,
diff --git a/components/send_tab_to_self/send_tab_to_self_service.h b/components/send_tab_to_self/send_tab_to_self_service.h
index 44941a2..123334d 100644
--- a/components/send_tab_to_self/send_tab_to_self_service.h
+++ b/components/send_tab_to_self/send_tab_to_self_service.h
@@ -26,7 +26,7 @@
  public:
   SendTabToSelfService(
       version_info::Channel channel,
-      syncer::LocalDeviceInfoProvider* local_device_info_provider);
+      const syncer::LocalDeviceInfoProvider* local_device_info_provider);
   ~SendTabToSelfService() override;
 
   SendTabToSelfModel* GetSendTabToSelfModel();
diff --git a/components/subresource_filter/content/browser/subframe_navigation_filtering_throttle.cc b/components/subresource_filter/content/browser/subframe_navigation_filtering_throttle.cc
index ffcb7a93..9d5a874 100644
--- a/components/subresource_filter/content/browser/subframe_navigation_filtering_throttle.cc
+++ b/components/subresource_filter/content/browser/subframe_navigation_filtering_throttle.cc
@@ -11,6 +11,7 @@
 #include "base/debug/dump_without_crashing.h"
 #include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
+#include "base/strings/stringprintf.h"
 #include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
 #include "components/subresource_filter/core/browser/subresource_filter_constants.h"
 #include "components/subresource_filter/core/common/time_measurements.h"
diff --git a/components/sync/engine_impl/syncer_proto_util.cc b/components/sync/engine_impl/syncer_proto_util.cc
index cac498d..80b80a30 100644
--- a/components/sync/engine_impl/syncer_proto_util.cc
+++ b/components/sync/engine_impl/syncer_proto_util.cc
@@ -358,15 +358,22 @@
                             ClientToServerMessage::Contents_MAX + 1);
 
   std::map<int, std::string> progress_marker_token_per_data_type;
-  for (const sync_pb::DataTypeProgressMarker& progress_marker :
-       msg.get_updates().from_progress_marker()) {
-    progress_marker_token_per_data_type[progress_marker.data_type_id()] =
-        progress_marker.token();
-    UMA_HISTOGRAM_ENUMERATION(
-        "Sync.PostedDataTypeGetUpdatesRequest",
-        ModelTypeToHistogramInt(GetModelTypeFromSpecificsFieldNumber(
-            progress_marker.data_type_id())),
-        static_cast<int>(MODEL_TYPE_COUNT));
+
+  if (msg.has_get_updates()) {
+    UMA_HISTOGRAM_ENUMERATION("Sync.PostedGetUpdatesOrigin",
+                              msg.get_updates().get_updates_origin(),
+                              sync_pb::SyncEnums::GetUpdatesOrigin_ARRAYSIZE);
+
+    for (const sync_pb::DataTypeProgressMarker& progress_marker :
+         msg.get_updates().from_progress_marker()) {
+      progress_marker_token_per_data_type[progress_marker.data_type_id()] =
+          progress_marker.token();
+      UMA_HISTOGRAM_ENUMERATION(
+          "Sync.PostedDataTypeGetUpdatesRequest",
+          ModelTypeToHistogramInt(GetModelTypeFromSpecificsFieldNumber(
+              progress_marker.data_type_id())),
+          static_cast<int>(MODEL_TYPE_COUNT));
+    }
   }
 
   const base::Time start_time = base::Time::Now();
diff --git a/components/sync/model_impl/client_tag_based_model_type_processor.cc b/components/sync/model_impl/client_tag_based_model_type_processor.cc
index c76a24ef..abd575e 100644
--- a/components/sync/model_impl/client_tag_based_model_type_processor.cc
+++ b/components/sync/model_impl/client_tag_based_model_type_processor.cc
@@ -16,6 +16,7 @@
 #include "base/trace_event/memory_usage_estimator.h"
 #include "components/sync/base/data_type_histogram.h"
 #include "components/sync/base/hash_util.h"
+#include "components/sync/base/model_type.h"
 #include "components/sync/base/time.h"
 #include "components/sync/engine/commit_queue.h"
 #include "components/sync/engine/data_type_activation_response.h"
@@ -59,6 +60,19 @@
   return count;
 }
 
+void LogNonReflectionUpdateFreshnessToUma(ModelType type,
+                                          base::Time remote_modification_time) {
+  const base::TimeDelta latency = base::Time::Now() - remote_modification_time;
+
+  UMA_HISTOGRAM_LONG_TIMES("Sync.NonReflectionUpdateFreshnessPossiblySkewed",
+                           latency);
+
+  base::UmaHistogramLongTimes(
+      std::string("Sync.NonReflectionUpdateFreshnessPossiblySkewed.") +
+          ModelTypeToHistogramSuffix(type),
+      latency);
+}
+
 }  // namespace
 
 ClientTagBasedModelTypeProcessor::ClientTagBasedModelTypeProcessor(
@@ -1058,6 +1072,12 @@
       // have server tags instead).
       continue;
     }
+
+    LogNonReflectionUpdateFreshnessToUma(
+        type_,
+        /*remote_modification_time=*/
+        ProtoTimeToTime(entity->metadata().modification_time()));
+
     if (entity->storage_key().empty()) {
       // Storage key of this entity is not known yet. Don't update metadata, it
       // will be done from UpdateStorageKey.
diff --git a/components/sync/syncable/model_type.cc b/components/sync/syncable/model_type.cc
index bb45a57..2b50bc1 100644
--- a/components/sync/syncable/model_type.cc
+++ b/components/sync/syncable/model_type.cc
@@ -21,6 +21,7 @@
 #include "components/sync/protocol/preference_specifics.pb.h"
 #include "components/sync/protocol/reading_list_specifics.pb.h"
 #include "components/sync/protocol/search_engine_specifics.pb.h"
+#include "components/sync/protocol/send_tab_to_self_specifics.pb.h"
 #include "components/sync/protocol/session_specifics.pb.h"
 #include "components/sync/protocol/sync.pb.h"
 #include "components/sync/protocol/theme_specifics.pb.h"
diff --git a/components/variations/service/variations_service.cc b/components/variations/service/variations_service.cc
index 13d1bca..0583214 100644
--- a/components/variations/service/variations_service.cc
+++ b/components/variations/service/variations_service.cc
@@ -503,11 +503,11 @@
 
   safe_seed_manager_.RecordFetchStarted();
 
-  // Normally, there shouldn't be a |pending_request_| when this fires. However
-  // it's not impossible - for example if Chrome was paused (e.g. in a debugger
-  // or if the machine was suspended) and OnURLFetchComplete() hasn't had a
-  // chance to run yet from the previous request. In this case, don't start a
-  // new request and just let the previous one finish.
+  // Normally, there shouldn't be a |pending_seed_request_| when this fires.
+  // However it's not impossible - for example if Chrome was paused (e.g. in a
+  // debugger or if the machine was suspended) and OnURLFetchComplete() hasn't
+  // had a chance to run yet from the previous request. In this case, don't
+  // start a new request and just let the previous one finish.
   if (pending_seed_request_)
     return false;
 
@@ -920,6 +920,10 @@
   field_trial_creator_.OverrideCachedUIStrings();
 }
 
+void VariationsService::CancelCurrentRequestForTesting() {
+  pending_seed_request_.reset();
+}
+
 std::string VariationsService::GetStoredPermanentCountry() {
   const base::ListValue* list_value =
       local_state_->GetList(prefs::kVariationsPermanentConsistencyCountry);
diff --git a/components/variations/service/variations_service.h b/components/variations/service/variations_service.h
index 217a7c5..bc5fd826b 100644
--- a/components/variations/service/variations_service.h
+++ b/components/variations/service/variations_service.h
@@ -176,6 +176,11 @@
   // Exposed for testing.
   void GetClientFilterableStateForVersionCalledForTesting();
 
+  web_resource::ResourceRequestAllowedNotifier*
+  GetResourceRequestAllowedNotifierForTesting() {
+    return resource_request_allowed_notifier_.get();
+  }
+
   // Wrapper around VariationsFieldTrialCreator::SetupFieldTrials().
   bool SetupFieldTrials(const char* kEnableGpuBenchmarking,
                         const char* kEnableFeatures,
@@ -190,6 +195,9 @@
 
   int request_count() const { return request_count_; }
 
+  // Cancels the currently pending fetch request.
+  void CancelCurrentRequestForTesting();
+
  protected:
   // Starts the fetching process once, where |OnURLFetchComplete| is called with
   // the response. This calls DoFetchToURL with the set url.
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
index d79cb19..aa67edf7 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.cc
@@ -214,15 +214,19 @@
 
     GrGLFramebufferInfo framebuffer_info;
     framebuffer_info.fFBOID = 0;
-    framebuffer_info.fFormat =
-        gl_version_info_->is_es ? GL_BGRA8_EXT : GL_RGBA8;
+    if (supports_alpha_) {
+      framebuffer_info.fFormat =
+          gl_version_info_->is_es ? GL_BGRA8_EXT : GL_RGBA8;
+    } else {
+      framebuffer_info.fFormat = GL_RGB8_OES;
+    }
 
     GrBackendRenderTarget render_target(size.width(), size.height(), 0, 8,
                                         framebuffer_info);
 
     sk_surface_ = SkSurface::MakeFromBackendRenderTarget(
         gr_context(), render_target, kBottomLeft_GrSurfaceOrigin,
-        kBGRA_8888_SkColorType, color_space.ToSkColorSpace(), &surface_props);
+        FramebufferColorType(), color_space.ToSkColorSpace(), &surface_props);
     DCHECK(sk_surface_);
   } else {
 #if BUILDFLAG(ENABLE_VULKAN)
@@ -624,32 +628,39 @@
     return;
 
   auto* context = context_state_->real_context();
+  auto* current_gl = context->GetCurrentGL();
+  api_ = current_gl->Api;
   gl_version_info_ = context->GetVersionInfo();
 
   capabilities_.flipped_output_surface = gl_surface_->FlipsVertically();
 
-  // Get stencil bits from the default frame buffer.
-  auto* current_gl = context->GetCurrentGL();
-  const auto* version = current_gl->Version;
-  auto* api = current_gl->Api;
-  api->glBindFramebufferEXTFn(GL_FRAMEBUFFER, 0);
+  // Get alpha and stencil bits from the default frame buffer.
+  api_->glBindFramebufferEXTFn(GL_FRAMEBUFFER, 0);
   gr_context()->resetContext(kRenderTarget_GrGLBackendState);
+  const auto* version = current_gl->Version;
   GLint stencil_bits = 0;
+  GLint alpha_bits = 0;
   if (version->is_desktop_core_profile) {
-    api->glGetFramebufferAttachmentParameterivEXTFn(
+    api_->glGetFramebufferAttachmentParameterivEXTFn(
         GL_FRAMEBUFFER, GL_STENCIL, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE,
         &stencil_bits);
+    api_->glGetFramebufferAttachmentParameterivEXTFn(
+        GL_FRAMEBUFFER, GL_BACK_LEFT, GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE,
+        &alpha_bits);
   } else {
-    api->glGetIntegervFn(GL_STENCIL_BITS, &stencil_bits);
+    api_->glGetIntegervFn(GL_STENCIL_BITS, &stencil_bits);
+    api_->glGetIntegervFn(GL_ALPHA_BITS, &alpha_bits);
   }
   CHECK_GL_ERROR();
   capabilities_.supports_stencil = stencil_bits > 0;
+  supports_alpha_ = alpha_bits > 0;
 }
 
 void SkiaOutputSurfaceImplOnGpu::InitializeForVulkan(
     GpuServiceImpl* gpu_service) {
   context_state_ = gpu_service->GetContextStateForVulkan();
   DCHECK(context_state_);
+  supports_alpha_ = true;
 }
 
 void SkiaOutputSurfaceImplOnGpu::BindOrCopyTextureIfNecessary(
@@ -707,7 +718,7 @@
                                         vk_image_info);
     sk_surface = SkSurface::MakeFromBackendRenderTarget(
         gr_context(), render_target, kTopLeft_GrSurfaceOrigin,
-        kBGRA_8888_SkColorType, nullptr, &surface_props);
+        FramebufferColorType(), nullptr, &surface_props);
     DCHECK(sk_surface);
   } else {
     auto backend = sk_surface->getBackendRenderTarget(
diff --git a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
index d5d4d38..50c9521 100644
--- a/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
+++ b/components/viz/service/display_embedder/skia_output_surface_impl_on_gpu.h
@@ -38,6 +38,7 @@
 }
 
 namespace gl {
+class GLApi;
 class GLSurface;
 }
 
@@ -174,6 +175,10 @@
 
   GrContext* gr_context() { return context_state_->gr_context(); }
 
+  SkColorType FramebufferColorType() {
+    return supports_alpha_ ? kBGRA_8888_SkColorType : kRGB_888x_SkColorType;
+  }
+
   bool is_using_vulkan() const { return !!vulkan_context_provider_; }
 
   const gpu::SurfaceHandle surface_handle_;
@@ -229,6 +234,9 @@
 
   ui::LatencyTracker latency_tracker_;
 
+  gl::GLApi* api_ = nullptr;
+  bool supports_alpha_ = false;
+
   THREAD_CHECKER(thread_checker_);
 
   base::WeakPtr<SkiaOutputSurfaceImplOnGpu> weak_ptr_;
diff --git a/content/browser/accessibility/accessibility_tree_formatter_win.cc b/content/browser/accessibility/accessibility_tree_formatter_win.cc
index abf05c8..14c6537 100644
--- a/content/browser/accessibility/accessibility_tree_formatter_win.cc
+++ b/content/browser/accessibility/accessibility_tree_formatter_win.cc
@@ -724,7 +724,6 @@
     return related_accessibles_string;
 
   base::win::ScopedVariant variant_self(CHILDID_SELF);
-
   for (int index = 0; index < num_accessibles; index++) {
     related_accessibles_string += index > 0 ? L"," : L"<";
     Microsoft::WRL::ComPtr<IUnknown> unknown = accessibles[index];
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
index 50985897..90eb615 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.cc
@@ -13,6 +13,7 @@
 #include "content/public/browser/media_session.h"
 #include "content/public/browser/overlay_window.h"
 #include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/content_client.h"
 #include "media/base/media_switches.h"
 
diff --git a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
index 6f302c5..a7832f06 100644
--- a/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
+++ b/content/browser/picture_in_picture/picture_in_picture_window_controller_impl.h
@@ -8,14 +8,17 @@
 #include "base/memory/weak_ptr.h"
 #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
 #include "content/public/browser/picture_in_picture_window_controller.h"
+#include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "mojo/public/cpp/bindings/binding.h"
 #include "services/media_session/public/cpp/media_metadata.h"
 #include "services/media_session/public/mojom/media_session.mojom.h"
 
 namespace content {
+
 class OverlaySurfaceEmbedder;
 class WebContents;
+class WebContentsImpl;
 class MediaWebContentsObserver;
 
 // TODO(thakis,mlamouri): PictureInPictureWindowControllerImpl isn't
diff --git a/content/browser/portal/portal.cc b/content/browser/portal/portal.cc
index 65c34a95..093cfc2 100644
--- a/content/browser/portal/portal.cc
+++ b/content/browser/portal/portal.cc
@@ -4,6 +4,8 @@
 
 #include "content/browser/portal/portal.h"
 
+#include <utility>
+
 #include "base/feature_list.h"
 #include "base/memory/ptr_util.h"
 #include "content/browser/frame_host/render_frame_host_impl.h"
@@ -101,41 +103,24 @@
   portal_contents_impl_->GetController().LoadURLWithParams(load_url_params);
 }
 
-void Portal::Activate(
-    base::OnceCallback<void(blink::mojom::PortalActivationStatus)> callback) {
+void Portal::Activate(base::OnceCallback<void()> callback) {
   WebContents* outer_contents =
       WebContents::FromRenderFrameHost(owner_render_frame_host_);
   WebContentsDelegate* delegate = outer_contents->GetDelegate();
-  if (delegate) {
-    FrameTreeNode* outer_node = FrameTreeNode::GloballyFindByID(
-        portal_contents_impl_->GetOuterDelegateFrameTreeNodeId());
-    bool is_loading = portal_contents_impl_->IsLoading();
-    std::unique_ptr<WebContents> portal_contents =
-        portal_contents_impl_->DetachFromOuterWebContents();
-    // TODO(lfg): If there are nested portals, this would replace the entire tab
-    // upon a nested portal's activation. We should handle that case so that it
-    // would only replace the nested portal's contents.
-    std::unique_ptr<WebContents> contents = delegate->SwapWebContents(
-        outer_contents, std::move(portal_contents), true, is_loading);
-
-    if (contents.get() == outer_contents) {
-      // TODO(lfg): The old WebContents is currently discarded, but should be
-      // kept and passed to the new page.
-      portal_contents_impl_->set_portal(nullptr);
-      portal_contents_impl_->GetMainFrame()->OnPortalActivated();
-      std::move(callback).Run(blink::mojom::PortalActivationStatus::kSuccess);
-    } else {
-      DCHECK_EQ(portal_contents_impl_, contents.get());
-      portal_contents_impl_->AttachToOuterWebContentsFrame(
-          std::move(contents), outer_node->current_frame_host());
-      std::move(callback).Run(
-          blink::mojom::PortalActivationStatus::kNotSupported);
-    }
-
-    return;
-  }
-
-  std::move(callback).Run(blink::mojom::PortalActivationStatus::kNotSupported);
+  bool is_loading = portal_contents_impl_->IsLoading();
+  std::unique_ptr<WebContents> portal_contents =
+      portal_contents_impl_->DetachFromOuterWebContents();
+  // TODO(lfg): If there are nested portals, this would replace the entire tab
+  // upon a nested portal's activation. We should handle that case so that it
+  // would only replace the nested portal's contents. https://crbug.com/919110
+  std::unique_ptr<WebContents> contents = delegate->SwapWebContents(
+      outer_contents, std::move(portal_contents), true, is_loading);
+  CHECK_EQ(contents.get(), outer_contents);
+  // TODO(lfg): The old WebContents is currently discarded, but should be
+  // kept and passed to the new page. https://crbug.com/914122
+  portal_contents_impl_->set_portal(nullptr);
+  portal_contents_impl_->GetMainFrame()->OnPortalActivated();
+  std::move(callback).Run();
 }
 
 void Portal::RenderFrameDeleted(RenderFrameHost* render_frame_host) {
diff --git a/content/browser/portal/portal.h b/content/browser/portal/portal.h
index a58818dec..c3bff09 100644
--- a/content/browser/portal/portal.h
+++ b/content/browser/portal/portal.h
@@ -49,8 +49,7 @@
 
   // blink::mojom::Portal implementation.
   void Navigate(const GURL& url) override;
-  void Activate(base::OnceCallback<void(blink::mojom::PortalActivationStatus)>
-                    callback) override;
+  void Activate(base::OnceCallback<void()> callback) override;
 
   // WebContentsObserver overrides.
   void RenderFrameDeleted(RenderFrameHost* render_frame_host) override;
diff --git a/content/browser/portal/portal_browsertest.cc b/content/browser/portal/portal_browsertest.cc
index 7d35f324..db38596 100644
--- a/content/browser/portal/portal_browsertest.cc
+++ b/content/browser/portal/portal_browsertest.cc
@@ -41,8 +41,7 @@
       blink::mojom::PortalRequest request);
   static PortalInterceptorForTesting* From(content::Portal* portal);
 
-  void Activate(base::OnceCallback<void(blink::mojom::PortalActivationStatus)>
-                    callback) override {
+  void Activate(base::OnceCallback<void()> callback) override {
     portal_activated_ = true;
 
     if (run_loop_) {
@@ -102,25 +101,6 @@
   return interceptor;
 }
 
-class MockPortalWebContentsDelegate : public WebContentsDelegate {
- public:
-  MockPortalWebContentsDelegate() {}
-  ~MockPortalWebContentsDelegate() override {}
-
-  MOCK_METHOD4(
-      DoSwapWebContents,
-      std::unique_ptr<WebContents>(WebContents*, WebContents*, bool, bool));
-  std::unique_ptr<WebContents> SwapWebContents(
-      WebContents* old_contents,
-      std::unique_ptr<WebContents> new_contents,
-      bool did_start_load,
-      bool did_finish_load) override {
-    DoSwapWebContents(old_contents, new_contents.get(), did_start_load,
-                      did_finish_load);
-    return new_contents;
-  }
-};
-
 // The PortalCreatedObserver observes portal creations on
 // |render_frame_host_impl|. This observer can be used to monitor for multiple
 // Portal creations on the same RenderFrameHost, by repeatedly calling
@@ -275,45 +255,6 @@
   }
 }
 
-// Tests that the WebContentsDelegate will receive a request to swap the
-// WebContents when a portal is activated.
-// Disabled due to flakiness on Android.  See https://crbug.com/892669.
-#if defined(OS_ANDROID)
-#define MAYBE_ActivatePortal DISABLED_ActivatePortal
-#else
-#define MAYBE_ActivatePortal ActivatePortal
-#endif
-
-IN_PROC_BROWSER_TEST_F(PortalBrowserTest, MAYBE_ActivatePortal) {
-  EXPECT_TRUE(NavigateToURL(
-      shell(), embedded_test_server()->GetURL("portal.test", "/title1.html")));
-  WebContentsImpl* web_contents_impl =
-      static_cast<WebContentsImpl*>(shell()->web_contents());
-  RenderFrameHostImpl* main_frame = web_contents_impl->GetMainFrame();
-
-  PortalCreatedObserver portal_created_observer(main_frame);
-  GURL a_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
-  EXPECT_TRUE(ExecJs(main_frame,
-                     JsReplace("var portal = document.createElement('portal');"
-                               "portal.src = $1;"
-                               "document.body.appendChild(portal);",
-                               a_url)));
-  Portal* portal = portal_created_observer.WaitUntilPortalCreated();
-  MockPortalWebContentsDelegate mock_delegate;
-  shell()->web_contents()->SetDelegate(&mock_delegate);
-
-  base::RunLoop run_loop;
-  EXPECT_CALL(mock_delegate,
-              DoSwapWebContents(shell()->web_contents(),
-                                portal->GetPortalContents(), _, _))
-      .WillOnce(testing::DoAll(
-          testing::InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit),
-          testing::ReturnNull()));
-  EXPECT_TRUE(
-      ExecJs(main_frame, "document.querySelector('portal').activate();"));
-  run_loop.Run();
-}
-
 // Tests that a portal can be activated in content_shell.
 IN_PROC_BROWSER_TEST_F(PortalBrowserTest, ActivatePortalInShell) {
   EXPECT_TRUE(NavigateToURL(
diff --git a/content/browser/renderer_host/input/fling_browsertest.cc b/content/browser/renderer_host/input/fling_browsertest.cc
index 0741032..f172974 100644
--- a/content/browser/renderer_host/input/fling_browsertest.cc
+++ b/content/browser/renderer_host/input/fling_browsertest.cc
@@ -501,60 +501,6 @@
   EXPECT_TRUE(
       router->forced_last_fling_start_target_to_stop_flinging_for_test());
 }
-
-// Flaky, see https://crbug.com/850455
-#define MAYBE_ScrollEndGeneratedForFilteredFling \
-  DISABLED_ScrollEndGeneratedForFilteredFling
-IN_PROC_BROWSER_TEST_F(BrowserSideFlingBrowserTest,
-                       MAYBE_ScrollEndGeneratedForFilteredFling) {
-  LoadURL(kTouchActionFilterDataURL);
-
-  // Necessary for checking the ACK source of the sent events. The events are
-  // filtered when the Browser is the source.
-  auto scroll_begin_watcher = std::make_unique<InputMsgWatcher>(
-      GetWidgetHost(), blink::WebInputEvent::kGestureScrollBegin);
-  auto fling_start_watcher = std::make_unique<InputMsgWatcher>(
-      GetWidgetHost(), blink::WebInputEvent::kGestureFlingStart);
-  auto scroll_end_watcher = std::make_unique<InputMsgWatcher>(
-      GetWidgetHost(), blink::WebInputEvent::kGestureScrollEnd);
-
-  // Do a horizontal touchscreen scroll followed by a fling. The GFS must get
-  // filtered since the GSB is filtered.
-  SyntheticSmoothScrollGestureParams params;
-  params.gesture_source_type = SyntheticGestureParams::TOUCH_INPUT;
-  params.anchor = gfx::PointF(10, 10);
-  params.distances.push_back(gfx::Vector2d(-60, 0));
-  params.prevent_fling = false;
-
-  run_loop_ = std::make_unique<base::RunLoop>();
-
-  std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
-      new SyntheticSmoothScrollGesture(params));
-  GetWidgetHost()->QueueSyntheticGesture(
-      std::move(gesture),
-      base::BindOnce(&BrowserSideFlingBrowserTest::OnSyntheticGestureCompleted,
-                     base::Unretained(this)));
-
-  // Runs until we get the OnSyntheticGestureCompleted callback.
-  run_loop_->Run();
-
-  scroll_begin_watcher->GetAckStateWaitIfNecessary();
-  EXPECT_EQ(InputEventAckSource::BROWSER,
-            scroll_begin_watcher->last_event_ack_source());
-
-  fling_start_watcher->GetAckStateWaitIfNecessary();
-  EXPECT_EQ(InputEventAckSource::BROWSER,
-            fling_start_watcher->last_event_ack_source());
-
-  // Since the GFS is filtered. the input_router_impl will generate and forward
-  // a GSE to make sure that the scrolling sequence and the touch action filter
-  // state get reset properly. The generated GSE will also get filtered since
-  // its equivalent GSB is filtered. The test will timeout if the GSE is not
-  // generated.
-  scroll_end_watcher->GetAckStateWaitIfNecessary();
-  EXPECT_EQ(InputEventAckSource::BROWSER,
-            scroll_end_watcher->last_event_ack_source());
-}
 #endif  // !defined(OS_MACOSX)
 
 }  // namespace content
diff --git a/content/browser/renderer_host/input/touch_action_filter.cc b/content/browser/renderer_host/input/touch_action_filter.cc
index 6c13e1a..18bb05e 100644
--- a/content/browser/renderer_host/input/touch_action_filter.cc
+++ b/content/browser/renderer_host/input/touch_action_filter.cc
@@ -256,7 +256,9 @@
       break;
 
     case WebInputEvent::kGestureScrollEnd:
-      gesture_sequence_.clear();
+      if (gesture_sequence_.size() >= 1000)
+        gesture_sequence_.erase(gesture_sequence_.begin(),
+                                gesture_sequence_.end() - 250);
       gesture_sequence_in_progress_ = false;
       // Whenever there is a new touch start, white listed touch action will be
       // set. So it is fine to reset at GSE.
@@ -350,6 +352,17 @@
         gesture_sequence_.append("OY");
       else
         gesture_sequence_.append("ON");
+      if (compositor_touch_action_enabled_ &&
+          !allowed_touch_action_.has_value() &&
+          !white_listed_touch_action_.has_value()) {
+        static auto* crash_key = base::debug::AllocateCrashKeyString(
+            "tapdown-gestures", base::debug::CrashKeySize::Size256);
+        if (gesture_sequence_.size() >= 256)
+          gesture_sequence_.erase(gesture_sequence_.begin(),
+                                  gesture_sequence_.end() - 256);
+        base::debug::SetCrashKeyString(crash_key, gesture_sequence_);
+        gesture_sequence_.clear();
+      }
       // TODO(xidachen): investigate why the touch action has no value.
       if (compositor_touch_action_enabled_ && !touch_action.has_value())
         SetTouchAction(cc::kTouchActionAuto);
diff --git a/content/browser/renderer_host/render_widget_host_delegate.cc b/content/browser/renderer_host/render_widget_host_delegate.cc
index 3b51afe5..1c9aa4c 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.cc
+++ b/content/browser/renderer_host/render_widget_host_delegate.cc
@@ -12,14 +12,6 @@
 
 namespace content {
 
-bool RenderWidgetHostDelegate::DoBrowserControlsShrinkRendererSize() const {
-  return false;
-}
-
-int RenderWidgetHostDelegate::GetTopControlsHeight() const {
-  return 0;
-}
-
 KeyboardEventProcessingResult RenderWidgetHostDelegate::PreHandleKeyboardEvent(
     const NativeWebKeyboardEvent& event) {
   return KeyboardEventProcessingResult::NOT_HANDLED;
diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h
index aef0cf02..688a332 100644
--- a/content/browser/renderer_host/render_widget_host_delegate.h
+++ b/content/browser/renderer_host/render_widget_host_delegate.h
@@ -61,8 +61,6 @@
   virtual void SetTopControlsShownRatio(
       RenderWidgetHostImpl* render_widget_host,
       float ratio) {}
-  virtual bool DoBrowserControlsShrinkRendererSize() const;
-  virtual int GetTopControlsHeight() const;
   virtual void SetTopControlsGestureScrollInProgress(bool in_progress) {}
 
   // The RenderWidgetHost has just been created.
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 316e7cf..43baa4c 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -872,23 +872,37 @@
   visual_properties->page_scale_factor = page_scale_factor_;
 
   if (view_) {
+    // TODO(danakj): Move this browser controls code out of the if-view block?
+    if (delegate_) {
+      RenderViewHostDelegateView* rvh_delegate_view =
+          delegate_->GetDelegateView();
+      DCHECK(rvh_delegate_view);
+
+      visual_properties->browser_controls_shrink_blink_size =
+          rvh_delegate_view->DoBrowserControlsShrinkRendererSize();
+
+      float top_controls_height = rvh_delegate_view->GetTopControlsHeight();
+      float bottom_controls_height =
+          rvh_delegate_view->GetBottomControlsHeight();
+      float browser_controls_dsf_multiplier = 1.f;
+      // The top and bottom control sizes are physical pixels but the IPC wants
+      // DIPs *when not using page zoom for DSF* because blink layout is working
+      // in DIPs then.
+      if (!IsUseZoomForDSFEnabled()) {
+        browser_controls_dsf_multiplier =
+            visual_properties->screen_info.device_scale_factor;
+      }
+      visual_properties->top_controls_height =
+          top_controls_height / browser_controls_dsf_multiplier;
+      visual_properties->bottom_controls_height =
+          bottom_controls_height / browser_controls_dsf_multiplier;
+    }
+
     visual_properties->new_size = view_->GetRequestedRendererSize();
     visual_properties->capture_sequence_number =
         view_->GetCaptureSequenceNumber();
     visual_properties->compositor_viewport_pixel_size =
         view_->GetCompositorViewportPixelSize();
-    visual_properties->top_controls_height = view_->GetTopControlsHeight();
-    visual_properties->bottom_controls_height =
-        view_->GetBottomControlsHeight();
-    if (!IsUseZoomForDSFEnabled()) {
-      float device_scale = visual_properties->screen_info.device_scale_factor;
-      if (device_scale != 0.f) {
-        visual_properties->top_controls_height /= device_scale;
-        visual_properties->bottom_controls_height /= device_scale;
-      }
-    }
-    visual_properties->browser_controls_shrink_blink_size =
-        view_->DoBrowserControlsShrinkRendererSize();
     visual_properties->visible_viewport_size = view_->GetVisibleViewportSize();
     // TODO(ccameron): GetLocalSurfaceId is not synchronized with the device
     // scale factor of the surface. Fix this.
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 453a9d24..1492a21 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -334,9 +334,7 @@
         acked_event_count_(0),
         gesture_event_type_(-1),
         use_fake_compositor_viewport_pixel_size_(false),
-        ack_result_(INPUT_EVENT_ACK_STATE_UNKNOWN),
-        top_controls_height_(0.f),
-        bottom_controls_height_(0.f) {
+        ack_result_(INPUT_EVENT_ACK_STATE_UNKNOWN) {
     local_surface_id_allocator_.GenerateId();
   }
 
@@ -361,14 +359,6 @@
     *screen_info = screen_info_;
   }
 
-  void set_top_controls_height(float top_controls_height) {
-    top_controls_height_ = top_controls_height;
-  }
-
-  void set_bottom_controls_height(float bottom_controls_height) {
-    bottom_controls_height_ = bottom_controls_height;
-  }
-
   const WebTouchEvent& acked_event() const { return acked_event_; }
   int acked_event_count() const { return acked_event_count_; }
   void ClearAckedEvent() {
@@ -409,10 +399,6 @@
 
   // RenderWidgetHostView override.
   gfx::Rect GetViewBounds() const override { return bounds_; }
-  float GetTopControlsHeight() const override { return top_controls_height_; }
-  float GetBottomControlsHeight() const override {
-    return bottom_controls_height_;
-  }
   const viz::LocalSurfaceIdAllocation& GetLocalSurfaceIdAllocation()
       const override {
     return local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
@@ -454,8 +440,6 @@
   bool use_fake_compositor_viewport_pixel_size_;
   gfx::Size mock_compositor_viewport_pixel_size_;
   InputEventAckState ack_result_;
-  float top_controls_height_;
-  float bottom_controls_height_;
   viz::BeginFrameAck last_did_not_produce_frame_ack_;
   viz::ParentLocalSurfaceIdAllocator local_surface_id_allocator_;
   ScreenInfo screen_info_;
diff --git a/content/browser/renderer_host/render_widget_host_view_android.cc b/content/browser/renderer_host/render_widget_host_view_android.cc
index 575ff95..93a418bcb 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.cc
+++ b/content/browser/renderer_host/render_widget_host_view_android.cc
@@ -504,22 +504,6 @@
   return view_.GetPhysicalBackingSize();
 }
 
-bool RenderWidgetHostViewAndroid::DoBrowserControlsShrinkRendererSize() const {
-  auto* delegate_view = GetRenderViewHostDelegateView();
-  return delegate_view ? delegate_view->DoBrowserControlsShrinkRendererSize()
-                       : false;
-}
-
-float RenderWidgetHostViewAndroid::GetTopControlsHeight() const {
-  auto* delegate_view = GetRenderViewHostDelegateView();
-  return delegate_view ? delegate_view->GetTopControlsHeight() : 0.f;
-}
-
-float RenderWidgetHostViewAndroid::GetBottomControlsHeight() const {
-  auto* delegate_view = GetRenderViewHostDelegateView();
-  return delegate_view ? delegate_view->GetBottomControlsHeight() : 0.f;
-}
-
 int RenderWidgetHostViewAndroid::GetMouseWheelMinimumGranularity() const {
   auto* window = view_.GetWindowAndroid();
   if (!window)
@@ -1553,8 +1537,12 @@
 
 void RenderWidgetHostViewAndroid::TransformPointToRootSurface(
     gfx::PointF* point) {
-  *point += gfx::Vector2d(
-      0, DoBrowserControlsShrinkRendererSize() ? GetTopControlsHeight() : 0);
+  if (!host()->delegate())
+    return;
+  RenderViewHostDelegateView* rvh_delegate_view =
+      host()->delegate()->GetDelegateView();
+  if (rvh_delegate_view->DoBrowserControlsShrinkRendererSize())
+    *point += gfx::Vector2d(0, rvh_delegate_view->GetTopControlsHeight());
 }
 
 // TODO(jrg): Find out the implications and answer correctly here,
@@ -1597,12 +1585,6 @@
   gesture_listener_manager_->GestureEventAck(event, ack_result);
 }
 
-RenderViewHostDelegateView*
-RenderWidgetHostViewAndroid::GetRenderViewHostDelegateView() const {
-  RenderWidgetHostDelegate* delegate = host()->delegate();
-  return delegate ? delegate->GetDelegateView() : nullptr;
-}
-
 InputEventAckState RenderWidgetHostViewAndroid::FilterInputEvent(
     const blink::WebInputEvent& input_event) {
   if (overscroll_controller_ &&
diff --git a/content/browser/renderer_host/render_widget_host_view_android.h b/content/browser/renderer_host/render_widget_host_view_android.h
index a70fe2f9..de49eae 100644
--- a/content/browser/renderer_host/render_widget_host_view_android.h
+++ b/content/browser/renderer_host/render_widget_host_view_android.h
@@ -119,9 +119,6 @@
       base::OnceCallback<void(const SkBitmap&)> callback) override;
   void EnsureSurfaceSynchronizedForWebTest() override;
   uint32_t GetCaptureSequenceNumber() const override;
-  bool DoBrowserControlsShrinkRendererSize() const override;
-  float GetTopControlsHeight() const override;
-  float GetBottomControlsHeight() const override;
   int GetMouseWheelMinimumGranularity() const override;
   void UpdateCursor(const WebCursor& cursor) override;
   void SetIsLoading(bool is_loading) override;
@@ -405,7 +402,6 @@
                         float mouse_down_y);
 
   WebContentsAccessibilityAndroid* GetWebContentsAccessibilityAndroid() const;
-  RenderViewHostDelegateView* GetRenderViewHostDelegateView() const;
 
   void OnFocusInternal();
   void LostFocusInternal();
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.cc b/content/browser/renderer_host/render_widget_host_view_aura.cc
index cffe395..c037ef3 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura.cc
@@ -844,15 +844,6 @@
   return latest_capture_sequence_number_;
 }
 
-bool RenderWidgetHostViewAura::DoBrowserControlsShrinkRendererSize() const {
-  return host()->delegate() &&
-         host()->delegate()->DoBrowserControlsShrinkRendererSize();
-}
-
-float RenderWidgetHostViewAura::GetTopControlsHeight() const {
-  return host()->delegate() ? host()->delegate()->GetTopControlsHeight() : 0;
-}
-
 void RenderWidgetHostViewAura::CopyFromSurface(
     const gfx::Rect& src_subrect,
     const gfx::Size& dst_size,
diff --git a/content/browser/renderer_host/render_widget_host_view_aura.h b/content/browser/renderer_host/render_widget_host_view_aura.h
index 44e7879..fb40b669 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura.h
+++ b/content/browser/renderer_host/render_widget_host_view_aura.h
@@ -137,8 +137,6 @@
   void SetTooltipText(const base::string16& tooltip_text) override;
   void DisplayTooltipText(const base::string16& tooltip_text) override;
   uint32_t GetCaptureSequenceNumber() const override;
-  bool DoBrowserControlsShrinkRendererSize() const override;
-  float GetTopControlsHeight() const override;
   bool IsSurfaceAvailableForCopy() const override;
   void CopyFromSurface(
       const gfx::Rect& src_rect,
diff --git a/content/browser/renderer_host/render_widget_host_view_base.cc b/content/browser/renderer_host/render_widget_host_view_base.cc
index 7e2761a..318a27b 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.cc
+++ b/content/browser/renderer_host/render_widget_host_view_base.cc
@@ -145,14 +145,6 @@
                                 GetDeviceScaleFactor());
 }
 
-bool RenderWidgetHostViewBase::DoBrowserControlsShrinkRendererSize() const {
-  return false;
-}
-
-float RenderWidgetHostViewBase::GetTopControlsHeight() const {
-  return 0.f;
-}
-
 void RenderWidgetHostViewBase::SelectionBoundsChanged(
     const WidgetHostMsg_SelectionBounds_Params& params) {
 #if !defined(OS_ANDROID)
@@ -163,10 +155,6 @@
 #endif
 }
 
-float RenderWidgetHostViewBase::GetBottomControlsHeight() const {
-  return 0.f;
-}
-
 int RenderWidgetHostViewBase::GetMouseWheelMinimumGranularity() const {
   // Most platforms can specify the floating-point delta in the wheel event so
   // they don't have a minimum granularity. Android is currently the only
diff --git a/content/browser/renderer_host/render_widget_host_view_base.h b/content/browser/renderer_host/render_widget_host_view_base.h
index b7febba..e36a58a2 100644
--- a/content/browser/renderer_host/render_widget_host_view_base.h
+++ b/content/browser/renderer_host/render_widget_host_view_base.h
@@ -224,16 +224,6 @@
   // The size of the view's backing surface in non-DPI-adjusted pixels.
   virtual gfx::Size GetCompositorViewportPixelSize() const;
 
-  // Whether or not the renderer's viewport size should be shrunk by the height
-  // of the URL-bar.
-  virtual bool DoBrowserControlsShrinkRendererSize() const;
-
-  // The height of the URL-bar browser controls.
-  virtual float GetTopControlsHeight() const;
-
-  // The height of the bottom bar.
-  virtual float GetBottomControlsHeight() const;
-
   // If mouse wheels can only specify the number of ticks of some static
   // multiplier constant, this method returns that constant (in DIPs). If mouse
   // wheels can specify an arbitrary delta this returns 0.
diff --git a/content/browser/renderer_host/render_widget_host_view_cocoa.mm b/content/browser/renderer_host/render_widget_host_view_cocoa.mm
index b72f2d8fc..24e931f 100644
--- a/content/browser/renderer_host/render_widget_host_view_cocoa.mm
+++ b/content/browser/renderer_host/render_widget_host_view_cocoa.mm
@@ -302,6 +302,8 @@
   replacementRange.location += textSelectionOffset_;
   [self insertText:selectedResult.replacementString
       replacementRange:replacementRange];
+
+  ui::LogTouchBarUMA(ui::TouchBarAction::TEXT_SUGGESTION);
 }
 
 - (void)candidateListTouchBarItem:(NSCandidateListTouchBarItem*)anItem
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
index ee4c0da9..4c43834 100644
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -2201,14 +2201,6 @@
   delegate_->SetTopControlsShownRatio(this, ratio);
 }
 
-bool WebContentsImpl::DoBrowserControlsShrinkRendererSize() const {
-  return delegate_ && delegate_->DoBrowserControlsShrinkRendererSize(this);
-}
-
-int WebContentsImpl::GetTopControlsHeight() const {
-  return delegate_ ? delegate_->GetTopControlsHeight() : 0;
-}
-
 void WebContentsImpl::SetTopControlsGestureScrollInProgress(bool in_progress) {
   if (delegate_)
     delegate_->SetTopControlsGestureScrollInProgress(in_progress);
@@ -3254,9 +3246,8 @@
 }
 
 bool WebContentsImpl::OnUpdateDragCursor() {
-  if (browser_plugin_embedder_)
-    return browser_plugin_embedder_->OnUpdateDragCursor();
-  return false;
+  return browser_plugin_embedder_ &&
+         browser_plugin_embedder_->OnUpdateDragCursor();
 }
 
 bool WebContentsImpl::IsWidgetForMainFrame(
@@ -3906,9 +3897,10 @@
                                         float screen_y,
                                         blink::WebDragOperation operation,
                                         RenderWidgetHost* source_rwh) {
-  if (browser_plugin_embedder_.get())
+  if (browser_plugin_embedder_) {
     browser_plugin_embedder_->DragSourceEndedAt(
         client_x, client_y, screen_x, screen_y, operation);
+  }
   if (source_rwh) {
     source_rwh->DragSourceEndedAt(gfx::PointF(client_x, client_y),
                                   gfx::PointF(screen_x, screen_y), operation);
@@ -3968,7 +3960,7 @@
 void WebContentsImpl::SystemDragEnded(RenderWidgetHost* source_rwh) {
   if (source_rwh)
     source_rwh->DragSourceSystemDragEnded();
-  if (browser_plugin_embedder_.get())
+  if (browser_plugin_embedder_)
     browser_plugin_embedder_->SystemDragEnded();
 }
 
@@ -4908,7 +4900,7 @@
 
 void WebContentsImpl::OnBrowserPluginMessage(RenderFrameHost* render_frame_host,
                                              const IPC::Message& message) {
-  CHECK(!browser_plugin_embedder_.get());
+  CHECK(!browser_plugin_embedder_);
   CreateBrowserPluginEmbedderIfNecessary();
   browser_plugin_embedder_->OnMessageReceived(message, render_frame_host);
 }
@@ -5487,8 +5479,7 @@
 }
 
 void WebContentsImpl::RemoveBrowserPluginEmbedder() {
-  if (browser_plugin_embedder_)
-    browser_plugin_embedder_.reset();
+  browser_plugin_embedder_.reset();
 }
 
 RenderFrameHostImpl* WebContentsImpl::GetOuterWebContentsFrame() {
@@ -6260,7 +6251,7 @@
   // because this is a cross-process navigation, which means that it's a new
   // site that should not have to pay for the sins of its predecessor.
   //
-  // Note that we don't bother telling browser_plugin_embedder_ because the
+  // Note that we don't bother telling |browser_plugin_embedder_| because the
   // cross-process navigation will either destroy the browser plugins or not
   // require their dialogs to close.
   if (dialog_manager_) {
@@ -6789,9 +6780,9 @@
 base::Optional<gfx::Size> WebContentsImpl::GetFullscreenVideoSize() {
   base::Optional<WebContentsObserver::MediaPlayerId> id =
       media_web_contents_observer_->GetFullscreenVideoMediaPlayerId();
-  if (id && cached_video_sizes_.count(id.value()))
+  if (id && base::ContainsKey(cached_video_sizes_, id.value()))
     return base::Optional<gfx::Size>(cached_video_sizes_[id.value()]);
-  return base::Optional<gfx::Size>();
+  return base::nullopt;
 }
 
 int WebContentsImpl::GetCurrentlyPlayingVideoCount() {
diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h
index 5afa1aa..91546771 100644
--- a/content/browser/web_contents/web_contents_impl.h
+++ b/content/browser/web_contents/web_contents_impl.h
@@ -16,6 +16,7 @@
 #include <vector>
 
 #include "base/compiler_specific.h"
+#include "base/containers/flat_map.h"
 #include "base/gtest_prod_util.h"
 #include "base/macros.h"
 #include "base/observer_list.h"
@@ -709,8 +710,6 @@
   ukm::SourceId GetUkmSourceIdForLastCommittedSource() const override;
   void SetTopControlsShownRatio(RenderWidgetHostImpl* render_widget_host,
                                 float ratio) override;
-  bool DoBrowserControlsShrinkRendererSize() const override;
-  int GetTopControlsHeight() const override;
   void SetTopControlsGestureScrollInProgress(bool in_progress) override;
   void RenderWidgetCreated(RenderWidgetHostImpl* render_widget_host) override;
   void RenderWidgetDeleted(RenderWidgetHostImpl* render_widget_host) override;
@@ -1815,7 +1814,8 @@
   bool showing_context_menu_;
 
   int currently_playing_video_count_ = 0;
-  VideoSizeMap cached_video_sizes_;
+  base::flat_map<WebContentsObserver::MediaPlayerId, gfx::Size>
+      cached_video_sizes_;
 
   bool has_persistent_video_ = false;
 
diff --git a/content/browser/web_contents/web_contents_view_aura.cc b/content/browser/web_contents/web_contents_view_aura.cc
index 68fb77a4..3ae2cd8 100644
--- a/content/browser/web_contents/web_contents_view_aura.cc
+++ b/content/browser/web_contents/web_contents_view_aura.cc
@@ -1299,6 +1299,27 @@
   return ConvertFromWeb(current_drag_op_);
 }
 
+int WebContentsViewAura::GetTopControlsHeight() const {
+  WebContentsDelegate* delegate = web_contents_->GetDelegate();
+  if (!delegate)
+    return 0;
+  return delegate->GetTopControlsHeight();
+}
+
+int WebContentsViewAura::GetBottomControlsHeight() const {
+  WebContentsDelegate* delegate = web_contents_->GetDelegate();
+  if (!delegate)
+    return 0;
+  return delegate->GetBottomControlsHeight();
+}
+
+bool WebContentsViewAura::DoBrowserControlsShrinkRendererSize() const {
+  WebContentsDelegate* delegate = web_contents_->GetDelegate();
+  if (!delegate)
+    return false;
+  return delegate->DoBrowserControlsShrinkRendererSize(web_contents_);
+}
+
 #if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
 void WebContentsViewAura::ShowPopupMenu(RenderFrameHost* render_frame_host,
                                         const gfx::Rect& bounds,
diff --git a/content/browser/web_contents/web_contents_view_aura.h b/content/browser/web_contents/web_contents_view_aura.h
index 5fdd862..c917d91 100644
--- a/content/browser/web_contents/web_contents_view_aura.h
+++ b/content/browser/web_contents/web_contents_view_aura.h
@@ -133,6 +133,9 @@
   void GotFocus(RenderWidgetHostImpl* render_widget_host) override;
   void LostFocus(RenderWidgetHostImpl* render_widget_host) override;
   void TakeFocus(bool reverse) override;
+  int GetTopControlsHeight() const override;
+  int GetBottomControlsHeight() const override;
+  bool DoBrowserControlsShrinkRendererSize() const override;
 #if BUILDFLAG(USE_EXTERNAL_POPUP_MENU)
   void ShowPopupMenu(RenderFrameHost* render_frame_host,
                      const gfx::Rect& bounds,
diff --git a/content/public/android/java/src/org/chromium/content/app/ContentChildProcessServiceDelegate.java b/content/public/android/java/src/org/chromium/content/app/ContentChildProcessServiceDelegate.java
index e58907e..511b0596 100644
--- a/content/public/android/java/src/org/chromium/content/app/ContentChildProcessServiceDelegate.java
+++ b/content/public/android/java/src/org/chromium/content/app/ContentChildProcessServiceDelegate.java
@@ -15,7 +15,6 @@
 import org.chromium.base.CommandLine;
 import org.chromium.base.JNIUtils;
 import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
 import org.chromium.base.UnguessableToken;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
@@ -25,10 +24,12 @@
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.memory.MemoryPressureUma;
 import org.chromium.base.process_launcher.ChildProcessServiceDelegate;
+import org.chromium.base.task.PostTask;
 import org.chromium.content.browser.ChildProcessCreationParamsImpl;
 import org.chromium.content.browser.ContentChildProcessConstants;
 import org.chromium.content.common.IGpuProcessCallback;
 import org.chromium.content.common.SurfaceWrapper;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.common.ContentProcessInfo;
 import org.chromium.content_public.common.ContentSwitches;
 
@@ -172,7 +173,8 @@
     @Override
     public void onBeforeMain() {
         nativeInitChildProcess(mCpuCount, mCpuFeatures);
-        ThreadUtils.postOnUiThread(() -> MemoryPressureUma.initializeForChildService());
+        PostTask.postTask(
+                UiThreadTaskTraits.DEFAULT, () -> MemoryPressureUma.initializeForChildService());
     }
 
     @Override
diff --git a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java
index 164c0186..2c9ed9df 100644
--- a/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/BrowserStartupControllerImpl.java
@@ -20,8 +20,10 @@
 import org.chromium.base.library_loader.LibraryProcessType;
 import org.chromium.base.library_loader.LoaderErrors;
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.base.task.PostTask;
 import org.chromium.content.app.ContentMain;
 import org.chromium.content_public.browser.BrowserStartupController;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.ui.resources.ResourceExtractor;
 
 import java.lang.annotation.Retention;
@@ -130,7 +132,7 @@
         if (BuildInfo.isDebugAndroid()) {
             // Only set up the tracing broadcast receiver on debug builds of the OS. Normal tracing
             // should use the DevTools API.
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     addStartupCompletedObserver(new StartupCallback() {
diff --git a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java
index c019cf6..bf644f0b 100644
--- a/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/androidoverlay/DialogOverlayImpl.java
@@ -13,6 +13,8 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.gfx.mojom.Rect;
 import org.chromium.media.mojom.AndroidOverlay;
 import org.chromium.media.mojom.AndroidOverlayClient;
@@ -91,7 +93,7 @@
             public void run() {
                 dialogCore.initialize(context, config, mHoppingHost, asPanel);
                 // Now that |mDialogCore| has been initialized, we are ready for token callbacks.
-                ThreadUtils.postOnUiThread(new Runnable() {
+                PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                     @Override
                     public void run() {
                         if (mNativeHandle != 0) {
diff --git a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
index 3f2f81f..89109d7 100644
--- a/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
+++ b/content/public/android/java/src/org/chromium/content/browser/input/ThreadedInputConnection.java
@@ -22,6 +22,8 @@
 import org.chromium.base.Log;
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -215,7 +217,7 @@
             return mCachedTextInputState;
         }
         assertOnImeThread();
-        ThreadUtils.postOnUiThread(mRequestTextInputStateUpdate);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, mRequestTextInputStateUpdate);
         return blockAndGetStateUpdate();
     }
 
@@ -279,7 +281,7 @@
     }
 
     private void notifyUserAction() {
-        ThreadUtils.postOnUiThread(mNotifyUserActionRunnable);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, mNotifyUserActionRunnable);
     }
 
     /**
@@ -298,7 +300,7 @@
     @VisibleForTesting
     public boolean updateComposingText(
             final CharSequence text, final int newCursorPosition, final boolean isPendingAccent) {
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 updateComposingTextOnUiThread(text, newCursorPosition, isPendingAccent);
@@ -329,7 +331,7 @@
             beginBatchEdit();
             // Clear the current composition range (the keypress alone wouldn't do this).
             commitText("", 1);
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     mImeAdapter.sendSyntheticKeyPress(KeyEvent.KEYCODE_ENTER,
@@ -340,7 +342,7 @@
             return true;
         }
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 commitTextOnUiThread(text, newCursorPosition);
@@ -361,7 +363,7 @@
     @Override
     public boolean performEditorAction(final int actionCode) {
         if (DEBUG_LOGS) Log.i(TAG, "performEditorAction [%d]", actionCode);
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mImeAdapter.performEditorAction(actionCode);
@@ -376,7 +378,7 @@
     @Override
     public boolean performContextMenuAction(final int id) {
         if (DEBUG_LOGS) Log.i(TAG, "performContextMenuAction [%d]", id);
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mImeAdapter.performContextMenuAction(id);
@@ -447,7 +449,7 @@
     @Override
     public boolean deleteSurroundingText(final int beforeLength, final int afterLength) {
         if (DEBUG_LOGS) Log.i(TAG, "deleteSurroundingText [%d %d]", beforeLength, afterLength);
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 if (mPendingAccent != 0) {
@@ -468,7 +470,7 @@
         if (DEBUG_LOGS) {
             Log.i(TAG, "deleteSurroundingTextInCodePoints [%d %d]", beforeLength, afterLength);
         }
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 if (mPendingAccent != 0) {
@@ -486,7 +488,7 @@
     @Override
     public boolean sendKeyEvent(final KeyEvent event) {
         if (DEBUG_LOGS) Log.i(TAG, "sendKeyEvent [%d %d]", event.getAction(), event.getKeyCode());
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 if (handleCombiningAccentOnUiThread(event)) return;
@@ -568,7 +570,7 @@
         if (DEBUG_LOGS) Log.i(TAG, "finishComposingText");
         // This is the only function that may be called on UI thread because
         // of direct calls from InputMethodManager.
-        ThreadUtils.postOnUiThread(mFinishComposingTextRunnable);
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, mFinishComposingTextRunnable);
         return true;
     }
 
@@ -582,7 +584,7 @@
     @Override
     public boolean setSelection(final int start, final int end) {
         if (DEBUG_LOGS) Log.i(TAG, "setSelection [%d %d]", start, end);
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mImeAdapter.setEditableSelectionOffsets(start, end);
@@ -597,7 +599,7 @@
     @Override
     public boolean setComposingRegion(final int start, final int end) {
         if (DEBUG_LOGS) Log.i(TAG, "setComposingRegion [%d %d]", start, end);
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mImeAdapter.setComposingRegion(start, end);
@@ -711,7 +713,7 @@
     @Override
     public boolean requestCursorUpdates(final int cursorUpdateMode) {
         if (DEBUG_LOGS) Log.i(TAG, "requestCursorUpdates [%x]", cursorUpdateMode);
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 mImeAdapter.onRequestCursorUpdates(cursorUpdateMode);
diff --git a/content/public/android/java/src/org/chromium/content/browser/selection/LGEmailActionModeWorkaroundImpl.java b/content/public/android/java/src/org/chromium/content/browser/selection/LGEmailActionModeWorkaroundImpl.java
index 4901fa0..9d299a2 100644
--- a/content/public/android/java/src/org/chromium/content/browser/selection/LGEmailActionModeWorkaroundImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/selection/LGEmailActionModeWorkaroundImpl.java
@@ -20,7 +20,8 @@
 
 import org.chromium.base.Log;
 import org.chromium.base.PackageUtils;
-import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -103,7 +104,7 @@
 
                 @Override
                 public void onDestroyActionMode(final ActionMode mode) {
-                    ThreadUtils.postOnUiThread(new Runnable() {
+                    PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                         @Override
                         public void run() {
                             c.onDestroyActionMode(mode);
@@ -124,7 +125,7 @@
                     null, contentContainer, 150, new AnimatorListenerAdapter() {
                         @Override
                         public void onAnimationEnd(Animator animation) {
-                            ThreadUtils.postOnUiThread(new Runnable() {
+                            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                                 @Override
                                 public void run() {
                                     popupWindow.dismiss();
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java b/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java
index 2bca7dc..2857aa3e 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/BrowserTaskExecutor.java
@@ -47,6 +47,7 @@
             // TODO(alexclarke): ThreadUtils.getUiThreadHandler shouldn't be in base.
             taskRunner =
                     new SingleThreadTaskRunnerImpl(ThreadUtils.getUiThreadHandler(), taskTraits);
+            taskRunner.disableLifetimeCheck();
             mTaskRunners.put(taskTraits, taskRunner);
             return taskRunner;
         }
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/BrowserStartupControllerTest.java b/content/public/android/javatests/src/org/chromium/content/browser/BrowserStartupControllerTest.java
index b1a48db..b2e782bb 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/BrowserStartupControllerTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/BrowserStartupControllerTest.java
@@ -18,8 +18,10 @@
 import org.chromium.base.library_loader.LoaderErrors;
 import org.chromium.base.library_loader.ProcessInitException;
 import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.content_public.browser.BrowserStartupController.StartupCallback;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 /**
  * Test of BrowserStartupController
@@ -71,7 +73,7 @@
 
         private int kickOffStartup(boolean startServiceManagerOnly) {
             // Post to the UI thread to emulate what would happen in a real scenario.
-            ThreadUtils.postOnUiThread(new Runnable() {
+            PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
                 @Override
                 public void run() {
                     if (!mServiceManagerStarted) {
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java b/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java
index 6039657..1e4f382 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/ContentTextSelectionTest.java
@@ -20,6 +20,7 @@
 import org.junit.runner.RunWith;
 
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.util.DisableIf;
 import org.chromium.base.test.util.DisabledTest;
 import org.chromium.base.test.util.Feature;
@@ -28,6 +29,7 @@
 import org.chromium.content.browser.input.ImeTestUtils;
 import org.chromium.content.browser.selection.SelectionPopupControllerImpl;
 import org.chromium.content_public.browser.SelectionClient;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
 import org.chromium.content_public.browser.test.util.Criteria;
@@ -84,7 +86,8 @@
                 result = new SelectionClient.Result();
             }
 
-            ThreadUtils.postOnUiThread(() -> mResultCallback.onClassified(result));
+            PostTask.postTask(
+                    UiThreadTaskTraits.DEFAULT, () -> mResultCallback.onClassified(result));
             return true;
         }
 
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/scheduler/NativePostTaskTest.java b/content/public/android/javatests/src/org/chromium/content/browser/scheduler/NativePostTaskTest.java
index fcfc254..d4081fa 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/scheduler/NativePostTaskTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/scheduler/NativePostTaskTest.java
@@ -77,13 +77,10 @@
     public void testNativePostDelayedTask() throws Exception {
         final Object lock = new Object();
         final AtomicBoolean taskExecuted = new AtomicBoolean();
-        PostTask.postDelayedTask(new TaskTraits(), new Runnable() {
-            @Override
-            public void run() {
-                synchronized (lock) {
-                    taskExecuted.set(true);
-                    lock.notify();
-                }
+        PostTask.postDelayedTask(new TaskTraits(), () -> {
+            synchronized (lock) {
+                taskExecuted.set(true);
+                lock.notify();
             }
         }, 1);
 
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java b/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java
index c7eb954..b117056b 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/scheduler/UiThreadSchedulerTest.java
@@ -113,7 +113,6 @@
                     Assert.assertTrue(ThreadUtils.runningOnUiThread());
                 }
             });
-
             SchedulerTestHelpers.postTaskAndBlockUntilRun(uiThreadTaskRunner);
         } finally {
             uiThreadTaskRunner.destroy();
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java b/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java
index 8fee992..7cad67b 100644
--- a/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java
+++ b/content/public/android/javatests/src/org/chromium/content/browser/webcontents/WebContentsTest.java
@@ -17,11 +17,13 @@
 
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.process_launcher.ChildProcessConnection;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseJUnit4ClassRunner;
 import org.chromium.base.test.util.UrlUtils;
 import org.chromium.content.browser.ChildProcessLauncherHelperImpl;
 import org.chromium.content_public.browser.ChildProcessImportance;
 import org.chromium.content_public.browser.RenderFrameHost;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.content_public.browser.WebContents;
 import org.chromium.content_public.browser.WebContentsStatics;
 import org.chromium.content_shell.Shell;
@@ -350,7 +352,7 @@
         mActivityTestRule.waitForActiveShellToBeDoneLoading();
         final WebContents webContents = activity.getActiveWebContents();
 
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 RenderFrameHost frameHost = webContents.getMainFrame();
diff --git a/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java
index 23c8fb2c..27437bd 100644
--- a/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java
+++ b/content/public/android/junit/src/org/chromium/content/browser/input/ThreadedInputConnectionTest.java
@@ -36,8 +36,10 @@
 import org.robolectric.annotation.Config;
 
 import org.chromium.base.ThreadUtils;
+import org.chromium.base.task.PostTask;
 import org.chromium.base.test.BaseRobolectricTestRunner;
 import org.chromium.base.test.util.Feature;
+import org.chromium.content_public.browser.UiThreadTaskTraits;
 
 import java.util.concurrent.Callable;
 
@@ -186,7 +188,7 @@
     public void testRendererCannotUpdateState() {
         when(mImeAdapter.requestTextInputStateUpdate()).thenReturn(true);
         // We found that renderer cannot update state, e.g., due to a crash.
-        ThreadUtils.postOnUiThread(new Runnable() {
+        PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
             @Override
             public void run() {
                 try {
diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h
index 33c67fd..2230284 100644
--- a/content/public/browser/web_contents.h
+++ b/content/public/browser/web_contents.h
@@ -27,7 +27,6 @@
 #include "content/public/browser/screen_orientation_delegate.h"
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/visibility.h"
-#include "content/public/browser/web_contents_observer.h"
 #include "content/public/browser/web_ui.h"
 #include "content/public/common/stop_find_action.h"
 #include "third_party/blink/public/common/frame/sandbox_flags.h"
@@ -879,9 +878,6 @@
 
   virtual int GetCurrentlyPlayingVideoCount() = 0;
 
-  // Returns a map containing the sizes of all currently playing videos.
-  using VideoSizeMap =
-      base::flat_map<WebContentsObserver::MediaPlayerId, gfx::Size>;
   virtual base::Optional<gfx::Size> GetFullscreenVideoSize() = 0;
   virtual bool IsFullscreen() = 0;
 
diff --git a/content/public/common/common_param_traits_macros.h b/content/public/common/common_param_traits_macros.h
index d318223..075425da 100644
--- a/content/public/common/common_param_traits_macros.h
+++ b/content/public/common/common_param_traits_macros.h
@@ -201,12 +201,12 @@
   IPC_STRUCT_TRAITS_MEMBER(text_track_text_color)
   IPC_STRUCT_TRAITS_MEMBER(text_autosizing_enabled)
   IPC_STRUCT_TRAITS_MEMBER(double_tap_to_zoom_enabled)
+  IPC_STRUCT_TRAITS_MEMBER(web_app_scope)
 #if defined(OS_ANDROID)
   IPC_STRUCT_TRAITS_MEMBER(font_scale_factor)
   IPC_STRUCT_TRAITS_MEMBER(device_scale_adjustment)
   IPC_STRUCT_TRAITS_MEMBER(force_enable_zoom)
   IPC_STRUCT_TRAITS_MEMBER(fullscreen_supported)
-  IPC_STRUCT_TRAITS_MEMBER(web_app_scope)
   IPC_STRUCT_TRAITS_MEMBER(default_video_poster_url)
   IPC_STRUCT_TRAITS_MEMBER(support_deprecated_target_density_dpi)
   IPC_STRUCT_TRAITS_MEMBER(use_legacy_background_size_shorthand_behavior)
diff --git a/content/public/common/web_preferences.h b/content/public/common/web_preferences.h
index 579c086..52edd1a 100644
--- a/content/public/common/web_preferences.h
+++ b/content/public/common/web_preferences.h
@@ -226,12 +226,14 @@
 
   bool text_autosizing_enabled;
 
+  // Representation of the Web App Manifest scope if any.
+  GURL web_app_scope;
+
 #if defined(OS_ANDROID)
   float font_scale_factor;
   float device_scale_adjustment;
   bool force_enable_zoom;
   bool fullscreen_supported;
-  GURL web_app_scope;
   GURL default_video_poster_url;
   bool support_deprecated_target_density_dpi;
   bool use_legacy_background_size_shorthand_behavior;
diff --git a/content/public/test/web_contents_tester.h b/content/public/test/web_contents_tester.h
index ca36d6e..c041587 100644
--- a/content/public/test/web_contents_tester.h
+++ b/content/public/test/web_contents_tester.h
@@ -12,6 +12,7 @@
 #include "content/public/browser/site_instance.h"
 #include "content/public/browser/web_contents.h"
 #include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom.h"
+#include "third_party/blink/public/platform/web_input_event.h"
 #include "ui/base/page_transition_types.h"
 
 class GURL;
diff --git a/content/renderer/BUILD.gn b/content/renderer/BUILD.gn
index ce6ce32..24d1e1d 100644
--- a/content/renderer/BUILD.gn
+++ b/content/renderer/BUILD.gn
@@ -498,8 +498,6 @@
     "renderer_webapplicationcachehost_impl.h",
     "renderer_webcookiejar_impl.cc",
     "renderer_webcookiejar_impl.h",
-    "resizing_mode_selector.cc",
-    "resizing_mode_selector.h",
     "resource_timing_info_conversions.cc",
     "resource_timing_info_conversions.h",
     "sad_plugin.cc",
diff --git a/content/renderer/media/stream/media_stream_center.cc b/content/renderer/media/stream/media_stream_center.cc
index c3ab43fd..af27d64 100644
--- a/content/renderer/media/stream/media_stream_center.cc
+++ b/content/renderer/media/stream/media_stream_center.cc
@@ -20,8 +20,8 @@
 #include "content/renderer/media/webrtc_local_audio_source_provider.h"
 #include "media/base/sample_format.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_track.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
 #include "third_party/blink/public/platform/modules/mediastream/web_media_stream_audio_sink.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
diff --git a/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc b/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
index 8a49e26..d7f995e 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_audio_unittest.cc
@@ -19,7 +19,7 @@
 #include "media/base/audio_parameters.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_string.h"
 
diff --git a/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc b/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc
index 6116220..1c21c971 100644
--- a/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc
+++ b/content/renderer/media/stream/media_stream_constraints_util_video_content_unittest.cc
@@ -11,7 +11,7 @@
 #include "content/renderer/media/stream/mock_constraint_factory.h"
 #include "media/base/limits.h"
 #include "testing/gtest/include/gtest/gtest.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 
 namespace content {
diff --git a/content/renderer/media/stream/media_stream_video_source.h b/content/renderer/media/stream/media_stream_video_source.h
index ec92006..4d7a98f 100644
--- a/content/renderer/media/stream/media_stream_video_source.h
+++ b/content/renderer/media/stream/media_stream_video_source.h
@@ -19,8 +19,8 @@
 #include "media/capture/video_capture_types.h"
 #include "third_party/blink/public/common/media/video_capture.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_types.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
 #include "third_party/blink/public/platform/modules/mediastream/secure_display_link_tracker.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_stream_track.h"
diff --git a/content/renderer/media/stream/user_media_processor.h b/content/renderer/media/stream/user_media_processor.h
index d8970c0..cbc0b25 100644
--- a/content/renderer/media/stream/user_media_processor.h
+++ b/content/renderer/media/stream/user_media_processor.h
@@ -19,7 +19,7 @@
 #include "content/renderer/media/stream/media_stream_dispatcher_eventhandler.h"
 #include "third_party/blink/public/mojom/mediastream/media_devices.mojom.h"
 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_vector.h"
 #include "third_party/blink/public/web/web_user_media_request.h"
 
diff --git a/content/renderer/media/webrtc/rtc_peer_connection_handler_unittest.cc b/content/renderer/media/webrtc/rtc_peer_connection_handler_unittest.cc
index c1ee6ff..20d0151 100644
--- a/content/renderer/media/webrtc/rtc_peer_connection_handler_unittest.cc
+++ b/content/renderer/media/webrtc/rtc_peer_connection_handler_unittest.cc
@@ -39,7 +39,7 @@
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_track.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream.h"
diff --git a/content/renderer/media_recorder/video_track_recorder_unittest.cc b/content/renderer/media_recorder/video_track_recorder_unittest.cc
index bbe1552..9cf00d4 100644
--- a/content/renderer/media_recorder/video_track_recorder_unittest.cc
+++ b/content/renderer/media_recorder/video_track_recorder_unittest.cc
@@ -72,7 +72,7 @@
         blink::WebString::FromASCII("dummy"));
     blink_source_.Initialize(webkit_track_id,
                              blink::WebMediaStreamSource::kTypeVideo,
-                             webkit_track_id);
+                             webkit_track_id, false /*remote*/);
     blink_source_.SetPlatformSource(base::WrapUnique(mock_source_));
     blink_track_.Initialize(blink_source_);
 
diff --git a/content/renderer/render_view_impl.cc b/content/renderer/render_view_impl.cc
index f4ed0b0..318f5078 100644
--- a/content/renderer/render_view_impl.cc
+++ b/content/renderer/render_view_impl.cc
@@ -88,7 +88,6 @@
 #include "content/renderer/render_widget_fullscreen_pepper.h"
 #include "content/renderer/renderer_blink_platform_impl.h"
 #include "content/renderer/renderer_webapplicationcachehost_impl.h"
-#include "content/renderer/resizing_mode_selector.h"
 #include "content/renderer/savable_resources.h"
 #include "content/renderer/v8_value_converter_impl.h"
 #include "content/renderer/web_ui_extension_data.h"
@@ -822,6 +821,8 @@
       static_cast<blink::WebEffectiveConnectionType>(
           prefs.network_quality_estimator_web_holdback));
 
+  settings->SetWebAppScope(WebString::FromASCII(prefs.web_app_scope.spec()));
+
 #if defined(OS_ANDROID)
   settings->SetAllowCustomScrollbarInMainFrame(false);
   settings->SetAccessibilityFontScaleFactor(prefs.font_scale_factor);
@@ -829,7 +830,6 @@
   settings->SetFullscreenSupported(prefs.fullscreen_supported);
   web_view->SetIgnoreViewportTagScaleLimits(prefs.force_enable_zoom);
   settings->SetAutoZoomFocusedNodeToLegibleScale(true);
-  settings->SetWebAppScope(WebString::FromASCII(prefs.web_app_scope.spec()));
   settings->SetDefaultVideoPosterURL(
       WebString::FromASCII(prefs.default_video_poster_url.spec()));
   settings->SetSupportDeprecatedTargetDensityDPI(
diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc
index 3013a52..234f5d0 100644
--- a/content/renderer/render_widget.cc
+++ b/content/renderer/render_widget.cc
@@ -73,7 +73,6 @@
 #include "content/renderer/render_thread_impl.h"
 #include "content/renderer/render_view_impl.h"
 #include "content/renderer/renderer_blink_platform_impl.h"
-#include "content/renderer/resizing_mode_selector.h"
 #include "gpu/command_buffer/service/gpu_switches.h"
 #include "ipc/ipc_message_start.h"
 #include "ipc/ipc_sync_message.h"
@@ -421,7 +420,6 @@
       monitor_composition_info_(false),
       popup_origin_scale_for_emulation_(0.f),
       frame_swap_message_queue_(new FrameSwapMessageQueue(routing_id_)),
-      resizing_mode_selector_(new ResizingModeSelector()),
       has_host_context_menu_location_(false),
       has_focus_(false),
       for_child_local_root_frame_(false),
@@ -777,8 +775,41 @@
     bottom_controls_height_ = params.bottom_controls_height;
   }
 
-  if (!resizing_mode_selector_->ShouldAbortOnResize(this, params)) {
+  bool ignore_resize_ipc = false;
+  if (synchronous_resize_mode_for_testing_) {
+    // We can ignore browser-initialized resizing during synchronous
+    // (renderer-controlled) mode, unless it is switching us to/from
+    // fullsreen mode or changing the device scale factor.
+    // TODO(danakj): Does the browser actually change DSF inside a web test??
+    // TODO(danakj): Isn't the display mode check redundant with the fullscreen
+    // one?
+    if (params.is_fullscreen_granted == is_fullscreen_granted_ &&
+        params.display_mode == display_mode_ &&
+        params.screen_info.device_scale_factor ==
+            screen_info_.device_scale_factor)
+      ignore_resize_ipc = true;
+  }
+
+  // When controlling the size in the renderer, we should ignore sizes given by
+  // the browser IPC here.
+  // TODO(danakj): There are many things also being ignored that aren't the
+  // widget's size params. It works because tests that use this mode don't
+  // change those parameters, I guess. But it's more complicated then because it
+  // looks like they are related to sync resize mode. Let's move them out of
+  // this block.
+  // TODO(danakj): It would be nice if we can still use the emulator to emulate
+  // things other than the size if we are in sync resize mode - if the emulator
+  // is even used in sync resize tests. It probably isn't though, so either way
+  // it'd be good to get the emulator out of this block (maybe by overwriting
+  // some of |params| in sync resize mode instead of just skipping the emulator.
+  if (!ignore_resize_ipc) {
     if (screen_metrics_emulator_) {
+      // This will call our SynchronizeVisualProperties() method with a
+      // different set of VisualProperties, holding emulated values. Though not
+      // all VisualProperties are modified by the metrics emulator, so it's a
+      // bit unclear to do this with the full structure. Anything it does not
+      // modify can be consumed directly here instead of in
+      // SynchronizeVisualProperties().
       screen_metrics_emulator_->OnSynchronizeVisualProperties(params);
     } else {
       if (!delegate()) {
@@ -1935,7 +1966,7 @@
   WebRect window_rect = rect_in_screen;
   EmulatedToScreenRectIfNeeded(&window_rect);
 
-  if (resizing_mode_selector_->is_synchronous_mode()) {
+  if (synchronous_resize_mode_for_testing_) {
     // This is a web-test-only path. At one point, it was planned to be
     // removed. See https://crbug.com/309760.
     SetWindowRectSynchronously(window_rect);
@@ -2523,7 +2554,7 @@
       size_.height() != new_size_in_window.height) {
     size_ = gfx::Size(new_size_in_window.width, new_size_in_window.height);
 
-    if (resizing_mode_selector_->is_synchronous_mode()) {
+    if (synchronous_resize_mode_for_testing_) {
       gfx::Rect new_pos(WindowRect().x, WindowRect().y, size_.width(),
                         size_.height());
       widget_screen_rect_ = new_pos;
@@ -3324,7 +3355,7 @@
 }
 
 void RenderWidget::UseSynchronousResizeModeForTesting(bool enable) {
-  resizing_mode_selector_->set_is_synchronous_mode(enable);
+  synchronous_resize_mode_for_testing_ = enable;
 }
 
 void RenderWidget::SetDeviceScaleFactorForTesting(float factor) {
diff --git a/content/renderer/render_widget.h b/content/renderer/render_widget.h
index 21145d23d..6db4199f 100644
--- a/content/renderer/render_widget.h
+++ b/content/renderer/render_widget.h
@@ -114,7 +114,6 @@
 class RenderViewImpl;
 class RenderWidgetDelegate;
 class RenderWidgetScreenMetricsEmulator;
-class ResizingModeSelector;
 class TextInputClientObserver;
 class WidgetInputHandlerManager;
 struct ContextMenuParams;
@@ -889,6 +888,20 @@
   // the RenderViewImpl, we freeze it instead.
   bool is_frozen_;
 
+  // In web tests, synchronous resizing mode may be used. Normally each widget's
+  // size is controlled by IPC from the browser. In synchronous resize mode the
+  // renderer controls the size directly, and IPCs from the browser must be
+  // ignored. This was deprecated but then later undeprecated, so it is now
+  // called unfortunate instead. See https://crbug.com/309760. When this is
+  // enabled the various size properties will be controlled directly when
+  // SetWindowRect() is called instead of needing a round trip through the
+  // browser.
+  // Note that SetWindowRectSynchronouslyForTesting() provides a secondary way
+  // to control the size of the RenderWidget independently from the renderer
+  // process, without the use of this mode, however it would be overridden by
+  // the browser if they disagree.
+  bool synchronous_resize_mode_for_testing_ = false;
+
   // Stores information about the current text input.
   blink::WebTextInputInfo text_input_info_;
 
@@ -949,7 +962,6 @@
   float popup_origin_scale_for_emulation_;
 
   scoped_refptr<FrameSwapMessageQueue> frame_swap_message_queue_;
-  std::unique_ptr<ResizingModeSelector> resizing_mode_selector_;
 
   // Lists of RenderFrameProxy objects that need to be notified of
   // compositing-related events (e.g. DidCommitCompositorFrame).
diff --git a/content/renderer/resizing_mode_selector.cc b/content/renderer/resizing_mode_selector.cc
deleted file mode 100644
index cc16b16..0000000
--- a/content/renderer/resizing_mode_selector.cc
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "content/renderer/resizing_mode_selector.h"
-
-#include "content/common/view_messages.h"
-#include "content/renderer/render_thread_impl.h"
-#include "content/renderer/render_widget.h"
-
-namespace content {
-
-ResizingModeSelector::ResizingModeSelector() : is_synchronous_mode_(false) {}
-
-bool ResizingModeSelector::NeverUsesSynchronousResize() const {
-  return !RenderThreadImpl::current() ||  // can be NULL when in unit tests
-         !RenderThreadImpl::current()->web_test_mode();
-}
-
-bool ResizingModeSelector::ShouldAbortOnResize(
-    RenderWidget* widget,
-    const VisualProperties& visual_properties) {
-  if (!is_synchronous_mode_)
-    return false;
-  if (visual_properties.is_fullscreen_granted !=
-      widget->is_fullscreen_granted())
-    return false;
-  if (visual_properties.display_mode != widget->display_mode())
-    return false;
-  return visual_properties.screen_info.device_scale_factor ==
-         widget->screen_info().device_scale_factor;
-}
-
-void ResizingModeSelector::set_is_synchronous_mode(bool mode) {
-  is_synchronous_mode_ = mode;
-}
-
-bool ResizingModeSelector::is_synchronous_mode() const {
-  return is_synchronous_mode_;
-}
-
-}  // namespace content
diff --git a/content/renderer/resizing_mode_selector.h b/content/renderer/resizing_mode_selector.h
deleted file mode 100644
index 1743fba..0000000
--- a/content/renderer/resizing_mode_selector.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef CONTENT_RENDERER_RESIZING_MODE_SELECTOR_H_
-#define CONTENT_RENDERER_RESIZING_MODE_SELECTOR_H_
-
-#include "base/macros.h"
-
-namespace content {
-
-class RenderWidget;
-struct VisualProperties;
-
-// Enables switching between two modes of resizing:
-// 1) The "normal" (asynchronous) resizing, which involves sending messages to
-//    and receiving them from host; and
-// 2) The synchronous mode, which short-circuits the resizing logic to operate
-//    strictly inside renderer.
-// The latter is necessary to support a handful of web tests that were
-// written with the expectation of a synchronous resize, and we're going to
-// eventually rewrite or remove all of them. See http://crbug.com/309760 for
-// details.
-class ResizingModeSelector {
- public:
-  ResizingModeSelector();
-  bool NeverUsesSynchronousResize() const;
-  bool ShouldAbortOnResize(RenderWidget* widget,
-                           const VisualProperties& visual_properties);
-
-  void set_is_synchronous_mode(bool mode);
-  bool is_synchronous_mode() const;
-
- private:
-  bool is_synchronous_mode_;
-
-  DISALLOW_COPY_AND_ASSIGN(ResizingModeSelector);
-};
-
-}  // namespace content
-
-#endif  // CONTENT_RENDERER_RESIZING_MODE_SELECTOR_H_
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
index 0d5edc2..bdf583e 100644
--- a/content/test/BUILD.gn
+++ b/content/test/BUILD.gn
@@ -259,6 +259,7 @@
     "storage_partition_test_utils.h",
     "stub_layer_tree_view_delegate.cc",
     "stub_layer_tree_view_delegate.h",
+    "stub_render_view_host_delegate_view.h",
     "stub_render_widget_host_owner_delegate.cc",
     "stub_render_widget_host_owner_delegate.h",
     "test_background_sync_context.cc",
diff --git a/content/test/data/accessibility/aria/aria-col-attr.html b/content/test/data/accessibility/aria/aria-col-attr.html
index a4782a6..39dc90c 100644
--- a/content/test/data/accessibility/aria/aria-col-attr.html
+++ b/content/test/data/accessibility/aria/aria-col-attr.html
@@ -6,6 +6,9 @@
 @BLINK-ALLOW:ariaColumn*
 @BLINK-ALLOW:ariaCellColumn*
 -->
+<!-- For compatibility with earlier versions of Jaws, We do not expose
+    aria-row/colcount and aria-row/colindex information if they match the
+    physical coordinates of the table. -->
 <div role="grid" aria-colcount="5">
   <div role="row">
     <span role="columnheader" aria-colindex="2" aria-colspan="2">cell 2</span>
diff --git a/content/test/data/accessibility/aria/aria-row-attr.html b/content/test/data/accessibility/aria/aria-row-attr.html
index 1b5e6299..a906d6b 100644
--- a/content/test/data/accessibility/aria/aria-row-attr.html
+++ b/content/test/data/accessibility/aria/aria-row-attr.html
@@ -6,6 +6,9 @@
 @BLINK-ALLOW:ariaRow*
 @BLINK-ALLOW:ariaCellRow*
 -->
+<!-- For compatibility with earlier versions of Jaws, We do not expose
+    aria-row/colcount and aria-row/colindex information if they match the
+    physical coordinates of the table. -->
 <div role="grid" aria-rowcount="5">
   <div role="row">
     <span role="columnheader" aria-rowindex="3">cell 2</span>
diff --git a/content/test/data/accessibility/aria/table-column-hidden-expected-blink.txt b/content/test/data/accessibility/aria/table-column-hidden-expected-blink.txt
index 8c812f5..88f459b 100644
--- a/content/test/data/accessibility/aria/table-column-hidden-expected-blink.txt
+++ b/content/test/data/accessibility/aria/table-column-hidden-expected-blink.txt
@@ -1,33 +1,33 @@
 rootWebArea
-++grid ariaColumnCount=4 ariaRowCount=3 tableRowCount=3 tableColumnCount=3
+++grid ariaColumnCount=5 ariaRowCount=4 tableRowCount=3 tableColumnCount=3
 ++++row selected=false
-++++++columnHeader name='Month' ariaCellColumnIndex=1 ariaCellRowIndex=1 selected=false
+++++++columnHeader name='Month' ariaCellColumnIndex=2 ariaCellRowIndex=2 selected=false
 ++++++++staticText name='Month'
 ++++++++++inlineTextBox name='Month'
-++++++columnHeader name='Day' ariaCellColumnIndex=2 ariaCellRowIndex=1 selected=false
+++++++columnHeader name='Day' ariaCellColumnIndex=3 ariaCellRowIndex=2 selected=false
 ++++++++staticText name='Day'
 ++++++++++inlineTextBox name='Day'
-++++++columnHeader name='Weather' ariaCellColumnIndex=4 ariaCellRowIndex=1 selected=false
+++++++columnHeader name='Weather' ariaCellColumnIndex=5 ariaCellRowIndex=2 selected=false
 ++++++++staticText name='Weather'
 ++++++++++inlineTextBox name='Weather'
 ++++row selected=false
-++++++cell name='January' ariaCellColumnIndex=1 ariaCellRowIndex=2 selected=false
+++++++cell name='January' ariaCellColumnIndex=2 ariaCellRowIndex=3 selected=false
 ++++++++staticText name='January'
 ++++++++++inlineTextBox name='January'
-++++++cell name='01' ariaCellColumnIndex=2 ariaCellRowIndex=2 selected=false
+++++++cell name='01' ariaCellColumnIndex=3 ariaCellRowIndex=3 selected=false
 ++++++++staticText name='01'
 ++++++++++inlineTextBox name='01'
-++++++cell name='Sunny' ariaCellColumnIndex=4 ariaCellRowIndex=2 selected=false
+++++++cell name='Sunny' ariaCellColumnIndex=5 ariaCellRowIndex=3 selected=false
 ++++++++staticText name='Sunny'
 ++++++++++inlineTextBox name='Sunny'
 ++++row selected=false
-++++++cell name='January' ariaCellColumnIndex=1 ariaCellRowIndex=3 selected=false
+++++++cell name='January' ariaCellColumnIndex=2 ariaCellRowIndex=4 selected=false
 ++++++++staticText name='January'
 ++++++++++inlineTextBox name='January'
-++++++cell name='02' ariaCellColumnIndex=2 ariaCellRowIndex=3 selected=false
+++++++cell name='02' ariaCellColumnIndex=3 ariaCellRowIndex=4 selected=false
 ++++++++staticText name='02'
 ++++++++++inlineTextBox name='02'
-++++++cell name='Rainy' ariaCellColumnIndex=4 ariaCellRowIndex=3 selected=false
+++++++cell name='Rainy' ariaCellColumnIndex=5 ariaCellRowIndex=4 selected=false
 ++++++++staticText name='Rainy'
 ++++++++++inlineTextBox name='Rainy'
 ++paragraph
diff --git a/content/test/data/accessibility/aria/table-column-hidden-expected-mac.txt b/content/test/data/accessibility/aria/table-column-hidden-expected-mac.txt
index bf850ee..fc32f75 100644
--- a/content/test/data/accessibility/aria/table-column-hidden-expected-mac.txt
+++ b/content/test/data/accessibility/aria/table-column-hidden-expected-mac.txt
@@ -1,25 +1,25 @@
 AXWebArea
-++AXTable AXARIAColumnCount='4' AXARIARowCount='3'
+++AXTable AXARIAColumnCount='5' AXARIARowCount='4'
 ++++AXRow
-++++++AXCell AXARIAColumnIndex='1' AXARIARowIndex='1'
+++++++AXCell AXARIAColumnIndex='2' AXARIARowIndex='2'
 ++++++++AXStaticText AXValue='Month'
-++++++AXCell AXARIAColumnIndex='2' AXARIARowIndex='1'
+++++++AXCell AXARIAColumnIndex='3' AXARIARowIndex='2'
 ++++++++AXStaticText AXValue='Day'
-++++++AXCell AXARIAColumnIndex='4' AXARIARowIndex='1'
+++++++AXCell AXARIAColumnIndex='5' AXARIARowIndex='2'
 ++++++++AXStaticText AXValue='Weather'
 ++++AXRow
-++++++AXCell AXARIAColumnIndex='1' AXARIARowIndex='2'
+++++++AXCell AXARIAColumnIndex='2' AXARIARowIndex='3'
 ++++++++AXStaticText AXValue='January'
-++++++AXCell AXARIAColumnIndex='2' AXARIARowIndex='2'
+++++++AXCell AXARIAColumnIndex='3' AXARIARowIndex='3'
 ++++++++AXStaticText AXValue='01'
-++++++AXCell AXARIAColumnIndex='4' AXARIARowIndex='2'
+++++++AXCell AXARIAColumnIndex='5' AXARIARowIndex='3'
 ++++++++AXStaticText AXValue='Sunny'
 ++++AXRow
-++++++AXCell AXARIAColumnIndex='1' AXARIARowIndex='3'
+++++++AXCell AXARIAColumnIndex='2' AXARIARowIndex='4'
 ++++++++AXStaticText AXValue='January'
-++++++AXCell AXARIAColumnIndex='2' AXARIARowIndex='3'
+++++++AXCell AXARIAColumnIndex='3' AXARIARowIndex='4'
 ++++++++AXStaticText AXValue='02'
-++++++AXCell AXARIAColumnIndex='4' AXARIARowIndex='3'
+++++++AXCell AXARIAColumnIndex='5' AXARIARowIndex='4'
 ++++++++AXStaticText AXValue='Rainy'
 ++++AXColumn
 ++++AXColumn
diff --git a/content/test/data/accessibility/aria/table-column-hidden-expected-win.txt b/content/test/data/accessibility/aria/table-column-hidden-expected-win.txt
index 6887c8a6..c7e8573 100644
--- a/content/test/data/accessibility/aria/table-column-hidden-expected-win.txt
+++ b/content/test/data/accessibility/aria/table-column-hidden-expected-win.txt
@@ -1,25 +1,25 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
-++ROLE_SYSTEM_TABLE colcount:4 rowcount:3
+++ROLE_SYSTEM_TABLE colcount:5 rowcount:4
 ++++ROLE_SYSTEM_ROW
-++++++ROLE_SYSTEM_COLUMNHEADER name='Month' colindex:1 rowindex:1
+++++++ROLE_SYSTEM_COLUMNHEADER name='Month' colindex:2 rowindex:2
 ++++++++ROLE_SYSTEM_STATICTEXT name='Month'
-++++++ROLE_SYSTEM_COLUMNHEADER name='Day' colindex:2 rowindex:1
+++++++ROLE_SYSTEM_COLUMNHEADER name='Day' colindex:3 rowindex:2
 ++++++++ROLE_SYSTEM_STATICTEXT name='Day'
-++++++ROLE_SYSTEM_COLUMNHEADER name='Weather' colindex:4 rowindex:1
+++++++ROLE_SYSTEM_COLUMNHEADER name='Weather' colindex:5 rowindex:2
 ++++++++ROLE_SYSTEM_STATICTEXT name='Weather'
 ++++ROLE_SYSTEM_ROW
-++++++ROLE_SYSTEM_CELL name='January' FOCUSABLE colindex:1 rowindex:2
+++++++ROLE_SYSTEM_CELL name='January' FOCUSABLE colindex:2 rowindex:3
 ++++++++ROLE_SYSTEM_STATICTEXT name='January'
-++++++ROLE_SYSTEM_CELL name='01' FOCUSABLE colindex:2 rowindex:2
+++++++ROLE_SYSTEM_CELL name='01' FOCUSABLE colindex:3 rowindex:3
 ++++++++ROLE_SYSTEM_STATICTEXT name='01'
-++++++ROLE_SYSTEM_CELL name='Sunny' FOCUSABLE colindex:4 rowindex:2
+++++++ROLE_SYSTEM_CELL name='Sunny' FOCUSABLE colindex:5 rowindex:3
 ++++++++ROLE_SYSTEM_STATICTEXT name='Sunny'
 ++++ROLE_SYSTEM_ROW
-++++++ROLE_SYSTEM_CELL name='January' FOCUSABLE colindex:1 rowindex:3
+++++++ROLE_SYSTEM_CELL name='January' FOCUSABLE colindex:2 rowindex:4
 ++++++++ROLE_SYSTEM_STATICTEXT name='January'
-++++++ROLE_SYSTEM_CELL name='02' FOCUSABLE colindex:2 rowindex:3
+++++++ROLE_SYSTEM_CELL name='02' FOCUSABLE colindex:3 rowindex:4
 ++++++++ROLE_SYSTEM_STATICTEXT name='02'
-++++++ROLE_SYSTEM_CELL name='Rainy' FOCUSABLE colindex:4 rowindex:3
+++++++ROLE_SYSTEM_CELL name='Rainy' FOCUSABLE colindex:5 rowindex:4
 ++++++++ROLE_SYSTEM_STATICTEXT name='Rainy'
 ++IA2_ROLE_PARAGRAPH
 ++++ROLE_SYSTEM_STATICTEXT name='done'
diff --git a/content/test/data/accessibility/aria/table-column-hidden.html b/content/test/data/accessibility/aria/table-column-hidden.html
index 09b773d..40640e1d 100644
--- a/content/test/data/accessibility/aria/table-column-hidden.html
+++ b/content/test/data/accessibility/aria/table-column-hidden.html
@@ -13,24 +13,27 @@
 @BLINK-ALLOW:*RowCount*
 @BLINK-ALLOW:ariaCellRowIndex*
 -->
-<table role="grid" aria-rowcount="3" aria-colcount="4">
+<!-- For compatibility with earlier versions of Jaws, We do not expose
+    aria-row/colcount and aria-row/colindex information if they match the
+    physical coordinates of the table. -->
+<table role="grid" aria-rowcount="4" aria-colcount="5">
   <tbody><tr>
-    <th aria-rowindex="1" aria-colindex="1">Month</th>
-    <th aria-rowindex="1" aria-colindex="2">Day</th>
-    <th aria-rowindex="1" aria-colindex="3">Year</th>
-    <th aria-rowindex="1" aria-colindex="4">Weather</th>
+    <th aria-rowindex="2" aria-colindex="2">Month</th>
+    <th aria-rowindex="2" aria-colindex="3">Day</th>
+    <th aria-rowindex="2" aria-colindex="4">Year</th>
+    <th aria-rowindex="2" aria-colindex="5">Weather</th>
   </tr>
   <tr>
-    <td role="gridcell" tabindex="0" aria-rowindex="2" aria-colindex="1">January</td>
-    <td role="gridcell" tabindex="-1" aria-rowindex="2" aria-colindex="2">01</td>
-    <td role="gridcell" tabindex="-1" aria-rowindex="2" aria-colindex="3">2017</td>
-    <td role="gridcell" tabindex="-1" aria-rowindex="2" aria-colindex="4">Sunny</td>
+    <td role="gridcell" tabindex="0" aria-rowindex="3" aria-colindex="2">January</td>
+    <td role="gridcell" tabindex="-1" aria-rowindex="3" aria-colindex="3">01</td>
+    <td role="gridcell" tabindex="-1" aria-rowindex="3" aria-colindex="4">2017</td>
+    <td role="gridcell" tabindex="-1" aria-rowindex="3" aria-colindex="5">Sunny</td>
   </tr>
   <tr>
-    <td role="gridcell" tabindex="0" aria-rowindex="3" aria-colindex="1">January</td>
-    <td role="gridcell" tabindex="-1" aria-rowindex="3" aria-colindex="2">02</td>
-    <td role="gridcell" tabindex="-1" aria-rowindex="3" aria-colindex="3">2017</td>
-    <td role="gridcell" tabindex="-1" aria-rowindex="3" aria-colindex="4">Rainy</td>
+    <td role="gridcell" tabindex="0" aria-rowindex="4" aria-colindex="2">January</td>
+    <td role="gridcell" tabindex="-1" aria-rowindex="4" aria-colindex="3">02</td>
+    <td role="gridcell" tabindex="-1" aria-rowindex="4" aria-colindex="4">2017</td>
+    <td role="gridcell" tabindex="-1" aria-rowindex="4" aria-colindex="5">Rainy</td>
   </tr>
   </tbody>
 </table>
@@ -38,7 +41,7 @@
 
 <script>
   // Hide the year column.
-  let cells = document.querySelectorAll('[aria-colindex="3"]');
+  let cells = document.querySelectorAll('[aria-colindex="4"]');
   for (let cell of cells) {
     cell.style.display = 'none';
   }
diff --git a/content/test/data/accessibility/html/table-th-colheader-expected-win.txt b/content/test/data/accessibility/html/table-th-colheader-expected-win.txt
index 4f1741c..32d89f1 100644
--- a/content/test/data/accessibility/html/table-th-colheader-expected-win.txt
+++ b/content/test/data/accessibility/html/table-th-colheader-expected-win.txt
@@ -1,9 +1,9 @@
 ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
 ++ROLE_SYSTEM_TABLE
 ++++ROLE_SYSTEM_ROW
-++++++ROLE_SYSTEM_COLUMNHEADER name='Firstname'
+++++++ROLE_SYSTEM_COLUMNHEADER name='Firstname' column_headers='<Firstname>'
 ++++++++ROLE_SYSTEM_STATICTEXT name='Firstname'
-++++++ROLE_SYSTEM_COLUMNHEADER name='Lastname'
+++++++ROLE_SYSTEM_COLUMNHEADER name='Lastname' column_headers='<Lastname>'
 ++++++++ROLE_SYSTEM_STATICTEXT name='Lastname'
 ++++ROLE_SYSTEM_ROW
 ++++++ROLE_SYSTEM_CELL name='Jill' column_headers='<Firstname>'
diff --git a/content/test/data/accessibility/html/table-th-rowheader-expected-win.txt b/content/test/data/accessibility/html/table-th-rowheader-expected-win.txt
index 9fb75b5..335eb7a6 100644
--- a/content/test/data/accessibility/html/table-th-rowheader-expected-win.txt
+++ b/content/test/data/accessibility/html/table-th-rowheader-expected-win.txt
@@ -1,12 +1,12 @@
 ROLE_SYSTEM_DOCUMENT name='Table example - th rowheader' READONLY FOCUSABLE
 ++ROLE_SYSTEM_TABLE
 ++++ROLE_SYSTEM_ROW
-++++++ROLE_SYSTEM_ROWHEADER name='Firstname'
+++++++ROLE_SYSTEM_ROWHEADER name='Firstname' row_headers='<Firstname>'
 ++++++++ROLE_SYSTEM_STATICTEXT name='Firstname'
 ++++++ROLE_SYSTEM_CELL name='Jill' row_headers='<Firstname>'
 ++++++++ROLE_SYSTEM_STATICTEXT name='Jill'
 ++++ROLE_SYSTEM_ROW
-++++++ROLE_SYSTEM_ROWHEADER name='Lastname'
+++++++ROLE_SYSTEM_ROWHEADER name='Lastname' row_headers='<Lastname>'
 ++++++++ROLE_SYSTEM_STATICTEXT name='Lastname'
 ++++++ROLE_SYSTEM_CELL name='Smith' row_headers='<Lastname>'
 ++++++++ROLE_SYSTEM_STATICTEXT name='Smith'
diff --git a/content/test/data/gpu/pixel_video_context_loss.html b/content/test/data/gpu/pixel_video_context_loss.html
index c146e50..6dd9c36 100644
--- a/content/test/data/gpu/pixel_video_context_loss.html
+++ b/content/test/data/gpu/pixel_video_context_loss.html
@@ -34,40 +34,45 @@
 }();
 
 function CrashGpuProcess() {
-  // Create a canvas element and webgl context.
+  // Create a canvas element and webgl context -- without a context, we won't
+  // get a webglcontextlost event.
   var canvas = document.createElement('canvas');
   var gl = canvas.getContext('webgl');
 
   // Now wait for the context loss before starting video playback.
   canvas.addEventListener('webglcontextlost', function(e) {
-    // Now wait for the video to playback to play until the end end, by which
-    // point a frame should be visible onscreen.
+    // Now wait for the video to play until the end, by which point a frame
+    // should be visible on screen.
     video.addEventListener('ended', function(e) {
-      domAutomationController.send("SUCCESS");
+      domAutomationController.send('SUCCESS');
     }, false);
     video.play();
   }, false);
 
-  // Essentially invokes chrome://gpucrash.
+  // Invokes chrome://gpucrash, which triggers a webglcontextlost event above.
   chrome.gpuBenchmarking.crashGpuProcess();
 }
 
 function Main() {
-  video = document.getElementById("video");
+  video = document.getElementById('video');
 
   // Wait until we're sure a frame has been displayed before crashing GPU.
   video.addEventListener('canplaythrough', CrashGpuProcess, false);
 
-  // src needs to be set after listener is added.
+  // Add an error listener to avoid timeouts if playback fails.
+  video.addEventListener('error', function(e) {
+    console.log('Video playback failed: ' + e.code + ', "' + e.message + '"');
+    domAutomationController.send('ERROR');
+  }, false);
+
+  // src needs to be set after listeners are added.
   video.src = QueryString.src;
 }
 </script>
 </head>
 <body onload="Main()">
 <div id="container" style="position:absolute; top:0px; left:0px">
-<video class="nomargin" id="video" width="240" height="135">
-<source type="video/mp4">
-</video>
+<video class="nomargin" id="video" width="240" height="135"></video>
 </div>
 </body>
 </html>
diff --git a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
index ca9744d7..86b15d96 100644
--- a/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
+++ b/content/test/gpu/gpu_tests/webgl2_conformance_expectations.py
@@ -421,6 +421,8 @@
         ['linux', 'passthrough', 'opengl', 'nvidia'], bug=766918)
     self.Fail('deqp/functional/gles3/shaderoperator/common_functions_*.html',
         ['linux', 'passthrough', 'opengl', 'nvidia'], bug=793055)
+    self.Flaky('conformance/extensions/webgl-compressed-texture-s3tc.html',
+        ['linux', 'passthrough', 'opengl', 'nvidia'], bug=927407)
 
     # Passthrough command decoder / Linux / OpenGL / Intel
     self.Flaky('conformance/extensions/webgl-compressed-texture-s3tc.html',
diff --git a/content/test/mock_render_widget_host_delegate.cc b/content/test/mock_render_widget_host_delegate.cc
index d45a5daa..8e7922a 100644
--- a/content/test/mock_render_widget_host_delegate.cc
+++ b/content/test/mock_render_widget_host_delegate.cc
@@ -67,4 +67,8 @@
   return is_fullscreen_;
 }
 
+RenderViewHostDelegateView* MockRenderWidgetHostDelegate::GetDelegateView() {
+  return &rvh_delegate_view_;
+}
+
 }  // namespace content
diff --git a/content/test/mock_render_widget_host_delegate.h b/content/test/mock_render_widget_host_delegate.h
index 739770c..5cdc010 100644
--- a/content/test/mock_render_widget_host_delegate.h
+++ b/content/test/mock_render_widget_host_delegate.h
@@ -12,6 +12,7 @@
 #include "content/browser/renderer_host/render_widget_host_input_event_router.h"
 #include "content/browser/renderer_host/text_input_manager.h"
 #include "content/public/browser/keyboard_event_processing_result.h"
+#include "content/test/stub_render_view_host_delegate_view.h"
 
 namespace content {
 
@@ -51,6 +52,7 @@
   void SendScreenRects() override;
   TextInputManager* GetTextInputManager() override;
   bool IsFullscreenForCurrentTab() override;
+  RenderViewHostDelegateView* GetDelegateView() override;
 
  private:
   std::unique_ptr<NativeWebKeyboardEvent> last_event_;
@@ -61,6 +63,7 @@
   RenderWidgetHostImpl* focused_widget_ = nullptr;
   KeyboardEventProcessingResult pre_handle_keyboard_event_result_ =
       KeyboardEventProcessingResult::NOT_HANDLED;
+  StubRenderViewHostDelegateView rvh_delegate_view_;
 
   DISALLOW_COPY_AND_ASSIGN(MockRenderWidgetHostDelegate);
 };
diff --git a/content/test/stub_render_view_host_delegate_view.h b/content/test/stub_render_view_host_delegate_view.h
new file mode 100644
index 0000000..1fc64970
--- /dev/null
+++ b/content/test/stub_render_view_host_delegate_view.h
@@ -0,0 +1,19 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_TEST_STUB_RENDER_VIEW_HOST_DELEGATE_VIEW_H_
+#define CONTENT_TEST_STUB_RENDER_VIEW_HOST_DELEGATE_VIEW_H_
+
+#include "content/browser/renderer_host/render_view_host_delegate_view.h"
+
+namespace content {
+
+class StubRenderViewHostDelegateView : public RenderViewHostDelegateView {
+  // The class already implements every method as a stub, but can't be created
+  // and destroyed without a subclass.
+};
+
+}  // namespace content
+
+#endif  // CONTENT_TEST_STUB_RENDER_VIEW_HOST_DELEGATE_VIEW_H_
diff --git a/extensions/browser/extension_pref_value_map.cc b/extensions/browser/extension_pref_value_map.cc
index 93fe044..4b4ff07 100644
--- a/extensions/browser/extension_pref_value_map.cc
+++ b/extensions/browser/extension_pref_value_map.cc
@@ -50,8 +50,9 @@
                                              ExtensionPrefsScope scope,
                                              base::Value* value) {
   PrefValueMap* prefs = GetExtensionPrefValueMap(ext_id, scope);
-
-  if (prefs->SetValue(key, base::WrapUnique(value)))
+  std::unique_ptr<base::Value> owned_value = base::WrapUnique(value);
+  if (prefs->SetValue(key,
+                      base::Value::FromUniquePtrValue(std::move(owned_value))))
     NotifyPrefValueChanged(key);
 }
 
diff --git a/fuchsia/BUILD.gn b/fuchsia/BUILD.gn
index 43194bc..f682394 100644
--- a/fuchsia/BUILD.gn
+++ b/fuchsia/BUILD.gn
@@ -63,6 +63,7 @@
   ]
   public_deps = [
     ":web_fidl",
+    "//skia/public/interfaces",
   ]
 }
 
@@ -385,3 +386,18 @@
     ":restaged_packages",
   ]
 }
+
+# Used by the top-level "gn_all" target to discover Fuchsia build targets.
+group("gn_all") {
+  testonly = true
+  deps = [
+    ":service_exe",
+    ":webrunner_browsertests",
+    ":webrunner_unittests",
+    "http:http_service_tests",
+    "runners:cast_runner",
+    "runners:cast_runner_browsertests",
+    "runners:cast_runner_integration_tests",
+    "runners:web_runner",
+  ]
+}
diff --git a/fuchsia/fidl/cast/cast_channel.fidl b/fuchsia/fidl/cast/cast_channel.fidl
index 77a88d0..b73fcbb 100644
--- a/fuchsia/fidl/cast/cast_channel.fidl
+++ b/fuchsia/fidl/cast/cast_channel.fidl
@@ -8,10 +8,9 @@
 
 [Discoverable]
 interface CastChannel {
-  /// Handles Cast Channel open calls from the webpage.
-  /// The new Cast Channel's message port is returned asynchronously once
-  /// opened by the webpage. The port is disconnected when the peer's Cast
-  /// Channel is closed.
-  Connect() -> (chromium.web.MessagePort channel);
+  /// Receives an opened Cast |channel| from the Cast application.
+  /// The return callback is invoked when the service is ready to accept a new
+  /// Cast Channel. OnOpened() should not be called again until this happens.
+  OnOpened(chromium.web.MessagePort channel) -> ();
 };
 
diff --git a/fuchsia/runners/BUILD.gn b/fuchsia/runners/BUILD.gn
index e04a406c..fe19b7b 100644
--- a/fuchsia/runners/BUILD.gn
+++ b/fuchsia/runners/BUILD.gn
@@ -30,18 +30,21 @@
 
 source_set("cast_runner_core") {
   sources = [
-    "cast/bindings/cast_channel.cc",
-    "cast/bindings/cast_channel.h",
+    "cast/cast_channel_bindings.cc",
+    "cast/cast_channel_bindings.h",
+    "cast/cast_component.cc",
+    "cast/cast_component.h",
     "cast/cast_runner.cc",
     "cast/cast_runner.h",
   ]
   data = [
-    "cast/bindings/cast_channel.js",
+    "cast/cast_channel_bindings.js",
   ]
   deps = [
     "//base",
     "//fuchsia:mem_buffer_common",
     "//fuchsia:named_message_port_connector",
+    "//fuchsia:web_fidl",
     "//url",
   ]
   public_deps = [
@@ -97,24 +100,13 @@
   visibility = [ ":*" ]
 }
 
-test("cast_runner_unittests") {
-  sources = [
-    "cast/cast_runner_unittest.cc",
-  ]
-  deps = [
-    ":cast_runner_core",
-    ":cast_runner_test_core",
-    "//base/test:run_all_unittests",
-    "//base/test:test_support",
-    "//fuchsia:test_support",
-    "//testing/gtest",
-  ]
-}
-
 test("cast_runner_integration_tests") {
   sources = [
     "cast/cast_runner_integration_test.cc",
   ]
+  data = [
+    "cast/testdata",
+  ]
   deps = [
     ":cast_runner_core",
     ":cast_runner_test_core",
@@ -132,7 +124,7 @@
 
 test("cast_runner_browsertests") {
   sources = [
-    "cast/bindings/cast_channel_browsertest.cc",
+    "cast/cast_channel_bindings_browsertest.cc",
   ]
   defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
   data = [
diff --git a/fuchsia/runners/cast/bindings/cast_channel.cc b/fuchsia/runners/cast/bindings/cast_channel.cc
deleted file mode 100644
index 5066bd97..0000000
--- a/fuchsia/runners/cast/bindings/cast_channel.cc
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "fuchsia/runners/cast/bindings/cast_channel.h"
-
-#include <lib/fit/function.h>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "base/bind.h"
-#include "base/callback.h"
-#include "base/fuchsia/fuchsia_logging.h"
-#include "base/macros.h"
-#include "base/path_service.h"
-#include "base/threading/thread_task_runner_handle.h"
-#include "fuchsia/common/mem_buffer_util.h"
-#include "fuchsia/common/named_message_port_connector.h"
-
-// Unique identifier of the Cast Channel message port, used by the JavaScript
-// API to connect to the port.
-const char kMessagePortName[] = "cast.__platform__.channel";
-
-CastChannelImpl::CastChannelImpl(
-    chromium::web::Frame* frame,
-    webrunner::NamedMessagePortConnector* connector)
-    : frame_(frame), connector_(connector) {
-  DCHECK(connector_);
-  DCHECK(frame_);
-
-  connector->Register(
-      kMessagePortName,
-      base::BindRepeating(&CastChannelImpl::OnMasterPortReceived,
-                          base::Unretained(this)),
-      frame_);
-
-  // Load the cast.__platform__.channel bundle from the package assets, into a
-  // mem::Buffer.
-  base::FilePath assets_path;
-  CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
-  fuchsia::mem::Buffer bindings_buf = webrunner::MemBufferFromFile(base::File(
-      assets_path.AppendASCII("fuchsia/runners/cast/bindings/cast_channel.js"),
-      base::File::FLAG_OPEN | base::File::FLAG_READ));
-  CHECK(bindings_buf.vmo);
-
-  // Inject the cast.__platform__.channel API to all origins.
-  std::vector<std::string> origins = {"*"};
-
-  // Configure the bundle to be re-injected every time the |frame_| content is
-  // loaded.
-  frame_->ExecuteJavaScript(
-      std::move(origins), std::move(bindings_buf),
-      chromium::web::ExecuteMode::ON_PAGE_LOAD,
-      [](bool success) { CHECK(success) << "Couldn't insert bindings."; });
-}
-
-CastChannelImpl::~CastChannelImpl() {
-  connector_->Unregister(frame_, kMessagePortName);
-}
-
-void CastChannelImpl::OnMasterPortError() {
-  master_port_.Unbind();
-}
-
-void CastChannelImpl::Connect(ConnectCallback callback) {
-  // If there is already a bound Cast Channel ready, then return it.
-  if (pending_channel_) {
-    callback(std::move(pending_channel_));
-    return;
-  }
-
-  pending_connect_cb_ = std::move(callback);
-
-  if (master_port_) {
-    master_port_->ReceiveMessage(
-        fit::bind_member(this, &CastChannelImpl::OnCastChannelMessageReceived));
-  }
-
-  // If there is no master port available at this time, then defer invocation of
-  // |pending_connect_cb_| until the master port has been received.
-}
-
-void CastChannelImpl::OnMasterPortReceived(chromium::web::MessagePortPtr port) {
-  DCHECK(port);
-
-  master_port_ = std::move(port);
-  master_port_.set_error_handler([this](zx_status_t status) {
-    ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
-        << "Cast Channel master port disconnected.";
-    OnMasterPortError();
-  });
-
-  if (pending_connect_cb_) {
-    // Resolve the in-flight Connect call.
-    Connect(std::move(pending_connect_cb_));
-  }
-}
-
-void CastChannelImpl::OnCastChannelMessageReceived(
-    chromium::web::WebMessage message) {
-  if (!message.incoming_transfer ||
-      !message.incoming_transfer->is_message_port()) {
-    LOG(WARNING) << "Received a CastChannel without a message port.";
-    OnMasterPortError();
-    return;
-  }
-
-  // Fulfill an outstanding Connect() operation, if there is one.
-  if (pending_connect_cb_) {
-    pending_connect_cb_(std::move(message.incoming_transfer->message_port()));
-    pending_connect_cb_ = {};
-    return;
-  }
-
-  pending_channel_ = std::move(message.incoming_transfer->message_port());
-}
diff --git a/fuchsia/runners/cast/bindings/cast_channel.h b/fuchsia/runners/cast/bindings/cast_channel.h
deleted file mode 100644
index e8d1601..0000000
--- a/fuchsia/runners/cast/bindings/cast_channel.h
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright 2019 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef FUCHSIA_RUNNERS_CAST_BINDINGS_CAST_CHANNEL_H_
-#define FUCHSIA_RUNNERS_CAST_BINDINGS_CAST_CHANNEL_H_
-
-#include <string>
-
-#include "base/callback.h"
-#include "base/macros.h"
-#include "base/strings/string_piece.h"
-#include "fuchsia/common/fuchsia_export.h"
-#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
-
-namespace webrunner {
-class NamedMessagePortConnector;
-}
-
-// Handles the injection of cast.__platform__.channel bindings into pages'
-// scripting context, and establishes a bidirectional message pipe over
-// which the two communicate.
-class FUCHSIA_EXPORT CastChannelImpl : public chromium::cast::CastChannel {
- public:
-  // Attaches CastChannel bindings and port to a |frame|.
-  // |frame|: The frame to be provided with a CastChannel.
-  // |connector|: The NamedMessagePortConnector to use for establishing
-  // transport.
-  // Both |frame| and |connector| must outlive |this|.
-  CastChannelImpl(chromium::web::Frame* frame,
-                  webrunner::NamedMessagePortConnector* connector);
-  ~CastChannelImpl() override;
-
-  // chromium::cast::CastChannel implementation.
-  void Connect(ConnectCallback callback) override;
-
- private:
-  // Receives a port used for receiving new Cast Channel ports.
-  void OnMasterPortReceived(chromium::web::MessagePortPtr port);
-
-  // Receives a message containing a newly opened Cast Channel from
-  // |master_port_|.
-  void OnCastChannelMessageReceived(chromium::web::WebMessage message);
-
-  // Handles error conditions on |master_port_|.
-  void OnMasterPortError();
-
-  chromium::web::Frame* const frame_;
-  webrunner::NamedMessagePortConnector* const connector_;
-
-  // A long-lived port, used to receive new Cast Channel ports when they are
-  // opened. Should be automatically  populated by the
-  // NamedMessagePortConnector whenever |frame| loads a new page.
-  chromium::web::MessagePortPtr master_port_;
-
-  fuchsia::mem::Buffer bindings_script_;
-  ConnectCallback pending_connect_cb_;
-
-  // A Cast Channel received from the webpage, waiting to be handled via
-  // ListenForChannel().
-  fidl::InterfaceHandle<chromium::web::MessagePort> pending_channel_;
-
-  DISALLOW_COPY_AND_ASSIGN(CastChannelImpl);
-};
-
-#endif  // FUCHSIA_RUNNERS_CAST_BINDINGS_CAST_CHANNEL_H_
diff --git a/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc b/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc
deleted file mode 100644
index 65779fe0..0000000
--- a/fuchsia/runners/cast/bindings/cast_channel_browsertest.cc
+++ /dev/null
@@ -1,218 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include <lib/fidl/cpp/binding.h>
-
-#include "base/barrier_closure.h"
-#include "base/bind.h"
-#include "base/files/file_util.h"
-#include "base/macros.h"
-#include "base/path_service.h"
-#include "base/test/test_timeouts.h"
-#include "base/threading/thread_restrictions.h"
-#include "fuchsia/common/mem_buffer_util.h"
-#include "fuchsia/common/named_message_port_connector.h"
-#include "fuchsia/common/test/test_common.h"
-#include "fuchsia/common/test/webrunner_browser_test.h"
-#include "fuchsia/runners/cast/bindings/cast_channel.h"
-#include "fuchsia/test/promise.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#include "url/url_constants.h"
-
-// Use a shorter name for NavigationEvent, because it is
-// referenced frequently in this file.
-using NavigationDetails = chromium::web::NavigationEvent;
-
-class CastChannelImplTest : public webrunner::WebRunnerBrowserTest,
-                            public chromium::web::NavigationEventObserver {
- public:
-  CastChannelImplTest() : run_timeout_(TestTimeouts::action_timeout()) {
-    set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
-  }
-
-  ~CastChannelImplTest() override = default;
-
- protected:
-  void SetUpOnMainThread() override {
-    webrunner::WebRunnerBrowserTest::SetUpOnMainThread();
-    base::ScopedAllowBlockingForTesting allow_blocking;
-    frame_ = WebRunnerBrowserTest::CreateFrame(this);
-    connector_ = std::make_unique<webrunner::NamedMessagePortConnector>();
-  }
-
-  void OnNavigationStateChanged(
-      chromium::web::NavigationEvent change,
-      OnNavigationStateChangedCallback callback) override {
-    connector_->NotifyPageLoad(frame_.get());
-    if (navigate_run_loop_)
-      navigate_run_loop_->Quit();
-    callback();
-  }
-
-  void CheckLoadUrl(const std::string& url,
-                    chromium::web::NavigationController* controller) {
-    navigate_run_loop_ = std::make_unique<base::RunLoop>();
-    controller->LoadUrl(url, nullptr);
-    navigate_run_loop_->Run();
-    navigate_run_loop_.reset();
-  }
-
-  std::unique_ptr<base::RunLoop> navigate_run_loop_;
-  chromium::web::FramePtr frame_;
-  std::unique_ptr<webrunner::NamedMessagePortConnector> connector_;
-
- private:
-  const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
-
-  DISALLOW_COPY_AND_ASSIGN(CastChannelImplTest);
-};
-
-IN_PROC_BROWSER_TEST_F(CastChannelImplTest, CastChannelBuffered) {
-  base::ScopedAllowBlockingForTesting allow_blocking;
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL test_url(embedded_test_server()->GetURL("/cast_channel.html"));
-
-  frame_->SetJavaScriptLogLevel(chromium::web::LogLevel::INFO);
-  chromium::web::NavigationControllerPtr controller;
-  frame_->GetNavigationController(controller.NewRequest());
-
-  testing::InSequence seq;
-  CastChannelImpl cast_channel_instance(frame_.get(), connector_.get());
-  fidl::Binding<chromium::cast::CastChannel> cast_channel_binding(
-      &cast_channel_instance);
-  chromium::cast::CastChannelPtr cast_channel =
-      cast_channel_binding.NewBinding().Bind();
-
-  // Verify that CastChannelImpl can properly handle message, connect,
-  // disconnect, and MessagePort disconnection events.
-  CheckLoadUrl(test_url.spec(), controller.get());
-
-  chromium::web::MessagePortPtr channel;
-
-  // Expect channel open.
-  {
-    base::RunLoop run_loop;
-    webrunner::Promise<fidl::InterfaceHandle<chromium::web::MessagePort>>
-        channel_promise(run_loop.QuitClosure());
-    cast_channel->Connect(
-        webrunner::ConvertToFitFunction(channel_promise.GetReceiveCallback()));
-    run_loop.Run();
-
-    channel = channel_promise->Bind();
-  }
-
-  // Send a message to the channel.
-  auto expected_list = {"this", "is", "a", "test"};
-  for (const std::string& expected : expected_list) {
-    base::RunLoop run_loop;
-    webrunner::Promise<chromium::web::WebMessage> message(
-        run_loop.QuitClosure());
-    channel->ReceiveMessage(
-        webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
-    run_loop.Run();
-
-    EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data), expected);
-  }
-}
-
-IN_PROC_BROWSER_TEST_F(CastChannelImplTest, CastChannelReconnect) {
-  base::ScopedAllowBlockingForTesting allow_blocking;
-  ASSERT_TRUE(embedded_test_server()->Start());
-  GURL test_url(embedded_test_server()->GetURL("/cast_channel_reconnect.html"));
-  GURL empty_url(embedded_test_server()->GetURL("/defaultresponse"));
-
-  frame_->SetJavaScriptLogLevel(chromium::web::LogLevel::INFO);
-  chromium::web::NavigationControllerPtr controller;
-  frame_->GetNavigationController(controller.NewRequest());
-
-  testing::InSequence seq;
-  CastChannelImpl cast_channel_instance(frame_.get(), connector_.get());
-  fidl::Binding<chromium::cast::CastChannel> cast_channel_binding(
-      &cast_channel_instance);
-  chromium::cast::CastChannelPtr cast_channel =
-      cast_channel_binding.NewBinding().Bind();
-
-  // Verify that CastChannelImpl can properly handle message, connect,
-  // disconnect, and MessagePort disconnection events.
-  // Also verify that the cast channel is used across inter-page navigations.
-  for (int i = 0; i < 5; ++i) {
-    CheckLoadUrl(test_url.spec(), controller.get());
-
-    chromium::web::MessagePortPtr channel;
-
-    // Expect channel open.
-    {
-      base::RunLoop run_loop;
-      webrunner::Promise<fidl::InterfaceHandle<chromium::web::MessagePort>>
-          channel_promise(run_loop.QuitClosure());
-      cast_channel->Connect(webrunner::ConvertToFitFunction(
-          channel_promise.GetReceiveCallback()));
-      run_loop.Run();
-
-      channel = channel_promise->Bind();
-    }
-
-    // Expect channel close.
-    {
-      base::RunLoop run_loop;
-      channel.set_error_handler([&run_loop](zx_status_t) { run_loop.Quit(); });
-      run_loop.Run();
-    }
-
-    // Expect channel re-open.
-    {
-      base::RunLoop run_loop;
-      webrunner::Promise<fidl::InterfaceHandle<chromium::web::MessagePort>>
-          channel_promise(run_loop.QuitClosure());
-      cast_channel->Connect(webrunner::ConvertToFitFunction(
-          channel_promise.GetReceiveCallback()));
-      run_loop.Run();
-
-      channel = channel_promise->Bind();
-    }
-
-    // Read "reconnected" from the channel.
-    {
-      base::RunLoop run_loop;
-      webrunner::Promise<chromium::web::WebMessage> message(
-          run_loop.QuitClosure());
-      channel->ReceiveMessage(
-          webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
-      run_loop.Run();
-
-      EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data),
-                "reconnected");
-    }
-
-    // Send a message to the channel.
-    {
-      chromium::web::WebMessage message;
-      message.data = webrunner::MemBufferFromString("hello");
-
-      base::RunLoop run_loop;
-      webrunner::Promise<bool> post_result(run_loop.QuitClosure());
-      channel->PostMessage(
-          std::move(message),
-          webrunner::ConvertToFitFunction(post_result.GetReceiveCallback()));
-      run_loop.Run();
-      EXPECT_EQ(true, *post_result);
-    }
-
-    // Get a message from the channel.
-    {
-      base::RunLoop run_loop;
-      webrunner::Promise<chromium::web::WebMessage> message(
-          run_loop.QuitClosure());
-      channel->ReceiveMessage(
-          webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
-      run_loop.Run();
-
-      EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data),
-                "ack hello");
-    }
-
-    // Navigate away.
-    CheckLoadUrl(empty_url.spec(), controller.get());
-  }
-}
diff --git a/fuchsia/runners/cast/cast_channel_bindings.cc b/fuchsia/runners/cast/cast_channel_bindings.cc
new file mode 100644
index 0000000..7e78b58
--- /dev/null
+++ b/fuchsia/runners/cast/cast_channel_bindings.cc
@@ -0,0 +1,128 @@
+// 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 "fuchsia/runners/cast/cast_channel_bindings.h"
+
+#include <lib/fit/function.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+
+// Unique identifier of the Cast Channel message port, used by the JavaScript
+// API to connect to the port.
+const char kMessagePortName[] = "cast.__platform__.channel";
+
+CastChannelBindings::CastChannelBindings(
+    chromium::web::Frame* frame,
+    webrunner::NamedMessagePortConnector* connector,
+    chromium::cast::CastChannelPtr channel_consumer,
+    base::OnceClosure on_error_closure)
+    : frame_(frame),
+      connector_(connector),
+      on_error_closure_(std::move(on_error_closure)),
+      channel_consumer_(std::move(channel_consumer)) {
+  DCHECK(connector_);
+  DCHECK(frame_);
+
+  channel_consumer_.set_error_handler([this](zx_status_t status) mutable {
+    ZX_LOG(ERROR, status) << "CastChannel error:";
+    std::move(on_error_closure_).Run();
+  });
+
+  connector->Register(
+      kMessagePortName,
+      base::BindRepeating(&CastChannelBindings::OnMasterPortReceived,
+                          base::Unretained(this)),
+      frame_);
+
+  // Load the cast.__platform__.channel bundle from the package assets, into a
+  // mem::Buffer.
+  base::FilePath assets_path;
+  CHECK(base::PathService::Get(base::DIR_ASSETS, &assets_path));
+  fuchsia::mem::Buffer bindings_buf = webrunner::MemBufferFromFile(base::File(
+      assets_path.AppendASCII("fuchsia/runners/cast/cast_channel_bindings.js"),
+      base::File::FLAG_OPEN | base::File::FLAG_READ));
+  CHECK(bindings_buf.vmo);
+
+  // Inject the cast.__platform__.channel API to all origins.
+  std::vector<std::string> origins = {"*"};
+
+  // Configure the bundle to be re-injected every time the |frame_| content is
+  // loaded.
+  frame_->ExecuteJavaScript(
+      std::move(origins), std::move(bindings_buf),
+      chromium::web::ExecuteMode::ON_PAGE_LOAD,
+      [](bool success) { CHECK(success) << "Couldn't insert bindings."; });
+}
+
+CastChannelBindings::~CastChannelBindings() {
+  connector_->Unregister(frame_, kMessagePortName);
+}
+
+void CastChannelBindings::OnMasterPortError() {
+  master_port_.Unbind();
+}
+
+void CastChannelBindings::OnMasterPortReceived(
+    chromium::web::MessagePortPtr port) {
+  DCHECK(port);
+
+  master_port_ = std::move(port);
+  master_port_.set_error_handler([this](zx_status_t status) {
+    ZX_LOG_IF(WARNING, status != ZX_ERR_PEER_CLOSED, status)
+        << "Cast Channel master port disconnected.";
+    OnMasterPortError();
+  });
+  master_port_->ReceiveMessage(fit::bind_member(
+      this, &CastChannelBindings::OnCastChannelMessageReceived));
+}
+
+void CastChannelBindings::OnCastChannelMessageReceived(
+    chromium::web::WebMessage message) {
+  if (!message.incoming_transfer ||
+      !message.incoming_transfer->is_message_port()) {
+    LOG(WARNING) << "Received a CastChannel without a message port.";
+    OnMasterPortError();
+    return;
+  }
+
+  SendChannelToConsumer(message.incoming_transfer->message_port().Bind());
+
+  master_port_->ReceiveMessage(fit::bind_member(
+      this, &CastChannelBindings::OnCastChannelMessageReceived));
+}
+
+void CastChannelBindings::SendChannelToConsumer(
+    chromium::web::MessagePortPtr channel) {
+  if (consumer_ready_for_port_) {
+    consumer_ready_for_port_ = false;
+    channel_consumer_->OnOpened(
+        std::move(channel),
+        fit::bind_member(this, &CastChannelBindings::OnConsumerReadyForPort));
+  } else {
+    connected_channel_queue_.push_front(std::move(channel));
+  }
+}
+
+void CastChannelBindings::OnConsumerReadyForPort() {
+  DCHECK(!consumer_ready_for_port_);
+
+  consumer_ready_for_port_ = true;
+  if (!connected_channel_queue_.empty()) {
+    // Deliver the next enqueued channel.
+    chromium::web::MessagePortPtr next_port =
+        std::move(connected_channel_queue_.back());
+    SendChannelToConsumer(std::move(next_port));
+    connected_channel_queue_.pop_back();
+  }
+}
diff --git a/fuchsia/runners/cast/cast_channel_bindings.h b/fuchsia/runners/cast/cast_channel_bindings.h
new file mode 100644
index 0000000..01146e6
--- /dev/null
+++ b/fuchsia/runners/cast/cast_channel_bindings.h
@@ -0,0 +1,83 @@
+// 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 FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
+#define FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
+
+#include <deque>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/strings/string_piece.h"
+#include "fuchsia/common/fuchsia_export.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+
+namespace webrunner {
+class NamedMessagePortConnector;
+}
+
+// Handles the injection of cast.__platform__.channel bindings into pages'
+// scripting context, and establishes a bidirectional message pipe over
+// which the two communicate.
+class FUCHSIA_EXPORT CastChannelBindings {
+ public:
+  // Attaches CastChannel bindings and port to a |frame|.
+  // |frame|: The frame to be provided with a CastChannel.
+  // |connector|: The NamedMessagePortConnector to use for establishing
+  // transport.
+  // |on_error_closure|: Invoked in the event of an unrecoverable error (e.g.
+  //                     lost connection to the Agent). The callback must
+  //                     remain valid for the entire lifetime of |this|.
+  // |channel_consumer|: A FIDL service which receives opened Cast Channels.
+  // Both |frame| and |connector| must outlive |this|.
+  CastChannelBindings(chromium::web::Frame* frame,
+                      webrunner::NamedMessagePortConnector* connector,
+                      chromium::cast::CastChannelPtr channel_consumer,
+                      base::OnceClosure on_error_closure);
+  ~CastChannelBindings();
+
+ private:
+  // Receives a port used for receiving new Cast Channel ports.
+  void OnMasterPortReceived(chromium::web::MessagePortPtr port);
+
+  // Receives a message containing a newly opened Cast Channel from
+  // |master_port_|.
+  void OnCastChannelMessageReceived(chromium::web::WebMessage message);
+
+  // Indicates that |channel_consumer_| is ready to take another port.
+  void OnConsumerReadyForPort();
+
+  // Sends or enqueues a Cast Channel for sending to |channel_consumer_|.
+  void SendChannelToConsumer(chromium::web::MessagePortPtr channel);
+
+  // Handles error conditions on |master_port_|.
+  void OnMasterPortError();
+
+  chromium::web::Frame* const frame_;
+  webrunner::NamedMessagePortConnector* const connector_;
+
+  // A queue of channels waiting to be sent the Cast Channel FIDL service.
+  std::deque<chromium::web::MessagePortPtr> connected_channel_queue_;
+
+  // A long-lived port, used to receive new Cast Channel ports when they are
+  // opened. Should be automatically  populated by the
+  // NamedMessagePortConnector whenever |frame| loads a new page.
+  chromium::web::MessagePortPtr master_port_;
+
+  fuchsia::mem::Buffer bindings_script_;
+
+  base::OnceClosure on_error_closure_;
+
+  // The service which will receive connected Cast Channels.
+  chromium::cast::CastChannelPtr channel_consumer_;
+
+  // If set, indicates that |channel_consumer_| is ready to accept another Cast
+  // Channel.
+  bool consumer_ready_for_port_ = true;
+
+  DISALLOW_COPY_AND_ASSIGN(CastChannelBindings);
+};
+
+#endif  // FUCHSIA_RUNNERS_CAST_CAST_CHANNEL_BINDINGS_H_
diff --git a/fuchsia/runners/cast/bindings/cast_channel.js b/fuchsia/runners/cast/cast_channel_bindings.js
similarity index 100%
rename from fuchsia/runners/cast/bindings/cast_channel.js
rename to fuchsia/runners/cast/cast_channel_bindings.js
diff --git a/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc b/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc
new file mode 100644
index 0000000..fb00446c
--- /dev/null
+++ b/fuchsia/runners/cast/cast_channel_bindings_browsertest.cc
@@ -0,0 +1,211 @@
+// Copyright 2018 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/fidl/cpp/binding.h>
+
+#include "base/barrier_closure.h"
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/test_timeouts.h"
+#include "base/threading/thread_restrictions.h"
+#include "fuchsia/common/mem_buffer_util.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/common/test/webrunner_browser_test.h"
+#include "fuchsia/runners/cast/cast_channel_bindings.h"
+#include "fuchsia/test/promise.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/url_constants.h"
+
+namespace {
+
+// Use a shorter name for NavigationEvent, because it is
+// referenced frequently in this file.
+using NavigationDetails = chromium::web::NavigationEvent;
+
+void OnError() {
+  ADD_FAILURE();
+}
+
+class CastChannelBindingsTest : public webrunner::WebRunnerBrowserTest,
+                                public chromium::web::NavigationEventObserver,
+                                public chromium::cast::CastChannel {
+ public:
+  CastChannelBindingsTest()
+      : receiver_binding_(this), run_timeout_(TestTimeouts::action_timeout()) {
+    set_test_server_root(base::FilePath("fuchsia/runners/cast/testdata"));
+  }
+
+  ~CastChannelBindingsTest() override = default;
+
+ protected:
+  void SetUpOnMainThread() override {
+    webrunner::WebRunnerBrowserTest::SetUpOnMainThread();
+    base::ScopedAllowBlockingForTesting allow_blocking;
+    frame_ = WebRunnerBrowserTest::CreateFrame(this);
+    connector_ = std::make_unique<webrunner::NamedMessagePortConnector>();
+  }
+
+  void OnNavigationStateChanged(
+      chromium::web::NavigationEvent change,
+      OnNavigationStateChangedCallback callback) override {
+    connector_->NotifyPageLoad(frame_.get());
+    if (navigate_run_loop_)
+      navigate_run_loop_->Quit();
+    callback();
+  }
+
+  void OnOpened(fidl::InterfaceHandle<chromium::web::MessagePort> channel,
+                OnOpenedCallback receive_next_channel_cb) override {
+    connected_channel_ = channel.Bind();
+    receive_next_channel_cb_ = std::move(receive_next_channel_cb);
+
+    if (on_channel_connected_cb_)
+      std::move(on_channel_connected_cb_).Run();
+  }
+
+  void SignalReadyForNewChannel() { receive_next_channel_cb_(); }
+
+  void WaitUntilCastChannelOpened() {
+    if (connected_channel_)
+      return;
+
+    base::RunLoop run_loop;
+    on_channel_connected_cb_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  void WaitUntilCastChannelClosed() {
+    if (!connected_channel_)
+      return;
+
+    base::RunLoop run_loop;
+    connected_channel_.set_error_handler(
+        [quit_closure = run_loop.QuitClosure()](zx_status_t) {
+          quit_closure.Run();
+        });
+    run_loop.Run();
+  }
+
+  std::string ReadStringFromChannel() {
+    base::RunLoop run_loop;
+    webrunner::Promise<chromium::web::WebMessage> message(
+        run_loop.QuitClosure());
+    connected_channel_->ReceiveMessage(
+        webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
+    run_loop.Run();
+    return webrunner::StringFromMemBufferOrDie(message->data);
+  }
+
+  void CheckLoadUrl(const std::string& url,
+                    chromium::web::NavigationController* controller) {
+    navigate_run_loop_ = std::make_unique<base::RunLoop>();
+    controller->LoadUrl(url, nullptr);
+    navigate_run_loop_->Run();
+    navigate_run_loop_.reset();
+  }
+
+  std::unique_ptr<base::RunLoop> navigate_run_loop_;
+  chromium::web::FramePtr frame_;
+  std::unique_ptr<webrunner::NamedMessagePortConnector> connector_;
+  fidl::Binding<chromium::cast::CastChannel> receiver_binding_;
+
+  // The connected Cast Channel.
+  chromium::web::MessagePortPtr connected_channel_;
+
+  // A pending on-connect callback, to be invoked once a Cast Channel is
+  // received.
+  base::OnceClosure on_channel_connected_cb_;
+
+ private:
+  const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
+  OnOpenedCallback receive_next_channel_cb_;
+
+  DISALLOW_COPY_AND_ASSIGN(CastChannelBindingsTest);
+};
+
+IN_PROC_BROWSER_TEST_F(CastChannelBindingsTest, CastChannelBufferedInput) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL test_url(embedded_test_server()->GetURL("/cast_channel.html"));
+
+  frame_->SetJavaScriptLogLevel(chromium::web::LogLevel::INFO);
+  chromium::web::NavigationControllerPtr controller;
+  frame_->GetNavigationController(controller.NewRequest());
+
+  testing::InSequence seq;
+  CastChannelBindings cast_channel_instance(
+      frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind(),
+      base::MakeExpectedNotRunClosure(FROM_HERE));
+
+  // Verify that CastChannelBindings can properly handle message, connect,
+  // disconnect, and MessagePort disconnection events.
+  CheckLoadUrl(test_url.spec(), controller.get());
+
+  WaitUntilCastChannelOpened();
+
+  auto expected_list = {"this", "is", "a", "test"};
+  for (const std::string& expected : expected_list) {
+    EXPECT_EQ(ReadStringFromChannel(), expected);
+  }
+}
+
+IN_PROC_BROWSER_TEST_F(CastChannelBindingsTest, CastChannelReconnect) {
+  base::ScopedAllowBlockingForTesting allow_blocking;
+  ASSERT_TRUE(embedded_test_server()->Start());
+  GURL test_url(embedded_test_server()->GetURL("/cast_channel_reconnect.html"));
+  GURL empty_url(embedded_test_server()->GetURL("/defaultresponse"));
+
+  frame_->SetJavaScriptLogLevel(chromium::web::LogLevel::INFO);
+  chromium::web::NavigationControllerPtr controller;
+  frame_->GetNavigationController(controller.NewRequest());
+
+  testing::InSequence seq;
+  CastChannelBindings cast_channel_instance(
+      frame_.get(), connector_.get(), receiver_binding_.NewBinding().Bind(),
+      base::BindOnce(&OnError));
+
+  // Verify that CastChannelBindings can properly handle message, connect,
+  // disconnect, and MessagePort disconnection events.
+  // Also verify that the cast channel is used across inter-page navigations.
+  for (int i = 0; i < 5; ++i) {
+    CheckLoadUrl(test_url.spec(), controller.get());
+
+    WaitUntilCastChannelOpened();
+
+    WaitUntilCastChannelClosed();
+
+    SignalReadyForNewChannel();
+
+    WaitUntilCastChannelOpened();
+
+    EXPECT_EQ("reconnected", ReadStringFromChannel());
+
+    // Send a message to the channel.
+    {
+      chromium::web::WebMessage message;
+      message.data = webrunner::MemBufferFromString("hello");
+
+      base::RunLoop run_loop;
+      webrunner::Promise<bool> post_result(run_loop.QuitClosure());
+      connected_channel_->PostMessage(
+          std::move(message),
+          webrunner::ConvertToFitFunction(post_result.GetReceiveCallback()));
+      run_loop.Run();
+      EXPECT_EQ(true, *post_result);
+    }
+
+    EXPECT_EQ("ack hello", ReadStringFromChannel());
+
+    // Navigate away.
+    CheckLoadUrl(empty_url.spec(), controller.get());
+
+    SignalReadyForNewChannel();
+  }
+}
+
+}  // namespace
diff --git a/fuchsia/runners/cast/cast_component.cc b/fuchsia/runners/cast/cast_component.cc
new file mode 100644
index 0000000..03216c0
--- /dev/null
+++ b/fuchsia/runners/cast/cast_component.cc
@@ -0,0 +1,67 @@
+// 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 "fuchsia/runners/cast/cast_component.h"
+
+#include <lib/fidl/cpp/binding.h>
+#include <algorithm>
+#include <utility>
+
+#include "base/auto_reset.h"
+#include "base/fuchsia/fuchsia_logging.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
+#include "fuchsia/runners/cast/cast_runner.h"
+#include "fuchsia/runners/common/web_component.h"
+
+namespace {
+constexpr int kBindingsFailureExitCode = 129;
+}  // namespace
+
+CastComponent::CastComponent(
+    CastRunner* runner,
+    fuchsia::sys::StartupInfo startup_info,
+    fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+        controller_request)
+    : WebComponent(runner,
+                   std::move(startup_info),
+                   std::move(controller_request)),
+      navigation_observer_binding_(this) {
+  base::AutoReset<bool> constructor_active_reset(&constructor_active_, true);
+
+  if (!additional_services() || std::find(additional_service_names().begin(),
+                                          additional_service_names().end(),
+                                          chromium::cast::CastChannel::Name_) ==
+                                    additional_service_names().end()) {
+    LOG(ERROR) << "Component instantiated without required service: "
+               << chromium::cast::CastChannel::Name_;
+    DestroyComponent(1, fuchsia::sys::TerminationReason::UNSUPPORTED);
+    return;
+  }
+
+  cast_channel_ = std::make_unique<CastChannelBindings>(
+      frame(), &connector_,
+      additional_services()->ConnectToService<chromium::cast::CastChannel>(),
+      base::BindOnce(&CastComponent::DestroyComponent, base::Unretained(this),
+                     kBindingsFailureExitCode,
+                     fuchsia::sys::TerminationReason::INTERNAL_ERROR));
+  frame()->SetNavigationEventObserver(
+      navigation_observer_binding_.NewBinding());
+}
+
+CastComponent::~CastComponent() = default;
+
+void CastComponent::DestroyComponent(int termination_exit_code,
+                                     fuchsia::sys::TerminationReason reason) {
+  DCHECK(!constructor_active_);
+
+  WebComponent::DestroyComponent(termination_exit_code, reason);
+}
+
+void CastComponent::OnNavigationStateChanged(
+    chromium::web::NavigationEvent change,
+    OnNavigationStateChangedCallback callback) {
+  if (change.url)
+    connector_.NotifyPageLoad(frame());
+  callback();
+}
diff --git a/fuchsia/runners/cast/cast_component.h b/fuchsia/runners/cast/cast_component.h
new file mode 100644
index 0000000..7bb33b8
--- /dev/null
+++ b/fuchsia/runners/cast/cast_component.h
@@ -0,0 +1,50 @@
+// 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 FUCHSIA_RUNNERS_CAST_CAST_COMPONENT_H_
+#define FUCHSIA_RUNNERS_CAST_CAST_COMPONENT_H_
+
+#include <lib/fidl/cpp/binding.h>
+#include <memory>
+
+#include "base/fuchsia/service_directory.h"
+#include "fuchsia/common/named_message_port_connector.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
+#include "fuchsia/runners/cast/cast_channel_bindings.h"
+#include "fuchsia/runners/common/web_component.h"
+
+class CastRunner;
+
+// A specialization of WebComponent which adds Cast-specific services.
+class CastComponent : public WebComponent,
+                      public chromium::web::NavigationEventObserver {
+ public:
+  CastComponent(CastRunner* runner,
+                fuchsia::sys::StartupInfo startup_info,
+                fidl::InterfaceRequest<fuchsia::sys::ComponentController>
+                    controller_request);
+  ~CastComponent() override;
+
+ private:
+  // WebComponent overrides.
+  void DestroyComponent(int termination_exit_code,
+                        fuchsia::sys::TerminationReason reason) override;
+
+  // chromium::web::NavigationEventObserver implementation.
+  // Triggers the injection of API channels into the page content.
+  void OnNavigationStateChanged(
+      chromium::web::NavigationEvent change,
+      OnNavigationStateChangedCallback callback) override;
+
+  bool constructor_active_ = false;
+  webrunner::NamedMessagePortConnector connector_;
+  std::unique_ptr<CastChannelBindings> cast_channel_;
+
+  fidl::Binding<chromium::web::NavigationEventObserver>
+      navigation_observer_binding_;
+
+  DISALLOW_COPY_AND_ASSIGN(CastComponent);
+};
+
+#endif  // FUCHSIA_RUNNERS_CAST_CAST_COMPONENT_H_
diff --git a/fuchsia/runners/cast/cast_runner.cc b/fuchsia/runners/cast/cast_runner.cc
index 10e4b80..caeb1aa 100644
--- a/fuchsia/runners/cast/cast_runner.cc
+++ b/fuchsia/runners/cast/cast_runner.cc
@@ -5,10 +5,13 @@
 #include "fuchsia/runners/cast/cast_runner.h"
 
 #include <fuchsia/sys/cpp/fidl.h>
+#include <memory>
+#include <string>
 #include <utility>
 
 #include "base/logging.h"
-#include "fuchsia/runners/common/web_component.h"
+#include "base/memory/ptr_util.h"
+#include "fuchsia/runners/cast/cast_component.h"
 #include "url/gurl.h"
 
 CastRunner::CastRunner(
@@ -69,7 +72,8 @@
 
   // If a config was returned then use it to launch a component.
   GURL cast_app_url(app_config->web_url);
-  RegisterComponent(WebComponent::ForUrlRequest(this, std::move(cast_app_url),
-                                                std::move(startup_info),
-                                                std::move(controller_request)));
+  auto component = std::make_unique<CastComponent>(
+      this, std::move(startup_info), std::move(controller_request));
+  component->LoadUrl(std::move(cast_app_url));
+  RegisterComponent(std::move(component));
 }
diff --git a/fuchsia/runners/cast/cast_runner.h b/fuchsia/runners/cast/cast_runner.h
index da1b86a..b77eccf 100644
--- a/fuchsia/runners/cast/cast_runner.h
+++ b/fuchsia/runners/cast/cast_runner.h
@@ -28,14 +28,14 @@
                           controller_request) override;
 
  private:
-  chromium::cast::ApplicationConfigManagerPtr app_config_manager_;
-
   void GetConfigCallback(
       fuchsia::sys::StartupInfo startup_info,
       fidl::InterfaceRequest<fuchsia::sys::ComponentController>
           controller_request,
       chromium::cast::ApplicationConfigPtr app_config);
 
+  const chromium::cast::ApplicationConfigManagerPtr app_config_manager_;
+
   DISALLOW_COPY_AND_ASSIGN(CastRunner);
 };
 
diff --git a/fuchsia/runners/cast/cast_runner_integration_test.cc b/fuchsia/runners/cast/cast_runner_integration_test.cc
index 3f8b577..bf57eb58 100644
--- a/fuchsia/runners/cast/cast_runner_integration_test.cc
+++ b/fuchsia/runners/cast/cast_runner_integration_test.cc
@@ -11,16 +11,24 @@
 #include "base/message_loop/message_loop.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/test_timeouts.h"
+#include "fuchsia/common/test/test_common.h"
+#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
 #include "fuchsia/runners/cast/cast_runner.h"
 #include "fuchsia/runners/cast/fake_application_config_manager.h"
 #include "fuchsia/runners/cast/test_common.h"
 #include "fuchsia/runners/common/web_component.h"
 #include "fuchsia/runners/common/web_content_runner.h"
 #include "fuchsia/test/promise.h"
+#include "net/test/embedded_test_server/default_handlers.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace castrunner {
+
 namespace {
 
+const char kTestServerRoot[] =
+    FILE_PATH_LITERAL("fuchsia/runners/cast/testdata");
+
 void ComponentErrorHandler(zx_status_t status) {
   ZX_LOG(ERROR, status) << "Component launch failed.";
   ADD_FAILURE();
@@ -28,9 +36,12 @@
 
 }  // namespace
 
-class CastRunnerIntegrationTest : public testing::Test {
+class CastRunnerIntegrationTest : public testing::Test,
+                                  public chromium::cast::CastChannel {
  public:
-  CastRunnerIntegrationTest() : run_timeout_(TestTimeouts::action_timeout()) {
+  CastRunnerIntegrationTest()
+      : run_timeout_(TestTimeouts::action_timeout()),
+        cast_channel_binding_(this) {
     // Create a new test ServiceDirectory, and a test ComponentContext
     // connected to it, for the test to use to drive the CastRunner.
     zx::channel service_directory_request, service_directory_client;
@@ -44,11 +55,9 @@
         std::move(service_directory_client));
 
     // Create the AppConfigManager.
-    app_config_manager_ =
-        std::make_unique<FakeApplicationConfigManager>(&test_server_);
     app_config_binding_ = std::make_unique<
         fidl::Binding<chromium::cast::ApplicationConfigManager>>(
-        app_config_manager_.get());
+        &app_config_manager_);
     chromium::cast::ApplicationConfigManagerPtr app_config_manager_interface;
     app_config_binding_->Bind(app_config_manager_interface.NewRequest());
 
@@ -69,7 +78,11 @@
     });
   }
 
-  void SetUp() override { ASSERT_TRUE(test_server_.Start()); }
+  void SetUp() override {
+    test_server_.ServeFilesFromSourceDirectory(kTestServerRoot);
+    net::test_server::RegisterDefaultHandlers(&test_server_);
+    ASSERT_TRUE(test_server_.Start());
+  }
 
   void TearDown() override {
     // Disconnect the CastRunner.
@@ -77,17 +90,46 @@
     cast_runner_run_loop_.Run();
   }
 
+  void WaitUntilCastChannelOpened() {
+    if (connected_channel_)
+      return;
+
+    base::RunLoop run_loop;
+    on_channel_connected_cb_ = run_loop.QuitClosure();
+    run_loop.Run();
+  }
+
+  // chromium::web::CastChannel implementation.
+  void OnOpened(fidl::InterfaceHandle<chromium::web::MessagePort> channel,
+                OnOpenedCallback callback_ignored) override {
+    // |callback_ignored| is dropped because these tests don't exercise multiple
+    // channel lifetimes.
+    connected_channel_ = channel.Bind();
+
+    if (on_channel_connected_cb_)
+      std::move(on_channel_connected_cb_).Run();
+  }
+
  protected:
   const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
-
   base::MessageLoopForIO message_loop_;
-
   net::EmbeddedTestServer test_server_;
 
-  std::unique_ptr<FakeApplicationConfigManager> app_config_manager_;
+  // Returns fake Cast application information to the CastRunner.
+  FakeApplicationConfigManager app_config_manager_;
   std::unique_ptr<fidl::Binding<chromium::cast::ApplicationConfigManager>>
       app_config_binding_;
 
+  // The connected Cast Channel.
+  chromium::web::MessagePortPtr connected_channel_;
+
+  // A pending on-connect callback, to be invoked once a Cast Channel is
+  // received.
+  base::OnceClosure on_channel_connected_cb_;
+
+  // Receives connected channels from the CastRunner.
+  fidl::Binding<chromium::cast::CastChannel> cast_channel_binding_;
+
   // ServiceDirectory into which the CastRunner will publish itself.
   std::unique_ptr<base::fuchsia::ServiceDirectory> test_service_directory_;
   std::unique_ptr<base::fuchsia::ComponentContext> test_component_context_;
@@ -102,12 +144,16 @@
 // A basic integration test ensuring a basic cast request launches the right
 // URL in the Chromium service.
 TEST_F(CastRunnerIntegrationTest, BasicRequest) {
+  const char kBlankAppId[] = "00000000";
+  const char kBlankAppPath[] = "/defaultresponse";
+  app_config_manager_.AddAppMapping(kBlankAppId,
+                                    test_server_.GetURL(kBlankAppPath));
+
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
   base::fuchsia::ComponentContext component_services(StartCastComponent(
-      base::StringPrintf("cast:%s",
-                         FakeApplicationConfigManager::kTestCastAppId),
-      &cast_runner_ptr_, component_controller_ptr.NewRequest()));
+      base::StringPrintf("cast:%s", kBlankAppId), &cast_runner_ptr_,
+      component_controller_ptr.NewRequest(), &cast_channel_binding_));
   component_controller_ptr.set_error_handler(&ComponentErrorHandler);
 
   // Access the NavigationController from the WebComponent. The test will hang
@@ -132,16 +178,24 @@
     nav_controller->GetVisibleEntry(
         webrunner::ConvertToFitFunction(nav_entry.GetReceiveCallback()));
     run_loop.Run();
-    EXPECT_EQ(nav_entry->get()->url, test_server_.base_url().spec());
+    EXPECT_EQ(nav_entry->get()->url, test_server_.GetURL(kBlankAppPath).spec());
   }
+
+  // Verify that the component is torn down when |component_controller_ptr| is
+  // unbound, by observing the destruction of its child Interfaces.
+  base::RunLoop destruction_run_loop;
+  nav_controller.set_error_handler(
+      [&destruction_run_loop](zx_status_t) { destruction_run_loop.Quit(); });
+  component_controller_ptr.Unbind();
+  destruction_run_loop.Run();
 }
 
 TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) {
   // Launch the test-app component.
   fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-  base::fuchsia::ComponentContext component_services(
-      StartCastComponent("cast:99999999", &cast_runner_ptr_,
-                         component_controller_ptr.NewRequest()));
+  base::fuchsia::ComponentContext component_services(StartCastComponent(
+      "cast:99999999", &cast_runner_ptr_, component_controller_ptr.NewRequest(),
+      &cast_channel_binding_));
   component_controller_ptr.set_error_handler(&ComponentErrorHandler);
 
   // Ensure no WebComponent was created.
@@ -151,3 +205,110 @@
   run_loop.Run();
   EXPECT_EQ(*web_component, nullptr);
 }
+
+TEST_F(CastRunnerIntegrationTest, CastChannel) {
+  const char kCastChannelAppId[] = "00000001";
+  const char kCastChannelAppPath[] = "/cast_channel.html";
+  app_config_manager_.AddAppMapping(kCastChannelAppId,
+                                    test_server_.GetURL(kCastChannelAppPath));
+
+  // Launch the test-app component.
+  fuchsia::sys::ComponentControllerPtr component_controller_ptr;
+  base::fuchsia::ComponentContext component_services(StartCastComponent(
+      base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
+      component_controller_ptr.NewRequest(), &cast_channel_binding_));
+  component_controller_ptr.set_error_handler(&ComponentErrorHandler);
+
+  // Access the NavigationController from the WebComponent. The test will hang
+  // here if no WebComponent was created.
+  chromium::web::NavigationControllerPtr nav_controller;
+  {
+    base::RunLoop run_loop;
+    webrunner::Promise<WebComponent*> web_component(run_loop.QuitClosure());
+    cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
+    run_loop.Run();
+    ASSERT_NE(*web_component, nullptr);
+    (*web_component)
+        ->frame()
+        ->GetNavigationController(nav_controller.NewRequest());
+  }
+
+  // Ensure the NavigationEntry has the expected URL.
+  {
+    base::RunLoop run_loop;
+    webrunner::Promise<std::unique_ptr<chromium::web::NavigationEntry>>
+        nav_entry(run_loop.QuitClosure());
+    nav_controller->GetVisibleEntry(
+        webrunner::ConvertToFitFunction(nav_entry.GetReceiveCallback()));
+    run_loop.Run();
+    EXPECT_EQ(nav_entry->get()->url,
+              test_server_.GetURL(kCastChannelAppPath).spec());
+  }
+
+  WaitUntilCastChannelOpened();
+
+  auto expected_list = {"this", "is", "a", "test"};
+  for (const std::string& expected : expected_list) {
+    base::RunLoop run_loop;
+    webrunner::Promise<chromium::web::WebMessage> message(
+        run_loop.QuitClosure());
+    connected_channel_->ReceiveMessage(
+        webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
+    run_loop.Run();
+
+    EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data), expected);
+  }
+
+  component_controller_ptr.Unbind();
+  base::RunLoop run_loop;
+  cast_channel_binding_.set_error_handler(
+      [&run_loop](zx_status_t) { run_loop.Quit(); });
+  run_loop.Run();
+}
+
+TEST_F(CastRunnerIntegrationTest, CastChannelConsumerDropped) {
+  const char kCastChannelAppId[] = "00000001";
+  const char kCastChannelAppPath[] = "/cast_channel.html";
+  app_config_manager_.AddAppMapping(kCastChannelAppId,
+                                    test_server_.GetURL(kCastChannelAppPath));
+
+  // Launch the test-app component.
+  fuchsia::sys::ComponentControllerPtr component_controller_ptr;
+  base::fuchsia::ComponentContext component_services(StartCastComponent(
+      base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
+      component_controller_ptr.NewRequest(), &cast_channel_binding_));
+
+  // Expect that disconnecting the Cast Channel consumer service will trigger
+  // the destruction of the Cast Component.
+  cast_channel_binding_.Unbind();
+  base::RunLoop run_loop;
+  component_controller_ptr.set_error_handler(
+      [&run_loop](zx_status_t) { run_loop.Quit(); });
+  run_loop.Run();
+
+  EXPECT_FALSE(component_controller_ptr.is_bound());
+}
+
+TEST_F(CastRunnerIntegrationTest, CastChannelComponentControllerDropped) {
+  const char kCastChannelAppId[] = "00000001";
+  const char kCastChannelAppPath[] = "/cast_channel.html";
+  app_config_manager_.AddAppMapping(kCastChannelAppId,
+                                    test_server_.GetURL(kCastChannelAppPath));
+
+  // Launch the test-app component.
+  fuchsia::sys::ComponentControllerPtr component_controller_ptr;
+  base::fuchsia::ComponentContext component_services(StartCastComponent(
+      base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
+      component_controller_ptr.NewRequest(), &cast_channel_binding_));
+
+  // Expect that disconnecting the ComponentController will kill the Cast
+  // Component, which we verify indirectly by listening for the disconnection
+  // of one of its CastChannel FIDL client.
+  component_controller_ptr.Unbind();
+  base::RunLoop run_loop;
+  cast_channel_binding_.set_error_handler(
+      [&run_loop](zx_status_t) { run_loop.Quit(); });
+  run_loop.Run();
+}
+
+}  // namespace castrunner
diff --git a/fuchsia/runners/cast/cast_runner_unittest.cc b/fuchsia/runners/cast/cast_runner_unittest.cc
deleted file mode 100644
index 0c6b4fa..0000000
--- a/fuchsia/runners/cast/cast_runner_unittest.cc
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright 2018 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "fuchsia/runners/cast/cast_runner.h"
-
-#include <lib/fidl/cpp/binding.h>
-#include <lib/zx/channel.h>
-#include <zircon/status.h>
-
-#include "base/fuchsia/component_context.h"
-#include "base/fuchsia/fuchsia_logging.h"
-#include "base/fuchsia/service_directory.h"
-#include "base/message_loop/message_loop.h"
-#include "base/strings/stringprintf.h"
-#include "fuchsia/runners/cast/fake_application_config_manager.h"
-#include "fuchsia/runners/cast/test_common.h"
-#include "fuchsia/test/fake_context.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class CastRunnerUnitTest : public testing::Test {
- public:
-  CastRunnerUnitTest()
-      : fake_context_binding_(&fake_context_, fake_context_ptr_.NewRequest()) {
-    // Create a new ServiceDirectory, and a scoped default ComponentContext
-    // connected to it, for the test to use to drive the CastRunner.
-    zx::channel service_directory_request, service_directory_client;
-    zx_status_t status = zx::channel::create(0, &service_directory_client,
-                                             &service_directory_request);
-    ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
-
-    service_directory_ = std::make_unique<base::fuchsia::ServiceDirectory>(
-        std::move(service_directory_request));
-    scoped_default_component_context_ =
-        std::make_unique<base::fuchsia::ScopedDefaultComponentContext>(
-            std::move(service_directory_client));
-
-    // Create the AppConfigManager.
-    app_config_manager_ =
-        std::make_unique<FakeApplicationConfigManager>(&test_server_);
-    app_config_binding_ = std::make_unique<
-        fidl::Binding<chromium::cast::ApplicationConfigManager>>(
-        app_config_manager_.get());
-    chromium::cast::ApplicationConfigManagerPtr app_config_manager_interface;
-    app_config_binding_->Bind(app_config_manager_interface.NewRequest());
-
-    // Create the CastRunner, published into |service_directory_|.
-    cast_runner_ = std::make_unique<CastRunner>(
-        service_directory_.get(), std::move(fake_context_ptr_),
-        std::move(app_config_manager_interface),
-        until_runner_idle_loop_.QuitClosure());
-
-    // Connect to the CastRunner's fuchsia.sys.Runner interface.
-    cast_runner_ptr_ = base::fuchsia::ComponentContext::GetDefault()
-                           ->ConnectToService<fuchsia::sys::Runner>();
-    cast_runner_ptr_.set_error_handler([this](zx_status_t status) {
-      ADD_FAILURE() << "CastRunner closed channel.";
-      until_runner_idle_loop_.Quit();
-    });
-  }
-
-  void SetUp() override { ASSERT_TRUE(test_server_.Start()); }
-
-  void RunUntilCastRunnerIsIdle() { until_runner_idle_loop_.Run(); }
-
- protected:
-  base::MessageLoopForIO message_loop_;
-  base::RunLoop until_runner_idle_loop_;
-
-  // Test server.
-  net::EmbeddedTestServer test_server_;
-
-  // Test AppConfigManager and its binding.
-  std::unique_ptr<FakeApplicationConfigManager> app_config_manager_;
-  std::unique_ptr<fidl::Binding<chromium::cast::ApplicationConfigManager>>
-      app_config_binding_;
-
-  // Temporarily holds the InterfacePtr to the FakeContext, until it is passed
-  // to the CastRunner.
-  chromium::web::ContextPtr fake_context_ptr_;
-
-  // Fake web::Context, and binding to the client CastRunner.
-  webrunner::FakeContext fake_context_;
-  fidl::Binding<chromium::web::Context> fake_context_binding_;
-
-  // ServiceDirectory into which the CastRunner will publish itself.
-  std::unique_ptr<base::fuchsia::ServiceDirectory> service_directory_;
-  std::unique_ptr<base::fuchsia::ScopedDefaultComponentContext>
-      scoped_default_component_context_;
-
-  std::unique_ptr<CastRunner> cast_runner_;
-  fuchsia::sys::RunnerPtr cast_runner_ptr_;
-
-  DISALLOW_COPY_AND_ASSIGN(CastRunnerUnitTest);
-};
-
-TEST_F(CastRunnerUnitTest, TeardownOnClientUnbind) {
-  // Disconnect from the CastRunner and wait for it to terminate.
-  cast_runner_ptr_.Unbind();
-  RunUntilCastRunnerIsIdle();
-}
-
-TEST_F(CastRunnerUnitTest, TeardownOnComponentControllerUnbind) {
-  // Create a ComponentController pointer, to manage the component lifetime.
-  fuchsia::sys::ComponentControllerPtr component_controller_ptr;
-
-  // Launch the test-app component, passing a ComponentController request.
-  base::fuchsia::ComponentContext component_services(StartCastComponent(
-      base::StringPrintf("cast:%s",
-                         FakeApplicationConfigManager::kTestCastAppId),
-      &cast_runner_ptr_, component_controller_ptr.NewRequest()));
-
-  // Pump the message-loop to process StartComponent(). If the call is rejected
-  // then the ComponentControllerPtr's error-handler will be invoked at this
-  // point.
-  component_controller_ptr.set_error_handler([](zx_status_t status) {
-    ZX_LOG(ERROR, status) << "Component launch failed";
-    ADD_FAILURE();
-  });
-  base::RunLoop().RunUntilIdle();
-
-  // Disconnect ComponentController and expect the CastRunner will terminate.
-  component_controller_ptr.Unbind();
-  RunUntilCastRunnerIsIdle();
-}
diff --git a/fuchsia/runners/cast/fake_application_config_manager.cc b/fuchsia/runners/cast/fake_application_config_manager.cc
index 185b8ee..8172e78 100644
--- a/fuchsia/runners/cast/fake_application_config_manager.cc
+++ b/fuchsia/runners/cast/fake_application_config_manager.cc
@@ -4,19 +4,19 @@
 
 #include "fuchsia/runners/cast/fake_application_config_manager.h"
 
+#include <string>
+#include <utility>
+
 #include "base/logging.h"
 
-const char FakeApplicationConfigManager::kTestCastAppId[] = "00000000";
+FakeApplicationConfigManager::FakeApplicationConfigManager() = default;
 
-FakeApplicationConfigManager::FakeApplicationConfigManager(
-    net::EmbeddedTestServer* embedded_test_server)
-    : embedded_test_server_(embedded_test_server) {}
 FakeApplicationConfigManager::~FakeApplicationConfigManager() = default;
 
 void FakeApplicationConfigManager::GetConfig(std::string id,
                                              GetConfigCallback callback) {
-  if (id != kTestCastAppId) {
-    LOG(ERROR) << "Unknown Cast app Id: " << id;
+  if (id_to_url_.find(id) == id_to_url_.end()) {
+    LOG(ERROR) << "Unknown Cast App ID: " << id;
     callback(chromium::cast::ApplicationConfigPtr());
     return;
   }
@@ -25,6 +25,12 @@
       chromium::cast::ApplicationConfig::New();
   app_config->id = id;
   app_config->display_name = "Dummy test app";
-  app_config->web_url = embedded_test_server_->base_url().spec();
+  app_config->web_url = id_to_url_[id].spec();
+
   callback(std::move(app_config));
 }
+
+void FakeApplicationConfigManager::AddAppMapping(const std::string& id,
+                                                 const GURL& url) {
+  id_to_url_[id] = url;
+}
diff --git a/fuchsia/runners/cast/fake_application_config_manager.h b/fuchsia/runners/cast/fake_application_config_manager.h
index eee8008..7c6a5e1 100644
--- a/fuchsia/runners/cast/fake_application_config_manager.h
+++ b/fuchsia/runners/cast/fake_application_config_manager.h
@@ -5,26 +5,30 @@
 #ifndef FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
 #define FUCHSIA_RUNNERS_CAST_FAKE_APPLICATION_CONFIG_MANAGER_H_
 
+#include <map>
+#include <string>
+
 #include "base/macros.h"
 #include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
-#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
 
 // Test cast.ApplicationConfigManager implementation which maps a test Cast
-// AppId to an embedded test server address.
+// AppId to a URL.
 class FakeApplicationConfigManager
     : public chromium::cast::ApplicationConfigManager {
  public:
-  static const char kTestCastAppId[];
-
-  explicit FakeApplicationConfigManager(
-      net::EmbeddedTestServer* embedded_test_server);
+  FakeApplicationConfigManager();
   ~FakeApplicationConfigManager() override;
 
+  // Associates a Cast application |id| with a url, to be served from the
+  // EmbeddedTestServer.
+  void AddAppMapping(const std::string& id, const GURL& url);
+
   // chromium::cast::ApplicationConfigManager interface.
   void GetConfig(std::string id, GetConfigCallback config_callback) override;
 
  private:
-  net::EmbeddedTestServer* embedded_test_server_;
+  std::map<std::string, GURL> id_to_url_;
 
   DISALLOW_COPY_AND_ASSIGN(FakeApplicationConfigManager);
 };
diff --git a/fuchsia/runners/cast/test_common.cc b/fuchsia/runners/cast/test_common.cc
index 2f56a7e..3b8b0c0454 100644
--- a/fuchsia/runners/cast/test_common.cc
+++ b/fuchsia/runners/cast/test_common.cc
@@ -4,21 +4,54 @@
 
 #include "fuchsia/runners/cast/test_common.h"
 
+#include <lib/fidl/cpp/binding.h>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
 #include "base/fuchsia/fuchsia_logging.h"
+#include "base/fuchsia/service_directory.h"
+#include "base/run_loop.h"
 
 zx::channel StartCastComponent(
     const base::StringPiece& cast_url,
     fuchsia::sys::RunnerPtr* sys_runner,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-        component_controller_request) {
+        component_controller_request,
+    fidl::Binding<chromium::cast::CastChannel>* cast_channel_binding) {
+  // Construct, bind, and populate a ServiceDirectory for publishing
+  // the CastChannel service to the CastComponent.
+  auto service_list = std::make_unique<fuchsia::sys::ServiceList>();
+  zx::channel cast_channel_dir_request, cast_channel_dir_client;
+  zx_status_t status = zx::channel::create(0, &cast_channel_dir_request,
+                                           &cast_channel_dir_client);
+  ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
+  base::fuchsia::ServiceDirectory cast_channel_directory(
+      std::move(cast_channel_dir_request));
+  base::RunLoop service_connect_runloop;
+  cast_channel_directory.AddService(
+      chromium::cast::CastChannel::Name_,
+      base::BindRepeating(
+          [](base::RepeatingClosure on_connect_cb,
+             fidl::Binding<chromium::cast::CastChannel>* cast_channel_binding_,
+             zx::channel channel) {
+            cast_channel_binding_->Bind(std::move(channel));
+            on_connect_cb.Run();
+          },
+          base::Passed(service_connect_runloop.QuitClosure()),
+          base::Unretained(cast_channel_binding)));
+  service_list->names.push_back(chromium::cast::CastChannel::Name_);
+  service_list->host_directory = std::move(cast_channel_dir_client);
+
   fuchsia::sys::LaunchInfo launch_info;
   launch_info.url = cast_url.as_string();
+  launch_info.additional_services = std::move(service_list);
 
   // Create a channel to pass to the Runner, through which to expose the new
   // component's ServiceDirectory.
   zx::channel service_directory_client;
-  zx_status_t status = zx::channel::create(0, &service_directory_client,
-                                           &launch_info.directory_request);
+  status = zx::channel::create(0, &service_directory_client,
+                               &launch_info.directory_request);
   ZX_CHECK(status == ZX_OK, status) << "zx_channel_create";
 
   fuchsia::sys::StartupInfo startup_info;
@@ -33,5 +66,12 @@
 
   sys_runner->get()->StartComponent(std::move(package), std::move(startup_info),
                                     std::move(component_controller_request));
+
+  // Process the runloop until the CastChannel FIDL service is connected.
+  service_connect_runloop.Run();
+
+  // Prepare the service directory for clean teardown.
+  cast_channel_directory.RemoveAllServices();
+
   return service_directory_client;
 }
diff --git a/fuchsia/runners/cast/test_common.h b/fuchsia/runners/cast/test_common.h
index a811210..98dbac32 100644
--- a/fuchsia/runners/cast/test_common.h
+++ b/fuchsia/runners/cast/test_common.h
@@ -6,15 +6,21 @@
 #define FUCHSIA_RUNNERS_CAST_TEST_COMMON_H_
 
 #include <fuchsia/sys/cpp/fidl.h>
+#include <lib/fidl/cpp/binding.h>
 
 #include "base/strings/string_piece.h"
+#include "fuchsia/fidl/chromium/cast/cpp/fidl.h"
 
-// Starts a cast component from the runner |sys_runner| with the URL |cast_url|
-// and returns the service directory client channel.
+// Starts a Cast component from the runner |sys_runner| with the URL |cast_url|
+// and returns the outgoing service directory client channel.
+// The Cast component will connect to the CastChannel FIDL service bound at
+// |cast_channel_binding|.
+// Blocks until |cast_channel_binding| is bound.
 zx::channel StartCastComponent(
     const base::StringPiece& cast_url,
     fuchsia::sys::RunnerPtr* sys_runner,
     fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-        component_controller_request);
+        component_controller_request,
+    fidl::Binding<chromium::cast::CastChannel>* cast_channel_binding);
 
-#endif  // FUCHSIA_RUNNERS_CAST_TEST_COMMON_H_
\ No newline at end of file
+#endif  // FUCHSIA_RUNNERS_CAST_TEST_COMMON_H_
diff --git a/fuchsia/runners/common/web_component.cc b/fuchsia/runners/common/web_component.cc
index e410f79..50864fa 100644
--- a/fuchsia/runners/common/web_component.cc
+++ b/fuchsia/runners/common/web_component.cc
@@ -20,19 +20,10 @@
                                             termination_reason_);
 }
 
-// static
-std::unique_ptr<WebComponent> WebComponent::ForUrlRequest(
-    WebContentRunner* runner,
-    const GURL& url,
-    fuchsia::sys::StartupInfo startup_info,
-    fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-        controller_request) {
+void WebComponent::LoadUrl(const GURL& url) {
   DCHECK(url.is_valid());
-  std::unique_ptr<WebComponent> component(new WebComponent(
-      runner, std::move(startup_info), std::move(controller_request)));
   chromium::web::NavigationControllerPtr navigation_controller;
-  component->frame()->GetNavigationController(
-      navigation_controller.NewRequest());
+  frame()->GetNavigationController(navigation_controller.NewRequest());
 
   // Set the page activation flag on the initial load, so that features like
   // autoplay work as expected when a WebComponent first loads the specified
@@ -41,8 +32,6 @@
   params->user_activated = true;
 
   navigation_controller->LoadUrl(url.spec(), std::move(params));
-
-  return component;
 }
 
 WebComponent::WebComponent(
@@ -53,6 +42,16 @@
     : runner_(runner), controller_binding_(this) {
   DCHECK(runner);
 
+  // Handle the incoming services directory for this component.
+  if (startup_info.launch_info.additional_services &&
+      startup_info.launch_info.additional_services->host_directory) {
+    additional_services_ =
+        std::make_unique<base::fuchsia::ComponentContext>(std::move(
+            startup_info.launch_info.additional_services->host_directory));
+    additional_service_names_ =
+        std::move(startup_info.launch_info.additional_services->names);
+  }
+
   // If the ComponentController request is valid then bind it, and configure it
   // to destroy this component on error.
   if (controller_request.is_valid()) {
diff --git a/fuchsia/runners/common/web_component.h b/fuchsia/runners/common/web_component.h
index 3bd4334..6d493835 100644
--- a/fuchsia/runners/common/web_component.h
+++ b/fuchsia/runners/common/web_component.h
@@ -11,9 +11,11 @@
 #include <lib/fidl/cpp/binding.h>
 #include <lib/fidl/cpp/binding_set.h>
 #include <memory>
+#include <string>
 #include <utility>
 #include <vector>
 
+#include "base/fuchsia/component_context.h"
 #include "base/fuchsia/scoped_service_binding.h"
 #include "base/fuchsia/service_directory.h"
 #include "base/logging.h"
@@ -32,19 +34,6 @@
                      public fuchsia::ui::app::ViewProvider,
                      public fuchsia::ui::viewsv1::ViewProvider {
  public:
-  ~WebComponent() override;
-
-  // Creates a WebComponent and navigates its Frame to |url|.
-  static std::unique_ptr<WebComponent> ForUrlRequest(
-      WebContentRunner* runner,
-      const GURL& url,
-      fuchsia::sys::StartupInfo startup_info,
-      fidl::InterfaceRequest<fuchsia::sys::ComponentController>
-          controller_request);
-
-  chromium::web::Frame* frame() { return frame_.get(); }
-
- protected:
   // Creates a WebComponent encapsulating a web.Frame. A ViewProvider service
   // will be published to the service-directory specified in |startup_info|, and
   // if |controller_request| is valid then it will be bound to this component,
@@ -55,6 +44,14 @@
                fidl::InterfaceRequest<fuchsia::sys::ComponentController>
                    controller_request);
 
+  ~WebComponent() override;
+
+  // Navigates this component's Frame to |url|.
+  void LoadUrl(const GURL& url);
+
+  chromium::web::Frame* frame() const { return frame_.get(); }
+
+ protected:
   // fuchsia::sys::ComponentController implementation.
   void Kill() override;
   void Detach() override;
@@ -73,20 +70,37 @@
 
   // Reports the supplied exit-code and reason to the |controller_binding_| and
   // requests that the |runner_| delete this component.
-  void DestroyComponent(int termination_exit_code,
-                        fuchsia::sys::TerminationReason reason);
+  virtual void DestroyComponent(int termination_exit_code,
+                                fuchsia::sys::TerminationReason reason);
 
-  base::fuchsia::ServiceDirectory* service_directory() {
+  // Gets a directory of incoming services provided to the component, or returns
+  // nullptr if none was provided.
+  base::fuchsia::ComponentContext* additional_services() const {
+    return additional_services_.get();
+  }
+
+  // Gets the names of services available in additional_services().
+  const std::vector<std::string> additional_service_names() {
+    return additional_service_names_;
+  }
+
+  base::fuchsia::ServiceDirectory* service_directory() const {
     return service_directory_.get();
   }
 
  private:
-  WebContentRunner* runner_ = nullptr;
+  WebContentRunner* const runner_ = nullptr;
 
   chromium::web::FramePtr frame_;
 
   fidl::Binding<fuchsia::sys::ComponentController> controller_binding_;
 
+  // Incoming services provided at component creation.
+  std::unique_ptr<base::fuchsia::ComponentContext> additional_services_;
+
+  // The names of services provided at component creation.
+  std::vector<std::string> additional_service_names_;
+
   // Objects used for binding and exporting the ViewProvider service.
   std::unique_ptr<base::fuchsia::ServiceDirectory> service_directory_;
   std::unique_ptr<
diff --git a/fuchsia/runners/common/web_content_runner.cc b/fuchsia/runners/common/web_content_runner.cc
index 510de976..3eb9995 100644
--- a/fuchsia/runners/common/web_content_runner.cc
+++ b/fuchsia/runners/common/web_content_runner.cc
@@ -73,9 +73,10 @@
     return;
   }
 
-  RegisterComponent(WebComponent::ForUrlRequest(this, std::move(url),
-                                                std::move(startup_info),
-                                                std::move(controller_request)));
+  std::unique_ptr<WebComponent> component = std::make_unique<WebComponent>(
+      this, std::move(startup_info), std::move(controller_request));
+  component->LoadUrl(url);
+  RegisterComponent(std::move(component));
 }
 
 void WebContentRunner::GetWebComponentForTest(
@@ -88,6 +89,7 @@
 }
 
 void WebContentRunner::DestroyComponent(WebComponent* component) {
+  LOG(ERROR) << "DestroyComponent " << components_.size();
   components_.erase(components_.find(component));
 
   if (components_.empty())
@@ -99,9 +101,7 @@
   if (web_component_test_callback_) {
     std::move(web_component_test_callback_).Run(component.get());
   }
-  if (component) {
-    components_.insert(std::move(component));
-  }
+  components_.insert(std::move(component));
 }
 
 void WebContentRunner::RunOnIdleClosureIfValid() {
diff --git a/gpu/command_buffer/service/gl_utils.cc b/gpu/command_buffer/service/gl_utils.cc
index 39428c16..25def54d 100644
--- a/gpu/command_buffer/service/gl_utils.cc
+++ b/gpu/command_buffer/service/gl_utils.cc
@@ -903,6 +903,19 @@
       break;
   }
 
+  // CopyTex{Sub}Image2D() from GL_RGB10_A2 has issues on some Android chipsets.
+  if (source_internal_format == GL_RGB10_A2) {
+    if (feature_info->workarounds().disable_copy_tex_image_2d_rgb10_a2_tegra) {
+      if (dest_internal_format == GL_RGBA4)
+        return CopyTextureMethod::DIRECT_DRAW;
+      return CopyTextureMethod::DRAW_AND_COPY;
+    }
+    if (feature_info->workarounds().disable_copy_tex_image_2d_rgb10_a2_adreno &&
+        dest_internal_format != GL_RGB10_A2) {
+      return CopyTextureMethod::DRAW_AND_COPY;
+    }
+  }
+
   // CopyTexImage* should not allow internalformat of GL_BGRA_EXT and
   // GL_BGRA8_EXT. https://crbug.com/663086.
   bool copy_tex_image_format_valid =
@@ -1018,7 +1031,8 @@
       source_internal_format == GL_BGRA8_EXT ||
       source_internal_format == GL_RGB_YCBCR_420V_CHROMIUM ||
       source_internal_format == GL_RGB_YCBCR_422_CHROMIUM ||
-      source_internal_format == GL_R16_EXT;
+      source_internal_format == GL_R16_EXT ||
+      source_internal_format == GL_RGB10_A2;
   if (!valid_source_format) {
     *output_error_msg = "invalid source internal format " +
                         GLES2Util::GetStringEnum(source_internal_format);
diff --git a/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc b/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
index 939bf436..a58de95 100644
--- a/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
+++ b/gpu/command_buffer/service/gles2_cmd_copy_texture_chromium.cc
@@ -43,6 +43,7 @@
   S_FORMAT_RGB_YCBCR_420V_CHROMIUM,
   S_FORMAT_RGB_YCBCR_422_CHROMIUM,
   S_FORMAT_COMPRESSED,
+  S_FORMAT_RGB10_A2,
   NUM_S_FORMAT
 };
 
@@ -185,8 +186,12 @@
     case GL_ETC1_RGB8_OES:
       sourceFormatIndex = S_FORMAT_COMPRESSED;
       break;
+    case GL_RGB10_A2:
+      sourceFormatIndex = S_FORMAT_RGB10_A2;
+      break;
     default:
-      NOTREACHED();
+      NOTREACHED() << "Invalid source format "
+                   << gl::GLEnums::GetStringEnum(source_format);
       break;
   }
 
diff --git a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
index 78255a9..92a1da40 100644
--- a/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
+++ b/gpu/command_buffer/tests/gl_copy_texture_CHROMIUM_unittest.cc
@@ -14,10 +14,12 @@
 #include <stdint.h>
 
 #include "base/stl_util.h"
+#include "build/build_config.h"
 #include "gpu/command_buffer/tests/gl_manager.h"
 #include "gpu/command_buffer/tests/gl_test_utils.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "ui/gl/gl_enums.h"
 #include "ui/gl/gl_version_info.h"
 
 namespace gpu {
@@ -153,11 +155,11 @@
   color[3] = a;
 }
 
-void getExpectedColor(GLenum src_internal_format,
-                      GLenum dest_internal_format,
-                      uint8_t* color,
-                      uint8_t* expected_color,
-                      uint8_t* mask) {
+void getExpectedColorAndMask(GLenum src_internal_format,
+                             GLenum dest_internal_format,
+                             const uint8_t* color,
+                             uint8_t* expected_color,
+                             uint8_t* expected_mask) {
   uint8_t adjusted_color[4];
   switch (src_internal_format) {
     case GL_ALPHA:
@@ -187,37 +189,34 @@
     case GL_BGRA8_EXT:
       setColor(color[2], color[1], color[0], color[3], adjusted_color);
       break;
+    case GL_RGB10_A2: {
+      // Map the source 2-bit Alpha to 8-bits.
+      const uint8_t alpha_value = (color[3] & 0x3) * 255 / 3;
+      setColor(color[0] >> 2, color[1] >> 2, color[2] >> 2, alpha_value,
+               adjusted_color);
+      break;
+    }
     default:
-      NOTREACHED();
+      NOTREACHED() << gl::GLEnums::GetStringEnum(src_internal_format);
       break;
   }
 
   switch (dest_internal_format) {
-    case GL_ALPHA:
-      setColor(0, 0, 0, adjusted_color[3], expected_color);
-      setColor(0, 0, 0, 1, mask);
-      break;
+    // TODO(crbug.com/577144): Enable GL_ALPHA, GL_LUMINANCE and
+    // GL_LUMINANCE_ALPHA.
     case GL_R8:
     case GL_R16F:
     case GL_R32F:
     case GL_R8UI:
       setColor(adjusted_color[0], 0, 0, 0, expected_color);
-      setColor(1, 0, 0, 0, mask);
-      break;
-    case GL_LUMINANCE:
-      setColor(adjusted_color[0], 0, 0, 0, expected_color);
-      setColor(1, 0, 0, 0, mask);
-      break;
-    case GL_LUMINANCE_ALPHA:
-      setColor(adjusted_color[0], 0, 0, adjusted_color[3], expected_color);
-      setColor(1, 0, 0, 1, mask);
+      setColor(1, 0, 0, 0, expected_mask);
       break;
     case GL_RG8:
     case GL_RG16F:
     case GL_RG32F:
     case GL_RG8UI:
       setColor(adjusted_color[0], adjusted_color[1], 0, 0, expected_color);
-      setColor(1, 1, 0, 0, mask);
+      setColor(1, 1, 0, 0, expected_mask);
       break;
     case GL_RGB:
     case GL_RGB8:
@@ -231,7 +230,7 @@
     case GL_RGB8UI:
       setColor(adjusted_color[0], adjusted_color[1], adjusted_color[2], 0,
                expected_color);
-      setColor(1, 1, 1, 0, mask);
+      setColor(1, 1, 1, 0, expected_mask);
       break;
     case GL_RGBA:
     case GL_RGBA8:
@@ -245,8 +244,25 @@
     case GL_RGBA8UI:
       setColor(adjusted_color[0], adjusted_color[1], adjusted_color[2],
                adjusted_color[3], expected_color);
-      setColor(1, 1, 1, 1, mask);
+      setColor(1, 1, 1, 1, expected_mask);
       break;
+    case GL_RGB10_A2: {
+      //  Map the 2-bit Alpha values back to full bytes.
+      constexpr uint8_t step = 0x55;
+      const uint8_t alpha_value = (adjusted_color[3] + step / 2) / step * step;
+
+      setColor(adjusted_color[0], adjusted_color[1], adjusted_color[2],
+               alpha_value, expected_color);
+#if defined(OS_MACOSX) || defined(OS_LINUX)
+      // The alpha channel values for LUMINANCE_ALPHA source don't work OK
+      // on Mac or Linux, so skip comparison of those, see crbug.com/926579
+      setColor(1, 1, 1, src_internal_format != GL_LUMINANCE_ALPHA,
+               expected_mask);
+#else
+      setColor(1, 1, 1, 1, expected_mask);
+#endif
+      break;
+    }
     case GL_RGB5_A1:
       setColor(adjusted_color[0], adjusted_color[1], adjusted_color[2],
                (adjusted_color[3] >> 7) ? 0xFF : 0x0, expected_color);
@@ -254,10 +270,10 @@
       // channel of expected color is the source alpha value other than 255.
       // This should be wrong. Skip the alpha channel check and revisit this in
       // future.
-      setColor(1, 1, 1, 0, mask);
+      setColor(1, 1, 1, 0, expected_mask);
       break;
     default:
-      NOTREACHED();
+      NOTREACHED() << gl::GLEnums::GetStringEnum(dest_internal_format);
       break;
   }
 }
@@ -271,38 +287,51 @@
     uint8_t* expected_mask) {
   const uint32_t src_channel_count = gles2::GLES2Util::ElementsPerGroup(
       src_format_type.format, src_format_type.type);
-  uint8_t color[4] = {1u, 63u, 127u, 255u};
-  getExpectedColor(src_format_type.internal_format,
-                   dest_format_type.internal_format, color, expected_color,
-                   expected_mask);
+  constexpr uint8_t color[4] = {1u, 63u, 127u, 255u};
+  getExpectedColorAndMask(src_format_type.internal_format,
+                          dest_format_type.internal_format, color,
+                          expected_color, expected_mask);
+  const size_t num_pixels = width * height;
+  // TODO(mcasas): use std::make_unique<uint8_t[]> in this function.
+
   if (src_format_type.type == GL_UNSIGNED_BYTE) {
     std::unique_ptr<uint8_t[]> pixels(
-        new uint8_t[width * height * src_channel_count]);
-    for (uint32_t i = 0; i < width * height * src_channel_count;
+        new uint8_t[num_pixels * src_channel_count]);
+    for (uint32_t i = 0; i < num_pixels * src_channel_count;
          i += src_channel_count) {
       for (uint32_t j = 0; j < src_channel_count; ++j)
         pixels[i + j] = color[j];
     }
     return pixels;
   } else if (src_format_type.type == GL_UNSIGNED_SHORT) {
-    uint16_t color_16bit[4] = {1u << 8, 63u << 8, 127u << 8, 255u << 8};
+    constexpr uint16_t color_16bit[4] = {color[0] << 8, color[1] << 8,
+                                         color[2] << 8, color[3] << 8};
     std::unique_ptr<uint8_t[]> data(
-        new uint8_t[width * height * src_channel_count * sizeof(uint16_t)]);
+        new uint8_t[num_pixels * src_channel_count * sizeof(uint16_t)]);
     uint16_t* pixels = reinterpret_cast<uint16_t*>(data.get());
     int16_t flip_sign = -1;
-    for (uint32_t i = 0; i < width * height * src_channel_count;
+    for (uint32_t i = 0; i < num_pixels * src_channel_count;
          i += src_channel_count) {
       for (uint32_t j = 0; j < src_channel_count; ++j) {
         // Introduce an offset to the value to check. Expected value should be
         // the same as without the offset.
         flip_sign *= -1;
         pixels[i + j] =
-            color_16bit[j] + flip_sign * (0x7F * (i + j)) / (width * height);
+            color_16bit[j] + flip_sign * (0x7F * (i + j)) / num_pixels;
       }
     }
     return data;
+  } else if (src_format_type.type == GL_UNSIGNED_INT_2_10_10_10_REV) {
+    DCHECK_EQ(src_channel_count, 1u);
+    constexpr uint32_t color_rgb10_a2 = ((color[3] & 0x3) << 30) +
+                                        (color[2] << 20) + (color[1] << 10) +
+                                        color[0];
+    std::unique_ptr<uint8_t[]> data(new uint8_t[num_pixels * sizeof(uint32_t)]);
+    uint32_t* pixels = reinterpret_cast<uint32_t*>(data.get());
+    std::fill(pixels, pixels + num_pixels, color_rgb10_a2);
+    return data;
   }
-  NOTREACHED();
+  NOTREACHED() << gl::GLEnums::GetStringEnum(src_format_type.type);
   return nullptr;
 }
 
@@ -477,7 +506,9 @@
                                textures_[1], dest_level, 0, 0, 0, 0, width_,
                                height_, false, false, false);
     }
-    EXPECT_TRUE(glGetError() == GL_NO_ERROR);
+    const GLenum last_error = glGetError();
+    EXPECT_TRUE(last_error == GL_NO_ERROR)
+        << gl::GLEnums::GetStringError(last_error);
 
     // Draw destination texture to a fbo with a TEXTURE_2D texture attachment
     // in RGBA format.
@@ -571,6 +602,20 @@
 #endif
     return !gl_.decoder()->GetFeatureInfo()->feature_flags().ext_texture_norm16;
   }
+
+  bool ShouldSkipRGB10A2() const {
+    DCHECK(!ShouldSkipTest());
+    const gl::GLVersionInfo& gl_version_info =
+        gl_.decoder()->GetFeatureInfo()->gl_version_info();
+    // XB30 support was introduced in GLES 3.0/ OpenGL 3.3, before that it was
+    // signalled via a specific extension.
+    const bool supports_rgb10_a2 =
+        gl_version_info.IsAtLeastGL(3, 3) ||
+        gl_version_info.IsAtLeastGLES(3, 0) ||
+        GLTestHelper::HasExtension("GL_EXT_texture_type_2_10_10_10_REV");
+    EXPECT_TRUE(supports_rgb10_a2);
+    return !supports_rgb10_a2;
+  }
 };
 
 INSTANTIATE_TEST_CASE_P(CopyType,
@@ -627,7 +672,7 @@
         << "Passthrough command decoder expected failure. Skipping test...";
     return;
   }
-  CopyType copy_type = GetParam();
+  const CopyType copy_type = GetParam();
 
   FormatType src_format_types[] = {
       {GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE},
@@ -639,6 +684,7 @@
       {GL_BGRA_EXT, GL_BGRA_EXT, GL_UNSIGNED_BYTE},
       {GL_BGRA8_EXT, GL_BGRA_EXT, GL_UNSIGNED_BYTE},
       {GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT},
+      {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV},
   };
 
   FormatType dest_format_types[] = {
@@ -683,6 +729,7 @@
       {GL_RGBA16F, GL_RGBA, GL_FLOAT},
       {GL_RGBA32F, GL_RGBA, GL_FLOAT},
       {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE},
+      {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV},
   };
 
   for (auto src_format_type : src_format_types) {
@@ -695,14 +742,18 @@
         continue;
       }
       if (gles2::GLES2Util::IsFloatFormat(dest_format_type.internal_format) &&
-          ShouldSkipFloatFormat())
+          ShouldSkipFloatFormat()) {
         continue;
+      }
       if ((dest_format_type.internal_format == GL_SRGB_EXT ||
            dest_format_type.internal_format == GL_SRGB_ALPHA_EXT) &&
-          ShouldSkipSRGBEXT())
+          ShouldSkipSRGBEXT()) {
         continue;
+      }
       if (src_format_type.internal_format == GL_R16_EXT && ShouldSkipNorm16())
         continue;
+      if (src_format_type.internal_format == GL_RGB10_A2 && ShouldSkipRGB10A2())
+        continue;
 
       RunCopyTexture(GL_TEXTURE_2D, copy_type, src_format_type, 0,
                      dest_format_type, 0, true);
diff --git a/gpu/config/gpu_driver_bug_list.json b/gpu/config/gpu_driver_bug_list.json
index 2ac3cf7..9544558e 100644
--- a/gpu/config/gpu_driver_bug_list.json
+++ b/gpu/config/gpu_driver_bug_list.json
@@ -3067,6 +3067,41 @@
       "features": [
         "disable_direct_composition"
       ]
+    },
+    {
+      "id": 287,
+      "description": "glCopyTexImage2D on Adreno fails if source is GL_RGB10_A2 and destination is not.",
+      "cr_bugs": [925986],
+      "os": {
+        "type": "android",
+        "version": {
+          "op": ">=",
+          "value": "5.0.0"
+        }
+      },
+      "gl_vendor": "Qualcomm.*",
+      "gl_renderer": ".*4\\d\\d",
+      "gl_renderer": "Adreno \\(TM\\) [345].*",
+      "features": [
+        "disable_copy_tex_image_2d_rgb10_a2_adreno"
+      ]
+    },
+    {
+      "id": 288,
+      "description": "glCopyTexImage2D on NVIDIA Tegra fails in certain cases if source is GL_RGB10_A2.",
+      "cr_bugs": [925986],
+      "os": {
+        "type": "android"
+      },
+      "gl_vendor": "NVIDIA.*",
+      "gl_type": "gles",
+      "gl_version": {
+        "op": ">=",
+        "value": "3.0"
+      },
+      "features": [
+        "disable_copy_tex_image_2d_rgb10_a2_tegra"
+      ]
     }
   ]
 }
diff --git a/gpu/config/gpu_workaround_list.txt b/gpu/config/gpu_workaround_list.txt
index 826c74a..15db5f45 100644
--- a/gpu/config/gpu_workaround_list.txt
+++ b/gpu/config/gpu_workaround_list.txt
@@ -106,3 +106,5 @@
 validate_multisample_buffer_allocation
 wake_up_gpu_before_drawing
 use_copyteximage2d_instead_of_readpixels_on_multisampled_textures
+disable_copy_tex_image_2d_rgb10_a2_adreno
+disable_copy_tex_image_2d_rgb10_a2_tegra
diff --git a/ios/chrome/browser/snapshots/BUILD.gn b/ios/chrome/browser/snapshots/BUILD.gn
index f6f575e1..a35f0093 100644
--- a/ios/chrome/browser/snapshots/BUILD.gn
+++ b/ios/chrome/browser/snapshots/BUILD.gn
@@ -12,7 +12,6 @@
     "snapshot_cache_tab_model_list_observer.h",
     "snapshot_cache_web_state_list_observer.h",
     "snapshot_generator_delegate.h",
-    "snapshot_overlay.h",
     "snapshot_tab_helper.h",
     "snapshots_util.h",
   ]
@@ -24,7 +23,6 @@
     "snapshot_cache_web_state_list_observer.mm",
     "snapshot_generator.h",
     "snapshot_generator.mm",
-    "snapshot_overlay.mm",
     "snapshot_tab_helper.mm",
     "snapshots_util.mm",
   ]
diff --git a/ios/chrome/browser/snapshots/fake_snapshot_generator_delegate.mm b/ios/chrome/browser/snapshots/fake_snapshot_generator_delegate.mm
index 3565213..b81b35f 100644
--- a/ios/chrome/browser/snapshots/fake_snapshot_generator_delegate.mm
+++ b/ios/chrome/browser/snapshots/fake_snapshot_generator_delegate.mm
@@ -22,9 +22,8 @@
   return UIEdgeInsetsZero;
 }
 
-- (NSArray<SnapshotOverlay*>*)snapshotGenerator:
-                                  (SnapshotGenerator*)snapshotGenerator
-                    snapshotOverlaysForWebState:(web::WebState*)webState {
+- (NSArray<UIView*>*)snapshotGenerator:(SnapshotGenerator*)snapshotGenerator
+           snapshotOverlaysForWebState:(web::WebState*)webState {
   return nil;
 }
 
diff --git a/ios/chrome/browser/snapshots/snapshot_generator.mm b/ios/chrome/browser/snapshots/snapshot_generator.mm
index 2a4b6e59..4b6bacb 100644
--- a/ios/chrome/browser/snapshots/snapshot_generator.mm
+++ b/ios/chrome/browser/snapshots/snapshot_generator.mm
@@ -13,7 +13,6 @@
 #import "ios/chrome/browser/snapshots/snapshot_cache.h"
 #import "ios/chrome/browser/snapshots/snapshot_cache_factory.h"
 #import "ios/chrome/browser/snapshots/snapshot_generator_delegate.h"
-#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #import "ios/web/public/web_state/web_state.h"
@@ -27,6 +26,14 @@
 #error "This file requires ARC support."
 #endif
 
+// Contains information needed for snapshotting.
+struct SnapshotInfo {
+  UIView* baseView;
+  CGRect snapshotFrameInBaseView;
+  CGRect snapshotFrameInWindow;
+  NSArray<UIView*>* overlays;
+};
+
 @interface SnapshotGenerator ()<CRWWebStateObserver>
 
 // Property providing access to the snapshot's cache. May be nil.
@@ -115,23 +122,23 @@
     }
     return;
   }
-  UIView* snapshotView = [self.delegate snapshotGenerator:self
-                                      baseViewForWebState:self.webState];
-  CGRect snapshotFrame =
-      [self.webState->GetView() convertRect:[self snapshotFrame]
-                                   fromView:snapshotView];
-  NSArray<SnapshotOverlay*>* overlays =
-      [self.delegate snapshotGenerator:self
-           snapshotOverlaysForWebState:self.webState];
-
+  SnapshotInfo snapshotInfo = [self getSnapshotInfo];
+  CGRect snapshotFrameInWebView =
+      [self.webState->GetView() convertRect:snapshotInfo.snapshotFrameInBaseView
+                                   fromView:snapshotInfo.baseView];
   [self.delegate snapshotGenerator:self
       willUpdateSnapshotForWebState:self.webState];
   __weak SnapshotGenerator* weakSelf = self;
   self.webState->TakeSnapshot(
-      gfx::RectF(snapshotFrame), base::BindOnce(^(const gfx::Image& image) {
-        UIImage* snapshot = [weakSelf snapshotWithOverlays:overlays
-                                                 baseImage:image
-                                                     frame:snapshotFrame];
+      gfx::RectF(snapshotFrameInWebView),
+      base::BindOnce(^(const gfx::Image& image) {
+        UIImage* snapshot = nil;
+        if (!image.IsEmpty()) {
+          snapshot = [weakSelf
+              snapshotWithOverlays:snapshotInfo.overlays
+                         baseImage:image.ToUIImage()
+                     frameInWindow:snapshotInfo.snapshotFrameInWindow];
+        }
         [weakSelf updateSnapshotCacheWithImage:snapshot];
         if (completion)
           completion(snapshot);
@@ -141,17 +148,16 @@
 - (UIImage*)generateSnapshotWithOverlays:(BOOL)shouldAddOverlay {
   if (![self canTakeSnapshot])
     return nil;
-  NSArray<SnapshotOverlay*>* overlays =
-      shouldAddOverlay ? [self.delegate snapshotGenerator:self
-                              snapshotOverlaysForWebState:self.webState]
-                       : nil;
-  UIView* view = [self.delegate snapshotGenerator:self
-                              baseViewForWebState:self.webState];
+  SnapshotInfo snapshotInfo = [self getSnapshotInfo];
   [self.delegate snapshotGenerator:self
       willUpdateSnapshotForWebState:self.webState];
-  return [self snapshotWithOverlays:overlays
-                           baseView:view
-                              frame:[self snapshotFrame]];
+  UIImage* baseImage =
+      [self snapshotNonWebView:snapshotInfo.baseView
+               frameInBaseView:snapshotInfo.snapshotFrameInBaseView];
+  return [self
+      snapshotWithOverlays:(shouldAddOverlay ? snapshotInfo.overlays : nil)
+                 baseImage:baseImage
+             frameInWindow:snapshotInfo.snapshotFrameInWindow];
 }
 
 - (void)removeSnapshot {
@@ -175,37 +181,27 @@
                canTakeSnapshotForWebState:self.webState];
 }
 
-// Returns the frame of the snapshot.
-- (CGRect)snapshotFrame {
-  UIView* view = [self.delegate snapshotGenerator:self
-                              baseViewForWebState:self.webState];
-  UIEdgeInsets headerInsets = [self.delegate snapshotGenerator:self
-                                 snapshotEdgeInsetsForWebState:self.webState];
-  CGRect frame = UIEdgeInsetsInsetRect(view.bounds, headerInsets);
-  DCHECK(!CGRectIsEmpty(frame));
-  return frame;
-}
-
-// Returns an image of the |view| overlaid with |overlays| with the given
-// |frame|.
-- (UIImage*)snapshotWithOverlays:(NSArray<SnapshotOverlay*>*)overlays
-                        baseView:(UIView*)view
-                           frame:(CGRect)frame {
-  DCHECK(view);
-  DCHECK(!CGRectIsEmpty(frame));
+// Returns a snapshot of |baseView| with |frameInBaseView|.
+- (UIImage*)snapshotNonWebView:(UIView*)baseView
+               frameInBaseView:(CGRect)frameInBaseView {
+  DCHECK(baseView);
+  DCHECK(!CGRectIsEmpty(frameInBaseView));
   const CGFloat kScale =
       std::max<CGFloat>(1.0, [self.snapshotCache snapshotScaleForDevice]);
-  UIGraphicsBeginImageContextWithOptions(frame.size, YES, kScale);
+  UIGraphicsBeginImageContextWithOptions(frameInBaseView.size, YES, kScale);
   CGContext* context = UIGraphicsGetCurrentContext();
-  CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
+  // This shifts the origin of the context to be the origin of the snapshot
+  // frame.
+  CGContextTranslateCTM(context, -frameInBaseView.origin.x,
+                        -frameInBaseView.origin.y);
   BOOL snapshotSuccess = YES;
   if (base::FeatureList::IsEnabled(kSnapshotDrawView)) {
-    snapshotSuccess =
-        [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
+    // The rect's origin is ignored. Only size is used.
+    snapshotSuccess = [baseView drawViewHierarchyInRect:frameInBaseView
+                                     afterScreenUpdates:NO];
   } else {
-    [[view layer] renderInContext:context];
+    [[baseView layer] renderInContext:context];
   }
-  [self drawOverlays:overlays context:context];
   UIImage* image = nil;
   if (snapshotSuccess)
     image = UIGraphicsGetImageFromCurrentImageContext();
@@ -213,21 +209,30 @@
   return image;
 }
 
-// Returns an image of the |image| overlaid with |overlays| with the given
-// |frame|.
-- (UIImage*)snapshotWithOverlays:(NSArray<SnapshotOverlay*>*)overlays
-                       baseImage:(const gfx::Image&)image
-                           frame:(CGRect)frame {
-  DCHECK(!CGRectIsEmpty(frame));
-  if (image.IsEmpty())
+// Returns an image of the |baseImage| overlaid with |overlays| with the given
+// |frameInWindow|.
+- (UIImage*)snapshotWithOverlays:(NSArray<UIView*>*)overlays
+                       baseImage:(UIImage*)baseImage
+                   frameInWindow:(CGRect)frameInWindow {
+  DCHECK(!CGRectIsEmpty(frameInWindow));
+  if (!baseImage)
     return nil;
+  DCHECK(CGSizeEqualToSize(baseImage.size, frameInWindow.size));
   if (overlays.count == 0)
-    return image.ToUIImage();
+    return baseImage;
   const CGFloat kScale =
       std::max<CGFloat>(1.0, [self.snapshotCache snapshotScaleForDevice]);
-  UIGraphicsBeginImageContextWithOptions(frame.size, YES, kScale);
+  UIGraphicsBeginImageContextWithOptions(frameInWindow.size, YES, kScale);
   CGContext* context = UIGraphicsGetCurrentContext();
-  [image.ToUIImage() drawAtPoint:CGPointZero];
+  // The base image is already a cropped snapshot so it is drawn at the origin
+  // of the new image.
+  [baseImage drawAtPoint:CGPointZero];
+  // This shifts the origin of the context so that future drawings can be in
+  // window coordinates. For example, suppose that the desired snapshot area is
+  // at (0, 99) in the window coordinate space. Drawing at (0, 99) will appear
+  // as (0, 0) in the resulting image.
+  CGContextTranslateCTM(context, -frameInWindow.origin.x,
+                        -frameInWindow.origin.y);
   [self drawOverlays:overlays context:context];
   UIImage* snapshot = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();
@@ -244,30 +249,49 @@
   }
 }
 
-// Draws |overlays| onto |context|.
-- (void)drawOverlays:(NSArray<SnapshotOverlay*>*)overlays
-             context:(CGContext*)context {
-  for (SnapshotOverlay* overlay in overlays) {
-    // Render the overlay view at the desired offset. It is achieved
-    // by shifting origin of context because view frame is ignored when
-    // drawing to context.
+// Draws |overlays| onto |context| at offsets relative to the window.
+- (void)drawOverlays:(NSArray<UIView*>*)overlays context:(CGContext*)context {
+  for (UIView* overlay in overlays) {
     CGContextSaveGState(context);
-    CGContextTranslateCTM(context, 0, overlay.yOffset);
+    CGRect frameInWindow = [overlay.superview convertRect:overlay.frame
+                                                   toView:nil];
+    // This shifts the context so that drawing starts at the overlay's offset.
+    CGContextTranslateCTM(context, frameInWindow.origin.x,
+                          frameInWindow.origin.y);
     // |drawViewHierarchyInRect:| has undefined behavior when the view is not
     // in the visible view hierarchy. In practice, when this method is called
     // on a view that is part of view controller containment, an
     // UIViewControllerHierarchyInconsistency exception will be thrown.
-    if (base::FeatureList::IsEnabled(kSnapshotDrawView) &&
-        overlay.view.window) {
-      [overlay.view drawViewHierarchyInRect:overlay.view.bounds
-                         afterScreenUpdates:YES];
+    if (base::FeatureList::IsEnabled(kSnapshotDrawView) && overlay.window) {
+      // The rect's origin is ignored. Only size is used.
+      [overlay drawViewHierarchyInRect:overlay.bounds afterScreenUpdates:YES];
     } else {
-      [[overlay.view layer] renderInContext:context];
+      [[overlay layer] renderInContext:context];
     }
     CGContextRestoreGState(context);
   }
 }
 
+// Retrieves information needed for snapshotting.
+- (SnapshotInfo)getSnapshotInfo {
+  SnapshotInfo snapshotInfo;
+  snapshotInfo.baseView = [self.delegate snapshotGenerator:self
+                                       baseViewForWebState:self.webState];
+  DCHECK(snapshotInfo.baseView);
+  UIEdgeInsets baseViewInsets = [self.delegate snapshotGenerator:self
+                                   snapshotEdgeInsetsForWebState:self.webState];
+  snapshotInfo.snapshotFrameInBaseView =
+      UIEdgeInsetsInsetRect(snapshotInfo.baseView.bounds, baseViewInsets);
+  DCHECK(!CGRectIsEmpty(snapshotInfo.snapshotFrameInBaseView));
+  snapshotInfo.snapshotFrameInWindow =
+      [snapshotInfo.baseView convertRect:snapshotInfo.snapshotFrameInBaseView
+                                  toView:nil];
+  DCHECK(!CGRectIsEmpty(snapshotInfo.snapshotFrameInWindow));
+  snapshotInfo.overlays = [self.delegate snapshotGenerator:self
+                               snapshotOverlaysForWebState:self.webState];
+  return snapshotInfo;
+}
+
 #pragma mark - Properties
 
 - (SnapshotCache*)snapshotCache {
diff --git a/ios/chrome/browser/snapshots/snapshot_generator_delegate.h b/ios/chrome/browser/snapshots/snapshot_generator_delegate.h
index b5e61940..1f3fa51 100644
--- a/ios/chrome/browser/snapshots/snapshot_generator_delegate.h
+++ b/ios/chrome/browser/snapshots/snapshot_generator_delegate.h
@@ -8,7 +8,6 @@
 #import <UIKit/UIKit.h>
 
 @class SnapshotGenerator;
-@class SnapshotOverlay;
 
 namespace web {
 class WebState;
@@ -28,12 +27,13 @@
 - (UIEdgeInsets)snapshotGenerator:(SnapshotGenerator*)snapshotGenerator
     snapshotEdgeInsetsForWebState:(web::WebState*)webState;
 
-// Returns the list of SnapshotOverlays that should be rendered over the
+// Returns the list of overlay views that should be rendered over the
 // page when generating the snapshot for |webState|. If no overlays should
-// be rendered, the list may be nil or empty.
-- (NSArray<SnapshotOverlay*>*)snapshotGenerator:
-                                  (SnapshotGenerator*)snapshotGenerator
-                    snapshotOverlaysForWebState:(web::WebState*)webState;
+// be rendered, the list may be nil or empty. The order of views in the array
+// will be the z order of their image in the composed snapshot. A view at the
+// end of the array will appear in front of a view at the beginning.
+- (NSArray<UIView*>*)snapshotGenerator:(SnapshotGenerator*)snapshotGenerator
+           snapshotOverlaysForWebState:(web::WebState*)webState;
 
 // Invoked before capturing a snapshot for |webState|. The delegate can remove
 // subviews from the hierarchy or take other actions to ensure the snapshot
diff --git a/ios/chrome/browser/snapshots/snapshot_overlay.h b/ios/chrome/browser/snapshots/snapshot_overlay.h
deleted file mode 100644
index 6253795e..0000000
--- a/ios/chrome/browser/snapshots/snapshot_overlay.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_OVERLAY_H_
-#define IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_OVERLAY_H_
-
-#import <UIKit/UIKit.h>
-
-// Simple object containing all the information needed to display an overlay
-// view in a snapshot.
-@interface SnapshotOverlay : NSObject
-
-// Initialize SnapshotOverlay with the given |view| and |yOffset|.
-- (instancetype)initWithView:(UIView*)view
-                     yOffset:(CGFloat)yOffset NS_DESIGNATED_INITIALIZER;
-
-- (instancetype)init NS_UNAVAILABLE;
-
-// The overlay view.
-@property(nonatomic, strong, readonly) UIView* view;
-
-// Y offset for the overlay view. Used to adjust the y position of |_view|
-// within the snapshot.
-@property(nonatomic, assign, readonly) CGFloat yOffset;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_SNAPSHOTS_SNAPSHOT_OVERLAY_H_
diff --git a/ios/chrome/browser/snapshots/snapshot_overlay.mm b/ios/chrome/browser/snapshots/snapshot_overlay.mm
deleted file mode 100644
index b936f4e..0000000
--- a/ios/chrome/browser/snapshots/snapshot_overlay.mm
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2013 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
-
-#include "base/logging.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation SnapshotOverlay
-
-@synthesize view = _view;
-@synthesize yOffset = _yOffset;
-
-- (instancetype)initWithView:(UIView*)view yOffset:(CGFloat)yOffset {
-  self = [super init];
-  if (self) {
-    DCHECK(view);
-    _view = view;
-    _yOffset = yOffset;
-  }
-  return self;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/browser_view_controller.mm b/ios/chrome/browser/ui/browser_view_controller.mm
index d17989e0..3d3a21f 100644
--- a/ios/chrome/browser/ui/browser_view_controller.mm
+++ b/ios/chrome/browser/ui/browser_view_controller.mm
@@ -60,7 +60,6 @@
 #import "ios/chrome/browser/signin/account_consistency_service_factory.h"
 #include "ios/chrome/browser/signin/account_reconcilor_factory.h"
 #import "ios/chrome/browser/snapshots/snapshot_generator_delegate.h"
-#import "ios/chrome/browser/snapshots/snapshot_overlay.h"
 #import "ios/chrome/browser/snapshots/snapshot_tab_helper.h"
 #import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper.h"
 #import "ios/chrome/browser/ssl/captive_portal_detector_tab_helper_delegate.h"
@@ -2920,40 +2919,28 @@
   return insets;
 }
 
-- (NSArray<SnapshotOverlay*>*)snapshotGenerator:
-                                  (SnapshotGenerator*)snapshotGenerator
-                    snapshotOverlaysForWebState:(web::WebState*)webState {
+- (NSArray<UIView*>*)snapshotGenerator:(SnapshotGenerator*)snapshotGenerator
+           snapshotOverlaysForWebState:(web::WebState*)webState {
   DCHECK(webState);
   Tab* tab = LegacyTabHelper::GetTabForWebState(webState);
   DCHECK([self.tabModel indexOfTab:tab] != NSNotFound);
   if (!self.webUsageEnabled)
     return @[];
 
-  NSMutableArray* overlays = [NSMutableArray array];
+  NSMutableArray<UIView*>* overlays = [NSMutableArray array];
   UIView* infoBarView = [self infoBarOverlayViewForTab:tab];
   if (infoBarView) {
-    CGFloat infoBarYOffset = [self infoBarOverlayYOffsetForTab:tab];
-    SnapshotOverlay* infoBarOverlay =
-        [[SnapshotOverlay alloc] initWithView:infoBarView
-                                      yOffset:infoBarYOffset];
-    [overlays addObject:infoBarOverlay];
+    [overlays addObject:infoBarView];
   }
 
   UIView* downloadManagerView = _downloadManagerCoordinator.viewController.view;
   if (downloadManagerView) {
-    CGFloat offset = [self downloadManagerOverlayYOffsetForTab:tab];
-    SnapshotOverlay* downloadManagerOverlay =
-        [[SnapshotOverlay alloc] initWithView:downloadManagerView
-                                      yOffset:offset];
-    [overlays addObject:downloadManagerOverlay];
+    [overlays addObject:downloadManagerView];
   }
 
   UIView* sadTabView = _sadTabCoordinator.viewController.view;
   if (sadTabView) {
-    SnapshotOverlay* sadTabOverlay =
-        [[SnapshotOverlay alloc] initWithView:sadTabView
-                                      yOffset:self.headerHeight];
-    [overlays addObject:sadTabOverlay];
+    [overlays addObject:sadTabView];
   }
 
   return overlays;
@@ -3002,23 +2989,6 @@
   return nil;
 }
 
-// Returns a vertical infobar offset relative to the tab content. It is an error
-// to call this method on a tab that does not have an infobar overlay.
-- (CGFloat)infoBarOverlayYOffsetForTab:(Tab*)tab {
-  DCHECK_EQ(tab, self.tabModel.currentTab);
-  DCHECK([self.infobarContainerCoordinator
-      isInfobarPresentingForWebState:tab.webState]);
-  CGRect visibleFrame = [self visibleFrameForTab:self.tabModel.currentTab];
-  return CGRectGetMaxY(visibleFrame) -
-         CGRectGetHeight([self.infobarContainerCoordinator view].frame);
-}
-
-// Returns a vertical download manager offset relative to the tab content.
-- (CGFloat)downloadManagerOverlayYOffsetForTab:(Tab*)tab {
-  return CGRectGetMaxY([self visibleFrameForTab:tab]) -
-         CGRectGetHeight(_downloadManagerCoordinator.viewController.view.frame);
-}
-
 #pragma mark - PasswordControllerDelegate methods
 
 - (BOOL)displaySignInNotification:(UIViewController*)viewController
diff --git a/ios/chrome/browser/ui/settings/cells/BUILD.gn b/ios/chrome/browser/ui/settings/cells/BUILD.gn
index 0b9fcb0..7020b30 100644
--- a/ios/chrome/browser/ui/settings/cells/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/cells/BUILD.gn
@@ -10,16 +10,12 @@
     "autofill_data_item.mm",
     "byo_textfield_item.h",
     "byo_textfield_item.mm",
-    "card_multiline_item.h",
-    "card_multiline_item.mm",
     "clear_browsing_data_constants.h",
     "clear_browsing_data_constants.mm",
     "clear_browsing_data_item.h",
     "clear_browsing_data_item.mm",
     "copied_to_chrome_item.h",
     "copied_to_chrome_item.mm",
-    "encryption_item.h",
-    "encryption_item.mm",
     "passphrase_error_item.h",
     "passphrase_error_item.mm",
     "settings_cells_constants.h",
@@ -77,10 +73,8 @@
   sources = [
     "autofill_data_item_unittest.mm",
     "byo_textfield_item_unittest.mm",
-    "card_multiline_item_unittest.mm",
     "clear_browsing_data_item_unittest.mm",
     "copied_to_chrome_item_unittest.mm",
-    "encryption_item_unittest.mm",
     "passphrase_error_item_unittest.mm",
     "settings_multiline_detail_item_unittest.mm",
     "text_and_error_item_unittest.mm",
diff --git a/ios/chrome/browser/ui/settings/cells/card_multiline_item.h b/ios/chrome/browser/ui/settings/cells/card_multiline_item.h
deleted file mode 100644
index 2dd1d4c..0000000
--- a/ios/chrome/browser/ui/settings/cells/card_multiline_item.h
+++ /dev/null
@@ -1,27 +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 IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_CARD_MULTILINE_ITEM_H_
-#define IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_CARD_MULTILINE_ITEM_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
-
-// Item to display a multiline text, presented in the card style.
-@interface CardMultilineItem : TableViewItem
-
-// The text to display.
-@property(nonatomic, copy) NSString* text;
-
-@end
-
-@interface CardMultilineCell : UITableViewCell
-
-// UILabel corresponding to |text| from the item.
-@property(nonatomic, readonly, strong) UILabel* textLabel;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_CARD_MULTILINE_ITEM_H_
diff --git a/ios/chrome/browser/ui/settings/cells/card_multiline_item.mm b/ios/chrome/browser/ui/settings/cells/card_multiline_item.mm
deleted file mode 100644
index 31ccb4e..0000000
--- a/ios/chrome/browser/ui/settings/cells/card_multiline_item.mm
+++ /dev/null
@@ -1,91 +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.
-
-#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
-
-#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui_util/constraints_ui_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-// Padding used on the leading and trailing edges of the cell.
-const CGFloat kHorizontalPadding = 16;
-
-// Padding used on the top and bottom edges of the cell.
-const CGFloat kVerticalPadding = 16;
-}  // namespace
-
-@implementation CardMultilineItem
-
-@synthesize text = _text;
-
-- (instancetype)initWithType:(NSInteger)type {
-  self = [super initWithType:type];
-  if (self) {
-    self.cellClass = [CardMultilineCell class];
-  }
-  return self;
-}
-
-#pragma mark TableViewItem
-
-- (void)configureCell:(CardMultilineCell*)cell
-           withStyler:(ChromeTableViewStyler*)styler {
-  [super configureCell:cell withStyler:styler];
-  cell.selectionStyle = UITableViewCellSelectionStyleNone;
-  cell.textLabel.text = self.text;
-}
-
-@end
-
-@implementation CardMultilineCell
-
-@synthesize textLabel = _textLabel;
-
-- (instancetype)initWithStyle:(UITableViewCellStyle)style
-              reuseIdentifier:(NSString*)reuseIdentifier {
-  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
-  if (self) {
-    UIView* contentView = self.contentView;
-
-    _textLabel = [[UILabel alloc] init];
-    _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
-    _textLabel.numberOfLines = 0;
-    _textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-    _textLabel.adjustsFontForContentSizeCategory = YES;
-    [contentView addSubview:_textLabel];
-
-    // Set up the constraints.
-    [NSLayoutConstraint activateConstraints:@[
-      [_textLabel.leadingAnchor
-          constraintEqualToAnchor:contentView.leadingAnchor
-                         constant:kHorizontalPadding],
-      [_textLabel.trailingAnchor
-          constraintEqualToAnchor:contentView.trailingAnchor
-                         constant:-kHorizontalPadding],
-    ]];
-    AddOptionalVerticalPadding(contentView, _textLabel, kVerticalPadding);
-  }
-  return self;
-}
-
-// Implements -layoutSubviews as per instructions in documentation for
-// +[MDCCollectionViewCell cr_preferredHeightForWidth:forItem:].
-- (void)layoutSubviews {
-  [super layoutSubviews];
-
-  // Adjust the text label preferredMaxLayoutWidth when the parent's width
-  // changes, for instance on screen rotation.
-  CGFloat parentWidth = CGRectGetWidth(self.contentView.bounds);
-  self.textLabel.preferredMaxLayoutWidth = parentWidth - 2 * kHorizontalPadding;
-
-  // Re-layout with the new preferred width to allow the label to adjust its
-  // height.
-  [super layoutSubviews];
-}
-
-@end
diff --git a/ios/chrome/browser/ui/settings/cells/card_multiline_item_unittest.mm b/ios/chrome/browser/ui/settings/cells/card_multiline_item_unittest.mm
deleted file mode 100644
index 2514f83..0000000
--- a/ios/chrome/browser/ui/settings/cells/card_multiline_item_unittest.mm
+++ /dev/null
@@ -1,44 +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.
-
-#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
-
-#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#import "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-using CardMultilineItemTest = PlatformTest;
-
-// Tests that the text is honoured after a call to |configureCell:|.
-TEST_F(CardMultilineItemTest, ConfigureCell) {
-  CardMultilineItem* item = [[CardMultilineItem alloc] initWithType:0];
-  NSString* text = @"Test Disclaimer";
-
-  item.text = text;
-
-  id cell = [[[item cellClass] alloc] init];
-  ASSERT_TRUE([cell isMemberOfClass:[CardMultilineCell class]]);
-
-  CardMultilineCell* disclaimerCell = static_cast<CardMultilineCell*>(cell);
-  EXPECT_FALSE(disclaimerCell.textLabel.text);
-
-  [item configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]];
-  EXPECT_NSEQ(text, disclaimerCell.textLabel.text);
-}
-
-// Tests that the text label of an CardMultilineCell spans multiple
-// lines.
-TEST_F(CardMultilineItemTest, MultipleLines) {
-  CardMultilineCell* cell = [[CardMultilineCell alloc] init];
-  EXPECT_EQ(0, cell.textLabel.numberOfLines);
-}
-
-}  // namespace
diff --git a/ios/chrome/browser/ui/settings/cells/encryption_item.h b/ios/chrome/browser/ui/settings/cells/encryption_item.h
deleted file mode 100644
index 9029d49..0000000
--- a/ios/chrome/browser/ui/settings/cells/encryption_item.h
+++ /dev/null
@@ -1,32 +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 IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_ENCRYPTION_ITEM_H_
-#define IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_ENCRYPTION_ITEM_H_
-
-#import <UIKit/UIKit.h>
-
-#import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
-
-// Item displaying possible options in the Sync Encryption screen.
-@interface EncryptionItem : TableViewItem
-
-// The text to display.
-@property(nonatomic, copy) NSString* text;
-
-// Whether or not the cell is enabled.  Disabled cells are drawn with dimmed
-// text.
-@property(nonatomic, assign, getter=isEnabled) BOOL enabled;
-
-@end
-
-// The cell associated to |EncryptionCell|.
-@interface EncryptionCell : UITableViewCell
-
-// UILabel corresponding to |text| from the item.
-@property(nonatomic, readonly, strong) UILabel* textLabel;
-
-@end
-
-#endif  // IOS_CHROME_BROWSER_UI_SETTINGS_CELLS_ENCRYPTION_ITEM_H_
diff --git a/ios/chrome/browser/ui/settings/cells/encryption_item.mm b/ios/chrome/browser/ui/settings/cells/encryption_item.mm
deleted file mode 100644
index ce476d6..0000000
--- a/ios/chrome/browser/ui/settings/cells/encryption_item.mm
+++ /dev/null
@@ -1,84 +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.
-
-#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
-
-#import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
-#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
-#import "ios/chrome/common/ui_util/constraints_ui_util.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-@implementation EncryptionItem
-
-@synthesize text = _text;
-@synthesize enabled = _enabled;
-
-- (instancetype)initWithType:(NSInteger)type {
-  self = [super initWithType:type];
-  if (self) {
-    self.cellClass = [EncryptionCell class];
-    self.enabled = YES;
-    self.accessibilityTraits |= UIAccessibilityTraitButton;
-  }
-  return self;
-}
-
-- (void)configureCell:(EncryptionCell*)cell
-           withStyler:(ChromeTableViewStyler*)styler {
-  [super configureCell:cell withStyler:styler];
-  cell.textLabel.text = self.text;
-  cell.textLabel.textColor =
-      self.enabled ? [UIColor blackColor]
-                   : UIColorFromRGB(kTableViewSecondaryLabelLightGrayTextColor);
-}
-
-@end
-
-@implementation EncryptionCell
-
-@synthesize textLabel = _textLabel;
-
-- (instancetype)initWithStyle:(UITableViewCellStyle)style
-              reuseIdentifier:(NSString*)reuseIdentifier {
-  self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
-  if (self) {
-    self.isAccessibilityElement = YES;
-
-    _textLabel = [[UILabel alloc] init];
-    _textLabel.translatesAutoresizingMaskIntoConstraints = NO;
-    _textLabel.numberOfLines = 0;
-    _textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
-    _textLabel.adjustsFontForContentSizeCategory = YES;
-    [self.contentView addSubview:_textLabel];
-
-    // Set up the constraints.
-    [NSLayoutConstraint activateConstraints:@[
-      [_textLabel.leadingAnchor
-          constraintEqualToAnchor:self.contentView.leadingAnchor
-                         constant:kTableViewHorizontalSpacing],
-      [_textLabel.trailingAnchor
-          constraintEqualToAnchor:self.contentView.trailingAnchor
-                         constant:-kTableViewHorizontalSpacing],
-    ]];
-    AddOptionalVerticalPadding(self.contentView, _textLabel,
-                               kTableViewLargeVerticalSpacing);
-  }
-  return self;
-}
-
-- (void)prepareForReuse {
-  [super prepareForReuse];
-  self.textLabel.text = nil;
-}
-
-#pragma mark - UIAccessibility
-
-- (NSString*)accessibilityLabel {
-  return self.textLabel.text;
-}
-
-@end
diff --git a/ios/chrome/browser/ui/settings/cells/encryption_item_unittest.mm b/ios/chrome/browser/ui/settings/cells/encryption_item_unittest.mm
deleted file mode 100644
index 1e0dc9c..0000000
--- a/ios/chrome/browser/ui/settings/cells/encryption_item_unittest.mm
+++ /dev/null
@@ -1,40 +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.
-
-#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
-
-#import "ios/chrome/browser/ui/table_view/chrome_table_view_styler.h"
-#include "testing/gtest/include/gtest/gtest.h"
-#import "testing/gtest_mac.h"
-#include "testing/platform_test.h"
-
-#if !defined(__has_feature) || !__has_feature(objc_arc)
-#error "This file requires ARC support."
-#endif
-
-namespace {
-
-using EncryptionItemTest = PlatformTest;
-
-// Tests that the text label, enabled status and accessory type are set properly
-// after a call to |configureCell:|.
-TEST_F(EncryptionItemTest, ConfigureCell) {
-  EncryptionItem* item = [[EncryptionItem alloc] initWithType:0];
-  EncryptionCell* cell = [[[item cellClass] alloc] init];
-  EXPECT_TRUE([cell isMemberOfClass:[EncryptionCell class]]);
-  EXPECT_NSEQ(nil, cell.textLabel.text);
-  NSString* text = @"Test text";
-  UIColor* enabledColor = cell.textLabel.textColor;
-
-  item.text = text;
-  item.enabled = NO;
-  item.accessoryType = UITableViewCellAccessoryCheckmark;
-  [item configureCell:cell withStyler:[[ChromeTableViewStyler alloc] init]];
-
-  EXPECT_NSEQ(text, cell.textLabel.text);
-  EXPECT_NE(enabledColor, cell.textLabel.textColor);
-  EXPECT_EQ(UITableViewCellAccessoryCheckmark, cell.accessoryType);
-}
-
-}  // namespace
diff --git a/ios/chrome/browser/ui/settings/import_data_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/import_data_table_view_controller_unittest.mm
index 3fb7a8c..88b7a91 100644
--- a/ios/chrome/browser/ui/settings/import_data_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/import_data_table_view_controller_unittest.mm
@@ -6,7 +6,6 @@
 
 #include "base/mac/foundation_util.h"
 #include "base/strings/sys_string_conversions.h"
-#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_multiline_detail_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h"
 #import "ios/chrome/browser/ui/table_view/table_view_model.h"
@@ -71,7 +70,7 @@
   CheckController();
   ASSERT_EQ(2, NumberOfSections());
   EXPECT_EQ(1, NumberOfItemsInSection(0));
-  CardMultilineItem* item = GetTableViewItem(0, 0);
+  SettingsMultilineDetailItem* item = GetTableViewItem(0, 0);
   EXPECT_NSEQ(
       l10n_util::GetNSStringF(IDS_IOS_OPTIONS_IMPORT_DATA_HEADER,
                               base::SysNSStringToUTF16(@"fromEmail@gmail.com")),
@@ -97,7 +96,7 @@
   CheckController();
   ASSERT_EQ(2, NumberOfSections());
   EXPECT_EQ(1, NumberOfItemsInSection(0));
-  CardMultilineItem* item = GetTableViewItem(0, 0);
+  SettingsMultilineDetailItem* item = GetTableViewItem(0, 0);
   EXPECT_NSEQ(
       l10n_util::GetNSStringF(IDS_IOS_OPTIONS_IMPORT_DATA_HEADER,
                               base::SysNSStringToUTF16(@"fromEmail@gmail.com")),
diff --git a/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm b/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm
index 742753dc..a7e067e 100644
--- a/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/material_cell_catalog_view_controller.mm
@@ -30,7 +30,6 @@
 #import "ios/chrome/browser/ui/payments/cells/autofill_profile_item.h"
 #import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
 #import "ios/chrome/browser/ui/payments/cells/price_item.h"
-#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
 #import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
 #import "ios/chrome/browser/ui/settings/cells/legacy/legacy_account_signin_item.h"
 #import "ios/chrome/browser/ui/settings/cells/legacy/legacy_settings_detail_item.h"
diff --git a/ios/chrome/browser/ui/settings/sync/BUILD.gn b/ios/chrome/browser/ui/settings/sync/BUILD.gn
index 7400f1d..31980a4 100644
--- a/ios/chrome/browser/ui/settings/sync/BUILD.gn
+++ b/ios/chrome/browser/ui/settings/sync/BUILD.gn
@@ -86,6 +86,7 @@
     "//ios/chrome/browser/ui/settings/cells/legacy",
     "//ios/chrome/browser/ui/settings/sync/utils",
     "//ios/chrome/browser/ui/table_view:test_support",
+    "//ios/chrome/browser/ui/table_view/cells",
     "//ios/web/public/test",
     "//testing/gtest",
     "//ui/base",
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm
index cfd1bb4..feab597 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.mm
@@ -23,12 +23,12 @@
 #include "ios/chrome/browser/sync/sync_setup_service.h"
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #import "ios/chrome/browser/ui/settings/cells/byo_textfield_item.h"
-#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
 #import "ios/chrome/browser/ui/settings/cells/passphrase_error_item.h"
 #import "ios/chrome/browser/ui/settings/settings_navigation_controller.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
 #import "ios/chrome/browser/ui/settings/utils/settings_utils.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
 #import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
@@ -189,9 +189,10 @@
 
 // Returns a passphrase message item.
 - (TableViewItem*)passphraseMessageItem {
-  CardMultilineItem* item =
-      [[CardMultilineItem alloc] initWithType:ItemTypeMessage];
+  TableViewTextItem* item =
+      [[TableViewTextItem alloc] initWithType:ItemTypeMessage];
   item.text = self.headerMessage;
+  item.enabled = NO;
   return item;
 }
 
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm
index 9f8e3bf..e84e5543 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller_unittest.mm
@@ -20,9 +20,9 @@
 #include "ios/chrome/browser/sync/sync_setup_service_factory.h"
 #include "ios/chrome/browser/sync/sync_setup_service_mock.h"
 #import "ios/chrome/browser/ui/settings/cells/byo_textfield_item.h"
-#import "ios/chrome/browser/ui/settings/cells/card_multiline_item.h"
 #import "ios/chrome/browser/ui/settings/passphrase_table_view_controller_test.h"
 #import "ios/chrome/browser/ui/settings/sync/utils/sync_util.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
 #import "testing/gtest_mac.h"
 #include "testing/platform_test.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -105,7 +105,7 @@
   EXPECT_EQ(1, NumberOfSections());
   EXPECT_EQ(2, NumberOfItemsInSection(0));
   // Passphrase message item.
-  CardMultilineItem* item = GetTableViewItem(0, 0);
+  TableViewTextItem* item = GetTableViewItem(0, 0);
   EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY),
               item.text);
   // Passphrase items.
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm
index 02c605a..57a844f7 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller.mm
@@ -19,15 +19,16 @@
 #include "ios/chrome/browser/chrome_url_constants.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
 #import "ios/chrome/browser/sync/sync_observer_bridge.h"
-#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
 #import "ios/chrome/browser/ui/settings/sync/sync_create_passphrase_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/sync/sync_encryption_passphrase_table_view_controller.h"
 #import "ios/chrome/browser/ui/settings/utils/settings_utils.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_cells_constants.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_item.h"
 #import "ios/chrome/browser/ui/table_view/cells/table_view_link_header_footer_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
 #import "ios/chrome/browser/ui/table_view/table_view_model.h"
 #include "ios/chrome/browser/ui/ui_feature_flags.h"
+#import "ios/chrome/browser/ui/util/uikit_ui_util.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ui/base/l10n/l10n_util_mac.h"
 #include "url/gurl.h"
@@ -154,18 +155,6 @@
   return footerView;
 }
 
-- (BOOL)tableView:(UITableView*)tableView
-    shouldHighlightRowAtIndexPath:(NSIndexPath*)indexPath {
-  TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
-  if (item.type == ItemTypePassphrase || item.type == ItemTypeAccount) {
-    EncryptionItem* encryptionItem =
-        base::mac::ObjCCastStrict<EncryptionItem>(item);
-    // Don't perform any action if the cell isn't enabled.
-    return encryptionItem.isEnabled;
-  }
-  return YES;
-}
-
 - (void)tableView:(UITableView*)tableView
     didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
   DCHECK_EQ(indexPath.section,
@@ -173,12 +162,6 @@
                 sectionForSectionIdentifier:SectionIdentifierEncryption]);
 
   TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
-  if ([item respondsToSelector:@selector(isEnabled)] &&
-      ![item performSelector:@selector(isEnabled)]) {
-    // Don't perform any action if the cell isn't enabled.
-    return;
-  }
-
   switch (item.type) {
     case ItemTypePassphrase: {
       DCHECK(browser_sync::ProfileSyncService::IsSyncAllowedByFlag());
@@ -226,10 +209,14 @@
                           text:(NSString*)text
                        checked:(BOOL)checked
                        enabled:(BOOL)enabled {
-  EncryptionItem* item = [[EncryptionItem alloc] initWithType:type];
+  TableViewTextItem* item = [[TableViewTextItem alloc] initWithType:type];
+  item.accessibilityTraits |= UIAccessibilityTraitButton;
   item.text = text;
   item.accessoryType = checked ? UITableViewCellAccessoryCheckmark
                                : UITableViewCellAccessoryNone;
+  item.textColor =
+      enabled ? [UIColor blackColor]
+              : UIColorFromRGB(kTableViewSecondaryLabelLightGrayTextColor);
   item.enabled = enabled;
   return item;
 }
diff --git a/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller_unittest.mm
index 2b0336d..e8bcd9e 100644
--- a/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/sync/sync_encryption_table_view_controller_unittest.mm
@@ -13,7 +13,7 @@
 #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
 #include "ios/chrome/browser/sync/ios_chrome_profile_sync_test_util.h"
 #include "ios/chrome/browser/sync/profile_sync_service_factory.h"
-#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
+#import "ios/chrome/browser/ui/table_view/cells/table_view_text_item.h"
 #import "ios/chrome/browser/ui/table_view/chrome_table_view_controller_test.h"
 #include "ios/chrome/grit/ios_strings.h"
 #include "ios/web/public/test/test_web_thread_bundle.h"
@@ -84,11 +84,11 @@
   NSInteger const kSection = 0;
   EXPECT_EQ(2, NumberOfItemsInSection(kSection));
 
-  EncryptionItem* accountItem = GetTableViewItem(kSection, 0);
+  TableViewTextItem* accountItem = GetTableViewItem(kSection, 0);
   EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_BASIC_ENCRYPTION_DATA),
               accountItem.text);
 
-  EncryptionItem* passphraseItem = GetTableViewItem(kSection, 1);
+  TableViewTextItem* passphraseItem = GetTableViewItem(kSection, 1);
   EXPECT_NSEQ(l10n_util::GetNSString(IDS_SYNC_FULL_ENCRYPTION_DATA),
               passphraseItem.text);
 }
diff --git a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
index d0570e0..42b36f5fa 100644
--- a/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/table_cell_catalog_view_controller.mm
@@ -13,7 +13,6 @@
 #import "ios/chrome/browser/ui/settings/cells/account_sign_in_item.h"
 #import "ios/chrome/browser/ui/settings/cells/autofill_data_item.h"
 #import "ios/chrome/browser/ui/settings/cells/copied_to_chrome_item.h"
-#import "ios/chrome/browser/ui/settings/cells/encryption_item.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_detail_item.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_image_detail_text_item.h"
 #import "ios/chrome/browser/ui/settings/cells/settings_switch_item.h"
@@ -60,7 +59,6 @@
   ItemTypeURLWithSupplementalText,
   ItemTypeURLWithBadgeImage,
   ItemTypeTextSettingsDetail,
-  ItemTypeEncryption,
   ItemTypeLinkFooter,
   ItemTypeDetailText,
   ItemTypeAccountSignInItem,
@@ -227,25 +225,6 @@
   [model addItem:imageDetailTextItem
       toSectionWithIdentifier:SectionIdentifierSettings];
 
-  EncryptionItem* encryptionChecked =
-      [[EncryptionItem alloc] initWithType:ItemTypeEncryption];
-  encryptionChecked.text =
-      @"These two cells have exactly the same text, but one has a checkmark "
-      @"and the other does not.  They should lay out identically, and the "
-      @"presence of the checkmark should not cause the text to reflow.";
-  encryptionChecked.accessoryType = UITableViewCellAccessoryCheckmark;
-  [model addItem:encryptionChecked
-      toSectionWithIdentifier:SectionIdentifierSettings];
-
-  EncryptionItem* encryptionUnchecked =
-      [[EncryptionItem alloc] initWithType:ItemTypeEncryption];
-  encryptionUnchecked.text =
-      @"These two cells have exactly the same text, but one has a checkmark "
-      @"and the other does not.  They should lay out identically, and the "
-      @"presence of the checkmark should not cause the text to reflow.";
-  [model addItem:encryptionUnchecked
-      toSectionWithIdentifier:SectionIdentifierSettings];
-
   TableViewLinkHeaderFooterItem* linkFooter =
       [[TableViewLinkHeaderFooterItem alloc] initWithType:ItemTypeLinkFooter];
   linkFooter.text =
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h
index a9606d5..a26c511 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.h
@@ -25,6 +25,10 @@
 // If set to YES, |text| will be shown as "••••••" with fixed length.
 @property(nonatomic, assign) BOOL masked;
 
+// Whether this item is enabled. If it is not enabled, the corresponding cell
+// has its user interaction disabled. Enabled by default.
+@property(nonatomic, assign, getter=isEnabled) BOOL enabled;
+
 @end
 
 // UITableViewCell that displays a text label.
diff --git a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm
index d0e7537..caec2cb 100644
--- a/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm
+++ b/ios/chrome/browser/ui/table_view/cells/table_view_text_item.mm
@@ -28,6 +28,7 @@
   self = [super initWithType:type];
   if (self) {
     self.cellClass = [TableViewTextCell class];
+    _enabled = YES;
   }
   return self;
 }
@@ -71,6 +72,8 @@
   }
   cell.textLabel.textAlignment =
       self.textAlignment ? self.textAlignment : NSTextAlignmentLeft;
+
+  cell.userInteractionEnabled = self.enabled;
 }
 
 @end
@@ -128,6 +131,7 @@
 - (void)prepareForReuse {
   [super prepareForReuse];
   self.checked = NO;
+  self.userInteractionEnabled = YES;
 }
 
 @end
diff --git a/media/cdm/cdm_proxy.h b/media/cdm/cdm_proxy.h
index 91d4085..6d908e0 100644
--- a/media/cdm/cdm_proxy.h
+++ b/media/cdm/cdm_proxy.h
@@ -34,8 +34,11 @@
    public:
     Client();
     virtual ~Client();
-    // Called when there is a hardware reset and all the hardware context is
-    // lost.
+
+    // Called when there is a hardware reset. When hardware reset happens, all
+    // the hardware context is lost and all crypto sessions are destroyed. The
+    // CdmProxy returns to an uninitialized state and the caller must call
+    // Initialize() on the CdmProxy again to be able to continue using it.
     virtual void NotifyHardwareReset() = 0;
   };
 
@@ -82,7 +85,10 @@
       void(Status status, Protocol protocol, uint32_t crypto_session_id)>;
 
   // Initializes the proxy. The status and the return values of the call is
-  // reported to |init_cb|.
+  // reported to |init_cb|. All other methods should only be called after the
+  // proxy is fully initialized. Otherwise they may fail.
+  // Note: The proxy also needs to be reinitialized after hardware reset. See
+  // Client::NotifyHardwareReset() for details.
   virtual void Initialize(Client* client, InitializeCB init_cb) = 0;
 
   // Callback for Process(). |output_data| is the output of processing.
diff --git a/net/BUILD.gn b/net/BUILD.gn
index e4866fc..311e601 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -1811,6 +1811,8 @@
         "reporting/reporting_browsing_data_remover.h",
         "reporting/reporting_cache.cc",
         "reporting/reporting_cache.h",
+        "reporting/reporting_cache_impl.cc",
+        "reporting/reporting_cache_impl.h",
         "reporting/reporting_cache_observer.cc",
         "reporting/reporting_cache_observer.h",
         "reporting/reporting_client.cc",
diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc
index e9b0c4c..81705d66 100644
--- a/net/cookies/cookie_monster.cc
+++ b/net/cookies/cookie_monster.cc
@@ -675,18 +675,18 @@
   if (HasCookieableScheme(url)) {
     std::vector<CanonicalCookie*> cookie_ptrs;
     FindCookiesForRegistryControlledHost(url, &cookie_ptrs);
+    std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter);
 
     cookies.reserve(cookie_ptrs.size());
-    std::vector<CanonicalCookie*> filtered_cookie_ptrs;
-    FilterCookiesWithOptions(url, options, &cookie_ptrs, &filtered_cookie_ptrs);
+    std::vector<CanonicalCookie*> included_cookie_ptrs;
+    FilterCookiesWithOptions(url, options, &cookie_ptrs, &included_cookie_ptrs,
+                             &excluded_cookies);
 
-    std::sort(filtered_cookie_ptrs.begin(), filtered_cookie_ptrs.end(),
-              CookieSorter);
-
-    for (auto* cookie : filtered_cookie_ptrs) {
+    for (auto* cookie : included_cookie_ptrs) {
       cookies.push_back(*cookie);
     }
   }
+
   MaybeRunCookieCallback(std::move(callback), cookies, excluded_cookies);
 }
 
@@ -781,12 +781,13 @@
       CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
   // Get the cookies for this host and its domain(s).
   std::vector<CanonicalCookie*> cookie_ptrs;
-  std::vector<CanonicalCookie*> filtered_cookie_ptrs;
+  std::vector<CanonicalCookie*> included_cookie_ptrs;
   FindCookiesForRegistryControlledHost(url, &cookie_ptrs);
-  FilterCookiesWithOptions(url, options, &cookie_ptrs, &filtered_cookie_ptrs);
+  FilterCookiesWithOptions(url, options, &cookie_ptrs, &included_cookie_ptrs,
+                           nullptr);
   std::set<CanonicalCookie*> matching_cookies;
 
-  for (auto* cookie : filtered_cookie_ptrs) {
+  for (auto* cookie : included_cookie_ptrs) {
     DCHECK(cookie->IsOnPath(url.path()));
     DCHECK(cookie->IsDomainMatch(url.host()));
     if (cookie->Name() != cookie_name)
@@ -1113,7 +1114,8 @@
     const GURL url,
     const CookieOptions options,
     std::vector<CanonicalCookie*>* cookie_ptrs,
-    std::vector<CanonicalCookie*>* cookies) {
+    std::vector<CanonicalCookie*>* included_cookie_ptrs,
+    CookieStatusList* excluded_cookies) {
   DCHECK(thread_checker_.CalledOnValidThread());
 
   // Probe to save statistics relatively frequently.  We do it here rather
@@ -1127,15 +1129,19 @@
     // Filter out cookies that should not be included for a request to the
     // given |url|. HTTP only cookies are filtered depending on the passed
     // cookie |options|.
-    if ((*it)->IncludeForRequestURL(url, options) !=
-        CanonicalCookie::CookieInclusionStatus::INCLUDE) {
+    CanonicalCookie::CookieInclusionStatus status =
+        (*it)->IncludeForRequestURL(url, options);
+
+    if (status != CanonicalCookie::CookieInclusionStatus::INCLUDE) {
+      if (options.return_excluded_cookies())
+        excluded_cookies->push_back({**it, status});
       continue;
     }
 
     if (options.update_access_time())
       InternalUpdateCookieAccessTime(*it, current_time);
 
-    cookies->push_back(*it);
+    included_cookie_ptrs->push_back(*it);
   }
 }
 
diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h
index a4bb4e81..1138ffd 100644
--- a/net/cookies/cookie_monster.h
+++ b/net/cookies/cookie_monster.h
@@ -450,10 +450,12 @@
       const GURL& url,
       std::vector<CanonicalCookie*>* cookies);
 
-  void FilterCookiesWithOptions(const GURL url,
-                                const CookieOptions options,
-                                std::vector<CanonicalCookie*>* cookie_ptrs,
-                                std::vector<CanonicalCookie*>* cookies);
+  void FilterCookiesWithOptions(
+      const GURL url,
+      const CookieOptions options,
+      std::vector<CanonicalCookie*>* cookie_ptrs,
+      std::vector<CanonicalCookie*>* included_cookie_ptrs,
+      CookieStatusList* excluded_cookie_ptrs);
   // Delete any cookies that are equivalent to |ecc| (same path, domain, etc).
   // |source_secure| indicates if the source may override existing secure
   // cookies.
diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc
index 33d2459..c7f47ec 100644
--- a/net/cookies/cookie_monster_unittest.cc
+++ b/net/cookies/cookie_monster_unittest.cc
@@ -152,6 +152,20 @@
     return callback.cookies();
   }
 
+  CookieStatusList GetExcludedCookiesForURLWithOptions(
+      CookieMonster* cm,
+      const GURL& url,
+      const CookieOptions& options) {
+    DCHECK(cm);
+    GetCookieListCallback callback;
+    cm->GetCookieListWithOptionsAsync(
+        url, options,
+        base::BindOnce(&GetCookieListCallback::Run,
+                       base::Unretained(&callback)));
+    callback.WaitUntilDone();
+    return callback.excluded_cookies();
+  }
+
   bool SetAllCookies(CookieMonster* cm, const CookieList& list) {
     DCHECK(cm);
     ResultSavingCookieCallback<bool> callback;
@@ -1633,6 +1647,78 @@
   EXPECT_EQ(last_access_date, GetFirstCookieAccessDate(cm.get()));
 }
 
+TEST_F(CookieMonsterTest, GetExcludedCookiesForURL) {
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(nullptr, kLastAccessThreshold, &net_log_));
+
+  // Create an httponly cookie.
+  CookieOptions options;
+  options.set_include_httponly();
+
+  EXPECT_TRUE(SetCookieWithOptions(cm.get(), http_www_foo_.url(),
+                                   "A=B; httponly", options));
+  EXPECT_TRUE(SetCookieWithOptions(cm.get(), http_www_foo_.url(),
+                                   http_www_foo_.Format("C=D; domain=.%D"),
+                                   options));
+  EXPECT_TRUE(SetCookieWithOptions(
+      cm.get(), https_www_foo_.url(),
+      http_www_foo_.Format("E=F; domain=.%D; secure"), options));
+
+  base::PlatformThread::Sleep(kAccessDelay);
+
+  // Check that no cookies are sent when option is turned off
+  CookieOptions do_not_return_excluded;
+  do_not_return_excluded.unset_return_excluded_cookies();
+
+  CookieStatusList excluded_cookies = GetExcludedCookiesForURLWithOptions(
+      cm.get(), http_www_foo_.url(), do_not_return_excluded);
+  auto iter = excluded_cookies.begin();
+
+  EXPECT_TRUE(excluded_cookies.empty());
+
+  // Checking that excluded cookies get sent with their statuses with http
+  // request.
+  excluded_cookies = GetExcludedCookiesForURL(cm.get(), http_www_foo_.url());
+  iter = excluded_cookies.begin();
+
+  ASSERT_TRUE(iter != excluded_cookies.end());
+  EXPECT_EQ(http_www_foo_.Format(".%D"), iter->cookie.Domain());
+  EXPECT_EQ("E", iter->cookie.Name());
+  EXPECT_EQ(CanonicalCookie::CookieInclusionStatus::EXCLUDE_SECURE_ONLY,
+            iter->status);
+
+  ASSERT_TRUE(++iter == excluded_cookies.end());
+
+  // Checking that excluded cookies get sent with their statuses with http-only.
+  CookieOptions return_excluded = CookieOptions();
+  return_excluded.set_return_excluded_cookies();
+  return_excluded.set_exclude_httponly();
+
+  excluded_cookies = GetExcludedCookiesForURLWithOptions(
+      cm.get(), http_www_foo_.url(), return_excluded);
+  iter = excluded_cookies.begin();
+
+  ASSERT_TRUE(iter != excluded_cookies.end());
+  EXPECT_EQ(http_www_foo_.host(), iter->cookie.Domain());
+  EXPECT_EQ("A", iter->cookie.Name());
+  EXPECT_EQ(CanonicalCookie::CookieInclusionStatus::EXCLUDE_HTTP_ONLY,
+            iter->status);
+
+  ASSERT_TRUE(++iter != excluded_cookies.end());
+  EXPECT_EQ(http_www_foo_.Format(".%D"), iter->cookie.Domain());
+  EXPECT_EQ("E", iter->cookie.Name());
+  EXPECT_EQ(CanonicalCookie::CookieInclusionStatus::EXCLUDE_SECURE_ONLY,
+            iter->status);
+
+  ASSERT_TRUE(++iter == excluded_cookies.end());
+
+  // Check that no excluded cookies are sent with secure request
+  excluded_cookies = GetExcludedCookiesForURL(cm.get(), https_www_foo_.url());
+  iter = excluded_cookies.begin();
+
+  EXPECT_TRUE(excluded_cookies.empty());
+}
+
 TEST_F(CookieMonsterTest, GetAllCookiesForURLPathMatching) {
   std::unique_ptr<CookieMonster> cm(
       new CookieMonster(nullptr, nullptr, &net_log_));
@@ -1672,6 +1758,42 @@
   ASSERT_TRUE(++it == cookies.end());
 }
 
+TEST_F(CookieMonsterTest, GetExcludedCookiesForURLPathMatching) {
+  std::unique_ptr<CookieMonster> cm(
+      new CookieMonster(nullptr, nullptr, &net_log_));
+  CookieOptions options;
+
+  EXPECT_TRUE(SetCookieWithOptions(cm.get(), www_foo_foo_.url(),
+                                   "A=B; path=/foo;", options));
+  EXPECT_TRUE(SetCookieWithOptions(cm.get(), www_foo_bar_.url(),
+                                   "C=D; path=/bar;", options));
+  EXPECT_TRUE(
+      SetCookieWithOptions(cm.get(), http_www_foo_.url(), "E=F;", options));
+
+  CookieStatusList excluded_cookies =
+      GetExcludedCookiesForURL(cm.get(), www_foo_foo_.url());
+  auto it = excluded_cookies.begin();
+
+  ASSERT_TRUE(it != excluded_cookies.end());
+  EXPECT_EQ("C", it->cookie.Name());
+  EXPECT_EQ("/bar", it->cookie.Path());
+  EXPECT_EQ(CanonicalCookie::CookieInclusionStatus::EXCLUDE_NOT_ON_PATH,
+            it->status);
+
+  ASSERT_TRUE(++it == excluded_cookies.end());
+
+  excluded_cookies = GetExcludedCookiesForURL(cm.get(), www_foo_bar_.url());
+  it = excluded_cookies.begin();
+
+  ASSERT_TRUE(it != excluded_cookies.end());
+  EXPECT_EQ("A", it->cookie.Name());
+  EXPECT_EQ("/foo", it->cookie.Path());
+  EXPECT_EQ(CanonicalCookie::CookieInclusionStatus::EXCLUDE_NOT_ON_PATH,
+            it->status);
+
+  ASSERT_TRUE(++it == excluded_cookies.end());
+}
+
 TEST_F(CookieMonsterTest, CookieSorting) {
   std::unique_ptr<CookieMonster> cm(
       new CookieMonster(nullptr, nullptr, &net_log_));
diff --git a/net/cookies/cookie_options.cc b/net/cookies/cookie_options.cc
index 1b97f85..f7b0426 100644
--- a/net/cookies/cookie_options.cc
+++ b/net/cookies/cookie_options.cc
@@ -13,6 +13,7 @@
     : exclude_httponly_(true),
       same_site_cookie_mode_(SameSiteCookieMode::DO_NOT_INCLUDE),
       update_access_time_(true),
-      server_time_() {}
+      server_time_(),
+      return_excluded_cookies_(false) {}
 
 }  // namespace net
diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h
index 0e312b0..6d8f3e4 100644
--- a/net/cookies/cookie_options.h
+++ b/net/cookies/cookie_options.h
@@ -63,11 +63,16 @@
   void set_do_not_update_access_time() { update_access_time_ = false; }
   bool update_access_time() const { return update_access_time_; }
 
+  void set_return_excluded_cookies() { return_excluded_cookies_ = true; }
+  void unset_return_excluded_cookies() { return_excluded_cookies_ = false; }
+  bool return_excluded_cookies() const { return return_excluded_cookies_; }
+
  private:
   bool exclude_httponly_;
   SameSiteCookieMode same_site_cookie_mode_;
   bool update_access_time_;
   base::Time server_time_;
+  bool return_excluded_cookies_;
 };
 
 }  // namespace net
diff --git a/net/cookies/cookie_store_test_callbacks.cc b/net/cookies/cookie_store_test_callbacks.cc
index f50f3685..58a9c25 100644
--- a/net/cookies/cookie_store_test_callbacks.cc
+++ b/net/cookies/cookie_store_test_callbacks.cc
@@ -53,6 +53,7 @@
 void GetCookieListCallback::Run(const CookieList& cookies,
                                 const CookieStatusList& excluded_cookies) {
   cookies_ = cookies;
+  excluded_cookies_ = excluded_cookies;
   CallbackEpilogue();
 }
 
diff --git a/net/cookies/cookie_store_test_callbacks.h b/net/cookies/cookie_store_test_callbacks.h
index fa9ddb6..4a5ea9e 100644
--- a/net/cookies/cookie_store_test_callbacks.h
+++ b/net/cookies/cookie_store_test_callbacks.h
@@ -90,9 +90,11 @@
   void Run(const CookieList& cookies, const CookieStatusList& excluded_cookies);
 
   const CookieList& cookies() { return cookies_; }
+  const CookieStatusList& excluded_cookies() { return excluded_cookies_; }
 
  private:
   CookieList cookies_;
+  CookieStatusList excluded_cookies_;
 };
 
 }  // namespace net
diff --git a/net/cookies/cookie_store_unittest.h b/net/cookies/cookie_store_unittest.h
index c15f43c..5c5fb91 100644
--- a/net/cookies/cookie_store_unittest.h
+++ b/net/cookies/cookie_store_unittest.h
@@ -169,6 +169,22 @@
     return callback.cookies();
   }
 
+  CookieStatusList GetExcludedCookiesForURL(CookieStore* cs, const GURL& url) {
+    DCHECK(cs);
+    GetCookieListCallback callback;
+    CookieOptions options;
+    options.set_include_httponly();
+    options.set_same_site_cookie_mode(
+        CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
+    options.set_return_excluded_cookies();
+    cs->GetCookieListWithOptionsAsync(
+        url, options,
+        base::BindOnce(&GetCookieListCallback::Run,
+                       base::Unretained(&callback)));
+    callback.WaitUntilDone();
+    return callback.excluded_cookies();
+  }
+
   CookieList GetAllCookies(CookieStore* cs) {
     DCHECK(cs);
     GetCookieListCallback callback;
diff --git a/net/reporting/reporting_cache.cc b/net/reporting/reporting_cache.cc
index 01a7e06..8773122 100644
--- a/net/reporting/reporting_cache.cc
+++ b/net/reporting/reporting_cache.cc
@@ -4,634 +4,11 @@
 
 #include "net/reporting/reporting_cache.h"
 
-#include <algorithm>
-#include <map>
-#include <set>
-#include <string>
-#include <unordered_map>
-#include <unordered_set>
-#include <utility>
-#include <vector>
-
-#include "base/stl_util.h"
-#include "base/time/tick_clock.h"
-#include "base/time/time.h"
-#include "net/log/net_log.h"
-#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_cache_impl.h"
 #include "net/reporting/reporting_context.h"
-#include "net/reporting/reporting_report.h"
-#include "url/gurl.h"
 
 namespace net {
 
-namespace {
-
-// Returns the superdomain of a given domain, or the empty string if the given
-// domain is just a single label. Note that this does not take into account
-// anything like the Public Suffix List, so the superdomain may end up being a
-// bare TLD.
-//
-// Examples:
-//
-// GetSuperdomain("assets.example.com") -> "example.com"
-// GetSuperdomain("example.net") -> "net"
-// GetSuperdomain("littlebox") -> ""
-std::string GetSuperdomain(const std::string& domain) {
-  size_t dot_pos = domain.find('.');
-  if (dot_pos == std::string::npos)
-    return "";
-
-  return domain.substr(dot_pos + 1);
-}
-
-struct ClientMetadata {
-  base::TimeTicks last_used;
-  ReportingCache::ClientStatistics stats;
-};
-
-class ReportingCacheImpl : public ReportingCache {
- public:
-  ReportingCacheImpl(ReportingContext* context) : context_(context) {
-    DCHECK(context_);
-  }
-
-  ~ReportingCacheImpl() override {
-    base::TimeTicks now = tick_clock()->NowTicks();
-
-    // Mark all undoomed reports as erased at shutdown, and record outcomes of
-    // all remaining reports (doomed or not).
-    for (auto it = reports_.begin(); it != reports_.end(); ++it) {
-      ReportingReport* report = it->second.get();
-      if (!base::ContainsKey(doomed_reports_, report))
-        report->outcome = ReportingReport::Outcome::ERASED_REPORTING_SHUT_DOWN;
-      report->RecordOutcome(now);
-    }
-
-    reports_.clear();
-  }
-
-  void AddReport(const GURL& url,
-                 const std::string& user_agent,
-                 const std::string& group,
-                 const std::string& type,
-                 std::unique_ptr<const base::Value> body,
-                 int depth,
-                 base::TimeTicks queued,
-                 int attempts) override {
-    auto report = std::make_unique<ReportingReport>(
-        url, user_agent, group, type, std::move(body), depth, queued, attempts);
-
-    auto inserted =
-        reports_.insert(std::make_pair(report.get(), std::move(report)));
-    DCHECK(inserted.second);
-
-    if (reports_.size() > context_->policy().max_report_count) {
-      // There should be at most one extra report (the one added above).
-      DCHECK_EQ(context_->policy().max_report_count + 1, reports_.size());
-      const ReportingReport* to_evict = FindReportToEvict();
-      DCHECK_NE(nullptr, to_evict);
-      // The newly-added report isn't pending, so even if all other reports are
-      // pending, the cache should have a report to evict.
-      DCHECK(!base::ContainsKey(pending_reports_, to_evict));
-      reports_[to_evict]->outcome = ReportingReport::Outcome::ERASED_EVICTED;
-      RemoveReportInternal(to_evict);
-    }
-
-    context_->NotifyCachedReportsUpdated();
-  }
-
-  void GetReports(
-      std::vector<const ReportingReport*>* reports_out) const override {
-    reports_out->clear();
-    for (const auto& it : reports_) {
-      if (!base::ContainsKey(doomed_reports_, it.first))
-        reports_out->push_back(it.second.get());
-    }
-  }
-
-  base::Value GetReportsAsValue() const override {
-    // Sort the queued reports by origin and timestamp.
-    std::vector<const ReportingReport*> sorted_reports;
-    sorted_reports.reserve(reports_.size());
-    for (const auto& it : reports_) {
-      sorted_reports.push_back(it.second.get());
-    }
-    std::sort(
-        sorted_reports.begin(), sorted_reports.end(),
-        [](const ReportingReport* report1, const ReportingReport* report2) {
-          if (report1->queued < report2->queued)
-            return true;
-          else if (report1->queued > report2->queued)
-            return false;
-          else
-            return report1->url < report2->url;
-        });
-
-    std::vector<base::Value> report_list;
-    for (const ReportingReport* report : sorted_reports) {
-      base::Value report_dict(base::Value::Type::DICTIONARY);
-      report_dict.SetKey("url", base::Value(report->url.spec()));
-      report_dict.SetKey("group", base::Value(report->group));
-      report_dict.SetKey("type", base::Value(report->type));
-      report_dict.SetKey("depth", base::Value(report->depth));
-      report_dict.SetKey(
-          "queued", base::Value(NetLog::TickCountToString(report->queued)));
-      report_dict.SetKey("attempts", base::Value(report->attempts));
-      if (report->body) {
-        report_dict.SetKey("body", report->body->Clone());
-      }
-      if (base::ContainsKey(doomed_reports_, report)) {
-        report_dict.SetKey("status", base::Value("doomed"));
-      } else if (base::ContainsKey(pending_reports_, report)) {
-        report_dict.SetKey("status", base::Value("pending"));
-      } else {
-        report_dict.SetKey("status", base::Value("queued"));
-      }
-      report_list.push_back(std::move(report_dict));
-    }
-    return base::Value(std::move(report_list));
-  }
-
-  void GetNonpendingReports(
-      std::vector<const ReportingReport*>* reports_out) const override {
-    reports_out->clear();
-    for (const auto& it : reports_) {
-      if (!base::ContainsKey(pending_reports_, it.first) &&
-          !base::ContainsKey(doomed_reports_, it.first)) {
-        reports_out->push_back(it.second.get());
-      }
-    }
-  }
-
-  void SetReportsPending(
-      const std::vector<const ReportingReport*>& reports) override {
-    for (const ReportingReport* report : reports) {
-      auto inserted = pending_reports_.insert(report);
-      DCHECK(inserted.second);
-    }
-  }
-
-  void ClearReportsPending(
-      const std::vector<const ReportingReport*>& reports) override {
-    std::vector<const ReportingReport*> reports_to_remove;
-
-    for (const ReportingReport* report : reports) {
-      size_t erased = pending_reports_.erase(report);
-      DCHECK_EQ(1u, erased);
-      if (base::ContainsKey(doomed_reports_, report)) {
-        reports_to_remove.push_back(report);
-        doomed_reports_.erase(report);
-      }
-    }
-
-    for (const ReportingReport* report : reports_to_remove)
-      RemoveReportInternal(report);
-  }
-
-  void IncrementReportsAttempts(
-      const std::vector<const ReportingReport*>& reports) override {
-    for (const ReportingReport* report : reports) {
-      DCHECK(base::ContainsKey(reports_, report));
-      reports_[report]->attempts++;
-    }
-
-    context_->NotifyCachedReportsUpdated();
-  }
-
-  void IncrementEndpointDeliveries(const url::Origin& origin,
-                                   const GURL& endpoint,
-                                   int reports_delivered,
-                                   bool successful) override {
-    const ReportingClient* client =
-        GetClientByOriginAndEndpoint(origin, endpoint);
-    if (client) {
-      auto& metadata = client_metadata_[client];
-      metadata.stats.attempted_uploads++;
-      metadata.stats.attempted_reports += reports_delivered;
-      if (successful) {
-        metadata.stats.successful_uploads++;
-        metadata.stats.successful_reports += reports_delivered;
-      }
-    }
-  }
-
-  void RemoveReports(const std::vector<const ReportingReport*>& reports,
-                     ReportingReport::Outcome outcome) override {
-    for (const ReportingReport* report : reports) {
-      reports_[report]->outcome = outcome;
-      if (base::ContainsKey(pending_reports_, report)) {
-        doomed_reports_.insert(report);
-      } else {
-        DCHECK(!base::ContainsKey(doomed_reports_, report));
-        RemoveReportInternal(report);
-      }
-    }
-
-    context_->NotifyCachedReportsUpdated();
-  }
-
-  void RemoveAllReports(ReportingReport::Outcome outcome) override {
-    std::vector<const ReportingReport*> reports_to_remove;
-    for (auto it = reports_.begin(); it != reports_.end(); ++it) {
-      ReportingReport* report = it->second.get();
-      report->outcome = outcome;
-      if (!base::ContainsKey(pending_reports_, report))
-        reports_to_remove.push_back(report);
-      else
-        doomed_reports_.insert(report);
-    }
-
-    for (const ReportingReport* report : reports_to_remove)
-      RemoveReportInternal(report);
-
-    context_->NotifyCachedReportsUpdated();
-  }
-
-  void SetClient(const url::Origin& origin,
-                 const GURL& endpoint,
-                 ReportingClient::Subdomains subdomains,
-                 const std::string& group,
-                 base::TimeTicks expires,
-                 int priority,
-                 int weight) override {
-    DCHECK(endpoint.SchemeIsCryptographic());
-
-    base::TimeTicks last_used = tick_clock()->NowTicks();
-
-    const ReportingClient* old_client =
-        GetClientByOriginAndEndpoint(origin, endpoint);
-    if (old_client) {
-      last_used = client_metadata_[old_client].last_used;
-      RemoveClient(old_client);
-    }
-
-    AddClient(
-        std::make_unique<ReportingClient>(origin, endpoint, subdomains, group,
-                                          expires, priority, weight),
-        last_used);
-
-    if (client_metadata_.size() > context_->policy().max_client_count) {
-      // There should only ever be one extra client, added above.
-      DCHECK_EQ(context_->policy().max_client_count + 1,
-                client_metadata_.size());
-      // And that shouldn't happen if it was replaced, not added.
-      DCHECK(!old_client);
-      const ReportingClient* to_evict =
-          FindClientToEvict(tick_clock()->NowTicks());
-      DCHECK(to_evict);
-      RemoveClient(to_evict);
-    }
-
-    context_->NotifyCachedClientsUpdated();
-  }
-
-  void MarkClientUsed(const ReportingClient* client) override {
-    DCHECK(client);
-    client_metadata_[client].last_used = tick_clock()->NowTicks();
-  }
-
-  void GetClients(
-      std::vector<const ReportingClient*>* clients_out) const override {
-    clients_out->clear();
-    for (const auto& it : clients_)
-      for (const auto& endpoint_and_client : it.second)
-        clients_out->push_back(endpoint_and_client.second.get());
-  }
-
-  base::Value GetClientsAsValue() const override {
-    std::map<const url::Origin,
-             std::map<const std::string, std::vector<const ReportingClient*>>>
-        clients_by_origin_and_group;
-    for (const auto& it : clients_) {
-      const url::Origin& origin = it.first;
-      for (const auto& endpoint_and_client : it.second) {
-        const ReportingClient* client = endpoint_and_client.second.get();
-        clients_by_origin_and_group[origin][client->group].push_back(client);
-      }
-    }
-
-    std::vector<base::Value> origin_list;
-    for (const auto& it : clients_by_origin_and_group) {
-      const url::Origin& origin = it.first;
-      base::Value origin_dict(base::Value::Type::DICTIONARY);
-      origin_dict.SetKey("origin", base::Value(origin.Serialize()));
-      std::vector<base::Value> group_list;
-      for (const auto& group_and_clients : it.second) {
-        const std::string& group = group_and_clients.first;
-        const std::vector<const ReportingClient*>& clients =
-            group_and_clients.second;
-        base::Value group_dict(base::Value::Type::DICTIONARY);
-        group_dict.SetKey("name", base::Value(group));
-        std::vector<base::Value> endpoint_list;
-        for (const ReportingClient* client : clients) {
-          base::Value endpoint_dict(base::Value::Type::DICTIONARY);
-          // Reporting defines the group as a whole to have an expiration time
-          // and subdomains flag, not the individual endpoints within the group.
-          group_dict.SetKey(
-              "expires",
-              base::Value(NetLog::TickCountToString(client->expires)));
-          group_dict.SetKey("includeSubdomains",
-                            base::Value(client->subdomains ==
-                                        ReportingClient::Subdomains::INCLUDE));
-          endpoint_dict.SetKey("url", base::Value(client->endpoint.spec()));
-          endpoint_dict.SetKey("priority", base::Value(client->priority));
-          endpoint_dict.SetKey("weight", base::Value(client->weight));
-          auto metadata_it = client_metadata_.find(client);
-          if (metadata_it != client_metadata_.end()) {
-            const ClientStatistics& stats = metadata_it->second.stats;
-            base::Value successful_dict(base::Value::Type::DICTIONARY);
-            successful_dict.SetKey("uploads",
-                                   base::Value(stats.successful_uploads));
-            successful_dict.SetKey("reports",
-                                   base::Value(stats.successful_reports));
-            endpoint_dict.SetKey("successful", std::move(successful_dict));
-            base::Value failed_dict(base::Value::Type::DICTIONARY);
-            failed_dict.SetKey("uploads",
-                               base::Value(stats.attempted_uploads -
-                                           stats.successful_uploads));
-            failed_dict.SetKey("reports",
-                               base::Value(stats.attempted_reports -
-                                           stats.successful_reports));
-            endpoint_dict.SetKey("failed", std::move(failed_dict));
-          }
-          endpoint_list.push_back(std::move(endpoint_dict));
-        }
-        group_dict.SetKey("endpoints", base::Value(std::move(endpoint_list)));
-        group_list.push_back(std::move(group_dict));
-      }
-      origin_dict.SetKey("groups", base::Value(std::move(group_list)));
-      origin_list.push_back(std::move(origin_dict));
-    }
-    return base::Value(std::move(origin_list));
-  }
-
-  void GetClientsForOriginAndGroup(
-      const url::Origin& origin,
-      const std::string& group,
-      std::vector<const ReportingClient*>* clients_out) const override {
-    clients_out->clear();
-
-    const auto it = clients_.find(origin);
-    if (it != clients_.end()) {
-      for (const auto& endpoint_and_client : it->second) {
-        if (endpoint_and_client.second->group == group)
-          clients_out->push_back(endpoint_and_client.second.get());
-      }
-    }
-
-    // If no clients were found, try successive superdomain suffixes until a
-    // client with include_subdomains is found or there are no more domain
-    // components left.
-    std::string domain = origin.host();
-    while (clients_out->empty() && !domain.empty()) {
-      GetWildcardClientsForDomainAndGroup(domain, group, clients_out);
-      domain = GetSuperdomain(domain);
-    }
-  }
-
-  // TODO(juliatuttle): Unittests.
-  void GetEndpointsForOrigin(const url::Origin& origin,
-                             std::vector<GURL>* endpoints_out) const override {
-    endpoints_out->clear();
-
-    const auto it = clients_.find(origin);
-    if (it == clients_.end())
-      return;
-
-    for (const auto& endpoint_and_client : it->second)
-      endpoints_out->push_back(endpoint_and_client.first);
-  }
-
-  void RemoveClients(
-      const std::vector<const ReportingClient*>& clients_to_remove) override {
-    for (const ReportingClient* client : clients_to_remove)
-      RemoveClient(client);
-
-    context_->NotifyCachedClientsUpdated();
-  }
-
-  void RemoveClientForOriginAndEndpoint(const url::Origin& origin,
-                                        const GURL& endpoint) override {
-    const ReportingClient* client =
-        GetClientByOriginAndEndpoint(origin, endpoint);
-    if (!client)
-      return;
-    RemoveClient(client);
-
-    context_->NotifyCachedClientsUpdated();
-  }
-
-  void RemoveClientsForEndpoint(const GURL& endpoint) override {
-    std::vector<const ReportingClient*> clients_to_remove;
-
-    for (auto& origin_and_endpoints : clients_)
-      if (base::ContainsKey(origin_and_endpoints.second, endpoint))
-        clients_to_remove.push_back(
-            origin_and_endpoints.second[endpoint].get());
-
-    for (const ReportingClient* client : clients_to_remove)
-      RemoveClient(client);
-
-    if (!clients_to_remove.empty())
-      context_->NotifyCachedClientsUpdated();
-  }
-
-  void RemoveAllClients() override {
-    clients_.clear();
-    wildcard_clients_.clear();
-    client_metadata_.clear();
-
-    context_->NotifyCachedClientsUpdated();
-  }
-
-  ClientStatistics GetStatisticsForOriginAndEndpoint(
-      const url::Origin& origin,
-      const GURL& endpoint) const override {
-    const ReportingClient* client =
-        GetClientByOriginAndEndpoint(origin, endpoint);
-    auto it = client_metadata_.find(client);
-    if (it == client_metadata_.end()) {
-      return ClientStatistics();
-    }
-    return it->second.stats;
-  }
-
-  size_t GetFullReportCountForTesting() const override {
-    return reports_.size();
-  }
-
-  bool IsReportPendingForTesting(const ReportingReport* report) const override {
-    return base::ContainsKey(pending_reports_, report);
-  }
-
-  bool IsReportDoomedForTesting(const ReportingReport* report) const override {
-    return base::ContainsKey(doomed_reports_, report);
-  }
-
- private:
-  void RemoveReportInternal(const ReportingReport* report) {
-    reports_[report]->RecordOutcome(tick_clock()->NowTicks());
-    size_t erased = reports_.erase(report);
-    DCHECK_EQ(1u, erased);
-  }
-
-  const ReportingReport* FindReportToEvict() const {
-    const ReportingReport* earliest_queued = nullptr;
-
-    for (const auto& it : reports_) {
-      const ReportingReport* report = it.first;
-      if (base::ContainsKey(pending_reports_, report))
-        continue;
-      if (!earliest_queued || report->queued < earliest_queued->queued) {
-        earliest_queued = report;
-      }
-    }
-
-    return earliest_queued;
-  }
-
-  void AddClient(std::unique_ptr<ReportingClient> client,
-                 base::TimeTicks last_used) {
-    DCHECK(client);
-
-    url::Origin origin = client->origin;
-    GURL endpoint = client->endpoint;
-
-    auto inserted_metadata = client_metadata_.insert(
-        std::make_pair(client.get(), ClientMetadata{last_used}));
-    DCHECK(inserted_metadata.second);
-
-    if (client->subdomains == ReportingClient::Subdomains::INCLUDE) {
-      const std::string& domain = origin.host();
-      auto inserted_wildcard_client =
-          wildcard_clients_[domain].insert(client.get());
-      DCHECK(inserted_wildcard_client.second);
-    }
-
-    auto inserted_client =
-        clients_[origin].insert(std::make_pair(endpoint, std::move(client)));
-    DCHECK(inserted_client.second);
-  }
-
-  void RemoveClient(const ReportingClient* client) {
-    DCHECK(client);
-
-    url::Origin origin = client->origin;
-    GURL endpoint = client->endpoint;
-
-    if (client->subdomains == ReportingClient::Subdomains::INCLUDE) {
-      const std::string& domain = origin.host();
-      size_t erased_wildcard_client = wildcard_clients_[domain].erase(client);
-      DCHECK_EQ(1u, erased_wildcard_client);
-      if (wildcard_clients_[domain].empty()) {
-        size_t erased_wildcard_domain = wildcard_clients_.erase(domain);
-        DCHECK_EQ(1u, erased_wildcard_domain);
-      }
-    }
-
-    size_t erased_metadata = client_metadata_.erase(client);
-    DCHECK_EQ(1u, erased_metadata);
-
-    size_t erased_endpoint = clients_[origin].erase(endpoint);
-    DCHECK_EQ(1u, erased_endpoint);
-    if (clients_[origin].empty()) {
-      size_t erased_origin = clients_.erase(origin);
-      DCHECK_EQ(1u, erased_origin);
-    }
-  }
-
-  const ReportingClient* GetClientByOriginAndEndpoint(
-      const url::Origin& origin,
-      const GURL& endpoint) const {
-    const auto& origin_it = clients_.find(origin);
-    if (origin_it == clients_.end())
-      return nullptr;
-
-    const auto& endpoint_it = origin_it->second.find(endpoint);
-    if (endpoint_it == origin_it->second.end())
-      return nullptr;
-
-    return endpoint_it->second.get();
-  }
-
-  void GetWildcardClientsForDomainAndGroup(
-      const std::string& domain,
-      const std::string& group,
-      std::vector<const ReportingClient*>* clients_out) const {
-    clients_out->clear();
-
-    auto it = wildcard_clients_.find(domain);
-    if (it == wildcard_clients_.end())
-      return;
-
-    for (const ReportingClient* client : it->second) {
-      DCHECK_EQ(ReportingClient::Subdomains::INCLUDE, client->subdomains);
-      if (client->group == group)
-        clients_out->push_back(client);
-    }
-  }
-
-  const ReportingClient* FindClientToEvict(base::TimeTicks now) const {
-    DCHECK(!client_metadata_.empty());
-
-    const ReportingClient* earliest_used = nullptr;
-    base::TimeTicks earliest_used_last_used;
-    const ReportingClient* earliest_expired = nullptr;
-
-    for (const auto& it : client_metadata_) {
-      const ReportingClient* client = it.first;
-      base::TimeTicks client_last_used = it.second.last_used;
-      if (earliest_used == nullptr ||
-          client_last_used < earliest_used_last_used) {
-        earliest_used = client;
-        earliest_used_last_used = client_last_used;
-      }
-      if (earliest_expired == nullptr ||
-          client->expires < earliest_expired->expires) {
-        earliest_expired = client;
-      }
-    }
-
-    // If there are expired clients, return the earliest-expired.
-    if (earliest_expired->expires < now)
-      return earliest_expired;
-    else
-      return earliest_used;
-  }
-
-  const base::TickClock* tick_clock() { return context_->tick_clock(); }
-
-  ReportingContext* context_;
-
-  // Owns all reports, keyed by const raw pointer for easier lookup.
-  std::unordered_map<const ReportingReport*, std::unique_ptr<ReportingReport>>
-      reports_;
-
-  // Reports that have been marked pending (in use elsewhere and should not be
-  // deleted until no longer pending).
-  std::unordered_set<const ReportingReport*> pending_reports_;
-
-  // Reports that have been marked doomed (would have been deleted, but were
-  // pending when the deletion was requested).
-  std::unordered_set<const ReportingReport*> doomed_reports_;
-
-  // Owns all clients, keyed by origin, then endpoint URL.
-  // (These would be unordered_map, but neither url::Origin nor GURL has a hash
-  // function implemented.)
-  std::map<url::Origin, std::map<GURL, std::unique_ptr<ReportingClient>>>
-      clients_;
-
-  // References but does not own all clients with include_subdomains set, keyed
-  // by domain name.
-  std::unordered_map<std::string, std::unordered_set<const ReportingClient*>>
-      wildcard_clients_;
-
-  // The time that each client has last been used.
-  std::unordered_map<const ReportingClient*, ClientMetadata> client_metadata_;
-};
-
-}  // namespace
-
 // static
 std::unique_ptr<ReportingCache> ReportingCache::Create(
     ReportingContext* context) {
diff --git a/net/reporting/reporting_cache_impl.cc b/net/reporting/reporting_cache_impl.cc
new file mode 100644
index 0000000..7096ee7
--- /dev/null
+++ b/net/reporting/reporting_cache_impl.cc
@@ -0,0 +1,586 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/reporting/reporting_cache_impl.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/time/clock.h"
+#include "base/time/tick_clock.h"
+#include "net/log/net_log.h"
+
+namespace net {
+
+namespace {
+
+// Returns the superdomain of a given domain, or the empty string if the given
+// domain is just a single label. Note that this does not take into account
+// anything like the Public Suffix List, so the superdomain may end up being a
+// bare TLD.
+//
+// Examples:
+//
+// GetSuperdomain("assets.example.com") -> "example.com"
+// GetSuperdomain("example.net") -> "net"
+// GetSuperdomain("littlebox") -> ""
+std::string GetSuperdomain(const std::string& domain) {
+  size_t dot_pos = domain.find('.');
+  if (dot_pos == std::string::npos)
+    return "";
+
+  return domain.substr(dot_pos + 1);
+}
+
+}  // namespace
+
+ReportingCacheImpl::ReportingCacheImpl(ReportingContext* context)
+    : context_(context) {
+  DCHECK(context_);
+}
+
+ReportingCacheImpl::~ReportingCacheImpl() {
+  base::TimeTicks now = tick_clock()->NowTicks();
+
+  // Mark all undoomed reports as erased at shutdown, and record outcomes of
+  // all remaining reports (doomed or not).
+  for (auto it = reports_.begin(); it != reports_.end(); ++it) {
+    ReportingReport* report = it->second.get();
+    if (!base::ContainsKey(doomed_reports_, report))
+      report->outcome = ReportingReport::Outcome::ERASED_REPORTING_SHUT_DOWN;
+    report->RecordOutcome(now);
+  }
+
+  reports_.clear();
+}
+
+void ReportingCacheImpl::AddReport(const GURL& url,
+                                   const std::string& user_agent,
+                                   const std::string& group,
+                                   const std::string& type,
+                                   std::unique_ptr<const base::Value> body,
+                                   int depth,
+                                   base::TimeTicks queued,
+                                   int attempts) {
+  auto report = std::make_unique<ReportingReport>(
+      url, user_agent, group, type, std::move(body), depth, queued, attempts);
+
+  auto inserted =
+      reports_.insert(std::make_pair(report.get(), std::move(report)));
+  DCHECK(inserted.second);
+
+  if (reports_.size() > context_->policy().max_report_count) {
+    // There should be at most one extra report (the one added above).
+    DCHECK_EQ(context_->policy().max_report_count + 1, reports_.size());
+    const ReportingReport* to_evict = FindReportToEvict();
+    DCHECK_NE(nullptr, to_evict);
+    // The newly-added report isn't pending, so even if all other reports are
+    // pending, the cache should have a report to evict.
+    DCHECK(!base::ContainsKey(pending_reports_, to_evict));
+    reports_[to_evict]->outcome = ReportingReport::Outcome::ERASED_EVICTED;
+    RemoveReportInternal(to_evict);
+  }
+
+  context_->NotifyCachedReportsUpdated();
+}
+
+void ReportingCacheImpl::GetReports(
+    std::vector<const ReportingReport*>* reports_out) const {
+  reports_out->clear();
+  for (const auto& it : reports_) {
+    if (!base::ContainsKey(doomed_reports_, it.first))
+      reports_out->push_back(it.second.get());
+  }
+}
+
+base::Value ReportingCacheImpl::GetReportsAsValue() const {
+  // Sort the queued reports by origin and timestamp.
+  std::vector<const ReportingReport*> sorted_reports;
+  sorted_reports.reserve(reports_.size());
+  for (const auto& it : reports_) {
+    sorted_reports.push_back(it.second.get());
+  }
+  std::sort(sorted_reports.begin(), sorted_reports.end(),
+            [](const ReportingReport* report1, const ReportingReport* report2) {
+              if (report1->queued < report2->queued)
+                return true;
+              else if (report1->queued > report2->queued)
+                return false;
+              else
+                return report1->url < report2->url;
+            });
+
+  std::vector<base::Value> report_list;
+  for (const ReportingReport* report : sorted_reports) {
+    base::Value report_dict(base::Value::Type::DICTIONARY);
+    report_dict.SetKey("url", base::Value(report->url.spec()));
+    report_dict.SetKey("group", base::Value(report->group));
+    report_dict.SetKey("type", base::Value(report->type));
+    report_dict.SetKey("depth", base::Value(report->depth));
+    report_dict.SetKey("queued",
+                       base::Value(NetLog::TickCountToString(report->queued)));
+    report_dict.SetKey("attempts", base::Value(report->attempts));
+    if (report->body) {
+      report_dict.SetKey("body", report->body->Clone());
+    }
+    if (base::ContainsKey(doomed_reports_, report)) {
+      report_dict.SetKey("status", base::Value("doomed"));
+    } else if (base::ContainsKey(pending_reports_, report)) {
+      report_dict.SetKey("status", base::Value("pending"));
+    } else {
+      report_dict.SetKey("status", base::Value("queued"));
+    }
+    report_list.push_back(std::move(report_dict));
+  }
+  return base::Value(std::move(report_list));
+}
+
+void ReportingCacheImpl::GetNonpendingReports(
+    std::vector<const ReportingReport*>* reports_out) const {
+  reports_out->clear();
+  for (const auto& it : reports_) {
+    if (!base::ContainsKey(pending_reports_, it.first) &&
+        !base::ContainsKey(doomed_reports_, it.first)) {
+      reports_out->push_back(it.second.get());
+    }
+  }
+}
+
+void ReportingCacheImpl::SetReportsPending(
+    const std::vector<const ReportingReport*>& reports) {
+  for (const ReportingReport* report : reports) {
+    auto inserted = pending_reports_.insert(report);
+    DCHECK(inserted.second);
+  }
+}
+
+void ReportingCacheImpl::ClearReportsPending(
+    const std::vector<const ReportingReport*>& reports) {
+  std::vector<const ReportingReport*> reports_to_remove;
+
+  for (const ReportingReport* report : reports) {
+    size_t erased = pending_reports_.erase(report);
+    DCHECK_EQ(1u, erased);
+    if (base::ContainsKey(doomed_reports_, report)) {
+      reports_to_remove.push_back(report);
+      doomed_reports_.erase(report);
+    }
+  }
+
+  for (const ReportingReport* report : reports_to_remove)
+    RemoveReportInternal(report);
+}
+
+void ReportingCacheImpl::IncrementReportsAttempts(
+    const std::vector<const ReportingReport*>& reports) {
+  for (const ReportingReport* report : reports) {
+    DCHECK(base::ContainsKey(reports_, report));
+    reports_[report]->attempts++;
+  }
+
+  context_->NotifyCachedReportsUpdated();
+}
+
+void ReportingCacheImpl::IncrementEndpointDeliveries(const url::Origin& origin,
+                                                     const GURL& endpoint,
+                                                     int reports_delivered,
+                                                     bool successful) {
+  const ReportingClient* client =
+      GetClientByOriginAndEndpoint(origin, endpoint);
+  if (client) {
+    auto& metadata = client_metadata_[client];
+    metadata.stats.attempted_uploads++;
+    metadata.stats.attempted_reports += reports_delivered;
+    if (successful) {
+      metadata.stats.successful_uploads++;
+      metadata.stats.successful_reports += reports_delivered;
+    }
+  }
+}
+
+void ReportingCacheImpl::RemoveReports(
+    const std::vector<const ReportingReport*>& reports,
+    ReportingReport::Outcome outcome) {
+  for (const ReportingReport* report : reports) {
+    reports_[report]->outcome = outcome;
+    if (base::ContainsKey(pending_reports_, report)) {
+      doomed_reports_.insert(report);
+    } else {
+      DCHECK(!base::ContainsKey(doomed_reports_, report));
+      RemoveReportInternal(report);
+    }
+  }
+
+  context_->NotifyCachedReportsUpdated();
+}
+
+void ReportingCacheImpl::RemoveAllReports(ReportingReport::Outcome outcome) {
+  std::vector<const ReportingReport*> reports_to_remove;
+  for (auto it = reports_.begin(); it != reports_.end(); ++it) {
+    ReportingReport* report = it->second.get();
+    report->outcome = outcome;
+    if (!base::ContainsKey(pending_reports_, report))
+      reports_to_remove.push_back(report);
+    else
+      doomed_reports_.insert(report);
+  }
+
+  for (const ReportingReport* report : reports_to_remove)
+    RemoveReportInternal(report);
+
+  context_->NotifyCachedReportsUpdated();
+}
+
+void ReportingCacheImpl::SetClient(const url::Origin& origin,
+                                   const GURL& endpoint,
+                                   ReportingClient::Subdomains subdomains,
+                                   const std::string& group,
+                                   base::TimeTicks expires,
+                                   int priority,
+                                   int weight) {
+  DCHECK(endpoint.SchemeIsCryptographic());
+
+  base::TimeTicks last_used = tick_clock()->NowTicks();
+
+  const ReportingClient* old_client =
+      GetClientByOriginAndEndpoint(origin, endpoint);
+  if (old_client) {
+    last_used = client_metadata_[old_client].last_used;
+    RemoveClient(old_client);
+  }
+
+  AddClient(std::make_unique<ReportingClient>(origin, endpoint, subdomains,
+                                              group, expires, priority, weight),
+            last_used);
+
+  if (client_metadata_.size() > context_->policy().max_client_count) {
+    // There should only ever be one extra client, added above.
+    DCHECK_EQ(context_->policy().max_client_count + 1, client_metadata_.size());
+    // And that shouldn't happen if it was replaced, not added.
+    DCHECK(!old_client);
+    const ReportingClient* to_evict =
+        FindClientToEvict(tick_clock()->NowTicks());
+    DCHECK(to_evict);
+    RemoveClient(to_evict);
+  }
+
+  context_->NotifyCachedClientsUpdated();
+}
+
+void ReportingCacheImpl::MarkClientUsed(const ReportingClient* client) {
+  DCHECK(client);
+  client_metadata_[client].last_used = tick_clock()->NowTicks();
+}
+
+void ReportingCacheImpl::GetClients(
+    std::vector<const ReportingClient*>* clients_out) const {
+  clients_out->clear();
+  for (const auto& it : clients_)
+    for (const auto& endpoint_and_client : it.second)
+      clients_out->push_back(endpoint_and_client.second.get());
+}
+
+base::Value ReportingCacheImpl::GetClientsAsValue() const {
+  std::map<const url::Origin,
+           std::map<const std::string, std::vector<const ReportingClient*>>>
+      clients_by_origin_and_group;
+  for (const auto& it : clients_) {
+    const url::Origin& origin = it.first;
+    for (const auto& endpoint_and_client : it.second) {
+      const ReportingClient* client = endpoint_and_client.second.get();
+      clients_by_origin_and_group[origin][client->group].push_back(client);
+    }
+  }
+
+  std::vector<base::Value> origin_list;
+  for (const auto& it : clients_by_origin_and_group) {
+    const url::Origin& origin = it.first;
+    base::Value origin_dict(base::Value::Type::DICTIONARY);
+    origin_dict.SetKey("origin", base::Value(origin.Serialize()));
+    std::vector<base::Value> group_list;
+    for (const auto& group_and_clients : it.second) {
+      const std::string& group = group_and_clients.first;
+      const std::vector<const ReportingClient*>& clients =
+          group_and_clients.second;
+      base::Value group_dict(base::Value::Type::DICTIONARY);
+      group_dict.SetKey("name", base::Value(group));
+      std::vector<base::Value> endpoint_list;
+      for (const ReportingClient* client : clients) {
+        base::Value endpoint_dict(base::Value::Type::DICTIONARY);
+        // Reporting defines the group as a whole to have an expiration time
+        // and subdomains flag, not the individual endpoints within the group.
+        group_dict.SetKey(
+            "expires", base::Value(NetLog::TickCountToString(client->expires)));
+        group_dict.SetKey("includeSubdomains",
+                          base::Value(client->subdomains ==
+                                      ReportingClient::Subdomains::INCLUDE));
+        endpoint_dict.SetKey("url", base::Value(client->endpoint.spec()));
+        endpoint_dict.SetKey("priority", base::Value(client->priority));
+        endpoint_dict.SetKey("weight", base::Value(client->weight));
+        auto metadata_it = client_metadata_.find(client);
+        if (metadata_it != client_metadata_.end()) {
+          const ClientStatistics& stats = metadata_it->second.stats;
+          base::Value successful_dict(base::Value::Type::DICTIONARY);
+          successful_dict.SetKey("uploads",
+                                 base::Value(stats.successful_uploads));
+          successful_dict.SetKey("reports",
+                                 base::Value(stats.successful_reports));
+          endpoint_dict.SetKey("successful", std::move(successful_dict));
+          base::Value failed_dict(base::Value::Type::DICTIONARY);
+          failed_dict.SetKey("uploads", base::Value(stats.attempted_uploads -
+                                                    stats.successful_uploads));
+          failed_dict.SetKey("reports", base::Value(stats.attempted_reports -
+                                                    stats.successful_reports));
+          endpoint_dict.SetKey("failed", std::move(failed_dict));
+        }
+        endpoint_list.push_back(std::move(endpoint_dict));
+      }
+      group_dict.SetKey("endpoints", base::Value(std::move(endpoint_list)));
+      group_list.push_back(std::move(group_dict));
+    }
+    origin_dict.SetKey("groups", base::Value(std::move(group_list)));
+    origin_list.push_back(std::move(origin_dict));
+  }
+  return base::Value(std::move(origin_list));
+}
+
+void ReportingCacheImpl::GetClientsForOriginAndGroup(
+    const url::Origin& origin,
+    const std::string& group,
+    std::vector<const ReportingClient*>* clients_out) const {
+  clients_out->clear();
+
+  const auto it = clients_.find(origin);
+  if (it != clients_.end()) {
+    for (const auto& endpoint_and_client : it->second) {
+      if (endpoint_and_client.second->group == group)
+        clients_out->push_back(endpoint_and_client.second.get());
+    }
+  }
+
+  // If no clients were found, try successive superdomain suffixes until a
+  // client with include_subdomains is found or there are no more domain
+  // components left.
+  std::string domain = origin.host();
+  while (clients_out->empty() && !domain.empty()) {
+    GetWildcardClientsForDomainAndGroup(domain, group, clients_out);
+    domain = GetSuperdomain(domain);
+  }
+}
+
+// TODO(juliatuttle): Unittests.
+void ReportingCacheImpl::GetEndpointsForOrigin(
+    const url::Origin& origin,
+    std::vector<GURL>* endpoints_out) const {
+  endpoints_out->clear();
+
+  const auto it = clients_.find(origin);
+  if (it == clients_.end())
+    return;
+
+  for (const auto& endpoint_and_client : it->second)
+    endpoints_out->push_back(endpoint_and_client.first);
+}
+
+void ReportingCacheImpl::RemoveClients(
+    const std::vector<const ReportingClient*>& clients_to_remove) {
+  for (const ReportingClient* client : clients_to_remove)
+    RemoveClient(client);
+
+  context_->NotifyCachedClientsUpdated();
+}
+
+void ReportingCacheImpl::RemoveClientForOriginAndEndpoint(
+    const url::Origin& origin,
+    const GURL& endpoint) {
+  const ReportingClient* client =
+      GetClientByOriginAndEndpoint(origin, endpoint);
+  if (!client)
+    return;
+  RemoveClient(client);
+
+  context_->NotifyCachedClientsUpdated();
+}
+
+void ReportingCacheImpl::RemoveClientsForEndpoint(const GURL& endpoint) {
+  std::vector<const ReportingClient*> clients_to_remove;
+
+  for (auto& origin_and_endpoints : clients_)
+    if (base::ContainsKey(origin_and_endpoints.second, endpoint))
+      clients_to_remove.push_back(origin_and_endpoints.second[endpoint].get());
+
+  for (const ReportingClient* client : clients_to_remove)
+    RemoveClient(client);
+
+  if (!clients_to_remove.empty())
+    context_->NotifyCachedClientsUpdated();
+}
+
+void ReportingCacheImpl::RemoveAllClients() {
+  clients_.clear();
+  wildcard_clients_.clear();
+  client_metadata_.clear();
+
+  context_->NotifyCachedClientsUpdated();
+}
+
+ReportingCache::ClientStatistics
+ReportingCacheImpl::GetStatisticsForOriginAndEndpoint(
+    const url::Origin& origin,
+    const GURL& endpoint) const {
+  const ReportingClient* client =
+      GetClientByOriginAndEndpoint(origin, endpoint);
+  auto it = client_metadata_.find(client);
+  if (it == client_metadata_.end()) {
+    return ClientStatistics();
+  }
+  return it->second.stats;
+}
+
+size_t ReportingCacheImpl::GetFullReportCountForTesting() const {
+  return reports_.size();
+}
+
+bool ReportingCacheImpl::IsReportPendingForTesting(
+    const ReportingReport* report) const {
+  return base::ContainsKey(pending_reports_, report);
+}
+
+bool ReportingCacheImpl::IsReportDoomedForTesting(
+    const ReportingReport* report) const {
+  return base::ContainsKey(doomed_reports_, report);
+}
+
+void ReportingCacheImpl::RemoveReportInternal(const ReportingReport* report) {
+  reports_[report]->RecordOutcome(tick_clock()->NowTicks());
+  size_t erased = reports_.erase(report);
+  DCHECK_EQ(1u, erased);
+}
+
+const ReportingReport* ReportingCacheImpl::FindReportToEvict() const {
+  const ReportingReport* earliest_queued = nullptr;
+
+  for (const auto& it : reports_) {
+    const ReportingReport* report = it.first;
+    if (base::ContainsKey(pending_reports_, report))
+      continue;
+    if (!earliest_queued || report->queued < earliest_queued->queued) {
+      earliest_queued = report;
+    }
+  }
+
+  return earliest_queued;
+}
+
+void ReportingCacheImpl::AddClient(std::unique_ptr<ReportingClient> client,
+                                   base::TimeTicks last_used) {
+  DCHECK(client);
+
+  url::Origin origin = client->origin;
+  GURL endpoint = client->endpoint;
+
+  auto inserted_metadata = client_metadata_.insert(
+      std::make_pair(client.get(), ClientMetadata{last_used}));
+  DCHECK(inserted_metadata.second);
+
+  if (client->subdomains == ReportingClient::Subdomains::INCLUDE) {
+    const std::string& domain = origin.host();
+    auto inserted_wildcard_client =
+        wildcard_clients_[domain].insert(client.get());
+    DCHECK(inserted_wildcard_client.second);
+  }
+
+  auto inserted_client =
+      clients_[origin].insert(std::make_pair(endpoint, std::move(client)));
+  DCHECK(inserted_client.second);
+}
+
+void ReportingCacheImpl::RemoveClient(const ReportingClient* client) {
+  DCHECK(client);
+
+  url::Origin origin = client->origin;
+  GURL endpoint = client->endpoint;
+
+  if (client->subdomains == ReportingClient::Subdomains::INCLUDE) {
+    const std::string& domain = origin.host();
+    size_t erased_wildcard_client = wildcard_clients_[domain].erase(client);
+    DCHECK_EQ(1u, erased_wildcard_client);
+    if (wildcard_clients_[domain].empty()) {
+      size_t erased_wildcard_domain = wildcard_clients_.erase(domain);
+      DCHECK_EQ(1u, erased_wildcard_domain);
+    }
+  }
+
+  size_t erased_metadata = client_metadata_.erase(client);
+  DCHECK_EQ(1u, erased_metadata);
+
+  size_t erased_endpoint = clients_[origin].erase(endpoint);
+  DCHECK_EQ(1u, erased_endpoint);
+  if (clients_[origin].empty()) {
+    size_t erased_origin = clients_.erase(origin);
+    DCHECK_EQ(1u, erased_origin);
+  }
+}
+
+const ReportingClient* ReportingCacheImpl::GetClientByOriginAndEndpoint(
+    const url::Origin& origin,
+    const GURL& endpoint) const {
+  const auto& origin_it = clients_.find(origin);
+  if (origin_it == clients_.end())
+    return nullptr;
+
+  const auto& endpoint_it = origin_it->second.find(endpoint);
+  if (endpoint_it == origin_it->second.end())
+    return nullptr;
+
+  return endpoint_it->second.get();
+}
+
+void ReportingCacheImpl::GetWildcardClientsForDomainAndGroup(
+    const std::string& domain,
+    const std::string& group,
+    std::vector<const ReportingClient*>* clients_out) const {
+  clients_out->clear();
+
+  auto it = wildcard_clients_.find(domain);
+  if (it == wildcard_clients_.end())
+    return;
+
+  for (const ReportingClient* client : it->second) {
+    DCHECK_EQ(ReportingClient::Subdomains::INCLUDE, client->subdomains);
+    if (client->group == group)
+      clients_out->push_back(client);
+  }
+}
+
+const ReportingClient* ReportingCacheImpl::FindClientToEvict(
+    base::TimeTicks now) const {
+  DCHECK(!client_metadata_.empty());
+
+  const ReportingClient* earliest_used = nullptr;
+  base::TimeTicks earliest_used_last_used;
+  const ReportingClient* earliest_expired = nullptr;
+
+  for (const auto& it : client_metadata_) {
+    const ReportingClient* client = it.first;
+    base::TimeTicks client_last_used = it.second.last_used;
+    if (earliest_used == nullptr ||
+        client_last_used < earliest_used_last_used) {
+      earliest_used = client;
+      earliest_used_last_used = client_last_used;
+    }
+    if (earliest_expired == nullptr ||
+        client->expires < earliest_expired->expires) {
+      earliest_expired = client;
+    }
+  }
+
+  // If there are expired clients, return the earliest-expired.
+  if (earliest_expired->expires < now)
+    return earliest_expired;
+  else
+    return earliest_used;
+}
+
+}  // namespace net
diff --git a/net/reporting/reporting_cache_impl.h b/net/reporting/reporting_cache_impl.h
new file mode 100644
index 0000000..9ee2291
--- /dev/null
+++ b/net/reporting/reporting_cache_impl.h
@@ -0,0 +1,152 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef NET_REPORTING_REPORTING_CACHE_IMPL_H_
+#define NET_REPORTING_REPORTING_CACHE_IMPL_H_
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/values.h"
+#include "net/reporting/reporting_cache.h"
+#include "net/reporting/reporting_client.h"
+#include "net/reporting/reporting_context.h"
+#include "net/reporting/reporting_report.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace net {
+
+class ReportingCacheImpl : public ReportingCache {
+ public:
+  explicit ReportingCacheImpl(ReportingContext* context);
+
+  ~ReportingCacheImpl() override;
+
+  // ReportingCache implementation
+  void AddReport(const GURL& url,
+                 const std::string& user_agent,
+                 const std::string& group,
+                 const std::string& type,
+                 std::unique_ptr<const base::Value> body,
+                 int depth,
+                 base::TimeTicks queued,
+                 int attempts) override;
+  void GetReports(
+      std::vector<const ReportingReport*>* reports_out) const override;
+  base::Value GetReportsAsValue() const override;
+  void GetNonpendingReports(
+      std::vector<const ReportingReport*>* reports_out) const override;
+  void SetReportsPending(
+      const std::vector<const ReportingReport*>& reports) override;
+  void ClearReportsPending(
+      const std::vector<const ReportingReport*>& reports) override;
+  void IncrementReportsAttempts(
+      const std::vector<const ReportingReport*>& reports) override;
+  void IncrementEndpointDeliveries(const url::Origin& origin,
+                                   const GURL& endpoint,
+                                   int reports_delivered,
+                                   bool successful) override;
+  void RemoveReports(const std::vector<const ReportingReport*>& reports,
+                     ReportingReport::Outcome outcome) override;
+  void RemoveAllReports(ReportingReport::Outcome outcome) override;
+  void SetClient(const url::Origin& origin,
+                 const GURL& endpoint,
+                 ReportingClient::Subdomains subdomains,
+                 const std::string& group,
+                 base::TimeTicks expires,
+                 int priority,
+                 int weight) override;
+  void MarkClientUsed(const ReportingClient* client) override;
+  void GetClients(
+      std::vector<const ReportingClient*>* clients_out) const override;
+  base::Value GetClientsAsValue() const override;
+  void GetClientsForOriginAndGroup(
+      const url::Origin& origin,
+      const std::string& group,
+      std::vector<const ReportingClient*>* clients_out) const override;
+  void GetEndpointsForOrigin(const url::Origin& origin,
+                             std::vector<GURL>* endpoints_out) const override;
+  void RemoveClients(
+      const std::vector<const ReportingClient*>& clients_to_remove) override;
+  void RemoveClientForOriginAndEndpoint(const url::Origin& origin,
+                                        const GURL& endpoint) override;
+  void RemoveClientsForEndpoint(const GURL& endpoint) override;
+  void RemoveAllClients() override;
+  ClientStatistics GetStatisticsForOriginAndEndpoint(
+      const url::Origin& origin,
+      const GURL& endpoint) const override;
+  size_t GetFullReportCountForTesting() const override;
+  bool IsReportPendingForTesting(const ReportingReport* report) const override;
+  bool IsReportDoomedForTesting(const ReportingReport* report) const override;
+
+ private:
+  struct ClientMetadata {
+    base::TimeTicks last_used;
+    ReportingCache::ClientStatistics stats;
+  };
+
+  void RemoveReportInternal(const ReportingReport* report);
+
+  const ReportingReport* FindReportToEvict() const;
+
+  void AddClient(std::unique_ptr<ReportingClient> client,
+                 base::TimeTicks last_used);
+
+  void RemoveClient(const ReportingClient* client);
+
+  const ReportingClient* GetClientByOriginAndEndpoint(
+      const url::Origin& origin,
+      const GURL& endpoint) const;
+
+  void GetWildcardClientsForDomainAndGroup(
+      const std::string& domain,
+      const std::string& group,
+      std::vector<const ReportingClient*>* clients_out) const;
+
+  const ReportingClient* FindClientToEvict(base::TimeTicks now) const;
+
+  const base::TickClock* tick_clock() { return context_->tick_clock(); }
+
+  ReportingContext* context_;
+
+  // Owns all reports, keyed by const raw pointer for easier lookup.
+  std::unordered_map<const ReportingReport*, std::unique_ptr<ReportingReport>>
+      reports_;
+
+  // Reports that have been marked pending (in use elsewhere and should not be
+  // deleted until no longer pending).
+  std::unordered_set<const ReportingReport*> pending_reports_;
+
+  // Reports that have been marked doomed (would have been deleted, but were
+  // pending when the deletion was requested).
+  std::unordered_set<const ReportingReport*> doomed_reports_;
+
+  // Owns all clients, keyed by origin, then endpoint URL.
+  // (These would be unordered_map, but neither url::Origin nor GURL has a hash
+  // function implemented.)
+  std::map<url::Origin, std::map<GURL, std::unique_ptr<ReportingClient>>>
+      clients_;
+
+  // References but does not own all clients with include_subdomains set, keyed
+  // by domain name.
+  std::unordered_map<std::string, std::unordered_set<const ReportingClient*>>
+      wildcard_clients_;
+
+  // The time that each client has last been used.
+  std::unordered_map<const ReportingClient*, ClientMetadata> client_metadata_;
+
+  DISALLOW_COPY_AND_ASSIGN(ReportingCacheImpl);
+};
+
+}  // namespace net
+
+#endif  // NET_REPORTING_REPORTING_CACHE_IMPL_H_
diff --git a/testing/BUILD.gn b/testing/BUILD.gn
index fb17e192..34cf492 100644
--- a/testing/BUILD.gn
+++ b/testing/BUILD.gn
@@ -31,7 +31,6 @@
   data = [
     "//testing/scripts/common.py",
     "//testing/scripts/run_performance_tests.py",
-    "//testing/scripts/run_telemetry_benchmark_as_googletest.py",
     "//tools/perf/generate_legacy_perf_dashboard_json.py",
   ]
 
diff --git a/testing/buildbot/chromium.fyi.json b/testing/buildbot/chromium.fyi.json
index 4b6b4e0c..3b13615 100644
--- a/testing/buildbot/chromium.fyi.json
+++ b/testing/buildbot/chromium.fyi.json
@@ -1910,8 +1910,7 @@
       },
       {
         "args": [
-          "--enable-features=NetworkService",
-          "--test-launcher-filter-file=../../testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter"
+          "--enable-features=NetworkService"
         ],
         "name": "network_service_browser_tests",
         "swarming": {
@@ -4023,12 +4022,6 @@
         "test": "cast_runner_integration_tests"
       },
       {
-        "swarming": {
-          "can_use_on_swarming_builders": true
-        },
-        "test": "cast_runner_unittests"
-      },
-      {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter"
         ],
@@ -4186,17 +4179,6 @@
         "test": "cast_runner_integration_tests"
       },
       {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "kvm": "1"
-            }
-          ]
-        },
-        "test": "cast_runner_unittests"
-      },
-      {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter"
         ],
@@ -4444,17 +4426,6 @@
         "test": "cast_runner_integration_tests"
       },
       {
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "kvm": "1"
-            }
-          ]
-        },
-        "test": "cast_runner_unittests"
-      },
-      {
         "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter"
         ],
diff --git a/testing/buildbot/chromium.gpu.fyi.json b/testing/buildbot/chromium.gpu.fyi.json
index 14ac270..fe4da53 100644
--- a/testing/buildbot/chromium.gpu.fyi.json
+++ b/testing/buildbot/chromium.gpu.fyi.json
@@ -4803,7 +4803,16 @@
           ],
           "shards": 4
         },
-        "test": "dawn_end2end_tests"
+        "test": "dawn_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ]
   },
@@ -4950,7 +4959,16 @@
           ],
           "shards": 4
         },
-        "test": "angle_end2end_tests"
+        "test": "angle_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -4969,7 +4987,16 @@
             }
           ]
         },
-        "test": "angle_unittests"
+        "test": "angle_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -4986,7 +5013,16 @@
             }
           ]
         },
-        "test": "angle_white_box_tests"
+        "test": "angle_white_box_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -5004,7 +5040,16 @@
           ],
           "shards": 4
         },
-        "test": "dawn_end2end_tests"
+        "test": "dawn_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -5021,7 +5066,16 @@
             }
           ]
         },
-        "test": "gl_tests"
+        "test": "gl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -5038,7 +5092,16 @@
             }
           ]
         },
-        "test": "gl_unittests"
+        "test": "gl_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -5054,7 +5117,16 @@
             }
           ]
         },
-        "test": "gles2_conform_test"
+        "test": "gles2_conform_test",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "swarming": {
@@ -5067,7 +5139,16 @@
             }
           ]
         },
-        "test": "swiftshader_unittests"
+        "test": "swiftshader_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
@@ -5093,6 +5174,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5117,6 +5207,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5141,6 +5240,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5165,6 +5273,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5193,6 +5310,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5224,6 +5350,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5263,6 +5398,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5288,6 +5432,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5312,6 +5465,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5337,6 +5499,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -5362,6 +5533,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -6430,7 +6610,16 @@
           ],
           "shards": 4
         },
-        "test": "angle_end2end_tests"
+        "test": "angle_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -6449,7 +6638,16 @@
             }
           ]
         },
-        "test": "angle_unittests"
+        "test": "angle_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -6466,7 +6664,16 @@
             }
           ]
         },
-        "test": "angle_white_box_tests"
+        "test": "angle_white_box_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -6484,7 +6691,16 @@
           ],
           "shards": 4
         },
-        "test": "dawn_end2end_tests"
+        "test": "dawn_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -6501,7 +6717,16 @@
             }
           ]
         },
-        "test": "gl_tests"
+        "test": "gl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -6518,7 +6743,16 @@
             }
           ]
         },
-        "test": "gl_unittests"
+        "test": "gl_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -6534,7 +6768,16 @@
             }
           ]
         },
-        "test": "gles2_conform_test"
+        "test": "gles2_conform_test",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "swarming": {
@@ -6547,7 +6790,16 @@
             }
           ]
         },
-        "test": "swiftshader_unittests"
+        "test": "swiftshader_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
@@ -6574,6 +6826,15 @@
               "pool": "Chrome-GPU"
             }
           ]
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -7596,7 +7857,16 @@
           ],
           "shards": 4
         },
-        "test": "angle_end2end_tests"
+        "test": "angle_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7615,7 +7885,16 @@
             }
           ]
         },
-        "test": "angle_unittests"
+        "test": "angle_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7632,7 +7911,16 @@
             }
           ]
         },
-        "test": "angle_white_box_tests"
+        "test": "angle_white_box_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7653,7 +7941,16 @@
             }
           ]
         },
-        "test": "browser_tests"
+        "test": "browser_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7671,7 +7968,16 @@
           ],
           "shards": 4
         },
-        "test": "dawn_end2end_tests"
+        "test": "dawn_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7688,7 +7994,16 @@
             }
           ]
         },
-        "test": "gl_tests"
+        "test": "gl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7705,7 +8020,16 @@
             }
           ]
         },
-        "test": "gl_unittests"
+        "test": "gl_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -7721,7 +8045,16 @@
             }
           ]
         },
-        "test": "gles2_conform_test"
+        "test": "gles2_conform_test",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "swarming": {
@@ -7734,7 +8067,16 @@
             }
           ]
         },
-        "test": "swiftshader_unittests"
+        "test": "swiftshader_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
@@ -7761,6 +8103,15 @@
               "pool": "Chrome-GPU"
             }
           ]
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7785,6 +8136,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7809,6 +8169,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7833,6 +8202,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7857,6 +8235,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7885,6 +8272,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7916,6 +8312,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7955,6 +8360,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -7980,6 +8394,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -8004,6 +8427,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -8031,6 +8463,15 @@
           ],
           "idempotent": false,
           "shards": 20
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -8058,6 +8499,15 @@
           ],
           "idempotent": false,
           "shards": 20
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -8083,6 +8533,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -8108,6 +8567,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -8156,7 +8624,16 @@
           ],
           "shards": 4
         },
-        "test": "angle_deqp_egl_tests"
+        "test": "angle_deqp_egl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -8176,7 +8653,16 @@
           ],
           "shards": 4
         },
-        "test": "angle_deqp_gles2_tests"
+        "test": "angle_deqp_gles2_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -8196,7 +8682,16 @@
           ],
           "shards": 6
         },
-        "test": "angle_deqp_gles31_tests"
+        "test": "angle_deqp_gles31_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -8216,7 +8711,16 @@
           ],
           "shards": 12
         },
-        "test": "angle_deqp_gles3_tests"
+        "test": "angle_deqp_gles3_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ]
   },
@@ -15200,7 +15704,16 @@
           ],
           "shards": 4
         },
-        "test": "angle_end2end_tests"
+        "test": "angle_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -15217,7 +15730,16 @@
             }
           ]
         },
-        "test": "angle_white_box_tests"
+        "test": "angle_white_box_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -15235,7 +15757,16 @@
           ],
           "shards": 4
         },
-        "test": "dawn_end2end_tests"
+        "test": "dawn_end2end_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -15251,7 +15782,16 @@
             }
           ]
         },
-        "test": "gles2_conform_test"
+        "test": "gles2_conform_test",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "swarming": {
@@ -15264,7 +15804,16 @@
             }
           ]
         },
-        "test": "swiftshader_unittests"
+        "test": "swiftshader_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
@@ -15291,6 +15840,15 @@
               "pool": "Chrome-GPU"
             }
           ]
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15319,6 +15877,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15343,6 +15910,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15370,6 +15946,15 @@
           ],
           "idempotent": false,
           "shards": 20
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -15395,6 +15980,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
diff --git a/testing/buildbot/chromium.gpu.json b/testing/buildbot/chromium.gpu.json
index cab33d5..bd3151e 100644
--- a/testing/buildbot/chromium.gpu.json
+++ b/testing/buildbot/chromium.gpu.json
@@ -299,7 +299,16 @@
             }
           ]
         },
-        "test": "angle_unittests"
+        "test": "angle_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -316,7 +325,16 @@
             }
           ]
         },
-        "test": "gl_tests"
+        "test": "gl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -333,7 +351,16 @@
             }
           ]
         },
-        "test": "gl_unittests"
+        "test": "gl_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
@@ -359,6 +386,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -383,6 +419,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -407,6 +452,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -431,6 +485,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -459,6 +522,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -490,6 +562,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -529,6 +610,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -554,6 +644,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -578,6 +677,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -603,6 +711,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -626,7 +743,16 @@
             }
           ]
         },
-        "test": "angle_unittests"
+        "test": "angle_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -647,7 +773,16 @@
             }
           ]
         },
-        "test": "browser_tests"
+        "test": "browser_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -664,7 +799,16 @@
             }
           ]
         },
-        "test": "gl_tests"
+        "test": "gl_tests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       },
       {
         "args": [
@@ -681,7 +825,16 @@
             }
           ]
         },
-        "test": "gl_unittests"
+        "test": "gl_unittests",
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
+        }
       }
     ],
     "isolated_scripts": [
@@ -707,6 +860,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -731,6 +893,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -755,6 +926,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -779,6 +959,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -807,6 +996,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -838,6 +1036,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -877,6 +1084,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -902,6 +1118,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -926,6 +1151,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -951,6 +1185,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
diff --git a/testing/buildbot/chromium.linux.json b/testing/buildbot/chromium.linux.json
index 23309e82..d450cf9 100644
--- a/testing/buildbot/chromium.linux.json
+++ b/testing/buildbot/chromium.linux.json
@@ -695,20 +695,6 @@
       },
       {
         "args": [
-          "--qemu-require-kvm"
-        ],
-        "swarming": {
-          "can_use_on_swarming_builders": true,
-          "dimension_sets": [
-            {
-              "kvm": "1"
-            }
-          ]
-        },
-        "test": "cast_runner_unittests"
-      },
-      {
-        "args": [
           "--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter",
           "--qemu-require-kvm"
         ],
diff --git a/testing/buildbot/client.v8.fyi.json b/testing/buildbot/client.v8.fyi.json
index d9d699d..bab8378 100644
--- a/testing/buildbot/client.v8.fyi.json
+++ b/testing/buildbot/client.v8.fyi.json
@@ -296,6 +296,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -320,6 +329,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -344,6 +362,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -368,6 +395,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -399,6 +435,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -438,6 +483,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -463,6 +517,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -487,6 +550,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -514,6 +586,15 @@
           ],
           "idempotent": false,
           "shards": 20
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -539,6 +620,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
@@ -567,6 +657,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -591,6 +690,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -615,6 +723,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -639,6 +756,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -670,6 +796,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -709,6 +844,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -734,6 +878,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -758,6 +911,15 @@
             }
           ],
           "idempotent": false
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -785,6 +947,15 @@
           ],
           "idempotent": false,
           "shards": 20
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       },
       {
@@ -810,6 +981,15 @@
           ],
           "idempotent": false,
           "shards": 2
+        },
+        "trigger_script": {
+          "args": [
+            "--multiple-trigger-configs",
+            "[{\"gpu\": \"10de:1cb3-384.90\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}, {\"gpu\": \"10de:1cb3-410.78\", \"os\": \"Ubuntu\", \"pool\": \"Chrome-GPU\"}]",
+            "--multiple-dimension-script-verbose",
+            "True"
+          ],
+          "script": "//testing/trigger_scripts/trigger_multiple_dimensions.py"
         }
       }
     ]
diff --git a/testing/buildbot/filters/BUILD.gn b/testing/buildbot/filters/BUILD.gn
index ded4b8b1..a7210a8 100644
--- a/testing/buildbot/filters/BUILD.gn
+++ b/testing/buildbot/filters/BUILD.gn
@@ -22,7 +22,6 @@
   data = [
     "//testing/buildbot/filters/chromeos.mash.browser_tests.filter",
     "//testing/buildbot/filters/chromeos.mash.fyi.browser_tests.filter",
-    "//testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter",
     "//testing/buildbot/filters/webui_polymer2_browser_tests.filter",
     "//testing/buildbot/filters/webrtc_functional.browser_tests.filter",
   ]
diff --git a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter b/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
deleted file mode 100644
index 798aa2b..0000000
--- a/testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter
+++ /dev/null
@@ -1,14 +0,0 @@
-# NOTE: if adding an exclusion for an existing failure (e.g. additional test for
-# feature X that is already not working), please add it beside the existing
-# failures. Otherwise please reach out to network-service-dev@.
-
-# This filter contains Chrome OS only test failures (in addition to
-# cross-platform failures in mojo.fyi.network_browser_tests.filter).
-# See https://crbug.com/881976
-
-# Crashing on Mash. https://crbug.com/919108
--ChromeBrowserMainBrowserTest.VariationsServiceStartsRequestOnNetworkChange
-
-# NOTE: if adding an exclusion for an existing failure (e.g. additional test for
-# feature X that is already not working), please add it beside the existing
-# failures. Otherwise please reach out to network-service-dev@.
diff --git a/testing/buildbot/gn_isolate_map.pyl b/testing/buildbot/gn_isolate_map.pyl
index bfc4da6..25e996a 100644
--- a/testing/buildbot/gn_isolate_map.pyl
+++ b/testing/buildbot/gn_isolate_map.pyl
@@ -426,10 +426,6 @@
     "label": "//fuchsia/runners:cast_runner_integration_tests",
     "type": "console_test_launcher",
   },
-  "cast_runner_unittests": {
-    "label": "//fuchsia/runners:cast_runner_unittests",
-    "type": "console_test_launcher",
-  },
   "cast_audio_backend_unittests": {
     "label": "//chromecast/media/cma/backend:cast_audio_backend_unittests",
     "type": "console_test_launcher",
diff --git a/testing/buildbot/test_suites.pyl b/testing/buildbot/test_suites.pyl
index 930e5a0..8e4ceee 100644
--- a/testing/buildbot/test_suites.pyl
+++ b/testing/buildbot/test_suites.pyl
@@ -2740,7 +2740,6 @@
       'base_unittests': {},
       'cast_runner_browsertests': {},
       'cast_runner_integration_tests': {},
-      'cast_runner_unittests': {},
       'content_unittests': {
         'args': [
           '--test-launcher-filter-file=../../testing/buildbot/filters/fuchsia.content_unittests.filter',
@@ -3893,7 +3892,6 @@
       'network_service_browser_tests': {
         'args': [
           '--enable-features=NetworkService',
-          '--test-launcher-filter-file=../../testing/buildbot/filters/mojo.fyi.chromeos.network_browser_tests.filter',
         ],
         'swarming': {
           'shards': 10,
diff --git a/testing/buildbot/waterfalls.pyl b/testing/buildbot/waterfalls.pyl
index 6b5d1ab..f6395aa 100644
--- a/testing/buildbot/waterfalls.pyl
+++ b/testing/buildbot/waterfalls.pyl
@@ -1799,6 +1799,14 @@
           'gtest_tests': 'gpu_desktop_gtests',
           'gpu_telemetry_tests': 'gpu_common_win_and_linux_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Linux Release (NVIDIA)': {
         'browser_config': 'release',
@@ -1810,6 +1818,14 @@
           'gtest_tests': 'gpu_desktop_gtests',
           'gpu_telemetry_tests': 'gpu_common_win_and_linux_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Mac Debug (Intel)': {
         'browser_config': 'debug',
@@ -2182,6 +2198,14 @@
         'test_suites': {
           'gtest_tests': 'gpu_dawn_end2end_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Dawn GPU Mac Release (Intel)': {
         'os_type': 'mac',
@@ -2257,6 +2281,14 @@
           'gtest_tests': 'gpu_fyi_linux_debug_gtests',
           'gpu_telemetry_tests': 'gpu_fyi_win_and_linux_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Linux FYI Experimental Release (Intel HD 630)': {
         'os_type': 'linux',
@@ -2294,6 +2326,14 @@
           'gtest_tests': 'gpu_fyi_linux_debug_gtests',
           'isolated_scripts': 'gpu_angle_perftests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Linux FYI Ozone (Intel)': {
         'os_type': 'linux',
@@ -2343,6 +2383,14 @@
           'isolated_scripts': 'gpu_angle_perftests',
           'gpu_telemetry_tests': 'gpu_fyi_linux_intel_and_nvidia_release_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Linux FYI dEQP Release (Intel HD 630)': {
         'os_type': 'linux',
@@ -2363,6 +2411,14 @@
         'test_suites': {
           'gtest_tests': 'gpu_angle_deqp_linux_nvidia_gtests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Mac FYI 10.14 Release (AMD)': {
         'os_type': 'mac',
@@ -2600,6 +2656,14 @@
           'isolated_scripts': 'gpu_angle_perftests',
           'gpu_telemetry_tests': 'gpu_fyi_optional_linux_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Optional Mac Release (Intel)': {
         'os_type': 'mac',
@@ -3678,6 +3742,14 @@
         'test_suites': {
           'gpu_telemetry_tests': 'gpu_v8_desktop_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Linux V8 FYI Release - pointer compression (NVIDIA)': {
         'os_type': 'linux',
@@ -3688,6 +3760,14 @@
         'test_suites': {
           'gpu_telemetry_tests': 'gpu_v8_desktop_telemetry_tests',
         },
+        'use_multi_dimension_trigger_script': True,
+        'alternate_swarming_dimensions': [
+          {
+            'gpu': '10de:1cb3-410.78',
+            'os': 'Ubuntu',
+            'pool': 'Chrome-GPU',
+          },
+        ],
       },
       'Mac V8 FYI Release (Intel)': {
         'browser_config': 'release',
diff --git a/testing/perf/OWNERS b/testing/perf/OWNERS
index 6cdde70..731c5c6 100644
--- a/testing/perf/OWNERS
+++ b/testing/perf/OWNERS
@@ -1,2 +1 @@
 charliea@chromium.org
-nednguyen@google.com
diff --git a/testing/scripts/run_performance_tests.py b/testing/scripts/run_performance_tests.py
index 61658f0..fdc72a5b 100755
--- a/testing/scripts/run_performance_tests.py
+++ b/testing/scripts/run_performance_tests.py
@@ -55,8 +55,6 @@
 
 import common
 
-import run_telemetry_benchmark_as_googletest
-
 CHROMIUM_SRC_DIR = os.path.abspath(
     os.path.join(os.path.dirname(__file__), '..', '..'))
 PERF_DIR = os.path.join(CHROMIUM_SRC_DIR, 'tools', 'perf')
@@ -206,7 +204,7 @@
   # could be any format (chartjson, legacy, histogram). We just pass these
   # through, and expose these as results for this task.
   rc, perf_results, json_test_results, benchmark_log = (
-      run_telemetry_benchmark_as_googletest.run_benchmark(
+      execute_telemetry_benchmark_helper(
           args, per_benchmark_args, is_histograms))
 
   write_results(
@@ -217,6 +215,120 @@
   return rc
 
 
+def execute_telemetry_benchmark_helper(args, rest_args, histogram_results):
+  """Run benchmark with args.
+
+  Args:
+    args: the option object resulted from parsing commandline args required for
+      IsolatedScriptTest contract (see
+      https://cs.chromium.org/chromium/build/scripts/slave/recipe_modules/chromium_tests/steps.py?rcl=d31f256fb860701e6dc02544f2beffe4e17c9b92&l=1639).
+    rest_args: the args (list of strings) for running Telemetry benchmark.
+    histogram_results: a boolean describes whether to output histograms format
+      for the benchmark.
+
+  Returns: a tuple of (rc, perf_results, json_test_results, benchmark_log)
+    rc: the return code of benchmark
+    perf_results: json object contains the perf test results
+    json_test_results: json object contains the Pass/Fail data of the benchmark.
+    benchmark_log: string contains the stdout/stderr of the benchmark run.
+  """
+  # TODO(crbug.com/920002): These arguments cannot go into
+  # run_performance_tests.py because
+  # run_gtest_perf_tests.py does not yet support them. Note that ideally
+  # we would use common.BaseIsolatedScriptArgsAdapter, but this will take
+  # a good deal of refactoring to accomplish.
+  parser = argparse.ArgumentParser()
+  parser.add_argument(
+      '--isolated-script-test-repeat', type=int, required=False)
+  parser.add_argument(
+      '--isolated-script-test-launcher-retry-limit', type=int, required=False,
+      choices=[0])  # Telemetry does not support retries. crbug.com/894254#c21
+  parser.add_argument(
+      '--isolated-script-test-also-run-disabled-tests',
+      default=False, action='store_true', required=False)
+  # Parse leftover args not already parsed in run_performance_tests.py or in
+  # main().
+  args, rest_args = parser.parse_known_args(args=rest_args, namespace=args)
+
+  env = os.environ.copy()
+  env['CHROME_HEADLESS'] = '1'
+
+  # Assume we want to set up the sandbox environment variables all the
+  # time; doing so is harmless on non-Linux platforms and is needed
+  # all the time on Linux.
+  env[CHROME_SANDBOX_ENV] = CHROME_SANDBOX_PATH
+  tempfile_dir = tempfile.mkdtemp('telemetry')
+  benchmark_log = ''
+  stdoutfile = os.path.join(tempfile_dir, 'benchmark_log.txt')
+  valid = True
+  num_failures = 0
+  perf_results = None
+  json_test_results = None
+
+  results = None
+  cmd_args = rest_args
+  if args.isolated_script_test_filter:
+    filter_list = common.extract_filter_list(args.isolated_script_test_filter)
+    # Need to convert this to a valid regex.
+    filter_regex = '(' + '|'.join(filter_list) + ')'
+    cmd_args.append('--story-filter=' + filter_regex)
+  if args.isolated_script_test_repeat:
+    cmd_args.append('--pageset-repeat=' + str(args.isolated_script_test_repeat))
+  if args.isolated_script_test_also_run_disabled_tests:
+    cmd_args.append('--also-run-disabled-tests')
+  cmd_args.append('--output-dir=' + tempfile_dir)
+  cmd_args.append('--output-format=json-test-results')
+  cmd = [sys.executable] + cmd_args
+  rc = 1  # Set default returncode in case there is an exception.
+  try:
+    if args.xvfb:
+      rc = xvfb.run_executable(cmd, env=env, stdoutfile=stdoutfile)
+    else:
+      rc = test_env.run_command_with_output(cmd, env=env, stdoutfile=stdoutfile)
+
+    with open(stdoutfile) as f:
+      benchmark_log = f.read()
+
+    # If we have also output chartjson read it in and return it.
+    # results-chart.json is the file name output by telemetry when the
+    # chartjson output format is included
+    tempfile_name = None
+    if histogram_results:
+      tempfile_name = os.path.join(tempfile_dir, 'histograms.json')
+    else:
+      tempfile_name = os.path.join(tempfile_dir, 'results-chart.json')
+
+    if tempfile_name is not None:
+      with open(tempfile_name) as f:
+        perf_results = json.load(f)
+
+    # test-results.json is the file name output by telemetry when the
+    # json-test-results format is included
+    tempfile_name = os.path.join(tempfile_dir, 'test-results.json')
+    with open(tempfile_name) as f:
+      json_test_results = json.load(f)
+    num_failures = json_test_results['num_failures_by_type'].get('FAIL', 0)
+    valid = bool(rc == 0 or num_failures != 0)
+
+  except Exception:
+    traceback.print_exc()
+    if results:
+      print 'results, which possibly caused exception: %s' % json.dumps(
+          results, indent=2)
+    valid = False
+  finally:
+    # Add ignore_errors=True because otherwise rmtree may fail due to leaky
+    # processes of tests are still holding opened handles to files under
+    # |tempfile_dir|. For example, see crbug.com/865896
+    shutil.rmtree(tempfile_dir, ignore_errors=True)
+
+  if not valid and num_failures == 0:
+    if rc == 0:
+      rc = 1  # Signal an abnormal exit.
+
+  return rc, perf_results, json_test_results, benchmark_log
+
+
 def append_output_format(args, rest_args):
   # We need to determine if the output format is already passed in
   # or if we need to define it for this benchmark
diff --git a/testing/scripts/run_telemetry_benchmark_as_googletest.py b/testing/scripts/run_telemetry_benchmark_as_googletest.py
deleted file mode 100755
index 44ac668..0000000
--- a/testing/scripts/run_telemetry_benchmark_as_googletest.py
+++ /dev/null
@@ -1,221 +0,0 @@
-#!/usr/bin/env python
-# Copyright 2015 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.
-
-"""Runs an isolate bundled Telemetry benchmark.
-
-This script attempts to emulate the contract of gtest-style tests
-invoked via recipes. The main contract is that the caller passes the
-argument:
-
-  --isolated-script-test-output=[FILENAME]
-
-json is written to that file in the format detailed here:
-https://www.chromium.org/developers/the-json-test-results-format
-
-Optional argument:
-
-  --isolated-script-test-filter=[TEST_NAMES]
-
-is a double-colon-separated ("::") list of test names, to run just that subset
-of tests. This list is parsed by this harness and sent down via the
---story-filter argument.
-
-This script is intended to be the base command invoked by the isolate,
-followed by a subsequent Python script. It could be generalized to
-invoke an arbitrary executable.
-
-
-TESTING:
-To test changes to this script, please run
-cd tools/perf
-./run_tests ScriptsSmokeTest.testRunTelemetryBenchmarkAsGoogletest
-"""
-
-import argparse
-import json
-import os
-import shutil
-import sys
-import tempfile
-import traceback
-
-import common
-
-# Add src/testing/ into sys.path for importing xvfb.
-sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
-import xvfb
-import test_env
-
-# Unfortunately we need to copy these variables from ../test_env.py.
-# Importing it and using its get_sandbox_env breaks test runs on Linux
-# (it seems to unset DISPLAY).
-CHROME_SANDBOX_ENV = 'CHROME_DEVEL_SANDBOX'
-CHROME_SANDBOX_PATH = '/opt/chromium/chrome_sandbox'
-
-def main():
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      '--isolated-script-test-output', type=argparse.FileType('w'),
-      required=True)
-  parser.add_argument(
-      '--isolated-script-test-chartjson-output', required=False)
-  parser.add_argument(
-      '--isolated-script-test-perf-output', required=False)
-  parser.add_argument(
-      '--isolated-script-test-filter', type=str, required=False)
-  parser.add_argument('--xvfb', help='Start xvfb.', action='store_true')
-  parser.add_argument('--output-format', action='append')
-  args, rest_args = parser.parse_known_args()
-  for output_format in args.output_format:
-    rest_args.append('--output-format=' + output_format)
-
-  rc, perf_results, json_test_results, _ = run_benchmark(args, rest_args,
-      'histograms' in args.output_format)
-
-  if perf_results:
-    if args.isolated_script_test_perf_output:
-      filename = args.isolated_script_test_perf_output
-    elif args.isolated_script_test_chartjson_output:
-      filename = args.isolated_script_test_chartjson_output
-    else:
-      filename = None
-
-    if filename is not None:
-      with open(filename, 'w') as perf_results_output_file:
-        json.dump(perf_results, perf_results_output_file)
-
-  json.dump(json_test_results, args.isolated_script_test_output)
-
-  return rc
-
-def run_benchmark(args, rest_args, histogram_results):
-  """Run benchmark with args.
-
-  Args:
-    args: the option object resulted from parsing commandline args required for
-      IsolatedScriptTest contract (see
-      https://cs.chromium.org/chromium/build/scripts/slave/recipe_modules/chromium_tests/steps.py?rcl=d31f256fb860701e6dc02544f2beffe4e17c9b92&l=1639).
-    rest_args: the args (list of strings) for running Telemetry benchmark.
-    histogram_results: a boolean describes whether to output histograms format
-      for the benchmark.
-
-  Returns: a tuple of (rc, perf_results, json_test_results, benchmark_log)
-    rc: the return code of benchmark
-    perf_results: json object contains the perf test results
-    json_test_results: json object contains the Pass/Fail data of the benchmark.
-    benchmark_log: string contains the stdout/stderr of the benchmark run.
-  """
-  # TODO(crbug.com/920002): These arguments cannot go into
-  # run_performance_tests.py because
-  # run_gtest_perf_tests.py does not yet support them. Note that ideally
-  # we would use common.BaseIsolatedScriptArgsAdapter, but this will take
-  # a good deal of refactoring to accomplish.
-  parser = argparse.ArgumentParser()
-  parser.add_argument(
-      '--isolated-script-test-repeat', type=int, required=False)
-  parser.add_argument(
-      '--isolated-script-test-launcher-retry-limit', type=int, required=False,
-      choices=[0])  # Telemetry does not support retries. crbug.com/894254#c21
-  parser.add_argument(
-      '--isolated-script-test-also-run-disabled-tests',
-      default=False, action='store_true', required=False)
-  # Parse leftover args not already parsed in run_performance_tests.py or in
-  # main().
-  args, rest_args = parser.parse_known_args(args=rest_args, namespace=args)
-
-  env = os.environ.copy()
-  env['CHROME_HEADLESS'] = '1'
-
-  # Assume we want to set up the sandbox environment variables all the
-  # time; doing so is harmless on non-Linux platforms and is needed
-  # all the time on Linux.
-  env[CHROME_SANDBOX_ENV] = CHROME_SANDBOX_PATH
-  tempfile_dir = tempfile.mkdtemp('telemetry')
-  benchmark_log = ''
-  stdoutfile = os.path.join(tempfile_dir, 'benchmark_log.txt')
-  valid = True
-  num_failures = 0
-  perf_results = None
-  json_test_results = None
-
-  results = None
-  cmd_args = rest_args
-  if args.isolated_script_test_filter:
-    filter_list = common.extract_filter_list(args.isolated_script_test_filter)
-    # Need to convert this to a valid regex.
-    filter_regex = '(' + '|'.join(filter_list) + ')'
-    cmd_args.append('--story-filter=' + filter_regex)
-  if args.isolated_script_test_repeat:
-    cmd_args.append('--pageset-repeat=' + str(args.isolated_script_test_repeat))
-  if args.isolated_script_test_also_run_disabled_tests:
-    cmd_args.append('--also-run-disabled-tests')
-  cmd_args.append('--output-dir=' + tempfile_dir)
-  cmd_args.append('--output-format=json-test-results')
-  cmd = [sys.executable] + cmd_args
-  rc = 1  # Set default returncode in case there is an exception.
-  try:
-    if args.xvfb:
-      rc = xvfb.run_executable(cmd, env=env, stdoutfile=stdoutfile)
-    else:
-      rc = test_env.run_command_with_output(cmd, env=env, stdoutfile=stdoutfile)
-
-    with open(stdoutfile) as f:
-      benchmark_log = f.read()
-
-    # If we have also output chartjson read it in and return it.
-    # results-chart.json is the file name output by telemetry when the
-    # chartjson output format is included
-    tempfile_name = None
-    if histogram_results:
-      tempfile_name = os.path.join(tempfile_dir, 'histograms.json')
-    else:
-      tempfile_name = os.path.join(tempfile_dir, 'results-chart.json')
-
-    if tempfile_name is not None:
-      with open(tempfile_name) as f:
-        perf_results = json.load(f)
-
-    # test-results.json is the file name output by telemetry when the
-    # json-test-results format is included
-    tempfile_name = os.path.join(tempfile_dir, 'test-results.json')
-    with open(tempfile_name) as f:
-      json_test_results = json.load(f)
-    num_failures = json_test_results['num_failures_by_type'].get('FAIL', 0)
-    valid = bool(rc == 0 or num_failures != 0)
-
-  except Exception:
-    traceback.print_exc()
-    if results:
-      print 'results, which possibly caused exception: %s' % json.dumps(
-          results, indent=2)
-    valid = False
-  finally:
-    # Add ignore_errors=True because otherwise rmtree may fail due to leaky
-    # processes of tests are still holding opened handles to files under
-    # |tempfile_dir|. For example, see crbug.com/865896
-    shutil.rmtree(tempfile_dir, ignore_errors=True)
-
-  if not valid and num_failures == 0:
-    if rc == 0:
-      rc = 1  # Signal an abnormal exit.
-
-  return rc, perf_results, json_test_results, benchmark_log
-
-
-# This is not really a "script test" so does not need to manually add
-# any additional compile targets.
-def main_compile_targets(args):
-  json.dump([], args.output)
-
-
-if __name__ == '__main__':
-  # Conform minimally to the protocol defined by ScriptTest.
-  if 'compile_targets' in sys.argv:
-    funcs = {
-      'run': None,
-      'compile_targets': main_compile_targets,
-    }
-    sys.exit(common.run_script(sys.argv[1:], funcs))
-  sys.exit(main())
diff --git a/testing/trigger_scripts/base_test_triggerer.py b/testing/trigger_scripts/base_test_triggerer.py
index d66c485..b95c9dc1 100755
--- a/testing/trigger_scripts/base_test_triggerer.py
+++ b/testing/trigger_scripts/base_test_triggerer.py
@@ -209,9 +209,16 @@
     # Main implementation for base class to determine what
     # configs to trigger jobs on from self._bot_configs.
     # Returns a list of indices into the self._bot_configs and
-    # len(args.shards) == len(selected_indices).
+    # len(self.indices_to_trigger(args)) == len(selected_indices).
     pass
 
+  def indices_to_trigger(self, args):
+    """Returns the indices of the swarming shards that should be triggered."""
+    if args.shard_index is None:
+      return range(args.shards)
+    else:
+      return [args.shard_index]
+
   def trigger_tasks(self, args, remaining):
     """Triggers tasks for each bot.
 
@@ -244,7 +251,7 @@
 
     # Choose selected configs for this run of the test suite.
     selected_configs = self.select_config_indices(args, verbose)
-    for i in xrange(args.shards):
+    for i in self.indices_to_trigger(args):
       # For each shard that we're going to distribute, do the following:
       # 1. Pick which bot configuration to use.
       # 2. Insert that bot configuration's dimensions as command line
@@ -294,5 +301,8 @@
     parser.add_argument('--shards', type=int, default=1,
                         help='How many shards to trigger. Duplicated from the'
                        ' `swarming.py trigger` command.')
+    parser.add_argument('--shard-index', type=int, default=None,
+                        help='Which shard to trigger. Duplicated from the '
+                             '`swarming.py trigger` command.')
     return parser
 
diff --git a/testing/trigger_scripts/perf_device_trigger.py b/testing/trigger_scripts/perf_device_trigger.py
index c58ac07..e890acb 100755
--- a/testing/trigger_scripts/perf_device_trigger.py
+++ b/testing/trigger_scripts/perf_device_trigger.py
@@ -118,24 +118,25 @@
       # If specific bot ids were passed in, we want to trigger a job for
       # every valid config regardless of health status since
       # each config represents exactly one bot in the perf swarming pool.
-      return range(args.shards)
+      return range(len(self.indices_to_trigger(args)))
     return self._select_config_indices_with_soft_affinity(args, verbose)
 
   def _select_config_indices_with_soft_affinity(self, args, verbose):
+    trigger_count = len(self.indices_to_trigger(args))
     # First make sure the number of shards doesn't exceed the
     # number of eligible bots.  This means there is a config error somewhere.
-    if args.shards > len(self._eligible_bots_by_ids):
+    if trigger_count > len(self._eligible_bots_by_ids):
       if verbose:
         self._print_device_affinity_info({}, {},
-          self._eligible_bots_by_ids, args.shards)
+          self._eligible_bots_by_ids, trigger_count)
       raise ValueError('Not enough available machines exist in in swarming'
                        'pool.  Shards requested (%d) exceeds available bots '
                        '(%d).' % (
-                           args.shards, len(self._eligible_bots_by_ids)))
+                           trigger_count, len(self._eligible_bots_by_ids)))
 
     shard_to_bot_assignment_map = {}
     unallocated_bots_by_ids = copy.deepcopy(self._eligible_bots_by_ids)
-    for shard_index in xrange(args.shards):
+    for shard_index in self.indices_to_trigger(args):
       bot_id = self._query_swarming_for_last_shard_id(shard_index)
       if bot_id and bot_id in unallocated_bots_by_ids:
         bot = unallocated_bots_by_ids[bot_id]
@@ -174,14 +175,14 @@
 
     # Now populate the indices into the bot_configs array
     selected_configs = []
-    for shard_index in xrange(args.shards):
+    for shard_index in self.indices_to_trigger(args):
       selected_configs.append(self._find_bot_config_index(
           shard_to_bot_assignment_map[shard_index].id()))
     if verbose:
       self._print_device_affinity_info(
         shard_to_bot_assignment_map,
         existing_shard_bot_to_shard_map,
-        self._eligible_bots_by_ids, args.shards)
+        self._eligible_bots_by_ids, trigger_count)
     return selected_configs
 
 
diff --git a/testing/trigger_scripts/perf_device_trigger_unittest.py b/testing/trigger_scripts/perf_device_trigger_unittest.py
index 4ba1e2b..9ea923c 100755
--- a/testing/trigger_scripts/perf_device_trigger_unittest.py
+++ b/testing/trigger_scripts/perf_device_trigger_unittest.py
@@ -12,6 +12,7 @@
 class Args(object):
   def __init__(self):
     self.shards = 1
+    self.shard_index = None
     self.dump_json = ''
     self.multiple_trigger_configs = None
     self.multiple_dimension_script_verbose = False
diff --git a/testing/trigger_scripts/trigger_multiple_dimensions.py b/testing/trigger_scripts/trigger_multiple_dimensions.py
index a73e4c4..4fd765a 100755
--- a/testing/trigger_scripts/trigger_multiple_dimensions.py
+++ b/testing/trigger_scripts/trigger_multiple_dimensions.py
@@ -113,7 +113,7 @@
 
   def select_config_indices(self, args, verbose):
     selected_indices = []
-    for _ in xrange(args.shards):
+    for _ in self.indices_to_trigger(args):
       selected_indices.append(self.pick_bot_configuration(verbose))
     return selected_indices
 
diff --git a/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py b/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py
index c7530aef..9a29cd39 100755
--- a/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py
+++ b/testing/trigger_scripts/trigger_multiple_dimensions_unittest.py
@@ -12,6 +12,7 @@
 class Args(object):
   def __init__(self):
     self.shards = 1
+    self.shard_index = None
     self.dump_json = ''
     self.multiple_trigger_configs = []
     self.multiple_dimension_script_verbose = False
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index bf934ac..6e6cf504 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -1491,7 +1491,8 @@
                 "android",
                 "android_webview",
                 "chromeos",
-                "mac"
+                "mac",
+                "windows"
             ],
             "experiments": [
                 {
@@ -2097,6 +2098,22 @@
             ]
         }
     ],
+    "HardwareMediaKeyHandling": [
+        {
+            "platforms": [
+                "windows",
+                "mac"
+            ],
+            "experiments": [
+                {
+                    "name": "Enabled",
+                    "enable_features": [
+                        "HardwareMediaKeyHandling"
+                    ]
+                }
+            ]
+        }
+    ],
     "HomePageButtonForceEnabled": [
         {
             "platforms": [
diff --git a/third_party/blink/public/BUILD.gn b/third_party/blink/public/BUILD.gn
index 4d934bda..c47eea98 100644
--- a/third_party/blink/public/BUILD.gn
+++ b/third_party/blink/public/BUILD.gn
@@ -144,10 +144,10 @@
     "platform/modules/mediastream/media_stream_audio_source.h",
     "platform/modules/mediastream/media_stream_audio_track.h",
     "platform/modules/mediastream/media_stream_types.h",
-    "platform/modules/mediastream/platform_media_stream_source.h",
     "platform/modules/mediastream/secure_display_link_tracker.h",
     "platform/modules/mediastream/web_media_stream_audio_sink.h",
     "platform/modules/mediastream/web_media_stream_sink.h",
+    "platform/modules/mediastream/web_platform_media_stream_source.h",
     "platform/modules/mediastream/web_platform_media_stream_track.h",
     "platform/modules/notifications/web_notification_action.h",
     "platform/modules/notifications/web_notification_constants.h",
diff --git a/third_party/blink/public/mojom/portal/portal.mojom b/third_party/blink/public/mojom/portal/portal.mojom
index d30fb83..3902465 100644
--- a/third_party/blink/public/mojom/portal/portal.mojom
+++ b/third_party/blink/public/mojom/portal/portal.mojom
@@ -7,18 +7,11 @@
 import "mojo/public/mojom/base/unguessable_token.mojom";
 import "url/mojom/url.mojom";
 
-enum PortalActivationStatus {
-  // Portal has been successfully activated.
-  kSuccess,
-  // Operation is not supported by the embedder.
-  kNotSupported,
-};
-
 // The Portal interface is used by the renderer to interact with the Portal.
 interface Portal {
   // Navigates the portal to |url|.
   Navigate(url.mojom.Url url);
 
   // When a portal is activated, it'll replace the current tab with the portal.
-  Activate() => (PortalActivationStatus result);
+  Activate() => ();
 };
diff --git a/third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h b/third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h
index 4ec1c65..4cfa28e 100644
--- a/third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h
+++ b/third_party/blink/public/platform/modules/mediastream/media_stream_audio_source.h
@@ -13,7 +13,7 @@
 #include "base/memory/weak_ptr.h"
 #include "media/base/limits.h"
 #include "third_party/blink/public/platform/modules/mediastream/media_stream_audio_deliverer.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_common.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_stream_track.h"
diff --git a/third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h b/third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h
similarity index 93%
rename from third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h
rename to third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h
index 44b2882e..5dcf040 100644
--- a/third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h
+++ b/third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.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_MEDIASTREAM_PLATFORM_MEDIA_STREAM_SOURCE_H_
-#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_PLATFORM_MEDIA_STREAM_SOURCE_H_
+#ifndef THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_PLATFORM_MEDIA_STREAM_SOURCE_H_
+#define THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_PLATFORM_MEDIA_STREAM_SOURCE_H_
 
 #include "base/callback.h"
 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
@@ -98,4 +98,4 @@
 
 }  // namespace blink
 
-#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_PLATFORM_MEDIA_STREAM_SOURCE_H_
+#endif  // THIRD_PARTY_BLINK_PUBLIC_PLATFORM_MODULES_MEDIASTREAM_WEB_PLATFORM_MEDIA_STREAM_SOURCE_H_
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
index bfc4a95f..95b87461 100644
--- a/third_party/blink/public/platform/platform.h
+++ b/third_party/blink/public/platform/platform.h
@@ -429,18 +429,6 @@
   // DEPRECATED: Use Thread::CreateThread() instead.
   std::unique_ptr<Thread> CreateThread(const ThreadCreationParams&);
 
-  // DEPRECATED: Use Thread::CreateWebAudioThread() instead.
-  std::unique_ptr<Thread> CreateWebAudioThread();
-
-  // DEPRECATED: Use Thread::Current() instead.
-  Thread* CurrentThread();
-
-  // DEPRECATED: Use Thread::MainThread() instead.
-  Thread* MainThread();
-
-  // DEPRECATED: Use Thread::CompositorThread() instead.
-  Thread* CompositorThread();
-
   // The two compositor-related functions below are called by the embedder.
   // TODO(yutak): Perhaps we should move these to somewhere else?
 
diff --git a/third_party/blink/public/platform/web_media_stream_source.h b/third_party/blink/public/platform/web_media_stream_source.h
index 773fc1c6..9ebf4aa7 100644
--- a/third_party/blink/public/platform/web_media_stream_source.h
+++ b/third_party/blink/public/platform/web_media_stream_source.h
@@ -94,9 +94,6 @@
 
   BLINK_PLATFORM_EXPORT void Initialize(const WebString& id,
                                         Type,
-                                        const WebString& name);  // DEPRECATED
-  BLINK_PLATFORM_EXPORT void Initialize(const WebString& id,
-                                        Type,
                                         const WebString& name,
                                         bool remote);
   BLINK_PLATFORM_EXPORT void Reset();
diff --git a/third_party/blink/renderer/core/css/parser/css_variable_parser.cc b/third_party/blink/renderer/core/css/parser/css_variable_parser.cc
index 0ebe89d..4e76b1d0 100644
--- a/third_party/blink/renderer/core/css/parser/css_variable_parser.cc
+++ b/third_party/blink/renderer/core/css/parser/css_variable_parser.cc
@@ -13,12 +13,10 @@
 
 namespace {
 
-bool IsValidVariableReference(CSSParserTokenRange, bool);
-bool IsValidEnvVariableReference(CSSParserTokenRange, bool);
+bool IsValidVariableReference(CSSParserTokenRange);
+bool IsValidEnvVariableReference(CSSParserTokenRange);
 
-bool ClassifyBlock(CSSParserTokenRange range,
-                   bool& has_references,
-                   bool skip_variables) {
+bool ClassifyBlock(CSSParserTokenRange range, bool& has_references) {
   size_t block_stack_size = 0;
 
   while (!range.AtEnd()) {
@@ -31,13 +29,12 @@
       // and used as fallbacks.
       switch (token.FunctionId()) {
         case CSSValueVar:
-          if (!IsValidVariableReference(range.ConsumeBlock(), skip_variables))
+          if (!IsValidVariableReference(range.ConsumeBlock()))
             return false;  // Invalid reference.
           has_references = true;
           continue;
         case CSSValueEnv:
-          if (!IsValidEnvVariableReference(range.ConsumeBlock(),
-                                           skip_variables))
+          if (!IsValidEnvVariableReference(range.ConsumeBlock()))
             return false;  // Invalid reference.
           has_references = true;
           continue;
@@ -48,12 +45,6 @@
 
     const CSSParserToken& token = range.Consume();
     if (token.GetBlockType() == CSSParserToken::kBlockStart) {
-      // If we are an invalid function then we should skip over any variables
-      // this function contains.
-      if (token.GetType() == CSSParserTokenType::kFunctionToken &&
-          token.FunctionId() == CSSValueInvalid) {
-        skip_variables = true;
-      }
       ++block_stack_size;
     } else if (token.GetBlockType() == CSSParserToken::kBlockEnd) {
       --block_stack_size;
@@ -82,10 +73,8 @@
   return true;
 }
 
-bool IsValidVariableReference(CSSParserTokenRange range, bool skip_variables) {
+bool IsValidVariableReference(CSSParserTokenRange range) {
   range.ConsumeWhitespace();
-  if (skip_variables)
-    return false;
   if (!CSSVariableParser::IsValidVariableName(
           range.ConsumeIncludingWhitespace()))
     return false;
@@ -98,14 +87,11 @@
     return false;
 
   bool has_references = false;
-  return ClassifyBlock(range, has_references, skip_variables);
+  return ClassifyBlock(range, has_references);
 }
 
-bool IsValidEnvVariableReference(CSSParserTokenRange range,
-                                 bool skip_variables) {
+bool IsValidEnvVariableReference(CSSParserTokenRange range) {
   range.ConsumeWhitespace();
-  if (skip_variables)
-    return false;
   if (range.ConsumeIncludingWhitespace().GetType() !=
       CSSParserTokenType::kIdentToken)
     return false;
@@ -118,7 +104,7 @@
     return false;
 
   bool has_references = false;
-  return ClassifyBlock(range, has_references, skip_variables);
+  return ClassifyBlock(range, has_references);
 }
 
 CSSValueID ClassifyVariableRange(CSSParserTokenRange range,
@@ -133,7 +119,7 @@
       return id;
   }
 
-  if (ClassifyBlock(range, has_references, false /* skip_variables */))
+  if (ClassifyBlock(range, has_references))
     return CSSValueInternalVariableValue;
   return CSSValueInvalid;
 }
diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc
index 62a0b124..a63a29d 100644
--- a/third_party/blink/renderer/core/dom/document.cc
+++ b/third_party/blink/renderer/core/dom/document.cc
@@ -7921,6 +7921,18 @@
     javascript_url_task_handle_.Cancel();
 }
 
+bool Document::IsInWebAppScope() const {
+  if (!GetSettings())
+    return false;
+
+  const String& web_app_scope = GetSettings()->GetWebAppScope();
+  if (web_app_scope.IsNull() || web_app_scope.IsEmpty())
+    return false;
+
+  DCHECK_EQ(KURL(web_app_scope).GetString(), web_app_scope);
+  return Url().GetString().StartsWith(web_app_scope);
+}
+
 void Document::SendViolationReport(
     mojom::blink::CSPViolationParamsPtr violation_params) {
   std::unique_ptr<SourceLocation> source_location = SourceLocation::Create(
diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h
index b4d0984..03078d84 100644
--- a/third_party/blink/renderer/core/dom/document.h
+++ b/third_party/blink/renderer/core/dom/document.h
@@ -1524,6 +1524,11 @@
   void RemoveLockedDisplayLock();
   int LockedDisplayLockCount() const;
 
+  // Returns whether the document is inside the scope specified in the Web App
+  // Manifest. If the document doesn't run in a context of a Web App or has no
+  // associated Web App Manifest, it will return false.
+  bool IsInWebAppScope() const;
+
  protected:
   void DidUpdateSecurityOrigin() final;
 
diff --git a/third_party/blink/renderer/core/frame/ad_tracker.cc b/third_party/blink/renderer/core/frame/ad_tracker.cc
index 41c3968..37ecdf7 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker.cc
@@ -108,28 +108,25 @@
   DidExecuteScript();
 }
 
-void AdTracker::WillSendRequest(ExecutionContext* execution_context,
-                                unsigned long identifier,
-                                DocumentLoader* loader,
-                                ResourceRequest& request,
-                                const ResourceResponse& redirect_response,
-                                const FetchInitiatorInfo& initiator_info,
-                                ResourceType resource_type) {
-  // If the resource is not already marked as an ad, check if the document
-  // loading the resource is an ad or if any executing script is an ad.
-  if (!request.IsAdResource() &&
-      (IsKnownAdExecutionContext(execution_context) || IsAdScriptInStack())) {
-    request.SetIsAdResource();
-  }
+bool AdTracker::CalculateIfAdSubresource(ExecutionContext* execution_context,
+                                         const ResourceRequest& request,
+                                         ResourceType resource_type,
+                                         bool known_ad) {
+  // Check if the document loading the resource is an ad or if any executing
+  // script is an ad.
+  known_ad = known_ad || IsKnownAdExecutionContext(execution_context) ||
+             IsAdScriptInStack();
 
   // If it is a script marked as an ad and it's not in an ad context, append it
   // to the known ad script set. We don't need to keep track of ad scripts in ad
   // contexts, because any script executed inside an ad context is considered an
   // ad script by IsKnownAdScript.
-  if (resource_type == ResourceType::kScript && request.IsAdResource() &&
+  if (resource_type == ResourceType::kScript && known_ad &&
       !IsKnownAdExecutionContext(execution_context)) {
     AppendToKnownAdScripts(*execution_context, request.Url().GetString());
   }
+
+  return known_ad;
 }
 
 bool AdTracker::IsAdScriptInStack() {
diff --git a/third_party/blink/renderer/core/frame/ad_tracker.h b/third_party/blink/renderer/core/frame/ad_tracker.h
index 544605c..b88ff12 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker.h
+++ b/third_party/blink/renderer/core/frame/ad_tracker.h
@@ -16,11 +16,8 @@
 
 namespace blink {
 class ExecutionContext;
-class DocumentLoader;
 class ResourceRequest;
-class ResourceResponse;
 enum class ResourceType : uint8_t;
-struct FetchInitiatorInfo;
 
 namespace probe {
 class CallFunction;
@@ -40,19 +37,16 @@
   void Will(const probe::CallFunction&);
   void Did(const probe::CallFunction&);
 
-  // Called when a resource request is about to be sent. This will do the
-  // following:
-  // - Mark a resource request as an ad if any executing scripts contain an ad.
-  // - If the marked resource is a script, also save it to keep track of all
-  // those script resources that have been identified as ads.
+  // Called when a subresource request is about to be sent or is redirected.
+  // Returns true if:
+  // - If the resource is loaded in an ad iframe
+  // - If ad script is in the v8 stack
+  // - |known_ad| is true
   // Virtual for testing.
-  virtual void WillSendRequest(ExecutionContext*,
-                               unsigned long identifier,
-                               DocumentLoader*,
-                               ResourceRequest&,
-                               const ResourceResponse& redirect_response,
-                               const FetchInitiatorInfo&,
-                               ResourceType);
+  virtual bool CalculateIfAdSubresource(ExecutionContext* execution_context,
+                                        const ResourceRequest& request,
+                                        ResourceType resource_type,
+                                        bool known_ad);
 
   // Returns true if any script in the pseudo call stack has previously been
   // identified as an ad resource.
diff --git a/third_party/blink/renderer/core/frame/ad_tracker_test.cc b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
index 8983435..d8630bf 100644
--- a/third_party/blink/renderer/core/frame/ad_tracker_test.cc
+++ b/third_party/blink/renderer/core/frame/ad_tracker_test.cc
@@ -54,22 +54,20 @@
     return execution_context_;
   }
 
-  void WillSendRequest(ExecutionContext* execution_context,
-                       unsigned long identifier,
-                       DocumentLoader* document_loader,
-                       ResourceRequest& resource_request,
-                       const ResourceResponse& redirect_response,
-                       const FetchInitiatorInfo& fetch_initiator_info,
-                       ResourceType resource_type) override {
+  bool CalculateIfAdSubresource(ExecutionContext* execution_context,
+                                const ResourceRequest& resource_request,
+                                ResourceType resource_type,
+                                bool ad_request) override {
     if (!ad_suffix_.IsEmpty() &&
         resource_request.Url().GetString().EndsWith(ad_suffix_)) {
-      resource_request.SetIsAdResource();
+      ad_request = true;
     }
-    AdTracker::WillSendRequest(execution_context, identifier, document_loader,
-                               resource_request, redirect_response,
-                               fetch_initiator_info, resource_type);
-    is_ad_.insert(resource_request.Url().GetString(),
-                  resource_request.IsAdResource());
+
+    ad_request = AdTracker::CalculateIfAdSubresource(
+        execution_context, resource_request, resource_type, ad_request);
+
+    is_ad_.insert(resource_request.Url().GetString(), ad_request);
+    return ad_request;
   }
 
  private:
@@ -385,8 +383,10 @@
 TEST_F(AdTrackerSimTest, FrameLoadedWhileExecutingAdScript) {
   const char kAdUrl[] = "https://example.com/ad_script.js";
   const char kVanillaUrl[] = "https://example.com/vanilla_page.html";
+  const char kVanillaImgUrl[] = "https://example.com/vanilla_img.jpg";
   SimSubresourceRequest ad_resource(kAdUrl, "text/javascript");
   SimRequest vanilla_page(kVanillaUrl, "text/html");
+  SimSubresourceRequest vanilla_image(kVanillaImgUrl, "image/jpeg");
 
   ad_tracker_->SetAdSuffix("ad_script.js");
 
@@ -397,12 +397,14 @@
     iframe.src = "vanilla_page.html";
     document.body.appendChild(iframe);
     )SCRIPT");
-  vanilla_page.Complete("");
+  vanilla_page.Complete("<img src=vanilla_img.jpg></img>");
+  vanilla_image.Complete("");
 
   EXPECT_TRUE(IsKnownAdScript(&GetDocument(), kAdUrl));
   EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kAdUrl));
   Frame* child_frame = GetDocument().GetFrame()->Tree().FirstChild();
   EXPECT_TRUE(ToLocalFrame(child_frame)->IsAdSubframe());
+  EXPECT_TRUE(ad_tracker_->RequestWithUrlTaggedAsAd(kVanillaImgUrl));
 }
 
 // A script tagged as an ad in one frame shouldn't cause it to be considered
diff --git a/third_party/blink/renderer/core/html/link_style.cc b/third_party/blink/renderer/core/html/link_style.cc
index 4998764e..5231bd7 100644
--- a/third_party/blink/renderer/core/html/link_style.cc
+++ b/third_party/blink/renderer/core/html/link_style.cc
@@ -45,7 +45,6 @@
       disabled_state_(kUnset),
       pending_sheet_type_(kNone),
       loading_(false),
-      fired_load_(false),
       loaded_sheet_(false) {}
 
 LinkStyle::~LinkStyle() = default;
@@ -145,12 +144,9 @@
 
 void LinkStyle::NotifyLoadedSheetAndAllCriticalSubresources(
     Node::LoadedSheetErrorStatus error_status) {
-  if (fired_load_)
-    return;
   loaded_sheet_ = (error_status == Node::kNoErrorLoadingSubresource);
   if (owner_)
     owner_->ScheduleEvent();
-  fired_load_ = true;
 }
 
 void LinkStyle::StartLoadingDynamicSheet() {
diff --git a/third_party/blink/renderer/core/html/link_style.h b/third_party/blink/renderer/core/html/link_style.h
index 22e838ca..f8ba8b8 100644
--- a/third_party/blink/renderer/core/html/link_style.h
+++ b/third_party/blink/renderer/core/html/link_style.h
@@ -79,7 +79,6 @@
   PendingSheetType pending_sheet_type_;
   StyleEngineContext style_engine_context_;
   bool loading_;
-  bool fired_load_;
   bool loaded_sheet_;
 };
 
diff --git a/third_party/blink/renderer/core/html/media/autoplay_policy.cc b/third_party/blink/renderer/core/html/media/autoplay_policy.cc
index 9153dd2..41583e78 100644
--- a/third_party/blink/renderer/core/html/media/autoplay_policy.cc
+++ b/third_party/blink/renderer/core/html/media/autoplay_policy.cc
@@ -38,20 +38,6 @@
 const char kErrorAutoplayFuncMobile[] =
     "play() can only be initiated by a user gesture.";
 
-// Returns whether |document| is whitelisted for autoplay. If true, the user
-// gesture lock will be initilized as false, indicating that the element is
-// allowed to autoplay unmuted without user gesture.
-bool IsDocumentWhitelisted(const Document& document) {
-  DCHECK(document.GetSettings());
-
-  const String& web_app_scope = document.GetSettings()->GetWebAppScope();
-  if (web_app_scope.IsNull() || web_app_scope.IsEmpty())
-    return false;
-
-  DCHECK_EQ(KURL(web_app_scope).GetString(), web_app_scope);
-  return document.Url().GetString().StartsWith(web_app_scope);
-}
-
 // Return true if and only if the document settings specifies media playback
 // requires user gesture on the element.
 bool ComputeLockPendingUserGestureRequired(const Document& document) {
@@ -79,7 +65,7 @@
   if (!document.GetSettings())
     return Type::kNoUserGestureRequired;
 
-  if (IsDocumentWhitelisted(document))
+  if (document.IsInWebAppScope())
     return Type::kNoUserGestureRequired;
 
   if (DocumentHasUserExceptionFlag(document))
diff --git a/third_party/blink/renderer/core/html/portal/html_portal_element.cc b/third_party/blink/renderer/core/html/portal/html_portal_element.cc
index 87081ef..ef7b239 100644
--- a/third_party/blink/renderer/core/html/portal/html_portal_element.cc
+++ b/third_party/blink/renderer/core/html/portal/html_portal_element.cc
@@ -53,25 +53,15 @@
   ScriptPromise promise = resolver->Promise();
 
   if (portal_ptr_) {
-    portal_ptr_->Activate(WTF::Bind(
-        [](HTMLPortalElement* portal, ScriptPromiseResolver* resolver,
-           mojom::blink::PortalActivationStatus result) {
-          switch (result) {
-            case mojom::blink::PortalActivationStatus::kNotSupported:
-              resolver->Reject(
-                  DOMException::Create(DOMExceptionCode::kNotSupportedError,
-                                       "Portal activation is not supported."));
-              break;
-
-            case mojom::blink::PortalActivationStatus::kSuccess:
-              resolver->Resolve();
-              break;
-
-            default:
-              NOTREACHED();
-          }
-        },
-        WrapPersistent(this), WrapPersistent(resolver)));
+    // The HTMLPortalElement is bound as a persistent so that it won't get
+    // garbage collected while there is a pending callback. This is necessary
+    // because the HTMLPortalElement owns the mojo interface, so if it were
+    // garbage collected the callback would never be called and the promise
+    // would never be resolved.
+    portal_ptr_->Activate(
+        WTF::Bind([](HTMLPortalElement* portal,
+                     ScriptPromiseResolver* resolver) { resolver->Resolve(); },
+                  WrapPersistent(this), WrapPersistent(resolver)));
   } else {
     resolver->Reject(DOMException::Create(
         DOMExceptionCode::kInvalidStateError,
@@ -134,4 +124,11 @@
   return new LayoutIFrame(this);
 }
 
+void HTMLPortalElement::AttachLayoutTree(AttachContext& context) {
+  HTMLFrameOwnerElement::AttachLayoutTree(context);
+
+  if (GetLayoutEmbeddedContent() && ContentFrame())
+    SetEmbeddedContentView(ContentFrame()->View());
+}
+
 }  // namespace blink
diff --git a/third_party/blink/renderer/core/html/portal/html_portal_element.h b/third_party/blink/renderer/core/html/portal/html_portal_element.h
index d1ac773..c08b655a 100644
--- a/third_party/blink/renderer/core/html/portal/html_portal_element.h
+++ b/third_party/blink/renderer/core/html/portal/html_portal_element.h
@@ -61,6 +61,7 @@
   ParsedFeaturePolicy ConstructContainerPolicy(Vector<String>*) const override {
     return ParsedFeaturePolicy();
   }
+  void AttachLayoutTree(AttachContext& context) override;
 
   // Uniquely identifies the portal, this token is used by the browser process
   // to reference this portal when communicating with the renderer.
diff --git a/third_party/blink/renderer/core/layout/layout_box.cc b/third_party/blink/renderer/core/layout/layout_box.cc
index 592726f..2f0d5ca 100644
--- a/third_party/blink/renderer/core/layout/layout_box.cc
+++ b/third_party/blink/renderer/core/layout/layout_box.cc
@@ -301,21 +301,6 @@
     }
   }
 
-  // Our opaqueness might have changed without triggering layout.
-  if (diff.NeedsFullPaintInvalidation()) {
-    // Invalidate self.
-    InvalidateBackgroundObscurationStatus();
-    LayoutObject* parent_to_invalidate = Parent();
-    // Also invalidate up to kBackgroundObscurationTestMaxDepth parents.
-    // This constant corresponds to a descendant walk of the same depth;
-    // see ComputeBackgroundIsKnownToBeObscured.
-    for (unsigned i = 0;
-         i < kBackgroundObscurationTestMaxDepth && parent_to_invalidate; ++i) {
-      parent_to_invalidate->InvalidateBackgroundObscurationStatus();
-      parent_to_invalidate = parent_to_invalidate->Parent();
-    }
-  }
-
   UpdateShapeOutsideInfoAfterStyleChange(*Style(), old_style);
   UpdateGridPositionAfterStyleChange(old_style);
 
@@ -752,8 +737,6 @@
 }
 
 void LayoutBox::UpdateAfterLayout() {
-  InvalidateBackgroundObscurationStatus();
-
   // Transform-origin depends on box size, so we need to update the layer
   // transform after layout.
   if (HasLayer()) {
@@ -1892,7 +1875,6 @@
     for (const FillLayer* layer = &StyleRef().BackgroundLayers(); layer;
          layer = layer->Next()) {
       if (layer->GetImage() && image == layer->GetImage()->Data()) {
-        InvalidateBackgroundObscurationStatus();
         bool maybe_animated =
             layer->GetImage()->CachedImage() &&
             layer->GetImage()->CachedImage()->GetImage() &&
@@ -1977,6 +1959,12 @@
 void LayoutBox::EnsureIsReadyForPaintInvalidation() {
   LayoutBoxModelObject::EnsureIsReadyForPaintInvalidation();
 
+  bool new_obscured = ComputeBackgroundIsKnownToBeObscured();
+  if (BackgroundIsKnownToBeObscured() != new_obscured) {
+    SetBackgroundIsKnownToBeObscured(new_obscured);
+    SetBackgroundNeedsFullPaintInvalidation();
+  }
+
   if (MayNeedPaintInvalidationAnimatedBackgroundImage() &&
       !BackgroundIsKnownToBeObscured()) {
     SetBackgroundNeedsFullPaintInvalidation();
diff --git a/third_party/blink/renderer/core/layout/layout_box.h b/third_party/blink/renderer/core/layout/layout_box.h
index d4252aa2..0d1df0c5 100644
--- a/third_party/blink/renderer/core/layout/layout_box.h
+++ b/third_party/blink/renderer/core/layout/layout_box.h
@@ -1562,6 +1562,9 @@
       LayoutUnit& min_logical_width,
       LayoutUnit& max_logical_width) const;
 
+  // Make it public.
+  using LayoutObject::BackgroundIsKnownToBeObscured;
+
  protected:
   ~LayoutBox() override;
 
@@ -1590,13 +1593,11 @@
 
   // Returns false if it could not cheaply compute the extent (e.g. fixed
   // background), in which case the returned rect may be incorrect.
-  // FIXME: make this a const method once the LayoutBox reference in BoxPainter
-  // is const.
   bool GetBackgroundPaintedExtent(LayoutRect&) const;
   virtual bool ForegroundIsKnownToBeOpaqueInRect(
       const LayoutRect& local_rect,
       unsigned max_depth_to_test) const;
-  bool ComputeBackgroundIsKnownToBeObscured() const override;
+  virtual bool ComputeBackgroundIsKnownToBeObscured() const;
 
   virtual void ComputePositionedLogicalWidth(
       LogicalExtentComputedValues&) const;
diff --git a/third_party/blink/renderer/core/layout/layout_box_test.cc b/third_party/blink/renderer/core/layout/layout_box_test.cc
index 13af1a91..f9cc589 100644
--- a/third_party/blink/renderer/core/layout/layout_box_test.cc
+++ b/third_party/blink/renderer/core/layout/layout_box_test.cc
@@ -39,9 +39,8 @@
     <div class='column'> <div> <div id='target' class='white-background'>
     <div class='black-background'></div> </div> </div> </div>
   )HTML");
-  LayoutObject* layout_object = GetLayoutObjectByElementId("target");
-  ASSERT_TRUE(layout_object);
-  ASSERT_TRUE(layout_object->BackgroundIsKnownToBeObscured());
+  const auto* target = GetLayoutBoxByElementId("target");
+  EXPECT_TRUE(target->BackgroundIsKnownToBeObscured());
 }
 
 TEST_P(LayoutBoxTest, BackgroundNotObscuredWithCssClippedChild) {
@@ -66,10 +65,10 @@
       <div id="child"></div>
     </div>
   )HTML");
-  auto* child = GetLayoutObjectByElementId("child");
+  auto* child = GetLayoutBoxByElementId("child");
   EXPECT_FALSE(child->BackgroundIsKnownToBeObscured());
 
-  auto* parent = GetLayoutObjectByElementId("parent");
+  auto* parent = GetLayoutBoxByElementId("parent");
   EXPECT_FALSE(parent->BackgroundIsKnownToBeObscured());
 }
 
@@ -102,13 +101,13 @@
       </div>
     </div>
   )HTML");
-  auto* grandchild = GetLayoutObjectByElementId("grandchild");
+  auto* grandchild = GetLayoutBoxByElementId("grandchild");
   EXPECT_FALSE(grandchild->BackgroundIsKnownToBeObscured());
 
-  auto* child = GetLayoutObjectByElementId("child");
+  auto* child = GetLayoutBoxByElementId("child");
   EXPECT_FALSE(child->BackgroundIsKnownToBeObscured());
 
-  auto* parent = GetLayoutObjectByElementId("parent");
+  auto* parent = GetLayoutBoxByElementId("parent");
   EXPECT_FALSE(parent->BackgroundIsKnownToBeObscured());
 }
 
diff --git a/third_party/blink/renderer/core/layout/layout_image.cc b/third_party/blink/renderer/core/layout/layout_image.cc
index 23fab7d45..4448000a8 100644
--- a/third_party/blink/renderer/core/layout/layout_image.cc
+++ b/third_party/blink/renderer/core/layout/layout_image.cc
@@ -278,8 +278,6 @@
   if (DocumentBeingDestroyed())
     return;
 
-  InvalidateBackgroundObscurationStatus();
-
   // Check for optimized image policies.
   if (IsHTMLImageElement(GetNode()))
     ValidateImagePolicies();
diff --git a/third_party/blink/renderer/core/layout/layout_object.h b/third_party/blink/renderer/core/layout/layout_object.h
index 6128199..29f9392 100644
--- a/third_party/blink/renderer/core/layout/layout_object.h
+++ b/third_party/blink/renderer/core/layout/layout_object.h
@@ -901,8 +901,6 @@
     return bitfields_.HasBoxDecorationBackground();
   }
 
-  bool BackgroundIsKnownToBeObscured() const;
-
   bool NeedsLayout() const {
     return bitfields_.SelfNeedsLayout() ||
            bitfields_.NormalChildNeedsLayout() ||
@@ -1208,14 +1206,6 @@
 
   void SetHasBoxDecorationBackground(bool);
 
-  enum BackgroundObscurationState {
-    kBackgroundObscurationStatusInvalid,
-    kBackgroundKnownToBeObscured,
-    kBackgroundMayBeVisible,
-  };
-  void InvalidateBackgroundObscurationStatus();
-  virtual bool ComputeBackgroundIsKnownToBeObscured() const { return false; }
-
   void SetIsText() { bitfields_.SetIsText(true); }
   void SetIsBox() { bitfields_.SetIsBox(true); }
   void SetIsAtomicInlineLevel(bool is_atomic_inline_level) {
@@ -2018,9 +2008,6 @@
       layout_object_.fragment_.SetSelectionVisualRect(r);
     }
 
-    void SetPreviousBackgroundObscured(bool b) {
-      layout_object_.bitfields_.SetPreviousBackgroundObscured(b);
-    }
     void SetPreviousBackgroundPaintLocation(BackgroundPaintLocation location) {
       layout_object_.bitfields_.SetPreviousBackgroundPaintLocation(location);
     }
@@ -2134,9 +2121,6 @@
   bool CompositedScrollsWithRespectTo(
       const LayoutBoxModelObject& paint_invalidation_container) const;
 
-  bool PreviousBackgroundObscured() const {
-    return bitfields_.PreviousBackgroundObscured();
-  }
   BackgroundPaintLocation PreviousBackgroundPaintLocation() const {
     return bitfields_.PreviousBackgroundPaintLocation();
   }
@@ -2401,6 +2385,17 @@
     return ToElement(GetNode())->GetDisplayLockContext();
   }
 
+  bool BackgroundIsKnownToBeObscured() const {
+    DCHECK_GE(GetDocument().Lifecycle().GetState(),
+              DocumentLifecycle::kInPrePaint);
+    return bitfields_.BackgroundIsKnownToBeObscured();
+  }
+  void SetBackgroundIsKnownToBeObscured(bool b) {
+    DCHECK_EQ(GetDocument().Lifecycle().GetState(),
+              DocumentLifecycle::kInPrePaint);
+    bitfields_.SetBackgroundIsKnownToBeObscured(b);
+  }
+
  private:
   // Used only by applyFirstLineChanges to get a first line style based off of a
   // given new style, without accessing the cache.
@@ -2588,7 +2583,7 @@
           children_inline_(false),
           contains_inline_with_outline_and_continuation_(false),
           always_create_line_boxes_for_layout_inline_(false),
-          previous_background_obscured_(false),
+          background_is_known_to_be_obscured_(false),
           is_background_attachment_fixed_object_(false),
           is_scroll_anchor_object_(false),
           scroll_anchor_disabling_style_changed_(false),
@@ -2605,7 +2600,6 @@
           pending_update_first_line_image_observers_(false),
           positioned_state_(kIsStaticallyPositioned),
           selection_state_(static_cast<unsigned>(SelectionState::kNone)),
-          background_obscuration_state_(kBackgroundObscurationStatusInvalid),
           subtree_paint_property_update_reasons_(
               static_cast<unsigned>(SubtreePaintPropertyUpdateReason::kNone)),
           previous_background_paint_location_(0) {}
@@ -2783,9 +2777,10 @@
     ADD_BOOLEAN_BITFIELD(always_create_line_boxes_for_layout_inline_,
                          AlwaysCreateLineBoxesForLayoutInline);
 
-    // Background obscuration status of the previous frame.
-    ADD_BOOLEAN_BITFIELD(previous_background_obscured_,
-                         PreviousBackgroundObscured);
+    // For LayoutBox to cache the result of LayoutBox::
+    // ComputeBackgroundIsKnownToBeObscured(). It's updated during PrePaint.
+    ADD_BOOLEAN_BITFIELD(background_is_known_to_be_obscured_,
+                         BackgroundIsKnownToBeObscured);
 
     ADD_BOOLEAN_BITFIELD(is_background_attachment_fixed_object_,
                          IsBackgroundAttachmentFixedObject);
@@ -2850,9 +2845,6 @@
     // (see ComputedStyle::position).
     unsigned positioned_state_ : 2;  // PositionedState
     unsigned selection_state_ : 3;   // SelectionState
-    // Mutable for getter which lazily update this field.
-    mutable unsigned
-        background_obscuration_state_ : 2;  // BackgroundObscurationState
 
     // Reasons for the full subtree invalidation.
     unsigned subtree_paint_property_update_reasons_
@@ -2926,16 +2918,6 @@
           static_cast<unsigned>(SubtreePaintPropertyUpdateReason::kNone);
     }
 
-    ALWAYS_INLINE BackgroundObscurationState
-    GetBackgroundObscurationState() const {
-      return static_cast<BackgroundObscurationState>(
-          background_obscuration_state_);
-    }
-    ALWAYS_INLINE void SetBackgroundObscurationState(
-        BackgroundObscurationState s) const {
-      background_obscuration_state_ = s;
-    }
-
     ALWAYS_INLINE BackgroundPaintLocation
     PreviousBackgroundPaintLocation() const {
       return static_cast<BackgroundPaintLocation>(
@@ -3098,24 +3080,6 @@
     return;
 
   bitfields_.SetHasBoxDecorationBackground(b);
-  InvalidateBackgroundObscurationStatus();
-}
-
-inline void LayoutObject::InvalidateBackgroundObscurationStatus() {
-  bitfields_.SetBackgroundObscurationState(kBackgroundObscurationStatusInvalid);
-}
-
-DISABLE_CFI_PERF
-inline bool LayoutObject::BackgroundIsKnownToBeObscured() const {
-  if (bitfields_.GetBackgroundObscurationState() ==
-      kBackgroundObscurationStatusInvalid) {
-    BackgroundObscurationState state = ComputeBackgroundIsKnownToBeObscured()
-                                           ? kBackgroundKnownToBeObscured
-                                           : kBackgroundMayBeVisible;
-    bitfields_.SetBackgroundObscurationState(state);
-  }
-  return bitfields_.GetBackgroundObscurationState() ==
-         kBackgroundKnownToBeObscured;
 }
 
 inline void MakeMatrixRenderable(TransformationMatrix& matrix,
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.cc b/third_party/blink/renderer/core/loader/base_fetch_context.cc
index 8eb50a3..5a7fa1cb 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.cc
@@ -181,17 +181,15 @@
   return blocked_reason;
 }
 
-bool BaseFetchContext::IsAdResource(
-    const KURL& resource_url,
-    ResourceType type,
-    mojom::RequestContextType request_context) const {
+bool BaseFetchContext::CalculateIfAdSubresource(const ResourceRequest& request,
+                                                ResourceType type) {
+  // A base class should override this is they have more signals than just the
+  // SubresourceFilter.
   SubresourceFilter* filter = GetSubresourceFilter();
 
-  // We do not need main document tagging currently so skipping main resources.
-  if (filter)
-    return filter->IsAdResource(resource_url, request_context);
-
-  return false;
+  return request.IsAdResource() ||
+         (filter &&
+          filter->IsAdResource(request.Url(), request.GetRequestContext()));
 }
 
 void BaseFetchContext::PrintAccessDeniedMessage(const KURL& url) const {
diff --git a/third_party/blink/renderer/core/loader/base_fetch_context.h b/third_party/blink/renderer/core/loader/base_fetch_context.h
index 4e69f48..39b9cd6 100644
--- a/third_party/blink/renderer/core/loader/base_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/base_fetch_context.h
@@ -54,9 +54,8 @@
   virtual std::unique_ptr<WebSocketHandshakeThrottle>
   CreateWebSocketHandshakeThrottle() = 0;
 
-  bool IsAdResource(const KURL&,
-                    ResourceType,
-                    mojom::RequestContextType) const override;
+  bool CalculateIfAdSubresource(const ResourceRequest& resource_request,
+                                ResourceType type) override;
 
  protected:
   BaseFetchContext() = default;
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.cc b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
index ed513ac..1eca168 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.cc
@@ -55,6 +55,7 @@
 #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_core.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/fileapi/public_url_manager.h"
+#include "third_party/blink/renderer/core/frame/ad_tracker.h"
 #include "third_party/blink/renderer/core/frame/deprecation.h"
 #include "third_party/blink/renderer/core/frame/frame_console.h"
 #include "third_party/blink/renderer/core/frame/local_dom_window.h"
@@ -1254,6 +1255,24 @@
   return ResourceLoadPriority::kLowest;
 }
 
+bool FrameFetchContext::CalculateIfAdSubresource(
+    const ResourceRequest& resource_request,
+    ResourceType type) {
+  // Mark the resource as an Ad if the SubresourceFilter thinks it's an ad.
+  bool known_ad =
+      BaseFetchContext::CalculateIfAdSubresource(resource_request, type);
+  if (GetResourceFetcherProperties().IsDetached() ||
+      !GetFrame()->GetAdTracker()) {
+    return known_ad;
+  }
+
+  // The AdTracker needs to know about the request as well, and may also mark it
+  // as an ad.
+  return GetFrame()->GetAdTracker()->CalculateIfAdSubresource(
+      frame_or_imported_document_->GetDocument(), resource_request, type,
+      known_ad);
+}
+
 void FrameFetchContext::DispatchNetworkQuiet() {
   if (WebServiceWorkerNetworkProvider* service_worker_network_provider =
           MasterDocumentLoader()->GetServiceWorkerNetworkProvider()) {
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context.h b/third_party/blink/renderer/core/loader/frame_fetch_context.h
index 550402e..93a0759 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context.h
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context.h
@@ -146,6 +146,10 @@
 
   ResourceLoadPriority ModifyPriorityForExperiments(
       ResourceLoadPriority) const override;
+
+  bool CalculateIfAdSubresource(const ResourceRequest& resource_request,
+                                ResourceType type) override;
+
   void DispatchNetworkQuiet() override;
 
  private:
diff --git a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
index 6ed04e5..b82a96a4 100644
--- a/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
+++ b/third_party/blink/renderer/core/loader/frame_fetch_context_test.cc
@@ -228,10 +228,9 @@
       bool expect_is_ad) {
     base::Optional<ResourceRequestBlockedReason> reason =
         CanRequestInternal(SecurityViolationReportingPolicy::kReport);
-    const KURL url("http://example.com/");
-    EXPECT_EQ(expect_is_ad, GetFetchContext()->IsAdResource(
-                                url, ResourceType::kMock,
-                                mojom::RequestContextType::UNSPECIFIED));
+    ResourceRequest request(KURL("http://example.com/"));
+    EXPECT_EQ(expect_is_ad, GetFetchContext()->CalculateIfAdSubresource(
+                                request, ResourceType::kMock));
     return reason;
   }
 
diff --git a/third_party/blink/renderer/core/paint/object_paint_invalidator.cc b/third_party/blink/renderer/core/paint/object_paint_invalidator.cc
index e5cff41..b65003b 100644
--- a/third_party/blink/renderer/core/paint/object_paint_invalidator.cc
+++ b/third_party/blink/renderer/core/paint/object_paint_invalidator.cc
@@ -218,21 +218,12 @@
 ObjectPaintInvalidatorWithContext::ComputePaintInvalidationReason() {
   // This is before any early return to ensure the background obscuration status
   // is saved.
-  bool background_obscuration_changed = false;
-  bool background_obscured = object_.BackgroundIsKnownToBeObscured();
-  if (background_obscured != object_.PreviousBackgroundObscured()) {
-    object_.GetMutableForPainting().SetPreviousBackgroundObscured(
-        background_obscured);
-    background_obscuration_changed = true;
-  }
-
   if (!object_.ShouldCheckForPaintInvalidation() &&
       (!context_.subtree_flags ||
        context_.subtree_flags ==
            PaintInvalidatorContext::kSubtreeVisualRectUpdate)) {
     // No paint invalidation flag, or just kSubtreeVisualRectUpdate (which has
     // been handled in PaintInvalidator). No paint invalidation is needed.
-    DCHECK(!background_obscuration_changed);
     return PaintInvalidationReason::kNone;
   }
 
@@ -249,9 +240,6 @@
       context_.fragment_data->VisualRect().IsEmpty())
     return PaintInvalidationReason::kNone;
 
-  if (background_obscuration_changed)
-    return PaintInvalidationReason::kBackground;
-
   if (object_.PaintedOutputOfObjectHasNoEffectRegardlessOfSize())
     return PaintInvalidationReason::kNone;
 
diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
index 2ea7bcc1..53f6548f 100644
--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
+++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc
@@ -1034,7 +1034,9 @@
   if (&fragment_data_ != &object_.FirstFragment()) {
     // All fragments share the same LinkHighlightEffect node.
     DCHECK(object_.FirstFragment().PaintProperties());
-    DCHECK(object_.FirstFragment().PaintProperties()->LinkHighlightEffect());
+    // TODO(crbug.com/923729): Temporary CHECK to debug the referenced bug. This
+    // should become a DCHECK once the bug is resolved.
+    CHECK(object_.FirstFragment().PaintProperties()->LinkHighlightEffect());
     properties_->SetLinkHighlightEffect(
         object_.FirstFragment().PaintProperties()->LinkHighlightEffect());
     return;
diff --git a/third_party/blink/renderer/core/probe/core_probes.json5 b/third_party/blink/renderer/core/probe/core_probes.json5
index 56e4adf..b76043c 100644
--- a/third_party/blink/renderer/core/probe/core_probes.json5
+++ b/third_party/blink/renderer/core/probe/core_probes.json5
@@ -20,7 +20,6 @@
       probes: [
         "CallFunction",
         "ExecuteScript",
-        "willSendRequest",
       ]
     },
     InspectorAnimationAgent: {
diff --git a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
index f5d29f9b..3039b1c 100644
--- a/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
+++ b/third_party/blink/renderer/modules/picture_in_picture/picture_in_picture_controller_impl.cc
@@ -242,7 +242,6 @@
   DCHECK(GetSupplementable());
 
   // Auto Picture-in-Picture is allowed only in a PWA window.
-  // TODO(crbug.com/922884) It should apply in the scope of the manifest.
   if (!GetSupplementable()->GetFrame() ||
       !GetSupplementable()->GetFrame()->View() ||
       GetSupplementable()->GetFrame()->View()->DisplayMode() ==
@@ -250,6 +249,10 @@
     return;
   }
 
+  // Auto Picture-in-Picture is allowed only in the scope of a PWA.
+  if (!GetSupplementable()->IsInWebAppScope())
+    return;
+
   // If page becomes visible and Picture-in-Picture element has entered
   // automatically Picture-in-Picture and is still eligible to Auto
   // Picture-in-Picture, exit Picture-in-Picture.
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index 09f9eea..ee98ccd2 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -505,10 +505,11 @@
     "drag_image.h",
     "exported/file_path_conversion.cc",
     "exported/interface_registry.cc",
-    "exported/media_stream_audio_source.cc",
-    "exported/media_stream_audio_track.cc",
+    "exported/mediastream/media_stream_audio_source.cc",
+    "exported/mediastream/media_stream_audio_track.cc",
+    "exported/mediastream/web_platform_media_stream_source.cc",
+    "exported/mediastream/web_platform_media_stream_track.cc",
     "exported/platform.cc",
-    "exported/platform_media_stream_source.cc",
     "exported/service_registry.cc",
     "exported/url_conversion.cc",
     "exported/web_audio_bus.cc",
@@ -554,7 +555,6 @@
     "exported/web_memory_coordinator.cc",
     "exported/web_mixed_content.cc",
     "exported/web_network_state_notifier.cc",
-    "exported/web_platform_media_stream_track.cc",
     "exported/web_prerender.cc",
     "exported/web_prerendering_support.cc",
     "exported/web_rtc_answer_options.cc",
diff --git a/third_party/blink/renderer/platform/exported/DEPS b/third_party/blink/renderer/platform/exported/DEPS
index 3ab3617..53f699c 100644
--- a/third_party/blink/renderer/platform/exported/DEPS
+++ b/third_party/blink/renderer/platform/exported/DEPS
@@ -1,6 +1,5 @@
 
 include_rules = [
-    "+media/base/audio_bus.h",
     "+net/cookies/canonical_cookie.h",
     "+net/cookies/cookie_constants.h",
 ]
diff --git a/third_party/blink/renderer/platform/exported/mediastream/DEPS b/third_party/blink/renderer/platform/exported/mediastream/DEPS
new file mode 100644
index 0000000..36f8289a
--- /dev/null
+++ b/third_party/blink/renderer/platform/exported/mediastream/DEPS
@@ -0,0 +1,4 @@
+
+include_rules = [
+    "+media/base/audio_bus.h",
+]
diff --git a/third_party/blink/renderer/platform/exported/media_stream_audio_source.cc b/third_party/blink/renderer/platform/exported/mediastream/media_stream_audio_source.cc
similarity index 100%
rename from third_party/blink/renderer/platform/exported/media_stream_audio_source.cc
rename to third_party/blink/renderer/platform/exported/mediastream/media_stream_audio_source.cc
diff --git a/third_party/blink/renderer/platform/exported/media_stream_audio_track.cc b/third_party/blink/renderer/platform/exported/mediastream/media_stream_audio_track.cc
similarity index 100%
rename from third_party/blink/renderer/platform/exported/media_stream_audio_track.cc
rename to third_party/blink/renderer/platform/exported/mediastream/media_stream_audio_track.cc
diff --git a/third_party/blink/renderer/platform/exported/platform_media_stream_source.cc b/third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_source.cc
similarity index 98%
rename from third_party/blink/renderer/platform/exported/platform_media_stream_source.cc
rename to third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_source.cc
index 343a7a8..817da4ff 100644
--- a/third_party/blink/renderer/platform/exported/platform_media_stream_source.cc
+++ b/third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_source.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 "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 
 #include "third_party/blink/renderer/platform/mediastream/media_stream_source.h"
 
diff --git a/third_party/blink/renderer/platform/exported/web_platform_media_stream_track.cc b/third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_track.cc
similarity index 100%
rename from third_party/blink/renderer/platform/exported/web_platform_media_stream_track.cc
rename to third_party/blink/renderer/platform/exported/mediastream/web_platform_media_stream_track.cc
diff --git a/third_party/blink/renderer/platform/exported/platform.cc b/third_party/blink/renderer/platform/exported/platform.cc
index 63703db..20b35af 100644
--- a/third_party/blink/renderer/platform/exported/platform.cc
+++ b/third_party/blink/renderer/platform/exported/platform.cc
@@ -250,14 +250,6 @@
   return g_platform;
 }
 
-Thread* Platform::MainThread() {
-  return Thread::MainThread();
-}
-
-Thread* Platform::CurrentThread() {
-  return Thread::Current();
-}
-
 service_manager::Connector* Platform::GetConnector() {
   DEFINE_STATIC_LOCAL(DefaultConnector, connector, ());
   return connector.Get();
@@ -281,21 +273,13 @@
   return Thread::CreateThread(params);
 }
 
-std::unique_ptr<Thread> Platform::CreateWebAudioThread() {
-  return Thread::CreateWebAudioThread();
-}
-
 void Platform::CreateAndSetCompositorThread() {
   Thread::CreateAndSetCompositorThread();
 }
 
-Thread* Platform::CompositorThread() {
-  return Thread::CompositorThread();
-}
-
 scoped_refptr<base::SingleThreadTaskRunner>
 Platform::CompositorThreadTaskRunner() {
-  if (Thread* compositor_thread = CompositorThread())
+  if (Thread* compositor_thread = Thread::CompositorThread())
     return compositor_thread->GetTaskRunner();
   return nullptr;
 }
diff --git a/third_party/blink/renderer/platform/exported/web_media_stream_source.cc b/third_party/blink/renderer/platform/exported/web_media_stream_source.cc
index d45ddd5f..396ba52 100644
--- a/third_party/blink/renderer/platform/exported/web_media_stream_source.cc
+++ b/third_party/blink/renderer/platform/exported/web_media_stream_source.cc
@@ -67,13 +67,6 @@
 
 void WebMediaStreamSource::Initialize(const WebString& id,
                                       Type type,
-                                      const WebString& name) {
-  private_ = MediaStreamSource::Create(
-      id, static_cast<MediaStreamSource::StreamType>(type), name, false);
-}
-
-void WebMediaStreamSource::Initialize(const WebString& id,
-                                      Type type,
                                       const WebString& name,
                                       bool remote) {
   private_ = MediaStreamSource::Create(
diff --git a/third_party/blink/renderer/platform/fonts/font_global_context.cc b/third_party/blink/renderer/platform/fonts/font_global_context.cc
index ae6358c..20f6dd7 100644
--- a/third_party/blink/renderer/platform/fonts/font_global_context.cc
+++ b/third_party/blink/renderer/platform/fonts/font_global_context.cc
@@ -38,9 +38,4 @@
   GetFontCache().Invalidate();
 }
 
-void FontGlobalContext::ClearForTesting() {
-  FontGlobalContext* ctx = Get();
-  ctx->font_cache_.Invalidate();
-}
-
 }  // namespace blink
diff --git a/third_party/blink/renderer/platform/fonts/font_global_context.h b/third_party/blink/renderer/platform/fonts/font_global_context.h
index 4dcb928..7cd6a0b 100644
--- a/third_party/blink/renderer/platform/fonts/font_global_context.h
+++ b/third_party/blink/renderer/platform/fonts/font_global_context.h
@@ -46,8 +46,6 @@
   // Called by MemoryCoordinator to clear memory.
   static void ClearMemory();
 
-  static void ClearForTesting();
-
  private:
   friend class WTF::ThreadSpecific<FontGlobalContext>;
 
diff --git a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
index c6db1e5..e86ae5b 100644
--- a/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
+++ b/third_party/blink/renderer/platform/graphics/paint/paint_chunker.cc
@@ -28,6 +28,17 @@
 void PaintChunker::UpdateCurrentPaintChunkProperties(
     const base::Optional<PaintChunk::Id>& chunk_id,
     const PropertyTreeState& properties) {
+  // TODO(crbug.com/923729): This is a temporary set of checks to determine the
+  // cause of the referenced bug. At this point we should have all of the
+  // properties given to us. Note that scoped objects that restore previous
+  // properties might put us back into uninitialized properties state, but if
+  // we're not being set to uninitialized then we must have all properties set.
+  if (properties != UninitializedProperties()) {
+    CHECK(properties.Transform());
+    CHECK(properties.Clip());
+    CHECK(properties.Effect());
+  }
+
   // If properties are the same, continue to use the previously set
   // |next_chunk_id_| because the id of the outer painting is likely to be
   // more stable to reduce invalidation because of chunk id changes.
diff --git a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
index 07cd4f3..f070dd5 100644
--- a/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
+++ b/third_party/blink/renderer/platform/graphics/video_frame_submitter.cc
@@ -15,6 +15,7 @@
 #include "media/base/video_frame.h"
 #include "media/base/video_types.h"
 #include "services/viz/public/interfaces/compositing/compositor_frame_sink.mojom-blink.h"
+#include "services/viz/public/interfaces/hit_test/hit_test_region_list.mojom-blink.h"
 #include "services/ws/public/cpp/gpu/context_provider_command_buffer.h"
 #include "third_party/blink/public/platform/interface_provider.h"
 #include "third_party/blink/public/platform/modules/frame_sinks/embedded_frame_sink.mojom-blink.h"
diff --git a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
index 4f2fa79..3fe67b4 100644
--- a/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
+++ b/third_party/blink/renderer/platform/loader/fetch/fetch_context.h
@@ -226,10 +226,10 @@
     return priority;
   }
 
-  // Returns if the |resource_url| is identified as ad.
-  virtual bool IsAdResource(const KURL& resource_url,
-                            ResourceType type,
-                            mojom::RequestContextType request_context) const {
+  // Determine if the request is on behalf of an advertisement. If so, return
+  // true.
+  virtual bool CalculateIfAdSubresource(const ResourceRequest& resource_request,
+                                        ResourceType type) {
     return false;
   }
 
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
index 0957ba4..4e16af2 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_fetcher.cc
@@ -819,10 +819,8 @@
                            reporting_policy,
                            resource_request.GetRedirectStatus());
 
-  if (Context().IsAdResource(url, resource_type,
-                             resource_request.GetRequestContext())) {
+  if (Context().CalculateIfAdSubresource(resource_request, resource_type))
     resource_request.SetIsAdResource();
-  }
 
   if (blocked_reason)
     return blocked_reason;
diff --git a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
index 27c3a1b..0253184e 100644
--- a/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
+++ b/third_party/blink/renderer/platform/loader/fetch/resource_loader.cc
@@ -639,10 +639,8 @@
             resource_type, *new_request, new_url, options, reporting_policy,
             ResourceRequest::RedirectStatus::kFollowedRedirect);
 
-    if (Context().IsAdResource(new_url, resource_type,
-                               new_request->GetRequestContext())) {
+    if (Context().CalculateIfAdSubresource(*new_request, resource_type))
       new_request->SetIsAdResource();
-    }
 
     if (blocked_reason) {
       CancelForRedirectAccessCheckError(new_url, blocked_reason.value());
diff --git a/third_party/blink/renderer/platform/mediastream/media_stream_source.h b/third_party/blink/renderer/platform/mediastream/media_stream_source.h
index e48d994..5071f3a 100644
--- a/third_party/blink/renderer/platform/mediastream/media_stream_source.h
+++ b/third_party/blink/renderer/platform/mediastream/media_stream_source.h
@@ -36,7 +36,7 @@
 #include <utility>
 
 #include "base/optional.h"
-#include "third_party/blink/public/platform/modules/mediastream/platform_media_stream_source.h"
+#include "third_party/blink/public/platform/modules/mediastream/web_platform_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_constraints.h"
 #include "third_party/blink/public/platform/web_media_stream_source.h"
 #include "third_party/blink/public/platform/web_media_stream_track.h"
diff --git a/third_party/blink/renderer/platform/transforms/rotate_transform_operation.cc b/third_party/blink/renderer/platform/transforms/rotate_transform_operation.cc
index 3f8852f..f32cb5a 100644
--- a/third_party/blink/renderer/platform/transforms/rotate_transform_operation.cc
+++ b/third_party/blink/renderer/platform/transforms/rotate_transform_operation.cc
@@ -48,9 +48,6 @@
     const TransformOperation* from,
     double progress,
     bool blend_to_identity) {
-  if (from && !from->IsSameType(*this))
-    return this;
-
   if (blend_to_identity)
     return RotateTransformOperation::Create(
         Rotation(Axis(), Angle() * (1 - progress)), type_);
@@ -60,17 +57,18 @@
     return RotateTransformOperation::Create(
         Rotation(Axis(), Angle() * progress), type_);
 
+  // Apply spherical linear interpolation. Rotate around a common axis if
+  // possible. Otherwise, convert rotations to 4x4 matrix representations and
+  // interpolate the matrix decompositions. The 'from' and 'to' transforms can
+  // be of different types (based on axis), but must both have equivalent
+  // rotate3d representations.
+  DCHECK(from->PrimitiveType() == OperationType::kRotate3D);
+  OperationType type =
+      from->IsSameType(*this) ? type_ : OperationType::kRotate3D;
   const RotateTransformOperation& from_rotate =
       ToRotateTransformOperation(*from);
-  if (GetType() == kRotate3D) {
-    return RotateTransformOperation::Create(
-        Rotation::Slerp(from_rotate.rotation_, rotation_, progress), kRotate3D);
-  }
-
-  DCHECK(Axis() == from_rotate.Axis());
   return RotateTransformOperation::Create(
-      Rotation(Axis(), blink::Blend(from_rotate.Angle(), Angle(), progress)),
-      type_);
+      Rotation::Slerp(from_rotate.rotation_, rotation_, progress), type);
 }
 
 bool RotateTransformOperation::CanBlendWith(
diff --git a/third_party/blink/renderer/platform/transforms/transform_operations.cc b/third_party/blink/renderer/platform/transforms/transform_operations.cc
index 7149321..b511af3 100644
--- a/third_party/blink/renderer/platform/transforms/transform_operations.cc
+++ b/third_party/blink/renderer/platform/transforms/transform_operations.cc
@@ -202,7 +202,7 @@
   if (axis.Dot(to_transform.Axis()) < 0)
     to_degrees *= -1;
 
-  from_degrees = Blend(from_degrees, to_transform.Angle(), min_progress);
+  from_degrees = Blend(from_degrees, to_degrees, min_progress);
   to_degrees = Blend(to_degrees, from_transform.Angle(), 1.0 - max_progress);
   if (from_degrees > to_degrees)
     std::swap(from_degrees, to_degrees);
diff --git a/third_party/blink/renderer/platform/transforms/transform_operations_test.cc b/third_party/blink/renderer/platform/transforms/transform_operations_test.cc
index 288d83c..95c6800 100644
--- a/third_party/blink/renderer/platform/transforms/transform_operations_test.cc
+++ b/third_party/blink/renderer/platform/transforms/transform_operations_test.cc
@@ -297,7 +297,7 @@
 };
 
 TEST(TransformOperationsTest, AbsoluteAnimatedProblematicAxisRotationBounds) {
-  // Zeros in the components of the axis osf rotation turned out to be tricky to
+  // Zeros in the components of the axis of rotation turned out to be tricky to
   // deal with in practice. This function tests some potentially problematic
   // axes to ensure sane behavior.
 
diff --git a/third_party/blink/renderer/platform/wtf/std_lib_extras.h b/third_party/blink/renderer/platform/wtf/std_lib_extras.h
index 94f9924f..4c5bf0e 100644
--- a/third_party/blink/renderer/platform/wtf/std_lib_extras.h
+++ b/third_party/blink/renderer/platform/wtf/std_lib_extras.h
@@ -96,7 +96,7 @@
 #if DCHECK_IS_ON()
         ,
         safely_initialized_(WTF::IsBeforeThreadCreated()),
-        thread_(WTF::internal::CurrentThreadSyscall())
+        thread_(WTF::CurrentThread())
 #endif
   {
     static_assert(!WTF::IsGarbageCollectedType<Type>::value,
@@ -122,7 +122,7 @@
     // keeps being called on the same thread if cross-thread
     // use is not permitted.
     return allow_cross_thread_use || safely_initialized_ ||
-           thread_ == WTF::internal::CurrentThreadSyscall();
+           thread_ == WTF::CurrentThread();
   }
 #endif
   template <typename T, bool is_small = sizeof(T) <= 32>
diff --git a/third_party/blink/renderer/platform/wtf/threading.cc b/third_party/blink/renderer/platform/wtf/threading.cc
index 4728e5e..51dd1b47 100644
--- a/third_party/blink/renderer/platform/wtf/threading.cc
+++ b/third_party/blink/renderer/platform/wtf/threading.cc
@@ -5,102 +5,12 @@
 #include "third_party/blink/renderer/platform/wtf/threading.h"
 
 #include "build/build_config.h"
-#include "third_party/blink/renderer/platform/wtf/date_math.h"
-#include "third_party/blink/renderer/platform/wtf/dtoa/double-conversion.h"
-#include "third_party/blink/renderer/platform/wtf/wtf_thread_data.h"
-
-#if defined(OS_WIN)
-#include <windows.h>
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
-#include <pthread.h>
-#else
-#error Blink does not support threading on your platform.
-#endif
-
-#if defined(OS_LINUX)
-#include <sys/syscall.h>
-#elif defined(OS_ANDROID)
-#include <sys/types.h>
-#endif
 
 namespace WTF {
 
-// Current thread identity
-
-namespace internal {
-
-ThreadIdentifier CurrentThreadSyscall() {
-#if defined(OS_WIN)
-  return static_cast<ThreadIdentifier>(GetCurrentThreadId());
-#elif defined(OS_MACOSX)
-  return pthread_mach_thread_np(pthread_self());
-#elif defined(OS_LINUX)
-  return syscall(__NR_gettid);
-#elif defined(OS_ANDROID)
-  return gettid();
-#else
-  return reinterpret_cast<uintptr_t>(pthread_self());
-#endif
-}
-
-}  // namespace internal
-
-namespace {
-bool g_current_thread_key_initialized = false;
-
-#if defined(OS_WIN)
-DWORD g_current_thread_key;
-void RawCurrentThreadInit() {
-  g_current_thread_key = ::TlsAlloc();
-  CHECK_NE(g_current_thread_key, TLS_OUT_OF_INDEXES);
-}
-void* RawCurrentThreadGet() {
-  return ::TlsGetValue(g_current_thread_key);
-}
-void RawCurrentThreadSet(void* value) {
-  ::TlsSetValue(g_current_thread_key, value);
-}
-#elif defined(OS_POSIX) || defined(OS_FUCHSIA)
-pthread_key_t g_current_thread_key;
-void RawCurrentThreadInit() {
-  int error = pthread_key_create(&g_current_thread_key, nullptr);
-  CHECK(!error);
-}
-void* RawCurrentThreadGet() {
-  return pthread_getspecific(g_current_thread_key);
-}
-void RawCurrentThreadSet(void* value) {
-  pthread_setspecific(g_current_thread_key, value);
-}
-#endif
-}  // namespace
-
-void InitializeCurrentThread() {
-  DCHECK(!g_current_thread_key_initialized);
-  RawCurrentThreadInit();
-  g_current_thread_key_initialized = true;
-}
-
 ThreadIdentifier CurrentThread() {
-  // This doesn't use WTF::ThreadSpecific (e.g. WTFThreadData) because
-  // ThreadSpecific now depends on currentThread. It is necessary to avoid this
-  // or a similar loop:
-  //
-  // CurrentThread
-  // -> WtfThreadData
-  // -> ThreadSpecific::operator*
-  // -> IsMainThread
-  // -> CurrentThread
-  static_assert(sizeof(ThreadIdentifier) <= sizeof(void*),
-                "ThreadIdentifier must fit in a void*.");
-  DCHECK(g_current_thread_key_initialized);
-  void* value = RawCurrentThreadGet();
-  if (UNLIKELY(!value)) {
-    value = reinterpret_cast<void*>(internal::CurrentThreadSyscall());
-    DCHECK(value);
-    RawCurrentThreadSet(value);
-  }
-  return reinterpret_cast<ThreadIdentifier>(value);
+  thread_local ThreadIdentifier g_id = base::PlatformThread::CurrentId();
+  return g_id;
 }
 
 // For debugging only -- whether a non-main thread has been created.
diff --git a/third_party/blink/renderer/platform/wtf/threading.h b/third_party/blink/renderer/platform/wtf/threading.h
index 61ca3d30..4f7aedac 100644
--- a/third_party/blink/renderer/platform/wtf/threading.h
+++ b/third_party/blink/renderer/platform/wtf/threading.h
@@ -33,21 +33,14 @@
 #include <stdint.h>
 
 #include "base/logging.h"
+#include "base/threading/platform_thread.h"
 #include "build/build_config.h"
 #include "third_party/blink/renderer/platform/wtf/type_traits.h"
 #include "third_party/blink/renderer/platform/wtf/wtf_export.h"
 
 namespace WTF {
 
-#if defined(OS_WIN)
-typedef uint32_t ThreadIdentifier;
-#else
-typedef intptr_t ThreadIdentifier;
-#endif
-
-namespace internal {
-WTF_EXPORT ThreadIdentifier CurrentThreadSyscall();
-}  // namespace internal
+using ThreadIdentifier = base::PlatformThreadId;
 
 // Initializes global state required by |currentThread|.
 // Needs to be called once during program execution, before |currentThread|.
diff --git a/third_party/blink/renderer/platform/wtf/wtf.cc b/third_party/blink/renderer/platform/wtf/wtf.cc
index 26f42da5..1d85de8b 100644
--- a/third_party/blink/renderer/platform/wtf/wtf.cc
+++ b/third_party/blink/renderer/platform/wtf/wtf.cc
@@ -73,7 +73,6 @@
   CHECK(!g_initialized);
   g_initialized = true;
   g_is_main_thread = true;
-  InitializeCurrentThread();
   g_main_thread_identifier = CurrentThread();
 
   WTFThreadData::Initialize();
diff --git a/third_party/blink/renderer/platform/wtf/wtf_thread_data.cc b/third_party/blink/renderer/platform/wtf/wtf_thread_data.cc
index 91bdf2e..60ff8e2 100644
--- a/third_party/blink/renderer/platform/wtf/wtf_thread_data.cc
+++ b/third_party/blink/renderer/platform/wtf/wtf_thread_data.cc
@@ -37,7 +37,7 @@
 WTFThreadData::WTFThreadData()
     : atomic_string_table_(new AtomicStringTable),
       cached_converter_icu_(new ICUConverterWrapper),
-      thread_id_(internal::CurrentThreadSyscall()) {}
+      thread_id_(WTF::CurrentThread()) {}
 
 WTFThreadData::~WTFThreadData() = default;
 
diff --git a/third_party/blink/web_tests/NeverFixTests b/third_party/blink/web_tests/NeverFixTests
index 6cde269..c9303fd 100644
--- a/third_party/blink/web_tests/NeverFixTests
+++ b/third_party/blink/web_tests/NeverFixTests
@@ -2174,8 +2174,8 @@
 Bug(none) external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-similar-but-cross-origin-success.sub.html [ WontFix ]
 Bug(none) virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-success.sub.html [ WontFix ]
 Bug(none) virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-similar-but-cross-origin-success.sub.html [ WontFix ]
-Bug(none) external/wpt/wasm/serialization/window-domain-success.sub.html [ WontFix ]
-Bug(none) external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html [ WontFix ]
+Bug(none) external/wpt/wasm/serialization/module/window-domain-success.sub.html [ WontFix ]
+Bug(none) external/wpt/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html [ WontFix ]
 
 # Code cache isolation tests only make sense if either
 # 1) site isolation is disabled or
diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations
index 766dc5a1..fbaee64 100644
--- a/third_party/blink/web_tests/TestExpectations
+++ b/third_party/blink/web_tests/TestExpectations
@@ -142,6 +142,7 @@
 crbug.com/771003 http/tests/security/mixedContent/insecure-iframe-in-main-frame.html [ Failure ]
 crbug.com/771003 virtual/outofblink-cors-ns/http/tests/security/mixedContent/insecure-iframe-in-main-frame.html [ Failure ]
 crbug.com/771003 virtual/outofblink-cors/http/tests/security/mixedContent/insecure-iframe-in-main-frame.html [ Failure ]
+crbug.com/771003 virtual/feature-policy-for-sandbox/http/tests/security/mixedContent/insecure-iframe-in-main-frame.html [ Failure ]
 
 # Tests temporarily disabled with Site Isolation - known differences in product
 # behavior (either accepted for the long-term, or for the short-term):
@@ -153,6 +154,7 @@
 crbug.com/669083 http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture.html [ Failure ]
 crbug.com/669083 virtual/outofblink-cors-ns/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture.html [ Failure ]
 crbug.com/669083 virtual/outofblink-cors/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture.html [ Failure ]
+crbug.com/669083 virtual/feature-policy-for-sandbox/http/tests/security/frameNavigation/xss-DENIED-top-navigation-without-user-gesture.html [ Failure ]
 crbug.com/886588 external/wpt/dom/events/EventListener-addEventListener.sub.window.html [ Failure ]
 
 # Tests temporarily disabled with Site Isolation - uninvestigated bugs:
@@ -168,6 +170,7 @@
 crbug.com/793127 http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html [ Crash ]
 crbug.com/793127 virtual/outofblink-cors-ns/http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html [ Crash ]
 crbug.com/793127 virtual/outofblink-cors/http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html [ Crash ]
+crbug.com/793127 virtual/feature-policy-for-sandbox/http/tests/security/upgrade-insecure-requests/iframe-upgrade.https.html [ Crash ]
 crbug.com/801992 http/tests/misc/iframe-script-modify-attr.html [ Pass Crash ]
 crbug.com/807675 http/tests/images/image-decode-in-frame.html [ Crash Failure ]
 crbug.com/819800 external/wpt/payment-request/allowpaymentrequest/setting-allowpaymentrequest-timing.https.sub.html [ Failure ]
@@ -2054,6 +2057,7 @@
 crbug.com/518883 crbug.com/390452 http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
 crbug.com/518883 crbug.com/390452 virtual/outofblink-cors/http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
 crbug.com/518883 crbug.com/390452 virtual/outofblink-cors-ns/http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
+crbug.com/518883 crbug.com/390452 virtual/feature-policy-for-sandbox/http/tests/security/isolatedWorld/media-query-wrapper-leaks.html [ Failure Pass Timeout ]
 crbug.com/518987 http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
 crbug.com/518987 virtual/outofblink-cors/http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
 crbug.com/518987 virtual/outofblink-cors-ns/http/tests/xmlhttprequest/navigation-abort-detaches-frame.html [ Pass Timeout ]
@@ -2455,6 +2459,10 @@
 crbug.com/528062 [ Win ] virtual/outofblink-cors-ns/http/tests/security/contentSecurityPolicy/cached-frame-csp.html [ Failure ]
 crbug.com/528062 [ Win ] virtual/outofblink-cors-ns/http/tests/security/xssAuditor/cached-frame.html [ Failure ]
 crbug.com/528062 [ Win ] virtual/outofblink-cors-ns/http/tests/security/xssAuditor/chunked-big-script.html [ Failure ]
+crbug.com/528062 [ Win ] virtual/feature-policy-for-sandbox/http/tests/security/XFrameOptions/x-frame-options-cached.html [ Failure ]
+crbug.com/528062 [ Win ] virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/cached-frame-csp.html [ Failure ]
+crbug.com/528062 [ Win ] virtual/feature-policy-for-sandbox/http/tests/security/xssAuditor/cached-frame.html [ Failure ]
+crbug.com/528062 [ Win ] virtual/feature-policy-for-sandbox/http/tests/security/xssAuditor/chunked-big-script.html [ Failure ]
 
 # When drawing subpixel smoothed glyphs, CoreGraphics will fake bold the glyphs.
 # In this configuration, the pixel smoothed glyphs will be created from subpixel smoothed glyphs.
@@ -2736,6 +2744,7 @@
 crbug.com/501659 http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
 crbug.com/501659 virtual/outofblink-cors/http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
 crbug.com/501659 virtual/outofblink-cors-ns/http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
+crbug.com/501659 virtual/feature-policy-for-sandbox/http/tests/security/xss-DENIED-xml-external-entity.xhtml [ Failure ]
 crbug.com/501659 fast/css/stylesheet-candidate-nodes-crash.xhtml [ Failure ]
 
 crbug.com/545140 [ Mac ] fast/encoding/denormalised-voiced-japanese-chars.html [ Failure ]
@@ -2860,6 +2869,7 @@
 crbug.com/763830 http/tests/security/cors-rfc1918/ [ Skip ]
 crbug.com/763830 virtual/outofblink-cors/http/tests/security/cors-rfc1918/ [ Skip ]
 crbug.com/763830 virtual/outofblink-cors-ns/http/tests/security/cors-rfc1918/ [ Skip ]
+crbug.com/763830 virtual/feature-policy-for-sandbox/http/tests/security/cors-rfc1918/ [ Skip ]
 
 crbug.com/831729 external/wpt/event-timing/event-timing-crossiframe.html [ Timeout ]
 crbug.com/831729 external/wpt/event-timing/event-timing-observer-manual.html [ Skip ]
@@ -4383,6 +4393,7 @@
 crbug.com/610835 http/tests/security/XFrameOptions/x-frame-options-deny-multiple-clients.html [ Failure Pass ]
 crbug.com/610835 virtual/outofblink-cors/http/tests/security/XFrameOptions/x-frame-options-deny-multiple-clients.html [ Failure Pass ]
 crbug.com/610835 virtual/outofblink-cors-ns/http/tests/security/XFrameOptions/x-frame-options-deny-multiple-clients.html [ Failure Pass ]
+crbug.com/610835 virtual/feature-policy-for-sandbox/http/tests/security/XFrameOptions/x-frame-options-deny-multiple-clients.html [ Failure Pass ]
 
 # Added 2016-12-12
 crbug.com/673539 [ Linux ] css3/filters/effect-contrast-hw.html [ Pass Failure ]
@@ -4527,11 +4538,11 @@
 
 crbug.com/874302 virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success-and-failure.html [ Timeout ]
 crbug.com/874302 virtual/sharedarraybuffer/external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/broadcastchannel-success.html [ Timeout ]
-crbug.com/874302 external/wpt/wasm/serialization/broadcastchannel-success-and-failure.html [ Timeout ]
-crbug.com/874302 external/wpt/wasm/serialization/broadcastchannel-success.html [ Timeout ]
+crbug.com/874302 external/wpt/wasm/serialization/module/broadcastchannel-success-and-failure.html [ Timeout ]
+crbug.com/874302 external/wpt/wasm/serialization/module/broadcastchannel-success.html [ Timeout ]
 
-crbug.com/877286 external/wpt/wasm/serialization/no-transferring.html [ Failure ]
-crbug.com/877296 external/wpt/wasm/serialization/window-serviceworker-failure.https.html [ Failure ]
+crbug.com/877286 external/wpt/wasm/serialization/module/no-transferring.html [ Failure ]
+crbug.com/877296 external/wpt/wasm/serialization/module/window-serviceworker-failure.https.html [ Failure ]
 
 crbug.com/831509 external/wpt/service-workers/service-worker/skip-waiting-installed.https.html [ Failure Pass ]
 crbug.com/831509 virtual/navigation-mojo-response/external/wpt/service-workers/service-worker/skip-waiting-installed.https.html [ Failure Pass ]
@@ -4560,6 +4571,8 @@
 crbug.com/724027 virtual/outofblink-cors/http/tests/security/contentSecurityPolicy/source-list-parsing-04.html [ Skip ]
 crbug.com/724027 virtual/outofblink-cors-ns/http/tests/security/contentSecurityPolicy/directive-parsing-03.html [ Skip ]
 crbug.com/724027 virtual/outofblink-cors-ns/http/tests/security/contentSecurityPolicy/source-list-parsing-04.html [ Skip ]
+crbug.com/724027 virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/directive-parsing-03.html [ Skip ]
+crbug.com/724027 virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/source-list-parsing-04.html [ Skip ]
 
 # Sheriff failures 2017-05-16
 crbug.com/722212 fast/events/pointerevents/mouse-pointer-event-properties.html [ Failure Timeout Pass ]
@@ -4659,6 +4672,7 @@
 crbug.com/708994 http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
 crbug.com/708994 virtual/outofblink-cors/http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
 crbug.com/708994 virtual/outofblink-cors-ns/http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
+crbug.com/708994 virtual/feature-policy-for-sandbox/http/tests/security/cross-frame-mouse-source-capabilities.html [ Timeout Pass ]
 
 crbug.com/745887 [ Mac ] fast/frames/sandboxed-iframe-plugins.html [ Failure Pass ]
 crbug.com/745887 [ Win ] fast/frames/sandboxed-iframe-plugins.html [ Failure Pass ]
@@ -5889,13 +5903,18 @@
 crbug.com/921719 virtual/prefer_compositing_to_lcd_text/scrollbars/mock-scrollbars.html [ Skip ]
 crbug.com/921719 virtual/threaded/printing/offscreencanvas-2d-printing.html [ Skip ]
 crbug.com/921719 virtual/threaded/printing/offscreencanvas-webgl-printing.html [ Skip ]
+crbug.com/922951 animations/direction-and-fill/fill-mode-iteration-count-non-integer.html [ Skip ]
+crbug.com/922951 animations/direction-and-fill/fill-mode-missing-from-to-keyframes.html [ Skip ]
 crbug.com/922951 animations/direction-and-fill/fill-mode-transform.html [ Skip ]
 crbug.com/922951 animations/direction-and-fill/fill-mode.html [ Skip ]
 crbug.com/922951 compositing/overflow/overflow-scroll-with-local-background.html [ Skip ]
+crbug.com/922951 external/wpt/longtask-timing/longtask-in-sibling-iframe-crossorigin.html [ Skip ]
 crbug.com/922951 external/wpt/pointerevents/pointerevent_capture_mouse.html [ Skip ]
 crbug.com/922951 external/wpt/pointerevents/pointerevent_releasepointercapture_invalid_pointerid.html [ Skip ]
 crbug.com/922951 external/wpt/pointerevents/pointerevent_setpointercapture_disconnected.html [ Skip ]
 crbug.com/922951 external/wpt/pointerevents/pointerevent_setpointercapture_invalid_pointerid.html [ Skip ]
+crbug.com/922951 fast/canvas/OffscreenCanvas-placeholder-createImageBitmap.html [ Skip ]
+crbug.com/922951 fast/css/pseudo-hover-active-display-none.html [ Skip ]
 crbug.com/922951 fast/dom/shadow/move-marquee-crossing-treescope-crash.html [ Skip ]
 crbug.com/922951 fast/forms/number/number-input-event-composed.html [ Skip ]
 crbug.com/922951 fast/loader/detach-while-printing.html [ Skip ]
@@ -5903,10 +5922,13 @@
 crbug.com/922951 fast/scroll-snap/snaps-after-keyboard-scrolling.html [ Skip ]
 crbug.com/922951 fast/scroll-snap/snaps-for-different-key-granularity.html [ Skip ]
 crbug.com/922951 fast/text/international/inline-plaintext-relayout-with-leading-neutrals.html [ Skip ]
+crbug.com/922951 http/tests/cache/subresource-fragment-identifier.html [ Skip ]
 crbug.com/922951 http/tests/devtools/audits2/audits2-successful-run.js [ Skip ]
+crbug.com/922951 http/tests/devtools/isolated-code-cache/same-origin-test.js [ Skip ]
 crbug.com/922951 http/tests/devtools/tracing-session-id.js [ Skip ]
 crbug.com/922951 http/tests/devtools/tracing/console-timeline.js [ Skip ]
 crbug.com/922951 http/tests/devtools/tracing/timeline-network-received-data.js [ Skip ]
+crbug.com/922951 http/tests/history/back-to-post.html [ Skip ]
 crbug.com/922951 http/tests/images/feature-policy-unoptimized-images-cached-image.html [ Skip ]
 crbug.com/922951 http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Skip ]
 crbug.com/922951 http/tests/webaudio/autoplay-crossorigin.html [ Skip ]
@@ -5914,11 +5936,30 @@
 crbug.com/922951 media/controls/overflow-menu-hide-on-click-outside-stoppropagation.html [ Skip ]
 crbug.com/922951 media/controls/overflow-menu-hide-on-click-outside.html [ Skip ]
 crbug.com/922951 media/controls/overflow-menu-toggle-class-for-animation.html [ Skip ]
+crbug.com/922951 scrollbars/resize-scales-with-dpi-150.html [ Skip ]
 crbug.com/922951 svg/animations/dynamic-modify-transform-without-baseval.html [ Skip ]
 crbug.com/922951 svg/animations/target-condition-crash.html [ Skip ]
 crbug.com/922951 svg/as-object/embedded-svg-immediate-offsetWidth-query.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-auto-auto-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-auto-auto-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-auto-fixed-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-auto-fixed-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-auto-percentage-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-auto-percentage-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-auto-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-auto-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-fixed-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-fixed-no-intrinsic-ratio.html [ Skip ]
 crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-percentage-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-fixed-percentage-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-percentage-auto-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-percentage-auto-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-percentage-fixed-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-percentage-fixed-no-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-percentage-percentage-intrinsic-ratio.html [ Skip ]
+crbug.com/922951 svg/as-object/sizing/svg-in-object-placeholder-percentage-percentage-no-intrinsic-ratio.html [ Skip ]
 crbug.com/922951 svg/custom/object-sizing-zero-intrinsic-width-height.html [ Skip ]
+crbug.com/922951 virtual/gpu/fast/canvas/OffscreenCanvas-MessageChannel-transfer.html [ Skip ]
 crbug.com/922951 virtual/gpu/fast/canvas/OffscreenCanvas-placeholder-createImageBitmap.html [ Skip ]
 crbug.com/922951 virtual/gpu/fast/canvas/color-space/canvas-drawImage-offscreenCanvas.html [ Skip ]
 crbug.com/922951 virtual/mouseevent_fractional/fast/events/synthetic-events/tap-on-scaled-screen.html [ Skip ]
@@ -5929,6 +5970,7 @@
 crbug.com/922951 virtual/new-remote-playback-pipeline/media/controls/overflow-menu-toggle-class-for-animation.html [ Skip ]
 crbug.com/922951 virtual/outofblink-cors-ns/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Skip ]
 crbug.com/922951 virtual/outofblink-cors/http/tests/security/offscreencanvas-placeholder-read-blocked-no-crossorigin.html [ Skip ]
+crbug.com/922951 virtual/prefer_compositing_to_lcd_text/scrollbars/resize-scales-with-dpi-150.html [ Skip ]
 crbug.com/922951 virtual/scalefactor150/fast/events/synthetic-events/tap-on-scaled-screen.html [ Skip ]
 crbug.com/922951 virtual/stable/compositing/overflow/overflow-scroll-with-local-background.html [ Skip ]
 crbug.com/922951 virtual/threaded/animations/direction-and-fill/fill-mode-iteration-count-non-integer.html [ Skip ]
@@ -5971,6 +6013,19 @@
 # WebXR feature policy feature name in Chrome doesn't match spec.
 crbug.com/924670 external/wpt/webvr/webvr-supported-by-feature-policy.html [ Failure ]
 
+# Feature Policy for Sandbox currently causes these tests to fail.
+crbug.com/926245 virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/sandbox-empty-subframe.html [ Failure ]
+crbug.com/926245 virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/sandbox-empty.html [ Failure ]
+crbug.com/926245 virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/sandbox-in-http-header-control.html [ Failure ]
+crbug.com/926245 virtual/feature-policy-for-sandbox/http/tests/security/contentSecurityPolicy/sandbox-invalid-header.html [ Failure ]
+crbug.com/926247 virtual/feature-policy-for-sandbox/http/tests/navigation/new-window-sandboxed-iframe.html [ Failure ]
+crbug.com/926247 virtual/feature-policy-for-sandbox/http/tests/security/popup-allowed-by-sandbox-is-sandboxed.html [ Failure ]
+crbug.com/926248 virtual/feature-policy-for-sandbox/mhtml/page_with_css_and_js_ie.mht [ Failure ]
+crbug.com/926248 virtual/feature-policy-for-sandbox/mhtml/page_with_css_and_js_unmht.mht [ Failure ]
+crbug.com/926248 virtual/feature-policy-for-sandbox/mhtml/page_with_javascript.mht [ Failure ]
+crbug.com/926248 virtual/feature-policy-for-sandbox/mhtml/transfer_encoding_8bit.mht [ Failure ]
+crbug.com/926249 virtual/feature-policy-for-sandbox/http/tests/security/sandbox-iframe-blocks-modals.php [ Failure ]
+
 # Sheriff 2019-01-25
 crbug.com/925325 [ Mac ] storage/indexeddb/index-population.html [ Pass Failure ]
 
@@ -5985,3 +6040,5 @@
 crbug.com/v8/8319 external/wpt/wasm/jsapi/module/customSections.any.worker.html [ Pass Failure ]
 
 crbug.com/927296 virtual/wasm-site-isolated-code-cache/http/tests/devtools/wasm-isolated-code-cache/wasm-cache-test.js [ Pass Failure ]
+
+crbug.com/927334 [ Win ] fast/dom/document-contentType-data-uri.html [ Pass Failure ]
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index 77893f7..d3e1231 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -881,12 +881,12 @@
   },
   {
     "prefix": "not-site-per-process",
-    "base": "external/wpt/wasm/serialization/window-domain-success.sub.html",
+    "base": "external/wpt/wasm/serialization/module/window-domain-success.sub.html",
     "args": ["--disable-site-isolation-trials"]
   },
   {
     "prefix": "not-site-per-process",
-    "base": "external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html",
+    "base": "external/wpt/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html",
     "args": ["--disable-site-isolation-trials"]
   },
   {
@@ -1013,5 +1013,20 @@
     "prefix": "bidi-caret-affinity",
     "base": "editing/selection/modify_move",
     "args": ["--enable-blink-features=BidiCaretAffinity,EditingNG"]
+  },
+  {
+    "prefix": "feature-policy-for-sandbox",
+    "base": "http/tests/navigation",
+    "args": ["--enable-blink-features=FeaturePolicyForSandbox"]
+  },
+  {
+    "prefix": "feature-policy-for-sandbox",
+    "base": "http/tests/security",
+    "args": ["--enable-blink-features=FeaturePolicyForSandbox"]
+  },
+  {
+    "prefix": "feature-policy-for-sandbox",
+    "base": "mhtml",
+    "args": ["--enable-blink-features=FeaturePolicyForSandbox"]
   }
 ]
diff --git a/third_party/blink/web_tests/animations/interpolation/transform-interpolation-001.html b/third_party/blink/web_tests/animations/interpolation/transform-interpolation-001.html
index 6bfcfbf..c77204f 100644
--- a/third_party/blink/web_tests/animations/interpolation/transform-interpolation-001.html
+++ b/third_party/blink/web_tests/animations/interpolation/transform-interpolation-001.html
@@ -117,6 +117,32 @@
   {at: 1, is: 'rotateZ(900deg)'},
   {at: 2, is: 'rotateZ(1800deg)'},
 ]);
+// Interpolation is about a common axis if either endpoint has a rotation angle
+// of zero.
+assertInterpolation({
+  property: 'transform',
+  from: 'rotateX(0deg)',
+  to: 'rotateY(900deg)'
+}, [
+  {at: -1, is: 'rotateY(-900deg)'},
+  {at: 0, is: 'rotateY(0deg)'},
+  {at: 0.25, is: 'rotateY(225deg)'},
+  {at: 0.75, is: 'rotateY(675deg)'},
+  {at: 1, is: 'rotateY(900deg)'},
+  {at: 2, is: 'rotateY(1800deg)'},
+]);
+assertInterpolation({
+  property: 'transform',
+  from: 'rotateY(900deg)',
+  to: 'rotateZ(0deg)'
+}, [
+  {at: -1, is: 'rotateY(1800deg)'},
+  {at: 0, is: 'rotateY(900deg)'},
+  {at: 0.25, is: 'rotateY(675deg)'},
+  {at: 0.75, is: 'rotateY(225deg)'},
+  {at: 1, is: 'rotateY(0deg)'},
+  {at: 2, is: 'rotateY(-900deg)'},
+]);
 assertInterpolation({
   property: 'transform',
   from: 'rotate3d(7, 8, 9, 100deg)',
@@ -153,6 +179,19 @@
   {at: 1, is: 'rotate3d(0, 1, 0, 450deg)'},
   {at: 2, is: 'rotate3d(0, 1, 0, 900deg)'},
 ]);
+// Rotation is about a common axis if the axes are colinear.
+assertInterpolation({
+  property: 'transform',
+  from: 'rotate3d(0, 1, 0, 0deg)',
+  to: 'rotate3d(0, 2, 0, 450deg)'
+}, [
+  {at: -1, is: 'rotate3d(0, 1, 0, -450deg)'},
+  {at: 0, is: 'rotate3d(0, 1, 0, 0deg)'},
+  {at: 0.25, is: 'rotate3d(0, 1, 0, 112.5deg)'},
+  {at: 0.75, is: 'rotate3d(0, 1, 0, 337.5deg)'},
+  {at: 1, is: 'rotate3d(0, 1, 0, 450deg)'},
+  {at: 2, is: 'rotate3d(0, 1, 0, 900deg)'},
+]);
 assertInterpolation({
   property: 'transform',
   from: 'rotate3d(1, 1, 0, 90deg)',
diff --git a/third_party/blink/web_tests/css-parser/variables-invalid-functions.html b/third_party/blink/web_tests/css-parser/variables-invalid-functions.html
deleted file mode 100644
index 5f9ed691..0000000
--- a/third_party/blink/web_tests/css-parser/variables-invalid-functions.html
+++ /dev/null
@@ -1,94 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <link rel="help" href="https://drafts.csswg.org/css-env-1/">
-    <title>Test env() and var() throw syntax errors if used with invalid functions</title>
-    <script src="../resources/testharness.js"></script>
-    <script src="../resources/testharnessreport.js"></script>
-    <style>
-      div {
-        --a: 0px;
-        margin-left: 10px;
-      }
-    </style>
-  </head>
-  <body>
-    <script>
-    // This value is expected if the syntax is valid.
-    const workingValue = "0px";
-
-    // This value is expected if the syntax is invalid.
-    const pageDefaultValue = "10px";
-
-    // This value is expected if the calc() syntax is valid.
-    const workingCalcValue = "20px";
-
-    const testCases = [
-      { style: "", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: env(safe-area-inset-left)", expectedPropertyValue: workingValue },
-
-      // min and max() are not supported.
-      { style: "margin-left: min(env(safe-area-inset-left))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(safe-area-inset-left), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(safe-area-inset-left, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(safe-area-inset-left, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(test))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(test), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(test, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(env(test, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(safe-area-inset-left))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(safe-area-inset-left), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(safe-area-inset-left, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(safe-area-inset-left, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(test))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(test), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(test, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(env(test, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-
-      // calc() should work.
-      { style: "margin-left: calc(env(safe-area-inset-left) + 20px)", expectedPropertyValue: workingCalcValue },
-      { style: "margin-left: calc(env(safe-area-inset-left, 0px) + 20px)", expectedPropertyValue: workingCalcValue },
-      { style: "margin-left: calc(env(safe-area-inset-left, 0) + 20px)", expectedPropertyValue: workingCalcValue },
-      { style: "margin-left: calc(env(test) + 20px)", expectedPropertyValue: "0px" },
-      { style: "margin-left: calc(env(test, 1px) + 20px)", expectedPropertyValue: "21px" },
-
-      // min and max() are not supported.
-      { style: "margin-left: min(var(--a))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--a), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--a, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--a, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--b))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--b), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--b, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: min(var(--b, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--a))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--a), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--a, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--a, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--b))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--b), 1px)", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--b, 1px))", expectedPropertyValue: pageDefaultValue },
-      { style: "margin-left: max(var(--b, 1px), 1px)", expectedPropertyValue: pageDefaultValue },
-
-      // calc() should work.
-      { style: "margin-left: calc(var(--a) + 20px)", expectedPropertyValue: workingCalcValue },
-      { style: "margin-left: calc(var(--a, 0px) + 20px)", expectedPropertyValue: workingCalcValue },
-      { style: "margin-left: calc(var(--a, 0) + 20px)", expectedPropertyValue: workingCalcValue },
-      { style: "margin-left: calc(var(--b) + 20px)", expectedPropertyValue: "0px" },
-      { style: "margin-left: calc(var(--b, 1px) + 20px)", expectedPropertyValue: "21px" },
-    ];
-
-    testCases.forEach((testcase) => {
-      test(() => {
-        const elem = document.createElement("div");
-        const style = window.getComputedStyle(elem);
-
-        document.body.appendChild(elem);
-        elem.style.cssText = testcase.style;
-
-        assert_equals(style.getPropertyValue("margin-left"), testcase.expectedPropertyValue);
-      }, testcase.style + " " + testcase.expectedPropertyValue);
-    });
-    </script>
-  </body>
-</html>
diff --git a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
index 936e25b..7aa93ae 100644
--- a/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
+++ b/third_party/blink/web_tests/external/WPT_BASE_MANIFEST_5.json
@@ -172949,6 +172949,11 @@
      {}
     ]
    ],
+   "infrastructure/metadata/infrastructure/reftest/size.html.ini": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/metadata/infrastructure/server/context.any.js.ini": [
     [
      {}
@@ -172959,6 +172964,11 @@
      {}
     ]
    ],
+   "infrastructure/metadata/infrastructure/server/order-of-metas.window.js.ini": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/metadata/infrastructure/server/secure-context.https.any.js.ini": [
     [
      {}
@@ -172994,6 +173004,11 @@
      {}
     ]
    ],
+   "infrastructure/metadata/infrastructure/testdriver/file_upload.sub.html.ini": [
+    [
+     {}
+    ]
+   ],
    "infrastructure/reftest-wait-ref.html": [
     [
      {}
@@ -263824,6 +263839,12 @@
      {}
     ]
    ],
+   "portals/portals-activate-no-browsing-context.html": [
+    [
+     "/portals/portals-activate-no-browsing-context.html",
+     {}
+    ]
+   ],
    "portals/portals-host-null.html": [
     [
      "/portals/portals-host-null.html",
@@ -381774,7 +381795,7 @@
    "testharness"
   ],
   "css/geometry/interfaces-expected.txt": [
-   "9b73550b32d8e2a205c845edb04bbdfb42a32145",
+   "d9727cdf97c2590fb795b7c9f107f446f2e1b2de",
    "support"
   ],
   "css/geometry/interfaces.html": [
@@ -417774,11 +417795,11 @@
    "testharness"
   ],
   "infrastructure/metadata/infrastructure/assumptions/allowed-to-play.html.ini": [
-   "c9fbabede6d695cd3795e4390b35da0454ffcc34",
+   "f9be6a64bdfd7ca033fda2a9cd1bdb105836df20",
    "support"
   ],
   "infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini": [
-   "b7303c645fce287ae4e2bb34dbb1999e6e27e147",
+   "5bb1bf554ccbc73a605f4364c59b91aa59a7ee87",
    "support"
   ],
   "infrastructure/metadata/infrastructure/expected-fail/failing-test.html.ini": [
@@ -417813,20 +417834,28 @@
    "0d1b9bade95d7b101c8dbf51547ffbaec2260c27",
    "support"
   ],
+  "infrastructure/metadata/infrastructure/reftest/size.html.ini": [
+   "1c6cd9eff891b315fcbe4bfcded859259d50e509",
+   "support"
+  ],
   "infrastructure/metadata/infrastructure/server/context.any.js.ini": [
-   "f985548b2f0987992331415d1661a2fdfb48ed99",
+   "ff57a138029ca49e7e63d9dd27e25288709c61f3",
    "support"
   ],
   "infrastructure/metadata/infrastructure/server/order-of-metas.any.js.ini": [
-   "b29646288b23b7e1603bcae7927e61c9d9adc241",
+   "e4a96de0e8f60fbbd48676deb59bd8ba85f99133",
+   "support"
+  ],
+  "infrastructure/metadata/infrastructure/server/order-of-metas.window.js.ini": [
+   "20e4cbad939b92111c17bdf0d5fdb58d379cf8df",
    "support"
   ],
   "infrastructure/metadata/infrastructure/server/secure-context.https.any.js.ini": [
-   "272935af1ecad7ea785a716948aec8f0a4139330",
+   "f483bca13f6b372774e1494c75455cd075ba0313",
    "support"
   ],
   "infrastructure/metadata/infrastructure/server/title.any.js.ini": [
-   "6852b8033d8ebf2b15786af1286a1ced98c793fb",
+   "cbae6b15410e13433c4a9fadd8c2a8cc5fbc4fdc",
    "support"
   ],
   "infrastructure/metadata/infrastructure/testdriver/actions/elementPosition.html.ini": [
@@ -417849,6 +417878,10 @@
    "7ceec9f531bfe3ede763a41726590f3effdbac29",
    "support"
   ],
+  "infrastructure/metadata/infrastructure/testdriver/file_upload.sub.html.ini": [
+   "42c98117c58bbe7b9201d1883e0c1649898b5570",
+   "support"
+  ],
   "infrastructure/reftest-wait-ref.html": [
    "6772c2c460e79993979688ddf46e2045b14f7d71",
    "support"
@@ -420322,7 +420355,7 @@
    "testharness"
   ],
   "mediacapture-streams/idlharness.https.window-expected.txt": [
-   "b0be4a5647ace311ffc0e90d4c880069079524c6",
+   "2720255440e5406c8144308f6b93c579c39a266b",
    "support"
   ],
   "mediacapture-streams/idlharness.https.window.js": [
@@ -430950,7 +430983,7 @@
    "testharness"
   ],
   "performance-timeline/idlharness.any-expected.txt": [
-   "0b65efbd20ed67e8fa96eb7400a1ef5c47ddc350",
+   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
    "support"
   ],
   "performance-timeline/idlharness.any.js": [
@@ -430958,15 +430991,15 @@
    "testharness"
   ],
   "performance-timeline/idlharness.any.serviceworker-expected.txt": [
-   "0b65efbd20ed67e8fa96eb7400a1ef5c47ddc350",
+   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
    "support"
   ],
   "performance-timeline/idlharness.any.sharedworker-expected.txt": [
-   "0b65efbd20ed67e8fa96eb7400a1ef5c47ddc350",
+   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
    "support"
   ],
   "performance-timeline/idlharness.any.worker-expected.txt": [
-   "0b65efbd20ed67e8fa96eb7400a1ef5c47ddc350",
+   "7d03cc1c92e43122223b32d6e059dbbef3ad7df8",
    "support"
   ],
   "performance-timeline/performanceentry-tojson.any.js": [
@@ -431573,6 +431606,10 @@
    "ac1505d2a5b2fe1df083eae75893483e025a2ad7",
    "testharness"
   ],
+  "portals/portals-activate-no-browsing-context.html": [
+   "9a822e9238a938e48c5c1bc6d76669d48962ee65",
+   "testharness"
+  ],
   "portals/portals-host-null.html": [
    "e0f1d63743c54c687d62f86abe278873fa823430",
    "testharness"
@@ -441870,7 +441907,7 @@
    "support"
   ],
   "resources/idlharness.js": [
-   "8b7a1e2d98ea3167793f6996d56211ebb2cf0f41",
+   "b765d1f6199fc8c13dcd74b534bbae9e729f225b",
    "support"
   ],
   "resources/idlharness.js.headers": [
@@ -454082,11 +454119,11 @@
    "testharness"
   ],
   "webrtc/RTCRtpParameters-encodings-expected.txt": [
-   "3ce1fdd644b7bf4b094d0cb965399a592cb2c200",
+   "ce30e4583cded08a3e54fc2228f98e9bd54ce232",
    "support"
   ],
   "webrtc/RTCRtpParameters-encodings.html": [
-   "b8ae4e4c187ce0ccea70421a248c900aae844af6",
+   "23cb76919c021f43d804fa9e213b16f79c341755",
    "testharness"
   ],
   "webrtc/RTCRtpParameters-headerExtensions.html": [
@@ -454262,7 +454299,7 @@
    "testharness"
   ],
   "webrtc/idlharness.https.window-expected.txt": [
-   "12913bcb81ebcc8dcccedfe5573b9937700111a2",
+   "a55770611b4412d120939d29495bf5fabe01b68d",
    "support"
   ],
   "webrtc/idlharness.https.window.js": [
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/child-move-reveals-parent-background-ref.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/child-move-reveals-parent-background-ref.html
new file mode 100644
index 0000000..1032496
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/child-move-reveals-parent-background-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+  #parent {
+    width: 150px;
+    height: 150px;
+    background-color: green;
+  }
+</style>
+<p>There should be a green square below.</p>
+<div id="parent"></div>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-backgrounds/child-move-reveals-parent-background.html b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/child-move-reveals-parent-background.html
new file mode 100644
index 0000000..e369eccd
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-backgrounds/child-move-reveals-parent-background.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Child moves and reveals previously obscured background of the parent</title>
+<link rel="match" href="child-move-reveals-parent-background-ref.html">
+<link rel="help" href="https://drafts.csswg.org/css-backgrounds">
+<script src="/common/reftest-wait.js"></script>
+<style>
+  #parent {
+    width: 150px;
+    height: 150px;
+    background-color: green;
+  }
+  #child {
+    width: 150px;
+    height: 150px;
+    background-color: white;
+    position: relative;
+  }
+</style>
+<p>There should be a green square below.</p>
+<div id="parent">
+  <div id="child"></div>
+</div>
+<script>
+  requestAnimationFrame(() => requestAnimationFrame(() => {
+    child.style.left = '150px';
+    takeScreenshot();
+  }));
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-env/supports-script.tentative.html b/third_party/blink/web_tests/external/wpt/css/css-env/supports-script.tentative.html
index ec9b6d0d..7ab4db2 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-env/supports-script.tentative.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-env/supports-script.tentative.html
@@ -11,6 +11,8 @@
     test(() => {
       assert_true(CSS.supports("background: env(test)"));
       assert_true(CSS.supports("background", "env(test)"));
+      assert_true(CSS.supports("background", "env(test, 10px)"));
+      assert_true(CSS.supports("background", "foobar(env(test))"));
       assert_false(CSS.supports("background", "env()"));
       assert_false(CSS.supports("background", "env(test,)"));
     });
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-loose.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-loose.html
index cf7c5a44..def6adf 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-loose.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-loose.html
@@ -89,17 +89,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and loose');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and loose');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-normal.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-normal.html
index 7c701c85..bc204f5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-normal.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-normal.html
@@ -89,17 +89,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and normal');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and normal');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-strict.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-strict.html
index 9fe1804..cdb8398 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-strict.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cj-strict.html
@@ -89,17 +89,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and strict');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and strict');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose.html
index 6120e62..c932cb4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose.html
@@ -48,17 +48,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and loose');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and loose');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-normal.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-normal.html
index 1e1d4bb..7816fb5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-normal.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-normal.html
@@ -48,17 +48,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and normal');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and normal');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-strict.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-strict.html
index ddf3c2c..833e45e 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-strict.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-strict.html
@@ -48,17 +48,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and strict');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and strict');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-loose.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-loose.html
index 647bfc8..fbb13ec 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-loose.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-loose.html
@@ -42,17 +42,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and loose');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and loose');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-normal.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-normal.html
index 5e22458..dc22d616 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-normal.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-normal.html
@@ -42,17 +42,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and normal');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and normal');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-strict.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-strict.html
index 3db8bb8..cd5d5aa93 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-strict.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-hyphens-strict.html
@@ -42,17 +42,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and strict');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and strict');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-loose.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-loose.html
index 09c5758..235d381 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-loose.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-loose.html
@@ -43,17 +43,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and loose');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and loose');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-normal.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-normal.html
index 8bf3aa9..7bdedd5 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-normal.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-normal.html
@@ -43,17 +43,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and normal');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and normal');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-strict.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-strict.html
index 3c14e18..58c05bb 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-strict.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-in-strict.html
@@ -43,17 +43,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and strict');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and strict');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-loose.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-loose.html
index dbf77a0f..49f51f4 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-loose.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-loose.html
@@ -44,17 +44,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and loose');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and loose');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-normal.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-normal.html
index ecdcdc3..55f54ea8 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-normal.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-normal.html
@@ -44,17 +44,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and normal');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and normal');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-strict.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-strict.html
index 3b2a2ee..1c2cd9a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-strict.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-iteration-strict.html
@@ -44,17 +44,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and strict');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and strict');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose.html
index 795cb02..4d79a12 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose.html
@@ -48,17 +48,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文文<br/>&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may appear at line start if ja and loose');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may appear at line start if ja and loose');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-normal.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-normal.html
index 7c591eb..eb3003c 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-normal.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-normal.html
@@ -48,17 +48,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and normal');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and normal');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-strict.html b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-strict.html
index c41ea73..08e16f26 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-strict.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-strict.html
@@ -48,17 +48,26 @@
 	 '<div class="ref" id="ref'+i+'" lang="ja">文文文文文<br/>文&#x'+hex+';字<span id="refSpan'+i+'">字</span></div>' +
 	 '</div>'
 	}
+function spansNearEnough(counter) {
+  return Math.abs( document.getElementById('testSpan'+counter).getBoundingClientRect().left
+           - document.getElementById('refSpan'+counter).getBoundingClientRect().left ) < 1;
+}
+
 document.querySelector('body').innerHTML = out
-// hide successful tests
-for (i=0;i<lines.length;i++) {
-    if (document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft) document.getElementById('test'+i).parentNode.style.display = 'none'
-    }
-// run the test framework
- for (i=0;i<lines.length;i++) {
-   test(function() {
-        assert_true(document.getElementById('testSpan'+i).offsetLeft === document.getElementById('refSpan'+i).offsetLeft);
-        }, lines[i]+' may NOT appear at line start if ja and strict');
-   }
+setup({explicit_done: true});
+
+document.fonts.ready.then(validate);
+
+function validate() {
+  for (i=0;i<lines.length;i++) {
+    test(function() {
+      assert_true(spansNearEnough(i));
+    }, lines[i]+' may NOT appear at line start if ja and strict');
+    // Hide successful tests.
+    if (spansNearEnough(i)) document.getElementById('test'+i).parentNode.style.display = 'none'
+  }
+  done();
+}
 </script>
 <!--Notes:
 The test creates a box with room for 6 characters, causing wrapping to occur either between the 6th and the 7th character, or before the 6th if the breaks after the 6th or before the 7th are prohibited.
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation-expected.txt
index 7d67a78..55e208a 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation-expected.txt
@@ -14,9 +14,13 @@
 PASS Matches on primitives: "translateX(100px) scaleX(3) translate(500px) scale(2)" and "translateY(200px) scale(5) translateX(100px) scaleY(3)" are valid transform values
 PASS Matches on primitives: Animation between "translateX(100px) scaleX(3) translate(500px) scale(2)" and "translateY(200px) scale(5) translateX(100px) scaleY(3)" at progress 0.25
 PASS Match on rotation vector: "rotateX(90deg) translateX(100px)" and "rotate3d(50, 0, 0, 180deg) translateY(200px)" are valid transform values
-FAIL Match on rotation vector: Animation between "rotateX(90deg) translateX(100px)" and "rotate3d(50, 0, 0, 180deg) translateY(200px)" at progress 0.25 assert_equals: expected "matrix3d(1, 0, 0, 0, 0, -0.382683, 0.92388, 0, 0, -0.92388, -0.382683, 0, 75, -19.1342, 46.194, 1)" but got "matrix3d(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 75, -50, 0, 1)"
+PASS Match on rotation vector: Animation between "rotateX(90deg) translateX(100px)" and "rotate3d(50, 0, 0, 180deg) translateY(200px)" at progress 0.25
 PASS Match on rotation due to 0deg angle: "rotateX(90deg) translateX(100px)" and "rotateY(0deg) translateY(200px)" are valid transform values
-FAIL Match on rotation due to 0deg angle: Animation between "rotateX(90deg) translateX(100px)" and "rotateY(0deg) translateY(200px)" at progress 0.25 assert_equals: expected "matrix3d(1, 0, 0, 0, 0, 0.382683, 0.92388, 0, 0, -0.92388, 0.382683, 0, 75, 19.1342, 46.194, 1)" but got "matrix(1, 0, 0, 1, 75, 50)"
+PASS Match on rotation due to 0deg angle: Animation between "rotateX(90deg) translateX(100px)" and "rotateY(0deg) translateY(200px)" at progress 0.25
+PASS Match on rotation using collinear rotation axes: "rotate3d(1, 1, 1, -60deg) translateX(100px)" and "rotate3d(2, 2, 2, 60deg) translateY(200px)" are valid transform values
+PASS Match on rotation using collinear rotation axes: Animation between "rotate3d(1, 1, 1, -60deg) translateX(100px)" and "rotate3d(2, 2, 2, 60deg) translateY(200px)" at progress 0.25
+PASS Match on rotation with spherical interpolation: "rotate3d(1, 0, 0, 360deg) translateX(100px)" and "rotate3d(0, 1, 0, -720deg) translateY(200px)" are valid transform values
+PASS Match on rotation with spherical interpolation: Animation between "rotate3d(1, 0, 0, 360deg) translateX(100px)" and "rotate3d(0, 1, 0, -720deg) translateY(200px)" at progress 0.25
 PASS Common prefix: "rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" are valid transform values
 PASS Common prefix: Animation between "rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" at progress 0.25
 PASS Complete mismatch (except length): "scale(2) rotate(0deg) translate(100px)" and "rotate(720deg) scale(2) translate(200px)" are valid transform values
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation.html b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation.html
index af221e5f..4755279 100644
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation.html
+++ b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/list-interpolation.html
@@ -109,6 +109,24 @@
 test_interpolation(
   {
     property: 'transform',
+    from: 'rotate3d(1, 1, 1, -60deg) translateX(100px)',
+    to: 'rotate3d(2, 2, 2, 60deg) translateY(200px)',
+  }, [{ at: 0.25, expect: 'rotate3d(1, 1, 1, -30deg) translate(75px, 50px)' }],
+  'Match on rotation using collinear rotation axes'
+);
+
+test_interpolation(
+  {
+    property: 'transform',
+    from: 'rotate3d(1, 0, 0, 360deg) translateX(100px)',
+    to: 'rotate3d(0, 1, 0, -720deg) translateY(200px)',
+  }, [{ at: 0.25, expect: 'rotate3d(0, 0, 1, 0deg) translate(75px, 50px)' }],
+  'Match on rotation with spherical interpolation'
+);
+
+test_interpolation(
+  {
+    property: 'transform',
     from: 'rotate(0deg) translate(100px)',
     to: 'rotate(720deg) scale(2) translate(200px)',
   },
diff --git a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/matrix-interpolation-expected.txt b/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/matrix-interpolation-expected.txt
deleted file mode 100644
index eb3376c..0000000
--- a/third_party/blink/web_tests/external/wpt/css/css-transforms/animation/matrix-interpolation-expected.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-This is a testharness.js-based test.
-PASS "rotateY(360deg)" and "rotateX(720deg)" are valid transform values
-FAIL Animation between "rotateY(360deg)" and "rotateX(720deg)" at progress 0.5 assert_equals: expected "matrix(1, 0, 0, 1, 0, 0)" but got "matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)"
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/external/wpt/css/cssom/CSS.html b/third_party/blink/web_tests/external/wpt/css/cssom/CSS.html
index 7d558f0..ba048c5 100644
--- a/third_party/blink/web_tests/external/wpt/css/cssom/CSS.html
+++ b/third_party/blink/web_tests/external/wpt/css/cssom/CSS.html
@@ -26,6 +26,8 @@
         assert_equals(CSS.supports("color: red"), true, "CSS.supports: Single-argument form allows for declarations without enclosing parentheses");
         assert_equals(CSS.supports("(color: red) and (color: blue)"), true, "CSS.supports: Complex conditions allowed");
         assert_equals(CSS.supports("not (foobar)"), true, "CSS.supports: general_enclosed still parses");
+        assert_equals(CSS.supports("color: something-pointless var(--foo)"), true, "Variable references always parse");
+        assert_equals(CSS.supports("color: something-pointless(var(--foo))"), true, "Variable references in an unknown function always parse");
     }, "CSS.supports, one argument form");
     test(function () {
         // https://drafts.csswg.org/css-conditional/#dom-css-supports
diff --git a/third_party/blink/web_tests/external/wpt/css/geometry/interfaces-expected.txt b/third_party/blink/web_tests/external/wpt/css/geometry/interfaces-expected.txt
index 9b73550..d9727cd 100644
--- a/third_party/blink/web_tests/external/wpt/css/geometry/interfaces-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/css/geometry/interfaces-expected.txt
@@ -25,7 +25,7 @@
 PASS DOMPointReadOnly interface: new DOMPointReadOnly() must inherit property "matrixTransform(DOMMatrixInit)" with the proper type
 PASS DOMPointReadOnly interface: calling matrixTransform(DOMMatrixInit) on new DOMPointReadOnly() with too few arguments must throw TypeError
 PASS DOMPointReadOnly interface: new DOMPointReadOnly() must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of DOMPointReadOnly
+PASS DOMPointReadOnly interface: default toJSON operation on new DOMPointReadOnly()
 PASS DOMPoint interface: existence and properties of interface object
 PASS DOMPoint interface object length
 PASS DOMPoint interface object name
@@ -55,7 +55,7 @@
 PASS DOMPointReadOnly interface: new DOMPoint() must inherit property "matrixTransform(DOMMatrixInit)" with the proper type
 PASS DOMPointReadOnly interface: calling matrixTransform(DOMMatrixInit) on new DOMPoint() with too few arguments must throw TypeError
 PASS DOMPointReadOnly interface: new DOMPoint() must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of DOMPoint
+PASS DOMPointReadOnly interface: default toJSON operation on new DOMPoint()
 PASS DOMRectReadOnly interface: existence and properties of interface object
 PASS DOMRectReadOnly interface object length
 PASS DOMRectReadOnly interface object name
@@ -85,7 +85,7 @@
 PASS DOMRectReadOnly interface: new DOMRectReadOnly() must inherit property "bottom" with the proper type
 PASS DOMRectReadOnly interface: new DOMRectReadOnly() must inherit property "left" with the proper type
 PASS DOMRectReadOnly interface: new DOMRectReadOnly() must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of DOMRectReadOnly
+PASS DOMRectReadOnly interface: default toJSON operation on new DOMRectReadOnly()
 PASS DOMRect interface: existence and properties of interface object
 PASS DOMRect interface object length
 PASS DOMRect interface object name
@@ -117,7 +117,7 @@
 PASS DOMRectReadOnly interface: new DOMRect() must inherit property "bottom" with the proper type
 PASS DOMRectReadOnly interface: new DOMRect() must inherit property "left" with the proper type
 PASS DOMRectReadOnly interface: new DOMRect() must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of DOMRect
+PASS DOMRectReadOnly interface: default toJSON operation on new DOMRect()
 PASS DOMRectList interface: existence and properties of interface object
 PASS DOMRectList interface object length
 PASS DOMRectList interface object name
@@ -157,7 +157,7 @@
 PASS DOMQuad interface: new DOMQuad() must inherit property "p4" with the proper type
 PASS DOMQuad interface: new DOMQuad() must inherit property "getBounds()" with the proper type
 PASS DOMQuad interface: new DOMQuad() must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of DOMQuad
+PASS DOMQuad interface: default toJSON operation on new DOMQuad()
 PASS DOMMatrixReadOnly interface: existence and properties of interface object
 PASS DOMMatrixReadOnly interface object length
 PASS DOMMatrixReadOnly interface object name
@@ -269,7 +269,7 @@
 PASS DOMMatrixReadOnly interface: new DOMMatrixReadOnly() must inherit property "toFloat32Array()" with the proper type
 PASS DOMMatrixReadOnly interface: new DOMMatrixReadOnly() must inherit property "toFloat64Array()" with the proper type
 PASS DOMMatrixReadOnly interface: new DOMMatrixReadOnly() must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of DOMMatrixReadOnly
+PASS DOMMatrixReadOnly interface: default toJSON operation on new DOMMatrixReadOnly()
 PASS DOMMatrixReadOnly must be primary interface of DOMMatrixReadOnly.fromMatrix({is2D: false})
 PASS Stringification of DOMMatrixReadOnly.fromMatrix({is2D: false})
 PASS DOMMatrix interface: existence and properties of interface object
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html
new file mode 100644
index 0000000..ea7e496
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-error-events.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link id=link rel=stylesheet id=style_test
+      onload="t.unreached_func('Sheet should fail to load')">
+<script>
+  var t = async_test("Check if the <link>'s error event fires for each style " +
+                     "sheet it fails to load");
+
+  link.onerror = t.step_func(() => {
+    link.onerror = t.step_func_done(() => {});
+    link.href = 'nonexistent.css?second';
+  });
+
+  link.href = 'nonexistent.css?first';
+</script>
+
+</head>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html
new file mode 100644
index 0000000..24cd5ba
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/html/semantics/document-metadata/the-link-element/link-multiple-load-events.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<link rel="author" title="Dom Farolino" href="mailto:dom@chromium.org">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#the-link-element">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<link id=link rel=stylesheet id=style_test
+      onerror="t.unreached_func('Sheet should load successfully')">
+<script>
+  var t = async_test("Check if the <link>'s load event fires for each style " +
+                     "sheet it loads");
+
+  link.onload = t.step_func(() => {
+    link.onload = t.step_func_done(() => {});
+    link.href = 'style.css?second';
+  });
+
+  link.href = 'style.css?first';
+</script>
+
+</head>
+</html>
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/allowed-to-play.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/allowed-to-play.html.ini
index c9fbabe..f9be6a6 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/allowed-to-play.html.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/allowed-to-play.html.ini
@@ -1,8 +1,10 @@
 [allowed-to-play.html]
   expected:
+    if product == "edge_webdriver": TIMEOUT
     if product == "safari": ERROR # https://bugs.webkit.org/show_bug.cgi?id=190775
 
 
   [<audio> autoplay]
     expected:
+      if product == "edge_webdriver": TIMEOUT
       if product == "safari": FAIL # https://bugs.webkit.org/show_bug.cgi?id=190775
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini
index b7303c6..5bb1bf5 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/assumptions/html-elements.html.ini
@@ -1,4 +1,9 @@
 [html-elements.html]
+  [(pre-req for comparison tests) all CSS short-hand supported]
+    expected:
+      if product == "edge_webdriver": FAIL
+
+
   [Compare CSS span definitions (only valid if pre-reqs pass)]
     expected:
       if product == "safari": FAIL # https://webkit.org/show_bug.cgi?id=187052
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/reftest/size.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/reftest/size.html.ini
new file mode 100644
index 0000000..1c6cd9e
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/reftest/size.html.ini
@@ -0,0 +1,4 @@
+[size.html]
+  type: reftest
+  expected:
+    if product == "edge_webdriver": FAIL # https://github.com/web-platform-tests/wpt/issues/15159
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/context.any.js.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/context.any.js.ini
index f985548..ff57a13 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/context.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/context.any.js.ini
@@ -1,4 +1,5 @@
 [context.any.sharedworker.html]
   [context]
     expected:
+      if product == "edge_webdriver": FAIL
       if product == "safari": FAIL # https://bugs.webkit.org/show_bug.cgi?id=149850
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.any.js.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.any.js.ini
index b296462..e4a96de 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.any.js.ini
@@ -1,4 +1,27 @@
+[order-of-metas.any.worker.html]
+  expected:
+    if product == "edge_webdriver": TIMEOUT
+
+
+[order-of-metas.any.html]
+  [<meta name=timeout> exists]
+    expected:
+      if product == "edge_webdriver": FAIL
+
+
 [order-of-metas.any.sharedworker.html]
+  [foo\n]
+    expected:
+      if product == "edge_webdriver": FAIL
+
+
   [foo]
     expected:
+      if product == "edge_webdriver": FAIL
       if product == "safari": FAIL # https://bugs.webkit.org/show_bug.cgi?id=149850
+
+
+[order-of-metas.window.html]
+  [<meta name=timeout> exists]
+    expected:
+      if product == "edge_webdriver": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.window.js.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.window.js.ini
new file mode 100644
index 0000000..20e4cbad
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/order-of-metas.window.js.ini
@@ -0,0 +1,4 @@
+[order-of-metas.window.html]
+  [<meta name=timeout> exists]
+    expected:
+      if product == "edge_webdriver": FAIL
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/secure-context.https.any.js.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/secure-context.https.any.js.ini
index 272935a..f483bca 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/secure-context.https.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/secure-context.https.any.js.ini
@@ -1,4 +1,5 @@
 [secure-context.https.any.sharedworker.html]
   [secure-context]
     expected:
+      if product == "edge_webdriver": FAIL
       if product == "safari": FAIL # https://bugs.webkit.org/show_bug.cgi?id=149850
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/title.any.js.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/title.any.js.ini
index 6852b8033..cbae6b15 100644
--- a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/title.any.js.ini
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/server/title.any.js.ini
@@ -1,4 +1,20 @@
+[title.any.html]
+  [foobar\n]
+    expected:
+      if product == "edge_webdriver": FAIL
+
+
 [title.any.sharedworker.html]
+  [foobar\n]
+    expected:
+      if product == "edge_webdriver": FAIL
+
+
   [foobar]
     expected:
       if product == "safari": FAIL # https://bugs.webkit.org/show_bug.cgi?id=149850
+
+
+[title.any.worker.html]
+  expected:
+    if product == "edge_webdriver": TIMEOUT
diff --git a/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/file_upload.sub.html.ini b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/file_upload.sub.html.ini
new file mode 100644
index 0000000..42c9811
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/infrastructure/metadata/infrastructure/testdriver/file_upload.sub.html.ini
@@ -0,0 +1,3 @@
+[file_upload.sub.html]
+  expected:
+    if product == "edge_webdriver": ERROR
diff --git a/third_party/blink/web_tests/external/wpt/mediacapture-streams/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/mediacapture-streams/idlharness.https.window-expected.txt
index b0be4a5..2720255 100644
--- a/third_party/blink/web_tests/external/wpt/mediacapture-streams/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/mediacapture-streams/idlharness.https.window-expected.txt
@@ -157,7 +157,7 @@
 PASS MediaDeviceInfo interface: [object InputDeviceInfo] must inherit property "label" with the proper type
 PASS MediaDeviceInfo interface: [object InputDeviceInfo] must inherit property "groupId" with the proper type
 PASS MediaDeviceInfo interface: [object InputDeviceInfo] must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of InputDeviceInfo
+PASS MediaDeviceInfo interface: default toJSON operation on [object InputDeviceInfo]
 PASS Navigator interface: attribute mediaDevices
 PASS Navigator interface: operation getUserMedia(MediaStreamConstraints, NavigatorUserMediaSuccessCallback, NavigatorUserMediaErrorCallback)
 PASS Navigator interface: navigator must inherit property "mediaDevices" with the proper type
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
index 0b65efbd..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any-expected.txt
@@ -70,6 +70,6 @@
 PASS PerformanceEntry interface: mark must inherit property "startTime" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "duration" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of PerformanceMark
+PASS PerformanceEntry interface: default toJSON operation on mark
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
index 0b65efbd..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.serviceworker-expected.txt
@@ -70,6 +70,6 @@
 PASS PerformanceEntry interface: mark must inherit property "startTime" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "duration" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of PerformanceMark
+PASS PerformanceEntry interface: default toJSON operation on mark
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
index 0b65efbd..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.sharedworker-expected.txt
@@ -70,6 +70,6 @@
 PASS PerformanceEntry interface: mark must inherit property "startTime" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "duration" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of PerformanceMark
+PASS PerformanceEntry interface: default toJSON operation on mark
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
index 0b65efbd..7d03cc1 100644
--- a/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/performance-timeline/idlharness.any.worker-expected.txt
@@ -70,6 +70,6 @@
 PASS PerformanceEntry interface: mark must inherit property "startTime" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "duration" with the proper type
 PASS PerformanceEntry interface: mark must inherit property "toJSON()" with the proper type
-PASS Test default toJSON operation of PerformanceMark
+PASS PerformanceEntry interface: default toJSON operation on mark
 Harness: the test ran to completion.
 
diff --git a/third_party/blink/web_tests/external/wpt/resources/idlharness.js b/third_party/blink/web_tests/external/wpt/resources/idlharness.js
index 8b7a1e2..b765d1f 100644
--- a/third_party/blink/web_tests/external/wpt/resources/idlharness.js
+++ b/third_party/blink/web_tests/external/wpt/resources/idlharness.js
@@ -2309,7 +2309,7 @@
     }
 }
 
-IdlInterface.prototype.test_to_json_operation = function(memberHolderObject, member) {
+IdlInterface.prototype.test_to_json_operation = function(desc, memberHolderObject, member) {
     var instanceName = memberHolderObject && memberHolderObject.constructor.name
         || member.name + " object";
     if (member.has_extended_attribute("Default")) {
@@ -2325,12 +2325,12 @@
                 this.array.assert_type_is(json[k], type);
                 delete json[k];
             }, this);
-        }.bind(this), "Test default toJSON operation of " + instanceName);
+        }.bind(this), this.name + " interface: default toJSON operation on " + desc);
     } else {
         subsetTestByKey(this.name, test, function() {
             assert_true(this.array.is_json_type(member.idlType), JSON.stringify(member.idlType) + " is not an appropriate return value for the toJSON operation of " + instanceName);
             this.array.assert_type_is(memberHolderObject.toJSON(), member.idlType);
-        }.bind(this), "Test toJSON operation of " + instanceName);
+        }.bind(this), this.name + " interface: toJSON operation on " + desc);
     }
 };
 
@@ -2722,7 +2722,7 @@
         }
 
         if (member.is_to_json_regular_operation()) {
-            this.test_to_json_operation(obj, member);
+            this.test_to_json_operation(desc, obj, member);
         }
     }
 };
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/broadcastchannel-success-and-failure.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/broadcastchannel-success-and-failure.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/broadcastchannel-success-and-failure.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/broadcastchannel-success-and-failure.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/broadcastchannel-success.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/broadcastchannel-success.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/broadcastchannel-success.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/broadcastchannel-success.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/identity-not-preserved.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/identity-not-preserved.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/identity-not-preserved.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/identity-not-preserved.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/incrementer.wasm b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/incrementer.wasm
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/incrementer.wasm
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/incrementer.wasm
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/nested-worker-success-sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success-sharedworker-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/nested-worker-success-sharedworker-expected.txt
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success-sharedworker-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/nested-worker-success.any.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success.any.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/nested-worker-success.any.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success.any.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/nested-worker-success.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success.any.sharedworker-expected.txt
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/nested-worker-success.any.sharedworker-expected.txt
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/nested-worker-success.any.sharedworker-expected.txt
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/no-transferring.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/no-transferring.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/no-transferring.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/no-transferring.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/blank.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/blank.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/blank.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/blank.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/broadcastchannel-iframe.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/broadcastchannel-iframe.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/broadcastchannel-iframe.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/broadcastchannel-iframe.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/broadcastchannel-sharedworker.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/broadcastchannel-sharedworker.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/broadcastchannel-sharedworker.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/broadcastchannel-sharedworker.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/broadcastchannel-worker.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/broadcastchannel-worker.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/broadcastchannel-worker.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/broadcastchannel-worker.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/create-empty-wasm-module.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/create-empty-wasm-module.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/create-empty-wasm-module.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/create-empty-wasm-module.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/echo-iframe.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/echo-iframe.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/echo-iframe.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/echo-iframe.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/echo-worker.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/echo-worker.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/echo-worker.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/echo-worker.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-iframe-domain.sub.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-iframe-domain.sub.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-iframe.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-iframe.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-iframe.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-iframe.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-popup.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-popup.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-popup.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-popup.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-worker-with-channel.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-worker-with-channel.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-worker-with-channel.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-worker-with-channel.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-worker.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-worker.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer-worker.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer-worker.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer.wasm b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer.wasm
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/incrementer.wasm
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/incrementer.wasm
Binary files differ
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-1.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-1.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-1.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-1.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-2.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-2.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-2.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-2.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-3.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-3.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-3.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-3.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-4-incrementer.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-4-incrementer.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/nested-iframe-4-incrementer.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/nested-iframe-4-incrementer.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/serviceworker-failure.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/serviceworker-failure.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/serviceworker-failure.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/serviceworker-failure.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/sharedworker-failure.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/sharedworker-failure.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/sharedworker-failure.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/sharedworker-failure.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/resources/test-incrementer.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/test-incrementer.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/resources/test-incrementer.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/resources/test-incrementer.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/serialization-via-history.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/serialization-via-history.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/serialization-via-history.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/serialization-via-history.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/serialization-via-idb.any.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/serialization-via-idb.any.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/serialization-via-idb.any.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/serialization-via-idb.any.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/serialization-via-notifications-api.any.js b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/serialization-via-notifications-api.any.js
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/serialization-via-notifications-api.any.js
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/serialization-via-notifications-api.any.js
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-domain-success.sub.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-domain-success.sub.html
similarity index 93%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/window-domain-success.sub.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-domain-success.sub.html
index 51d4c5c..07360d82 100644
--- a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-domain-success.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-domain-success.sub.html
@@ -18,7 +18,7 @@
     iframe.onload = t.step_func(() => {
       resolve(testSharingViaIncrementerScript(t, window, "window", iframe.contentWindow, "iframe", "*"));
     });
-    iframe.src = "//{{domains[www1]}}:{{location[port]}}/wasm/serialization/resources/incrementer-iframe-domain.sub.html";
+    iframe.src = "//{{domains[www1]}}:{{location[port]}}/wasm/serialization/module/resources/incrementer-iframe-domain.sub.html";
     document.body.appendChild(iframe);
   });
 }, "postMessaging to a same-origin-domain (but not same-origin) iframe allows them to instantiate");
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-messagechannel-success.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-messagechannel-success.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/window-messagechannel-success.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-messagechannel-success.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-serviceworker-failure.https.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-serviceworker-failure.https.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/window-serviceworker-failure.https.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-serviceworker-failure.https.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-sharedworker-failure.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-sharedworker-failure.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/window-sharedworker-failure.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-sharedworker-failure.html
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html
similarity index 94%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html
index 070cf0a..a615547 100644
--- a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html
+++ b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html
@@ -18,7 +18,7 @@
     iframe.onload = t.step_func(() => {
       resolve(testSharingViaIncrementerScript(t, window, "window", iframe.contentWindow, "iframe", "*"));
     });
-    iframe.src = "//{{domains[www1]}}:{{location[port]}}/wasm/serialization/resources/incrementer-iframe.html";
+    iframe.src = "//{{domains[www1]}}:{{location[port]}}/wasm/serialization/module/resources/incrementer-iframe.html";
     document.body.appendChild(iframe);
   });
 }, "postMessaging to a not same-origin-domain, but similar origin, iframe allows them to instantiate");
diff --git a/third_party/blink/web_tests/external/wpt/wasm/serialization/window-simple-success.html b/third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-simple-success.html
similarity index 100%
rename from third_party/blink/web_tests/external/wpt/wasm/serialization/window-simple-success.html
rename to third_party/blink/web_tests/external/wpt/wasm/serialization/module/window-simple-success.html
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt b/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt
index 12913bcb8..a557706 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/webrtc/idlharness.https.window-expected.txt
@@ -140,7 +140,7 @@
 PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "type" with the proper type
 FAIL RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "sdp" with the proper type assert_equals: expected "string" but got "object"
 PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "toJSON()" with the proper type
-FAIL Test default toJSON operation of RTCSessionDescription assert_equals: expected "string" but got "object"
+FAIL RTCSessionDescription interface: default toJSON operation on new RTCSessionDescription({ type: 'offer' }) assert_equals: expected "string" but got "object"
 PASS RTCIceCandidate interface: existence and properties of interface object
 PASS RTCIceCandidate interface object length
 PASS RTCIceCandidate interface object name
@@ -179,7 +179,7 @@
 FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "relatedPort" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
 FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "usernameFragment" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
 FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "toJSON()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
-FAIL Test toJSON operation of toJSON object Cannot read property 'toJSON' of undefined
+FAIL RTCIceCandidate interface: toJSON operation on new RTCIceCandidate({ sdpMid: 1 }) Cannot read property 'toJSON' of undefined
 PASS RTCPeerConnectionIceEvent interface: existence and properties of interface object
 PASS RTCPeerConnectionIceEvent interface object length
 PASS RTCPeerConnectionIceEvent interface object name
diff --git a/third_party/blink/web_tests/platform/win7/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose-expected.txt b/third_party/blink/web_tests/platform/win7/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose-expected.txt
deleted file mode 100644
index 0009750..0000000
--- a/third_party/blink/web_tests/platform/win7/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-cpm-loose-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-PASS 30FB  KATAKANA MIDDLE DOT may appear at line start if ja and loose
-PASS FF1A  FULLWIDTH COLON may appear at line start if ja and loose
-PASS FF1B  FULLWIDTH SEMICOLON may appear at line start if ja and loose
-FAIL FF65  HALFWIDTH KATAKANA MIDDLE DOT may appear at line start if ja and loose assert_true: expected true got false
-PASS 203C  DOUBLE EXCLAMATION MARK may appear at line start if ja and loose
-FAIL 2047  DOUBLE QUESTION MARK may appear at line start if ja and loose assert_true: expected true got false
-FAIL 2048  QUESTION EXCLAMATION MARK may appear at line start if ja and loose assert_true: expected true got false
-PASS 2049  EXCLAMATION QUESTION MARK may appear at line start if ja and loose
-PASS FF01  FULLWIDTH EXCLAMATION MARK may appear at line start if ja and loose
-PASS FF1F  FULLWIDTH QUESTION MARK may appear at line start if ja and loose
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/platform/win7/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose-expected.txt b/third_party/blink/web_tests/platform/win7/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose-expected.txt
deleted file mode 100644
index f3d9c85..0000000
--- a/third_party/blink/web_tests/platform/win7/external/wpt/css/css-text/i18n/ja/css-text-line-break-ja-po-loose-expected.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-This is a testharness.js-based test.
-PASS 00B0  DEGREE SIGN may appear at line start if ja and loose
-PASS 2030  PER MILLE SIGN may appear at line start if ja and loose
-PASS 2032  PRIME may appear at line start if ja and loose
-PASS 2033  DOUBLE PRIME may appear at line start if ja and loose
-PASS 2035  REVERSED PRIME may appear at line start if ja and loose
-FAIL 2103  DEGREE CELSIUS may appear at line start if ja and loose assert_true: expected true got false
-FAIL 2109  DEGREE FAHRENHEIT may appear at line start if ja and loose assert_true: expected true got false
-PASS FE6A  SMALL PERCENT SIGN may appear at line start if ja and loose
-PASS FF05  FULLWIDTH PERCENT SIGN may appear at line start if ja and loose
-PASS FFE0  FULLWIDTH CENT SIGN may appear at line start if ja and loose
-Harness: the test ran to completion.
-
diff --git a/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/http/tests/navigation/README.txt b/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/http/tests/navigation/README.txt
new file mode 100644
index 0000000..2ff197f5
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/http/tests/navigation/README.txt
@@ -0,0 +1,2 @@
+This directory is for running navigation tests with the FeaturePolicyForSandbox
+runtime flag.
diff --git a/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/http/tests/security/README.txt b/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/http/tests/security/README.txt
new file mode 100644
index 0000000..b71a013
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/http/tests/security/README.txt
@@ -0,0 +1,2 @@
+This directory is for running tests with the FeaturePolicyForSandbox
+runtime flag.
diff --git a/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/mhtml/README.txt b/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/mhtml/README.txt
new file mode 100644
index 0000000..b71a013
--- /dev/null
+++ b/third_party/blink/web_tests/virtual/feature-policy-for-sandbox/mhtml/README.txt
@@ -0,0 +1,2 @@
+This directory is for running tests with the FeaturePolicyForSandbox
+runtime flag.
diff --git a/third_party/blink/web_tests/virtual/not-site-per-process/README.md b/third_party/blink/web_tests/virtual/not-site-per-process/README.md
index 6fc09489..2c70610a 100644
--- a/third_party/blink/web_tests/virtual/not-site-per-process/README.md
+++ b/third_party/blink/web_tests/virtual/not-site-per-process/README.md
@@ -42,8 +42,8 @@
 - external/wpt/html/browsers/the-windowproxy-exotic-object/windowproxy-prototype-setting-same-origin-domain.sub.html
 - external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-domain-success.sub.html
 - external/wpt/html/infrastructure/safe-passing-of-structured-data/shared-array-buffers/window-similar-but-cross-origin-success.sub.html
-- external/wpt/wasm/serialization/window-domain-success.sub.html
-- external/wpt/wasm/serialization/window-similar-but-cross-origin-success.sub.html
+- external/wpt/wasm/serialization/module/window-domain-success.sub.html
+- external/wpt/wasm/serialization/module/window-similar-but-cross-origin-success.sub.html
 
 ## Tests that need further investigation and/or decisions
 
diff --git a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt
index b4d15e8..7416e921 100644
--- a/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt
+++ b/third_party/blink/web_tests/virtual/webrtc-wpt-plan-b/external/wpt/webrtc/idlharness.https.window-expected.txt
@@ -140,7 +140,7 @@
 PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "type" with the proper type
 FAIL RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "sdp" with the proper type assert_equals: expected "string" but got "object"
 PASS RTCSessionDescription interface: new RTCSessionDescription({ type: 'offer' }) must inherit property "toJSON()" with the proper type
-FAIL Test default toJSON operation of RTCSessionDescription assert_equals: expected "string" but got "object"
+FAIL RTCSessionDescription interface: default toJSON operation on new RTCSessionDescription({ type: 'offer' }) assert_equals: expected "string" but got "object"
 PASS RTCIceCandidate interface: existence and properties of interface object
 PASS RTCIceCandidate interface object length
 PASS RTCIceCandidate interface object name
@@ -179,7 +179,7 @@
 FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "relatedPort" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
 FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "usernameFragment" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
 FAIL RTCIceCandidate interface: new RTCIceCandidate({ sdpMid: 1 }) must inherit property "toJSON()" with the proper type assert_equals: Unexpected exception when evaluating object expected null but got object "TypeMismatchError: Failed to construct 'RTCIceCandidate': The 'candidate' property is not a string, or is empty."
-FAIL Test toJSON operation of toJSON object Cannot read property 'toJSON' of undefined
+FAIL RTCIceCandidate interface: toJSON operation on new RTCIceCandidate({ sdpMid: 1 }) Cannot read property 'toJSON' of undefined
 PASS RTCPeerConnectionIceEvent interface: existence and properties of interface object
 PASS RTCPeerConnectionIceEvent interface object length
 PASS RTCPeerConnectionIceEvent interface object name
diff --git a/third_party/zlib/BUILD.gn b/third_party/zlib/BUILD.gn
index 51d477a..9f7897a 100644
--- a/third_party/zlib/BUILD.gn
+++ b/third_party/zlib/BUILD.gn
@@ -120,6 +120,9 @@
 
   if (use_arm_neon_optimizations) {
     defines = [ "INFLATE_CHUNK_SIMD_NEON" ]
+    if (current_cpu == "arm64") {
+      defines += [ "INFLATE_CHUNK_READ_64LE" ]
+    }
   }
 }
 
diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml
index 32390d5e..cb09cb6 100644
--- a/tools/metrics/histograms/enums.xml
+++ b/tools/metrics/histograms/enums.xml
@@ -1210,6 +1210,12 @@
   </int>
 </enum>
 
+<enum name="AndroidSmsFcmMessageType">
+  <int value="0" label="Start"/>
+  <int value="1" label="Resume"/>
+  <int value="2" label="Stop"/>
+</enum>
+
 <enum name="AndroidTabCloseUndoToastEvent">
   <int value="0" label="Undo Shown (Cold)"/>
   <int value="1" label="Undo Shown (Warm)"/>
@@ -51257,6 +51263,22 @@
   <int value="3" label="Disabled"/>
 </enum>
 
+<enum name="SyncGetUpdatesOrigin">
+  <summary>
+    Equivalent to proto SyncEnums.GetUpdatesOrigin: represents a reason for a
+    client to issue a GetUpdates request to the sync server.
+  </summary>
+  <int value="0" label="UNKNOWN_ORIGIN"/>
+  <int value="4" label="PERIODIC"/>
+  <int value="7" label="NEWLY_SUPPORTED_DATATYPE"/>
+  <int value="8" label="MIGRATION"/>
+  <int value="9" label="NEW_CLIENT"/>
+  <int value="10" label="RECONFIGURATION"/>
+  <int value="12" label="GU_TRIGGER"/>
+  <int value="13" label="RETRY"/>
+  <int value="14" label="PROGRAMMATIC"/>
+</enum>
+
 <enum name="SyncGetUpdatesToken">
   <int value="0" label="New token"/>
   <int value="1" label="Same token"/>
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ca0e5bf..2132b39 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -3040,6 +3040,36 @@
   </summary>
 </histogram>
 
+<histogram name="AndroidSms.FcmMessageDispatchFailure"
+    enum="AndroidSmsFcmMessageType">
+  <owner>azeemarshad@chromium.org</owner>
+  <summary>
+    Records message types for which all retry attempts failed when dispatching
+    to Android Messages for Web Service-Worker. This is recorded when using FCM
+    web push for background notificaitons.
+  </summary>
+</histogram>
+
+<histogram name="AndroidSms.FcmMessageDispatchRetry"
+    enum="AndroidSmsFcmMessageType">
+  <owner>azeemarshad@chromium.org</owner>
+  <summary>
+    Records message types for which a retry was attempted when dispatching to
+    Android Messages for Web Service-Worker. This is recorded when using FCM web
+    push for background notificaitons.
+  </summary>
+</histogram>
+
+<histogram name="AndroidSms.FcmMessageDispatchSuccess"
+    enum="AndroidSmsFcmMessageType">
+  <owner>azeemarshad@chromium.org</owner>
+  <summary>
+    Records message types for which dispatching to Android Messages for Web
+    Service-Worker succeeded. This is recorded when using FCM web push for
+    background notificaitons.
+  </summary>
+</histogram>
+
 <histogram name="AndroidSms.MultiDeviceFeatureState"
     enum="MultiDevice_FeatureState">
   <owner>jlklein@chromium.org</owner>
@@ -113733,6 +113763,7 @@
 </histogram>
 
 <histogram base="true" name="Sync.E2ELatency" units="ms" expires_after="M75">
+  <owner>mastiz@chromium.org</owner>
   <owner>melandory@chromium.org</owner>
   <summary>
     Recorded for a subset of users (Finch-controlled) per sync reflection, that
@@ -114413,6 +114444,18 @@
   </summary>
 </histogram>
 
+<histogram name="Sync.NonReflectionUpdateFreshnessPossiblySkewed" units="ms">
+  <owner>mastiz@chromium.org</owner>
+  <owner>melandory@chromium.org</owner>
+  <summary>
+    Freshness of the sync data per received sync entity update, excluding
+    reflections. The time represents the clock difference from the model being
+    modified (usually on another device) until the change is processing by this
+    instance of the browser. Beware of potential clock skew due to two clients
+    being involved.
+  </summary>
+</histogram>
+
 <histogram name="Sync.PageRevisitBookmarksDuration" units="ms">
   <obsolete>
     Deprecated in M66.
@@ -114675,6 +114718,15 @@
   </summary>
 </histogram>
 
+<histogram name="Sync.PostedGetUpdatesOrigin" enum="SyncGetUpdatesOrigin">
+  <owner>mastiz@chromium.org</owner>
+  <owner>jkrcal@chromium.org</owner>
+  <summary>
+    Emitted per network GetUpdates() request to the sync server, it represents
+    the reason for sending such GetUpdates() request.
+  </summary>
+</histogram>
+
 <histogram name="Sync.PreferenceAssociationTime" units="ms">
   <obsolete>
     Deprecated in M53.
@@ -139915,6 +139967,14 @@
   <affected-histogram name="PasswordManager.TotalAccountsHiRes.WithScheme"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="PasswordSecurityOrigin" separator=".">
+  <suffix name="InsecureOrigin"
+      label="The recording took place on an insecure origin."/>
+  <suffix name="SecureOrigin"
+      label="The recording took place on a secure origin."/>
+  <affected-histogram name="PasswordManager.FillingAssistance"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="PaymentRequestOutcome" separator=".">
   <suffix name="Completed" label="The Payment Request was completed"/>
   <suffix name="OtherAborted"
@@ -143195,6 +143255,7 @@
   </affected-histogram>
   <affected-histogram name="Sync.ModelTypeEntityChange3"/>
   <affected-histogram name="Sync.ModelTypeMemoryKB"/>
+  <affected-histogram name="Sync.NonReflectionUpdateFreshnessPossiblySkewed"/>
   <affected-histogram name="Sync.ReceivedDataTypeGetUpdatesResponseWithToken"/>
   <affected-histogram name="Sync.USSMigrationEntityCount"/>
 </histogram_suffixes>
@@ -143395,6 +143456,16 @@
   <affected-histogram name="Tabs.UsedInInterval.Count"/>
 </histogram_suffixes>
 
+<histogram_suffixes name="TaskSchedulerCancelledDelayedTasks" separator=".">
+  <suffix name="Browser" label="TaskScheduler for the browser process."/>
+  <suffix name="ContentChild"
+      label="TaskSchedulers for various instantiations of
+             content::ChildProcess."/>
+  <suffix name="Renderer" label="TaskSchedulers for renderer processes."/>
+  <affected-histogram name="TaskScheduler.NumCancelledDelayedTasks"/>
+  <affected-histogram name="TaskScheduler.PercentCancelledDelayedTasks"/>
+</histogram_suffixes>
+
 <histogram_suffixes name="TaskSchedulerMayBlock" separator=".">
   <obsolete>
     Deprecated 1/2018. Merged with TaskSchedulerTaskPriority into
@@ -143437,12 +143508,10 @@
   <affected-histogram name="TaskScheduler.DetachDuration"/>
   <affected-histogram name="TaskScheduler.HeartbeatLatencyMicroseconds"/>
   <affected-histogram name="TaskScheduler.NumActiveWorkers"/>
-  <affected-histogram name="TaskScheduler.NumCancelledDelayedTasks"/>
   <affected-histogram name="TaskScheduler.NumTasksBeforeDetach"/>
   <affected-histogram name="TaskScheduler.NumTasksBetweenWaits"/>
   <affected-histogram name="TaskScheduler.NumTasksRunWhileQueuing"/>
   <affected-histogram name="TaskScheduler.NumWorkers"/>
-  <affected-histogram name="TaskScheduler.PercentCancelledDelayedTasks"/>
   <affected-histogram name="TaskScheduler.TaskLatencyMicroseconds"/>
 </histogram_suffixes>
 
diff --git a/tools/perf/OWNERS b/tools/perf/OWNERS
index 21aa6c8..48cdce1 100644
--- a/tools/perf/OWNERS
+++ b/tools/perf/OWNERS
@@ -2,7 +2,6 @@
 crouleau@chromium.org
 eyaich@chromium.org
 sullivan@chromium.org
-nednguyen@google.com
 
 # For changes related to ChromeOS.
 achuith@chromium.org
diff --git a/tools/perf/benchmark.csv b/tools/perf/benchmark.csv
index f5721c6..f734db9 100644
--- a/tools/perf/benchmark.csv
+++ b/tools/perf/benchmark.csv
@@ -18,8 +18,8 @@
 blink_perf.svg,"kouhei@chromium.org, fs@opera.com",Blink>SVG,https://bit.ly/blink-perf-benchmarks,
 components_perftests,csharrison@chromium.org,,,
 dromaeo,"jbroman@chromium.org, yukishiino@chromium.org, haraken@chromium.org",Blink>Bindings,,
-dummy_benchmark.noisy_benchmark_1,nednguyen@google.com,Speed>Telemetry,,
-dummy_benchmark.stable_benchmark_1,nednguyen@google.com,Speed>Telemetry,,
+dummy_benchmark.noisy_benchmark_1,crouleau@chromium.org,Speed>Telemetry,,
+dummy_benchmark.stable_benchmark_1,crouleau@chromium.org,Speed>Telemetry,,
 gpu_perftests,"reveman@chromium.org, chrome-gpu-perf-owners@chromium.org",Internals>GPU,,
 jetstream,hablich@chromium.org,Blink>JavaScript,,
 kraken,hablich@chromium.org,Blink>JavaScript,,
diff --git a/tools/perf/benchmarks/dummy_benchmark.py b/tools/perf/benchmarks/dummy_benchmark.py
index 64d02403..8c15c56 100644
--- a/tools/perf/benchmarks/dummy_benchmark.py
+++ b/tools/perf/benchmarks/dummy_benchmark.py
@@ -39,7 +39,7 @@
   page_set = dummy_story_set.DummyStorySet
 
 
-@benchmark.Info(emails=['nednguyen@google.com'], component='Speed>Telemetry')
+@benchmark.Info(emails=['crouleau@chromium.org'], component='Speed>Telemetry')
 class DummyBenchmarkOne(_DummyBenchmark):
   """A low noise benchmark with mean=100 & std=1."""
 
@@ -51,7 +51,7 @@
     return 'dummy_benchmark.stable_benchmark_1'
 
 
-@benchmark.Info(emails=['nednguyen@google.com'], component='Speed>Telemetry')
+@benchmark.Info(emails=['crouleau@chromium.org'], component='Speed>Telemetry')
 class DummyBenchmarkTwo(_DummyBenchmark):
   """A noisy benchmark with mean=50 & std=20."""
 
diff --git a/tools/perf/contrib/blink_perf_cmdline/OWNERS b/tools/perf/contrib/blink_perf_cmdline/OWNERS
index 93a9996c..33952d7 100644
--- a/tools/perf/contrib/blink_perf_cmdline/OWNERS
+++ b/tools/perf/contrib/blink_perf_cmdline/OWNERS
@@ -1,3 +1,2 @@
 charliea@chromium.org
-nednguyen@google.com
 wangxianzhu@chromium.org
diff --git a/tools/perf/contrib/tracing/OWNERS b/tools/perf/contrib/tracing/OWNERS
index 63ef69a..6f75aa01 100644
--- a/tools/perf/contrib/tracing/OWNERS
+++ b/tools/perf/contrib/tracing/OWNERS
@@ -1,3 +1,2 @@
-nednguyen@google.com
 oysteine@chromium.org
 zhenw@chromium.org
diff --git a/tools/perf/expectations.config b/tools/perf/expectations.config
index 94f1443..a85661fa 100644
--- a/tools/perf/expectations.config
+++ b/tools/perf/expectations.config
@@ -230,6 +230,7 @@
 crbug.com/874935 [ Nexus_5 ] rendering.mobile/yahoo_news_2018 [ Skip ]
 crbug.com/910207 [ Nexus_5X ] rendering.mobile/yahoo_news_2018 [ Skip ]
 crbug.com/901526 [ All ] rendering.mobile/microsoft_fireflies [ Skip ]
+crbug.com/924400 [ Nexus6_Webview ] rendering.mobile/canvas_animation_no_clear [ Skip ]
 
 # Benchmark: rasterize_and_record_micro.top_25
 crbug.com/764543 [ All ] rasterize_and_record_micro.top_25/file://static_top_25/wikipedia.html [ Skip ]
@@ -256,6 +257,8 @@
 crbug.com/911214 [ Win_10 ] system_health.common_desktop/multitab:misc:typical24 [ Skip ]
 crbug.com/923106 [ Linux ] system_health.common_desktop/browse:media:tumblr [ Skip ]
 crbug.com/923106 [ Win ] system_health.common_desktop/browse:media:tumblr [ Skip ]
+crbug.com/927409 [ Win ] system_health.common_desktop/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
+crbug.com/927409 [ Linux ] system_health.common_desktop/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
 
 # Benchmark: system_health.common_mobile
 crbug.com/914390 [ Nexus_5 ] system_health.common_mobile/browse:chrome:newtab [ Skip ]
@@ -376,6 +379,9 @@
 crbug.com/906654 [ All ] v8.browsing_desktop-future/browse:social:twitter_infinite_scroll [ Skip ]
 crbug.com/906654 [ All ] v8.browsing_desktop-future/browse:social:twitter [ Skip ]
 crbug.com/906654 [ All ] v8.browsing_desktop-future/browse:tech:discourse_infinite_scroll [ Skip ]
+crbug.com/927409 [ Linux ] v8.browsing_desktop-future/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
+crbug.com/927409 [ Win ] v8.browsing_desktop-future/browse:social:tumblr_infinite_scroll:2018 [ Skip ]
+
 
 # Benchmark: v8.browsing_mobile
 crbug.com/714650 [ Android ] v8.browsing_mobile/browse:news:globo [ Skip ]
diff --git a/tools/perf/scripts_smoke_unittest.py b/tools/perf/scripts_smoke_unittest.py
index f2b130f..09ca602 100644
--- a/tools/perf/scripts_smoke_unittest.py
+++ b/tools/perf/scripts_smoke_unittest.py
@@ -4,8 +4,10 @@
 
 import json
 import os
+import shutil
 import subprocess
 import sys
+import tempfile
 import unittest
 
 from telemetry import decorators
@@ -60,30 +62,34 @@
   def testRunTelemetryBenchmarkAsGoogletest(self):
     options = options_for_unittests.GetCopy()
     browser_type = options.browser_type
+    tempdir = tempfile.mkdtemp()
+    benchmark = 'dummy_benchmark.stable_benchmark_1'
     return_code, stdout = self.RunPerfScript(
-        '../../testing/scripts/run_telemetry_benchmark_as_googletest.py '
-        'run_benchmark dummy_benchmark.stable_benchmark_1 --browser=%s '
+        '../../testing/scripts/run_performance_tests.py '
+        '../../tools/perf/run_benchmark '
+        '--benchmarks=dummy_benchmark.stable_benchmark_1 '
+        '--browser=%s '
         '--isolated-script-test-repeat=2 '
         '--isolated-script-test-also-run-disabled-tests '
-        '--isolated-script-test-output=output.json '
-        '--isolated-script-test-chartjson-output=chartjson_output.json '
-        '--output-format=chartjson' % browser_type)
+        '--isolated-script-test-output=%s' % (
+            browser_type,
+            os.path.join(tempdir, 'output.json')
+        ))
     self.assertEquals(return_code, 0, stdout)
     try:
-      with open('../../tools/perf/output.json') as f:
+      # By design, run_performance_tests.py does not output test results
+      # to the location passed in by --isolated-script-test-output. Instead
+      # it uses that directory of that file and puts stuff in its own
+      # subdirectories for the purposes of merging later.
+      with open(os.path.join(tempdir, benchmark, 'test_results.json')) as f:
         test_results = json.load(f)
         self.assertIsNotNone(
             test_results, 'json_test_results should be populated: ' + stdout)
         test_repeats = test_results['num_failures_by_type']['PASS']
         self.assertEqual(
             test_repeats, 2, '--isolated-script-test-repeat=2 should work.')
-      os.remove('../../tools/perf/output.json')
     except IOError as e:
       self.fail('json_test_results should be populated: ' + stdout + str(e))
-    try:
-      with open('../../tools/perf/chartjson_output.json') as f:
-        self.assertIsNotNone(
-            json.load(f), 'chartjson should be populated: ' + stdout)
-      os.remove('../../tools/perf/chartjson_output.json')
-    except IOError as e:
-      self.fail('chartjson should be populated: ' + stdout + str(e))
+    finally:
+      shutil.rmtree(tempdir)
+
diff --git a/ui/accessibility/ax_node.cc b/ui/accessibility/ax_node.cc
index 0a3e1d9..85c53f4ec 100644
--- a/ui/accessibility/ax_node.cc
+++ b/ui/accessibility/ax_node.cc
@@ -344,7 +344,7 @@
 //
 
 bool AXNode::IsTableRow() const {
-  return data().role == ax::mojom::Role::kRow;
+  return ui::IsTableRow(data().role);
 }
 
 int32_t AXNode::GetTableRowRowIndex() const {
@@ -362,12 +362,13 @@
 }
 
 #if defined(OS_MACOSX)
+
 //
 // Table column-like nodes. These nodes are only present on macOS.
 //
 
 bool AXNode::IsTableColumn() const {
-  return data().role == ax::mojom::Role::kColumn;
+  return ui::IsTableColumn(data().role);
 }
 
 int32_t AXNode::GetTableColColIndex() const {
@@ -386,6 +387,7 @@
   }
   return index;
 }
+
 #endif  // defined(OS_MACOSX)
 
 //
@@ -465,7 +467,7 @@
 int32_t AXNode::GetTableCellAriaColIndex() const {
   AXTableInfo* table_info = GetAncestorTableInfo();
   if (!table_info)
-    return -0;
+    return 0;
 
   int32_t index = GetTableCellIndex();
   if (index == -1)
@@ -477,11 +479,11 @@
 int32_t AXNode::GetTableCellAriaRowIndex() const {
   AXTableInfo* table_info = GetAncestorTableInfo();
   if (!table_info)
-    return -0;
+    return -1;
 
   int32_t index = GetTableCellIndex();
   if (index == -1)
-    return 0;
+    return -1;
 
   return table_info->cell_data_vector[index].aria_row_index;
 }
diff --git a/ui/accessibility/ax_role_properties.cc b/ui/accessibility/ax_role_properties.cc
index ef06d922..a3642dc 100644
--- a/ui/accessibility/ax_role_properties.cc
+++ b/ui/accessibility/ax_role_properties.cc
@@ -297,6 +297,17 @@
   }
 }
 
+bool IsTableColumn(ax::mojom::Role role) {
+  switch (role) {
+    case ax::mojom::Role::kColumn:
+      return true;
+    case ax::mojom::Role::kLayoutTableColumn:
+      return kExposeLayoutTableAsDataTable;
+    default:
+      return false;
+  }
+}
+
 bool IsTableHeader(ax::mojom::Role role) {
   switch (role) {
     case ax::mojom::Role::kColumnHeader:
diff --git a/ui/accessibility/ax_role_properties.h b/ui/accessibility/ax_role_properties.h
index dc056e7..d79ec98 100644
--- a/ui/accessibility/ax_role_properties.h
+++ b/ui/accessibility/ax_role_properties.h
@@ -72,13 +72,18 @@
 // Returns true if the provided role belongs to a non-interactive list.
 AX_EXPORT bool IsStaticList(const ax::mojom::Role role);
 
+// Returns true if the provided role belongs to a table or grid column, and the
+// table is not used for layout purposes.
+AX_EXPORT bool IsTableColumn(ax::mojom::Role role);
+
 // Returns true if the provided role belongs to a table header.
 AX_EXPORT bool IsTableHeader(ax::mojom::Role role);
 
 // Returns true if the provided role belongs to a table, a grid or a treegrid.
 AX_EXPORT bool IsTableLike(const ax::mojom::Role role);
 
-// Returns true if the provided role belongs to a table or grid row.
+// Returns true if the provided role belongs to a table or grid row, and the
+// table is not used for layout purposes.
 AX_EXPORT bool IsTableRow(ax::mojom::Role role);
 
 // Returns true if the provided role supports expand/collapse.
diff --git a/ui/accessibility/ax_table_info.cc b/ui/accessibility/ax_table_info.cc
index aa559a202..250b91d 100644
--- a/ui/accessibility/ax_table_info.cc
+++ b/ui/accessibility/ax_table_info.cc
@@ -236,7 +236,7 @@
 
       // Update the row count and col count for the whole table to make
       // sure they're large enough to fit this cell, including its spans.
-      // The -1 in the ARIA calcluations is because ARIA indices are 1-based,
+      // The -1 in the ARIA calculations is because ARIA indices are 1-based,
       // whereas all other indices are zero-based.
       row_count = std::max(row_count, cell_data.row_index + cell_data.row_span);
       col_count = std::max(col_count, cell_data.col_index + cell_data.col_span);
diff --git a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
index 98abda2..5f0630e7 100644
--- a/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
+++ b/ui/accessibility/platform/ax_platform_node_auralinux_unittest.cc
@@ -86,7 +86,7 @@
   AtkAttributeSet* current = attributes;
   while (current) {
     AtkAttribute* attribute = static_cast<AtkAttribute*>(current->data);
-    ASSERT_NE(0, strcmp(attribute_name, attribute->name));
+    ASSERT_NE(0, strcmp(attribute_name, attribute->name)) << attribute_name;
     current = current->next;
   }
   atk_attribute_set_free(attributes);
@@ -575,7 +575,7 @@
   g_object_unref(root_atk_object);
 }
 
-TEST_F(AXPlatformNodeAuraLinuxTest, TestAtkObjectIntAttributes) {
+TEST_F(AXPlatformNodeAuraLinuxTest, DISABLED_TestAtkObjectIntAttributes) {
   AXNodeData root_data;
   root_data.id = 1;
 
diff --git a/ui/accessibility/platform/ax_platform_node_base.cc b/ui/accessibility/platform/ax_platform_node_base.cc
index bbe67573..71666916 100644
--- a/ui/accessibility/platform/ax_platform_node_base.cc
+++ b/ui/accessibility/platform/ax_platform_node_base.cc
@@ -228,7 +228,7 @@
 int AXPlatformNodeBase::GetIntAttribute(
     ax::mojom::IntAttribute attribute) const {
   if (!delegate_)
-    return false;
+    return 0;
   return GetData().GetIntAttribute(attribute);
 }
 
@@ -426,6 +426,7 @@
   if (!table)
     return nullptr;
 
+  DCHECK(table->delegate_);
   return static_cast<AXPlatformNodeBase*>(
       table->delegate_->GetFromNodeID(table->delegate_->CellIndexToId(index)));
 }
@@ -444,58 +445,68 @@
   if (!table)
     return nullptr;
 
+  DCHECK(table->delegate_);
   int32_t cell_id = table->delegate_->GetCellId(row, column);
   return static_cast<AXPlatformNodeBase*>(
       table->delegate_->GetFromNodeID(cell_id));
 }
 
 int AXPlatformNodeBase::GetTableCellIndex() const {
-  return delegate_->GetTableCellIndex();
+  if (!delegate_)
+    return 0;
+  return int{delegate_->GetTableCellIndex()};
 }
 
 int AXPlatformNodeBase::GetTableColumn() const {
-  return GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnIndex);
+  if (!delegate_)
+    return 0;
+  return int{delegate_->GetTableCellColIndex()};
 }
 
 int AXPlatformNodeBase::GetTableColumnCount() const {
+  if (!delegate_)
+    return 0;
+
   AXPlatformNodeBase* table = GetTable();
   if (!table)
     return 0;
 
-  return table->GetIntAttribute(ax::mojom::IntAttribute::kTableColumnCount);
+  DCHECK(table->delegate_);
+  return int{table->delegate_->GetTableColCount()};
 }
 
 int AXPlatformNodeBase::GetTableColumnSpan() const {
-  if (!IsCellOrTableHeader(GetData().role))
-    return 0;
-
-  int column_span;
-  if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellColumnSpan,
-                      &column_span))
-    return column_span;
-  return 1;
+  if (!delegate_)
+    return 1;
+  return int{delegate_->GetTableCellColSpan()};
 }
 
 int AXPlatformNodeBase::GetTableRow() const {
-  return GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowIndex);
+  if (!delegate_)
+    return 0;
+  if (delegate_->IsTableRow())
+    return int{delegate_->GetTableRowRowIndex()};
+  if (delegate_->IsTableCellOrHeader())
+    return int{delegate_->GetTableCellRowIndex()};
+  return 0;
 }
 
 int AXPlatformNodeBase::GetTableRowCount() const {
+  if (!delegate_)
+    return 0;
+
   AXPlatformNodeBase* table = GetTable();
   if (!table)
     return 0;
 
-  return table->GetIntAttribute(ax::mojom::IntAttribute::kTableRowCount);
+  DCHECK(table->delegate_);
+  return int{table->delegate_->GetTableRowCount()};
 }
 
 int AXPlatformNodeBase::GetTableRowSpan() const {
-  if (!IsCellOrTableHeader(GetData().role))
-    return 0;
-
-  int row_span;
-  if (GetIntAttribute(ax::mojom::IntAttribute::kTableCellRowSpan, &row_span))
-    return row_span;
-  return 1;
+  if (!delegate_)
+    return 1;
+  return int{delegate_->GetTableCellRowSpan()};
 }
 
 bool AXPlatformNodeBase::HasCaret() {
@@ -633,6 +644,8 @@
 }
 
 void AXPlatformNodeBase::ComputeAttributes(PlatformAttributeList* attributes) {
+  DCHECK(delegate_) << "Many attributes need to be retrieved from our "
+                       "AXPlatformNodeDelegate.";
   // Expose some HTML and ARIA attributes in the IAccessible2 attributes string
   // "display", "tag", and "xml-roles" have somewhat unusual names for
   // historical reasons. Aside from that virtually every ARIA attribute
@@ -781,22 +794,31 @@
   if (GetData().role == ax::mojom::Role::kLayoutTable)
     AddAttributeToList("layout-guess", "true", attributes);
 
-  // Expose aria-colcount and aria-rowcount in a table, grid or treegrid.
-  if (IsTableLike(GetData().role)) {
+  // Expose aria-colcount and aria-rowcount in a table, grid or treegrid if they
+  // are different from its physical dimensions.
+  if (IsTableLike(GetData().role) &&
+      (delegate_->GetTableAriaRowCount() != delegate_->GetTableRowCount() ||
+       delegate_->GetTableAriaColCount() != delegate_->GetTableColCount())) {
     AddAttributeToList(ax::mojom::IntAttribute::kAriaColumnCount, "colcount",
                        attributes);
     AddAttributeToList(ax::mojom::IntAttribute::kAriaRowCount, "rowcount",
                        attributes);
   }
 
-  // Expose aria-colindex and aria-rowindex in a cell or row.
-  if (IsCellOrTableHeader(GetData().role) ||
-      GetData().role == ax::mojom::Role::kRow) {
-    if (GetData().role != ax::mojom::Role::kRow)
-      AddAttributeToList(ax::mojom::IntAttribute::kAriaCellColumnIndex,
-                         "colindex", attributes);
-    AddAttributeToList(ax::mojom::IntAttribute::kAriaCellRowIndex, "rowindex",
-                       attributes);
+  if (IsCellOrTableHeader(GetData().role) || IsTableRow(GetData().role)) {
+    // Expose aria-colindex and aria-rowindex in a cell or row only if they are
+    // different from the table's physical coordinates.
+    if (delegate_->GetTableCellAriaRowIndex() !=
+            delegate_->GetTableCellRowIndex() ||
+        delegate_->GetTableCellAriaColIndex() !=
+            delegate_->GetTableCellColIndex()) {
+      if (!IsTableRow(GetData().role)) {
+        AddAttributeToList(ax::mojom::IntAttribute::kAriaCellColumnIndex,
+                           "colindex", attributes);
+      }
+      AddAttributeToList(ax::mojom::IntAttribute::kAriaCellRowIndex, "rowindex",
+                         attributes);
+    }
 
     // Experimental: expose aria-rowtext / aria-coltext. Not standardized
     // yet, but obscure enough that it's safe to expose.
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc
index 58547edf..8303f08 100644
--- a/ui/accessibility/platform/ax_platform_node_win.cc
+++ b/ui/accessibility/platform/ax_platform_node_win.cc
@@ -2628,7 +2628,7 @@
     return E_INVALIDARG;
 
   *n_column_header_cells = 0;
-  if (GetData().role != ax::mojom::Role::kCell)
+  if (!IsCellOrTableHeader(GetData().role))
     return S_FALSE;
 
   AXPlatformNodeBase* table = GetTable();
@@ -2697,13 +2697,12 @@
     return E_INVALIDARG;
 
   *n_row_header_cells = 0;
-  if (GetData().role != ax::mojom::Role::kCell)
+  if (!IsCellOrTableHeader(GetData().role))
     return S_FALSE;
 
   AXPlatformNodeBase* table = GetTable();
-  if (!table) {
+  if (!table)
     return S_FALSE;
-  }
 
   int row = GetTableRow();
   int columns = GetTableColumnCount();
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.cc b/ui/accessibility/platform/test_ax_node_wrapper.cc
index 36e43d6..03a5f05 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.cc
+++ b/ui/accessibility/platform/test_ax_node_wrapper.cc
@@ -194,6 +194,10 @@
   node_->SetData(new_data);
 }
 
+bool TestAXNodeWrapper::IsTable() const {
+  return node_->IsTable();
+}
+
 int TestAXNodeWrapper::GetTableRowCount() const {
   return node_->GetTableRowCount();
 }
@@ -202,6 +206,18 @@
   return node_->GetTableColCount();
 }
 
+int TestAXNodeWrapper::GetTableAriaRowCount() const {
+  return node_->GetTableAriaRowCount();
+}
+
+int TestAXNodeWrapper::GetTableAriaColCount() const {
+  return node_->GetTableAriaColCount();
+}
+
+int TestAXNodeWrapper::GetTableCellCount() const {
+  return node_->GetTableCellCount();
+}
+
 const std::vector<int32_t> TestAXNodeWrapper::GetColHeaderNodeIds() const {
   std::vector<int32_t> header_ids;
   node_->GetTableCellColHeaderNodeIds(&header_ids);
@@ -228,6 +244,46 @@
   return header_ids;
 }
 
+bool TestAXNodeWrapper::IsTableRow() const {
+  return node_->IsTableRow();
+}
+
+int TestAXNodeWrapper::GetTableRowRowIndex() const {
+  return node_->GetTableRowRowIndex();
+}
+
+bool TestAXNodeWrapper::IsTableCellOrHeader() const {
+  return node_->IsTableCellOrHeader();
+}
+
+int TestAXNodeWrapper::GetTableCellIndex() const {
+  return node_->GetTableCellIndex();
+}
+
+int TestAXNodeWrapper::GetTableCellColIndex() const {
+  return node_->GetTableCellColIndex();
+}
+
+int TestAXNodeWrapper::GetTableCellRowIndex() const {
+  return node_->GetTableCellRowIndex();
+}
+
+int TestAXNodeWrapper::GetTableCellColSpan() const {
+  return node_->GetTableCellColSpan();
+}
+
+int TestAXNodeWrapper::GetTableCellRowSpan() const {
+  return node_->GetTableCellRowSpan();
+}
+
+int TestAXNodeWrapper::GetTableCellAriaColIndex() const {
+  return node_->GetTableCellAriaColIndex();
+}
+
+int TestAXNodeWrapper::GetTableCellAriaRowIndex() const {
+  return node_->GetTableCellAriaRowIndex();
+}
+
 int32_t TestAXNodeWrapper::GetCellId(int32_t row_index,
                                      int32_t col_index) const {
   ui::AXNode* cell = node_->GetTableCellFromCoords(row_index, col_index);
@@ -237,10 +293,6 @@
   return -1;
 }
 
-int32_t TestAXNodeWrapper::GetTableCellIndex() const {
-  return node_->GetTableCellIndex();
-}
-
 int32_t TestAXNodeWrapper::CellIndexToId(int32_t cell_index) const {
   ui::AXNode* cell = node_->GetTableCellFromIndex(cell_index);
   if (cell)
diff --git a/ui/accessibility/platform/test_ax_node_wrapper.h b/ui/accessibility/platform/test_ax_node_wrapper.h
index deceb33e..4c374cd 100644
--- a/ui/accessibility/platform/test_ax_node_wrapper.h
+++ b/ui/accessibility/platform/test_ax_node_wrapper.h
@@ -42,16 +42,29 @@
   gfx::NativeViewAccessible HitTestSync(int x, int y) override;
   AXPlatformNode* GetFromNodeID(int32_t id) override;
   int GetIndexInParent() const override;
+  bool IsTable() const override;
   int GetTableRowCount() const override;
   int GetTableColCount() const override;
+  int GetTableAriaColCount() const override;
+  int GetTableAriaRowCount() const override;
+  int GetTableCellCount() const override;
   const std::vector<int32_t> GetColHeaderNodeIds() const override;
   const std::vector<int32_t> GetColHeaderNodeIds(
       int32_t col_index) const override;
   const std::vector<int32_t> GetRowHeaderNodeIds() const override;
   const std::vector<int32_t> GetRowHeaderNodeIds(
       int32_t row_index) const override;
+  bool IsTableRow() const override;
+  int GetTableRowRowIndex() const override;
+  bool IsTableCellOrHeader() const override;
+  int GetTableCellIndex() const override;
+  int GetTableCellColIndex() const override;
+  int GetTableCellRowIndex() const override;
+  int GetTableCellColSpan() const override;
+  int GetTableCellRowSpan() const override;
+  int GetTableCellAriaColIndex() const override;
+  int GetTableCellAriaRowIndex() const override;
   int32_t GetCellId(int32_t row_index, int32_t col_index) const override;
-  int32_t GetTableCellIndex() const override;
   int32_t CellIndexToId(int32_t cell_index) const override;
   bool AccessibilityPerformAction(const AXActionData& data) override;
   bool ShouldIgnoreHoveredStateForTesting() override;
diff --git a/ui/base/BUILD.gn b/ui/base/BUILD.gn
index 8e04ec3..f913d76 100644
--- a/ui/base/BUILD.gn
+++ b/ui/base/BUILD.gn
@@ -848,7 +848,6 @@
   if (build_ime) {
     sources += [
       "ime/candidate_window_unittest.cc",
-      "ime/chromeos/character_composer_unittest.cc",
       "ime/chromeos/extension_ime_util_unittest.cc",
       "ime/chromeos/ime_keyboard_unittest.cc",
       "ime/chromeos/input_method_util_unittest.cc",
@@ -870,6 +869,11 @@
     if (use_x11) {
       sources += [ "ime/composition_text_util_pango_unittest.cc" ]
     }
+    if (is_chromeos || use_ozone) {
+      sources += [
+        "ime/character_composer_unittest.cc",
+      ]
+    }
   }
 
   deps = [
diff --git a/ui/base/ime/BUILD.gn b/ui/base/ime/BUILD.gn
index d686d1d..47fb630 100644
--- a/ui/base/ime/BUILD.gn
+++ b/ui/base/ime/BUILD.gn
@@ -98,8 +98,6 @@
 
   if (is_chromeos) {
     sources += [
-      "chromeos/character_composer.cc",
-      "chromeos/character_composer.h",
       "chromeos/component_extension_ime_manager.cc",
       "chromeos/component_extension_ime_manager.h",
       "chromeos/extension_ime_util.cc",
@@ -175,6 +173,14 @@
     ]
   }
 
+  if (is_chromeos || use_ozone) {
+    sources += [
+      "character_composer.cc",
+      "character_composer.h",
+    ]
+    deps += [ "//ui/events:dom_keycode_converter" ]
+  }
+
   if (!toolkit_views && !use_aura) {
     sources -= [
       "input_method_factory.cc",
@@ -190,7 +196,6 @@
       "//services/ws/public/cpp/input_devices",
       "//ui/base/ime/chromeos/public/interfaces",
       "//ui/chromeos/strings",
-      "//ui/events:dom_keycode_converter",
     ]
     sources += [
       "chromeos/ime_keyboard_mus.cc",
diff --git a/ui/base/ime/chromeos/PRESUBMIT.py b/ui/base/ime/PRESUBMIT.py
similarity index 97%
rename from ui/base/ime/chromeos/PRESUBMIT.py
rename to ui/base/ime/PRESUBMIT.py
index ed44dfb..e9f33bbe 100644
--- a/ui/base/ime/chromeos/PRESUBMIT.py
+++ b/ui/base/ime/PRESUBMIT.py
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-"""Presubmit script for ui/base/ime/chromeos
+"""Presubmit script for ui/base/ime
 
 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts
 for more details about the presubmit API built into depot_tools.
diff --git a/ui/base/ime/chromeos/character_composer.cc b/ui/base/ime/character_composer.cc
similarity index 97%
rename from ui/base/ime/chromeos/character_composer.cc
rename to ui/base/ime/character_composer.cc
index e0026255..d1ddcaa8 100644
--- a/ui/base/ime/chromeos/character_composer.cc
+++ b/ui/base/ime/character_composer.cc
@@ -2,10 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/base/ime/chromeos/character_composer.h"
+#include "ui/base/ime/character_composer.h"
 
 #include <algorithm>
 #include <iterator>
+#include <string>
 
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversion_utils.h"
@@ -18,7 +19,7 @@
 
 namespace {
 
-#include "ui/base/ime/chromeos/character_composer_data.h"
+#include "ui/base/ime/character_composer_data.h"
 
 bool CheckCharacterComposeTable(
     const ui::CharacterComposer::ComposeBuffer& compose_sequence,
@@ -58,11 +59,9 @@
 
 namespace ui {
 
-CharacterComposer::CharacterComposer() : composition_mode_(KEY_SEQUENCE_MODE) {
-}
+CharacterComposer::CharacterComposer() : composition_mode_(KEY_SEQUENCE_MODE) {}
 
-CharacterComposer::~CharacterComposer() {
-}
+CharacterComposer::~CharacterComposer() {}
 
 void CharacterComposer::Reset() {
   compose_buffer_.clear();
diff --git a/ui/base/ime/chromeos/character_composer.h b/ui/base/ime/character_composer.h
similarity index 93%
rename from ui/base/ime/chromeos/character_composer.h
rename to ui/base/ime/character_composer.h
index d1befb6c..b3845c3 100644
--- a/ui/base/ime/chromeos/character_composer.h
+++ b/ui/base/ime/character_composer.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 UI_BASE_IME_CHROMEOS_CHARACTER_COMPOSER_H_
-#define UI_BASE_IME_CHROMEOS_CHARACTER_COMPOSER_H_
+#ifndef UI_BASE_IME_CHARACTER_COMPOSER_H_
+#define UI_BASE_IME_CHARACTER_COMPOSER_H_
 
 #include <stddef.h>
 #include <stdint.h>
@@ -102,6 +102,7 @@
   virtual CheckSequenceResult CheckSequence(
       const ui::CharacterComposer::ComposeBuffer& sequence,
       uint32_t* composed_character) const = 0;
+
  private:
   DISALLOW_COPY_AND_ASSIGN(ComposeChecker);
 };
@@ -115,7 +116,7 @@
     const uint16_t* tree;
   };
 
-  TreeComposeChecker(const CompositionData& data) : data_(data) {}
+  explicit TreeComposeChecker(const CompositionData& data) : data_(data) {}
   CheckSequenceResult CheckSequence(
       const ui::CharacterComposer::ComposeBuffer& sequence,
       uint32_t* composed_character) const override;
@@ -127,4 +128,4 @@
 
 }  // namespace ui
 
-#endif  // UI_BASE_IME_CHROMEOS_CHARACTER_COMPOSER_H_
+#endif  // UI_BASE_IME_CHARACTER_COMPOSER_H_
diff --git a/ui/base/ime/chromeos/character_composer_data.h b/ui/base/ime/character_composer_data.h
similarity index 100%
rename from ui/base/ime/chromeos/character_composer_data.h
rename to ui/base/ime/character_composer_data.h
diff --git a/ui/base/ime/chromeos/character_composer_sequences.txt b/ui/base/ime/character_composer_sequences.txt
similarity index 100%
rename from ui/base/ime/chromeos/character_composer_sequences.txt
rename to ui/base/ime/character_composer_sequences.txt
diff --git a/ui/base/ime/chromeos/character_composer_unittest.cc b/ui/base/ime/character_composer_unittest.cc
similarity index 98%
rename from ui/base/ime/chromeos/character_composer_unittest.cc
rename to ui/base/ime/character_composer_unittest.cc
index 6ea0a0f..95ade2d 100644
--- a/ui/base/ime/chromeos/character_composer_unittest.cc
+++ b/ui/base/ime/character_composer_unittest.cc
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include "ui/base/ime/chromeos/character_composer.h"
+#include "ui/base/ime/character_composer.h"
 
 #include <stdint.h>
 
@@ -63,9 +63,9 @@
                             DomCode code,
                             int flags,
                             base::char16 character) const {
-    KeyEvent* event = new KeyEvent(ET_KEY_PRESSED, vkey, code, flags,
-                                   DomKey::FromCharacter(character),
-                                   EventTimeForNow());
+    KeyEvent* event =
+        new KeyEvent(ET_KEY_PRESSED, vkey, code, flags,
+                     DomKey::FromCharacter(character), EventTimeForNow());
     return event;
   }
 
@@ -233,7 +233,7 @@
 // This file is included here intentionally, instead of the top of the file,
 // because including this file at the top of the file will define a
 // global constant and contaminate the global namespace.
-#include "ui/base/ime/chromeos/character_composer_data.h"
+#include "ui/base/ime/character_composer_data.h"
   const int kTypes = 2;
 
   // Record the subtree locations and check subtable sizes.
@@ -331,8 +331,7 @@
   ExpectUnicodeKeyFiltered(VKEY_BACK, DomCode::BACKSPACE, EF_NONE, '\b');
   EXPECT_EQ(ASCIIToUTF16("u304"), character_composer_.preedit_string());
   ExpectUnicodeKeyFiltered(VKEY_2, DomCode::DIGIT2, EF_NONE, '2');
-  ExpectUnicodeKeyComposed(VKEY_RETURN, DomCode::ENTER, EF_NONE,
-                           '\r',
+  ExpectUnicodeKeyComposed(VKEY_RETURN, DomCode::ENTER, EF_NONE, '\r',
                            base::string16(1, 0x3042));
   EXPECT_EQ(ASCIIToUTF16(""), character_composer_.preedit_string());
 
@@ -462,8 +461,7 @@
   EXPECT_EQ(ASCIIToUTF16("u304"), character_composer_.preedit_string());
   ExpectUnicodeKeyFiltered(ui::VKEY_2, DomCode::DIGIT2, kControlShift, 0);
   EXPECT_EQ(ASCIIToUTF16("u3042"), character_composer_.preedit_string());
-  ExpectUnicodeKeyComposed(VKEY_RETURN, DomCode::ENTER, kControlShift,
-                           '\r',
+  ExpectUnicodeKeyComposed(VKEY_RETURN, DomCode::ENTER, kControlShift, '\r',
                            base::string16(1, 0x3042));
   EXPECT_EQ(ASCIIToUTF16(""), character_composer_.preedit_string());
 
diff --git a/ui/base/ime/chromeos/generate_character_composer_data.py b/ui/base/ime/generate_character_composer_data.py
similarity index 100%
rename from ui/base/ime/chromeos/generate_character_composer_data.py
rename to ui/base/ime/generate_character_composer_data.py
diff --git a/ui/base/ime/input_method_chromeos.h b/ui/base/ime/input_method_chromeos.h
index 52942a6..7787491 100644
--- a/ui/base/ime/input_method_chromeos.h
+++ b/ui/base/ime/input_method_chromeos.h
@@ -15,7 +15,7 @@
 #include "base/compiler_specific.h"
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
-#include "ui/base/ime/chromeos/character_composer.h"
+#include "ui/base/ime/character_composer.h"
 #include "ui/base/ime/composition_text.h"
 #include "ui/base/ime/ime_input_context_handler_interface.h"
 #include "ui/base/ime/input_method_base.h"
diff --git a/ui/base/test/ui_controls_aura.cc b/ui/base/test/ui_controls_aura.cc
index 5d0a3e7..bd6f409 100644
--- a/ui/base/test/ui_controls_aura.cc
+++ b/ui/base/test/ui_controls_aura.cc
@@ -111,6 +111,7 @@
 
 // static. declared in ui_controls.h
 void InstallUIControlsAura(UIControlsAura* instance) {
+  EnableUIControls();
   delete instance_;
   instance_ = instance;
 }
diff --git a/ui/webui/resources/cr_elements/shared_vars_css.html b/ui/webui/resources/cr_elements/shared_vars_css.html
index f117edb..fc8a660 100644
--- a/ui/webui/resources/cr_elements/shared_vars_css.html
+++ b/ui/webui/resources/cr_elements/shared_vars_css.html
@@ -184,6 +184,8 @@
       white-space: nowrap;
     }
 
+    --cr-text-element-min-width: 200px;
+
     --cr-title-text: {
         color: var(--cr-title-text-color);
         font-size: 107.6923%;  /* Go to 14px from 13px. */
@@ -200,7 +202,7 @@
       font-size: 92.31%;  /* Effectively 12px if the host default is 13px. */
       font-weight: 500;
       max-width: 330px;
-      min-width: 200px;
+      min-width: var(--cr-text-element-min-width);
       padding: 10px 8px;
     }
 
diff --git a/ui/webui/resources/html/md_select_css.html b/ui/webui/resources/html/md_select_css.html
index 9c72fee..c73f901 100644
--- a/ui/webui/resources/html/md_select_css.html
+++ b/ui/webui/resources/html/md_select_css.html
@@ -28,6 +28,7 @@
         font-size: inherit;
         line-height: inherit;
         max-width: 100%;
+        min-width: var(--cr-text-element-min-width);
         outline: none;
         padding-bottom: 6px;
         /* Ensures 3px space between text and arrow */